Repository: fetchai/agents-aea Branch: main Commit: bec49adaeba6 Files: 2006 Total size: 16.9 MB Directory structure: gitextract_i5wtg620/ ├── .dockerignore ├── .firebaserc ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ └── feature-request.yml │ ├── PULL_REQUEST_TEMPLATE/ │ │ └── release.md │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── codeql-analysis.yml │ ├── docs_pr_preview.yml │ ├── publish.yaml │ ├── upload_docker_images.yaml │ └── workflow.yml ├── .gitignore ├── .spelling ├── AUTHORS.md ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPING.md ├── HISTORY.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── SECURITY.md ├── aea/ │ ├── __init__.py │ ├── __version__.py │ ├── abstract_agent.py │ ├── aea.py │ ├── aea_builder.py │ ├── agent.py │ ├── agent_loop.py │ ├── cli/ │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── add.py │ │ ├── add_key.py │ │ ├── build.py │ │ ├── config.py │ │ ├── core.py │ │ ├── create.py │ │ ├── delete.py │ │ ├── eject.py │ │ ├── fetch.py │ │ ├── fingerprint.py │ │ ├── freeze.py │ │ ├── generate.py │ │ ├── generate_key.py │ │ ├── generate_wealth.py │ │ ├── get_address.py │ │ ├── get_multiaddress.py │ │ ├── get_public_key.py │ │ ├── get_wealth.py │ │ ├── init.py │ │ ├── install.py │ │ ├── interact.py │ │ ├── issue_certificates.py │ │ ├── launch.py │ │ ├── list.py │ │ ├── local_registry_sync.py │ │ ├── login.py │ │ ├── logout.py │ │ ├── plugin.py │ │ ├── publish.py │ │ ├── push.py │ │ ├── register.py │ │ ├── registry/ │ │ │ ├── __init__.py │ │ │ ├── add.py │ │ │ ├── fetch.py │ │ │ ├── login.py │ │ │ ├── logout.py │ │ │ ├── publish.py │ │ │ ├── push.py │ │ │ ├── registration.py │ │ │ ├── settings.py │ │ │ └── utils.py │ │ ├── remove.py │ │ ├── remove_key.py │ │ ├── reset_password.py │ │ ├── run.py │ │ ├── scaffold.py │ │ ├── search.py │ │ ├── transfer.py │ │ ├── upgrade.py │ │ └── utils/ │ │ ├── __init__.py │ │ ├── click_utils.py │ │ ├── config.py │ │ ├── constants.py │ │ ├── context.py │ │ ├── decorators.py │ │ ├── exceptions.py │ │ ├── formatting.py │ │ ├── generic.py │ │ ├── loggers.py │ │ └── package_utils.py │ ├── common.py │ ├── components/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── loader.py │ │ └── utils.py │ ├── configurations/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── constants.py │ │ ├── data_types.py │ │ ├── loader.py │ │ ├── manager.py │ │ ├── pypi.py │ │ ├── schemas/ │ │ │ ├── aea-config_schema.json │ │ │ ├── configurable_parts/ │ │ │ │ ├── base-custom_config.json │ │ │ │ ├── connection-custom_config.json │ │ │ │ ├── contract-custom_config.json │ │ │ │ ├── protocol-custom_config.json │ │ │ │ └── skill-custom_config.json │ │ │ ├── connection-config_schema.json │ │ │ ├── contract-config_schema.json │ │ │ ├── definitions.json │ │ │ ├── protocol-config_schema.json │ │ │ ├── protocol-specification_schema.json │ │ │ └── skill-config_schema.json │ │ ├── utils.py │ │ └── validation.py │ ├── connections/ │ │ ├── __init__.py │ │ ├── base.py │ │ └── scaffold/ │ │ ├── __init__.py │ │ ├── connection.py │ │ ├── connection.yaml │ │ └── readme.md │ ├── context/ │ │ ├── __init__.py │ │ └── base.py │ ├── contracts/ │ │ ├── __init__.py │ │ ├── base.py │ │ └── scaffold/ │ │ ├── __init__.py │ │ ├── contract.py │ │ └── contract.yaml │ ├── crypto/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── helpers.py │ │ ├── ledger_apis.py │ │ ├── plugin.py │ │ ├── registries/ │ │ │ ├── __init__.py │ │ │ └── base.py │ │ └── wallet.py │ ├── decision_maker/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── default.py │ │ ├── gop.py │ │ └── scaffold.py │ ├── error_handler/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── default.py │ │ └── scaffold.py │ ├── exceptions.py │ ├── helpers/ │ │ ├── __init__.py │ │ ├── acn/ │ │ │ ├── __init__.py │ │ │ ├── agent_record.py │ │ │ └── uri.py │ │ ├── async_friendly_queue.py │ │ ├── async_utils.py │ │ ├── base.py │ │ ├── constants.py │ │ ├── env_vars.py │ │ ├── exception_policy.py │ │ ├── exec_timeout.py │ │ ├── file_io.py │ │ ├── file_lock.py │ │ ├── http_requests.py │ │ ├── install_dependency.py │ │ ├── io.py │ │ ├── ipfs/ │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── pb/ │ │ │ │ ├── __init__.py │ │ │ │ ├── merkledag.proto │ │ │ │ ├── merkledag_pb2.py │ │ │ │ ├── unixfs.proto │ │ │ │ └── unixfs_pb2.py │ │ │ └── utils.py │ │ ├── logging.py │ │ ├── multiaddr/ │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── crypto.proto │ │ │ └── crypto_pb2.py │ │ ├── multiple_executor.py │ │ ├── pipe.py │ │ ├── preference_representations/ │ │ │ ├── __init__.py │ │ │ └── base.py │ │ ├── profiling.py │ │ ├── search/ │ │ │ ├── __init__.py │ │ │ ├── generic.py │ │ │ ├── models.proto │ │ │ ├── models.py │ │ │ └── models_pb2.py │ │ ├── serializers.py │ │ ├── storage/ │ │ │ ├── __init__.py │ │ │ ├── backends/ │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ ├── binaries/ │ │ │ │ │ └── README.txt │ │ │ │ └── sqlite.py │ │ │ └── generic_storage.py │ │ ├── sym_link.py │ │ ├── transaction/ │ │ │ ├── __init__.py │ │ │ └── base.py │ │ ├── win32.py │ │ └── yaml_utils.py │ ├── identity/ │ │ ├── __init__.py │ │ └── base.py │ ├── launcher.py │ ├── mail/ │ │ ├── __init__.py │ │ ├── base.proto │ │ ├── base.py │ │ └── base_pb2.py │ ├── manager/ │ │ ├── __init__.py │ │ ├── manager.py │ │ ├── project.py │ │ └── utils.py │ ├── multiplexer.py │ ├── protocols/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── dialogue/ │ │ │ ├── __init__.py │ │ │ └── base.py │ │ ├── generator/ │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── common.py │ │ │ ├── extract_specification.py │ │ │ ├── isort.cfg │ │ │ └── validate.py │ │ └── scaffold/ │ │ ├── __init__.py │ │ ├── message.py │ │ ├── protocol.yaml │ │ └── serialization.py │ ├── py.typed │ ├── registries/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── filter.py │ │ └── resources.py │ ├── runner.py │ ├── runtime.py │ ├── skills/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── behaviours.py │ │ ├── scaffold/ │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── handlers.py │ │ │ ├── my_model.py │ │ │ └── skill.yaml │ │ └── tasks.py │ └── test_tools/ │ ├── __init__.py │ ├── click_testing.py │ ├── constants.py │ ├── exceptions.py │ ├── generic.py │ ├── test_cases.py │ ├── test_contract.py │ └── test_skill.py ├── benchmark/ │ ├── Dockerfile │ ├── README.md │ ├── __init__.py │ ├── benchmark-deployment.yaml │ ├── cases/ │ │ ├── __init__.py │ │ ├── cpu_burn.py │ │ ├── helpers/ │ │ │ ├── __init__.py │ │ │ └── dummy_handler.py │ │ ├── react_multi_agents_fake_connection.py │ │ ├── react_speed_in_loop.py │ │ └── react_speed_multi_agents.py │ ├── checks/ │ │ ├── __init__.py │ │ ├── check_agent_construction_time.py │ │ ├── check_decision_maker.py │ │ ├── check_dialogues_memory_usage.py │ │ ├── check_mem_usage.py │ │ ├── check_messages_memory_usage.py │ │ ├── check_multiagent.py │ │ ├── check_multiagent_http_dialogues.py │ │ ├── check_proactive.py │ │ ├── check_reactive.py │ │ ├── data/ │ │ │ ├── 2020.09.05_17-49.txt │ │ │ ├── 2020.10.27_mem_usage_report.txt │ │ │ ├── 2020.10.30 optimized messages.txt │ │ │ ├── 2020.12.10_optimized_messages.txt │ │ │ ├── 2021.03.09_test_run.txt │ │ │ └── 2021.04.01_v1_benchmark.txt │ │ ├── run_benchmark.sh │ │ ├── run_benchmark_messages_mem.sh │ │ └── utils.py │ ├── framework/ │ │ ├── __init__.py │ │ ├── aea_test_wrapper.py │ │ ├── benchmark.py │ │ ├── cli.py │ │ ├── executor.py │ │ ├── fake_connection.py │ │ ├── func_details.py │ │ └── report_printer.py │ ├── run_from_branch.sh │ └── run_mem_check_in_cloud.sh ├── codecov.yml ├── deploy-image/ │ ├── .aea/ │ │ └── cli_config.yaml │ ├── Dockerfile │ ├── README.md │ ├── build.sh │ ├── entrypoint.sh │ └── packages/ │ └── __init__.py ├── develop-image/ │ ├── Dockerfile │ ├── README.md │ ├── docker-env.sh │ ├── k8s/ │ │ └── deployment.yaml │ ├── scripts/ │ │ ├── docker-build-img.sh │ │ └── docker-publish-img.sh │ └── skaffold.yaml ├── docs/ │ ├── 12-factor.md │ ├── Pipfile │ ├── acn-internals.md │ ├── acn.md │ ├── aea-vs-mvc.md │ ├── aeas.md │ ├── agent-oriented-development.md │ ├── agent-vs-aea.md │ ├── aggregation-demo.md │ ├── api/ │ │ ├── abstract_agent.md │ │ ├── aea.md │ │ ├── aea_builder.md │ │ ├── agent.md │ │ ├── agent_loop.md │ │ ├── common.md │ │ ├── components/ │ │ │ ├── base.md │ │ │ ├── loader.md │ │ │ └── utils.md │ │ ├── configurations/ │ │ │ ├── base.md │ │ │ ├── constants.md │ │ │ ├── data_types.md │ │ │ ├── loader.md │ │ │ ├── manager.md │ │ │ ├── pypi.md │ │ │ ├── utils.md │ │ │ └── validation.md │ │ ├── connections/ │ │ │ └── base.md │ │ ├── context/ │ │ │ └── base.md │ │ ├── contracts/ │ │ │ └── base.md │ │ ├── crypto/ │ │ │ ├── base.md │ │ │ ├── helpers.md │ │ │ ├── ledger_apis.md │ │ │ ├── plugin.md │ │ │ ├── registries/ │ │ │ │ └── base.md │ │ │ └── wallet.md │ │ ├── decision_maker/ │ │ │ ├── base.md │ │ │ ├── default.md │ │ │ └── gop.md │ │ ├── error_handler/ │ │ │ ├── base.md │ │ │ └── default.md │ │ ├── exceptions.md │ │ ├── helpers/ │ │ │ ├── acn/ │ │ │ │ ├── agent_record.md │ │ │ │ └── uri.md │ │ │ ├── async_friendly_queue.md │ │ │ ├── async_utils.md │ │ │ ├── base.md │ │ │ ├── constants.md │ │ │ ├── env_vars.md │ │ │ ├── exception_policy.md │ │ │ ├── exec_timeout.md │ │ │ ├── file_io.md │ │ │ ├── file_lock.md │ │ │ ├── http_requests.md │ │ │ ├── install_dependency.md │ │ │ ├── io.md │ │ │ ├── ipfs/ │ │ │ │ ├── base.md │ │ │ │ └── utils.md │ │ │ ├── logging.md │ │ │ ├── multiaddr/ │ │ │ │ └── base.md │ │ │ ├── multiple_executor.md │ │ │ ├── pipe.md │ │ │ ├── preference_representations/ │ │ │ │ └── base.md │ │ │ ├── profiling.md │ │ │ ├── search/ │ │ │ │ ├── generic.md │ │ │ │ └── models.md │ │ │ ├── serializers.md │ │ │ ├── storage/ │ │ │ │ ├── backends/ │ │ │ │ │ ├── base.md │ │ │ │ │ └── sqlite.md │ │ │ │ └── generic_storage.md │ │ │ ├── sym_link.md │ │ │ ├── transaction/ │ │ │ │ └── base.md │ │ │ ├── win32.md │ │ │ └── yaml_utils.md │ │ ├── identity/ │ │ │ └── base.md │ │ ├── launcher.md │ │ ├── mail/ │ │ │ └── base.md │ │ ├── manager/ │ │ │ ├── manager.md │ │ │ ├── project.md │ │ │ └── utils.md │ │ ├── multiplexer.md │ │ ├── plugins/ │ │ │ ├── aea_cli_ipfs/ │ │ │ │ ├── core.md │ │ │ │ └── ipfs_utils.md │ │ │ ├── aea_ledger_cosmos/ │ │ │ │ └── cosmos.md │ │ │ ├── aea_ledger_ethereum/ │ │ │ │ └── ethereum.md │ │ │ └── aea_ledger_fetchai/ │ │ │ ├── _cosmos.md │ │ │ └── fetchai.md │ │ ├── protocols/ │ │ │ ├── base.md │ │ │ ├── default/ │ │ │ │ ├── custom_types.md │ │ │ │ ├── dialogues.md │ │ │ │ ├── message.md │ │ │ │ └── serialization.md │ │ │ ├── dialogue/ │ │ │ │ └── base.md │ │ │ ├── generator/ │ │ │ │ ├── base.md │ │ │ │ ├── common.md │ │ │ │ ├── extract_specification.md │ │ │ │ └── validate.md │ │ │ ├── signing/ │ │ │ │ ├── custom_types.md │ │ │ │ ├── dialogues.md │ │ │ │ ├── message.md │ │ │ │ └── serialization.md │ │ │ └── state_update/ │ │ │ ├── dialogues.md │ │ │ ├── message.md │ │ │ └── serialization.md │ │ ├── registries/ │ │ │ ├── base.md │ │ │ ├── filter.md │ │ │ └── resources.md │ │ ├── runner.md │ │ ├── runtime.md │ │ ├── skills/ │ │ │ ├── base.md │ │ │ ├── behaviours.md │ │ │ └── tasks.md │ │ └── test_tools/ │ │ ├── constants.md │ │ ├── exceptions.md │ │ ├── generic.md │ │ ├── test_cases.md │ │ ├── test_contract.md │ │ └── test_skill.md │ ├── application.md │ ├── aries-cloud-agent-demo.md │ ├── build-aea-programmatically.md │ ├── build-aea-step-by-step.md │ ├── car-park-skills.md │ ├── cli-commands.md │ ├── cli-vs-programmatic-aeas.md │ ├── config.md │ ├── connect-a-frontend.md │ ├── connection.md │ ├── contract.md │ ├── core-components-1.md │ ├── core-components-2.md │ ├── core-components.md │ ├── css/ │ │ ├── admonitions.css │ │ └── my-styles.css │ ├── debug.md │ ├── decision-maker-transaction.md │ ├── decision-maker.md │ ├── defining-data-models.md │ ├── demos.md │ ├── deployment.md │ ├── design-principles.md │ ├── development-setup.md │ ├── diagram.md │ ├── ecosystem.md │ ├── erc1155-skills.md │ ├── generic-skills-step-by-step.md │ ├── generic-skills.md │ ├── generic-storage.md │ ├── glossary.md │ ├── gym-example.md │ ├── gym-skill.md │ ├── http-connection-and-skill.md │ ├── identity.md │ ├── index.md │ ├── install.md │ ├── interaction-protocol.md │ ├── known-limits.md │ ├── language-agnostic-definition.md │ ├── ledger-integration.md │ ├── limits.md │ ├── logging.md │ ├── message-routing.md │ ├── ml-skills.md │ ├── modes.md │ ├── multi-agent-manager.md │ ├── multiplexer-standalone.md │ ├── oracle-demo.md │ ├── orm-integration.md │ ├── p2p-connection.md │ ├── package-imports.md │ ├── performance-benchmark.md │ ├── por.md │ ├── prometheus.md │ ├── protocol-generator.md │ ├── protocol.md │ ├── query-language.md │ ├── questions-and-answers.md │ ├── quickstart.md │ ├── raspberry-set-up.md │ ├── runtime-cost.md │ ├── scaffolding.md │ ├── security.md │ ├── setup.md │ ├── simple-oef-usage.md │ ├── simple-oef.md │ ├── skill-guide.md │ ├── skill-testing.md │ ├── skill.md │ ├── standalone-transaction.md │ ├── step-one.md │ ├── tac-skills-contract.md │ ├── tac-skills.md │ ├── tac.md │ ├── thermometer-skills.md │ ├── trust.md │ ├── upgrading.md │ ├── wealth.md │ └── weather-skills.md ├── examples/ │ ├── __init__.py │ ├── aealite_go/ │ │ ├── README.md │ │ └── default/ │ │ ├── default.pb.go │ │ └── default.proto │ ├── gym_ex/ │ │ ├── README.md │ │ ├── gyms/ │ │ │ ├── __init__.py │ │ │ └── env.py │ │ ├── proxy/ │ │ │ ├── __init__.py │ │ │ ├── agent.py │ │ │ └── env.py │ │ ├── rl/ │ │ │ ├── __init__.py │ │ │ └── agent.py │ │ └── train.py │ ├── http_ex/ │ │ └── petstore.yaml │ ├── ml_ex/ │ │ └── model.json │ ├── protocol_specification_ex/ │ │ └── sample.yaml │ └── tac_deploy/ │ ├── .aea/ │ │ └── cli_config.yaml │ ├── Dockerfile │ ├── README.md │ ├── build.sh │ ├── entrypoint.sh │ ├── packages/ │ │ └── __init__.py │ ├── tac-deployment.yaml │ └── tac_run.sh ├── firebase.json ├── install_packages.py ├── libs/ │ └── go/ │ ├── aea_end2end/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── fipa_dummy_seller.env │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ ├── pexpect_popen.py │ │ ├── protocols/ │ │ │ ├── fipa.pb.go │ │ │ ├── fipa.proto │ │ │ └── fipa.yaml │ │ ├── run_buyer.sh │ │ ├── run_seller.sh │ │ └── test_fipa_end2end.py │ ├── aealite/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── agent.go │ │ ├── agent_test.go │ │ ├── connections/ │ │ │ ├── acn/ │ │ │ │ ├── acn.go │ │ │ │ ├── pipe_iface.go │ │ │ │ └── protocol.go │ │ │ ├── connections.go │ │ │ ├── p2pclient.go │ │ │ ├── p2pclient_test.go │ │ │ └── tcpsocket.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── helpers/ │ │ │ ├── base.go │ │ │ └── base_test.go │ │ ├── protocols/ │ │ │ ├── acn/ │ │ │ │ └── v1_0_0/ │ │ │ │ ├── acn.pb.go │ │ │ │ ├── acn.proto │ │ │ │ └── acn.yaml │ │ │ ├── base.pb.go │ │ │ ├── base.proto │ │ │ ├── dialogue.go │ │ │ ├── dialogue_label.go │ │ │ ├── dialogue_label_test.go │ │ │ ├── dialogue_test.go │ │ │ ├── dialogues.go │ │ │ ├── message.go │ │ │ ├── message_test.go │ │ │ ├── search.pb.go │ │ │ ├── search.proto │ │ │ ├── storage.go │ │ │ ├── storage_test.go │ │ │ └── versions.go │ │ ├── test_env_file.env │ │ └── wallet/ │ │ ├── utils.go │ │ ├── wallet.go │ │ └── wallet_test.go │ └── libp2p_node/ │ ├── .dockerignore │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── acn/ │ │ └── utils.go │ ├── aea/ │ │ ├── api.go │ │ ├── envelope.pb.go │ │ ├── envelope.proto │ │ ├── pipe.go │ │ └── utils.go │ ├── common/ │ │ └── common.go │ ├── dht/ │ │ ├── common/ │ │ │ └── handlers.go │ │ ├── dhtclient/ │ │ │ ├── dhtclient.go │ │ │ ├── dhtclient_test.go │ │ │ └── options.go │ │ ├── dhtnode/ │ │ │ ├── dhtnode.go │ │ │ ├── streams.go │ │ │ └── utils.go │ │ ├── dhtpeer/ │ │ │ ├── benchmarks_test.go │ │ │ ├── dhtpeer.go │ │ │ ├── dhtpeer_test.go │ │ │ ├── mailbox.go │ │ │ ├── notifee.go │ │ │ ├── options.go │ │ │ └── utils.go │ │ ├── dhttests/ │ │ │ └── dhttests.go │ │ └── monitoring/ │ │ ├── file.go │ │ ├── prometheus.go │ │ └── service.go │ ├── go.mod │ ├── go.sum │ ├── libp2p_node.go │ ├── link │ ├── mocks/ │ │ ├── mock_host.go │ │ ├── mock_net.go │ │ ├── mock_network.go │ │ └── mock_peerstore.go │ ├── protocols/ │ │ └── acn/ │ │ └── v1_0_0/ │ │ ├── acn.pb.go │ │ ├── acn.proto │ │ └── acn.yaml │ └── utils/ │ ├── utils.go │ └── utils_test.go ├── mkdocs.yml ├── packages/ │ ├── __init__.py │ ├── fetchai/ │ │ ├── __init__.py │ │ ├── agents/ │ │ │ ├── aries_alice/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── aries_faber/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── car_data_buyer/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── car_detector/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── coin_price_feed/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── coin_price_oracle/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── coin_price_oracle_client/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── confirmation_aea_aw1/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── confirmation_aea_aw2/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── confirmation_aea_aw3/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── confirmation_aea_aw5/ │ │ │ │ └── aea-config.yaml │ │ │ ├── erc1155_client/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── erc1155_deployer/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── error_test/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── fipa_dummy_buyer/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── generic_buyer/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── generic_seller/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── gym_aea/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── hello_world/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── latest_block_feed/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── ml_data_provider/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── ml_model_trainer/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── my_first_aea/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── registration_aea_aw1/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── simple_aggregator/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── simple_buyer_aw2/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── simple_buyer_aw5/ │ │ │ │ └── aea-config.yaml │ │ │ ├── simple_seller_aw2/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── simple_seller_aw5/ │ │ │ │ └── aea-config.yaml │ │ │ ├── simple_service_registration/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── simple_service_search/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── tac_controller/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── tac_controller_contract/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── tac_participant/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── tac_participant_contract/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── thermometer_aea/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── thermometer_client/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ ├── weather_client/ │ │ │ │ ├── README.md │ │ │ │ └── aea-config.yaml │ │ │ └── weather_station/ │ │ │ ├── README.md │ │ │ └── aea-config.yaml │ │ ├── connections/ │ │ │ ├── __init__.py │ │ │ ├── gym/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── http_client/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── http_server/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── ledger/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ ├── connection.py │ │ │ │ ├── connection.yaml │ │ │ │ ├── contract_dispatcher.py │ │ │ │ └── ledger_dispatcher.py │ │ │ ├── local/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── oef/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ ├── connection.yaml │ │ │ │ └── object_translator.py │ │ │ ├── p2p_libp2p/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── check_dependencies.py │ │ │ │ ├── connection.py │ │ │ │ ├── connection.yaml │ │ │ │ ├── consts.py │ │ │ │ └── libp2p_node/ │ │ │ │ ├── .dockerignore │ │ │ │ ├── Dockerfile │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── acn/ │ │ │ │ │ └── utils.go │ │ │ │ ├── aea/ │ │ │ │ │ ├── api.go │ │ │ │ │ ├── envelope.pb.go │ │ │ │ │ ├── envelope.proto │ │ │ │ │ ├── pipe.go │ │ │ │ │ └── utils.go │ │ │ │ ├── common/ │ │ │ │ │ └── common.go │ │ │ │ ├── dht/ │ │ │ │ │ ├── common/ │ │ │ │ │ │ └── handlers.go │ │ │ │ │ ├── dhtclient/ │ │ │ │ │ │ ├── dhtclient.go │ │ │ │ │ │ ├── dhtclient_test.go │ │ │ │ │ │ └── options.go │ │ │ │ │ ├── dhtnode/ │ │ │ │ │ │ ├── dhtnode.go │ │ │ │ │ │ ├── streams.go │ │ │ │ │ │ └── utils.go │ │ │ │ │ ├── dhtpeer/ │ │ │ │ │ │ ├── benchmarks_test.go │ │ │ │ │ │ ├── dhtpeer.go │ │ │ │ │ │ ├── dhtpeer_test.go │ │ │ │ │ │ ├── mailbox.go │ │ │ │ │ │ ├── notifee.go │ │ │ │ │ │ ├── options.go │ │ │ │ │ │ └── utils.go │ │ │ │ │ ├── dhttests/ │ │ │ │ │ │ └── dhttests.go │ │ │ │ │ └── monitoring/ │ │ │ │ │ ├── file.go │ │ │ │ │ ├── prometheus.go │ │ │ │ │ └── service.go │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ ├── libp2p_node.go │ │ │ │ ├── link │ │ │ │ ├── mocks/ │ │ │ │ │ ├── mock_host.go │ │ │ │ │ ├── mock_net.go │ │ │ │ │ ├── mock_network.go │ │ │ │ │ └── mock_peerstore.go │ │ │ │ ├── protocols/ │ │ │ │ │ └── acn/ │ │ │ │ │ └── v1_0_0/ │ │ │ │ │ ├── acn.pb.go │ │ │ │ │ ├── acn.proto │ │ │ │ │ └── acn.yaml │ │ │ │ └── utils/ │ │ │ │ ├── utils.go │ │ │ │ └── utils_test.go │ │ │ ├── p2p_libp2p_client/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── p2p_libp2p_mailbox/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── p2p_stub/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── prometheus/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── soef/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── stub/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── connection.py │ │ │ │ └── connection.yaml │ │ │ ├── tcp/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ ├── connection.py │ │ │ │ ├── connection.yaml │ │ │ │ ├── tcp_client.py │ │ │ │ └── tcp_server.py │ │ │ └── webhook/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── connection.py │ │ │ └── connection.yaml │ │ ├── contracts/ │ │ │ ├── __init__.py │ │ │ ├── erc1155/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── build/ │ │ │ │ │ ├── Migrations.json │ │ │ │ │ ├── erc1155.json │ │ │ │ │ └── erc1155.wasm │ │ │ │ ├── contract.py │ │ │ │ ├── contract.yaml │ │ │ │ ├── contracts/ │ │ │ │ │ ├── Migrations.sol │ │ │ │ │ └── erc1155.vy │ │ │ │ └── migrations/ │ │ │ │ ├── 1_initial_migration.js │ │ │ │ └── 2_deploy_contracts.js │ │ │ ├── fet_erc20/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── build/ │ │ │ │ │ └── FetERC20Mock.json │ │ │ │ ├── contract.py │ │ │ │ └── contract.yaml │ │ │ ├── oracle/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── build/ │ │ │ │ │ ├── FetchOracle.json │ │ │ │ │ └── oracle.wasm │ │ │ │ ├── contract.py │ │ │ │ ├── contract.yaml │ │ │ │ └── contracts/ │ │ │ │ └── FetchOracle.sol │ │ │ ├── oracle_client/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── build/ │ │ │ │ │ ├── FetchOracleTestClient.json │ │ │ │ │ └── oracle_client.wasm │ │ │ │ ├── contract.py │ │ │ │ ├── contract.yaml │ │ │ │ └── contracts/ │ │ │ │ └── FetchOracleTestClient.sol │ │ │ └── staking_erc20/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── build/ │ │ │ │ ├── Migrations.json │ │ │ │ └── staking_erc20.json │ │ │ ├── contract.py │ │ │ └── contract.yaml │ │ ├── protocols/ │ │ │ ├── __init__.py │ │ │ ├── acn/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── acn.proto │ │ │ │ ├── acn_pb2.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── aggregation/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── aggregation.proto │ │ │ │ ├── aggregation_pb2.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── contract_api/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── contract_api.proto │ │ │ │ ├── contract_api_pb2.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── cosm_trade/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── cosm_trade.proto │ │ │ │ ├── cosm_trade_pb2.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── default/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── default.proto │ │ │ │ ├── default_pb2.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── fipa/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── fipa.proto │ │ │ │ ├── fipa_pb2.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── gym/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── gym.proto │ │ │ │ ├── gym_pb2.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── http/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── http.proto │ │ │ │ ├── http_pb2.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── ledger_api/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── ledger_api.proto │ │ │ │ ├── ledger_api_pb2.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── ml_trade/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── ml_trade.proto │ │ │ │ ├── ml_trade_pb2.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── oef_search/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── oef_search.proto │ │ │ │ ├── oef_search_pb2.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── prometheus/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── prometheus.proto │ │ │ │ ├── prometheus_pb2.py │ │ │ │ ├── protocol.yaml │ │ │ │ └── serialization.py │ │ │ ├── register/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ ├── register.proto │ │ │ │ ├── register_pb2.py │ │ │ │ └── serialization.py │ │ │ ├── signing/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ ├── serialization.py │ │ │ │ ├── signing.proto │ │ │ │ └── signing_pb2.py │ │ │ ├── state_update/ │ │ │ │ ├── README.md │ │ │ │ ├── __init__.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ ├── serialization.py │ │ │ │ ├── state_update.proto │ │ │ │ └── state_update_pb2.py │ │ │ └── tac/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── custom_types.py │ │ │ ├── dialogues.py │ │ │ ├── message.py │ │ │ ├── protocol.yaml │ │ │ ├── serialization.py │ │ │ ├── tac.proto │ │ │ └── tac_pb2.py │ │ └── skills/ │ │ ├── __init__.py │ │ ├── advanced_data_request/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── api_spec.yaml │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── models.py │ │ │ └── skill.yaml │ │ ├── aries_alice/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── aries_faber/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── carpark_client/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── carpark_detection/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── database.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ ├── strategy.py │ │ │ └── temp_files_placeholder/ │ │ │ ├── mask.tiff │ │ │ └── mask_ref.tiff │ │ ├── confirmation_aw1/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── registration_db.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── confirmation_aw2/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── registration_db.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── confirmation_aw3/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── registration_db.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── echo/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ └── skill.yaml │ │ ├── erc1155_client/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── erc1155_deploy/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── error/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── handlers.py │ │ │ └── skill.yaml │ │ ├── error_test_skill/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ └── skill.yaml │ │ ├── fetch_block/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ └── skill.yaml │ │ ├── fipa_dummy_buyer/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ └── skill.yaml │ │ ├── generic_buyer/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── generic_seller/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── gym/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── helpers.py │ │ │ ├── rl_agent.py │ │ │ ├── skill.yaml │ │ │ └── tasks.py │ │ ├── hello_world/ │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ └── skill.yaml │ │ ├── http_echo/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ └── skill.yaml │ │ ├── ml_data_provider/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── ml_train/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ ├── strategy.py │ │ │ └── tasks.py │ │ ├── registration_aw1/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── simple_aggregation/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── simple_buyer/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── simple_data_request/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ └── skill.yaml │ │ ├── simple_oracle/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── simple_oracle_client/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── simple_seller/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── simple_service_registration/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── simple_service_search/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── tac_control/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── game.py │ │ │ ├── handlers.py │ │ │ ├── helpers.py │ │ │ ├── parameters.py │ │ │ └── skill.yaml │ │ ├── tac_control_contract/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── game.py │ │ │ ├── handlers.py │ │ │ ├── helpers.py │ │ │ ├── parameters.py │ │ │ └── skill.yaml │ │ ├── tac_negotiation/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── helpers.py │ │ │ ├── skill.yaml │ │ │ ├── strategy.py │ │ │ └── transactions.py │ │ ├── tac_participation/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── game.py │ │ │ ├── handlers.py │ │ │ └── skill.yaml │ │ ├── task_test_skill/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── skill.yaml │ │ │ └── tasks.py │ │ ├── thermometer/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── thermometer_client/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ ├── weather_client/ │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dialogues.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── strategy.py │ │ └── weather_station/ │ │ ├── README.md │ │ ├── __init__.py │ │ ├── behaviours.py │ │ ├── db_communication.py │ │ ├── dialogues.py │ │ ├── dummy_weather_station_data.py │ │ ├── handlers.py │ │ ├── skill.yaml │ │ └── strategy.py │ └── hashes.csv ├── plugins/ │ ├── aea-cli-ipfs/ │ │ ├── LICENSE │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── aea_cli_ipfs/ │ │ │ ├── __init__.py │ │ │ ├── core.py │ │ │ └── ipfs_utils.py │ │ ├── pyproject.toml │ │ ├── setup.py │ │ └── tests/ │ │ ├── __init__.py │ │ └── test_aea_cli_ipfs.py │ ├── aea-ledger-cosmos/ │ │ ├── LICENSE │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── aea_ledger_cosmos/ │ │ │ ├── __init__.py │ │ │ └── cosmos.py │ │ ├── pyproject.toml │ │ ├── setup.py │ │ └── tests/ │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── data/ │ │ │ ├── cosmos_private_key.txt │ │ │ └── dummy_contract/ │ │ │ ├── __init__.py │ │ │ ├── build/ │ │ │ │ ├── some.json │ │ │ │ └── some.wasm │ │ │ ├── contract.py │ │ │ └── contract.yaml │ │ └── test_cosmos.py │ ├── aea-ledger-ethereum/ │ │ ├── LICENSE │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── aea_ledger_ethereum/ │ │ │ ├── __init__.py │ │ │ └── ethereum.py │ │ ├── pyproject.toml │ │ ├── setup.py │ │ └── tests/ │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── data/ │ │ │ ├── dummy_contract/ │ │ │ │ ├── __init__.py │ │ │ │ ├── build/ │ │ │ │ │ ├── some.json │ │ │ │ │ └── some.wasm │ │ │ │ ├── contract.py │ │ │ │ └── contract.yaml │ │ │ └── ethereum_private_key.txt │ │ ├── docker_image.py │ │ ├── test_ethereum.py │ │ └── test_ethereum_contract.py │ └── aea-ledger-fetchai/ │ ├── LICENSE │ ├── MANIFEST.in │ ├── README.md │ ├── aea_ledger_fetchai/ │ │ ├── __init__.py │ │ ├── _cosmos.py │ │ └── fetchai.py │ ├── pyproject.toml │ ├── setup.py │ └── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── data/ │ │ └── dummy_contract/ │ │ ├── __init__.py │ │ ├── build/ │ │ │ ├── some.json │ │ │ └── some.wasm │ │ ├── contract.py │ │ └── contract.yaml │ └── test_fetchai.py ├── protolint.yaml ├── pyproject.toml ├── pytest.ini ├── scripts/ │ ├── NOTES.md │ ├── RELEASE_PROCESS.md │ ├── __init__.py │ ├── acn/ │ │ ├── Dockerfile │ │ ├── Dockerfile.dev │ │ ├── README.md │ │ ├── build_upload_img.sh │ │ ├── helm-chart/ │ │ │ ├── Chart.yaml │ │ │ ├── templates/ │ │ │ │ ├── acnnode.yaml │ │ │ │ ├── boostrapistio.yaml │ │ │ │ ├── bootstrapnode.yaml │ │ │ │ ├── bootstrapsecret.yaml │ │ │ │ ├── dns.yaml │ │ │ │ ├── istio.yaml │ │ │ │ └── secret.yaml │ │ │ └── values.yaml │ │ ├── k8s/ │ │ │ ├── deployment.yaml │ │ │ ├── dns.yaml │ │ │ ├── istio.yaml │ │ │ └── secret.yaml │ │ ├── k8s_deploy_acn_node.py │ │ └── run_acn_node_standalone.py │ ├── bump_aea_version.py │ ├── bump_year.sh │ ├── check_copyright_notice.py │ ├── check_doc_links.py │ ├── check_imports_and_dependencies.py │ ├── check_package_versions_in_docs.py │ ├── check_packages.py │ ├── check_pipfile_and_toxini.py │ ├── common.py │ ├── deploy_to_registry.py │ ├── freeze_dependencies.py │ ├── generate_all_protocols.py │ ├── generate_api_docs.py │ ├── generate_ipfs_hashes.py │ ├── install.ps1 │ ├── install.sh │ ├── ledger_network_update.py │ ├── oef/ │ │ ├── launch.py │ │ ├── launch_config.json │ │ └── node_config.json │ ├── parse_main_dependencies_from_lock.py │ ├── spell-check.sh │ ├── update_package_versions.py │ ├── update_plugin_versions.py │ ├── update_symlinks_cross_platform.py │ └── whitelist.py ├── setup.cfg ├── strategy.ini ├── tests/ │ ├── __init__.py │ ├── common/ │ │ ├── __init__.py │ │ ├── docker_image.py │ │ ├── mocks.py │ │ ├── oef_search_pluto_scripts/ │ │ │ ├── launch.py │ │ │ ├── launch_config.json │ │ │ └── node_config.json │ │ ├── pexpect_popen.py │ │ └── utils.py │ ├── conftest.py │ ├── data/ │ │ ├── __init__.py │ │ ├── aea-config.example.yaml │ │ ├── aea-config.example_multipage.yaml │ │ ├── aea-config.example_w_keys.yaml │ │ ├── certs/ │ │ │ ├── server.crt │ │ │ ├── server.csr │ │ │ └── server.key │ │ ├── cosmos_private_key.txt │ │ ├── custom_crypto.py │ │ ├── dependencies_skill/ │ │ │ ├── __init__.py │ │ │ └── skill.yaml │ │ ├── dot_env_file │ │ ├── dummy_aea/ │ │ │ ├── __init__.py │ │ │ ├── aea-config.yaml │ │ │ ├── bad_requirements.txt │ │ │ ├── connections/ │ │ │ │ └── __init__.py │ │ │ ├── contracts/ │ │ │ │ └── __init__.py │ │ │ ├── cosmos_private_key.txt │ │ │ ├── default_private_key.pem │ │ │ ├── ethereum_private_key.txt │ │ │ ├── fetchai_private_key.txt │ │ │ ├── protocols/ │ │ │ │ └── __init__.py │ │ │ ├── requirements.txt │ │ │ ├── skills/ │ │ │ │ └── __init__.py │ │ │ └── vendor/ │ │ │ ├── __init__.py │ │ │ └── fetchai/ │ │ │ ├── __init__.py │ │ │ ├── connections/ │ │ │ │ └── __init__.py │ │ │ ├── contracts/ │ │ │ │ └── __init__.py │ │ │ ├── protocols/ │ │ │ │ └── __init__.py │ │ │ └── skills/ │ │ │ └── __init__.py │ │ ├── dummy_connection/ │ │ │ ├── __init__.py │ │ │ ├── connection.py │ │ │ └── connection.yaml │ │ ├── dummy_contract/ │ │ │ ├── __init__.py │ │ │ ├── build/ │ │ │ │ ├── some.json │ │ │ │ └── some.wasm │ │ │ ├── contract.py │ │ │ └── contract.yaml │ │ ├── dummy_protocol/ │ │ │ └── protocol.yaml │ │ ├── dummy_skill/ │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── dummy.py │ │ │ ├── dummy_subpackage/ │ │ │ │ ├── __init__.py │ │ │ │ └── foo.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── tasks.py │ │ ├── ethereum_private_key.txt │ │ ├── ethereum_private_key_two.txt │ │ ├── exception_skill/ │ │ │ ├── __init__.py │ │ │ ├── behaviours.py │ │ │ ├── handlers.py │ │ │ ├── skill.yaml │ │ │ └── tasks.py │ │ ├── fetchai_private_key.txt │ │ ├── fetchai_private_key_wrong.txt │ │ ├── generator/ │ │ │ ├── __init__.py │ │ │ ├── t_protocol/ │ │ │ │ ├── __init__.py │ │ │ │ ├── custom_types.py │ │ │ │ ├── dialogues.py │ │ │ │ ├── message.py │ │ │ │ ├── protocol.yaml │ │ │ │ ├── serialization.py │ │ │ │ ├── t_protocol.proto │ │ │ │ └── t_protocol_pb2.py │ │ │ └── t_protocol_no_ct/ │ │ │ ├── __init__.py │ │ │ ├── dialogues.py │ │ │ ├── message.py │ │ │ ├── protocol.yaml │ │ │ ├── serialization.py │ │ │ ├── t_protocol_no_ct.proto │ │ │ └── t_protocol_no_ct_pb2.py │ │ ├── gym-connection.yaml │ │ ├── hashes.csv │ │ ├── petstore_sim.yaml │ │ ├── sample_specification.yaml │ │ └── sample_specification_no_custom_types.yaml │ ├── list_of_packages_for_aea_tests.txt │ ├── oracle_contract_address.txt │ ├── test_aea/ │ │ ├── __init__.py │ │ ├── test_act_storage.py │ │ ├── test_aea.py │ │ ├── test_aea_builder.py │ │ ├── test_agent.py │ │ ├── test_agent_loop.py │ │ ├── test_cli/ │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── test_add/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_connection.py │ │ │ │ ├── test_contract.py │ │ │ │ ├── test_generic.py │ │ │ │ ├── test_protocol.py │ │ │ │ └── test_skill.py │ │ │ ├── test_add_key.py │ │ │ ├── test_build.py │ │ │ ├── test_config.py │ │ │ ├── test_create.py │ │ │ ├── test_delete.py │ │ │ ├── test_eject.py │ │ │ ├── test_fetch.py │ │ │ ├── test_fingerprint.py │ │ │ ├── test_freeze.py │ │ │ ├── test_generate/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_generate.py │ │ │ │ └── test_protocols.py │ │ │ ├── test_generate_key.py │ │ │ ├── test_generate_wealth.py │ │ │ ├── test_get_address.py │ │ │ ├── test_get_multiaddress.py │ │ │ ├── test_get_public_key.py │ │ │ ├── test_get_wealth.py │ │ │ ├── test_init.py │ │ │ ├── test_install.py │ │ │ ├── test_interact.py │ │ │ ├── test_issue_certificates.py │ │ │ ├── test_launch.py │ │ │ ├── test_launch_end_to_end.py │ │ │ ├── test_list.py │ │ │ ├── test_local_registry_update.py │ │ │ ├── test_loggers.py │ │ │ ├── test_login.py │ │ │ ├── test_logout.py │ │ │ ├── test_misc.py │ │ │ ├── test_plugin.py │ │ │ ├── test_publish.py │ │ │ ├── test_push.py │ │ │ ├── test_register.py │ │ │ ├── test_registry/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_add.py │ │ │ │ ├── test_fetch.py │ │ │ │ ├── test_login.py │ │ │ │ ├── test_logout.py │ │ │ │ ├── test_publish.py │ │ │ │ ├── test_push.py │ │ │ │ ├── test_registration.py │ │ │ │ └── test_utils.py │ │ │ ├── test_remove/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_base.py │ │ │ │ ├── test_connection.py │ │ │ │ ├── test_contract.py │ │ │ │ ├── test_dependencies.py │ │ │ │ ├── test_protocol.py │ │ │ │ └── test_skill.py │ │ │ ├── test_remove_key.py │ │ │ ├── test_reset_password.py │ │ │ ├── test_run.py │ │ │ ├── test_scaffold/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_connection.py │ │ │ │ ├── test_decision_maker_handler.py │ │ │ │ ├── test_error_handler.py │ │ │ │ ├── test_generic.py │ │ │ │ ├── test_protocols.py │ │ │ │ └── test_skills.py │ │ │ ├── test_search.py │ │ │ ├── test_transfer.py │ │ │ ├── test_upgrade.py │ │ │ ├── test_utils/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_config.py │ │ │ │ └── test_utils.py │ │ │ └── tools_for_testing.py │ │ ├── test_components/ │ │ │ ├── __init__.py │ │ │ ├── test_base.py │ │ │ ├── test_loader.py │ │ │ └── test_utils.py │ │ ├── test_configurations/ │ │ │ ├── __init__.py │ │ │ ├── test_aea_config.py │ │ │ ├── test_base.py │ │ │ ├── test_loader.py │ │ │ ├── test_manager.py │ │ │ ├── test_pypi.py │ │ │ ├── test_schema.py │ │ │ ├── test_utils.py │ │ │ └── test_validation.py │ │ ├── test_connections/ │ │ │ ├── __init__.py │ │ │ ├── test_base.py │ │ │ ├── test_scaffold.py │ │ │ └── test_sync_connection.py │ │ ├── test_context/ │ │ │ ├── __init__.py │ │ │ └── test_base.py │ │ ├── test_contracts/ │ │ │ ├── __init__.py │ │ │ └── test_base.py │ │ ├── test_crypto/ │ │ │ ├── __init__.py │ │ │ ├── test_helpers.py │ │ │ ├── test_ledger_apis.py │ │ │ ├── test_password_end2end.py │ │ │ ├── test_registries.py │ │ │ ├── test_registry/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_crypto_registry.py │ │ │ │ ├── test_ledger_api_registry.py │ │ │ │ └── test_misc.py │ │ │ └── test_wallet.py │ │ ├── test_decision_maker/ │ │ │ ├── __init__.py │ │ │ ├── test_default.py │ │ │ ├── test_gop.py │ │ │ ├── test_ownership_state.py │ │ │ ├── test_preferences.py │ │ │ └── test_scaffold.py │ │ ├── test_error_handler/ │ │ │ ├── __init__.py │ │ │ ├── test_base.py │ │ │ └── test_scaffold.py │ │ ├── test_exceptions.py │ │ ├── test_helpers/ │ │ │ ├── __init__.py │ │ │ ├── test_acn/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_agent_record.py │ │ │ │ └── test_uri.py │ │ │ ├── test_async_friendly_queue.py │ │ │ ├── test_async_utils.py │ │ │ ├── test_base.py │ │ │ ├── test_env_vars.py │ │ │ ├── test_exec_timeout.py │ │ │ ├── test_file_io.py │ │ │ ├── test_install_dependency.py │ │ │ ├── test_io.py │ │ │ ├── test_ipfs/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_base.py │ │ │ ├── test_logging.py │ │ │ ├── test_multiaddr.py │ │ │ ├── test_multiple_executor.py │ │ │ ├── test_pipe/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_pipe.py │ │ │ ├── test_preference_representations/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_base.py │ │ │ ├── test_profiling.py │ │ │ ├── test_search/ │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ ├── test_generic.py │ │ │ │ └── test_models.py │ │ │ ├── test_serializers.py │ │ │ ├── test_storage.py │ │ │ ├── test_sym_link.py │ │ │ ├── test_transaction/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_base.py │ │ │ └── test_yaml_utils.py │ │ ├── test_identity/ │ │ │ ├── __init__.py │ │ │ └── test_base.py │ │ ├── test_launcher.py │ │ ├── test_mail/ │ │ │ ├── __init__.py │ │ │ └── test_base.py │ │ ├── test_multiplexer.py │ │ ├── test_package_loading.py │ │ ├── test_protocols/ │ │ │ ├── __init__.py │ │ │ ├── test_base.py │ │ │ ├── test_dialogue/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_base.py │ │ │ │ └── test_msg_resolve.py │ │ │ ├── test_generator/ │ │ │ │ ├── __init__.py │ │ │ │ ├── common.py │ │ │ │ ├── test_common.py │ │ │ │ ├── test_end_to_end.py │ │ │ │ ├── test_extract_specification.py │ │ │ │ ├── test_generator.py │ │ │ │ └── test_validate.py │ │ │ └── test_scaffold.py │ │ ├── test_registries/ │ │ │ ├── __init__.py │ │ │ ├── test_base.py │ │ │ └── test_filter.py │ │ ├── test_runner.py │ │ ├── test_runtime.py │ │ ├── test_skills/ │ │ │ ├── __init__.py │ │ │ ├── test_base.py │ │ │ ├── test_behaviours.py │ │ │ ├── test_error.py │ │ │ ├── test_scaffold.py │ │ │ ├── test_task_subprocess.py │ │ │ └── test_tasks.py │ │ ├── test_task.py │ │ └── test_test_tools/ │ │ ├── __init__.py │ │ ├── test_click_testing.py │ │ ├── test_test_cases.py │ │ ├── test_test_contract.py │ │ └── test_test_skill.py │ ├── test_aea_core_packages/ │ │ ├── test_connections/ │ │ │ ├── test_http_client/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_http_client.py │ │ │ ├── test_http_server/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_http_server.py │ │ │ │ └── test_http_server_and_client.py │ │ │ └── test_ledger/ │ │ │ ├── __init__.py │ │ │ ├── test_contract_api.py │ │ │ └── test_ledger_api.py │ │ ├── test_contracts/ │ │ │ ├── __init__.py │ │ │ └── test_erc1155/ │ │ │ ├── __init__.py │ │ │ └── test_contract.py │ │ └── test_skills_integration/ │ │ ├── test_echo.py │ │ ├── test_generic.py │ │ ├── test_hello_world.py │ │ └── test_http_echo.py │ ├── test_aea_extra/ │ │ ├── __init__.py │ │ └── test_manager/ │ │ ├── __init__.py │ │ ├── test_manager.py │ │ └── test_utils.py │ ├── test_docs/ │ │ ├── __init__.py │ │ ├── helper.py │ │ ├── test_agent_vs_aea/ │ │ │ ├── __init__.py │ │ │ ├── agent_code_block.py │ │ │ └── test_agent_vs_aea.py │ │ ├── test_bash_yaml/ │ │ │ ├── __init__.py │ │ │ ├── md_files/ │ │ │ │ ├── __init__.py │ │ │ │ ├── bash-aggregation-demo.md │ │ │ │ ├── bash-aries-cloud-agent-demo.md │ │ │ │ ├── bash-car-park-skills.md │ │ │ │ ├── bash-cli-vs-programmatic-aeas.md │ │ │ │ ├── bash-config.md │ │ │ │ ├── bash-connection.md │ │ │ │ ├── bash-contract.md │ │ │ │ ├── bash-decision-maker.md │ │ │ │ ├── bash-deployment.md │ │ │ │ ├── bash-erc1155-skills.md │ │ │ │ ├── bash-generic-skills-step-by-step.md │ │ │ │ ├── bash-generic-skills.md │ │ │ │ ├── bash-gym-example.md │ │ │ │ ├── bash-gym-skill.md │ │ │ │ ├── bash-http-connection-and-skill.md │ │ │ │ ├── bash-language-agnostic-definition.md │ │ │ │ ├── bash-ledger-integration.md │ │ │ │ ├── bash-logging.md │ │ │ │ ├── bash-ml-skills.md │ │ │ │ ├── bash-oracle-demo.md │ │ │ │ ├── bash-orm-integration.md │ │ │ │ ├── bash-p2p-connection.md │ │ │ │ ├── bash-package-imports.md │ │ │ │ ├── bash-performance-benchmark.md │ │ │ │ ├── bash-por.md │ │ │ │ ├── bash-protocol-generator.md │ │ │ │ ├── bash-quickstart.md │ │ │ │ ├── bash-raspberry-set-up.md │ │ │ │ ├── bash-scaffolding.md │ │ │ │ ├── bash-skill-guide.md │ │ │ │ ├── bash-skill.md │ │ │ │ ├── bash-tac-skills-contract.md │ │ │ │ ├── bash-tac-skills.md │ │ │ │ ├── bash-tac.md │ │ │ │ ├── bash-thermometer-skills.md │ │ │ │ ├── bash-wealth.md │ │ │ │ └── bash-weather-skills.md │ │ │ └── test_demo_docs.py │ │ ├── test_build_aea_programmatically/ │ │ │ ├── __init__.py │ │ │ ├── programmatic_aea.py │ │ │ └── test_programmatic_aea.py │ │ ├── test_cli_commands.py │ │ ├── test_cli_vs_programmatic_aeas/ │ │ │ ├── __init__.py │ │ │ ├── programmatic_aea.py │ │ │ └── test_cli_vs_programmatic_aea.py │ │ ├── test_data_models.py │ │ ├── test_decision_maker_transaction/ │ │ │ ├── __init__.py │ │ │ ├── decision_maker_transaction.py │ │ │ └── test_decision_maker_transaction.py │ │ ├── test_docs_http_connection_and_skill.py │ │ ├── test_docs_protocol.py │ │ ├── test_docs_skill.py │ │ ├── test_generic_step_by_step_guide/ │ │ │ ├── __init__.py │ │ │ └── test_generic_step_by_step_guide.py │ │ ├── test_generic_storage.py │ │ ├── test_language_agnostic_definition.py │ │ ├── test_ledger_integration.py │ │ ├── test_multiagent_manager.py │ │ ├── test_multiplexer_standalone/ │ │ │ ├── __init__.py │ │ │ ├── multiplexer_standalone.py │ │ │ └── test_multiplexer_standalone.py │ │ ├── test_orm_integration/ │ │ │ ├── __init__.py │ │ │ ├── orm_seller_strategy.py │ │ │ └── test_orm_integration.py │ │ ├── test_query_language.py │ │ ├── test_quickstart.py │ │ ├── test_simple_oef_usage.py │ │ ├── test_skill_guide/ │ │ │ ├── __init__.py │ │ │ └── test_skill_guide.py │ │ ├── test_skill_testing.py │ │ └── test_standalone_transaction/ │ │ ├── __init__.py │ │ ├── standalone_transaction.py │ │ └── test_standalone_transaction.py │ ├── test_examples/ │ │ ├── __init__.py │ │ ├── test_gym_ex.py │ │ └── test_http_client_connection_to_aries_cloud_agent.py │ ├── test_packages/ │ │ ├── __init__.py │ │ ├── test_connections/ │ │ │ ├── __init__.py │ │ │ ├── test_p2p_libp2p/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_aea_cli.py │ │ │ │ ├── test_build.py │ │ │ │ ├── test_communication.py │ │ │ │ ├── test_errors.py │ │ │ │ ├── test_fault_tolerance.py │ │ │ │ ├── test_integration.py │ │ │ │ ├── test_public_dht.py │ │ │ │ └── test_slow_queue.py │ │ │ ├── test_prometheus/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_prometheus.py │ │ │ └── test_webhook/ │ │ │ ├── __init__.py │ │ │ └── test_webhook.py │ │ ├── test_contracts/ │ │ │ └── __init__.py │ │ ├── test_protocols/ │ │ │ ├── __init__.py │ │ │ ├── test_aggregation.py │ │ │ ├── test_ml_trade.py │ │ │ └── test_prometheus.py │ │ ├── test_skills/ │ │ │ ├── __init__.py │ │ │ ├── test_advanced_data_request/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ └── test_handlers.py │ │ │ ├── test_aries_alice/ │ │ │ │ ├── __init__.py │ │ │ │ ├── intermediate_class.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_aries_faber/ │ │ │ │ ├── __init__.py │ │ │ │ ├── intermediate_class.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_carpark_detection/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_database.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_confirmation_aw1/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ ├── test_registration_db.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_confirmation_aw2/ │ │ │ │ ├── __init__.py │ │ │ │ ├── intermediate_class.py │ │ │ │ ├── test_handlers.py │ │ │ │ ├── test_registration_db.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_confirmation_aw3/ │ │ │ │ ├── __init__.py │ │ │ │ ├── intermediate_class.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ ├── test_registration_db.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_erc1155_deploy/ │ │ │ │ ├── __init__.py │ │ │ │ ├── intermediate_class.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_fetch_block/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ └── test_handlers.py │ │ │ ├── test_hello_world/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_behaviours.py │ │ │ ├── test_http_echo/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_dialogues.py │ │ │ │ └── test_handlers.py │ │ │ ├── test_ml_data_provider/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_ml_train/ │ │ │ │ ├── __init__.py │ │ │ │ ├── helpers.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ ├── test_strategy.py │ │ │ │ └── test_task.py │ │ │ ├── test_registration_aw1/ │ │ │ │ ├── __init__.py │ │ │ │ ├── intermediate_class.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_simple_aggregation/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_simple_buyer/ │ │ │ │ └── __init__.py │ │ │ ├── test_simple_data_request/ │ │ │ │ ├── __init__.py │ │ │ │ ├── intermediate_class.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ └── test_handlers.py │ │ │ ├── test_simple_oracle/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ └── test_handlers.py │ │ │ ├── test_simple_oracle_client/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ └── test_handlers.py │ │ │ ├── test_simple_seller/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_simple_service_registration/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_simple_service_search/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ └── test_strategy.py │ │ │ ├── test_tac_control/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_game.py │ │ │ │ ├── test_handlers.py │ │ │ │ ├── test_helpers.py │ │ │ │ └── test_parameters.py │ │ │ ├── test_tac_control_contract/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ ├── test_helpers.py │ │ │ │ └── test_parameters.py │ │ │ ├── test_tac_negotiation/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_handlers.py │ │ │ │ ├── test_helpers.py │ │ │ │ ├── test_logical.py │ │ │ │ ├── test_strategy.py │ │ │ │ └── test_transactions.py │ │ │ ├── test_tac_participation/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_behaviours.py │ │ │ │ ├── test_dialogues.py │ │ │ │ ├── test_game.py │ │ │ │ └── test_handlers.py │ │ │ └── test_thermometer/ │ │ │ ├── __init__.py │ │ │ └── test_strategy.py │ │ └── test_skills_integration/ │ │ ├── __init__.py │ │ ├── test_aries_demo.py │ │ ├── test_carpark.py │ │ ├── test_coin_price.py │ │ ├── test_erc1155.py │ │ ├── test_fetch_block.py │ │ ├── test_generic.py │ │ ├── test_gym.py │ │ ├── test_ml_skills.py │ │ ├── test_simple_aggregation.py │ │ ├── test_simple_oracle.py │ │ ├── test_tac.py │ │ ├── test_thermometer.py │ │ └── test_weather.py │ └── test_packages_for_aea_tests/ │ ├── __init__.py │ ├── test_connections/ │ │ ├── __init__.py │ │ ├── test_gym/ │ │ │ ├── __init__.py │ │ │ └── test_gym.py │ │ ├── test_local/ │ │ │ ├── __init__.py │ │ │ ├── test_misc.py │ │ │ └── test_search_services.py │ │ ├── test_oef/ │ │ │ ├── __init__.py │ │ │ ├── test_communication.py │ │ │ ├── test_models.py │ │ │ └── test_oef_serializer.py │ │ ├── test_p2p_libp2p_client/ │ │ │ ├── __init__.py │ │ │ ├── test_aea_cli.py │ │ │ ├── test_communication.py │ │ │ └── test_errors.py │ │ ├── test_p2p_libp2p_mailbox/ │ │ │ ├── __init__.py │ │ │ ├── test_aea_cli.py │ │ │ ├── test_communication.py │ │ │ ├── test_errors.py │ │ │ └── test_mailbox_service.py │ │ ├── test_p2p_stub/ │ │ │ ├── __init__.py │ │ │ └── test_p2p_stub.py │ │ ├── test_soef/ │ │ │ ├── __init__.py │ │ │ ├── models.py │ │ │ ├── test_soef.py │ │ │ └── test_soef_integration.py │ │ ├── test_stub/ │ │ │ ├── __init__.py │ │ │ └── test_stub.py │ │ └── test_tcp/ │ │ ├── __init__.py │ │ ├── test_base.py │ │ └── test_communication.py │ ├── test_protocols/ │ │ ├── test_acn.py │ │ ├── test_contract_api.py │ │ ├── test_default.py │ │ ├── test_fipa.py │ │ ├── test_gym.py │ │ ├── test_http.py │ │ ├── test_ledger_api.py │ │ ├── test_oef_search.py │ │ ├── test_register.py │ │ ├── test_signing.py │ │ ├── test_state_update.py │ │ └── test_tac.py │ └── test_skills/ │ ├── __init__.py │ ├── test_echo/ │ │ ├── __init__.py │ │ ├── test_behaviours.py │ │ ├── test_dialogues.py │ │ └── test_handlers.py │ ├── test_erc1155_client/ │ │ ├── __init__.py │ │ ├── intermediate_class.py │ │ ├── test_behaviours.py │ │ ├── test_dialogues.py │ │ ├── test_handlers.py │ │ └── test_strategy.py │ ├── test_generic_buyer/ │ │ ├── __init__.py │ │ ├── test_behaviours.py │ │ ├── test_dialogues.py │ │ ├── test_handlers.py │ │ └── test_models.py │ ├── test_generic_seller/ │ │ ├── __init__.py │ │ ├── test_behaviours.py │ │ ├── test_dialogues.py │ │ ├── test_handlers.py │ │ └── test_models.py │ ├── test_gym/ │ │ ├── __init__.py │ │ ├── helpers.py │ │ ├── intermediate_class.py │ │ ├── test_dialogues.py │ │ ├── test_handlers.py │ │ ├── test_helpers.py │ │ ├── test_rl_agent.py │ │ └── test_task.py │ └── test_weather_station/ │ ├── __init__.py │ ├── test_dummy_weather_station_data.py │ ├── test_registration_db.py │ └── test_strategy.py ├── tox.ini └── user-image/ ├── Dockerfile ├── README.md ├── docker-env.sh └── scripts/ ├── docker-build-img.sh └── docker-publish-img.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .tox .pytest_cache build dist htmlcov tac.egg-info log*.txt ================================================ FILE: .firebaserc ================================================ { "projects": { "default": "fetch-docs-preview" } } ================================================ FILE: .gitattributes ================================================ # Set the default behavior, in case people don't have core.autocrlf set. * text=auto # Explicitly declare text files you want to always be normalized and converted # to native line endings on checkout. # text # Declare files that will always have CRLF line endings on checkout. # text eol=crlf # Declare files that will always have LF line endings on checkout. *.css text eol=lf *.html text eol=lf *.java text eol=lf *.js text eol=lf *.json text eol=lf *.properties text eol=lf *.txt text eol=lf *.xml text eol=lf *.py text eol=lf *.yaml text eol=lf *.md text eol=lf # Denote all files that are truly binary and should not be modified. *.class binary *.jar binary *.gif binary *.jpg binary *.png binary *.ico binary ================================================ FILE: .github/CODEOWNERS ================================================ * @5A11 @jrriehl ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: 🐛 Bug Report description: Report a reproducible bug title: "" labels: ["bug", "unconfirmed"] body: - type: markdown attributes: value: | Thank you for reporting the issue you are facing. Please complete this form so we can have the necessary information to assist you. - type: checkboxes id: prerequisites attributes: label: Prerequisites description: Please confirm before submitting a new issue options: - label: I am running the [latest version of the AEA Framework](https://docs.fetch.ai/aea/version/). required: true - label: I checked the [documentation](https://docs.fetch.ai/aea/) and found no answer to my problem. required: true - label: I checked the [existing issues](https://github.com/fetchai/agents-aea/issues) to make sure my problem has not already been reported. required: true - label: I have read the [code of conduct](https://github.com/fetchai/agents-aea/blob/main/CODE_OF_CONDUCT.md) before creating this issue. required: true - type: textarea id: expected attributes: label: Expected Behavior description: Describe the behavior you are expecting validations: required: true - type: textarea id: actual attributes: label: Current Behavior description: Describe the current behavior validations: required: true - type: textarea id: steps attributes: label: To Reproduce description: Detailed steps for reproducing the issue validations: required: false - type: textarea id: context attributes: label: Context description: Any relevant information about your setup (this is important in case the issue is not reproducible except under certain conditions) placeholder: | Operating system [e.g. MacOS], Python version [e.g. 3.8.5], AEA version [e.g. 1.2.0], ... validations: required: false - type: textarea id: logs attributes: label: Failure Logs description: Include any relevant log snippets or files here validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ name: ☝️ Feature request description: Suggest an idea for this project title: "" labels: ["feature-request", "unconfirmed"] body: - type: markdown attributes: value: | Thank you for suggesting a new idea for the AEA project. Please complete this form so we can have the necessary information about the feature you are requesting. - type: checkboxes id: prerequisites attributes: label: Prerequisites description: Please confirm before submitting a feature request. options: - label: I checked the [documentation](https://docs.fetch.ai/aea/) and made sure this feature does not already exist. required: true - label: I checked the [existing issues](https://github.com/fetchai/agents-aea/issues) to make sure this feature has not already been requested. required: true - label: I have read the [code of conduct](https://github.com/fetchai/agents-aea/blob/main/CODE_OF_CONDUCT.md) before creating this issue. required: true - type: textarea id: problem attributes: label: Problem description: | If your feature request relates to a problem, provide a description here, e.g. I'm always frustrated when [...] validations: required: false - type: textarea id: solution attributes: label: Feature / Solution description: | Provide a description of what you want to happen validations: required: true - type: textarea id: alternatives attributes: label: Alternatives description: | Provide any alternative solutions or features you've considered - type: textarea id: info attributes: label: Additional Context description: | Any other context or screenshots about the feature request validations: required: false ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/release.md ================================================ ## Release summary Version number: [e.g. 1.0.1] ## Release details Describe in short the main changes with the new release. ## Checklist _Put an `x` in the boxes that apply._ - [ ] I am making a pull request against the `main` branch from `develop`. - [ ] Lint and unit tests pass locally. - [ ] I have checked the fingerprint hashes are correct by running (`scripts/generate_ipfs_hashes.py`). - [ ] I have regenerated and updated the latest API docs. - [ ] I built the documentation and updated it with the latest changes. - [ ] I have added an item in `HISTORY.md` for this release. - [ ] I bumped the version number in the `aea/__version__.py` file. - [ ] I bumped the version number in every Docker image of the repo and published it. Also, I built and published them with tag `latest` (check the READMEs of [`aea-develop`](https://github.com/fetchai/agents-aea/blob/master/develop-image/README.md#publish) and [`aea-user`](https://github.com/fetchai/agents-aea/blob/master/develop-image/user-image/README.md#publish)) - [ ] I have pushed the latest packages to the registry. - [ ] I have uploaded the latest `aea` to PyPI. - [ ] I have uploaded the latest plugins to PyPI. ## Further comments Write here any other comment about the release, if any. ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: # Maintain dependencies for Pip - package-ecosystem: "pip" directory: "." schedule: interval: "weekly" target-branch: "develop" labels: - "dependencies" ================================================ FILE: .github/pull_request_template.md ================================================ ## Proposed changes Describe the changes here. ## Issues Links to any issues resolved. ## Types of changes What types of changes does your code introduce to agents-aea? _Put an `x` in the boxes that apply_ - [ ] Bugfix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to stop working as expected) - [ ] Something else (e.g. test, package, script, example, deployment, infrastructure, ...) ## Checklist _Put an `x` in the boxes that apply._ - [ ] I have read the [CONTRIBUTING](https://github.com/fetchai/agents-aea/blob/main/CONTRIBUTING.md) document. - [ ] I have based my branch, and I am making a pull request against, the `develop` branch. - [ ] Lint and unit tests pass locally with my changes. ### If applicable - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have checked that code coverage does not decrease. - [ ] I have added/updated the documentations. - [ ] Dependent changes have been merged and published in downstream modules. ## Further comments If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did, what alternatives you considered, etc. ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ main, develop ] pull_request: # The branches below must be a subset of the branches above branches: [ main ] schedule: - cron: '35 1 * * 0' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: [ 'go', 'javascript', 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 ================================================ FILE: .github/workflows/docs_pr_preview.yml ================================================ name: Documentation Preview on: pull_request: branches: - master - develop paths: - 'docs/**' jobs: build: name: Docs Ephemerial Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Use python 3.7 uses: actions/setup-python@v4 with: python-version: '3.7' - name: Install Dependencies run: cd docs && pip3 install pipenv && pipenv install -d --skip-lock --python python3 - name: Build run: cd docs && pipenv run mkdocs build -f ../mkdocs.yml - name: Archive Production Artifact uses: actions/upload-artifact@master with: name: dist path: site deploy: name: Docs Ephemerial Deploy needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Download Artifact uses: actions/download-artifact@master with: name: dist path: site - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: "${{ secrets.GITHUB_TOKEN }}" firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT }}" expires: 2d projectId: fetch-docs-preview # entryPoint: docs/ ================================================ FILE: .github/workflows/publish.yaml ================================================ name: Build, Publish and Deploy Docker Container on: push: branches: - develop - main jobs: publish: runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v3 - name: Setup GCloud - sandbox uses: google-github-actions/setup-gcloud@v0 if: github.ref == 'refs/heads/develop' with: project_id: ${{ secrets.GCLOUD_FETCH_AI_SANDBOX_PROJECT }} service_account_key: ${{ secrets.GCLOUD_FETCH_AI_SANDBOX_KEY }} - name: Setup GCloud - production uses: google-github-actions/setup-gcloud@v0 if: github.ref == 'refs/heads/main' with: project_id: ${{ secrets.GCLOUD_FETCH_AI_PROD_PROJECT }} service_account_key: ${{ secrets.GCLOUD_FETCH_AI_PROD_KEY }} - name: Configure Docker run: | gcloud auth configure-docker - name: Set Image Tag id: vars run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" # Push image to Google Container Registry - name: Build and Push Images run: | chmod +x ./scripts/acn/build_upload_img.sh if [ ${{ github.ref }} == 'refs/heads/develop' ] then ./scripts/acn/build_upload_img.sh fi if [ ${{ github.ref }} == 'refs/heads/main' ] then ./scripts/acn/build_upload_img.sh prod fi - name: Repository Dispatch env: IMAGE_TAG: ${{ steps.vars.outputs.sha_short }} run: | if [ ${{ github.ref }} == 'refs/heads/develop' ] then curl -H "Accept: application/vnd.github.everest-preview+json" \ -H "Authorization: token ${{ secrets.GH_PAT }}" \ --request POST \ --data '{"event_type": "agents-dht-testnet", "client_payload": {"image": "gcr.io/fetch-ai-sandbox/acn_node", "tag": "'"$IMAGE_TAG"'"}}' \ https://api.github.com/repos/fetchai/infra-sandbox-london-b-deployment/dispatches fi if [ ${{ github.ref }} == 'refs/heads/main' ] then curl -H "Accept: application/vnd.github.everest-preview+json" \ -H "Authorization: token ${{ secrets.GH_PAT }}" \ --request POST \ --data '{"event_type": "agents-dht", "client_payload": {"image": "gcr.io/fetch-ai-images/acn_node", "tag": "'"$IMAGE_TAG"'"}}' \ https://api.github.com/repos/fetchai/infra-mainnet-v2-deployment/dispatches fi ================================================ FILE: .github/workflows/upload_docker_images.yaml ================================================ name: Build and upload develop-image to docker hub on: push: branches: [ main ] jobs: build: env: BASE_TAG: fetchai/aea-develop runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up tag run: echo export TAG=${BASE_TAG}:$(python3 -c "from setup import about; print(about[\"__version__\"])") > env.sh - name: docker login env: DOCKER_USER: ${{secrets.DOCKER_USER}} DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}} run: | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - name: Build the Docker image run: | source env.sh docker build . -f ./develop-image/Dockerfile --tag $TAG - name: Tag to latest run: | source env.sh docker tag $TAG $BASE_TAG:latest - name: Docker Push run: | source env.sh docker push $TAG docker push $BASE_TAG:latest ================================================ FILE: .github/workflows/workflow.yml ================================================ name: AEA framework sanity checks and tests on: pull_request jobs: python_checks: continue-on-error: False runs-on: '${{ matrix.os }}' strategy: matrix: os: - ubuntu-latest python-version: - '3.8' tox-job: - check_plugins_code_consistency - check_go_code_consistency - bandit - safety - black-check - isort-check - flake8 - vulture - mypy - pylint timeout-minutes: 30 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '${{ matrix.python-version }}' - name: Install dependencies run: pip install tox==3.25.1 - name: 'Run check ${{ matrix.tox-job }}' run: | tox -e ${{ matrix.tox-job }} go_checks: continue-on-error: False runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean - name: Golang code style check (libp2p_node) uses: golangci/golangci-lint-action@v3.1.0 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true with: version: v1.48.0 working-directory: libs/go/libp2p_node - name: Golang code style check (aealite) uses: golangci/golangci-lint-action@v3.1.0 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true with: version: v1.48.0 working-directory: libs/go/aealite misc_checks: runs-on: ubuntu-latest continue-on-error: False timeout-minutes: 10 strategy: matrix: python-version: - '3.8' tox-job: - liccheck - copyright_check - hash_check -- --timeout 40.0 - package_version_checks - package_dependencies_checks - check_generate_all_protocols - docs steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '${{ matrix.python-version }}' - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install --user --upgrade setuptools # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip unzip protoc-3.19.4-linux-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc # install IPFS sudo apt-get install -y wget wget -O ./go-ipfs.tar.gz https://dist.ipfs.io/go-ipfs/v0.6.0/go-ipfs_v0.6.0_linux-amd64.tar.gz tar xvfz go-ipfs.tar.gz sudo mv go-ipfs/ipfs /usr/local/bin/ipfs ipfs init make protolint_install - name: Install dependencies run: pip install tox==3.25.1 - name: 'Run check ${{ matrix.tox-job }}' run: | tox -e ${{ matrix.tox-job }} misc_checks_extra: continue-on-error: False runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox - name: Check copyright year is up to date run: | ./scripts/bump_year.sh $(date +%Y) git diff --quiet||(echo "Some files have the incorrect year in their copyright header. Run ./scripts/bump_year.sh!"; exit 1) echo "all good" docs_check: continue-on-error: False runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-node@v1 with: node-version: 12.x - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox - name: Install markdown-spellcheck run: sudo npm install -g markdown-spellcheck - name: Check Docs links run: tox -e check-doc-links - name: Check API Docs updated run: tox -e check_api_docs - name: Check spelling run: tox -e spell_check plugins_install: continue-on-error: False runs-on: ${{ matrix.sys.os }} strategy: matrix: sys: - { os: windows-latest, shell: "msys2 {0}" } - { os: ubuntu-latest, shell: bash } - { os: macos-latest, shell: bash } python_version: [3.8] timeout-minutes: 10 steps: - uses: actions/checkout@v3 - if: matrix.sys.os == 'windows-latest' uses: msys2/setup-msys2@v2 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python_version }} - if: matrix.os == 'ubuntu-latest' name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean - name: Install tox run: | pip install tox==3.28.0 - name: Check plugin aea-ledger-cosmos run: | tox -r -e plugins_env -- sh -c "pip install ./plugins/aea-ledger-cosmos && aea generate-key cosmos && echo aea-ledger-cosmos checked!" - name: Check plugin aea-ledger-ethereum run: | tox -r -e plugins_env -- sh -c "pip install ./plugins/aea-ledger-ethereum && aea generate-key ethereum && echo aea-ledger-ethereum checked!" - name: Check plugin aea-ledger-fetchai run: | tox -r -e plugins_env -- sh -c "pip install ./plugins/aea-ledger-fetchai && aea generate-key fetchai && echo aea-ledger-fetchai checked!" - name: Check plugin aea-cli-ipfs run: | tox -r -e plugins_env -- sh -c "pip install ./plugins/aea-cli-ipfs && aea ipfs --help && echo aea-cli-ipfs checked!" protolint: continue-on-error: False runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Install protolint (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get autoremove sudo apt-get autoclean make protolint_install - name: Protolint check run: | make protolint integration_tests: if: github.base_ref == 'main' continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - plugins_install runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Setup GCloud - production uses: google-github-actions/setup-gcloud@v0 with: project_id: ${{ secrets.GCLOUD_FETCH_AI_PROD_PROJECT }} service_account_key: ${{ secrets.GCLOUD_FETCH_AI_PROD_KEY }} - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip unzip protoc-3.19.4-linux-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc - name: Configure Docker run: | gcloud auth configure-docker - name: Pull SOEF Image run: | docker pull gcr.io/fetch-ai-images/soef:9e78611 # change this to latest tag - name: Pull fetchd run: | docker pull fetchai/fetchd:0.10.2 - name: Pull ganache run: | docker pull trufflesuite/ganache-cli:latest - name: Async integration tests run: tox -e py3.8 -- -m 'integration and not unstable and not ledger' ./tests --ignore=tests/test_aea_core_packages core_packages_tests: # tests intersection with ledger and integration # limited tests set for quick checking primary functionality of the AEA continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - plugins_install runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Setup GCloud - production uses: google-github-actions/setup-gcloud@v0 with: project_id: ${{ secrets.GCLOUD_FETCH_AI_PROD_PROJECT }} service_account_key: ${{ secrets.GCLOUD_FETCH_AI_PROD_KEY }} - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip unzip protoc-3.19.4-linux-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc - name: Configure Docker run: | gcloud auth configure-docker - name: Pull SOEF Image run: | docker pull gcr.io/fetch-ai-images/soef:9e78611 # change this to latest tag - name: Pull fetchd run: | docker pull fetchai/fetchd:0.10.2 - name: Pull ganache run: | docker pull trufflesuite/ganache-cli:latest - name: Async integration tests run: tox -e py3.8 -- tests/test_aea_core_packages/ integration_ledger_tests: if: github.base_ref == 'main' continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - plugins_install runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - name: Setup GCloud - production uses: google-github-actions/setup-gcloud@v0 with: project_id: ${{ secrets.GCLOUD_FETCH_AI_PROD_PROJECT }} service_account_key: ${{ secrets.GCLOUD_FETCH_AI_PROD_KEY }} - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get autoremove sudo apt-get autoclean pip install tox - name: Configure Docker run: | gcloud auth configure-docker - name: Pull SOEF Image run: | docker pull gcr.io/fetch-ai-images/soef:9e78611 # change this to latest tag - name: Integration tests run: tox -e py3.8 -- -m 'integration and not unstable and ledger' ./tests --ignore=tests/test_aea_core_packages aea-tests: continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - plugins_install runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python_version: [3.8, 3.9, "3.10"] timeout-minutes: 90 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python_version }} - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - if: matrix.os == 'ubuntu-latest' name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip unzip protoc-3.19.4-linux-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc make protolint_install # sudo apt-get install -y protobuf-compiler # use sudo rm /var/lib/apt/lists/lock above in line above update if dependency install failures persist # use sudo apt-get dist-upgrade above in line below update if dependency install failures persist - if: matrix.os == 'macos-latest' name: Install dependencies (macos-latest) run: | pip install tox brew install gcc # brew install protobuf # brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/72457f0166d5619a83f508f2345b22d0617b5021/Formula/protobuf.rb wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-osx-x86_64.zip unzip protoc-3.19.4-osx-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc brew tap yoheimuta/protolint brew install protolint - if: matrix.os == 'windows-latest' name: Install dependencies (windows-latest) env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true run: | python -m pip install -U pip echo "::add-path::C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64" choco install protoc --version 3.19.4 choco install mingw -y choco install make -y # to check make was installed make --version pip install tox # wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip # unzip protoc-3.19.4-win64.zip -d protoc # sudo mv protoc/bin/protoc /usr/local/bin/protoc python scripts/update_symlinks_cross_platform.py make protolint_install_win # just check protolint runs protolint version - if: True name: Unit tests run: | tox -e py${{ matrix.python_version }} -- -m 'not integration and not unstable and not ledger' ./tests/test_docs ./tests/test_aea - name: Plugin tests run: | tox -e plugins-py${{ matrix.python_version }} -- -m 'not integration and not unstable' examples-tests: if: github.base_ref == 'main' continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - plugins_install runs-on: ubuntu-latest timeout-minutes: 90 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get autoremove sudo apt-get autoclean pip install tox - name: Unit tests run: tox -e py3.8 -- tests/test_examples aea-extras-tests: continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - plugins_install runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python_version: [3.8, 3.9, "3.10"] timeout-minutes: 90 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python_version }} - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - if: matrix.os == 'ubuntu-latest' name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip unzip protoc-3.19.4-linux-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc make protolint_install # sudo apt-get install -y protobuf-compiler # use sudo rm /var/lib/apt/lists/lock above in line above update if dependency install failures persist # use sudo apt-get dist-upgrade above in line below update if dependency install failures persist - if: matrix.os == 'macos-latest' name: Install dependencies (macos-latest) run: | pip install tox brew install gcc # brew install protobuf # brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/72457f0166d5619a83f508f2345b22d0617b5021/Formula/protobuf.rb wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-osx-x86_64.zip unzip protoc-3.19.4-osx-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc brew tap yoheimuta/protolint brew install protolint - if: matrix.os == 'windows-latest' name: Install dependencies (windows-latest) env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true run: | python -m pip install -U pip echo "::add-path::C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64" choco install protoc --version 3.19.4 choco install mingw -y choco install make -y # to check make was installed make --version pip install tox # wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip # unzip protoc-3.19.4-win64.zip -d protoc # sudo mv protoc/bin/protoc /usr/local/bin/protoc python scripts/update_symlinks_cross_platform.py make protolint_install_win # just check protolint runs protolint version - name: Unit tests run: | tox -e py${{ matrix.python_version }} -- ./tests/test_aea_extra packages-tests: if: github.base_ref == 'main' continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - plugins_install runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python_version: [3.8, 3.9, "3.10"] timeout-minutes: 90 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python_version }} - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - if: matrix.os == 'ubuntu-latest' name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip unzip protoc-3.19.4-linux-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc make protolint_install # sudo apt-get install -y protobuf-compiler # use sudo rm /var/lib/apt/lists/lock above in line above update if dependency install failures persist # use sudo apt-get dist-upgrade above in line below update if dependency install failures persist - if: matrix.os == 'macos-latest' name: Install dependencies (macos-latest) run: | pip install tox brew install gcc # brew install protobuf # brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/72457f0166d5619a83f508f2345b22d0617b5021/Formula/protobuf.rb wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-osx-x86_64.zip unzip protoc-3.19.4-osx-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc brew tap yoheimuta/protolint brew install protolint - if: matrix.os == 'windows-latest' name: Install dependencies (windows-latest) env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true run: | python -m pip install -U pip echo "::add-path::C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64" choco install protoc --version 3.19.4 choco install mingw -y choco install make -y # to check make was installed make --version pip install tox # wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip # unzip protoc-3.19.4-win64.zip -d protoc # sudo mv protoc/bin/protoc /usr/local/bin/protoc python scripts/update_symlinks_cross_platform.py make protolint_install_win # just check protolint runs protolint version - if: True name: Unit tests run: | tox -e py${{ matrix.python_version }} -- --cov=packages/fetchai/connections --cov=packages/fetchai/contracts --cov=packages/fetchai/protocols --cov=packages/fetchai/skills -m 'not integration and not unstable' ./tests/test_packages_for_aea_tests ./tests/test_packages golang_tests: continue-on-error: True needs: - go_checks runs-on: ${{ matrix.os }} strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest python-version: [3.8] timeout-minutes: 45 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - if: matrix.os == 'macos-latest' working-directory: ./libs/go/libp2p_node run: | export LINKPATH=`go env GOTOOLDIR`/link echo $LINKPATH sudo cp $LINKPATH ${LINKPATH}_orig sudo cp link $LINKPATH sudo chmod a+x $LINKPATH - if: matrix.python-version == '3.8' name: Golang unit tests (libp2p_node) working-directory: ./libs/go/libp2p_node run: make test - if: matrix.python-version == '3.8' name: Golang unit tests (aealite) working-directory: ./libs/go/aealite run: go test -p 1 -timeout 0 -count 1 -v ./... libp2p_coverage: name: libp2p_coverage runs-on: ubuntu-latest steps: - name: Set up Go 1.17 uses: actions/setup-go@v3 with: go-version: 1.17 id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean sudo apt-get install make -y - name: Get dependencies working-directory: ./libs/go/libp2p_node/ run: | make install - name: Generate coverage report working-directory: ./libs/go/libp2p_node/ run: | make test - name: Print coverage report working-directory: ./libs/go/libp2p_node/ run: | go tool cover -func=coverage.txt coverage_checks: continue-on-error: True needs: - python_checks - go_checks - misc_checks - misc_checks_extra - docs_check - plugins_install runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/setup-go@v3 with: go-version: "^1.17.0" - name: Install dependencies (ubuntu-latest) run: | sudo apt-get update --fix-missing sudo apt-get install libmbedtls-dev sudo apt-get autoremove sudo apt-get autoclean pip install tox pip install coverage # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip unzip protoc-3.19.4-linux-x86_64.zip -d protoc sudo mv protoc/bin/protoc /usr/local/bin/protoc make protolint_install - name: Run all tests run: | tox -e py3.8-cov -- --ignore=tests/test_docs --ignore=tests/test_examples --ignore=tests/test_packages/test_skills_integration -m 'not unstable' ./tests tox -e plugins-py3.8-cov -- --cov-append -m 'not unstable' continue-on-error: true - name: Show full coverage report run: | coverage report -m -i - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml flags: unittests name: codecov-umbrella fail_ci_if_error: false ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST Pipfile.lock # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments # .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ *.sqlite3 pip-wheel-metadata/ .DS_Store */.DS_Store data/* !data/aea.png !data/video-aea.png temp_private_key.pem .idea/ input_file output_file !packages/fetchai/contracts/erc1155/build !packages/fetchai/contracts/staking_erc20/build !packages/fetchai/contracts/oracle/build !packages/fetchai/contracts/oracle_client/build !packages/fetchai/contracts/fet_erc20/build packages/fetchai/connections/p2p_libp2p/libp2p_node/libp2p_node !tests/data/dummy_contract/build !plugins/aea-ledger-ethereum/tests/data/dummy_contract/build !plugins/aea-ledger-cosmos/tests/data/dummy_contract/build !plugins/aea-ledger-fetchai/tests/data/dummy_contract/build deploy-image/.env libs/go/aealite/aealite libs/go/libp2p_node/libp2p_node libs/go/libp2p_node/coverage.txt libs/go/libp2p_node/dht/dhtclient/agent_records_store* libs/go/libp2p_node/dht/dhtpeer/agent_records_store* !libs/go/aea_end2end/seller_agent coverage.txt ================================================ FILE: .spelling ================================================ # markdown-spellcheck spelling configuration file # Format - lines beginning # are comments # global dictionary is at the start, file overrides afterwards # one word per line, to define a file override use ' - filename' # where filename is relative to this configuration file liveness pre-shared Blockchain-based Asynchronisation asynchronisation Wooldridge driverless AEA AEAs Fetch.ai blockchains blockchain ACAs Alice_AEA Faber_AEA Alice_ACA Faber_ACA webhook webhooks localhost testnet multiaddress regex scaffolded Ethereum stdlib VSCode booleans Colab Dockerfile SDKs Teardown trustless deployer Ganache Ropsten faucet Fetch.ai. unregisters teardown unregister Agentland instantiable startup backend SQLite backends stdout sOEF Simple-OEF soef OpenAI's OpenAI PyPI OpenAPI fingerprinter Hyperledger proactiveness composable reusability Github tradeoffs protobuf secp256k1 a-zA-Z_ a-zA-Z0-9_ a-zA-Z- 0-9a-zA-Z- a-zA-Z 0-9a-zA-Z DLTs permissionless libp2p ORMs standalone namespaced prometheus toolset deserialised snake_case proto3 deserialising Rowling subexpression subexpressions deserialise frontend plugins MacOs WiFi pipenv automobiles IoT positionless 11km 1.1km mainnet testnets v2 75km 50km outbox subclassing gotchas trustlessness submodules setuptools linters Favorito MarcoFavorito Minarsch DavidMinarsch Hosseini Aristotelis Triantafyllidis Totoual Diarmid dishmop Panasevych Panasevychol Kevin-Chen0 Turchenkov solarw Lokman Rahmani lrahmani Jiří Vestfál MissingNO57 ejfitzgerald Riehl jrriehl README.md reentrancy serialiser simple-oef vendorised white_check_mark k8s illustrational OAuth ssh keychain submodule Django Multiaddresses Grafana Fipa Base64 Pi3 Raspbian Visdom PRs multiaddresses unregistration unregistering pipx BibTex Kubernetes Golang web3 CosmWasm Shoham Yoti PowerShell deregisters plugin Fetch.AI AEALite Pipfile 12-factor Disposability disposability ad-hoc Nuitka Perun 1.0.0rc1 docstrings 1.0.0rc2 install.ps1 pylint quickstart CVE-2021-27291 AgentLand GCloud p2p LookupRequest LookupResponse AeaEnvelope _envelope_bytes_ AgentRecord AgentApi DHTPeer sequenceDiagram listenLoop OutputQueue outputLoop SendQueue sendLoop DhtNode alt ack-timeout conn-error generic_error DESERIALIZATION_ERROR RouteEnvelope PoR choco utc-0 docstring utils kademlia Capricorn stargateworld-3 v0.8.x fetch.ai RaiseError serializer's pywin32 CosmPy dockerised Mermaid-JS darglint aea-cli-ipfs tensorflow ethereum mypy ipfshttpclient 0.8.0a2 int64 int32 cosmpy tx asyncio golang basecontracttesttool linter stargateworld faq fetchai agents-aea ci perf scipy numpy scikit-image hello_world pis prebuilt jsonschema tox - docs/language-agnostic-definition.md fetchai protocol_id - docs/simple-oef.md _do_ CosmWasm - docs/ledger-integration.md atestfet v0.2.x dorado-1 - docs/questions-and-answers.md state_update txt - docs/protocol-generator.md java javascript protoc - docs/acn.md p2p_libp2p_mailbox - docs/cli-commands.md aea - docs/contract.md init code_id init_msg fetchai erc1155 ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION - docs/decision-maker.md tac_ - docs/limits.md spec micropython Subprotocols mempool - docs/p2p-connection.md p2p_libp2p - docs/quickstart.md my_first_aea output_file AEATestCase test.py aea-config yaml - docs/skill-guide.md TickerBehaviour behaviours.py strategy.py dialogues.py oef_search handlers.py skill.yaml packages.fetchai.skills.my_search.dialogues capricorn-1 dorado dorado-1 - acapy-alice-bob/AdminAPI.md ACA-Py ACA-Py-based prover - CODE_OF_CONDUCT.md color behavior ================================================ FILE: AUTHORS.md ================================================ # Authors This is the official list of the AEA Framework authors: ### Lead - Ali Hosseini [5A11](https://github.com/5A11) ### Primary Current and Past Contributors - James Riehl [jrriehl](https://github.com/jrriehl) - David Minarsch [DavidMinarsch](https://github.com/DavidMinarsch) - Marco Favorito [MarcoFavorito](https://github.com/MarcoFavorito) - Yuri Turchenkov [solarw](https://github.com/solarw) - Oleg Panasevych [Panasevychol](https://github.com/panasevychol) - Lokman Rahmani [lrahmani](https://github.com/lrahmani) - Jiří Vestfál [MissingNO57](https://github.com/MissingNO57) ================================================ FILE: CITATION.cff ================================================ cff-version: 1.2.0 message: "If you use the AEA framework in your research, please cite it as below:" title: Autonomous Economic Agent (AEA) Framework authors: - family-names: Hosseini given-names: Seyed Ali - family-names: Minarsch given-names: David - family-names: Favorito given-names: Marco - family-names: Riehl given-names: James - family-names: Turchenkov given-names: Yuri - family-names: Panasevych given-names: Oleg - family-names: Rahmani given-names: Lokman - family-names: Vestfál given-names: Jiří - family-names: Triantafyllidis given-names: Aristotelis - family-names: Campbell given-names: Diarmid - family-names: Chen given-names: Kevin date-released: "2019-08-21" url: "https://github.com/fetchai/agents-aea/" license: "Apache-2.0" ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others’ private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at developer@fetch.ai. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the , version 2.1, available at . Community Impact Guidelines were inspired by [Mozilla’s code of conduct enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . ================================================ FILE: CONTRIBUTING.md ================================================ # Contribution Guidelines Contributions to the framework, plugins, packages and related tools are welcome. As a contributor, here are the guidelines we would like you to follow: - [Code of Conduct](#coc) - [Question or Problem?](#question) - [Issues and Bugs](#issue) - [Feature Requests](#feature) - [Submission Guidelines](#submit) - [Coding Rules](#rules) - [Commit Message Guidelines](#commit) ## Code of Conduct Please read and follow our [Code of Conduct][coc]. ## Question or Problem? Please use [GitHub Discussions][ghdiscussion] for support related questions and general discussions. Do NOT open issues as they are for bug reports and feature requests. This is because: - Questions and answers stay available for public viewing so your question/answer might help someone else. - GitHub Discussions voting system ensures the best answers are prominently visible. ## Found a Bug? If you find a bug in the source code [submit a bug report issue](#submit-issue). Even better, you can [submit a Pull Request](#submit-pr) with a fix. ## Missing a Feature? You can *request* a new feature by [submitting a feature request issue](#submit-issue). If you would like to *implement* a new feature: - For a **Major Feature**, first [open an issue](#submit-issue) and outline your proposal so that it can be discussed. - **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). ## Submission Guidelines ### Submitting an Issue Before you submit an issue, please search the [issue tracker][issues]. An issue for your problem might already exist and the discussion might inform you of workarounds readily available. For bug reports, it is important that we can reproduce and confirm it. For this, we need you to provide a minimal reproduction instruction (this is part of the bug report issue template). You can file new issues by selecting from our [new issue templates][new-issue] and filling out the issue template. ### Submitting a Pull Request (PR) Before you submit your Pull Request (PR) consider the following guidelines: 1. All Pull Requests should be based off of and opened against the `develop` branch. Do **not** open a Pull Request against `main`! 2. Search [Existing PRs][prs] for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. 3. Be sure that an issue exists describing the problem you're fixing, or the design for the feature you'd like to add. 4. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the [repository][github]. 5. In your forked repository, make your changes in a new git branch created from the `develop` branch. 6. Make your changes, **including test cases** and updating documentation where appropriate. 7. Follow our [coding rules](#rules). 8. Run all tests and checks locally, as described in the [development guide][developing], and ensure they pass. This saves CI hours and ensures you only commit clean code. 9. Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit). 10. Push your branch to GitHub. 11. In GitHub, send a pull request to `fetchai:develop`. > Where possible, try to take advantage of the modularity of the framework and add new functionality via a new module. Currently, ledger plugins are supported and packages (skills, connections, protocols, contracts) allow for extensibility. #### Reviewing a Pull Request The AEA team reserves the right not to accept pull requests from community members who haven't been good citizens of the community. Such behavior includes not following our [code of conduct][coc] and applies within or outside the managed channels. When you contribute a new feature, the maintenance burden is transferred to the core team. This means that the benefit of the contribution must be compared against the cost of maintaining the feature. #### Addressing review feedback If we ask for changes via code reviews then: 1. Make the required updates to the code. 2. Re-run the tests and checks to ensure they are still passing. 3. Create a new commit and push to your GitHub repository (this will update your Pull Request). #### After your pull request is merged After your pull request is merged, you can safely delete your branch and pull the changes from the (upstream) repository. ## Coding Rules To ensure consistency throughout the source code, keep these rules in mind as you are working: - All code must pass our code quality checks (linters, formatters, etc). See the [development guide][developing] section for more detail. - All features or bug fixes **must be tested** via unit-tests and if applicable integration-tests. These help to, a. prove that your code works correctly, and b. guard against future breaking changes and lower the maintenance cost. - All public features **must be documented**. - All files must include a license header. ## Commit Message Convention Please follow the [Conventional Commits v1.0.0][convcommit]. The commit types must be one of the following: - **build**: Changes that affect the build system or external dependencies - **ci**: Changes to our CI configuration files and scripts - **docs**: Documentation only changes - **feat**: A new feature - **fix**: A bug fix - **nfunc**: Code that improves some non-functional characteristic, such as performance, security, ... - **refactor**: A code change that neither fixes a bug nor adds a feature - **test**: Adding missing tests or correcting existing tests [coc]: https://github.com/fetchai/agents-aea/blob/main/CODE_OF_CONDUCT.md [developing]: https://github.com/fetchai/agents-aea/blob/main/DEVELOPING.md [ghdiscussion]: https://github.com/fetchai/agents-aea/discussions [issues]: https://github.com/fetchai/agents-aea/issues [new-issue]: https://github.com/fetchai/agents-aea/issues/new/choose [prs]: https://github.com/fetchai/agents-aea/pulls [convcommit]: https://www.conventionalcommits.org/en/v1.0.0/ [github]: https://github.com/fetchai/agents-aea ================================================ FILE: DEVELOPING.md ================================================ # Development Guidelines - [Getting the Source](#get) - [Setting up a New Development Environment](#setup) - [Development](#dev) - [General code quality checks](#general) - [Updating API documentation](#api) - [Updating documentation](#docs) - [Updating dependencies](#deps) - [Updating packages](#package) - [Tests](#tests) - [Miscellaneous checks](#misc) - [Contributing](#contributing) - [Making Releases](#release) ## Getting the Source 1. Fork the [agents-aea repository][repo]. 2. Clone your fork of the agents-aea repository: ``` shell git clone git@github.com:/agents-aea.git ``` 3. Define an `upstream` remote pointing back to the main agents-aea repository: ``` shell git remote add upstream https://github.com/fetchai/agents-aea.git ``` ## Setting up a New Development Environment 1. Ensure you have Python (version `3.8`, `3.9` or `3.10`) and [`poetry`][poetry]. 2. ``` shell make new-env ``` This will create a new virtual environment using poetry with the project and all the development dependencies installed. > We use poetry to manage dependencies. All python specific dependencies are specified in `pyproject.toml` and installed with the framework. > > You can have more control on the installed dependencies by leveraging poetry's features. 3. ``` shell poetry shell ``` To enter the virtual environment. Depending on what you want to do, you might need extra tools on your system: - The project uses [Google Protocol Buffers][protobuf] compiler for message serialization. The compiler's version must match the `protobuf` library installed with the project (see `pyproject.toml`). - The `fetchai/p2p_libp2p` package is partially developed in Go. To make changes, [install Golang][go]. - To update fingerprint hashes of packages, you will need the [IPFS daemon][ipfs]. ## Development ### General code quality checks To run general code quality checkers, formatters and linters: - ``` shell make lint ``` Automatically formats your code and sorts your imports, checks your code's quality and scans for any unused code. - ``` shell make mypy ``` Statically checks the correctness of the types. - ``` shell make pylint ``` Analyses the quality of your code. - ``` shell make security ``` Checks the code for known vulnerabilities and common security issues. - ``` shell make clean ``` Cleans your development environment and deletes temporary files and directories. - For the Go parts, we use [`golines`][golines] and [`golangci-lint`][golangci-lint] for linting. ### Updating documentation We use [`mkdocs`][mkdocs] and [`material-for-mkdocs`][material] for static documentation pages. To make changes to the documentation: - ``` shell make docs-live ``` This starts a live-reloading docs server on localhost which you can access by going to in your browser. Making changes to the documentation automatically reloads this page, showing you the latest changes. To create a new documentation page, add a markdown file under `/docs/` and add a reference to this page in `mkdocs.yml` under `nav`. ### Updating API documentation If you've made changes to the core `aea` package that affects the public API: - ``` shell make generate-api-docs ``` This regenerates the API docs. If pages are added/deleted, or there are changes in their structure, these need to be reflected manually in the `nav` section of `mkdocs.yaml`. ### Updating dependencies We use [`poetry`][poetry] and `pyproject.toml` to manage the project's dependencies. If you've made any changes to the dependencies (e.g. added/removed dependencies, or updated package version requirements): - ``` shell poetry lock ``` This re-locks the dependencies. Ensure that the `poetry.lock` file is pushed into the repository (by default it is). - ``` shell make liccheck ``` Checks that the licence for the framework is correct, taking into account the licences for all dependencies, their dependencies and so forth. ### Updating packages If you've made changes to the packages included in the repository (e.g. skills, protocols, connections, contracts): - ``` shell make update-package-hashes ``` Updates the fingerprint hashes of every package in the repository. - ``` shell make package-checks ``` Checks, a. that the package hashes are correct, b. the documentation correctly references the latest packages, and c) runs other correctness checks on packages. ### Tests To test the Python part of the project, we use `pytest`. To run the tests: - ``` shell make test ``` Runs all the tests. - ``` shell make test-plugins ``` Runs all plugin tests. - ``` shell make dir={SUBMODULE} tdir={TESTMODULE} test-sub ``` Runs the tests for `aea.{SUBMODULE}`. For example, to run the tests for the CLI: `make dir=cli tdir=cli test-sub` To test the Go parts of the project: - ``` shell go test -p 1 -timeout 0 -count 1 -v ./... ``` from the root directory of a Go package (e.g. `fetchai/p2p_libp2p`) to run the Golang tests. If you experience installation or build issues, run `go clean -modcache`. ### Miscellaneous checks - ``` shell copyright-check ``` Checks that all files have the correct copyright header (where applicable). - ``` shell check-doc-links ``` Checks that the links in the documentations are valid and alive. - ``` shell make libp2p-diffs ``` Checks the libp2p code under `libs` and in the connection packages aren't different. - ``` shell make plugin-diffs ``` Checks the plugin licenses and the codes under `cosmos` and `fetchai` ledger plugins aren't different. ## Contributing For instructions on how to contribute to the project (e.g. creating Pull Requests, commit message convention, etc), see the [contributing guide][contributing guide]. ## Making Releases For instructions on how to make a release, see the [release process][release process] guide. [protobuf]: https://developers.google.com/protocol-buffers/ [ipfs]: https://docs.ipfs.tech/install/ [go]: https://golang.org/doc/install [golines]: https://github.com/segmentio/golines [golangci-lint]: https://golangci-lint.run [mkdocs]: https://www.mkdocs.org [material]: https://squidfunk.github.io/mkdocs-material/ [poetry]: https://python-poetry.org [contributing guide]: https://github.com/fetchai/agents-aea/blob/main/CONTRIBUTING.md [release process]: https://github.com/fetchai/agents-aea/blob/main/scripts/RELEASE_PROCESS.md [repo]: https://github.com/fetchai/agents-aea ================================================ FILE: HISTORY.md ================================================ # Release History ## 1.2.5 (2023-01-18) Docs: - Sweeping foundational updates across all documentation Misc: - Update some dependencies - Update copyright headers across all files (including year 2023) - Remove `docker-images` submodule - Various bug fixes ## 1.2.4 (2022-11-30) Agents: - Add hello_world skill and agent Docs: - Update repository documentations - Update installation guide for Raspberry Pis (add a link to prebuilt image for Raspberry Pis) Misc: - Update the dependencies (protobuf, jsonschema, cosmpy) - Various improvements in the repository structure (e.g. makefile, tox, workflows) - Speed up CI/CD - Various bug fixes ## 1.2.3 (2022-11-03) AEA: - core and development are dependencies updated. - ci improvements - cosmpy updated to 0.6.0 - Small code format improvements and better linting Plugins: - Small code format improvements - cosmpy updated to 0.6.0 Packages: - Small code format improvements ## 1.2.2 (2022-10-17) AEA: - Dependency management switched from pipenv to poetry. - Protocol generator updated to support Unions - Dependencies versions updates: click, mypy, black, ipfshttpclient - Small code format improvements Plugins: - Update web3 to version 5.31 - Small code format improvements - ipfshttpclient dependency version updated to 0.8.0a2 Packages: - Protocols regenerated according to the latest protocol generator improvements: Union support - Small code format improvements ## 1.2.1 (2022-07-12) AEA: - Protocol generator uses int64 instead of int32 - Dependencies versions updates Plugins: - Upgrades fetchai plugin to use cosmpy>=0.5.0 - Upgrades cosmos plugin to use cosmpy>=0.5.0 Packages: - Fixes for skills to work with dorado testnet (tx fee adjusted) ## 1.2.0 (2022-05-05) AEA: - Adds support for Python 3.10 - Updates protobuf dependency - Updates asyncio dependency - Updates golang modules - Updates many dependencies to their latest versions - Fixes dependency issues Plugins: - Upgrades fetchai plugin to be compatible with Dorado networks - Upgrades cosmos plugin to be compatible with Dorado networks Packages: - Adds more logging to the p2p_libp2p packages (vanilla, client, mailbox) - Aries demo updated to cover the full base scenario - Protocols were regenerated with newer protobuf Chores: - Fixed various tests - Fixed docker container issue in tests - Added automated script to add support for new versions of the Fetchai network - Added automated script to update copyright headers and check their validity - Apply the above script on all packages - Adds tests for BaseContractTestTool - Improves the script that automatically updates package versions ## 1.1.1 (2021-12-15) AEA: - Updates the protocol generator to generate protocols that satisfy linter constraints Plugins: - aea-cli-ipfs plugin small update Packages: - Fixes fetchai/p2p_libp2p connection to address a slow DHT lookup problem - Updates protocols with the latest protocol generator - Updates random beacon agent so it produces block data instead of the (now deprecated feature of the test-net) random beacon data Misc - Bumps go library versions - Various fixes and improvements ## 1.1.0 (2021-10-13) AEA: - Adds public keys to agent identity and skill context - Adds contract test tool - Adds multiprocess support for task manager - Adds multiprocess backed support to `MultiAgentManager` - Adds support for excluding connection on `aea run` - Adds support for adding a key that is being generated (`—add-key` option for `generate-key` command) - Adds check for dependencies to be present in registry on a package push - Makes more efficient installing of project dependencies on `aea install` - Adds dependency conflict detection on `aea install` - Improves pip install error details on `aea install` - Adds validation of `aea_version` when loading configuration - Adds a check for consistency of package versions in `MultiAgent Manager` - Adds better error reporting for aea registry requests - Fixes IPFS hash calculation for large files - Fixes protobuf dictionary serializer's uncovered cases and makes it deterministic - Fixes scaffolding of error and decision maker handlers - Fixes pywin32 problem when checking dependency - Improves existing testing tools Benchmarks: - Adds agents construction and decision maker benchmark cases Plugins: - Upgrades fetchai plugin to use CosmPy instead of CLI calls - Upgrades cosmos plugin to use CosmPy instead of CLI calls - Upgrades fetchai plugin to use StargateWorld - Upgrades cosmos plugin to Stargate - Sets the correct maximum Gas for fetch.ai plugin Packages: - Adds support for Tac to be run against fetchai StargateWorld test-net - Adds more informative error messages to CosmWasm ERC1155 contract - Adds support for atomic swap to CosmWasm ERC1155 contract - Adds an ACN protocol that formalises ACN communication using the framework's protocol language - Adds `cosm_trade` protocol for preparing atomic swap transactions for cosmos-based networks - Adds https support for server connection - Adds parametrising of http(s) in soef connection - Fixes http server content length response problem - Updates Oracle contract to 0.14 - Implements the full ACN spec throughout the ACN packages - Implements correct error code usage in ACN packages - Refactors ACN packages to unify reused logic - Adds tests for gym skills - Adds dockerised SOEF - Adds libp2p mailbox connection - Multiple fixes and stability improvements for `p2p_libp2p` connections Docs: - Adds ACN internals documentation - Fixes tutorial for HTTP connection and skill - Multiple additional docs updates - Adds more context to private keys docs Chores: - Various development features bumped - Bumped Mermaid-JS, for UML diagrams to major version 8 - Applies darglint to the code Examples: - Adds a unified script for running various versions/modes of Tac ## 1.0.2 (2021-06-03) AEA: - Bounds versions of dependencies by next major - Fixes incoherent warning message during package loading - Improves various incomprehensible error messages - Adds debug log message when abstract components are loaded - Adds tests and minor fixes for password related CLI commands and password usage in `MultiAgentManager` - Adds default error handler in `MultiAgentManager` - Ensures private key checks are performed after override setting in `MultiAgentManager` - Applies docstring fixes suggested by `darglint` - Fixes `aea push --local` command to use correct author - Fixes `aea get-multiaddress` command to consider overrides Plugins: - Bounds versions of dependencies by next major Packages: - Updates `p2p_libp2p` connection to use TCP sockets for all platforms - Multiple fixes on `libp2p_node` including better error handling and stream creation - Adds sending queue in `p2p_libp2p` connection to handle sending failures - Adds unit tests for `libp2p_node` utils - Adds additional tests for `p2p_libp2p` connection - Fixes location bug in AW5 - Improves connection check handling in soef connection - Updates oracle and oracle client contracts for better access control - Adds skill tests for `erc1155` skills - Adds skill tests for `aries` skills - Fixes minor bug in ML skills - Multiple additional tests and test stability fixes Docs: - Extends demo docs to include guidance of usage in AEA Manager - Adds short guide on Kubernetes deployment - Multiple additional docs updates Chores: - Adds `--no-bump` option to `generate_all_protocols` script - Adds script to detect if aea or plugins need bumping - Bumps various development dependencies - Adds Golang and GCC in Windows install script - Adds `darglint` to CI Examples: - Updates TAC deployment scripts and images ## - (2021-05-05) Packages: - Adds node watcher to `p2p_libp2p` connection - Improves logging and error handling in `p2p_libp2p` node - Addresses potential overflow issue in `p2p_libp2p` node - Fixes concurrency issue in `p2p_libp2p` node which could lead to wrongly ordered envelopes - Improves logging in TAC skills - Fixes Exception handling in connect/disconnect calls of soef connection - Extends public DHT tests to include staging - Adds tests for envelope ordering for all routes - Multiple additional tests and test stability fixes ## 1.0.1 (2021-04-30) AEA: - Fixes wheels issue for Windows - Fixes password propagation for certificate issuance in `MultiAgentManager` - Improves error message when local registry not present AEALite: - Adds full protocol support - Adds end-to-end interaction example with AEA (based on `fetchai/fipa` protocol) - Multiple additional tests and test stability fixes Packages: - Fixes multiple bugs in `ERC1155` version of TAC - Refactors p2p connections for better separation of concerns and maintainability - Integrates aggregation with simple oracle skill - Ensures genus and classifications are used in all skills using SOEF - Extends SOEF connection to implement `oef_search` protocol fully - Handles SOEF failures in skills - Adds simple aggregation skills including tests and docs - Adds tests for registration AW agents - Adds tests for reconnection logic in p2p connections - Multiple additional tests and test stability fixes Docs: - Extends car park demo with usage guide for AEA manager - Multiple additional docs updates Examples: - Adds TAC deployment example ## 1.0.0 (2021-03-30) - Improves contributor guide - Enables additional pylint checks - Adds configuration support on exception behaviour in ledger plugins - Improves exception handling in `aea-ledger-cosmos` and `aea-ledger-fetchai` plugins - Improves quickstart guide - Fixes multiple flaky tests - Fixes various outdated metadata - Resolves a CVE (CVE-2021-27291) affecting development dependencies - Adds end-to-end support and tests for simple oracle on Ethereum and Fetch.ai ledgers - Multiple minor fixes - Multiple additional tests and test stability fixes ## 1.0.0rc2 (2021-03-28) - Extends CLI command `aea fingerprint` to allow fingerprinting of agents - Improves `deploy-image` Docker example - Fixes a bug in `MultiAgentManager` which leaves it in an unclean state when project adding fails - Fixes dependencies of `aea-legder-fetchai` - Improves guide on HTTP client and server connection - Removes pickle library usage in the ML skills - Adds various consistency checks in configurations - Replaces usage of `pyaes` with `pycryptodome` in plugins - Changes generator to avoid non-idiomatic usage of type checks - Multiple minor fixes - Multiple additional tests and test stability fixes ## 1.0.0rc1 (2021-03-24) - Adds CLI command `aea get-public-key` - Adds support for encrypting private keys at rest - Adds support for configuration of decision maker and error handler instances from `aea-config.yaml` - Adds support for explicitly marking behaviours and handlers as dynamic - Adds support for fetchai ledger to oracle skills and contract - Adds timeout support on multiplexer calls to connections - Fixes bug in regex constrained string for id validation - Adds docs section on how AEAs satisfy 12-factor methodology - Adds docs section on tradeoffs made in `v1` - Adds example for logs streaming to browser - Removes multiple temporary hacks for backwards compatibility - Adds skills tests coverage for `echo` and `http_echo` skills - Adds `required_ledgers` field in `aea-config.yaml` - Removes `registry_path` field in `aea-config.yaml` - Adds `message_format` field to cert requests - Removes requirement for exact protocol buffers compiler, prints version used in protocols - Adds support to configure task manager mode via `aea-config.yaml` - Fixed spelling across docstrings in code base - Multiple minor fixes - Multiple docs updates to fix order of CLI commands with respect to installing dependencies - Multiple additional tests and test stability fixes ## 0.11.2 (2021-03-17) - Fixes a package import issue - Fixes an issue where `AgentLoop` did not teardown properly under certain conditions - Fixes a bug in testing tools - Fixes a bug where plugins are not loaded after installation in `MultiAgentManager` - Adds unit tests for weather, thermometer and car park skills - Fixes a missing dependency in Windows - Improves SOEF connections' error handling - Fixes bug in ML skills and adds unit tests - Adds script to bump plugin versions - Adds gas price strategy support in `aea-ledger-ethereum` plugin - Adds CLI plugin for IPFS interactions (add/get) - Adds support for CLI plugins to framework - Multiple additional tests and test stability fixes ## 0.11.1 (2021-03-06) - Bumps `aiohttp` to `>=3.7.4` to address a CVE affecting `http_server`, `http_client` and `webhook` connections - Adds script to ensure Pipfile and `tox.ini` dependencies align - Enforces presence of `protocol_specification_id` in `protocol.yaml` - Adds support for installation of agent-level PyPI dependencies in `AEABuilder` - Sets default ledger plugin during `aea create` - Updates various agent packages with missing ledger plugin dependencies - Bumps various development dependencies - Renames `coin_price` skill to `advanced_data_request` skill and generalises it - Updates `fetch_beacon` skill to use `ledger` connection - Multiple docs updates to fix order of CLI commands with respect to installing dependencies - Multiple additional tests and test stability fixes ## 0.11.0 (2021-03-04) - Adds slots usage in frequently used framework objects, including `Dialogue` - Fixes a bug in `aea upgrade` command where eject prompt was not offered - Refactors skill component configurations to allow for skill components (`Handler`, `Behaviour`, `Model`) to be placed anywhere in a skill - Extends skill component configuration to specify optional `file_path` field - Extracts all ledger specific functionality in plugins - Improves error logging in http server connection - Updates `Development - Use case` documentation - Adds restart support to `p2p_libp2p` connection on read/write failure - Adds validation of default routing and default connection configuration - Refactors and significantly simplifies routing between components - Limits usage of `EnvelopeContext` - Adds support for new CosmWasm message format in ledger plugins - Adds project loading checks and optional auto removal in `MultiAgentManager` - Adds support for reuse of threaded `Multiplexer` - Fixes bug in TAC which caused agents to make suboptimal trades - Adds support to specify dependencies on `aea-config.yaml` level - Improves release scripts - Adds lightweight Golang AEALite library - Adds support for skill-to-skill messages - Removes CLI GUI - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.10.1 (2021-02-21) - Changes default URL of `soef` connection to https - Improves teardown, retry and edge case handling of `p2p_libp2p` and `p2p_libp2p_client` connections - Adds auto-generation of private keys to `MultiAgentManager` - Exposes address getters on `MultiAgentManager` - Improves package validation error messages - Simplifies default `DecisionMakerHandler` and extracts advanced features in separate class - Fixes task manager and its usage in skills - Adds support for multi-language protocol stub generation - Adds `data_dir` usage to additional connections - Adds IO helper function for consistent file usage - Extends release helper scripts - Removes stub connection as default connection - Adds support for AEA usage without connections - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.10.0 (2021-02-11) - Removes error skill from agents which do not need it - Adds support for relay connection reconnect in ACN - Multiplexer refactoring for easier connection handling - Fix `erc1155` skill tests on CosmWasm chains - Extends docs on usage of CosmWasm chains - Adds version compatibility in `aea upgrade` command - Introduces protocol specification id and related changes for better interoperability - Adds synchronous connection base class - Exposes state setter in connection base class - Adds Yoti protocol and connection - Multiple updates to generic buyer - Adds additional automation to `MultiAgentManager`, including automated handling of certs, keys and other package specific data - Multiple test improvements and fixes - Add stricter typing and checks - Fixes to MacOS install script - Adds threading patch for web3 - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.9.2 (2021-01-21) - Fixes `CosmosApi`, in particular for CosmWasm - Fixes error output from `add-key` CLI command - Update `aea_version` in non-vendor packages when calling `upgrade` CLI command - Extend `upgrade` command to fetch newer agent if present on registry - Add support for mixed fetch mode in `MultiAgentManager` - Fixes logging overrides in `MultiAgentManager` - Configuration overrides now properly handle `None` values - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.9.1 (2021-01-14) - Fixes multiple issues with `MultiAgentManager` including overrides not being correctly applied - Restructures docs navigation - Updates `MultiAgentManager` documentation - Extends functionality of `aea upgrade` command to cover more cases - Fixes a bug in the `aea upgrade` command which prevented upgrading across version minors - Fixes a bug in `aea fetch` where the console output was inconsistent with the actual error - Fixes scaffold connection constructor - Multiple additional tests to improve stability - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.9.0 (2021-01-06) - Adds multiple bug fixes on `MultiAgentManager` - Adds `AgentConfigManager` for better programmatic configuration management - Fixes auto-filling of `aea_version` field in AEA configuration - Adds tests for confirmation skills AW2/3 - Extends `MultiAgentManager` to support proper configuration overriding - Fixes ML skills demo - Fixes environment variable resolution in configuration files - Adds support to fingerprint packages by providing a path - Adds `local-registry-sync` CLI command to sync local and remote registry - Adds support to push vendorised packages to local registry - Adds missing tests for code in documentation - Adds prompt in `scaffold protocol` CLI command to hint at protocol generator - Adds `issue-certificates` CLI command for Proof of Representation - Adds `cert_requests` support in connections for Proof of Representation - Adds support for Proof of Representation in ACN (`p2p_libp2p*` connections) - Adds automated spell checking for all `.md` files and makes related fixes - Multiple additional tests to improve stability - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.8.0 (2020-12-17) - Adds support for protocol dialogue rules validation - Fixes URL forwarding in http server connection - Revises protocols to correctly define terminal states - Adds a build command - Adds build command support for libp2p connection - Adds multiple fixes to libp2p connection - Adds prometheus connection and protocol - Adds tests for confirmation AW1 skill - Adds oracle demo docs - Replaces pickle with protobuf in all protocols - Refactors OEF models to account for semantic irregularities - Updates docs for demos relying on Ganache - Adds generic storage support - Adds configurable dialogue offloading - Fixes transaction generation on confirmation bugs - Fixes transaction processing order in all buyer skills - Extends ledger API protocol to query ledger state - Adds remove-key command in CLI - Multiple tac stability fixes - Adds support for configurable error handler - Multiple additional tests to improve stability - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.7.5 (2020-11-25) - Adds AW3 AEAs - Adds basic oracle skills and contracts - Replaces usage of Ropsten testnet with Ganache in packages - Fixes multiplexer setup when used outside AEA - Improves help command output of CLI - Adds integration tests for simple skills - Adds version check on CLI push - Adds integration tests for tac negotiation skills - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.7.4 (2020-11-18) - Replaces error skill handler usage with built in handler - Extends `MultiAgentManager` to support persistence between runs - Replaces usage of Ropsten testnet with Ganache - Adds support for symlink creation during scaffold and add - Makes contract interface loading extensible - Adds support for PEP561 - Adds integration tests for launcher command - Adds support for storage of unique page address in SOEF - Fixes publish command bug on Windows - Refactors constants usage throughout - Adds support for profiling on `aea run` - Multiple stability improvements to core asynchronous modules - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.7.3 (2020-11-12) - Extends AW AEAs - Fixes overwriting of private key files on startup - Fixes behaviour bugs - Adds tests for tac participation skill - Adds development setup guide - Improves exception logging for easier debugging - Fixes mixed mode in upgrade command - Reduces verbosity of some CLI commands - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.7.2 (2020-11-09) - Fixes some AW2 AEAs - Improves generic buyer AEA - Fixes a few backwards incompatibilities on CLI (upgrade, add, fetch) introduced in 0.7.1 - Fixes geolocation in some tests - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.7.1 (2020-11-05) - Adds two AEAs for Agent World 2 - Refactors dialogue class to optimize for memory - Refactors message class to optimize for memory - Adds mixed registry mode to CLI and makes it default - Extends upgrade command to automatically update references of non-vendor packages - Adds deployment scripts for `kubernetes` - Extends configuration set/get support for lists and dictionaries - Fixes location specifiers throughout code base - Imposes limits on length of user defined strings like author and package name - Relaxes version specifiers for some dependencies - Adds support for skills to reference connections as dependencies - Makes ledger and currency ids configurable - Adds test coverage for the tac control skills - Improves quick start guidance and adds docker images - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.7.0 (2020-10-22) - Adds two AEAs for Agent World 1 - Adds support to apply configuration overrides to CLI calls transfer and get-wealth - Adds install scripts to install AEA and dependencies on all major OS (Windows, MacOs, Ubuntu) - Adds developer mailing list opt-in step to CLI `init` - Modifies custom configurations in `aea-config` to use public id - Adds all non-optional fields in `aea-config` by default - Fixes upgrade command to properly handle dependencies of non-vendor packages - Remove all distributed packages and add them to registry - Adds public ids to all skill `init` files and makes it a requirement - Adds primitive benchmarks for libp2p node - Adds Prometheus monitoring to libp2p node - Makes body a private attribute in message base class - Renames `bodyy` to `body` in HTTP protocol - Adds support for abstract connections - Refactors protobuf schemas for protocols to avoid code duplication - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.6.3 (2020-10-16) - Adds skill testing tools and documentation - Adds human readable log output regarding configuration for `p2p_libp2p` connection - Adds support to install PyPI dependencies from `AEABuilder` and `MultiAgentManager` - Adds CLI upgrade command to upgrade entire agent project and components - Extends CLI remove command to include option to remove dependencies - Extends SOEF chain identifier support - Adds CLI transfer command to transfer wealth - Adds integration tests for skills generic buyer and seller using skill testing tool - Adds validation of component configurations when setting component configuration overrides - Multiple refactoring of internal configuration and helper objects and methods - Fix a bug on CLI push local with latest rather than version specifier - Adds `README.md` files in all agent projects - Adds agent name in logger paths of runnable objects - Fixes tac skills to work with and without ERC1155 contract - Adds additional validations on message flow - Multiple docs updates based on user feedback - Multiple additional tests and test stability fixes ## 0.6.2 (2020-10-01) - Adds `MultiAgentManager` to manage multiple agent projects programmatically - Improves SOEF connection reliability on unregister - Extends configuration classes to handle overriding configurations programmatically - Improves configuration schemas and validations - Fixes Multiplexer termination errors - Allow finer-grained override of component configurations from `aea-config` - Fixes tac controller to work with Ethereum contracts again - Fixes multiple deploy and development scripts - Introduces `isort` to development dependencies for automated import sorting - Adds reset password command to CLI - Adds support for abbreviated public ids (latest) to CLI and configurations - Adds additional documentation string linters for improved API documentation checks - Multiple docs updates including additional explanations of ACN architecture - Multiple additional tests and test stability fixes ## 0.6.1 (2020-09-17) - Adds a standalone script to deploy an ACN node - Adds filtering of out-dated addresses in DHT lookups - Updates multiple developer scripts - Increases code coverage of all protocols to 100% - Fixes a disconnection issue of the multiplexer - Extends soef connection to support additional registration commands and search responses - Extends `oef_search` protocol to include success performative and agent info in search response - Adds `README.md` files to all skills - Adds configurable exception policy handling for multiplexer - Fixes support for http headers in http server connection - Adds additional consistency checks on addresses in dialogues - Exposes decision maker address on skill context - Adds comprehensive benchmark scripts - Multiple docs updates including additional explanations of soef usage - Multiple additional tests and test stability fixes ## 0.6.0 (2020-09-01) - Makes `FetchAICrypto` default again - Bumps `web3` dependencies - Introduces support for arbitrary protocol handling by DM - Removes custom fields in signing protocol - Refactors and updates dialogue and dialogues models - Moves dialogue module to protocols module - Introduces `MultiplexerStatus` to collect aggregate connection status - Moves Address types from mail to common - Updates `FetchAICrypto` to work with Agentland - Fixes circular dependencies in helpers and configurations - Unifies contract loading with loading mechanism of other packages - Adds get-multiaddress command to CLI - Updates helpers scripts - Introduces `MultiInbox` to unify internal message handling - Adds additional linters (eradicate, more `pylint` options) - Improves error reporting in libp2p connection - Replaces all assert statements with proper exceptions - Adds skill id to envelope context for improved routing - Refactors IPC pipes - Refactors core dependencies - Adds support for multi-page agent configurations - Adds type field to all package configurations - Multiple docs updates including additional explanations of contracts usage - Multiple additional tests and test stability fixes ## 0.5.4 (2020-08-13) - Adds support for Windows in P2P connections - Makes all tests Windows compatible - Adds integration tests for P2P public DHT - Modifies contract base class to make it cross-ledger compatible - Changes dialogue reference nonce generation - Fixes tac skills (non-contract versions) - Fixes Aries identity skills - Extends cosmos crypto API to support `cosmwasm` - Adds full test coverage for framework and connection packages - Multiple docs updates including automated link integrity checks - Multiple additional tests and test stability fixes ## 0.5.3 (2020-08-05) - Adds support for re-starting agent after stopping it - Adds full test coverage for protocols generator - Adds support for dynamically adding handlers - Improves P2P connection startup reliability - Addresses P2P connection race condition with long running processes - Adds connection states in connections - Applies consistent logger usage throughout - Adds key rotation and randomised locations for integration tests - Adds request delays in SOEF connection to avoid request limits - Exposes runtime states on agent and removes agent liveness object - Adds readme files in protocols and connections - Improves edge case handling in dialogue models - Adds support for `cosmwasm` message signing - Adds test coverage for test tools - Adds dialogues models in all connections where required - Transitions ERC1155 skills and simple search to SOEF and P2P - Adds full test coverage for skills modules - Multiple docs updates - Multiple additional tests and test stability fixes ## 0.5.2 (2020-07-21) - Transitions demos to agent-land test network, P2P and SOEF connections - Adds full test coverage for helpers modules - Adds full test coverage for core modules - Adds CLI functionality to upload `README.md` files with packages - Adds full test coverage for registries module - Multiple docs updates - Multiple additional tests and test stability fixes ## 0.5.1 (2020-07-14) - Adds support for agent name being appended to all log statements - Adds redesigned GUI - Extends dialogue API for easier dialogue maintenance - Resolves blocking logic in OEF and gym connections - Adds full test coverage on AEA modules configurations, components and mail - Adds ping background task for soef connection - Adds full test coverage for all connection packages - Multiple docs updates - Multiple additional tests and test stability fixes ## 0.5.0 (2020-07-06) - Refactors all connections to be fully asynchronous friendly - Adds almost complete test coverage on connections - Adds complete test coverage for CLI and CLI GUI - Fixes CLI GUI functionality and removes OEF node dependency - Refactors P2P go code and increases test coverage - Refactors protocol generator for higher code reusability - Adds option for skills to depend on other skills - Adds abstract skills option - Adds ledger connections to execute ledger related queries and transactions, removes ledger APIs from skill context - Adds contracts registry and removes them from skill context - Rewrites all skills to be fully message based - Replaces internal messages with protocols (signing and state update) - Multiple refactoring to improve `pylint` adherence - Multiple docs updates - Multiple test stability fixes ## 0.4.1 (2020-06-15) - Updates component package module loading for skill and connection - Unifies component package loading across package types - Adds connections registry to resources - Upgrades CLI commands for easier programmatic usage - Adds `AEARunner` and `AEALauncher` for programmatic launch of multiple agents - Refactors `AEABuilder` to support reentrancy and resetting - Fixes tac packages to work with ERC1155 contract - Multiple refactoring to improve public and private access patterns - Multiple docs updates - Multiple test stability fixes ## 0.4.0 (2020-06-08) - Updates message handling in skills - Replaces serialiser implementation; all serialization is now performed framework side - Updates all skills for compatibility with new message handling - Updates all protocols and protocol generator - Updates package loading mechanism - Adds `p2p_libp2p_client` connection - Fixes CLI bugs and refactors CLI - Adds eject command to CLI - Exposes identity and connection cryptos to all connections - Updates connection loading mechanism - Updates all connections for compatibility with new loading mechanism - Extracts multiplexer into its own module - Implements list all CLI command - Updates wallet to split into several crypto stores - Refactors component registry and resources - Extends soef connection functionality - Implements `AEABuilder` reentrancy - Updates `p2p_libp2p` connection - Adds support for configurable runtime - Refactors documentation - Multiple docs updates - Multiple test stability fixes ## 0.3.3 (2020-05-24) - Adds option to pass ledger APIs to `AEABuilder` - Refactors decision maker: separates interface and implementation; adds loading mechanisms so framework users can provide their own implementation - Adds asynchronous and synchronous agent loop implementations; agent can be run in both `sync` and `async` mode - Completes transition to atomic CLI commands (fetch, generate, scaffold) - Refactors dialogue API: adds much simplified API; updates generator accordingly; updates skills - Adds support for crypto module extensions: framework users can register their own crypto module - Adds crypto module and ledger support for cosmos - Adds simple-oef (soef) connection - Adds `p2p_libp2p` connection for true P2P connectivity - Adds PyPI dependency consistency checks for AEA projects - Refactors CLI for improved programmatic usage of components - Adds skill exception handling policies and configuration options - Adds comprehensive documentation of configuration files - Multiple docs updates - Multiple test stability fixes ## 0.3.2 (2020-05-07) - Adds dialogue generation functionality to protocol generator - Fixes add CLI commands to be atomic - Adds Windows platform support - Stability improvements to test pipeline - Improves test coverage of CLI - Implements missing doc tests - Implements end-to-end tests for all skills - Adds missing agent projects to registry - Improves `AEABuilder` class for programmatic usage - Exposes missing AEA configurations on agent configuration file - Extends Aries demo - Adds method to check stdout for test cases - Adds code of conduct and security guidelines to repo - Multiple docs updates - Multiple additional unit tests - Multiple additional minor fixes and changes ## 0.3.1 (2020-04-27) - Adds `p2p_stub` connection - Adds `p2p_noise` connection - Adds webhook connection - Upgrades error handling for error skill - Fixes default timeout on main agent loop and provides setter in `AEABuilder` - Adds multithreading support for launch command - Provides support for keyword arguments to AEA constructor to be set on skill context - Renames `ConfigurationType` with `PackageType` for consistency - Provides a new `AEATestCase` class for improved testing - Adds execution time limits for act/react calls - TAC skills refactoring and contract integration - Supports contract dependencies being added automatically - Adds HTTP example skill - Allows for skill inactivation during initialisation - Improves error messages on skill loading errors - Improves `README.md` files, particularly for PyPI - Adds support for Location based queries and descriptions - Refactors skills tests to use `AEATestCase` - Adds fingerprint and scaffold CLI command for contract - Adds multiple additional docs tests - Makes task manager initialize pool lazily - Multiple docs updates - Multiple additional unit tests - Multiple additional minor fixes and changes ## 0.3.0 (2020-04-02) - Introduces IPFS based hashing of files to detect changes, ensure consistency and allow for content addressing - Introduces `aea fingerprint` command to CLI - Adds support for contract type packages which wrap smart contracts and their APIs - Introduces `AEABuilder` class for much improved programmatic usage of the framework - Moves protocol generator into alpha stage for light protocols - Switches CLI to use remote registry by default - Comprehensive documentation updates on new and existing features - Additional demos to introduce the contracts functionality - Protocol, Contract, Skill and Connection inherits from the same class, Component - Improved APIs for Configuration classes - All protocols now generated with protocol generator - Multiple additional unit tests - Multiple additional minor fixes and changes ## 0.2.4 (2020-03-25) - Breaking change to all protocols as we transition to auto-generated protocols - Fixes to protocol generator to move it to alpha status - Updates to documentation on protocols and OEF search and communication nodes - Improvements and fixes to AEA launch command - Multiple docs updates and restructuring - Multiple additional minor fixes and changes ## 0.2.3 (2020-03-19) - Fixes stub connection file I/O - Fixes OEF connection teardown - Fixes CLI GUI subprocesses issues - Adds support for URI based routing of envelopes - Improves skill guide by adding a service provider agent - Protocol generator bug fixes - Add `aea_version` field to package YAML files for version management - Multiple docs updates and restructuring - Multiple additional minor fixes and changes ## 0.2.2 (2020-03-09) - Fixes registry to only load registered packages - Migrates default protocol to generator produced version - Adds http connection and http protocol - Adds CLI `init` command for easier setting of author - Refactoring and behind the scenes improvements to CLI - Multiple docs updates - Protocol generator improvements and fixes - Adds CLI launch command to launch multiple agents - Increases test coverage for AEA package and tests package - Make project comply with PEP 518 - Multiple additional minor fixes and changes ## 0.2.1 (2020-02-21) - Add minimal `aea install` - Updates finite state machine behaviour to use any simple behaviour in states - Adds example of programmatic and CLI based AEAs interacting - Exposes the logger on the skill context - Adds serialization (encoding/decoding) support to protocol generator - Adds additional docs and videos - Introduces test coverage to all code in docs - Increases test coverage for AEA package - Multiple additional minor fixes and changes ## 0.2.0 (2020-02-07) - Skills can now programmatically register behaviours - Tasks are no longer a core component of the skill, the functor pattern is used - Refactors the task manager - Adds nonces to transaction data so transactions can be verified - Adds documentation for the protocol generator - Fixes several compatibility issues between CLI and registry - Adds skills to connect a thermometer to an AEA - Adds generic buyer and seller skills - Adds much more documentation on AEA vs MVC frameworks, core components, new guides and more - Removes the wallet from the agent constructor and moves it to the AEA constructor - Allows behaviours to be initialized from a skill - Adds multiple improvements to the protocol generator, including custom types and serialization - Removes the default crypto object - Replaces `SharedClass` with `Model` taxonomy for easier transition for web developers - Adds bandit to CLI for security checks - Makes private key paths in configurations a dictionary so values can be set from CLI - Introduces Identity object - Increases test coverage - Multiple additional minor fixes and changes ## 0.1.17 (2020-01-27) - Add programmatic mode flag to AEA - Introduces vendorised project structure - Adds further tests for decision maker - Upgrades sign transaction function for Ethereum API proxy - Adds black and bugbear to linters - Applies public id usage throughout AEA business logic - Adds guide on how to deploy an AEA on a raspberry pi - Addresses multiple issues in the protocol generator - Fixes `aea-config` - Adds CLI commands to create wealth and get wealth and address - Change default author and license - Adds guide on agent vs AEAs - Updates docs and improves guides - Adds support for inactivating skills programmatically - Makes decision maker run in separate thread - Multiple additional minor fixes and changes ## 0.1.16 (2020-01-12) - Completes tac skills implementation - Adds default ledger field to agent configuration - Converts ledger APIs to dictionary fields in agent configuration - Introduces public ids to CLI and deprecate usage of package names only - Adds local push and public commands to CLI - Introduces ledger API abstract class - Unifies import paths for static and dynamic imports - Disambiguates import paths by introducing pattern of `packages.author.package_type_pluralized.package_name` - Adds agent directory to packages with some samples - Adds protocol generator and exposes on CLI - Removes unused configuration fields - Updates docs to align with recent changes - Adds additional tests on CLI - Multiple additional minor fixes and changes ## 0.1.15 (2019-12-19) - Moves non-default packages from AEA to packages directory - Supports get & set on package configurations - Changes skill configuration resource types from lists to dictionaries - Adds additional features to decision maker - Refactors most protocols and improves their API - Removes multiple unintended side-effects of the CLI - Improves dependency referencing in configuration files - Adds push and publish functionality to CLI - Introduces simple and composite behaviours and applies them in skills - Adds URI to envelopes - Adds guide for programmatic assembly of an AEA - Adds guide on agent-oriented development - Multiple minor doc updates - Adds additional tests - Multiple additional minor fixes and changes ## 0.1.14 (2019-11-29) - Removes dependency on OEF SDK's FIPA API - Replaces dialogue id with dialogue references - Improves CLI logging and list/search command output - Introduces multiplexer and removes mailbox - Adds much improved tac skills - Adds support for CLI integration with registry - Increases test coverage to 99% - Introduces integration tests for skills and examples - Adds support to run multiple connections from CLI - Updates the docs and adds UML diagrams - Multiple additional minor fixes and changes ## 0.1.13 (2019-11-08) - Adds envelope serialiser - Adds support for programmatically initializing an AEA - Adds some tests for the GUI and other components - Exposes connection status to skills - Updates OEF connection to re-establish dropped connections - Updates the car park agent - Multiple additional minor fixes and changes ## 0.1.12 (2019-11-01) - Adds TCP connection (server and client) - Fixes some examples and docs - Refactors crypto modules and adds additional tests - Multiple additional minor fixes and changes ## 0.1.11 (2019-10-30) - Adds Python 3.8 test coverage - Adds almost complete test coverage on AEA package - Adds filter concept for message routing - Adds ledger integrations for Fetch.ai and Ethereum - Adds car park examples and ledger examples - Multiple additional minor fixes and changes ## 0.1.10 (2019-10-19) - Compatibility fixes for Ubuntu and Windows platforms - Multiple additional minor fixes and changes ## 0.1.9 (2019-10-18) - Stability improvements - Higher test coverage, including on Python 3.6 - Multiple additional minor fixes and changes ## 0.1.8 (2019-10-18) - Multiple bug fixes and improvements to GUI of CLI - Adds full test coverage on CLI - Improves docs - Multiple additional minor fixes and changes ## 0.1.7 (2019-10-14) - Adds GUI to interact with CLI - Adds new connection stub to read from/write to file - Adds ledger entities (fetchai and Ethereum); creates wallet for ledger entities - Adds more documentation and fixes old one - Multiple additional minor fixes and changes ## 0.1.6 (2019-10-04) - Adds several new skills - Extended docs on framework and skills - Introduces core framework components like decision maker and shared classes - Multiple additional minor fixes and changes ## 0.1.5 (2019-09-26) - Adds scaffolding command to the CLI tool - Extended docs - Increased test coverage - Multiple additional minor fixes and changes ## 0.1.4 (2019-09-20) - Adds CLI functionality to add connections - Multiple additional minor fixes and changes ## 0.1.3 (2019-09-19) - Adds Jenkins for CI - Adds docker develop image - Parses dependencies of connections/protocols/skills on the fly - Adds validations of configuration files - Adds first two working skills and fixes gym examples - Adds docs - Multiple additional minor fixes and changes ## 0.1.2 (2019-09-16) - Adds AEA CLI tool. - Adds AEA skills framework. - Introduces static typing checks across AEA, using `Mypy`. - Extends gym example ## 0.1.1 (2019-09-04) - Provides examples and fixes. ## 0.1.0 (2019-08-21) - Initial release of the package. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2019 Fetch.AI Limited Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MANIFEST.in ================================================ include README.md LICENSE HISTORY.md AUTHORS.md SECURITY.md CODE_OF_CONDUCT.md Pipfile mkdocs.yml tox.ini pytest.ini strategy.ini recursive-include aea *.json *.yaml *.proto *.ico *png *.html *.js *.css *.md *.cfg json1.dll recursive-include docs * recursive-include examples * recursive-include packages * recursive-include plugins * recursive-include scripts * recursive-include tests * ================================================ FILE: Makefile ================================================ AEA_SRC_DIR := aea BENCHMARK_DIR := benchmark EXAMPLES_DIR := examples LIBS_DIR := libs PACKAGES_DIR := packages PLUGINS_DIR := plugins SCRIPTS_DIR := scripts AEA_TESTS_DIR := tests AEA_CORE_TESTS_DIRS := tests/test_aea tests/test_aea_extra ./tests/test_docs EXAMPLES_TESTS_DIRS := tests/test_examples PACKAGES_TESTS_DIRS := packages/fetchai/protocols packages/fetchai/connections packages/fetchai/skills ./tests/test_packages ./tests/test_packages_for_aea_tests ./tests/test_aea_core_packages DOCS_TESTS_DIR := tests/test_docs CONNECTIONS_DIR := packages/fetchai/connections CONTRACTS_DIR := packages/fetchai/contracts PROTOCOLS_DIR := packages/fetchai/protocols SKILLS_DIR := packages/fetchai/skills PLUGIN_FETCHAI_SRC := plugins/aea-ledger-fetchai/aea_ledger_fetchai PLUGIN_ETHEREUM_SRC := plugins/aea-ledger-ethereum/aea_ledger_ethereum PLUGIN_COSMOS_SRC := plugins/aea-ledger-cosmos/aea_ledger_cosmos PLUGIN_CLI_IPFS_SRC := plugins/aea-cli-ipfs/aea_cli_ipfs PLUGINS_SRC := $(PLUGIN_FETCHAI_SRC) $(PLUGIN_ETHEREUM_SRC) $(PLUGIN_COSMOS_SRC) $(PLUGIN_CLI_IPFS_SRC) PLUGIN_FETCHAI_TESTS := plugins/aea-ledger-fetchai/tests PLUGIN_ETHEREUM_TESTS := plugins/aea-ledger-ethereum/tests PLUGIN_COSMOS_TESTS := plugins/aea-ledger-cosmos/tests PLUGIN_CLI_IPFS_TESTS := plugins/aea-cli-ipfs/tests PLUGINS_TESTS := $(PLUGIN_FETCHAI_TESTS) $(PLUGIN_ETHEREUM_TESTS) $(PLUGIN_COSMOS_TESTS) $(PLUGIN_CLI_IPFS_TESTS) PLUGIN_FETCHAI := plugins/aea-ledger-fetchai PLUGIN_ETHEREUM := plugins/aea-ledger-ethereum PLUGIN_COSMOS := plugins/aea-ledger-cosmos PLUGIN_CLI_IPFS := plugins/aea-cli-ipfs PYTHON_CODE_DIRS := $(AEA_SRC_DIR) $(BENCHMARK_DIR) $(EXAMPLES_DIR) $(PACKAGES_DIR) $(PLUGINS_DIR) $(SCRIPTS_DIR) $(AEA_TESTS_DIR) ######################################## ### Initialise dev environment ######################################## # Create a new poetry virtual environment with all the necessary dependencies installed. # Once finished, `poetry shell` to enter the virtual environment v := $(shell pip -V | grep virtualenvs) .PHONY: new-env new-env: clean if [ -z "$v" ];\ then\ poetry install --with dev,docs,packages,tools,testing,types;\ poetry run pip install --no-deps file:plugins/aea-ledger-ethereum;\ poetry run pip install --no-deps file:plugins/aea-ledger-cosmos;\ poetry run pip install --no-deps file:plugins/aea-ledger-fetchai;\ poetry run pip install --no-deps file:plugins/aea-cli-ipfs;\ echo "Enter virtual environment with all development dependencies now: 'poetry shell'.";\ else\ echo "In a virtual environment! Exit first: 'exit'.";\ fi ######################################## ### Tests ######################################## # Run all tests .PHONY: test test: test-aea-all test-plugins # Run all aea tests .PHONY: test-aea-all test-aea-all: pytest -rfE --doctest-modules $(AEA_TESTS_DIR) --cov=$(AEA_SRC_DIR) --cov=$(CONNECTIONS_DIR) --cov=$(CONTRACTS_DIR) --cov=$(PROTOCOLS_DIR) --cov=$(SKILLS_DIR) --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; .PHONY: test-aea-core test-aea-core: pytest -rfE --doctest-modules $(AEA_CORE_TESTS_DIRS) --cov=$(AEA_SRC_DIR) --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; .PHONY: test-packages test-packages: pytest -rfE --doctest-modules $(PACKAGES_TESTS_DIRS) --cov=$(AEA_SRC_DIR) --cov=$(CONNECTIONS_DIR) --cov=$(CONTRACTS_DIR) --cov=$(PROTOCOLS_DIR) --cov=$(SKILLS_DIR) --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; .PHONY: test-docs test-docs: pytest -rfE --doctest-modules $(DOCS_TESTS_DIR) --cov=$(AEA_SRC_DIR) --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; .PHONY: test-examples test-examples: pytest -rfE --doctest-modules $(EXAMPLES_TESTS_DIRS) --cov=$(AEA_SRC_DIR) --cov=$(CONNECTIONS_DIR) --cov=$(CONTRACTS_DIR) --cov=$(PROTOCOLS_DIR) --cov=$(SKILLS_DIR) --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; # Run all plugin tests .PHONY: test-plugins test-plugins: pytest -rfE $(PLUGIN_FETCHAI_TESTS) --cov=aea_ledger_fetchai --cov-report=term-missing --cov-config=pyproject.toml pytest -rfE $(PLUGIN_ETHEREUM_TESTS) --cov=aea_ledger_ethereum --cov-report=term-missing --cov-config=pyproject.toml pytest -rfE $(PLUGIN_COSMOS_TESTS) --cov=aea_ledger_cosmos --cov-report=term-missing --cov-config=pyproject.toml pytest -rfE $(PLUGIN_CLI_IPFS_TESTS) --cov=aea_cli_ipfs --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; # Run tests for a particular python package .PHONY: test-sub test-sub: pytest -rfE --doctest-modules $(AEA_TESTS_DIR)/test_$(tdir) --cov=aea.$(dir) --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; # Run tests for a particular aea package .PHONY: test-sub-p test-sub-p: pytest -rfE --doctest-modules $(AEA_TESTS_DIR)/test_packages/test_$(tdir) --cov=packages.fetchai.$(dir) --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; # Produce the coverage report. Can see a report summary on the terminal. # Detailed report on all modules are placed under /htmlcov .PHONY: coverage-report coverage-report: coverage report -m -i coverage html ######################################## ### Code Styling ######################################## # Automatically run black and isort to format the code, and run flake8 and vulture checks .PHONY: lint lint: black isort flake8 vulture # Automatically format the code using black .PHONY: black black: black $(PYTHON_CODE_DIRS) # Automatically sort the imports .PHONY: isort isort: isort $(PYTHON_CODE_DIRS) # Check the code format .PHONY: black-check black-check: black --check --verbose $(PYTHON_CODE_DIRS) # Check the imports are sorted .PHONY: isort-check isort-check: isort --check-only --verbose $(PYTHON_CODE_DIRS) # Run flake8 linter .PHONY: flake8 flake8: flake8 $(PYTHON_CODE_DIRS) # Check for unused code .PHONY: vulture vulture: vulture $(AEA_SRC_DIR) scripts/whitelist.py --exclude '*_pb2.py' ######################################## ### Security & safety checks ######################################## # Run bandit and safety .PHONY: security security: bandit safety # Check the security of the code .PHONY: bandit bandit: bandit -r $(AEA_SRC_DIR) $(BENCHMARK_DIR) $(EXAMPLES_DIR) $(PACKAGES_DIR) $(PLUGIN_FETCHAI_SRC) $(PLUGIN_ETHEREUM_SRC) $(PLUGIN_COSMOS_SRC) $(PLUGIN_CLI_IPFS_SRC) bandit -s B101 -r $(AEA_TESTS_DIR) $(SCRIPTS_DIR) # Check the security of the code for known vulnerabilities .PHONY: safety safety: safety check -i 44610 -i 50473 ######################################## ### Linters ######################################## # Check types (statically) using mypy .PHONY: mypy mypy: mypy aea packages benchmark --disallow-untyped-defs mypy examples --check-untyped-defs mypy scripts mypy tests --exclude "serialization.py" # Lint the code using pylint .PHONY: pylint pylint: pylint -j0 -d E1136 $(AEA_SRC_DIR) $(BENCHMARK_DIR) $(EXAMPLES_DIR) $(PACKAGES_DIR) $(SCRIPTS_DIR) $(PLUGIN_FETCHAI_SRC) $(PLUGIN_ETHEREUM_SRC) $(PLUGIN_COSMOS_SRC) $(PLUGIN_CLI_IPFS_SRC) ######################################## ### License and copyright checks ######################################## # Check dependency licenses .PHONY: liccheck liccheck: poetry export > tmp-requirements.txt liccheck -s strategy.ini -r tmp-requirements.txt -l PARANOID rm -frv tmp-requirements.txt # Check that the relevant files have appropriate Copyright header .PHONY: copyright-check copyright-check: python scripts/check_copyright_notice.py --directory . ######################################## ### Docs ######################################## # Build documentation .PHONY: docs docs: mkdocs build --clean # Live documentation server .PHONY: docs-live docs-live: mkdocs serve # Generate API documentation (ensure you add the new pages created into /mkdocs.yml --> nav) .PHONY: generate-api-docs generate-api-docs: python scripts/generate_api_docs.py $(args) # Check links are live in the documentation .PHONY: check-doc-links check-doc-links: python scripts/check_doc_links.py ######################################## ### Poetry Lock ######################################## # Updates the poetry lock poetry.lock: pyproject.toml poetry lock ######################################## ### Clear the caches and temporary files ######################################## # clean the caches and temporary files and directories .PHONY: clean clean: clean-build clean-pyc clean-test clean-docs .PHONY: clean-build clean-build: rm -fr build/ rm -fr dist/ rm -fr .eggs/ rm -fr pip-wheel-metadata find . -name '*.egg-info' -exec rm -fr {} + find . -name '*.egg' -exec rm -fr {} + rm -rf plugins/*/build rm -rf plugins/*/dist .PHONY: clean-docs clean-docs: rm -fr site/ .PHONY: clean-pyc clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + find . -name '.DS_Store' -exec rm -fr {} + .PHONY: clean-test clean-test: rm -fr .tox/ rm -f .coverage find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; rm -fr coverage.xml rm -fr htmlcov/ rm -fr .hypothesis rm -fr .pytest_cache rm -fr .mypy_cache/ rm -fr input_file rm -fr output_file find . -name 'log.txt' -exec rm -fr {} + find . -name 'log.*.txt' -exec rm -fr {} + ######################################## ### Packages ######################################## # Update package hashes .PHONY: update-package-hashes update-package-hashes: python scripts/generate_ipfs_hashes.py # Run all package checks .PHONY: package-checks package-checks: check-package-hashes check-package-versions-in-docs check-packages # Check package hashes .PHONY: check-package-hashes check-package-hashes: python scripts/generate_ipfs_hashes.py --check # Check correct package version in the docs .PHONY: check-package-versions-in-docs check-package-versions-in-docs: python scripts/check_package_versions_in_docs.py # Perform various checks on packages .PHONY: check-packages check-packages: python scripts/check_packages.py ######################################## ### Other checks ######################################## # Check that libp2p code in libs and connection aren't different .PHONY: libp2p-diffs libp2p-diffs: diff libs/go/libp2p_node packages/fetchai/connections/p2p_libp2p/libp2p_node -r # Check that plugins for Cosmos and Fetch.ai, and Plugins' and main Licenses aren't different .PHONY: plugin-diffs plugin-diffs: diff $(PLUGIN_COSMOS_SRC)/cosmos.py $(PLUGIN_FETCHAI_SRC)/_cosmos.py diff LICENSE $(PLUGIN_COSMOS)/LICENSE diff LICENSE $(PLUGIN_ETHEREUM)/LICENSE diff LICENSE $(PLUGIN_FETCHAI)/LICENSE ######################################## ### Build ######################################## # Build the project .PHONY: dist dist: clean poetry build protolint_install: GO111MODULE=on GOPATH=~/go go install github.com/yoheimuta/protolint/cmd/protolint@v0.27.0 protolint: PATH=${PATH}:${GOPATH}/bin/:~/go/bin protolint lint -config_path=./protolint.yaml -fix ./aea/mail ./packages/fetchai/protocols protolint_install_win: powershell -command '$$env:GO111MODULE="on"; go install github.com/yoheimuta/protolint/cmd/protolint@v0.27.0' protolint_win: protolint lint -config_path=./protolint.yaml -fix ./aea/mail ./packages/fetchai/protocols ================================================ FILE: README.md ================================================

AEA Framework

Create Autonomous Economic Agents (AEAs)

PyPI PyPI - Python Version License License
AEA framework sanity checks and tests Codecov Discord conversation

The AEA framework allows you to create **Autonomous Economic Agents**: - An AEA is an Agent, representing an individual, family, organisation or object (a.k.a. its "owner") in the digital world. It looks after its owner's interests and has their preferences in mind when acting on their behalf. - AEAs are Autonomous; acting with no, or minimal, interference from their owners. - AEAs have a narrow and specific focus: creating Economic value for their owners.

AEA Video

## To install 1. Ensure you have Python (version `3.8`, `3.9` or `3.10`). 2. (optional) Use a virtual environment (e.g. [`pipenv`][pipenv] or [`poetry`][poetry]). 3. Install: `pip install aea[all]` Please see the [installation page][docs-install] for more details. ## Documentation The full documentation, including how to get started, can be found [here][docs]. ## Contributing All contributions are very welcome! Remember, contribution is not only PRs and code, but any help with docs or helping other developers solve their issues are very appreciated! Read below to learn how you can take part in the AEA project. ### Code of Conduct Please be sure to read and follow our [Code of Conduct][coc]. By participating, you are expected to uphold this code. ### Contribution Guidelines Read our [contribution guidelines][contributing] to learn about our issue and PR submission processes, coding rules, and more. ### Development Guidelines Read our [development guidelines][developing] to learn about the development processes and workflows when contributing to different parts of the AEA project. ### Issues, Questions and Discussions We use [GitHub Issues][issues] for tracking requests and bugs, and [GitHub Discussions][discussion] for general questions and discussion. ## License The AEA project is licensed under [Apache License 2.0][license]. [poetry]: https://python-poetry.org [pipenv]: https://pypi.org/project/pipenv/ [docs]: https://docs.fetch.ai [contributing]: https://github.com/fetchai/agents-aea/blob/main/CONTRIBUTING.md [developing]: https://github.com/fetchai/agents-aea/blob/main/DEVELOPING.md [coc]: https://github.com/fetchai/agents-aea/blob/main/CODE_OF_CONDUCT.md [discussion]: https://github.com/fetchai/agents-aea/discussions [issues]: https://github.com/fetchai/agents-aea/issues [license]: https://github.com/fetchai/agents-aea/blob/main/LICENSE [docs-install]: https://docs.fetch.ai/aea/installation/ ================================================ FILE: SECURITY.md ================================================ # Security Policy This document outlines security procedures and general policies for the `aea` project. ## Supported Versions The following table shows which versions of `aea` are currently being supported with security updates. | Version | Supported | |-----------|--------------------| | `1.2.x` | :white_check_mark: | | `< 1.2.0` | :x: | ## Reporting a Vulnerability The `aea` team and community take all security bugs in `aea` seriously. Thank you for improving the security of `aea`. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. Report security bugs by emailing `developer@fetch.ai`. The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavour to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. Report security bugs in third-party modules to the person or team maintaining the module. ## Disclosure Policy When the security team receives a security bug report, they will assign it to a primary handler. This person will coordinate the fix and release process, involving the following steps: - Confirm the problem and determine the affected versions. - Audit code to find any potential similar problems. - Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible to PyPI. ## Comments on this Policy If you have suggestions on how this process could be improved please submit a pull request. ================================================ FILE: aea/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Contains the AEA package.""" import inspect import os from packaging.version import Version import aea.crypto # triggers registry population from aea.__version__ import ( __author__, __copyright__, __description__, __license__, __title__, __url__, __version__, ) from aea.crypto.plugin import load_all_plugins AEA_DIR = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore load_all_plugins() def get_current_aea_version() -> Version: """Get current version.""" return Version(__version__) ================================================ FILE: aea/__version__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Specifies the version of the AEA package.""" __title__ = "aea" __description__ = "Autonomous Economic Agent framework" __url__ = "https://github.com/fetchai/agents-aea.git" __version__ = "1.2.5" __author__ = "Fetch.AI Limited" __license__ = "Apache-2.0" __copyright__ = "2022 Fetch.AI Limited" ================================================ FILE: aea/abstract_agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the interface definition of the abstract agent.""" import datetime from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List, Optional, Tuple from aea.mail.base import Envelope class AbstractAgent(ABC): """This class provides an abstract base interface for an agent.""" @property @abstractmethod def name(self) -> str: """Get agent's name.""" @property @abstractmethod def storage_uri(self) -> Optional[str]: """Return storage uri.""" @abstractmethod def start(self) -> None: """ Start the agent. :return: None """ @abstractmethod def stop(self) -> None: """ Stop the agent. :return: None """ @abstractmethod def setup(self) -> None: """ Set up the agent. :return: None """ @abstractmethod def act(self) -> None: """ Perform actions on period. :return: None """ @abstractmethod def handle_envelope(self, envelope: Envelope) -> None: """ Handle an envelope. :param envelope: the envelope to handle. :return: None """ @abstractmethod def get_periodic_tasks( self, ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]]: """ Get all periodic tasks for agent. :return: dict of callable with period specified """ @abstractmethod def get_message_handlers(self) -> List[Tuple[Callable[[Any], None], Callable]]: """ Get handlers with message getters. :return: List of tuples of callables: handler and coroutine to get a message """ @abstractmethod def exception_handler( self, exception: Exception, function: Callable ) -> Optional[bool]: """ Handle exception raised during agent main loop execution. :param exception: exception raised :param function: a callable exception raised in. :return: skip exception if True, otherwise re-raise it """ @abstractmethod def teardown(self) -> None: """ Tear down the agent. :return: None """ ================================================ FILE: aea/aea.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of an autonomous economic agent (AEA).""" import datetime from asyncio import AbstractEventLoop from logging import Logger from multiprocessing.pool import AsyncResult from typing import ( Any, Callable, Collection, Dict, List, Optional, Sequence, Tuple, Type, cast, ) from aea.agent import Agent from aea.agent_loop import AsyncAgentLoop, BaseAgentLoop, SyncAgentLoop from aea.configurations.base import PublicId from aea.configurations.constants import ( DEFAULT_BUILD_DIR_NAME, DEFAULT_SEARCH_SERVICE_ADDRESS, ) from aea.context.base import AgentContext from aea.crypto.ledger_apis import DEFAULT_CURRENCY_DENOMINATIONS from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMakerHandler from aea.error_handler.base import AbstractErrorHandler from aea.error_handler.default import ErrorHandler as DefaultErrorHandler from aea.exceptions import AEAException, _StopRuntime from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.helpers.logging import AgentLoggerAdapter, WithLogger, get_logger from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Message, Protocol from aea.registries.filter import Filter from aea.registries.resources import Resources from aea.skills.base import Behaviour, Handler class AEA(Agent): """This class implements an autonomous economic agent.""" RUN_LOOPS: Dict[str, Type[BaseAgentLoop]] = { "async": AsyncAgentLoop, "sync": SyncAgentLoop, } DEFAULT_RUN_LOOP: str = "async" DEFAULT_BUILD_DIR_NAME = DEFAULT_BUILD_DIR_NAME def __init__( self, identity: Identity, wallet: Wallet, resources: Resources, data_dir: str, loop: Optional[AbstractEventLoop] = None, period: float = 0.05, execution_timeout: float = 0, max_reactions: int = 20, error_handler_class: Optional[Type[AbstractErrorHandler]] = None, error_handler_config: Optional[Dict[str, Any]] = None, decision_maker_handler_class: Optional[Type[DecisionMakerHandler]] = None, decision_maker_handler_config: Optional[Dict[str, Any]] = None, skill_exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum.propagate, connection_exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum.propagate, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, default_ledger: Optional[str] = None, currency_denominations: Optional[Dict[str, str]] = None, default_connection: Optional[PublicId] = None, default_routing: Optional[Dict[PublicId, PublicId]] = None, connection_ids: Optional[Collection[PublicId]] = None, search_service_address: str = DEFAULT_SEARCH_SERVICE_ADDRESS, storage_uri: Optional[str] = None, task_manager_mode: Optional[str] = None, **kwargs: Any, ) -> None: """ Instantiate the agent. :param identity: the identity of the agent :param wallet: the wallet of the agent. :param resources: the resources (protocols and skills) of the agent. :param data_dir: directory where to put local files. :param loop: the event loop to run the connections. :param period: period to call agent's act :param execution_timeout: amount of time to limit single act/handle to execute. :param max_reactions: the processing rate of envelopes per tick (i.e. single loop). :param error_handler_class: the class implementing the error handler :param error_handler_config: the configuration of the error handler :param decision_maker_handler_class: the class implementing the decision maker handler to be used. :param decision_maker_handler_config: the configuration of the decision maker handler :param skill_exception_policy: the skill exception policy enum :param connection_exception_policy: the connection exception policy enum :param loop_mode: loop_mode to choose agent run loop. :param runtime_mode: runtime mode (async, threaded) to run AEA in. :param default_ledger: default ledger id :param currency_denominations: mapping from ledger id to currency denomination :param default_connection: public id to the default connection :param default_routing: dictionary for default routing. :param connection_ids: active connection ids. Default: consider all the ones in the resources. :param search_service_address: the address of the search service used. :param storage_uri: optional uri to set generic storage :param task_manager_mode: task manager mode (threaded) to run tasks with. :param kwargs: keyword arguments to be attached in the agent context namespace. """ self._skills_exception_policy = skill_exception_policy self._connection_exception_policy = connection_exception_policy aea_logger = AgentLoggerAdapter( logger=get_logger(__name__, identity.name), agent_name=identity.name, ) self._resources = resources super().__init__( identity=identity, connections=[], loop=loop, period=period, loop_mode=loop_mode, runtime_mode=runtime_mode, storage_uri=storage_uri, logger=cast(Logger, aea_logger), task_manager_mode=task_manager_mode, ) default_routing = default_routing if default_routing is not None else {} connection_ids = connection_ids or [] connections = [ c for c in self.resources.get_all_connections() if (not connection_ids) or (c.connection_id in connection_ids) ] if not bool(self.resources.get_all_connections()): self.logger.warning( "Resource's connections list is empty! Instantiating AEA without connections..." ) elif bool(self.resources.get_all_connections()) and not bool(connections): self.logger.warning( # pragma: nocover "No connection left after filtering! Instantiating AEA without connections..." ) self._set_runtime_and_mail_boxes( runtime_class=self._get_runtime_class(), loop_mode=loop_mode, loop=loop, multiplexer_options=dict( connections=connections, default_routing=default_routing, default_connection=default_connection, protocols=self.resources.get_all_protocols(), ), ) self.max_reactions = max_reactions if decision_maker_handler_class is None: from aea.decision_maker.default import ( # isort:skip # pylint: disable=import-outside-toplevel DecisionMakerHandler as DefaultDecisionMakerHandler, ) decision_maker_handler_class = DefaultDecisionMakerHandler if decision_maker_handler_config is None: decision_maker_handler_config = {} decision_maker_handler = decision_maker_handler_class( identity=identity, wallet=wallet, config=decision_maker_handler_config ) self.runtime.set_decision_maker(decision_maker_handler) if error_handler_class is None: error_handler_class = DefaultErrorHandler if error_handler_config is None: error_handler_config = {} self._error_handler = error_handler_class(**error_handler_config) default_ledger_id = ( default_ledger if default_ledger is not None else identity.default_address_key ) currency_denominations = ( currency_denominations if currency_denominations is not None else DEFAULT_CURRENCY_DENOMINATIONS ) self._context = AgentContext( self.identity, self.runtime.multiplexer.connection_status, self.outbox, self.runtime.decision_maker.message_in_queue, decision_maker_handler.context, self.runtime.task_manager, default_ledger_id, currency_denominations, default_connection, default_routing, search_service_address, decision_maker_handler.self_address, data_dir, storage_callable=lambda: self.runtime.storage, build_dir=self.get_build_dir(), send_to_skill=self.runtime.agent_loop.send_to_skill, **kwargs, ) self._execution_timeout = execution_timeout self._filter = Filter( self.resources, self.runtime.decision_maker.message_out_queue ) self._setup_loggers() @classmethod def get_build_dir(cls) -> str: """Get agent build directory.""" return cls.DEFAULT_BUILD_DIR_NAME @property def context(self) -> AgentContext: """Get (agent) context.""" return self._context @property def resources(self) -> Resources: """Get resources.""" return self._resources @resources.setter def resources(self, resources: "Resources") -> None: """Set resources.""" self._resources = resources @property def filter(self) -> Filter: """Get the filter.""" return self._filter @property def active_behaviours(self) -> List[Behaviour]: """Get all active behaviours to use in act.""" return self.filter.get_active_behaviours() def setup(self) -> None: """ Set up the agent. Calls setup() on the resources. """ self.resources.setup() def act(self) -> None: """ Perform actions. Adds new handlers and behaviours for use/execution by the runtime. """ self.filter.handle_new_handlers_and_behaviours() def _get_error_handler(self) -> AbstractErrorHandler: """Get error handler.""" return self._error_handler def _get_msg_and_handlers_for_envelope( self, envelope: Envelope ) -> Tuple[Optional[Message], List[Handler]]: """Get the msg and its handlers.""" protocol = self.resources.get_protocol_by_specification_id( envelope.protocol_specification_id ) error_handler = self._get_error_handler() if protocol is None: error_handler.send_unsupported_protocol(envelope, self.logger) return None, [] msg, handlers = self._handle_decoding(envelope, protocol, error_handler) return msg, handlers def _handle_decoding( self, envelope: Envelope, protocol: Protocol, error_handler: AbstractErrorHandler, ) -> Tuple[Optional[Message], List[Handler]]: handlers = self.filter.get_active_handlers( protocol.public_id, envelope.to_as_public_id ) if len(handlers) == 0: reason = ( f"no active handler for protocol={protocol.public_id} in skill={envelope.to_as_public_id}" if envelope.is_component_to_component_message else f"no active handler for protocol={protocol.public_id}" ) error_handler.send_no_active_handler(envelope, reason, self.logger) return None, [] if isinstance(envelope.message, Message): msg = envelope.message return msg, handlers try: msg = protocol.serializer.decode(envelope.message) msg.sender = envelope.sender msg.to = envelope.to return msg, handlers except Exception as e: # pylint: disable=broad-except # thats ok, because we send the decoding error back error_handler.send_decoding_error(envelope, e, self.logger) return None, [] def handle_envelope(self, envelope: Envelope) -> None: """ Handle an envelope. Performs the following: - fetching the protocol referenced by the envelope, and - handling if the protocol is unsupported, using the error handler, or - handling if there is a decoding error, using the error handler, or - handling if no active handler is available for the specified protocol, using the error handler, or - handling the message recovered from the envelope with all active handlers for the specified protocol. :param envelope: the envelope to handle. :return: None """ self.logger.debug("Handling envelope: {}".format(envelope)) msg, handlers = self._get_msg_and_handlers_for_envelope(envelope) if msg is None: return for handler in handlers: handler.handle_wrapper(msg) def _setup_loggers(self) -> None: """Set up logger with agent name.""" for element in [ self.runtime.agent_loop, self.runtime.multiplexer, self.runtime.task_manager, self.resources.component_registry, self.resources.behaviour_registry, self.resources.handler_registry, self.resources.model_registry, ]: element = cast(WithLogger, element) element.logger = cast( Logger, AgentLoggerAdapter(element.logger, agent_name=self._identity.name), ) def get_periodic_tasks( self, ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]]: """ Get all periodic tasks for agent. :return: dict of callable with period specified """ tasks = super().get_periodic_tasks() tasks.update(self._get_behaviours_tasks()) return tasks def _get_behaviours_tasks( self, ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]]: """ Get all periodic tasks for AEA behaviours. :return: dict of callable with period specified """ tasks = {} for behaviour in self.active_behaviours: tasks[behaviour.act_wrapper] = (behaviour.tick_interval, behaviour.start_at) return tasks def get_message_handlers(self) -> List[Tuple[Callable[[Any], None], Callable]]: """ Get handlers with message getters. :return: List of tuples of callables: handler and coroutine to get a message """ return super().get_message_handlers() + [ ( self.filter.handle_internal_message, self.filter.get_internal_message, ), (self.handle_envelope, self.runtime.agent_loop.skill2skill_queue.get), ] def exception_handler(self, exception: Exception, function: Callable) -> bool: """ Handle exception raised during agent main loop execution. :param exception: exception raised :param function: a callable exception raised in. :return: bool, propagate exception if True otherwise skip it. """ # docstyle: ignore # noqa: E800 def log_exception(e: Exception, fn: Callable, is_debug: bool = False) -> None: if is_debug: self.logger.debug(f"<{e}> raised during `{fn}`") else: self.logger.exception(f"<{e}> raised during `{fn}`") if self._skills_exception_policy == ExceptionPolicyEnum.propagate: log_exception(exception, function, is_debug=True) return True if self._skills_exception_policy == ExceptionPolicyEnum.stop_and_exit: log_exception(exception, function) raise _StopRuntime( AEAException( f"AEA was terminated cause exception `{exception}` in skills {function}! Please check logs." ) ) if self._skills_exception_policy == ExceptionPolicyEnum.just_log: log_exception(exception, function) return False raise AEAException( f"Unsupported exception policy: {self._skills_exception_policy}" ) def teardown(self) -> None: """ Tear down the agent. Performs the following: - tears down the resources. """ self.resources.teardown() def get_task_result(self, task_id: int) -> AsyncResult: """ Get the result from a task. :param task_id: the id of the task :return: async result for task_id """ return self.runtime.task_manager.get_task_result(task_id) def enqueue_task( self, func: Callable, args: Sequence = (), kwargs: Optional[Dict[str, Any]] = None, ) -> int: """ Enqueue a task with the task manager. :param func: the callable instance to be enqueued :param args: the positional arguments to be passed to the function. :param kwargs: the keyword arguments to be passed to the function. :return: the task id to get the the result. """ return self.runtime.task_manager.enqueue_task(func, args, kwargs) ================================================ FILE: aea/aea_builder.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains utilities for building an AEA.""" import ast import logging import logging.config import os import pprint import subprocess # nosec import sys from collections import defaultdict from copy import deepcopy from importlib import import_module from pathlib import Path from typing import Any, Collection, Dict, List, Optional, Set, Tuple, Type, Union, cast import jsonschema from packaging.specifiers import SpecifierSet from aea.aea import AEA from aea.common import PathLike from aea.components.base import Component, load_aea_package from aea.components.loader import load_component_from_config from aea.configurations.base import ( AgentConfig, ComponentConfiguration, ComponentId, ComponentType, ConnectionConfig, ContractConfig, Dependencies, PackageType, ProtocolConfig, PublicId, SkillConfig, ) from aea.configurations.constants import ( CONNECTIONS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_ENV_DOTFILE, DEFAULT_LEDGER, DEFAULT_LOGGING_CONFIG, DEFAULT_PROTOCOL, DEFAULT_REGISTRY_NAME, ) from aea.configurations.constants import ( DEFAULT_SEARCH_SERVICE_ADDRESS as _DEFAULT_SEARCH_SERVICE_ADDRESS, ) from aea.configurations.constants import ( DOTTED_PATH_MODULE_ELEMENT_SEPARATOR, PROTOCOLS, SIGNING_PROTOCOL, SKILLS, STATE_UPDATE_PROTOCOL, _FETCHAI_IDENTIFIER, ) from aea.configurations.data_types import PackageIdPrefix from aea.configurations.loader import ConfigLoader, load_component_configuration from aea.configurations.manager import ( AgentConfigManager, find_component_directory_from_component_id, ) from aea.configurations.pypi import ( is_satisfiable, merge_dependencies, merge_dependencies_list, ) from aea.configurations.validation import ExtraPropertiesError from aea.crypto.helpers import private_key_verify from aea.crypto.ledger_apis import DEFAULT_CURRENCY_DENOMINATIONS from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMakerHandler from aea.error_handler.base import AbstractErrorHandler from aea.exceptions import ( AEAException, AEAValidationError, AEAWalletNoAddressException, enforce, ) from aea.helpers.base import ( SimpleId, find_topological_order, load_env_file, load_module, ) from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.helpers.install_dependency import install_dependency from aea.helpers.io import open_file from aea.helpers.logging import AgentLoggerAdapter, WithLogger, get_logger from aea.identity.base import Identity from aea.registries.resources import Resources _default_logger = logging.getLogger(__name__) class _DependenciesManager: """Class to manage dependencies of agent packages.""" def __init__(self) -> None: """Initialize the dependency graph.""" # adjacency list of the dependency DAG # an arc means "depends on" self._dependencies = {} # type: Dict[ComponentId, ComponentConfiguration] self._all_dependencies_by_type = ( {} ) # type: Dict[ComponentType, Dict[ComponentId, ComponentConfiguration]] self._prefix_to_components = {} # type: Dict[PackageIdPrefix, Set[ComponentId]] self._inverse_dependency_graph = {} # type: Dict[ComponentId, Set[ComponentId]] self.agent_pypi_dependencies: Dependencies = {} @property def all_dependencies(self) -> Set[ComponentId]: """Get all dependencies.""" result = set(self._dependencies.keys()) return result @property def dependencies_highest_version(self) -> Set[ComponentId]: """Get the dependencies with highest version.""" return {max(ids) for _, ids in self._prefix_to_components.items()} def get_components_by_type( self, component_type: ComponentType ) -> Dict[ComponentId, ComponentConfiguration]: """Get the components by type.""" return self._all_dependencies_by_type.get(component_type, {}) @property def protocols(self) -> Dict[ComponentId, ProtocolConfig]: """Get the protocols.""" return cast( Dict[ComponentId, ProtocolConfig], self._all_dependencies_by_type.get(ComponentType.PROTOCOL, {}), ) @property def connections(self) -> Dict[ComponentId, ConnectionConfig]: """Get the connections.""" return cast( Dict[ComponentId, ConnectionConfig], self._all_dependencies_by_type.get(ComponentType.CONNECTION, {}), ) @property def skills(self) -> Dict[ComponentId, SkillConfig]: """Get the skills.""" return cast( Dict[ComponentId, SkillConfig], self._all_dependencies_by_type.get(ComponentType.SKILL, {}), ) @property def contracts(self) -> Dict[ComponentId, ContractConfig]: """Get the contracts.""" return cast( Dict[ComponentId, ContractConfig], self._all_dependencies_by_type.get(ComponentType.CONTRACT, {}), ) def add_component(self, configuration: ComponentConfiguration) -> None: """ Add a component to the dependency manager. :param configuration: the component configuration to add. """ # add to main index self._dependencies[configuration.component_id] = configuration # add to index by type self._all_dependencies_by_type.setdefault(configuration.component_type, {})[ configuration.component_id ] = configuration # add to prefix to id index self._prefix_to_components.setdefault( configuration.component_id.component_prefix, set() ).add(configuration.component_id) # populate inverse dependency for dependency in configuration.package_dependencies: self._inverse_dependency_graph.setdefault(dependency, set()).add( configuration.component_id ) def remove_component(self, component_id: ComponentId) -> None: """ Remove a component. :param component_id: the component id :raises ValueError: if some component depends on this package. """ if component_id not in self.all_dependencies: raise ValueError( "Component {} of type {} not present.".format( component_id.public_id, component_id.component_type ) ) dependencies = self._inverse_dependency_graph.get(component_id, set()) if len(dependencies) != 0: raise ValueError( "Cannot remove component {} of type {}. Other components depends on it: {}".format( component_id.public_id, component_id.component_type, dependencies ) ) # remove from the index of all dependencies component = self._dependencies.pop(component_id) # remove from the index of all dependencies grouped by type self._all_dependencies_by_type[component_id.component_type].pop(component_id) if len(self._all_dependencies_by_type[component_id.component_type]) == 0: self._all_dependencies_by_type.pop(component_id.component_type) # remove from prefix to id index self._prefix_to_components.get(component_id.component_prefix, set()).discard( component_id ) # update inverse dependency graph for dependency in component.package_dependencies: self._inverse_dependency_graph[dependency].discard(component_id) @property def pypi_dependencies(self) -> Dependencies: """ Get all the PyPI dependencies. We currently consider only dependency that have the default PyPI index url and that specify only the version field. :return: the merged PyPI dependencies """ all_pypi_dependencies = merge_dependencies_list( self.agent_pypi_dependencies, *[ configuration.pypi_dependencies for configuration in self._dependencies.values() ], ) return all_pypi_dependencies def install_dependencies(self) -> None: """Install extra dependencies for components.""" for name, d in self.pypi_dependencies.items(): install_dependency(name, d, _default_logger) class AEABuilder(WithLogger): # pylint: disable=too-many-public-methods """ This class helps to build an AEA. It follows the fluent interface. Every method of the builder returns the instance of the builder itself. Note: the method 'build()' is guaranteed of being re-entrant with respect to the 'add_component(path)' method. That is, you can invoke the building method many times against the same builder instance, and the returned agent instance will not share the components with other agents, e.g.: builder = AEABuilder() builder.add_component(...) ... # first call my_aea_1 = builder.build() # following agents will have different components. my_aea_2 = builder.build() # all good However, if you manually loaded some of the components and added them with the method 'add_component_instance()', then calling build more than one time is prevented: builder = AEABuilder() builder.add_component_instance(...) ... # other initialization code # first call my_aea_1 = builder.build() # second call to `build()` would raise a Value Error. # call reset builder.reset() # re-add the component and private keys builder.add_component_instance(...) ... # add private keys # second call my_aea_2 = builder.builder() """ DEFAULT_LEDGER = DEFAULT_LEDGER DEFAULT_CURRENCY_DENOMINATIONS = DEFAULT_CURRENCY_DENOMINATIONS DEFAULT_AGENT_ACT_PERIOD = 0.05 # seconds DEFAULT_EXECUTION_TIMEOUT = 0 DEFAULT_MAX_REACTIONS = 20 DEFAULT_SKILL_EXCEPTION_POLICY = ExceptionPolicyEnum.propagate DEFAULT_CONNECTION_EXCEPTION_POLICY = ExceptionPolicyEnum.propagate DEFAULT_LOOP_MODE = "async" DEFAULT_RUNTIME_MODE = "threaded" DEFAULT_TASKMANAGER_MODE = "threaded" DEFAULT_SEARCH_SERVICE_ADDRESS = _DEFAULT_SEARCH_SERVICE_ADDRESS AEA_CLASS = AEA BUILD_TIMEOUT = 120 loader = ConfigLoader.from_configuration_type(PackageType.AGENT) # pylint: disable=attribute-defined-outside-init def __init__( self, with_default_packages: bool = True, registry_dir: str = DEFAULT_REGISTRY_NAME, build_dir_root: Optional[str] = None, ) -> None: """ Initialize the builder. :param with_default_packages: add the default packages. :param registry_dir: the registry directory. :param build_dir_root: the root of the build directory. """ WithLogger.__init__(self, logger=_default_logger) self.registry_dir = os.path.join(os.getcwd(), registry_dir) self._with_default_packages = with_default_packages self.build_dir_root = build_dir_root self._reset(is_full_reset=True) def reset(self, is_full_reset: bool = False) -> None: """ Reset the builder. A full reset causes a reset of all data on the builder. A partial reset only resets: - name, - private keys, and - component instances :param is_full_reset: whether it is a full reset or not. """ self._reset(is_full_reset) def _reset(self, is_full_reset: bool = False) -> None: """ Reset the builder (private usage). :param is_full_reset: whether it is a full reset or not. """ self._name: Optional[str] = None self._private_key_paths: Dict[str, Optional[str]] = {} self._connection_private_key_paths: Dict[str, Optional[str]] = {} if not is_full_reset: self._remove_components_from_dependency_manager() self._component_instances: Dict[ ComponentType, Dict[ComponentConfiguration, Component] ] = { ComponentType.CONNECTION: {}, ComponentType.CONTRACT: {}, ComponentType.PROTOCOL: {}, ComponentType.SKILL: {}, } self._custom_component_configurations: Dict[ComponentId, Dict] = {} self._to_reset: bool = False self._build_called: bool = False if not is_full_reset: return self._default_ledger: Optional[str] = None self._required_ledgers: Optional[List[str]] = None self._build_entrypoint: Optional[str] = None self._currency_denominations: Dict[str, str] = {} self._default_connection: Optional[PublicId] = None self._context_namespace: Dict[str, Any] = {} self._period: Optional[float] = None self._execution_timeout: Optional[float] = None self._max_reactions: Optional[int] = None self._decision_maker_handler_class: Optional[Type[DecisionMakerHandler]] = None self._decision_maker_handler_dotted_path: Optional[str] = None self._decision_maker_handler_file_path: Optional[str] = None self._decision_maker_handler_config: Optional[Dict[str, Any]] = None self._error_handler_class: Optional[Type[AbstractErrorHandler]] = None self._error_handler_dotted_path: Optional[str] = None self._error_handler_file_path: Optional[str] = None self._error_handler_config: Optional[Dict[str, Any]] = None self._skill_exception_policy: Optional[ExceptionPolicyEnum] = None self._connection_exception_policy: Optional[ExceptionPolicyEnum] = None self._default_routing: Dict[PublicId, PublicId] = {} self._loop_mode: Optional[str] = None self._runtime_mode: Optional[str] = None self._task_manager_mode: Optional[str] = None self._search_service_address: Optional[str] = None self._storage_uri: Optional[str] = None self._data_dir: Optional[str] = None self._logging_config: Dict = DEFAULT_LOGGING_CONFIG self._package_dependency_manager = _DependenciesManager() if self._with_default_packages: self._add_default_packages() def _remove_components_from_dependency_manager(self) -> None: """Remove components added via 'add_component' from the dependency manager.""" for ( component_type ) in ( self._component_instances.keys() ): # pylint: disable=consider-using-dict-items for component_config in self._component_instances[component_type].keys(): self._package_dependency_manager.remove_component( component_config.component_id ) def set_period(self, period: Optional[float]) -> "AEABuilder": """ Set agent act period. :param period: period in seconds :return: self """ self._period = period return self def set_execution_timeout(self, execution_timeout: Optional[float]) -> "AEABuilder": """ Set agent execution timeout in seconds. :param execution_timeout: execution_timeout in seconds :return: self """ self._execution_timeout = execution_timeout return self def set_max_reactions(self, max_reactions: Optional[int]) -> "AEABuilder": """ Set agent max reaction in one react. :param max_reactions: int :return: self """ self._max_reactions = max_reactions return self def set_decision_maker_handler_details( self, decision_maker_handler_dotted_path: str, file_path: str, config: Dict[str, Any], ) -> "AEABuilder": """ Set error handler details. :param decision_maker_handler_dotted_path: the dotted path to the decision maker handler :param file_path: the file path to the file which contains the decision maker handler :param config: the configuration passed to the decision maker handler on instantiation :return: self """ self._decision_maker_handler_dotted_path = decision_maker_handler_dotted_path self._decision_maker_handler_file_path = file_path self._decision_maker_handler_config = config return self def _load_decision_maker_handler_class( self, ) -> Optional[Type[DecisionMakerHandler]]: """ Load decision maker handler class. :return: decision maker handler class """ _class = self._get_decision_maker_handler_class() if _class is not None and self._decision_maker_handler_dotted_path is not None: raise ValueError( # pragma: nocover "DecisionMakerHandler class and dotted path set: can only set one!" ) if _class is not None: return _class # pragma: nocover if self._decision_maker_handler_dotted_path is None: return None dotted_path, class_name = self._decision_maker_handler_dotted_path.split( DOTTED_PATH_MODULE_ELEMENT_SEPARATOR ) try: if self._decision_maker_handler_file_path is None: module = import_module(dotted_path) else: module = load_module( dotted_path, Path(self._decision_maker_handler_file_path) ) except Exception as e: # pragma: nocover self.logger.error( "Could not locate decision maker handler for dotted path '{}' and file path '{}'. Error message: {}".format( dotted_path, self._decision_maker_handler_file_path, e ) ) raise # log and re-raise because we should not build an agent from an invalid configuration try: _class = getattr(module, class_name) except Exception as e: # pragma: nocover self.logger.error( "Could not locate decision maker handler for dotted path '{}', class name '{}' and file path '{}'. Error message: {}".format( dotted_path, class_name, self._decision_maker_handler_file_path, e ) ) raise # log and re-raise because we should not build an agent from an invalid configuration return _class def _load_error_handler_class( self, ) -> Optional[Type[AbstractErrorHandler]]: """ Load error handler class. :return: error handler class """ _class = self._get_error_handler_class() if _class is not None and self._error_handler_dotted_path is not None: raise ValueError( # pragma: nocover "ErrorHandler class and dotted path set: can only set one!" ) if _class is not None: return _class # pragma: nocover if self._error_handler_dotted_path is None: return None dotted_path, class_name = self._error_handler_dotted_path.split( DOTTED_PATH_MODULE_ELEMENT_SEPARATOR ) try: if self._error_handler_file_path is None: module = import_module(dotted_path) else: module = load_module(dotted_path, Path(self._error_handler_file_path)) except Exception as e: # pragma: nocover self.logger.error( "Could not locate error handler for dotted path '{}' and file path '{}'. Error message: {}".format( dotted_path, self._error_handler_file_path, e ) ) raise # log and re-raise because we should not build an agent from an invalid configuration try: _class = getattr(module, class_name) except Exception as e: # pragma: nocover self.logger.error( "Could not locate error handler for dotted path '{}', class name '{}' and file path '{}'. Error message: {}".format( dotted_path, class_name, self._error_handler_file_path, e ) ) raise # log and re-raise because we should not build an agent from an invalid configuration return _class def set_error_handler_details( self, error_handler_dotted_path: str, file_path: str, config: Dict[str, Any] ) -> "AEABuilder": """ Set error handler details. :param error_handler_dotted_path: the dotted path to the error handler :param file_path: the file path to the file which contains the error handler :param config: the configuration passed to the error handler on instantiation :return: self """ self._error_handler_dotted_path = error_handler_dotted_path self._error_handler_file_path = file_path self._error_handler_config = config return self def set_skill_exception_policy( self, skill_exception_policy: Optional[ExceptionPolicyEnum] ) -> "AEABuilder": # pragma: nocover """ Set skill exception policy. :param skill_exception_policy: the policy :return: self """ self._skill_exception_policy = skill_exception_policy return self def set_connection_exception_policy( self, connection_exception_policy: Optional[ExceptionPolicyEnum] ) -> "AEABuilder": # pragma: nocover """ Set connection exception policy. :param connection_exception_policy: the policy :return: self """ self._connection_exception_policy = connection_exception_policy return self def set_default_routing( self, default_routing: Dict[PublicId, PublicId] ) -> "AEABuilder": """ Set default routing. This is a map from public ids (protocols) to public ids (connections). :param default_routing: the default routing mapping :return: self """ for protocol_id, connection_id in default_routing.items(): if ( ComponentId("protocol", protocol_id) not in self._package_dependency_manager.protocols ): raise ValueError( f"Protocol {protocol_id} specified in `default_routing` is not a project dependency!" ) if ( ComponentId("connection", connection_id) not in self._package_dependency_manager.connections ): raise ValueError( f"Connection {connection_id} specified in `default_routing` is not a project dependency!" ) self._default_routing = default_routing # pragma: nocover return self def set_loop_mode( self, loop_mode: Optional[str] ) -> "AEABuilder": # pragma: nocover """ Set the loop mode. :param loop_mode: the agent loop mode :return: self """ self._loop_mode = loop_mode return self def set_runtime_mode( self, runtime_mode: Optional[str] ) -> "AEABuilder": # pragma: nocover """ Set the runtime mode. :param runtime_mode: the agent runtime mode :return: self """ self._runtime_mode = runtime_mode return self def set_task_manager_mode( self, task_manager_mode: Optional[str] ) -> "AEABuilder": # pragma: nocover """ Set the task_manager_mode. :param task_manager_mode: the agent task_manager_mode :return: self """ self._task_manager_mode = task_manager_mode return self def set_storage_uri( self, storage_uri: Optional[str] ) -> "AEABuilder": # pragma: nocover """ Set the storage uri. :param storage_uri: storage uri :return: self """ self._storage_uri = storage_uri return self def set_data_dir(self, data_dir: Optional[str]) -> "AEABuilder": # pragma: nocover """ Set the data directory. :param data_dir: path to directory where to store data. :return: self """ self._data_dir = data_dir return self def set_logging_config( self, logging_config: Dict ) -> "AEABuilder": # pragma: nocover """ Set the logging configurations. The dictionary must satisfy the following schema: https://docs.python.org/3/library/logging.config.html#logging-config-dictschema :param logging_config: the logging configurations. :return: self """ self._logging_config = logging_config return self def set_search_service_address( self, search_service_address: str ) -> "AEABuilder": # pragma: nocover """ Set the search service address. :param search_service_address: the search service address :return: self """ self._search_service_address = search_service_address return self def _add_default_packages(self) -> None: """Add default packages.""" # add default protocol default_protocol = PublicId.from_str(DEFAULT_PROTOCOL) self.add_protocol( Path( self.registry_dir, _FETCHAI_IDENTIFIER, PROTOCOLS, default_protocol.name ) ) # add signing protocol signing_protocol = PublicId.from_str(SIGNING_PROTOCOL) self.add_protocol( Path( self.registry_dir, _FETCHAI_IDENTIFIER, PROTOCOLS, signing_protocol.name ) ) # add state update protocol state_update_protocol = PublicId.from_str(STATE_UPDATE_PROTOCOL) self.add_protocol( Path( self.registry_dir, _FETCHAI_IDENTIFIER, PROTOCOLS, state_update_protocol.name, ) ) def _check_can_remove(self, component_id: ComponentId) -> None: """ Check if a component can be removed. :param component_id: the component id. :raises ValueError: if the component is already present. """ if component_id not in self._package_dependency_manager.all_dependencies: raise ValueError( "Component {} of type {} not present.".format( component_id.public_id, component_id.component_type ) ) def _check_can_add(self, configuration: ComponentConfiguration) -> None: """ Check if the component can be added, given its configuration. :param configuration: the configuration of the component. """ self._check_configuration_not_already_added(configuration) self._check_package_dependencies(configuration) self._check_pypi_dependencies(configuration) def set_name(self, name: str) -> "AEABuilder": # pragma: nocover """ Set the name of the agent. :param name: the name of the agent. :return: the AEABuilder """ self._name = name return self def set_default_connection( self, public_id: Optional[PublicId] = None ) -> "AEABuilder": # pragma: nocover """ Set the default connection. :param public_id: the public id of the default connection package. :return: the AEABuilder """ if ( public_id and ComponentId("connection", public_id) not in self._package_dependency_manager.connections ): raise ValueError( f"Connection {public_id} specified as `default_connection` is not a project dependency!" ) self._default_connection = public_id return self def add_private_key( self, identifier: str, private_key_path: Optional[PathLike] = None, is_connection: bool = False, ) -> "AEABuilder": """ Add a private key path. :param identifier: the identifier for that private key path. :param private_key_path: an (optional) path to the private key file. If None, the key will be created at build time. :param is_connection: if the pair is for the connection cryptos :return: the AEABuilder """ if is_connection: self._connection_private_key_paths[identifier] = ( str(private_key_path) if private_key_path is not None else None ) else: self._private_key_paths[identifier] = ( str(private_key_path) if private_key_path is not None else None ) if private_key_path is not None: self._to_reset = True return self def remove_private_key( self, identifier: str, is_connection: bool = False ) -> "AEABuilder": """ Remove a private key path by identifier, if present. :param identifier: the identifier of the private key. :param is_connection: if the pair is for the connection cryptos :return: the AEABuilder """ if is_connection: self._connection_private_key_paths.pop(identifier, None) else: self._private_key_paths.pop(identifier, None) return self @property def private_key_paths(self) -> Dict[str, Optional[str]]: """Get the private key paths.""" return self._private_key_paths @property def connection_private_key_paths(self) -> Dict[str, Optional[str]]: """Get the connection private key paths.""" return self._connection_private_key_paths def set_default_ledger( self, identifier: Optional[str] ) -> "AEABuilder": # pragma: nocover """ Set a default ledger API to use. :param identifier: the identifier of the ledger api :return: the AEABuilder """ self._default_ledger = ( str(SimpleId(identifier)) if identifier is not None else None ) return self def set_required_ledgers( self, required_ledgers: Optional[List[str]] ) -> "AEABuilder": # pragma: nocover """ Set the required ledger identifiers. These are the ledgers for which the AEA requires a key pair. :param required_ledgers: the required ledgers. :return: the AEABuilder. """ self._required_ledgers = ( [str(SimpleId(ledger)) for ledger in required_ledgers] if required_ledgers is not None else None ) return self def set_build_entrypoint( self, build_entrypoint: Optional[str] ) -> "AEABuilder": # pragma: nocover """ Set build entrypoint. :param build_entrypoint: path to the builder script. :return: the AEABuilder """ self._build_entrypoint = build_entrypoint return self def set_currency_denominations( self, currency_denominations: Dict[str, str] ) -> "AEABuilder": # pragma: nocover """ Set the mapping from ledger ids to currency denominations. :param currency_denominations: the mapping :return: the AEABuilder """ self._currency_denominations = currency_denominations return self def add_component( self, component_type: ComponentType, directory: PathLike, skip_consistency_check: bool = False, ) -> "AEABuilder": """ Add a component, given its type and the directory. :param component_type: the component type. :param directory: the directory path. :param skip_consistency_check: if True, the consistency check are skipped. :raises AEAException: if a component is already registered with the same component id. # noqa: DAR402 | or if there's a missing dependency. # noqa: DAR402 :return: the AEABuilder """ directory = Path(directory) configuration = load_component_configuration( component_type, directory, skip_consistency_check ) self._set_component_build_directory(configuration) self._check_can_add(configuration) # update dependency graph self._package_dependency_manager.add_component(configuration) configuration.directory = directory return self def _set_component_build_directory( self, configuration: ComponentConfiguration ) -> None: """ Set component build directory, create if not presents. :param configuration: component configuration """ configuration.build_directory = os.path.join( self.get_build_root_directory(), configuration.component_type.value, configuration.author, configuration.name, ) def add_component_instance(self, component: Component) -> "AEABuilder": """ Add already initialized component object to resources or connections. Please, pay attention, all dependencies have to be already loaded. Notice also that this will make the call to 'build()' non re-entrant. You will have to `reset()` the builder before calling `build()` again. :param component: Component instance already initialized. :return: self """ self._to_reset = True self._check_can_add(component.configuration) # update dependency graph self._package_dependency_manager.add_component(component.configuration) self._component_instances[component.component_type][ component.configuration ] = component return self def set_context_namespace( self, context_namespace: Dict[str, Any] ) -> "AEABuilder": # pragma: nocover """Set the context namespace.""" self._context_namespace = context_namespace return self def set_agent_pypi_dependencies(self, dependencies: Dependencies) -> "AEABuilder": """ Set agent PyPI dependencies. :param dependencies: PyPI dependencies for the agent. :return: the AEABuilder. """ self._package_dependency_manager.agent_pypi_dependencies = dependencies return self def remove_component(self, component_id: ComponentId) -> "AEABuilder": """ Remove a component. :param component_id: the public id of the component. :return: the AEABuilder """ self._check_can_remove(component_id) self._remove(component_id) return self def _remove(self, component_id: ComponentId) -> None: self._package_dependency_manager.remove_component(component_id) def add_protocol(self, directory: PathLike) -> "AEABuilder": """ Add a protocol to the agent. :param directory: the path to the protocol directory :return: the AEABuilder """ self.add_component(ComponentType.PROTOCOL, directory) return self def remove_protocol(self, public_id: PublicId) -> "AEABuilder": """ Remove protocol. :param public_id: the public id of the protocol :return: the AEABuilder """ self.remove_component(ComponentId(ComponentType.PROTOCOL, public_id)) return self def add_connection(self, directory: PathLike) -> "AEABuilder": """ Add a connection to the agent. :param directory: the path to the connection directory :return: the AEABuilder """ self.add_component(ComponentType.CONNECTION, directory) return self def remove_connection(self, public_id: PublicId) -> "AEABuilder": """ Remove a connection. :param public_id: the public id of the connection :return: the AEABuilder """ self.remove_component(ComponentId(ComponentType.CONNECTION, public_id)) return self def add_skill(self, directory: PathLike) -> "AEABuilder": """ Add a skill to the agent. :param directory: the path to the skill directory :return: the AEABuilder """ self.add_component(ComponentType.SKILL, directory) return self def remove_skill(self, public_id: PublicId) -> "AEABuilder": """ Remove protocol. :param public_id: the public id of the skill :return: the AEABuilder """ self.remove_component(ComponentId(ComponentType.SKILL, public_id)) return self def add_contract(self, directory: PathLike) -> "AEABuilder": """ Add a contract to the agent. :param directory: the path to the contract directory :return: the AEABuilder """ self.add_component(ComponentType.CONTRACT, directory) return self def remove_contract(self, public_id: PublicId) -> "AEABuilder": """ Remove protocol. :param public_id: the public id of the contract :return: the AEABuilder """ self.remove_component(ComponentId(ComponentType.CONTRACT, public_id)) return self def call_all_build_entrypoints(self) -> None: """Call all the build entrypoints.""" for config in self._package_dependency_manager._dependencies.values(): # type: ignore # pylint: disable=protected-access self.run_build_for_component_configuration(config, logger=self.logger) target_directory = self.get_build_root_directory() if self._build_entrypoint: self.logger.info("Building AEA package...") source_directory = "." build_entrypoint = cast(str, self._build_entrypoint) self._run_build_entrypoint( build_entrypoint, source_directory, target_directory, logger=self.logger ) def get_build_root_directory(self) -> str: """Get build directory root.""" return os.path.join(self.build_dir_root or ".", self.AEA_CLASS.get_build_dir()) @classmethod def run_build_for_component_configuration( cls, config: ComponentConfiguration, logger: Optional[logging.Logger] = None, ) -> None: """Run a build entrypoint script for component configuration.""" if not config.build_entrypoint: return enforce(bool(config.build_directory), f"{config}.build_directory is not set!") if not config.build_directory: # pragma: nocover return if logger: logger.info(f"Building package {config.component_id}...") source_directory = cast(str, config.directory) target_directory = os.path.abspath(config.build_directory) build_entrypoint = cast(str, config.build_entrypoint) cls._run_build_entrypoint( build_entrypoint, source_directory, target_directory, logger=logger ) @classmethod def _run_build_entrypoint( cls, build_entrypoint: str, source_directory: str, target_directory: str, logger: Optional[logging.Logger] = None, ) -> None: """ Run a build entrypoint script. :param build_entrypoint: the path to the build script relative to directory. :param source_directory: the source directory. :param target_directory: the target directory. :param logger: logger """ cls._check_valid_entrypoint(build_entrypoint, source_directory) command = [sys.executable, build_entrypoint, target_directory] command_str = " ".join(command) if logger: logger.info(f"Running command '{command_str}'...") stdout, stderr, code = cls._run_in_subprocess(command, source_directory) if code == 0: if logger: logger.info(f"Command '{command_str}' succeded with output:\n{stdout}") else: raise AEAException( f"An error occurred while running command '{command_str}':\n{stderr}" ) @classmethod def _run_in_subprocess( cls, command: List[str], source_directory: str ) -> Tuple[str, str, int]: """ Run in subprocess. :param command: command to run :param source_directory: source directory :return: stdout, stderr, code """ res = subprocess.run( # nosec command, cwd=source_directory, check=False, timeout=cls.BUILD_TIMEOUT, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) code = res.returncode stdout = res.stdout.decode("utf-8") stderr = res.stderr.decode("utf-8") return stdout, stderr, code def _build_wallet( self, data_directory: str, password: Optional[str] = None ) -> Wallet: """ Build the wallet. We need to prepend the path to the data directory to each private key path, but only if the path is not an absolute path. :param data_directory: the path prefix to be prepended to each private key path. :param password: the password to encrypt/decrypt the private key. :return: the wallet instance. """ def _prepend_if_not_none( obj: Dict[str, Optional[str]] ) -> Dict[str, Optional[str]]: return { key: os.path.join(data_directory, value) if value is not None and not os.path.isabs(value) else value for key, value in obj.items() } private_key_paths = _prepend_if_not_none(self.private_key_paths) connection_private_key_paths = _prepend_if_not_none( self.connection_private_key_paths ) wallet = Wallet( private_key_paths, connection_private_key_paths, password=password ) return wallet def _build_identity_from_wallet(self, wallet: Wallet) -> Identity: """ Get the identity associated to a wallet. :param wallet: the wallet :return: the identity """ if self._name is None: # pragma: nocover raise ValueError("You must set the name of the agent.") default_ledger = self.get_default_ledger() if not wallet.addresses: raise AEAWalletNoAddressException("Wallet has no addresses.") if default_ledger not in wallet.addresses: raise ValueError( # pragma: nocover f"Specified default ledger '{default_ledger}' not found in available addresses of types: {'[' + ','.join(wallet.addresses.keys()) + ']'}" ) if len(wallet.addresses) > 1: identity = Identity( self._name, addresses=wallet.addresses, public_keys=wallet.public_keys, default_address_key=default_ledger, ) else: identity = Identity( self._name, address=wallet.addresses[default_ledger], public_key=wallet.public_keys[default_ledger], default_address_key=default_ledger, ) return identity def _process_connection_ids( # pylint: disable=unsubscriptable-object self, connection_ids: Optional[Collection[PublicId]] = None, ) -> List[PublicId]: """ Process connection ids. :param connection_ids: an optional list of connection ids :return: a list of connections """ if connection_ids is not None: # check that all the connections are in the configuration file. connection_ids_set = set(connection_ids) all_supported_connection_ids = { cid.public_id for cid in self._package_dependency_manager.connections.keys() } non_supported_connections = connection_ids_set.difference( all_supported_connection_ids ) if len(non_supported_connections) > 0: raise ValueError( "Connection ids {} not declared in the configuration file.".format( sorted(map(str, non_supported_connections)) ) ) selected_connections_ids = [ component_id.public_id for component_id in self._package_dependency_manager.connections.keys() if component_id.public_id in connection_ids_set ] else: selected_connections_ids = [ component_id.public_id for component_id in self._package_dependency_manager.connections.keys() ] if len(selected_connections_ids) == 0: return selected_connections_ids # sort default id to be first default_connection = self._get_default_connection() if default_connection is None: return [] full_default_connection_id = [ connection_id for connection_id in selected_connections_ids if connection_id.same_prefix(default_connection) ] if len(full_default_connection_id) == 1: selected_connections_ids.remove(full_default_connection_id[0]) sorted_selected_connections_ids = ( full_default_connection_id + selected_connections_ids ) else: raise ValueError( "Default connection not a dependency. Please add it and retry." ) return sorted_selected_connections_ids def install_pypi_dependencies(self) -> None: """Install components extra dependencies.""" self._package_dependency_manager.install_dependencies() def build( # pylint: disable=unsubscriptable-object self, connection_ids: Optional[Collection[PublicId]] = None, password: Optional[str] = None, ) -> AEA: """ Build the AEA. This method is re-entrant only if the components have been added through the method 'add_component'. If some of them have been loaded with 'add_component_instance', it can be called only once, and further calls are only possible after a call to 'reset' and re-loading of the components added via 'add_component_instance' and the private keys. :param connection_ids: select only these connections to run the AEA. :param password: the password to encrypt/decrypt the private key. :return: the AEA object. """ datadir = self._get_data_dir() self._check_we_can_build() self._preliminary_checks_before_build() logging.config.dictConfig(self._logging_config) wallet = self._build_wallet(datadir, password=password) identity = self._build_identity_from_wallet(wallet) resources = Resources(identity.name) self._load_and_add_components(ComponentType.PROTOCOL, resources, identity.name) self._load_and_add_components(ComponentType.CONTRACT, resources, identity.name) self._load_and_add_components( ComponentType.CONNECTION, resources, identity.name, identity=identity, crypto_store=wallet.connection_cryptos, data_dir=datadir, ) connection_ids = self._process_connection_ids(connection_ids) aea = self.AEA_CLASS( identity, wallet, resources, datadir, loop=None, period=self._get_agent_act_period(), execution_timeout=self._get_execution_timeout(), max_reactions=self._get_max_reactions(), error_handler_class=self._load_error_handler_class(), error_handler_config=self._get_error_handler_config(), decision_maker_handler_class=self._load_decision_maker_handler_class(), decision_maker_handler_config=self._get_decision_maker_handler_config(), skill_exception_policy=self._get_skill_exception_policy(), connection_exception_policy=self._get_connection_exception_policy(), currency_denominations=self._get_currency_denominations(), default_routing=self._get_default_routing(), default_connection=self._get_default_connection(), loop_mode=self._get_loop_mode(), runtime_mode=self._get_runtime_mode(), task_manager_mode=self._get_task_manager_mode(), connection_ids=connection_ids, search_service_address=self._get_search_service_address(), storage_uri=self._get_storage_uri(), **deepcopy(self._context_namespace), ) self._load_and_add_components( ComponentType.SKILL, resources, identity.name, agent_context=aea.context ) self._build_called = True return aea def get_default_ledger(self) -> str: """ Return default ledger. :return: the default ledger identifier. """ return self._default_ledger or self.DEFAULT_LEDGER def get_required_ledgers(self) -> List[str]: """ Get the required ledger identifiers. These are the ledgers for which the AEA requires a key pair. :return: the list of required ledgers. """ return self._required_ledgers or [self.DEFAULT_LEDGER] def _get_agent_act_period(self) -> float: """ Return agent act period. :return: period in seconds if set else default value. """ return self._period or self.DEFAULT_AGENT_ACT_PERIOD def _get_execution_timeout(self) -> float: """ Return execution timeout. :return: timeout in seconds if set else default value. """ return ( self._execution_timeout if self._execution_timeout is not None else self.DEFAULT_EXECUTION_TIMEOUT ) def _get_max_reactions(self) -> int: """ Return agent max_reaction. :return: max-reactions if set else default value. """ return ( self._max_reactions if self._max_reactions is not None else self.DEFAULT_MAX_REACTIONS ) def _get_error_handler_class( self, ) -> Optional[Type]: """ Return the error handler class. :return: error handler class """ return self._error_handler_class def _get_error_handler_config( self, ) -> Optional[Dict[str, Any]]: """ Return the error handler config. :return: error handler config """ return self._error_handler_config def _get_decision_maker_handler_class( self, ) -> Optional[Type[DecisionMakerHandler]]: """ Return the decision maker handler class. :return: decision maker handler class """ return self._decision_maker_handler_class def _get_decision_maker_handler_config( self, ) -> Optional[Dict[str, Any]]: """ Return the decision maker handler config. :return: decision maker handler config """ return self._decision_maker_handler_config def _get_skill_exception_policy(self) -> ExceptionPolicyEnum: """ Return the skill exception policy. :return: the skill exception policy. """ return ( self._skill_exception_policy if self._skill_exception_policy is not None else self.DEFAULT_SKILL_EXCEPTION_POLICY ) def _get_connection_exception_policy(self) -> ExceptionPolicyEnum: """ Return the skill exception policy. :return: the skill exception policy. """ return ( self._connection_exception_policy if self._connection_exception_policy is not None else self.DEFAULT_CONNECTION_EXCEPTION_POLICY ) def _get_currency_denominations(self) -> Dict[str, str]: """ Return the mapping from ledger id to currency denominations. :return: the mapping """ return ( self._currency_denominations if self._currency_denominations != {} else self.DEFAULT_CURRENCY_DENOMINATIONS ) def _get_default_routing(self) -> Dict[PublicId, PublicId]: """ Return the default routing. :return: the default routing """ return self._default_routing def _get_default_connection(self) -> Optional[PublicId]: """ Return the default connection. :return: the default connection """ return self._default_connection def _get_loop_mode(self) -> str: """ Return the loop mode name. :return: the loop mode name """ return ( self._loop_mode if self._loop_mode is not None else self.DEFAULT_LOOP_MODE ) def _get_runtime_mode(self) -> str: """ Return the runtime mode name. :return: the runtime mode name """ return ( self._runtime_mode if self._runtime_mode is not None else self.DEFAULT_RUNTIME_MODE ) def _get_task_manager_mode(self) -> str: """ Return the askmanager mode name. :return: the taskmanager mode name """ return ( self._task_manager_mode if self._task_manager_mode is not None else self.DEFAULT_TASKMANAGER_MODE ) def _get_storage_uri(self) -> Optional[str]: """ Return the storage uri. :return: the storage uri """ return self._storage_uri def _get_data_dir(self) -> str: """ Return the data directory. :return: the data directory. """ return self._data_dir if self._data_dir is not None else os.getcwd() def _get_search_service_address(self) -> str: """ Return the search service address. :return: the search service address. """ return ( self._search_service_address if self._search_service_address is not None else self.DEFAULT_SEARCH_SERVICE_ADDRESS ) def _check_configuration_not_already_added( self, configuration: ComponentConfiguration ) -> None: """ Check the component configuration has not already been added. :param configuration: the component configuration being added :raises AEAException: if the component is already present. """ if ( configuration.component_id in self._package_dependency_manager.all_dependencies ): raise AEAException( "Component '{}' of type '{}' already added.".format( configuration.public_id, configuration.component_type ) ) def _check_package_dependencies( self, configuration: ComponentConfiguration ) -> None: """ Check that we have all the dependencies needed to the package. :param configuration: the component configuration :raises AEAException: if there's a missing dependency. """ not_supported_packages = configuration.package_dependencies.difference( self._package_dependency_manager.all_dependencies ) # type: Set[ComponentId] has_all_dependencies = len(not_supported_packages) == 0 if not has_all_dependencies: raise AEAException( "Package '{}' of type '{}' cannot be added. Missing dependencies: {}".format( configuration.public_id, configuration.component_type.value, pprint.pformat(sorted(map(str, not_supported_packages))), ) ) def _check_pypi_dependencies(self, configuration: ComponentConfiguration) -> None: """ Check that PyPI dependencies of a package don't conflict with the existing ones. :param configuration: the component configuration. :raises AEAException: if some PyPI dependency is conflicting. """ all_pypi_dependencies = self._package_dependency_manager.pypi_dependencies all_pypi_dependencies = merge_dependencies( all_pypi_dependencies, configuration.pypi_dependencies ) for pkg_name, dep_info in all_pypi_dependencies.items(): set_specifier = SpecifierSet(dep_info.version) if not is_satisfiable(set_specifier): raise AEAException( f"Conflict on package {pkg_name}: specifier set '{dep_info.version}' not satisfiable." ) @classmethod def try_to_load_agent_configuration_file( cls, aea_project_path: Union[str, Path] ) -> AgentConfig: """Try to load the agent configuration file..""" try: aea_project_path = Path(aea_project_path) configuration_file_path = cls.get_configuration_file_path(aea_project_path) with open_file(configuration_file_path, mode="r", encoding="utf-8") as fp: loader = ConfigLoader.from_configuration_type(PackageType.AGENT) agent_configuration = loader.load(fp) return agent_configuration except FileNotFoundError: # pragma: nocover raise ValueError( "Agent configuration file '{}' not found in the current directory.".format( DEFAULT_AEA_CONFIG_FILE ) ) except ( AEAValidationError, jsonschema.exceptions.ValidationError, ExtraPropertiesError, ) as e: # pragma: nocover raise AEAValidationError( "Agent configuration file '{}' is invalid: `{}`. Please check the documentation.".format( DEFAULT_AEA_CONFIG_FILE, str(e) ) ) @staticmethod def _check_valid_entrypoint(build_entrypoint: str, directory: str) -> None: """ Check a configuration has a valid entrypoint. :param build_entrypoint: the build entrypoint. :param directory: the directory from where to start reading the script. """ enforce( build_entrypoint is not None, "Package has not a build entrypoint specified.", ) build_entrypoint = cast(str, build_entrypoint) script_path = Path(directory) / build_entrypoint enforce( script_path.exists(), f"File '{build_entrypoint}' does not exists.", ) enforce( script_path.is_file(), f"'{build_entrypoint}' is not a file.", ) try: ast.parse(script_path.read_text()) except SyntaxError as e: message = f"{str(e)}: {e.text}" raise AEAException( f"The Python script at '{build_entrypoint}' has a syntax error: {message}" ) from e def set_from_configuration( self, agent_configuration: AgentConfig, aea_project_path: Path, skip_consistency_check: bool = False, ) -> None: """ Set builder variables from AgentConfig. :param agent_configuration: AgentConfig to get values from. :param aea_project_path: PathLike root directory of the agent project. :param skip_consistency_check: if True, the consistency check are skipped. """ # set name and other configurations self.set_name(agent_configuration.name) self.set_default_ledger(agent_configuration.default_ledger) self.set_required_ledgers(agent_configuration.required_ledgers) self.set_build_entrypoint(agent_configuration.build_entrypoint) self.set_currency_denominations(agent_configuration.currency_denominations) self.set_period(agent_configuration.period) self.set_execution_timeout(agent_configuration.execution_timeout) self.set_max_reactions(agent_configuration.max_reactions) if agent_configuration.decision_maker_handler != {}: dotted_path = agent_configuration.decision_maker_handler["dotted_path"] file_path = agent_configuration.decision_maker_handler["file_path"] config = agent_configuration.decision_maker_handler["config"] self.set_decision_maker_handler_details(dotted_path, file_path, config) if agent_configuration.error_handler != {}: dotted_path = agent_configuration.error_handler["dotted_path"] file_path = agent_configuration.error_handler["file_path"] config = agent_configuration.error_handler["config"] self.set_error_handler_details(dotted_path, file_path, config) if agent_configuration.skill_exception_policy is not None: self.set_skill_exception_policy( ExceptionPolicyEnum(agent_configuration.skill_exception_policy) ) if agent_configuration.connection_exception_policy is not None: self.set_connection_exception_policy( ExceptionPolicyEnum(agent_configuration.connection_exception_policy) ) self.set_loop_mode(agent_configuration.loop_mode) self.set_runtime_mode(agent_configuration.runtime_mode) self.set_task_manager_mode(agent_configuration.task_manager_mode) self.set_storage_uri(agent_configuration.storage_uri) self.set_data_dir(agent_configuration.data_dir) self.set_logging_config(agent_configuration.logging_config) # load private keys for ( ledger_identifier, private_key_path, ) in agent_configuration.private_key_paths_dict.items(): self.add_private_key(ledger_identifier, private_key_path) # load connection private keys for ( ledger_identifier, private_key_path, ) in agent_configuration.connection_private_key_paths_dict.items(): self.add_private_key( ledger_identifier, private_key_path, is_connection=True ) for component_type in [ ComponentType.PROTOCOL, ComponentType.CONTRACT, ComponentType.CONNECTION, ComponentType.SKILL, ]: self._add_components_of_type( component_type, agent_configuration, aea_project_path, skip_consistency_check, ) self._custom_component_configurations = ( agent_configuration.component_configurations ) self.set_default_connection(agent_configuration.default_connection) self.set_default_routing(agent_configuration.default_routing) self.set_agent_pypi_dependencies(agent_configuration.dependencies) @staticmethod def _find_import_order( component_ids: List[ComponentId], aea_project_path: Path, skip_consistency_check: bool, ) -> List[ComponentId]: """ Find import order for skills/connections. We need to handle skills and connections separately, since skills/connections can depend on each other. That is, we need to: - load the skill/connection configurations to find the import order - detect if there are cycles - import skills/connections from the leaves of the dependency graph, by finding a topological ordering. :param component_ids: component ids to check :param aea_project_path: project path to AEA :param skip_consistency_check: consistency check of AEA :return: list of component ids ordered for import """ # the adjacency list for the inverse dependency graph dependency_to_supported_dependencies: Dict[ ComponentId, Set[ComponentId] ] = defaultdict(set) for component_id in component_ids: component_path = find_component_directory_from_component_id( aea_project_path, component_id ) configuration = load_component_configuration( component_id.component_type, component_path, skip_consistency_check ) if component_id not in dependency_to_supported_dependencies: dependency_to_supported_dependencies[component_id] = set() if isinstance(configuration, SkillConfig): dependencies, component_type = configuration.skills, SKILLS elif isinstance(configuration, ConnectionConfig): dependencies, component_type = configuration.connections, CONNECTIONS else: raise AEAException("Not a valid configuration type.") # pragma: nocover for dependency in dependencies: dependency_to_supported_dependencies[ ComponentId(ComponentType.SKILL, dependency) ].add(component_id) try: order = find_topological_order(dependency_to_supported_dependencies) except ValueError: raise AEAException( f"Cannot load {component_type}, there is a cyclic dependency." ) return order @classmethod def from_aea_project( cls, aea_project_path: PathLike, skip_consistency_check: bool = False, password: Optional[str] = None, ) -> "AEABuilder": """ Construct the builder from an AEA project. - load agent configuration file - set name and default configurations - load private keys - load ledger API configurations - set default ledger - load every component :param aea_project_path: path to the AEA project. :param skip_consistency_check: if True, the consistency check are skipped. :param password: the password to encrypt/decrypt private keys. :return: an AEABuilder. """ aea_project_path = Path(aea_project_path) cls.try_to_load_agent_configuration_file(aea_project_path) load_env_file(str(aea_project_path / DEFAULT_ENV_DOTFILE)) # check and create missing, do not replace env variables. updates config AgentConfigManager.verify_private_keys( aea_project_path, substitude_env_vars=False, private_key_helper=private_key_verify, password=password, ).dump_config() # just validate agent_configuration = AgentConfigManager.verify_private_keys( aea_project_path, substitude_env_vars=True, private_key_helper=private_key_verify, password=password, ).agent_config builder = AEABuilder(with_default_packages=False) builder.set_from_configuration( agent_configuration, aea_project_path, skip_consistency_check ) return builder @staticmethod def get_configuration_file_path(aea_project_path: Union[Path, str]) -> Path: """Return path to aea-config file for the given aea project path.""" return Path(aea_project_path) / DEFAULT_AEA_CONFIG_FILE def _load_and_add_components( self, component_type: ComponentType, resources: Resources, agent_name: str, **kwargs: Any, ) -> None: """ Load and add components added to the builder to a Resources instance. :param component_type: the component type for which :param resources: the resources object to populate. :param agent_name: the AEA name for logging purposes. :param kwargs: keyword argument to forward to the component loader. """ for configuration in self._package_dependency_manager.get_components_by_type( component_type ).values(): if configuration in self._component_instances[component_type].keys(): component = self._component_instances[component_type][configuration] if configuration.component_type != ComponentType.SKILL: component.logger = cast( logging.Logger, make_component_logger(configuration, agent_name) ) else: new_configuration = self._overwrite_custom_configuration(configuration) if new_configuration.is_abstract_component: load_aea_package(configuration) self.logger.debug( f"Package {configuration.public_id} of type {configuration.component_type} is abstract, " f"therefore only the Python modules have been loaded." ) continue _logger = make_component_logger(new_configuration, agent_name) component = load_component_from_config( new_configuration, logger=_logger, **kwargs ) resources.add_component(component) def _check_we_can_build(self) -> None: if self._build_called and self._to_reset: raise ValueError( "Cannot build the agent; You have done one of the following:\n" "- added a component instance;\n" "- added a private key manually.\n" "Please call 'reset() if you want to build another agent." ) def _overwrite_custom_configuration( self, configuration: ComponentConfiguration ) -> ComponentConfiguration: """ Overwrite custom configurations. It deep-copies the configuration, to avoid undesired side-effects. :param configuration: the configuration object. :return: the new configuration instance. """ new_configuration = deepcopy(configuration) custom_config = self._custom_component_configurations.get( new_configuration.component_id, {} ) new_configuration.update(custom_config) return new_configuration def _add_components_of_type( self, component_type: ComponentType, agent_configuration: AgentConfig, aea_project_path: Path, skip_consistency_check: bool, ) -> None: """ Add components of a given type. :param component_type: the type of components to add. :param agent_configuration: the agent configuration from where to retrieve the components. :param aea_project_path: path to the AEA project. :param skip_consistency_check: if true, skip consistency checks. """ public_ids = getattr(agent_configuration, component_type.to_plural()) component_ids = [ ComponentId(component_type, public_id) for public_id in public_ids ] if component_type in {ComponentType.PROTOCOL, ComponentType.CONTRACT}: # if protocols or contracts, import order doesn't matter. import_order = component_ids else: import_order = self._find_import_order( component_ids, aea_project_path, skip_consistency_check ) for component_id in import_order: component_path = find_component_directory_from_component_id( aea_project_path, component_id ) self.add_component( component_id.component_type, component_path, skip_consistency_check=skip_consistency_check, ) def _preliminary_checks_before_build(self) -> None: """ Do consistency check on build parameters. - Check that the specified default ledger is in the list of specified required ledgers. """ default_ledger = self.get_default_ledger() required_ledgers = self.get_required_ledgers() enforce( default_ledger in required_ledgers, exception_text=f"Default ledger '{default_ledger}' not declared in the list of required ledgers: {required_ledgers}.", exception_class=AEAValidationError, ) def make_component_logger( configuration: ComponentConfiguration, agent_name: str, ) -> Optional[logging.Logger]: """ Make the logger for a component. :param configuration: the component configuration :param agent_name: the agent name :return: the logger. """ if configuration.component_type == ComponentType.SKILL: # skip because skill object already have their own logger from the skill context. return None logger_name = f"aea.packages.{configuration.author}.{configuration.component_type.to_plural()}.{configuration.name}" _logger = AgentLoggerAdapter(get_logger(logger_name, agent_name), agent_name) return cast(logging.Logger, _logger) ================================================ FILE: aea/agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of a generic agent.""" import datetime import logging from asyncio import AbstractEventLoop from logging import Logger from typing import Any, Callable, Dict, List, Optional, Tuple, Type from aea.abstract_agent import AbstractAgent from aea.connections.base import Connection from aea.exceptions import AEAException from aea.helpers.logging import WithLogger from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import InBox, OutBox from aea.runtime import AsyncRuntime, BaseRuntime, RuntimeStates, ThreadedRuntime _default_logger = logging.getLogger(__name__) class Agent(AbstractAgent, WithLogger): """This class provides an abstract base class for a generic agent.""" RUNTIMES: Dict[str, Type[BaseRuntime]] = { "async": AsyncRuntime, "threaded": ThreadedRuntime, } DEFAULT_RUNTIME: str = "threaded" _runtime: BaseRuntime _inbox: InBox _outbox: OutBox def __init__( self, identity: Identity, connections: List[Connection], loop: Optional[AbstractEventLoop] = None, period: float = 1.0, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, storage_uri: Optional[str] = None, logger: Logger = _default_logger, task_manager_mode: Optional[str] = None, ) -> None: """ Instantiate the agent. :param identity: the identity of the agent. :param connections: the list of connections of the agent. :param loop: the event loop to run the connections. :param period: period to call agent's act :param loop_mode: loop_mode to choose agent run loop. :param runtime_mode: runtime mode to up agent. :param storage_uri: optional uri to set generic storage :param task_manager_mode: task manager mode. :param logger: the logger. :param task_manager_mode: mode of the task manager. """ WithLogger.__init__(self, logger=logger) self._identity = identity self._period = period self._tick = 0 self._runtime_mode = runtime_mode or self.DEFAULT_RUNTIME self._task_manager_mode = task_manager_mode self._storage_uri = storage_uri runtime_class = self._get_runtime_class() self._set_runtime_and_mail_boxes( runtime_class=runtime_class, loop_mode=loop_mode, loop=loop, multiplexer_options={"connections": connections}, ) def _set_runtime_and_mail_boxes( self, runtime_class: Type[BaseRuntime], multiplexer_options: Dict, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, ) -> None: """Set the runtime and inbox and outbox.""" self._runtime = runtime_class( agent=self, loop_mode=loop_mode, loop=loop, multiplexer_options=multiplexer_options, task_manager_mode=self._task_manager_mode, ) self._inbox = InBox(self.runtime.multiplexer) self._outbox = OutBox(self.runtime.multiplexer) def _get_runtime_class(self) -> Type[BaseRuntime]: """Get runtime class based on runtime mode.""" if self._runtime_mode not in self.RUNTIMES: raise ValueError( f"Runtime `{self._runtime_mode} is not supported. valid are: `{list(self.RUNTIMES.keys())}`" ) return self.RUNTIMES[self._runtime_mode] @property def storage_uri(self) -> Optional[str]: """Return storage uri.""" return self._storage_uri @property def is_running(self) -> bool: """Get running state of the runtime and agent.""" return self.runtime.is_running @property def is_stopped(self) -> bool: """Get running state of the runtime and agent.""" return self.runtime.is_stopped @property def identity(self) -> Identity: """Get the identity.""" return self._identity @property def inbox(self) -> InBox: # pragma: nocover """ Get the inbox. The inbox contains Envelopes from the Multiplexer. The agent can pick these messages for processing. :return: InBox instance """ return self._inbox @property def outbox(self) -> OutBox: # pragma: nocover """ Get the outbox. The outbox contains Envelopes for the Multiplexer. Envelopes placed in the Outbox are processed by the Multiplexer. :return: OutBox instance """ return self._outbox @property def name(self) -> str: """Get the agent name.""" return self.identity.name @property def tick(self) -> int: # pragma: nocover """ Get the tick or agent loop count. Each agent loop (one call to each one of act(), react(), update()) increments the tick. :return: tick count """ return self._tick @property def state(self) -> RuntimeStates: """ Get state of the agent's runtime. :return: RuntimeStates """ return self.runtime.state @property def period(self) -> float: """Get a period to call act.""" return self._period @property def runtime(self) -> BaseRuntime: """Get the runtime.""" return self._runtime def setup(self) -> None: """Set up the agent.""" raise NotImplementedError # pragma: nocover def start(self) -> None: """ Start the agent. Performs the following: - calls start() on runtime. - waits for runtime to complete running (blocking) """ was_started = self.runtime.start() if was_started: self.runtime.wait_completed(sync=True) else: # pragma: nocover raise AEAException("Failed to start runtime! Was it already started?") def handle_envelope(self, envelope: Envelope) -> None: """ Handle an envelope. :param envelope: the envelope to handle. """ raise NotImplementedError # pragma: nocover def act(self) -> None: """Perform actions on period.""" raise NotImplementedError # pragma: nocover def stop(self) -> None: """ Stop the agent. Performs the following: - calls stop() on runtime - waits for runtime to stop (blocking) """ self.runtime.stop() self.runtime.wait_completed(sync=True) def teardown(self) -> None: """Tear down the agent.""" raise NotImplementedError # pragma: nocover def get_periodic_tasks( self, ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]]: """ Get all periodic tasks for agent. :return: dict of callable with period specified """ return {self.act: (self.period, None)} def get_message_handlers(self) -> List[Tuple[Callable[[Any], None], Callable]]: """ Get handlers with message getters. :return: List of tuples of callables: handler and coroutine to get a message """ return [ (self.handle_envelope, self.inbox.async_get), ] def exception_handler( self, exception: Exception, function: Callable ) -> bool: # pragma: nocover """ Handle exception raised during agent main loop execution. :param exception: exception raised :param function: a callable exception raised in. :return: bool, propagate exception if True otherwise skip it. """ self.logger.exception( f"Exception {repr(exception)} raised during {repr(function)} call." ) return True ================================================ FILE: aea/agent_loop.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of an agent loop using asyncio.""" import asyncio import datetime from abc import ABC, abstractmethod from asyncio import CancelledError from asyncio.events import AbstractEventLoop from asyncio.queues import Queue from asyncio.tasks import Task from contextlib import suppress from enum import Enum from functools import partial from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union, cast from aea.abstract_agent import AbstractAgent from aea.configurations.constants import LAUNCH_SUCCEED_MESSAGE from aea.exceptions import AEAException from aea.helpers.async_utils import AsyncState, PeriodicCaller, Runnable from aea.helpers.exec_timeout import ExecTimeoutThreadGuard, TimeoutException from aea.helpers.logging import WithLogger, get_logger from aea.mail.base import Envelope, EnvelopeContext from aea.protocols.base import Message class AgentLoopException(AEAException): """Exception for agent loop runtime errors.""" class AgentLoopStates(Enum): """Internal agent loop states.""" initial = None started = "started" starting = "starting" stopped = "stopped" stopping = "stopping" error = "error" class BaseAgentLoop(Runnable, WithLogger, ABC): """Base abstract agent loop class.""" def __init__( self, agent: AbstractAgent, loop: Optional[AbstractEventLoop] = None, threaded: bool = False, ) -> None: """Init loop. :param agent: Agent or AEA to run. :param loop: optional asyncio event loop. if not specified a new loop will be created. :param threaded: if True, run in threaded mode, else async """ logger = get_logger(__name__, agent.name) WithLogger.__init__(self, logger) Runnable.__init__(self, loop=loop, threaded=threaded) self._agent: AbstractAgent = agent self._tasks: List[asyncio.Task] = [] self._state: AsyncState = AsyncState(AgentLoopStates.initial) self._exceptions: List[Exception] = [] @property def agent(self) -> AbstractAgent: # pragma: nocover """Get agent.""" return self._agent @property def state(self) -> AgentLoopStates: """Get current main loop state.""" return self._state.get() async def wait_state( self, state_or_states: Union[Any, Sequence[Any]] ) -> Tuple[Any, Any]: """ Wait state to be set. :param state_or_states: state or list of states. :return: tuple of previous state and new state. """ return await self._state.wait(state_or_states) @property def is_running(self) -> bool: """Get running state of the loop.""" return self._state.get() == AgentLoopStates.started def set_loop(self, loop: AbstractEventLoop) -> None: """Set event loop and all event loop related objects.""" self._loop: AbstractEventLoop = loop def _setup(self) -> None: """Set up agent loop before started.""" # start and stop methods are classmethods cause one instance shared across multiple threads ExecTimeoutThreadGuard.start() def _teardown(self) -> None: """Tear down loop on stop.""" # start and stop methods are classmethods cause one instance shared across multiple threads ExecTimeoutThreadGuard.stop() async def run(self) -> None: """Run agent loop.""" self.logger.debug("agent loop starting...") self._state.set(AgentLoopStates.starting) self._setup() self._set_tasks() try: await self._gather_tasks() except (CancelledError, KeyboardInterrupt): pass finally: await self._stop() async def _stop(self) -> None: """Stop and cleanup.""" self._teardown() self._stop_tasks() for t in self._tasks: with suppress(CancelledError, KeyboardInterrupt): await t self._state.set(AgentLoopStates.stopped) self.logger.debug("agent loop stopped") async def _gather_tasks(self) -> None: """Wait till first task exception.""" await asyncio.gather(*self._tasks) @abstractmethod def _set_tasks(self) -> None: # pragma: nocover """Set run loop tasks.""" raise NotImplementedError def _stop_tasks(self) -> None: """Cancel all tasks.""" for task in self._tasks: task.cancel() @abstractmethod def send_to_skill( self, message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None, ) -> None: """ Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. :param message_or_envelope: envelope to send to another skill. :param context: envelope context """ @property @abstractmethod def skill2skill_queue(self) -> Queue: """Get skill to skill message queue.""" class AsyncAgentLoop(BaseAgentLoop): """Asyncio based agent loop suitable only for AEA.""" NEW_BEHAVIOURS_PROCESS_SLEEP = 1 # check new behaviours registered every second. def __init__( self, agent: AbstractAgent, loop: AbstractEventLoop = None, threaded: bool = False, ) -> None: """ Init agent loop. :param agent: AEA instance :param loop: asyncio loop to use. optional :param threaded: is a new thread to be started for the agent loop """ super().__init__(agent=agent, loop=loop, threaded=threaded) self._agent: AbstractAgent = self._agent self._periodic_tasks: Dict[Callable, PeriodicCaller] = {} self._skill2skill_message_queue: Optional[asyncio.Queue] = None def _setup(self) -> None: """Set up agent loop before started.""" super()._setup() self._skill2skill_message_queue = asyncio.Queue() @property def skill2skill_queue(self) -> Queue: """Get skill to skill message queue.""" if not self._skill2skill_message_queue: # pragma: nocover raise ValueError("_skill2skill_message_queue is not set!") return self._skill2skill_message_queue def send_to_skill( self, message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None, ) -> None: """ Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. :param message_or_envelope: envelope to send to another skill. :param context: envelope context """ if isinstance(message_or_envelope, Envelope): envelope = message_or_envelope message = cast(Message, envelope.message) elif isinstance(message_or_envelope, Message): message = message_or_envelope envelope = Envelope( to=message.to, sender=message.sender, message=message, context=context, ) else: raise ValueError( f"Unsupported message or envelope type: {type(message_or_envelope)}" ) if not message.has_to: # pragma: nocover raise ValueError("Provided message has message.to not set.") if not message.has_sender: # pragma: nocover raise ValueError("Provided message has message.sender not set.") self.skill2skill_queue.put_nowait(envelope) def _periodic_task_exception_callback( # pylint: disable=unused-argument self, task_callable: Callable, exc: Exception ) -> None: """ Call on periodic task exception. :param task_callable: function to be called :param: exc: Exception raised """ self._exceptions.append(exc) def _execution_control( self, fn: Callable, args: Optional[Sequence] = None, kwargs: Optional[Dict] = None, ) -> Any: """ Execute skill function in exception handling environment. Logs error, stop agent or propagate exception depends on policy defined. :param fn: function to call :param args: optional sequence of arguments to pass to function on call :param kwargs: optional dict of keyword arguments to pass to function on call :return: same as function """ execution_timeout = getattr(self.agent, "_execution_timeout", 0) try: with ExecTimeoutThreadGuard(execution_timeout): return fn(*(args or []), **(kwargs or {})) except TimeoutException: # pragma: nocover self.logger.warning( "`{}` was terminated as its execution exceeded the timeout of {} seconds. Please refactor your code!".format( fn, execution_timeout ) ) except Exception as e: # pylint: disable=broad-except try: if self.agent.exception_handler(e, fn) is True: self._state.set(AgentLoopStates.error) raise except Exception as e2: self._state.set(AgentLoopStates.error) self._exceptions.append(e2) raise return None def _register_periodic_task( self, task_callable: Callable, period: float, start_at: Optional[datetime.datetime], ) -> None: """ Register function to run periodically. :param task_callable: function to be called :param period: float in seconds :param start_at: optional datetime, when to run task for the first time, otherwise call it right now :return: None """ if task_callable in self._periodic_tasks: # pragma: nocover # already registered return periodic_caller = PeriodicCaller( partial(self._execution_control, task_callable), period=period, start_at=start_at, exception_callback=self._periodic_task_exception_callback, loop=self._loop, ) self._periodic_tasks[task_callable] = periodic_caller periodic_caller.start() self.logger.debug(f"Periodic task {task_callable} registered.") def _register_periodic_tasks(self) -> None: """Register all AEA related periodic tasks.""" for ( task_callable, (period, start_at), ) in self._agent.get_periodic_tasks().items(): self._register_periodic_task(task_callable, period, start_at) def _unregister_periodic_task(self, task_callable: Callable) -> None: """ Unregister periodic execution of the task. :param task_callable: function to be called periodically. :return: None """ periodic_caller = self._periodic_tasks.pop(task_callable, None) if periodic_caller is None: # pragma: nocover return periodic_caller.stop() def _stop_all_behaviours(self) -> None: """Unregister periodic execution of all registered behaviours.""" for task_callable in list(self._periodic_tasks.keys()): self._unregister_periodic_task(task_callable) async def _task_wait_for_error(self) -> None: """Wait for error and raise first.""" await self._state.wait(AgentLoopStates.error) raise self._exceptions[0] def _stop_tasks(self) -> None: """Cancel all tasks and stop behaviours registered.""" BaseAgentLoop._stop_tasks(self) self._stop_all_behaviours() def _set_tasks(self) -> None: """Set run loop tasks.""" self._tasks = self._create_tasks() self.logger.debug("tasks created!") def _create_tasks(self) -> List[Task]: """ Create tasks. :return: list of asyncio Tasks """ coros = [ self._process_messages(), self._task_register_periodic_tasks(), self._task_wait_for_error(), ] return list(map(self._loop.create_task, coros)) # type: ignore # some issue with map and create_task async def _message_processor( self, message_handler: Callable, message_getter: Callable ) -> None: """Fetch messages from the message getter and process it with message handler.""" try: while self.is_running: message = await message_getter() self._execution_control(message_handler, [message]) except CancelledError: # pylint: disable=try-except-raise raise except Exception: # pragma: nocover self.logger.exception( f"Exception in message processor ({message_handler, message_getter})" ) raise def _message_handlers(self) -> List[Tuple[Callable[[Any], None], Callable]]: """Get all agent's message handlers.""" return self._agent.get_message_handlers() async def _process_messages(self) -> None: """Start tasks for messages handlers and sources.""" coros = [] for handler, getter in self._message_handlers(): coros.append(self._message_processor(handler, getter)) self.logger.info(LAUNCH_SUCCEED_MESSAGE) self._state.set(AgentLoopStates.started) await asyncio.gather(*coros) async def _task_register_periodic_tasks(self) -> None: """Process new behaviours added to skills in runtime.""" while self.is_running: self._register_periodic_tasks() # re register, cause new may appear await asyncio.sleep(self.NEW_BEHAVIOURS_PROCESS_SLEEP) SyncAgentLoop = AsyncAgentLoop # temporary solution! ================================================ FILE: aea/cli/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of command-line tool 'aea'.""" from .core import cli ================================================ FILE: aea/cli/__main__.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Entry-point for the AEA command-line tool.""" from aea.cli.core import cli if __name__ == "__main__": cli(prog_name="aea") # pragma: no cover ================================================ FILE: aea/cli/add.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea add' subcommand.""" from pathlib import Path from typing import Union, cast import click from aea.cli.registry.add import fetch_package from aea.cli.utils.click_utils import PublicIdParameter, registry_flag from aea.cli.utils.config import load_item_config from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, clean_after, pass_ctx from aea.cli.utils.loggers import logger from aea.cli.utils.package_utils import ( copy_package_directory, find_item_in_distribution, find_item_locally, get_item_id_present, get_package_path, is_distributed_item, is_fingerprint_correct, is_item_present, register_item, ) from aea.configurations.base import ( ConnectionConfig, PackageConfiguration, PublicId, SkillConfig, ) from aea.configurations.constants import CONNECTION, CONTRACT, PROTOCOL, SKILL from aea.exceptions import enforce @click.group() @registry_flag() @click.pass_context @check_aea_project def add(click_context: click.Context, local: bool, remote: bool) -> None: """Add a package to the agent.""" ctx = cast(Context, click_context.obj) enforce( not (local and remote), "'local' and 'remote' options are mutually exclusive." ) if not local and not remote: try: ctx.registry_path except ValueError as e: click.echo(f"{e}\nTrying remote registry (`--remote`).") remote = True is_mixed = not local and not remote ctx.set_config("is_local", local and not remote) ctx.set_config("is_mixed", is_mixed) @add.command() @click.argument("connection_public_id", type=PublicIdParameter(), required=True) @pass_ctx def connection(ctx: Context, connection_public_id: PublicId) -> None: """Add a connection to the agent.""" add_item(ctx, CONNECTION, connection_public_id) @add.command() @click.argument("contract_public_id", type=PublicIdParameter(), required=True) @pass_ctx def contract(ctx: Context, contract_public_id: PublicId) -> None: """Add a contract to the agent.""" add_item(ctx, CONTRACT, contract_public_id) @add.command() @click.argument("protocol_public_id", type=PublicIdParameter(), required=True) @pass_ctx def protocol(ctx: Context, protocol_public_id: PublicId) -> None: """Add a protocol to the agent.""" add_item(ctx, PROTOCOL, protocol_public_id) @add.command() @click.argument("skill_public_id", type=PublicIdParameter(), required=True) @pass_ctx def skill(ctx: Context, skill_public_id: PublicId) -> None: """Add a skill to the agent.""" add_item(ctx, SKILL, skill_public_id) @clean_after def add_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: """ Add an item. :param ctx: Context object. :param item_type: the item type. :param item_public_id: the item public id. """ click.echo(f"Adding {item_type} '{item_public_id}'...") if is_item_present(ctx.cwd, ctx.agent_config, item_type, item_public_id): present_item_id = get_item_id_present( ctx.agent_config, item_type, item_public_id ) raise click.ClickException( "A {} with id '{}' already exists. Aborting...".format( item_type, present_item_id ) ) dest_path = get_package_path(ctx.cwd, item_type, item_public_id) is_local = ctx.config.get("is_local") is_mixed = ctx.config.get("is_mixed") ctx.clean_paths.append(dest_path) if is_mixed: package_path = fetch_item_mixed(ctx, item_type, item_public_id, dest_path) elif is_local: package_path = find_item_locally_or_distributed( ctx, item_type, item_public_id, dest_path ) else: package_path = fetch_package( item_type, public_id=item_public_id, cwd=ctx.cwd, dest=dest_path ) item_config = load_item_config(item_type, package_path) if not ctx.config.get("skip_consistency_check") and not is_fingerprint_correct( package_path, item_config ): # pragma: no cover raise click.ClickException("Failed to add an item with incorrect fingerprint.") _add_item_deps(ctx, item_type, item_config) register_item(ctx, item_type, item_config.public_id) click.echo(f"Successfully added {item_type} '{item_config.public_id}'.") def _add_item_deps( ctx: Context, item_type: str, item_config: PackageConfiguration ) -> None: """ Add item dependencies. Calls add_item recursively. :param ctx: Context object. :param item_type: type of item. :param item_config: item configuration object. """ if item_type in {CONNECTION, SKILL}: item_config = cast(Union[SkillConfig, ConnectionConfig], item_config) # add missing protocols for protocol_public_id in item_config.protocols: if protocol_public_id not in ctx.agent_config.protocols: add_item(ctx, PROTOCOL, protocol_public_id) if item_type == SKILL: item_config = cast(SkillConfig, item_config) # add missing contracts for contract_public_id in item_config.contracts: if contract_public_id not in ctx.agent_config.contracts: add_item(ctx, CONTRACT, contract_public_id) # add missing connections for connection_public_id in item_config.connections: if connection_public_id not in ctx.agent_config.connections: add_item(ctx, CONNECTION, connection_public_id) # add missing skill for skill_public_id in item_config.skills: if skill_public_id not in ctx.agent_config.skills: add_item(ctx, SKILL, skill_public_id) def find_item_locally_or_distributed( ctx: Context, item_type: str, item_public_id: PublicId, dest_path: str ) -> Path: """ Unify find item locally both in case it is distributed or not. :param ctx: the CLI context. :param item_type: the item type. :param item_public_id: the item public id. :param dest_path: the path to the destination. :return: the path to the found package. """ is_distributed = is_distributed_item(item_public_id) if is_distributed: # pragma: nocover source_path = find_item_in_distribution(ctx, item_type, item_public_id) package_path = copy_package_directory(source_path, dest_path) else: source_path, _ = find_item_locally(ctx, item_type, item_public_id) package_path = copy_package_directory(source_path, dest_path) return package_path def fetch_item_mixed( ctx: Context, item_type: str, item_public_id: PublicId, dest_path: str, ) -> Path: """ Find item, mixed mode. That is, give priority to local registry, and fall back to remote registry in case of failure. :param ctx: the CLI context. :param item_type: the item type. :param item_public_id: the item public id. :param dest_path: the path to the destination. :return: the path to the found package. """ try: package_path = find_item_locally_or_distributed( ctx, item_type, item_public_id, dest_path ) except click.ClickException as e: logger.debug( f"Fetch from local registry failed (reason={str(e)}), trying remote registry..." ) # the following might raise exception, but we don't catch it this time package_path = fetch_package( item_type, public_id=item_public_id, cwd=ctx.cwd, dest=dest_path ) return package_path ================================================ FILE: aea/cli/add_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea add_key' subcommand.""" import os from typing import Optional, cast import click from aea.cli.utils.click_utils import password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.configurations.constants import ( DEFAULT_AEA_CONFIG_FILE, PRIVATE_KEY_PATH_SCHEMA, ) from aea.crypto.helpers import try_validate_private_key_path from aea.crypto.registries import crypto_registry from aea.helpers.io import open_file key_file_argument = click.Path( exists=True, file_okay=True, dir_okay=False, readable=True ) @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice(list(crypto_registry.supported_ids)), required=True, ) @click.argument( "file", metavar="FILE", type=key_file_argument, required=False, ) @password_option() @click.option( "--connection", is_flag=True, help="For adding a private key for connections." ) @click.pass_context @check_aea_project def add_key( click_context: click.Context, type_: str, file: str, password: Optional[str], connection: bool, ) -> None: """Add a private key to the wallet of the agent.""" _add_private_key(click_context, type_, file, password, connection) def _add_private_key( click_context: click.core.Context, type_: str, file: Optional[str] = None, password: Optional[str] = None, connection: bool = False, ) -> None: """ Add private key to the wallet. :param click_context: click context object. :param type_: type. :param file: path to file. :param connection: whether or not it is a private key for a connection. :param password: the password to encrypt/decrypt the private key. """ ctx = cast(Context, click_context.obj) if file is None: file = PRIVATE_KEY_PATH_SCHEMA.format(type_) key_file_argument.convert(file, None, click_context) try: try_validate_private_key_path(type_, file, password=password) except Exception as e: raise click.ClickException(repr(e)) from e _try_add_key(ctx, type_, file, connection) def _try_add_key( ctx: Context, type_: str, filepath: str, connection: bool = False ) -> None: try: if connection: ctx.agent_config.connection_private_key_paths.create(type_, filepath) else: ctx.agent_config.private_key_paths.create(type_, filepath) except ValueError as e: # pragma: no cover raise click.ClickException(str(e)) with open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as fp: ctx.agent_loader.dump(ctx.agent_config, fp) ================================================ FILE: aea/cli/build.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea build' subcommand.""" from pathlib import Path from typing import cast import click from aea.aea_builder import AEABuilder from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project @click.command() @click.pass_context @check_aea_project def build(click_context: click.Context) -> None: """Build the agent and its components.""" ctx = cast(Context, click_context.obj) skip_consistency_check = ctx.config.get("skip_consistency_check", False) build_aea(skip_consistency_check) def build_aea(skip_consistency_check: bool) -> None: """ Build an AEA. That is, run the 'build entrypoint' script of each AEA package of the project. :param skip_consistency_check: the skip consistency check boolean. """ try: builder = AEABuilder.from_aea_project( Path("."), skip_consistency_check=skip_consistency_check, ) builder.call_all_build_entrypoints() except Exception as e: raise click.ClickException(str(e)) click.echo("Build completed!") ================================================ FILE: aea/cli/config.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea config' subcommand.""" import contextlib import json from typing import Optional, cast import click from click.exceptions import ClickException from aea.cli.utils.constants import CONFIG_SUPPORTED_KEY_TYPES from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, pass_ctx from aea.configurations.manager import AgentConfigManager, VariableDoesNotExist from aea.configurations.validation import ExtraPropertiesError from aea.exceptions import AEAException from aea.helpers.env_vars import convert_value_str_to_type @click.group() @click.pass_context @check_aea_project def config(click_context: click.Context) -> None: # pylint: disable=unused-argument """Read or modify a configuration of the agent.""" @config.command() @click.argument("JSON_PATH", required=True) @pass_ctx def get(ctx: Context, json_path: str) -> None: """Get a field.""" try: agent_config_manager = AgentConfigManager.load(ctx.cwd) value = agent_config_manager.get_variable(json_path) except (ValueError, AEAException) as e: raise ClickException(str(e.args[0])) if isinstance(value, dict): # turn it to json compatible string, not dict str representation value = json.dumps(value, sort_keys=True) click.echo(value) @config.command(name="set") @click.option( "--type", "type_", default=None, type=click.Choice(CONFIG_SUPPORTED_KEY_TYPES + [None]), # type: ignore help="Specify the type of the value.", ) @click.argument("JSON_PATH", required=True) @click.argument("VALUE", required=True, type=str) @pass_ctx def set_command( ctx: Context, json_path: str, value: str, type_: Optional[str], ) -> None: """Set a field.""" try: agent_config_manager = AgentConfigManager.load(ctx.cwd) current_value = None with contextlib.suppress(VariableDoesNotExist): current_value = agent_config_manager.get_variable(json_path) # type was not specified, tried to auto determine if type_ is None: # apply str as default type converted_value = convert_value_str_to_type(value, "str") if current_value is not None: # try to convert to original value's type with contextlib.suppress(Exception): converted_value = convert_value_str_to_type( value, type(current_value).__name__ ) else: # convert to type specified by user converted_value = convert_value_str_to_type(value, cast(str, type_)) agent_config_manager.set_variable(json_path, converted_value) agent_config_manager.dump_config() except ExtraPropertiesError as e: # pragma: nocover raise ClickException(f"Attribute `{e.args[0][0]}` is not allowed to change!") except (ValueError, AEAException) as e: raise ClickException(str(e.args[0])) ================================================ FILE: aea/cli/core.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Core definitions for the AEA command-line tool.""" from typing import Optional import click from pkg_resources import iter_entry_points import aea from aea.cli.add import add from aea.cli.add_key import add_key from aea.cli.build import build from aea.cli.config import config from aea.cli.create import create from aea.cli.delete import delete from aea.cli.eject import eject from aea.cli.fetch import fetch from aea.cli.fingerprint import fingerprint from aea.cli.freeze import freeze from aea.cli.generate import generate from aea.cli.generate_key import generate_key from aea.cli.generate_wealth import generate_wealth from aea.cli.get_address import get_address from aea.cli.get_multiaddress import get_multiaddress from aea.cli.get_public_key import get_public_key from aea.cli.get_wealth import get_wealth from aea.cli.init import init from aea.cli.install import install from aea.cli.interact import interact from aea.cli.issue_certificates import issue_certificates from aea.cli.launch import launch from aea.cli.list import list_command as _list from aea.cli.local_registry_sync import local_registry_sync from aea.cli.login import login from aea.cli.logout import logout from aea.cli.plugin import with_plugins from aea.cli.publish import publish from aea.cli.push import push from aea.cli.register import register from aea.cli.remove import remove from aea.cli.remove_key import remove_key from aea.cli.reset_password import reset_password from aea.cli.run import run from aea.cli.scaffold import scaffold from aea.cli.search import search from aea.cli.transfer import transfer from aea.cli.upgrade import upgrade from aea.cli.utils.click_utils import registry_path_option from aea.cli.utils.config import get_registry_path_from_cli_config from aea.cli.utils.context import Context from aea.cli.utils.loggers import logger, simple_verbosity_option from aea.helpers.win32 import enable_ctrl_c_support @with_plugins(iter_entry_points("aea.cli")) @click.group(name="aea") # type: ignore @click.version_option(aea.__version__, prog_name="aea") @simple_verbosity_option(logger, default="INFO") @click.option( "-s", "--skip-consistency-check", "skip_consistency_check", is_flag=True, required=False, default=False, help="Skip consistency checks of agent during command execution.", ) @registry_path_option @click.pass_context def cli( click_context: click.Context, skip_consistency_check: bool, registry_path: Optional[str], ) -> None: """Command-line tool for setting up an Autonomous Economic Agent (AEA).""" verbosity_option = click_context.meta.pop("verbosity") if not registry_path: registry_path = get_registry_path_from_cli_config() click_context.obj = Context( cwd=".", verbosity=verbosity_option, registry_path=registry_path ) click_context.obj.set_config("skip_consistency_check", skip_consistency_check) # enables CTRL+C support on windows! enable_ctrl_c_support() cli.add_command(_list) cli.add_command(add_key) cli.add_command(add) cli.add_command(build) cli.add_command(create) cli.add_command(config) cli.add_command(delete) cli.add_command(eject) cli.add_command(fetch) cli.add_command(fingerprint) cli.add_command(freeze) cli.add_command(generate_key) cli.add_command(generate_wealth) cli.add_command(generate) cli.add_command(get_address) cli.add_command(get_public_key) cli.add_command(get_multiaddress) cli.add_command(get_wealth) cli.add_command(init) cli.add_command(install) cli.add_command(interact) cli.add_command(issue_certificates) cli.add_command(launch) cli.add_command(login) cli.add_command(logout) cli.add_command(publish) cli.add_command(push) cli.add_command(register) cli.add_command(remove) cli.add_command(remove_key) cli.add_command(reset_password) cli.add_command(run) cli.add_command(scaffold) cli.add_command(search) cli.add_command(local_registry_sync) cli.add_command(transfer) cli.add_command(upgrade) ================================================ FILE: aea/cli/create.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea create' subcommand.""" import os from pathlib import Path from typing import Optional, cast import click from aea import get_current_aea_version from aea.cli.add import add_item from aea.cli.init import do_init from aea.cli.utils.click_utils import registry_flag from aea.cli.utils.config import get_or_create_cli_config from aea.cli.utils.constants import AUTHOR_KEY from aea.cli.utils.context import Context from aea.cli.utils.decorators import clean_after from aea.cli.utils.loggers import logger from aea.configurations.base import AgentConfig, PublicId, dependencies_from_json from aea.configurations.constants import ( CONNECTIONS, CONTRACTS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_DEPENDENCIES, DEFAULT_LEDGER, DEFAULT_LICENSE, DEFAULT_PROTOCOL, DEFAULT_VERSION, PROTOCOL, PROTOCOLS, SIGNING_PROTOCOL, SKILLS, STATE_UPDATE_PROTOCOL, VENDOR, ) from aea.exceptions import enforce from aea.helpers.base import compute_specifier_from_version from aea.helpers.io import open_file @click.command() @click.argument("agent_name", type=str, required=True) @click.option( "--author", type=str, required=False, help="Add the author to run `init` before `create` execution.", ) @registry_flag( help_local="For fetching agent from local folder.", help_remote="For fetching agent from remote registry.", ) @click.option("--empty", is_flag=True, help="Not adding default dependencies.") @click.pass_context def create( click_context: click.core.Context, agent_name: str, author: str, local: bool, remote: bool, empty: bool, ) -> None: """Create a new agent.""" ctx = cast(Context, click_context.obj) create_aea(ctx, agent_name, local, remote, author=author, empty=empty) @clean_after def create_aea( ctx: Context, agent_name: str, local: bool, remote: bool = False, # for backwards compatibility author: Optional[str] = None, empty: bool = False, ) -> None: """ Create AEA project. :param ctx: Context object. :param agent_name: agent name. :param local: boolean flag for local registry usage. :param remote: boolean flag for remote registry usage. :param author: optional author name (valid with local=True and remote=False only). :param empty: optional boolean flag for skip adding default dependencies. :raises ClickException: if an error occurred. """ enforce( not (local and remote), "'local' and 'remote' options are mutually exclusive." ) if not local and not remote: try: ctx.registry_path except ValueError as e: click.echo(f"{e}\nTrying remote registry (`--remote`).") remote = True is_mixed = not local and not remote is_local = local and not remote ctx.set_config("is_local", is_local) ctx.set_config("is_mixed", is_mixed) try: _check_is_parent_folders_are_aea_projects_recursively() except Exception: raise click.ClickException( "The current folder is already an AEA project. Please move to the parent folder." ) if author is not None: if is_local: do_init(author, False, False, False) # pragma: nocover else: raise click.ClickException( "Author is not set up. Please use 'aea init' to initialize." ) config = get_or_create_cli_config() set_author = config.get(AUTHOR_KEY, None) if set_author is None: raise click.ClickException( "The AEA configurations are not initialized. Uses `aea init` before continuing or provide optional argument `--author`." ) if Path(agent_name).exists(): raise click.ClickException("Directory already exist. Aborting...") click.echo("Initializing AEA project '{}'".format(agent_name)) click.echo("Creating project directory './{}'".format(agent_name)) path = Path(agent_name) ctx.clean_paths.append(str(path)) # we have already checked that the directory does not exist. path.mkdir(exist_ok=False) try: # set up packages directories. _setup_package_folder(Path(agent_name, PROTOCOLS)) _setup_package_folder(Path(agent_name, CONTRACTS)) _setup_package_folder(Path(agent_name, CONNECTIONS)) _setup_package_folder(Path(agent_name, SKILLS)) # set up a vendor directory Path(agent_name, VENDOR).mkdir(exist_ok=False) Path(agent_name, VENDOR, "__init__.py").touch(exist_ok=False) # create a config file inside it click.echo("Creating config file {}".format(DEFAULT_AEA_CONFIG_FILE)) agent_config = _create_agent_config(ctx, agent_name, set_author) # next commands must be done from the agent's directory -> overwrite ctx.cwd ctx.agent_config = agent_config ctx.cwd = agent_config.agent_name if not empty: click.echo("Adding default packages ...") add_item(ctx, PROTOCOL, PublicId.from_str(DEFAULT_PROTOCOL)) add_item(ctx, PROTOCOL, PublicId.from_str(SIGNING_PROTOCOL)) add_item(ctx, PROTOCOL, PublicId.from_str(STATE_UPDATE_PROTOCOL)) except Exception as e: raise click.ClickException(str(e)) def _create_agent_config(ctx: Context, agent_name: str, set_author: str) -> AgentConfig: """ Create agent config. :param ctx: context object. :param agent_name: agent name. :param set_author: author name to set. :return: AgentConfig object. """ agent_config = AgentConfig( agent_name=agent_name, aea_version=compute_specifier_from_version(get_current_aea_version()), author=set_author, version=DEFAULT_VERSION, license_=DEFAULT_LICENSE, description="", default_ledger=DEFAULT_LEDGER, required_ledgers=[DEFAULT_LEDGER], default_connection=None, dependencies=dependencies_from_json(DEFAULT_DEPENDENCIES), ) with open_file( os.path.join(agent_name, DEFAULT_AEA_CONFIG_FILE), "w" ) as config_file: ctx.agent_loader.dump(agent_config, config_file) return agent_config def _check_is_parent_folders_are_aea_projects_recursively() -> None: """Look for 'aea-config.yaml' in parent folders recursively up to the user home directory. :raises ValueError: if a parent folder has a file named 'aea-config.yaml'. """ current = Path(".").resolve() root = Path("/").resolve() home = current.home() while current not in (home, root): files = set(map(lambda x: x.name, current.iterdir())) if DEFAULT_AEA_CONFIG_FILE in files: raise ValueError( "Folder {} has file named {}".format(current, DEFAULT_AEA_CONFIG_FILE) ) current = current.parent.resolve() def _setup_package_folder(path: Path) -> None: """Set a package folder up.""" path.mkdir(exist_ok=False) init_module = path / "__init__.py" logger.debug("Creating {}".format(init_module)) Path(init_module).touch(exist_ok=False) ================================================ FILE: aea/cli/delete.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea delete' subcommand.""" import os import shutil from typing import cast import click from aea.cli.utils.click_utils import AgentDirectory from aea.cli.utils.context import Context @click.command() @click.argument( "agent_name", type=AgentDirectory(), required=True, ) @click.pass_context def delete(click_context: click.Context, agent_name: str) -> None: """Delete an agent.""" click.echo("Deleting AEA project directory './{}'...".format(agent_name)) ctx = cast(Context, click_context.obj) delete_aea(ctx, agent_name) def delete_aea(ctx: Context, agent_name: str) -> None: """ Delete agent's directory. :param ctx: click context :param agent_name: name of the agent (equal to folder name). :raises ClickException: if OSError occurred. """ agent_path = os.path.join(ctx.cwd, agent_name) try: shutil.rmtree(agent_path, ignore_errors=False) except OSError: raise click.ClickException( "An error occurred while deleting the agent directory. Aborting..." ) ================================================ FILE: aea/cli/eject.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea eject' subcommand.""" import shutil from pathlib import Path from typing import cast import click from packaging.version import Version import aea from aea.cli.remove import ItemRemoveHelper from aea.cli.utils.click_utils import PublicIdParameter from aea.cli.utils.config import ( load_item_config, set_cli_author, try_to_load_agent_config, update_item_config, ) from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, clean_after, pass_ctx from aea.cli.utils.package_utils import ( copy_package_directory, create_symlink_packages_to_vendor, create_symlink_vendor_to_local, fingerprint_all, get_package_path, is_item_present, replace_all_import_statements, update_item_public_id_in_init, update_references, ) from aea.configurations.base import ( ComponentId, ComponentType, PackageId, PackageType, PublicId, ) from aea.configurations.constants import ( CONNECTION, CONTRACT, DEFAULT_VERSION, PROTOCOL, SKILL, ) from aea.configurations.utils import get_latest_component_id_from_prefix from aea.helpers.base import ( compute_specifier_from_version, find_topological_order, reachable_nodes, ) @click.group() @click.option( "--with-symlinks", is_flag=True, help="Add symlinks from vendor to non-vendor and packages to vendor folders.", ) @click.option( "-q", "--quiet", "quiet", is_flag=True, required=False, default=False, help="If provided, the command will not ask the user for confirmation.", ) @click.pass_context @check_aea_project def eject(click_context: click.core.Context, quiet: bool, with_symlinks: bool) -> None: """Eject a vendor package of the agent.""" click_context.obj.set_config("quiet", quiet) click_context.obj.set_config("with_symlinks", with_symlinks) set_cli_author(click_context) @eject.command() @click.argument("public_id", type=PublicIdParameter(), required=True) @pass_ctx def connection(ctx: Context, public_id: PublicId) -> None: """Eject a vendor connection.""" quiet = ctx.config.get("quiet") with_symlinks = ctx.config.get("with_symlinks") _eject_item(ctx, CONNECTION, public_id, quiet=quiet, with_symlinks=with_symlinks) @eject.command() @click.argument("public_id", type=PublicIdParameter(), required=True) @pass_ctx def contract(ctx: Context, public_id: PublicId) -> None: """Eject a vendor contract.""" quiet = ctx.config.get("quiet") with_symlinks = ctx.config.get("with_symlinks") _eject_item(ctx, CONTRACT, public_id, quiet=quiet, with_symlinks=with_symlinks) @eject.command() @click.argument("public_id", type=PublicIdParameter(), required=True) @pass_ctx def protocol(ctx: Context, public_id: PublicId) -> None: """Eject a vendor protocol.""" quiet = ctx.config.get("quiet") with_symlinks = ctx.config.get("with_symlinks") _eject_item(ctx, PROTOCOL, public_id, quiet=quiet, with_symlinks=with_symlinks) @eject.command() @click.argument("public_id", type=PublicIdParameter(), required=True) @pass_ctx def skill(ctx: Context, public_id: PublicId) -> None: """Eject a vendor skill.""" quiet = ctx.config.get("quiet") with_symlinks = ctx.config.get("with_symlinks") _eject_item(ctx, SKILL, public_id, quiet=quiet, with_symlinks=with_symlinks) @clean_after def _eject_item( ctx: Context, item_type: str, public_id: PublicId, quiet: bool = True, with_symlinks: bool = False, ) -> None: """ Eject item from installed (vendor) to custom folder. :param ctx: context object. :param item_type: item type. :param public_id: item public ID. :param quiet: if false, the function will ask the user in case of recursive eject. :param with_symlinks: if eject should create symlinks. :raises ClickException: if item is absent at source path or present at destination path. """ # we know cli_author is set because of the above checks. cli_author: str = cast(str, ctx.config.get("cli_author")) item_type_plural = item_type + "s" if not is_item_present( ctx.cwd, ctx.agent_config, item_type, public_id, is_vendor=True, with_version=True, ): # pragma: no cover raise click.ClickException( f"{item_type.title()} {public_id} not found in agent's vendor items." ) src = get_package_path(ctx.cwd, item_type, public_id) dst = get_package_path(ctx.cwd, item_type, public_id, is_vendor=False) if is_item_present( ctx.cwd, ctx.agent_config, item_type, public_id, is_vendor=False ): # pragma: no cover raise click.ClickException( f"{item_type.title()} {public_id} is already a non-vendor package." ) configuration = load_item_config(item_type, Path(src)) if public_id.package_version.is_latest: # get 'concrete' public id, in case it is 'latest' component_prefix = ComponentType(item_type), public_id.author, public_id.name component_id = get_latest_component_id_from_prefix( ctx.agent_config, component_prefix ) # component id is necessarily found, due to the checks above. public_id = cast(ComponentId, component_id).public_id package_id = PackageId(PackageType(item_type), public_id) click.echo( f"Ejecting item {package_id.package_type.value} {str(package_id.public_id)}" ) # first, eject all the vendor packages that depend on this item_remover = ItemRemoveHelper(ctx, ignore_non_vendor=True) reverse_dependencies = ( item_remover.get_agent_dependencies_with_reverse_dependencies() ) reverse_reachable_dependencies = reachable_nodes(reverse_dependencies, {package_id}) # the reversed topological order of a graph # is the topological order of the reverse graph. eject_order = list(reversed(find_topological_order(reverse_reachable_dependencies))) eject_order.remove(package_id) if len(eject_order) > 0 and not quiet: click.echo(f"The following vendor packages will be ejected: {eject_order}") answer = click.confirm("Do you want to proceed?") if not answer: click.echo("Aborted.") return for dependency_package_id in eject_order: # 'dependency_package_id' depends on 'package_id', # so we need to eject it first _eject_item( ctx, dependency_package_id.package_type.value, dependency_package_id.public_id, quiet=True, ) # copy the vendor package into the non-vendor packages ctx.clean_paths.append(dst) copy_package_directory(Path(src), dst) new_public_id = PublicId(cli_author, public_id.name, DEFAULT_VERSION) current_version = Version(aea.__version__) new_aea_range = ( configuration.aea_version if configuration.aea_version_specifiers.contains(current_version) else compute_specifier_from_version(current_version) ) update_item_config( item_type, Path(dst), author=new_public_id.author, version=new_public_id.version, aea_version=new_aea_range, ) update_item_public_id_in_init(item_type, Path(dst), new_public_id) shutil.rmtree(src) # update references in all the other packages component_type = ComponentType(item_type_plural[:-1]) old_component_id = ComponentId(component_type, public_id) new_component_id = ComponentId(component_type, new_public_id) update_references(ctx, {old_component_id: new_component_id}) # need to reload agent configuration with the updated references try_to_load_agent_config(ctx) # replace import statements in all the non-vendor packages replace_all_import_statements( Path(ctx.cwd), ComponentType(item_type), public_id, new_public_id ) # fingerprint all (non-vendor) packages fingerprint_all(ctx) if with_symlinks: click.echo( "Adding symlinks from vendor to non-vendor and packages to vendor folders." ) create_symlink_vendor_to_local(ctx, item_type, new_public_id) create_symlink_packages_to_vendor(ctx) click.echo( f"Successfully ejected {item_type} {public_id} to {dst} as {new_public_id}." ) ================================================ FILE: aea/cli/fetch.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea fetch' subcommand.""" import os from pathlib import Path from shutil import copytree as copy_tree from typing import Optional, cast import click from aea.cli.add import add_item from aea.cli.registry.fetch import fetch_agent from aea.cli.utils.click_utils import PublicIdParameter, registry_flag from aea.cli.utils.config import try_to_load_agent_config from aea.cli.utils.context import Context from aea.cli.utils.decorators import clean_after from aea.cli.utils.loggers import logger from aea.cli.utils.package_utils import try_get_item_source_path from aea.configurations.base import PublicId from aea.configurations.constants import ( AGENTS, CONNECTION, CONTRACT, DEFAULT_AEA_CONFIG_FILE, PROTOCOL, SKILL, ) from aea.exceptions import enforce from aea.helpers.io import open_file @click.command(name="fetch") @registry_flag( help_local="For fetching agent from local folder.", help_remote="For fetching agent from remote registry.", ) @click.option( "--alias", type=str, required=False, help="Provide a local alias for the agent.", ) @click.argument("public-id", type=PublicIdParameter(), required=True) @click.pass_context def fetch( click_context: click.Context, public_id: PublicId, alias: str, local: bool, remote: bool, ) -> None: """Fetch an agent from the registry.""" ctx = cast(Context, click_context.obj) do_fetch(ctx, public_id, local, remote, alias) def do_fetch( ctx: Context, public_id: PublicId, local: bool, remote: bool, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Run the Fetch command. :param ctx: the CLI context. :param public_id: the public id. :param local: whether to fetch from local :param remote: whether to fetch from remote :param alias: the agent alias. :param target_dir: the target directory, in case fetching locally. """ enforce( not (local and remote), "'local' and 'remote' options are mutually exclusive." ) if not local and not remote: try: ctx.registry_path except ValueError as e: click.echo(f"{e}\nTrying remote registry (`--remote`).") remote = True is_mixed = not local and not remote ctx.set_config("is_local", local and not remote) ctx.set_config("is_mixed", is_mixed) if remote: fetch_agent(ctx, public_id, alias=alias, target_dir=target_dir) elif local: fetch_agent_locally(ctx, public_id, alias=alias, target_dir=target_dir) else: fetch_mixed(ctx, public_id, alias=alias, target_dir=target_dir) def _is_version_correct(ctx: Context, agent_public_id: PublicId) -> bool: """ Compare agent version to the one in public ID. :param ctx: Context object. :param agent_public_id: public ID of an agent. :return: bool is version correct. """ return ctx.agent_config.public_id.same_prefix(agent_public_id) and ( agent_public_id.package_version.is_latest or ctx.agent_config.version == agent_public_id.version ) @clean_after def fetch_agent_locally( ctx: Context, public_id: PublicId, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Fetch Agent from local packages. :param ctx: a Context object. :param public_id: public ID of agent to be fetched. :param alias: an optional alias. :param target_dir: the target directory to which the agent is fetched. """ try: registry_path = ctx.registry_path except ValueError as e: raise click.ClickException(str(e)) source_path = try_get_item_source_path( registry_path, public_id.author, AGENTS, public_id.name ) enforce( ctx.config.get("is_local") is True or ctx.config.get("is_mixed") is True, "Please use `ctx.set_config('is_local', True)` or `ctx.set_config('is_mixed', True)` to fetch agent and all components locally.", ) try_to_load_agent_config(ctx, agent_src_path=source_path) if not _is_version_correct(ctx, public_id): raise click.ClickException( "Wrong agent version in public ID: specified {}, found {}.".format( public_id.version, ctx.agent_config.version ) ) folder_name = target_dir or (public_id.name if alias is None else alias) target_path = os.path.join(ctx.cwd, folder_name) if os.path.exists(target_path): path = Path(target_path) raise click.ClickException( f'Item "{path.name}" already exists in target folder "{path.parent}".' ) if target_dir is not None: os.makedirs(target_path) # pragma: nocover ctx.clean_paths.append(target_path) copy_tree(source_path, target_path, dirs_exist_ok=True) # type: ignore ctx.cwd = target_path try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias ctx.agent_loader.dump( ctx.agent_config, open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w"), ) _fetch_agent_deps(ctx) click.echo("Agent {} successfully fetched.".format(public_id.name)) def _fetch_agent_deps(ctx: Context) -> None: """ Fetch agent dependencies. :param ctx: context object. """ for item_type in (PROTOCOL, CONTRACT, CONNECTION, SKILL): item_type_plural = "{}s".format(item_type) required_items = getattr(ctx.agent_config, item_type_plural) for item_id in required_items: add_item(ctx, item_type, item_id) def fetch_mixed( ctx: Context, public_id: PublicId, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Fetch an agent in mixed mode. :param ctx: the Context. :param public_id: the public id. :param alias: the alias to the agent. :param target_dir: the target directory. """ try: fetch_agent_locally(ctx, public_id, alias=alias, target_dir=target_dir) except click.ClickException as e: logger.debug( f"Fetch from local registry failed (reason={str(e)}), trying remote registry..." ) fetch_agent(ctx, public_id, alias=alias, target_dir=target_dir) ================================================ FILE: aea/cli/fingerprint.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea add' subcommand.""" import os from pathlib import Path from typing import Dict, Union, cast import click from aea.cli.utils.click_utils import PublicIdParameter from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, pass_ctx from aea.configurations.base import ( PublicId, _compute_fingerprint, _get_default_configuration_file_name_from_type, ) from aea.configurations.constants import ( # noqa: F401 # pylint: disable=unused-import CONFIG_FILE_TO_PACKAGE_TYPE, CONNECTION, CONTRACT, DEFAULT_IGNORE_DIRS_AGENT_FINGERPRINT, PROTOCOL, SKILL, ) from aea.configurations.data_types import PackageType from aea.configurations.loader import ConfigLoader from aea.helpers.io import open_file @click.group(invoke_without_command=True) @click.pass_context def fingerprint( click_context: click.core.Context, ) -> None: """Fingerprint a non-vendor package of the agent.""" if click_context.invoked_subcommand is None: fingerprint_agent(click_context) @fingerprint.command() @click.argument("connection_public_id", type=PublicIdParameter(), required=True) @pass_ctx def connection(ctx: Context, connection_public_id: PublicId) -> None: """Fingerprint a connection and add the fingerprints to the configuration file.""" fingerprint_item(ctx, CONNECTION, connection_public_id) @fingerprint.command() @click.argument("contract_public_id", type=PublicIdParameter(), required=True) @pass_ctx def contract(ctx: Context, contract_public_id: PublicId) -> None: """Fingerprint a contract and add the fingerprints to the configuration file.""" fingerprint_item(ctx, CONTRACT, contract_public_id) @fingerprint.command() @click.argument("protocol_public_id", type=PublicIdParameter(), required=True) @pass_ctx def protocol(ctx: Context, protocol_public_id: PublicId) -> None: """Fingerprint a protocol and add the fingerprints to the configuration file..""" fingerprint_item(ctx, PROTOCOL, protocol_public_id) @fingerprint.command() @click.argument("skill_public_id", type=PublicIdParameter(), required=True) @pass_ctx def skill(ctx: Context, skill_public_id: PublicId) -> None: """Fingerprint a skill and add the fingerprints to the configuration file.""" fingerprint_item(ctx, SKILL, skill_public_id) @fingerprint.command() @click.argument("path", type=str, required=True) @pass_ctx def by_path(ctx: Context, path: str) -> None: """Fingerprint a package by its path.""" try: click.echo("Fingerprinting component in '{}' ...".format(path)) full_path = Path(ctx.cwd) / Path(path) fingerprint_package_by_path(full_path) except Exception as e: raise click.ClickException(str(e)) def fingerprint_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: """ Fingerprint components of an item. :param ctx: the context. :param item_type: the item type. :param item_public_id: the item public id. """ item_type_plural = item_type + "s" click.echo( "Fingerprinting {} components of '{}' ...".format(item_type, item_public_id) ) # create fingerprints package_dir = Path(ctx.cwd, item_type_plural, item_public_id.name) try: fingerprint_package(package_dir, item_type) except Exception as e: raise click.ClickException(str(e)) def fingerprint_package_by_path(package_dir: Path) -> None: """ Fingerprint package placed in package_dir. :param package_dir: directory of the package """ package_type = determine_package_type_for_directory(package_dir) fingerprint_package(package_dir, package_type) def determine_package_type_for_directory(package_dir: Path) -> PackageType: """ Find package type for the package directory by checking config file names. :param package_dir: package dir to determine package type: :return: PackageType """ config_files = list( set(os.listdir(str(package_dir))).intersection( set(CONFIG_FILE_TO_PACKAGE_TYPE.keys()) ) ) if len(config_files) > 1: raise ValueError( f"Too many config files in the directory, only one has to present!: {', '.join(config_files)}" ) if len(config_files) == 0: raise ValueError( f"No package config file found in `{str(package_dir)}`. Incorrect directory?" ) config_file = config_files[0] package_type = PackageType(CONFIG_FILE_TO_PACKAGE_TYPE[config_file]) return package_type def fingerprint_package( package_dir: Path, package_type: Union[str, PackageType] ) -> None: """ Fingerprint components of an item. :param package_dir: the package directory. :param package_type: the package type. """ package_type = PackageType(package_type) item_type = str(package_type) default_config_file_name = _get_default_configuration_file_name_from_type(item_type) config_loader = ConfigLoader.from_configuration_type(item_type) config_file_path = Path(package_dir, default_config_file_name) config = config_loader.load(open_file(config_file_path)) if not package_dir.exists(): # we only permit non-vendorized packages to be fingerprinted raise ValueError("Package not found at path {}".format(package_dir)) fingerprints_dict = _compute_fingerprint( package_dir, ignore_patterns=config.fingerprint_ignore_patterns ) # type: Dict[str, str] # Load item specification yaml file and add fingerprints config.fingerprint = fingerprints_dict config_loader.dump(config, open_file(config_file_path, "w")) @check_aea_project(check_finger_prints=False) # pylint: disable=no-value-for-parameter def fingerprint_agent(click_context: click.Context) -> None: """Do a fingerprint for an agent.""" ctx = cast(Context, click_context.obj) click.echo( f"Fingerprinting files in agent project '{ctx.agent_config.agent_name}'..." ) fingerprints_dict = _compute_fingerprint( Path(ctx.cwd), ignore_patterns=ctx.agent_config.fingerprint_ignore_patterns, ignore_directories=DEFAULT_IGNORE_DIRS_AGENT_FINGERPRINT, ) # type: Dict[str, str] ctx.agent_config.fingerprint = fingerprints_dict ctx.dump_agent_config() click.echo(f"Fingerprint for agent `{ctx.agent_config.name}` calculated!") ================================================ FILE: aea/cli/freeze.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea delete' subcommand.""" from typing import List, cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project @click.command() @click.pass_context @check_aea_project def freeze(click_context: click.Context) -> None: """Get the dependencies of the agent.""" deps = _get_deps(click_context) for dependency in deps: click.echo(dependency) def _get_deps(click_context: click.core.Context) -> List[str]: """ Get dependencies list. :param click_context: click context object. :return: list of str dependencies. """ ctx = cast(Context, click_context.obj) deps = [] for dependency_name, dependency_data in sorted( ctx.get_dependencies().items(), key=lambda x: x[0] ): deps.append(dependency_name + dependency_data.version) return deps ================================================ FILE: aea/cli/generate.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea generate' subcommand.""" import os from typing import Set import click import yaml from aea.cli.fingerprint import fingerprint_item from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, clean_after, pass_ctx from aea.cli.utils.loggers import logger from aea.configurations.base import ( ProtocolSpecification, ProtocolSpecificationParseError, PublicId, ) from aea.configurations.constants import ( DEFAULT_AEA_CONFIG_FILE, PROTOCOL, PROTOCOL_LANGUAGE_PYTHON, SUPPORTED_PROTOCOL_LANGUAGES, ) from aea.helpers.io import open_file from aea.protocols.generator.base import ProtocolGenerator @click.group() @click.pass_context @check_aea_project def generate( click_context: click.core.Context, # pylint: disable=unused-argument ) -> None: """Generate a package for the agent.""" @generate.command() @click.argument("protocol_specification_path", type=str, required=True) @click.option( "--l", "language", type=click.Choice(SUPPORTED_PROTOCOL_LANGUAGES), required=False, default=PROTOCOL_LANGUAGE_PYTHON, help="Specify the language in which to generate the protocol package.", ) @pass_ctx def protocol(ctx: Context, protocol_specification_path: str, language: str) -> None: """Generate a protocol based on a specification and add it to the configuration file and agent.""" ctx.set_config("language", language) _generate_protocol(ctx, protocol_specification_path) @clean_after def _generate_protocol(ctx: Context, protocol_specification_path: str) -> None: """Generate a protocol based on a specification and add it to the configuration file and agent.""" protocol_plural = PROTOCOL + "s" # Create protocol generator (load, validate, # extract fields from protocol specification yaml file) try: output_path = os.path.join(ctx.cwd, protocol_plural) protocol_generator = ProtocolGenerator(protocol_specification_path, output_path) except FileNotFoundError as e: raise click.ClickException( # pragma: no cover "Protocol is NOT generated. The following error happened while generating the protocol:\n" + str(e) ) except yaml.YAMLError as e: raise click.ClickException( # pragma: no cover "Protocol is NOT generated. The following error happened while generating the protocol:\n" + "Yaml error in the protocol specification file:" + str(e) ) except ProtocolSpecificationParseError as e: raise click.ClickException( # pragma: no cover "Protocol is NOT generated. The following error happened while generating the protocol:\n" + "Error while parsing the protocol specification: " + str(e) ) except Exception as e: # pragma: no cover raise click.ClickException( # pragma: no cover "Protocol is NOT generated. The following error happened while generating the protocol:\n" + str(e) ) # helpers language = ctx.config.get("language") existing_protocol_ids_list = getattr(ctx.agent_config, "{}s".format(PROTOCOL)) existing_protocol_name_list = [ public_id.name for public_id in existing_protocol_ids_list ] protocol_spec = protocol_generator.protocol_specification protocol_directory_path = os.path.join(ctx.cwd, protocol_plural, protocol_spec.name) # Check if a protocol with the same name exists in the agent config if protocol_spec.name in existing_protocol_name_list: raise click.ClickException( "Protocol is NOT generated. The following error happened while generating the protocol:\n" + f"A {PROTOCOL} with name '{protocol_spec.name}' already exists. Aborting..." ) # Check if a directory with the same name as the protocol's exists # in the protocols directory of the agent's directory if os.path.exists(protocol_directory_path): raise click.ClickException( "Protocol is NOT generated. The following error happened while generating the protocol:\n" + f"A directory with name '{protocol_spec.name}' already exists. Aborting..." ) ctx.clean_paths.append(protocol_directory_path) agent_name = ctx.agent_config.agent_name click.echo( "Generating {} '{}' and adding it to the agent '{}'...".format( PROTOCOL, protocol_spec.name, agent_name ) ) if language == PROTOCOL_LANGUAGE_PYTHON: _generate_full_mode( ctx, protocol_generator, protocol_spec, existing_protocol_ids_list, language ) else: _generate_protobuf_mode(ctx, protocol_generator, language) @clean_after def _generate_full_mode( ctx: Context, protocol_generator: ProtocolGenerator, protocol_spec: ProtocolSpecification, existing_id_list: Set[PublicId], language: str, ) -> None: """Generate a protocol in 'full' mode, and add it to the configuration file and agent.""" try: warning_message = protocol_generator.generate( protobuf_only=False, language=language ) if warning_message is not None: click.echo(warning_message) # Add the item to the configurations logger.debug( "Registering the {} into {}".format(PROTOCOL, DEFAULT_AEA_CONFIG_FILE) ) existing_id_list.add( PublicId(protocol_spec.author, protocol_spec.name, protocol_spec.version) ) ctx.agent_loader.dump( ctx.agent_config, open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w"), ) except FileExistsError: raise click.ClickException( # pragma: no cover "A {} with this name already exists. Please choose a different name and try again.".format( PROTOCOL ) ) except Exception as e: raise click.ClickException( "Protocol is NOT generated. The following error happened while generating the protocol:\n" + str(e) ) fingerprint_item(ctx, PROTOCOL, protocol_spec.public_id) @clean_after def _generate_protobuf_mode( ctx: Context, # pylint: disable=unused-argument protocol_generator: ProtocolGenerator, language: str, ) -> None: """Generate a protocol in 'protobuf' mode, and add it to the configuration file and agent.""" try: warning_message = protocol_generator.generate( protobuf_only=True, language=language ) if warning_message is not None: click.echo(warning_message) except FileExistsError: # pragma: no cover raise click.ClickException( # pragma: no cover f"A {PROTOCOL} with this name already exists. Please choose a different name and try again." ) except Exception as e: # pragma: no cover raise click.ClickException( # pragma: no cover "Protocol is NOT generated. The following error happened while generating the protocol:\n" + str(e) ) ================================================ FILE: aea/cli/generate_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea generate_key' subcommand.""" from pathlib import Path from typing import Dict, Optional import click from aea.cli.add_key import _add_private_key from aea.cli.utils.click_utils import password_option from aea.cli.utils.decorators import _check_aea_project from aea.configurations.constants import PRIVATE_KEY_PATH_SCHEMA from aea.crypto.helpers import create_private_key from aea.crypto.registries import crypto_registry @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice([*list(crypto_registry.supported_ids), "all"]), required=True, ) @click.argument( "file", metavar="FILE", type=click.Path(exists=False, file_okay=True, dir_okay=False, readable=True), required=False, ) @password_option(confirmation_prompt=True) @click.option( "--add-key", is_flag=True, help="Add key generated.", ) @click.option( "--connection", is_flag=True, help="For adding a private key for connections." ) @click.pass_context def generate_key( click_context: click.core.Context, type_: str, file: str, password: Optional[str], add_key: bool = False, connection: bool = False, ) -> None: """Generate a private key and place it in a file.""" keys_generated = _generate_private_key(type_, file, password) if add_key: _check_aea_project((click_context,)) for key_type, key_filename in keys_generated.items(): _add_private_key( click_context, key_type, key_filename, password, connection ) def _generate_private_key( type_: str, file: Optional[str] = None, password: Optional[str] = None ) -> Dict[str, str]: """ Generate private key. :param type_: type. :param file: path to file. :param password: the password to encrypt/decrypt the private key. :return: dict of types and filenames of keys generated """ keys = {} if type_ == "all" and file is not None: raise click.ClickException("Type all cannot be used in combination with file.") types = list(crypto_registry.supported_ids) if type_ == "all" else [type_] for type__ in types: private_key_file = ( PRIVATE_KEY_PATH_SCHEMA.format(type__) if file is None else file ) if _can_write(private_key_file): create_private_key(type__, private_key_file, password) keys[type__] = private_key_file return keys def _can_write(path: str) -> bool: if Path(path).exists(): value = click.confirm( "The file {} already exists. Do you want to overwrite it?".format(path), default=False, ) return value return True ================================================ FILE: aea/cli/generate_wealth.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea generate_wealth' subcommand.""" from typing import Optional, cast import click from aea.cli.utils.click_utils import password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.package_utils import get_wallet_from_context from aea.crypto.helpers import try_generate_testnet_wealth from aea.crypto.registries import faucet_apis_registry, make_faucet_api_cls @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice(list(faucet_apis_registry.supported_ids)), required=True, ) @click.argument("url", metavar="URL", type=str, required=False, default=None) @password_option() @click.option( "--sync", is_flag=True, help="For waiting till the faucet has released the funds." ) @click.pass_context @check_aea_project def generate_wealth( click_context: click.Context, type_: str, url: str, password: Optional[str], sync: bool, ) -> None: """Generate wealth for the agent on a test network.""" ctx = cast(Context, click_context.obj) _try_generate_wealth(ctx, type_, url, sync, password) def _try_generate_wealth( ctx: Context, type_: str, url: Optional[str], sync: bool = False, password: Optional[str] = None, ) -> None: """ Try generate wealth for the provided network identifier. :param ctx: the click context :param type_: the network type :param url: the url :param sync: whether to sync or not :param password: the password to encrypt/decrypt the private key. """ wallet = get_wallet_from_context(ctx, password=password) try: address = wallet.addresses[type_] faucet_api_cls = make_faucet_api_cls(type_) testnet = faucet_api_cls.network_name click.echo( "Requesting funds for address {} on test network '{}'".format( address, testnet ) ) try_generate_testnet_wealth(type_, address, url, sync) except ValueError as e: # pragma: no cover raise click.ClickException(str(e)) ================================================ FILE: aea/cli/get_address.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea get_address' subcommand.""" from typing import Optional, cast import click from aea.cli.utils.click_utils import password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.package_utils import get_wallet_from_context from aea.crypto.registries import crypto_registry @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice(list(crypto_registry.supported_ids)), required=True, ) @password_option() @click.pass_context @check_aea_project def get_address( click_context: click.Context, type_: str, password: Optional[str] ) -> None: """Get the address associated with a private key of the agent.""" ctx = cast(Context, click_context.obj) address = _try_get_address(ctx, type_, password) click.echo(address) def _try_get_address(ctx: Context, type_: str, password: Optional[str] = None) -> str: """ Try to get address. :param ctx: click context object. :param type_: type. :param password: the password to encrypt/decrypt the private key. :return: address. """ wallet = get_wallet_from_context(ctx, password=password) try: address = wallet.addresses[type_] return address except ValueError as e: # pragma: no cover raise click.ClickException(str(e)) ================================================ FILE: aea/cli/get_multiaddress.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea get_multiaddress' subcommand.""" import re import typing from pathlib import Path from typing import Optional, Tuple, cast import click from click import ClickException from aea.cli.utils.click_utils import PublicIdParameter, password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.configurations.base import PublicId from aea.configurations.constants import CONNECTIONS from aea.configurations.manager import AgentConfigManager from aea.crypto.base import Crypto from aea.crypto.registries import crypto_registry from aea.exceptions import enforce from aea.helpers.multiaddr.base import MultiAddr URI_REGEX = re.compile(r"(?:https?://)?(?P[^:/ ]+):(?P[0-9]*)") @click.command() @click.argument( "ledger_id", metavar="TYPE", type=click.Choice(list(crypto_registry.supported_ids)), required=True, ) @password_option() @click.option("-c", "--connection", is_flag=True) @click.option( "-i", "--connection-id", type=PublicIdParameter(), required=False, default=None, ) @click.option( "-h", "--host-field", type=str, required=False, default=None, ) @click.option( "-p", "--port-field", type=str, required=False, default=None, ) @click.option( "-u", "--uri-field", type=str, required=False, default="public_uri", ) @click.pass_context @check_aea_project def get_multiaddress( click_context: click.Context, ledger_id: str, password: Optional[str], connection: bool, connection_id: Optional[PublicId], host_field: str, port_field: str, uri_field: str, ) -> None: """Get the multiaddress associated with a private key or connection of the agent.""" address = _try_get_multiaddress( click_context, ledger_id, password, connection, connection_id, host_field, port_field, uri_field, ) click.echo(address) def _try_get_multiaddress( click_context: click.Context, ledger_id: str, password: Optional[str] = None, is_connection: bool = False, connection_id: Optional[PublicId] = None, host_field: Optional[str] = None, port_field: Optional[str] = None, uri_field: str = "public_uri", ) -> str: """ Try to get the multi-address. :param click_context: click context object. :param ledger_id: the ledger id. :param password: the password to encrypt/decrypt the private key. :param is_connection: whether the key to load is from the wallet or from connections. :param connection_id: the connection id. :param host_field: if connection_id specified, the config field to retrieve the host :param port_field: if connection_id specified, the config field to retrieve the port :param uri_field: uri field :return: address. """ ctx = cast(Context, click_context.obj) # connection_id not None implies is_connection is_connection = connection_id is not None or is_connection private_key_paths = ( ctx.agent_config.private_key_paths if not is_connection else ctx.agent_config.connection_private_key_paths ) private_key_path = private_key_paths.read(ledger_id) if private_key_path is None: raise ClickException( f"Cannot find '{ledger_id}'. Please check {'private_key_path' if not is_connection else 'connection_private_key_paths'}." ) path_to_key = Path(private_key_path) crypto = crypto_registry.make( ledger_id, private_key_path=path_to_key, password=password ) if connection_id is None: return _try_get_peerid(crypto) return _try_get_connection_multiaddress( click_context, crypto, cast(PublicId, connection_id), host_field, port_field, uri_field, ) def _try_get_peerid(crypto: Crypto) -> str: """Try to get the peer id.""" try: peer_id = MultiAddr("", 0, crypto.public_key).peer_id return peer_id except Exception as e: raise ClickException(str(e)) def _read_host_and_port_from_config( connection_config: dict, connection_id: PublicId, uri_field: str, host_field: Optional[str], port_field: Optional[str], ) -> Tuple[str, int]: """ Read host and port from config connection. :param connection_config: connection configuration. :param connection_id: the connection id. :param uri_field: the uri field. :param host_field: the host field. :param port_field: the port field. :return: the host and the port. """ host_is_none = host_field is None port_is_none = port_field is None one_is_none = (not host_is_none and port_is_none) or ( host_is_none and not port_is_none ) if not host_is_none and not port_is_none: if host_field not in connection_config: raise ClickException( f"Host field '{host_field}' not present in connection configuration {connection_id}" ) if port_field not in connection_config: raise ClickException( f"Port field '{port_field}' not present in connection configuration {connection_id}" ) host = connection_config[host_field] port = int(connection_config[port_field]) return host, port if one_is_none: raise ClickException( "-h/--host-field and -p/--port-field must be specified together." ) if uri_field not in connection_config: raise ClickException( f"URI field '{uri_field}' not present in connection configuration {connection_id}" ) url_value = connection_config[uri_field] try: m = URI_REGEX.search(url_value) enforce(m is not None, f"URI Doesn't match regex '{URI_REGEX}'") m = cast(typing.Match, m) host = m.group("host") port = int(m.group("port")) return host, port except Exception as e: raise ClickException( f"Cannot extract host and port from {uri_field}: '{url_value}'. Reason: {str(e)}" ) def _try_get_connection_multiaddress( click_context: click.Context, crypto: Crypto, connection_id: PublicId, host_field: Optional[str], port_field: Optional[str], uri_field: str, ) -> str: """ Try to get the connection multiaddress. The host and the port options have the precedence over the uri option. :param click_context: the click context object. :param crypto: the crypto. :param connection_id: the connection id. :param host_field: the host field. :param port_field: the port field. :param uri_field: the uri field. :return: the multiaddress. """ ctx = cast(Context, click_context.obj) if connection_id not in ctx.agent_config.connections: raise ValueError(f"Cannot find connection with the public id {connection_id}.") agent_config_manager = AgentConfigManager.load(ctx.cwd) connection_config = cast( dict, agent_config_manager.get_variable( f"vendor.{connection_id.author}.{CONNECTIONS}.{connection_id.name}.config" ), ) host, port = _read_host_and_port_from_config( connection_config, connection_id, uri_field, host_field, port_field ) try: multiaddr = MultiAddr(host, port, crypto.public_key) return multiaddr.format() except Exception as e: raise ClickException(f"An error occurred while creating the multiaddress: {e}") ================================================ FILE: aea/cli/get_public_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea get_public_key' subcommand.""" from typing import Optional, cast import click from aea.cli.utils.click_utils import password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.package_utils import get_wallet_from_context from aea.crypto.registries import crypto_registry @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice(list(crypto_registry.supported_ids)), required=True, ) @password_option() @click.pass_context @check_aea_project def get_public_key( click_context: click.Context, type_: str, password: Optional[str] ) -> None: """Get the public key associated with a private key of the agent.""" ctx = cast(Context, click_context.obj) publickey = _try_get_public_key(ctx, type_, password) click.echo(publickey) def _try_get_public_key( ctx: Context, type_: str, password: Optional[str] = None ) -> str: """ Try to get public key. :param ctx: click context object. :param type_: type. :param password: the password to encrypt/decrypt the private key. :return: public key str. """ wallet = get_wallet_from_context(ctx, password=password) try: publickey = wallet.public_keys[type_] return publickey except ValueError as e: # pragma: no cover raise click.ClickException(str(e)) ================================================ FILE: aea/cli/get_wealth.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea get_wealth' subcommand.""" from typing import Optional, cast import click from aea.cli.utils.click_utils import password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.package_utils import ( _override_ledger_configurations, get_wallet_from_context, try_get_balance, ) from aea.crypto.registries import ledger_apis_registry @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice(list(ledger_apis_registry.supported_ids)), required=True, ) @password_option() @click.pass_context @check_aea_project def get_wealth( click_context: click.Context, type_: str, password: Optional[str] ) -> None: """Get the wealth associated with the private key of the agent.""" ctx = cast(Context, click_context.obj) wealth = _try_get_wealth(ctx, type_, password) click.echo(wealth) def _try_get_wealth(ctx: Context, type_: str, password: Optional[str] = None) -> int: """Try get wealth.""" wallet = get_wallet_from_context(ctx, password=password) _override_ledger_configurations(ctx.agent_config) return try_get_balance(ctx.agent_config, wallet, type_) ================================================ FILE: aea/cli/init.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea init' subcommand.""" import click from aea import __version__ from aea.cli.login import do_login from aea.cli.register import do_register from aea.cli.registry.settings import AUTH_TOKEN_KEY from aea.cli.registry.utils import check_is_author_logged_in, is_auth_token_present from aea.cli.utils.config import get_or_create_cli_config, update_cli_config from aea.cli.utils.constants import AEA_LOGO, AUTHOR_KEY from aea.cli.utils.context import Context from aea.cli.utils.decorators import pass_ctx from aea.cli.utils.package_utils import validate_author_name @click.command() @click.option("--author", type=str, required=False) @click.option("--reset", is_flag=True, help="To reset the initialization.") @click.option( "--register", is_flag=True, help="To register the author in the AEA registry." ) @click.option("--no-subscribe", is_flag=True, help="For developers subscription.") @pass_ctx def init( # pylint: disable=unused-argument ctx: Context, author: str, reset: bool, register: bool, no_subscribe: bool ) -> None: """Initialize your AEA configurations.""" do_init(author, reset, register, no_subscribe) def do_init(author: str, reset: bool, registry: bool, no_subscribe: bool) -> None: """ Initialize your AEA configurations. :param author: str author username. :param reset: True, if resetting the author name :param registry: True, if registry is used :param no_subscribe: bool flag for developers subscription skip on register. """ config = get_or_create_cli_config() if reset or config.get(AUTHOR_KEY, None) is None: author = validate_author_name(author) if registry: _registry_init(username=author, no_subscribe=no_subscribe) update_cli_config({AUTHOR_KEY: author}) config = get_or_create_cli_config() config.pop(AUTH_TOKEN_KEY, None) # for security reasons success_msg = "AEA configurations successfully initialized: {}".format(config) else: config.pop(AUTH_TOKEN_KEY, None) # for security reasons success_msg = "AEA configurations already initialized: {}. To reset use '--reset'.".format( config ) click.echo(AEA_LOGO + "v" + __version__ + "\n") click.echo(success_msg) def _registry_init(username: str, no_subscribe: bool) -> None: """ Create an author name on the registry. :param username: the user name :param no_subscribe: bool flag for developers subscription skip on register. """ if username is not None and is_auth_token_present(): check_is_author_logged_in(username) else: is_registered = click.confirm("Do you have a Registry account?") if is_registered: password = click.prompt("Password", type=str, hide_input=True) do_login(username, password) else: click.echo("Create a new account on the Registry now:") email = click.prompt("Email", type=str) password = click.prompt("Password", type=str, hide_input=True) password_confirmation = "" # nosec while password_confirmation != password: click.echo("Please make sure that passwords are equal.") password_confirmation = click.prompt( "Confirm password", type=str, hide_input=True ) do_register(username, email, password, password_confirmation, no_subscribe) ================================================ FILE: aea/cli/install.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea install' subcommand.""" from typing import Optional, cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.loggers import logger from aea.configurations.data_types import Dependencies from aea.configurations.pypi import is_satisfiable, is_simple_dep, to_set_specifier from aea.exceptions import AEAException from aea.helpers.install_dependency import call_pip, install_dependencies @click.command() @click.option( "-r", "--requirement", type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True), required=False, default=None, help="Install from the given requirements file.", ) @click.pass_context @check_aea_project def install(click_context: click.Context, requirement: Optional[str]) -> None: """Install the dependencies of the agent.""" ctx = cast(Context, click_context.obj) do_install(ctx, requirement) def do_install(ctx: Context, requirement: Optional[str] = None) -> None: """ Install necessary dependencies. :param ctx: context object. :param requirement: optional str requirement. :raises ClickException: if AEAException occurs. """ try: if requirement: logger.debug("Installing the dependencies in '{}'...".format(requirement)) _install_from_requirement(requirement) else: logger.debug("Installing all the dependencies...") dependencies = ctx.get_dependencies() logger.debug("Preliminary check on satisfiability of version specifiers...") unsat_dependencies = _find_unsatisfiable_dependencies(dependencies) if len(unsat_dependencies) != 0: raise AEAException( "cannot install the following dependencies " + "as the joint version specifier is unsatisfiable:\n - " + "\n -".join( [ f"{name}: {to_set_specifier(dep)}" for name, dep in unsat_dependencies.items() ] ) ) install_dependencies(list(dependencies.values()), logger=logger) except AEAException as e: raise click.ClickException(str(e)) def _find_unsatisfiable_dependencies(dependencies: Dependencies) -> Dependencies: """ Find unsatisfiable dependencies. It only checks among 'simple' dependencies (i.e. if it has no field specified, or only the 'version' field set.) :param dependencies: the dependencies to check. :return: the unsatisfiable dependencies. """ return { name: dep for name, dep in dependencies.items() if is_simple_dep(dep) and not is_satisfiable(to_set_specifier(dep)) } def _install_from_requirement(file: str, install_timeout: float = 300) -> None: """ Install from requirements. :param file: requirement.txt file path :param install_timeout: timeout to wait pip to install :raises AEAException: if an error occurs during installation. """ try: call_pip(["install", "-r", file], timeout=install_timeout) except Exception: raise AEAException( "An error occurred while installing requirement file {}. Stopping...".format( file ) ) ================================================ FILE: aea/cli/interact.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea interact' subcommand.""" import codecs import os from typing import Optional, TYPE_CHECKING, Type, Union import click from aea.cli.utils.constants import STUB_CONNECTION from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.exceptions import InterruptInputException from aea.common import Address from aea.configurations.base import ConnectionConfig, PackageType, PublicId from aea.configurations.constants import ( CONNECTIONS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_PROTOCOL, PROTOCOLS, SIGNING_PROTOCOL, STATE_UPDATE_PROTOCOL, VENDOR, ) from aea.configurations.loader import ConfigLoader from aea.connections.base import Connection from aea.crypto.wallet import CryptoStore from aea.helpers.io import open_file from aea.identity.base import Identity from aea.mail.base import Envelope, Message from aea.multiplexer import InBox, Multiplexer, OutBox from aea.protocols.base import Protocol from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import Dialogues if TYPE_CHECKING: # pragma: nocover from packages.fetchai.connections.stub.connection import ( # noqa: F401 DEFAULT_INPUT_FILE_NAME, DEFAULT_OUTPUT_FILE_NAME, StubConnection, ) from packages.fetchai.protocols.default.dialogues import ( # noqa: F401 DefaultDialogue, DefaultDialogues, ) from packages.fetchai.protocols.default.message import DefaultMessage # noqa: F401 @click.command() @click.pass_context @check_aea_project def interact( click_context: click.core.Context, # pylint: disable=unused-argument ) -> None: """Interact with the running agent via the stub connection.""" click.echo("Starting AEA interaction channel...") _run_interaction_channel() def _load_packages(agent_identity: Identity) -> None: """Load packages in the current interpreter.""" default_protocol_id = PublicId.from_str(DEFAULT_PROTOCOL) Protocol.from_dir( os.path.join( VENDOR, default_protocol_id.author, PROTOCOLS, default_protocol_id.name ) ) signing_protocol_id = PublicId.from_str(SIGNING_PROTOCOL) Protocol.from_dir( os.path.join( VENDOR, signing_protocol_id.author, PROTOCOLS, signing_protocol_id.name ) ) state_update_protocol_id = PublicId.from_str(STATE_UPDATE_PROTOCOL) Protocol.from_dir( os.path.join( VENDOR, state_update_protocol_id.author, PROTOCOLS, state_update_protocol_id.name, ) ) stub_connection_id = PublicId.from_str(STUB_CONNECTION) Connection.from_dir( os.path.join( VENDOR, stub_connection_id.author, CONNECTIONS, stub_connection_id.name, ), agent_identity, CryptoStore(), os.getcwd(), ) def _run_interaction_channel() -> None: loader = ConfigLoader.from_configuration_type(PackageType.AGENT) agent_configuration = loader.load(open_file(DEFAULT_AEA_CONFIG_FILE)) agent_name = agent_configuration.name identity_stub = Identity(agent_name + "_interact", "interact", "interact") _load_packages(identity_stub) # load agent configuration file from packages.fetchai.connections.stub.connection import ( # noqa: F811 # pylint: disable=import-outside-toplevel DEFAULT_INPUT_FILE_NAME, DEFAULT_OUTPUT_FILE_NAME, StubConnection, ) from packages.fetchai.protocols.default.dialogues import ( # noqa: F811 # pylint: disable=import-outside-toplevel DefaultDialogue, DefaultDialogues, ) from packages.fetchai.protocols.default.message import ( # noqa: F811 # pylint: disable=import-outside-toplevel DefaultMessage, ) # load stub connection configuration = ConnectionConfig( input_file=DEFAULT_OUTPUT_FILE_NAME, output_file=DEFAULT_INPUT_FILE_NAME, connection_id=StubConnection.connection_id, ) stub_connection = StubConnection( configuration=configuration, data_dir=os.getcwd(), identity=identity_stub ) multiplexer = Multiplexer([stub_connection]) inbox = InBox(multiplexer) outbox = OutBox(multiplexer) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT dialogues = DefaultDialogues(identity_stub.name, role_from_first_message) try: multiplexer.connect() while True: # pragma: no cover _process_envelopes(agent_name, inbox, outbox, dialogues, DefaultMessage) except KeyboardInterrupt: click.echo("Interaction interrupted!") except BaseException as e: # pylint: disable=broad-except # pragma: no cover click.echo(e) finally: multiplexer.disconnect() def _process_envelopes( agent_name: str, inbox: InBox, outbox: OutBox, dialogues: Dialogues, message_class: Type[Message], ) -> None: """ Process envelopes. :param agent_name: name of an agent. :param inbox: an inbox object. :param outbox: an outbox object. :param dialogues: the dialogues object. :param message_class: the message class. """ envelope = _try_construct_envelope(agent_name, dialogues, message_class) if envelope is None: _check_for_incoming_envelope(inbox, message_class) else: outbox.put(envelope) click.echo(_construct_message("sending", envelope, message_class)) def _check_for_incoming_envelope(inbox: InBox, message_class: Type[Message]) -> None: if not inbox.empty(): envelope = inbox.get_nowait() if envelope is None: raise ValueError("Could not recover envelope from inbox.") click.echo(_construct_message("received", envelope, message_class)) else: click.echo("Received no new envelope!") def _construct_message( action_name: str, envelope: Envelope, message_class: Type[Message] ) -> str: action_name = action_name.title() msg = ( message_class.serializer.decode(envelope.message) if isinstance(envelope.message, bytes) else envelope.message ) message = ( "\n{} envelope:\nto: " "{}\nsender: {}\nprotocol_specification_id: {}\nmessage: {}\n".format( action_name, envelope.to, envelope.sender, envelope.protocol_specification_id, msg, ) ) return message def _try_construct_envelope( agent_name: str, dialogues: Dialogues, message_class: Type[Message] ) -> Optional[Envelope]: """Try construct an envelope from user input.""" envelope = None # type: Optional[Envelope] try: performative_str = "bytes" performative = message_class.Performative(performative_str) click.echo( f"Provide message of protocol '{str(message_class.protocol_id)}' for performative {performative_str}:" ) message_escaped = input() # nosec message_escaped = message_escaped.strip() if message_escaped == "": raise InterruptInputException message_decoded = codecs.decode( message_escaped.encode("utf-8"), "unicode-escape" ) message = message_decoded.encode("utf-8") # type: Union[str, bytes] msg, _ = dialogues.create( counterparty=agent_name, performative=performative, content=message, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) except InterruptInputException: click.echo("Interrupting input, checking inbox ...") except KeyboardInterrupt: raise except BaseException as e: # pylint: disable=broad-except # pragma: no cover click.echo(e) return envelope ================================================ FILE: aea/cli/issue_certificates.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea issue_certificates' subcommand.""" from typing import Dict, List, Optional, cast import click from click import ClickException from aea.cli.utils.click_utils import password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.loggers import logger from aea.cli.utils.package_utils import get_dotted_package_path_unified from aea.configurations.base import AgentConfig, PublicId from aea.configurations.constants import CONNECTION from aea.configurations.manager import AgentConfigManager, VariableDoesNotExist from aea.crypto.helpers import make_certificate from aea.crypto.registries import crypto_registry from aea.exceptions import enforce from aea.helpers.base import CertRequest, prepend_if_not_absolute @click.command() @password_option() @click.pass_context @check_aea_project def issue_certificates(click_context: click.Context, password: Optional[str]) -> None: """Issue certificates for connections that require them.""" ctx = cast(Context, click_context.obj) agent_config_manager = AgentConfigManager.load(ctx.cwd) issue_certificates_(ctx.cwd, agent_config_manager, password=password) def issue_certificates_( project_directory: str, agent_config_manager: AgentConfigManager, path_prefix: Optional[str] = None, password: Optional[str] = None, ) -> None: """ Issue certificates for connections that require them. :param project_directory: the directory of the project. :param agent_config_manager: the agent configuration manager. :param path_prefix: the path prefix for "save_path". Defaults to project directory. :param password: the password to encrypt/decrypt the private key. """ path_prefix = path_prefix or project_directory for connection_id in agent_config_manager.agent_config.connections: cert_requests = _get_cert_requests( project_directory, agent_config_manager, connection_id ) _process_connection( path_prefix, agent_config_manager, cert_requests, connection_id, password ) click.echo("All certificates have been issued.") def _get_cert_requests( project_directory: str, manager: AgentConfigManager, connection_id: PublicId ) -> List[CertRequest]: """ Get certificate requests, taking the overrides into account. :param project_directory: aea project directory. :param manager: AgentConfigManager :param connection_id: the connection id. :return: the list of cert requests. """ path = get_dotted_package_path_unified( project_directory, manager.agent_config, CONNECTION, connection_id ) path_to_cert_requests = f"{path}.cert_requests" try: cert_requests = manager.get_variable(path_to_cert_requests) except VariableDoesNotExist: return [] cert_requests = cast(List[Dict], cert_requests) return [ CertRequest.from_json(cert_request_json) for cert_request_json in cert_requests ] def _process_certificate( path_prefix: str, agent_config: AgentConfig, cert_request: CertRequest, connection_id: PublicId, password: Optional[str] = None, ) -> None: """Process a single certificate request.""" ledger_id = cert_request.ledger_id if cert_request.key_identifier is not None: key_identifier = cert_request.key_identifier connection_private_key_path = agent_config.connection_private_key_paths.read( key_identifier ) if connection_private_key_path is None: raise ClickException( f"Cannot find connection private key with id '{key_identifier}'. Connection '{connection_id}' requires this. Please use `aea generate-key {key_identifier} connection_{key_identifier}_private_key.txt` and `aea add-key {key_identifier} connection_{key_identifier}_private_key.txt --connection` to add a connection private key with id '{key_identifier}'." ) new_connection_private_key_path = prepend_if_not_absolute( connection_private_key_path, path_prefix ) connection_crypto = crypto_registry.make( key_identifier, private_key_path=new_connection_private_key_path, password=password, ) public_key = connection_crypto.public_key else: public_key = cast(str, cert_request.public_key) enforce( public_key is not None, "Internal error - one of key_identifier or public_key must be not None.", ) crypto_private_key_path = agent_config.private_key_paths.read(ledger_id) if crypto_private_key_path is None: raise ClickException( f"Cannot find private key with id '{ledger_id}'. Please use `aea generate-key {key_identifier}` and `aea add-key {key_identifier}` to add a private key with id '{key_identifier}'." ) message = cert_request.get_message(public_key) output_path = cert_request.get_absolute_save_path(path_prefix) absolute_crypto_private_key_path = prepend_if_not_absolute( crypto_private_key_path, path_prefix ) cert = make_certificate( ledger_id, str(absolute_crypto_private_key_path), message, str(output_path), password=password, ) click.echo(f"Generated signature: '{cert}'") click.echo( f"Dumped certificate '{cert_request.identifier}' in '{output_path}' for connection {connection_id}." ) def _process_connection( path_prefix: str, agent_config_manager: AgentConfigManager, cert_requests: List[CertRequest], connection_id: PublicId, password: Optional[str] = None, ) -> None: """Process a single connection.""" if len(cert_requests) == 0: logger.debug("No certificates to process.") return logger.debug(f"Processing connection '{connection_id}'...") for cert_request in cert_requests: click.echo( f"Issuing certificate '{cert_request.identifier}' for connection {connection_id}..." ) _process_certificate( path_prefix, agent_config_manager.agent_config, cert_request, connection_id, password, ) ================================================ FILE: aea/cli/launch.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea launch' subcommand.""" import os import sys from collections import OrderedDict from pathlib import Path from typing import List, Optional, cast import click from aea.cli.utils.click_utils import AgentDirectory, password_option from aea.cli.utils.context import Context from aea.cli.utils.loggers import logger from aea.helpers.multiple_executor import ExecutorExceptionPolicies from aea.launcher import AEALauncher @click.command() @click.argument("agents", nargs=-1, type=AgentDirectory()) @password_option() @click.option("--multithreaded", is_flag=True) @click.pass_context def launch( click_context: click.Context, agents: List[str], password: Optional[str], multithreaded: bool, ) -> None: """Launch many agents at the same time.""" _launch_agents(click_context, agents, multithreaded, password) def _launch_agents( click_context: click.core.Context, agents: List[str], multithreaded: bool, password: Optional[str] = None, ) -> None: """ Run multiple agents. :param click_context: click context object. :param agents: agents names. :param multithreaded: bool flag to run as multithreads. :param password: the password to encrypt/decrypt the private key. """ agents_directories = list(map(Path, list(OrderedDict.fromkeys(agents)))) mode = "threaded" if multithreaded else "multiprocess" ctx = cast(Context, click_context.obj) launcher = AEALauncher( agent_dirs=agents_directories, mode=mode, fail_policy=ExecutorExceptionPolicies.log_only, log_level=ctx.verbosity, password=password, ) try: """ run in threaded mode and wait for thread finished cause issue with python 3.6/3.7 on windows probably keyboard interrupt exception gets lost in executor pool or in asyncio module """ launcher.start(threaded=True) launcher.try_join_thread() except KeyboardInterrupt: logger.info("Keyboard interrupt detected.") finally: timeout: Optional[float] = None if os.name == "nt": # Windows bug: https://bugs.python.org/issue21822 timeout = 0 # pragma: nocover launcher.stop(timeout) for agent in launcher.failed: logger.info(f"Agent {agent} terminated with exit code 1") for agent in launcher.not_failed: logger.info(f"Agent {agent} terminated with exit code 0") logger.debug(f"Exit cli. code: {launcher.num_failed}") sys.exit(1 if launcher.num_failed > 0 else 0) ================================================ FILE: aea/cli/list.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea list' subcommand.""" from collections.abc import Set from pathlib import Path from typing import Dict, List import click from aea.cli.utils.constants import ITEM_TYPES from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, pass_ctx from aea.cli.utils.formatting import format_items, retrieve_details, sort_items from aea.configurations.base import ( PackageType, PublicId, _get_default_configuration_file_name_from_type, ) from aea.configurations.constants import CONNECTION, CONTRACT, PROTOCOL, SKILL, VENDOR from aea.configurations.loader import ConfigLoader @click.group(name="list") @click.pass_context @check_aea_project def list_command( click_context: click.Context, # pylint: disable=unused-argument ) -> None: """List the installed packages of the agent.""" @list_command.command(name="all") @pass_ctx def all_command(ctx: Context) -> None: """List all the installed packages.""" for item_type in ITEM_TYPES: details = list_agent_items(ctx, item_type) if not details: continue output = "{}:\n{}".format( item_type.title() + "s", format_items(sort_items(details)) ) click.echo(output) @list_command.command() @pass_ctx def connections(ctx: Context) -> None: """List all the installed connections.""" result = list_agent_items(ctx, CONNECTION) click.echo(format_items(sort_items(result))) @list_command.command() @pass_ctx def contracts(ctx: Context) -> None: """List all the installed protocols.""" result = list_agent_items(ctx, CONTRACT) click.echo(format_items(sort_items(result))) @list_command.command() @pass_ctx def protocols(ctx: Context) -> None: """List all the installed protocols.""" result = list_agent_items(ctx, PROTOCOL) click.echo(format_items(sort_items(result))) @list_command.command() @pass_ctx def skills(ctx: Context) -> None: """List all the installed skills.""" result = list_agent_items(ctx, SKILL) click.echo(format_items(sorted(result, key=lambda k: k["name"]))) def list_agent_items(ctx: Context, item_type: str) -> List[Dict]: """Return a list of item details, given the item type.""" result = [] item_type_plural = item_type + "s" public_ids = getattr(ctx.agent_config, item_type_plural) # type: Set[PublicId] default_file_name = _get_default_configuration_file_name_from_type(item_type) for public_id in public_ids: # first, try to retrieve the item from the vendor directory. configuration_filepath = Path( ctx.cwd, VENDOR, public_id.author, item_type_plural, public_id.name, default_file_name, ) # otherwise, if it does not exist, retrieve the item from the agent custom packages if not configuration_filepath.exists(): configuration_filepath = Path( ctx.cwd, item_type_plural, public_id.name, default_file_name ) configuration_loader = ConfigLoader.from_configuration_type( PackageType(item_type) ) details = retrieve_details( public_id.name, configuration_loader, str(configuration_filepath) ) result.append(details) return result ================================================ FILE: aea/cli/local_registry_sync.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea sync-local-registry' subcommand.""" import os import shutil from pathlib import Path from tempfile import TemporaryDirectory from typing import Generator, Tuple, Union, cast import click from aea.cli.fingerprint import determine_package_type_for_directory from aea.cli.registry.add import fetch_package from aea.cli.registry.utils import get_package_meta from aea.cli.utils.context import Context from aea.cli.utils.decorators import pass_ctx from aea.cli.utils.loggers import logger from aea.configurations.data_types import PackageId, PackageType, PublicId from aea.configurations.loader import load_package_configuration PACKAGES_DIRS = [i.to_plural() for i in PackageType] @click.command() @pass_ctx def local_registry_sync(ctx: Context) -> None: """Upgrade the local package registry.""" skip_consistency_check = ctx.config["skip_consistency_check"] do_local_registry_update(ctx.cwd, skip_consistency_check) def do_local_registry_update( base_dir: Union[str, Path], skip_consistency_check: bool = True ) -> None: """ Perform local registry update. :param base_dir: root directory of the local registry. :param skip_consistency_check: whether or not to skip consistency checks. """ for package_id, package_dir in enlist_packages(base_dir, skip_consistency_check): current_public_id = package_id.public_id latest_public_id = get_package_latest_public_id(package_id) if not ( # pylint: disable=superfluous-parens current_public_id < latest_public_id ): click.echo(f"{package_id} is the latest version.") continue click.echo(f"Updating {current_public_id} to {latest_public_id}.") replace_package(str(package_id.package_type), latest_public_id, package_dir) def replace_package( package_type: str, public_id: PublicId, package_dir: Union[Path, str] ) -> None: """ Download, extract and replace exists package. :param package_type: str. :param public_id: package public id to download :param: package_dir: target package dir """ with TemporaryDirectory() as tmp_dir: new_package_dir = os.path.join(tmp_dir, public_id.name) os.mkdir(new_package_dir) fetch_package( package_type, public_id=public_id, cwd=tmp_dir, dest=new_package_dir ) shutil.rmtree(package_dir) shutil.move(new_package_dir, package_dir) def get_package_latest_public_id(package_id: PackageId) -> PublicId: """ Get package latest package id from the remote repo. :param package_id: id of the package to check :return: package id of the latest package in remote repo """ package_meta = get_package_meta( str(package_id.package_type), package_id.public_id.to_latest() ) return PublicId.from_str(cast(str, package_meta["public_id"])) def enlist_packages( base_dir: Union[Path, str], skip_consistency_check: bool = True ) -> Generator[Tuple[PackageId, Union[Path, str]], None, None]: """ Generate list of the packages in local repo directory. :param base_dir: path or str of the local repo. :param skip_consistency_check: whether or not to skip consistency checks. :yield: a Tuple of package_id and package directory. """ for author in os.listdir(base_dir): author_dir = os.path.join(base_dir, author) if not os.path.isdir(author_dir): continue # pragma: nocover for package_type_plural in os.listdir(author_dir): if package_type_plural not in PACKAGES_DIRS: continue # pragma: nocover package_type_dir = os.path.join(author_dir, package_type_plural) if not os.path.isdir(package_type_dir): continue # pragma: nocover for package_name in os.listdir(package_type_dir): package_dir = os.path.join(package_type_dir, package_name) if not os.path.isdir(package_dir): continue # pragma: nocover try: package_type = determine_package_type_for_directory( Path(package_dir) ) if package_type.to_plural() != package_type_plural: # incorrect package placing continue # pragma: nocover config = load_package_configuration( package_type, Path(package_dir), skip_consistency_check=skip_consistency_check, ) yield (config.package_id, package_dir) except ValueError as e: # pragma: nocover logger.error( # pragma: nocover f"Error with package_dir={package_dir}: {e}" ) ================================================ FILE: aea/cli/login.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea login' subcommand.""" import click from aea.cli.registry.login import registry_login from aea.cli.registry.settings import AUTH_TOKEN_KEY from aea.cli.utils.config import update_cli_config @click.command(name="login", help="Login to the registry account.") @click.argument("username", type=str, required=True) @click.option("--password", type=str, required=True, prompt=True, hide_input=True) def login(username: str, password: str) -> None: """Login to the registry account.""" do_login(username, password) def do_login(username: str, password: str) -> None: """ Login to the registry account and save auth token in config. :param username: str username. :param password: str password. """ click.echo("Signing in as {}...".format(username)) token = registry_login(username, password) update_cli_config({AUTH_TOKEN_KEY: token}) click.echo("Successfully signed in: {}.".format(username)) ================================================ FILE: aea/cli/logout.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea logout' subcommand.""" import click from aea.cli.registry.logout import registry_logout from aea.cli.registry.settings import AUTH_TOKEN_KEY from aea.cli.utils.config import update_cli_config @click.command(name="logout", help="Logout from the registry account.") def logout() -> None: """Logout from the registry account.""" click.echo("Logging out...") do_logout() click.echo("Successfully logged out.") def do_logout() -> None: """Logout from Registry account.""" registry_logout() update_cli_config({AUTH_TOKEN_KEY: None}) ================================================ FILE: aea/cli/plugin.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Plug-in system for adding new CLI commands.""" import os import sys import traceback from typing import Callable, Iterable, List import click import pkg_resources def with_plugins(plugins: Iterable[pkg_resources.EntryPoint]) -> Callable: """ A decorator to register external CLI commands to an instance of `click.Group()`. :param plugins: An iterable producing one `pkg_resources.EntryPoint()` per iteration. :return: a click.Group instance. """ def decorator(group: click.Group) -> click.Group: if not isinstance(group, click.Group): raise TypeError( "Plugins can only be attached to an instance of click.Group()" ) for entry_point in plugins or (): try: group.add_command(entry_point.load()) except Exception: # pylint: disable=broad-except # Catch this so a busted plugin doesn't take down the CLI. # Handled by registering a dummy command that does nothing # other than explain the error. group.add_command(BrokenCommand(entry_point.name)) return group return decorator class BrokenCommand(click.Command): """ Helper click.Command in case a broken plug-in is loaded. Rather than completely crash the CLI when a broken plugin is loaded, this class provides a modified help message informing the user that the plugin is broken and they should contact the owner. If the user executes the plugin or specifies `--help` a traceback is reported showing the exception the plugin loader encountered. """ def __init__(self, name: str) -> None: """ Define the special help messages after instantiating a `click.Command()`. :param name: the name of the command. """ click.Command.__init__(self, name) util_name = os.path.basename(sys.argv and sys.argv[0] or __file__) self.help = ( "\nWarning: entry point could not be loaded. Contact " "its author for help.\n\n\b\n" + traceback.format_exc() ) self.short_help = " Warning: could not load plugin. See `%s %s --help`." % ( util_name, self.name, ) def invoke(self, ctx: click.Context) -> None: """ Print the traceback instead of doing nothing. :param ctx: the click.Context object. """ click.echo(self.help, color=ctx.color) ctx.exit(1) def parse_args(self, ctx: click.Context, args: List) -> List: """ Parse arguments. :param ctx: the click.Context object. :param args: the raw arguments. :return: the arguments, parsed. """ return args ================================================ FILE: aea/cli/publish.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea publish' subcommand.""" import os from abc import ABC, abstractmethod from contextlib import suppress from pathlib import Path from shutil import copyfile from typing import cast import click from aea.cli.push import _save_item_locally as _push_item_locally from aea.cli.registry.publish import publish_agent from aea.cli.registry.push import push_item as _push_item_remote from aea.cli.registry.utils import get_package_meta from aea.cli.utils.click_utils import registry_flag from aea.cli.utils.config import validate_item_config from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.exceptions import AEAConfigException from aea.cli.utils.package_utils import ( try_get_item_source_path, try_get_item_target_path, ) from aea.configurations.base import AgentConfig, CRUDCollection, PublicId from aea.configurations.constants import ( AGENT, AGENTS, CONNECTIONS, CONTRACTS, DEFAULT_AEA_CONFIG_FILE, ITEM_TYPE_PLURAL_TO_TYPE, PROTOCOLS, SKILLS, ) PUSH_ITEMS_FLAG = "--push-missing" @click.command(name="publish") @registry_flag( help_local="For publishing agent to local folder.", help_remote="For publishing agent to remote registry.", ) @click.option( "--push-missing", is_flag=True, help="Push missing components to registry." ) @click.pass_context @check_aea_project def publish( click_context: click.Context, local: bool, remote: bool, push_missing: bool ) -> None: # pylint: disable=unused-argument """Publish the agent to the registry.""" ctx = cast(Context, click_context.obj) _validate_pkp(ctx.agent_config.private_key_paths) _validate_config(ctx) if remote: _publish_agent_remote(ctx, push_missing=push_missing) else: _save_agent_locally( ctx, is_mixed=not local and not remote, push_missing=push_missing ) def _validate_config(ctx: Context) -> None: """ Validate agent config. :param ctx: Context object. :raises ClickException: if validation is failed. """ try: validate_item_config(AGENT, Path(ctx.cwd)) except AEAConfigException as e: # pragma: no cover raise click.ClickException("Failed to validate agent config. {}".format(str(e))) def _validate_pkp(private_key_paths: CRUDCollection) -> None: """ Prevent to publish agents with non-empty private_key_paths. :param private_key_paths: private_key_paths from agent config. :raises ClickException: if private_key_paths is not empty. """ if private_key_paths.read_all() != []: raise click.ClickException( "You are not allowed to publish agents with non-empty private_key_paths. Use the `aea remove-key` command to remove key paths from `private_key_paths: {}` in `aea-config.yaml`." ) class BaseRegistry(ABC): """Base registry class.""" @abstractmethod def check_item_present(self, item_type_plural: str, public_id: PublicId) -> None: """ Check item present in registry. Raise ClickException if not found. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. :return: None """ @abstractmethod def push_item(self, item_type_plural: str, public_id: PublicId) -> None: """ Push item to registry. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. :return: None """ def check_item_present_and_push( self, item_type_plural: str, public_id: PublicId ) -> None: """ Check item present in registry and push if needed. Raise ClickException if not found. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. :return: None """ with suppress(click.ClickException): return self.check_item_present(item_type_plural, public_id) try: self.push_item(item_type_plural, public_id) except Exception as e: raise click.ClickException( f"Failed to push missing item: {item_type_plural} {public_id}: {e}" ) from e try: self.check_item_present(item_type_plural, public_id) except Exception as e: raise click.ClickException( f"Failed to find item after push: {item_type_plural} {public_id}: {e}" ) from e class LocalRegistry(BaseRegistry): """Local directory registry.""" def __init__(self, ctx: Context): """Init registry.""" self.ctx = ctx try: self.registry_path = ctx.registry_path except ValueError as e: # pragma: nocover raise click.ClickException(str(e)) def check_item_present(self, item_type_plural: str, public_id: PublicId) -> None: """ Check item present in registry. Raise ClickException if not found. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. """ try: try_get_item_source_path( self.registry_path, public_id.author, item_type_plural, public_id.name ) except click.ClickException as e: raise click.ClickException( f"Dependency is missing. {str(e)}\nPlease push it first and then retry or use {PUSH_ITEMS_FLAG} flag to push automatically." ) def push_item(self, item_type_plural: str, public_id: PublicId) -> None: """ Push item to registry. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. """ item_type = ITEM_TYPE_PLURAL_TO_TYPE[item_type_plural] _push_item_locally(self.ctx, item_type, public_id) class MixedRegistry(LocalRegistry): """Mixed remote and local component registry.""" def check_item_present(self, item_type_plural: str, public_id: PublicId) -> None: """ Check item present in registry. Raise ClickException if not found. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. """ item_type = ITEM_TYPE_PLURAL_TO_TYPE[item_type_plural] try: LocalRegistry.check_item_present(self, item_type_plural, public_id) except click.ClickException: click.echo( f"Can not find dependency locally: {item_type} {public_id}. Trying remote registry..." ) try: RemoteRegistry(self.ctx).check_item_present(item_type_plural, public_id) except click.ClickException: raise click.ClickException( f"Can not find dependency locally or remotely: {item_type} {public_id}. Try to add flag `{PUSH_ITEMS_FLAG}` to push dependency package to the registry." ) class RemoteRegistry(BaseRegistry): """Remote components registry.""" def __init__(self, ctx: Context) -> None: """Init registry.""" self.ctx = ctx def check_item_present(self, item_type_plural: str, public_id: PublicId) -> None: """ Check item present in registry. Raise ClickException if not found. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. """ item_type = ITEM_TYPE_PLURAL_TO_TYPE[item_type_plural] try: get_package_meta(item_type, public_id) except click.ClickException as e: raise click.ClickException( f"Package not found in remote registry: {str(e)}. You can try to add {PUSH_ITEMS_FLAG} flag." ) def push_item(self, item_type_plural: str, public_id: PublicId) -> None: """ Push item to registry. :param item_type_plural: str, item type. :param public_id: PublicId of the item to check. """ item_type = ITEM_TYPE_PLURAL_TO_TYPE[item_type_plural] _push_item_remote(self.ctx, item_type, public_id) def _check_dependencies_in_registry( registry: BaseRegistry, agent_config: AgentConfig, push_missing: bool ) -> None: """Check all agent dependencies present in registry.""" for item_type_plural in (PROTOCOLS, CONTRACTS, CONNECTIONS, SKILLS): dependencies = getattr(agent_config, item_type_plural) for public_id in dependencies: if push_missing: registry.check_item_present_and_push(item_type_plural, public_id) else: registry.check_item_present(item_type_plural, public_id) def _save_agent_locally( ctx: Context, is_mixed: bool = False, push_missing: bool = False ) -> None: """ Save agent to local packages. :param ctx: the context :param is_mixed: whether or not to fetch in mixed mode :param push_missing: bool. flag to push missing items """ try: registry_path = ctx.registry_path except ValueError as e: # pragma: nocover raise click.ClickException(str(e)) registry = MixedRegistry(ctx) if is_mixed else LocalRegistry(ctx) _check_dependencies_in_registry(registry, ctx.agent_config, push_missing) item_type_plural = AGENTS target_dir = try_get_item_target_path( registry_path, ctx.agent_config.author, item_type_plural, ctx.agent_config.name, ) if not os.path.exists(target_dir): os.makedirs(target_dir, exist_ok=True) source_path = os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE) target_path = os.path.join(target_dir, DEFAULT_AEA_CONFIG_FILE) copyfile(source_path, target_path) click.echo( f'Agent "{ctx.agent_config.name}" successfully saved in packages folder.' ) def _publish_agent_remote(ctx: Context, push_missing: bool) -> None: """ Push agent to remote registry. :param ctx: the context :param push_missing: bool. flag to push missing items """ registry = RemoteRegistry(ctx) _check_dependencies_in_registry(registry, ctx.agent_config, push_missing) publish_agent(ctx) ================================================ FILE: aea/cli/push.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea push' subcommand.""" import os from shutil import copytree from typing import cast import click from click.exceptions import ClickException from aea.cli.registry.push import check_package_public_id, push_item from aea.cli.utils.click_utils import PublicIdParameter from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, pass_ctx from aea.cli.utils.package_utils import ( try_get_item_source_path, try_get_item_target_path, ) from aea.configurations.base import PublicId from aea.configurations.constants import CONNECTION, CONTRACT, PROTOCOL, SKILL @click.group() @click.option("--local", is_flag=True, help="For pushing items to local folder.") @click.pass_context @check_aea_project def push(click_context: click.Context, local: bool) -> None: """Push a non-vendor package of the agent to the registry.""" ctx = cast(Context, click_context.obj) ctx.set_config("local", local) @push.command(name=CONNECTION) @click.argument("connection-id", type=PublicIdParameter(), required=True) @pass_ctx def connection(ctx: Context, connection_id: PublicId) -> None: """Push a connection to the registry or save it in local registry.""" if ctx.config.get("local"): _save_item_locally(ctx, CONNECTION, connection_id) else: push_item(ctx, CONNECTION, connection_id) @push.command(name=CONTRACT) @click.argument("contract-id", type=PublicIdParameter(), required=True) @pass_ctx def contract(ctx: Context, contract_id: PublicId) -> None: """Push a contract to the registry or save it in local registry.""" if ctx.config.get("local"): _save_item_locally(ctx, CONTRACT, contract_id) else: push_item(ctx, CONTRACT, contract_id) @push.command(name=PROTOCOL) @click.argument("protocol-id", type=PublicIdParameter(), required=True) @pass_ctx def protocol(ctx: Context, protocol_id: PublicId) -> None: """Push a protocol to the registry or save it in local registry.""" if ctx.config.get("local"): _save_item_locally(ctx, PROTOCOL, protocol_id) else: push_item(ctx, PROTOCOL, protocol_id) @push.command(name=SKILL) @click.argument("skill-id", type=PublicIdParameter(), required=True) @pass_ctx def skill(ctx: Context, skill_id: PublicId) -> None: """Push a skill to the registry or save it in local registry.""" if ctx.config.get("local"): _save_item_locally(ctx, SKILL, skill_id) else: push_item(ctx, SKILL, skill_id) def _save_item_locally(ctx: Context, item_type: str, item_id: PublicId) -> None: """ Save item to local packages. :param ctx: click context :param item_type: str type of item (connection/protocol/skill). :param item_id: the public id of the item. """ item_type_plural = item_type + "s" try: # try non vendor first source_path = try_get_item_source_path( ctx.cwd, None, item_type_plural, item_id.name ) except ClickException: # failed on user's packages # try vendors source_path = try_get_item_source_path( os.path.join(ctx.cwd, "vendor"), item_id.author, item_type_plural, item_id.name, ) check_package_public_id(source_path, item_type, item_id) try: registry_path = ctx.registry_path except ValueError as e: # pragma: nocover raise click.ClickException(str(e)) target_path = try_get_item_target_path( registry_path, item_id.author, item_type_plural, item_id.name, ) copytree(source_path, target_path) click.echo( f'{item_type.title()} "{item_id}" successfully saved in packages folder.' ) ================================================ FILE: aea/cli/register.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea login' subcommand.""" import click from aea.cli.registry.registration import register as register_new_account from aea.cli.registry.settings import AUTH_TOKEN_KEY from aea.cli.utils.config import update_cli_config from aea.cli.utils.package_utils import validate_author_name @click.command(name="register", help="Create a new registry account.") @click.option("--username", type=str, required=True, prompt=True) @click.option("--email", type=str, required=True, prompt=True) @click.option("--password", type=str, required=True, prompt=True, hide_input=True) @click.option( "--confirm_password", type=str, required=True, prompt=True, hide_input=True ) @click.option("--no-subscribe", is_flag=True, help="For developers subscription.") def register( username: str, email: str, password: str, confirm_password: str, no_subscribe: bool ) -> None: """Create a new registry account.""" do_register(username, email, password, confirm_password, no_subscribe) def do_register( username: str, email: str, password: str, password_confirmation: str, no_subscribe: bool, ) -> None: """ Register a new Registry account and save auth token. :param username: str username. :param email: str email. :param password: str password. :param password_confirmation: str password confirmation. :param no_subscribe: bool flag for developers subscription skip on register. """ username = validate_author_name(username) token = register_new_account(username, email, password, password_confirmation) update_cli_config({AUTH_TOKEN_KEY: token}) if not no_subscribe and click.confirm( "Do you want to subscribe for developer news?" ): click.echo( "Please visit `https://aea-registry.fetch.ai/mailing-list` " "to subscribe for developer news" ) click.echo("Successfully registered and logged in: {}".format(username)) ================================================ FILE: aea/cli/registry/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tools for operating Registry with CLI.""" ================================================ FILE: aea/cli/registry/add.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Registry utils used for CLI add command.""" import os from pathlib import Path from typing import cast from aea.cli.registry.utils import download_file, extract, get_package_meta from aea.cli.utils.loggers import logger from aea.configurations.base import PublicId def fetch_package(obj_type: str, public_id: PublicId, cwd: str, dest: str) -> Path: """ Fetch a package (connection/contract/protocol/skill) from Registry. :param obj_type: str type of object you want to fetch: 'connection', 'protocol', 'skill' :param public_id: str public ID of object. :param cwd: str path to current working directory. :param dest: destination where to save package. :return: package path """ logger.debug( "Fetching {obj_type} {public_id} from Registry...".format( public_id=public_id, obj_type=obj_type ) ) logger.debug( "Downloading {obj_type} {public_id}...".format( public_id=public_id, obj_type=obj_type ) ) package_meta = get_package_meta(obj_type, public_id) file_url = cast(str, package_meta["file"]) filepath = download_file(file_url, cwd) # next code line is needed because the items are stored in tarball packages as folders dest = os.path.split(dest)[0] logger.debug( "Extracting {obj_type} {public_id}...".format( public_id=public_id, obj_type=obj_type ) ) extract(filepath, dest) logger.debug( "Successfully fetched {obj_type} '{public_id}'.".format( public_id=public_id, obj_type=obj_type ) ) package_path = os.path.join(dest, public_id.name) return Path(package_path) ================================================ FILE: aea/cli/registry/fetch.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Methods for CLI fetch functionality.""" import os import shutil from pathlib import Path from typing import Optional, cast import click from click.exceptions import ClickException from aea.cli.add import add_item from aea.cli.registry.utils import download_file, extract, request_api from aea.cli.utils.config import try_to_load_agent_config from aea.cli.utils.context import Context from aea.cli.utils.decorators import clean_after from aea.common import JSONLike from aea.configurations.base import PublicId from aea.configurations.constants import ( CONNECTION, CONTRACT, DEFAULT_AEA_CONFIG_FILE, PROTOCOL, SKILL, ) from aea.helpers.io import open_file @clean_after def fetch_agent( ctx: Context, public_id: PublicId, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Fetch Agent from Registry. :param ctx: Context :param public_id: str public ID of desirable agent. :param alias: an optional alias. :param target_dir: the target directory to which the agent is fetched. """ author, name, version = public_id.author, public_id.name, public_id.version folder_name = target_dir or (name if alias is None else alias) aea_folder = os.path.join(ctx.cwd, folder_name) if os.path.exists(aea_folder): path = Path(aea_folder) raise ClickException( f'Item "{path.name}" already exists in target folder "{path.parent}".' ) ctx.clean_paths.append(aea_folder) api_path = f"/agents/{author}/{name}/{version}" resp = cast(JSONLike, request_api("GET", api_path)) file_url = cast(str, resp["file"]) filepath = download_file(file_url, ctx.cwd) extract(filepath, ctx.cwd) if alias or target_dir: shutil.move( os.path.join(ctx.cwd, name), aea_folder, ) ctx.cwd = aea_folder try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias with open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as fp: ctx.agent_loader.dump(ctx.agent_config, fp) click.echo("Fetching dependencies...") for item_type in (CONNECTION, CONTRACT, SKILL, PROTOCOL): item_type_plural = item_type + "s" # initialize fetched agent with empty folders for custom packages custom_items_folder = os.path.join(ctx.cwd, item_type_plural) os.makedirs(custom_items_folder, exist_ok=True) config = getattr(ctx.agent_config, item_type_plural) for item_public_id in config: try: add_item(ctx, item_type, item_public_id) except Exception as e: raise click.ClickException( f'Unable to fetch dependency for agent "{name}", aborting. {e}' ) click.echo("Dependencies successfully fetched.") click.echo(f"Agent {name} successfully fetched to {aea_folder}.") ================================================ FILE: aea/cli/registry/login.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Registry utils used for CLI login command.""" from typing import cast from aea.cli.registry.utils import request_api from aea.common import JSONLike def registry_login(username: str, password: str) -> str: """ Login into Registry account. :param username: str username. :param password: str password. :return: str token """ resp = cast( JSONLike, request_api( "POST", "/rest-auth/login/", data={"username": username, "password": password}, ), ) return cast(str, resp["key"]) def registry_reset_password(email: str) -> None: """ Request Registry to reset password. :param email: user email. """ request_api("POST", "/rest-auth/password/reset/", data={"email": email}) ================================================ FILE: aea/cli/registry/logout.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Registry utils used for CLI logout command.""" from aea.cli.registry.utils import request_api def registry_logout() -> None: """Logout from Registry account.""" request_api("POST", "/rest-auth/logout/") ================================================ FILE: aea/cli/registry/publish.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Methods for CLI publish functionality.""" import os import shutil import tarfile import tempfile from typing import cast import click from aea.cli.registry.utils import ( check_is_author_logged_in, clean_tarfiles, request_api, ) from aea.cli.utils.config import try_to_load_agent_config from aea.cli.utils.context import Context from aea.cli.utils.generic import is_readme_present from aea.cli.utils.loggers import logger from aea.common import JSONLike from aea.configurations.constants import ( CONNECTIONS, CONTRACTS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_README_FILE, PROTOCOLS, SKILLS, ) def _compress(output_filename: str, *filepaths: str) -> None: """Compare the output file.""" with tarfile.open(output_filename, "w:gz") as f: for filepath in filepaths: f.add(filepath, arcname=os.path.basename(filepath)) @clean_tarfiles def publish_agent(ctx: Context) -> None: """Publish an agent.""" try_to_load_agent_config(ctx) check_is_author_logged_in(ctx.agent_config.author) name = ctx.agent_config.agent_name config_file_source_path = os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE) readme_source_path = os.path.join(ctx.cwd, DEFAULT_README_FILE) output_tar = os.path.join(ctx.cwd, "{}.tar.gz".format(name)) with tempfile.TemporaryDirectory() as temp_dir: package_dir = os.path.join(temp_dir, name) os.makedirs(package_dir) config_file_target_path = os.path.join(package_dir, DEFAULT_AEA_CONFIG_FILE) shutil.copy(config_file_source_path, config_file_target_path) if is_readme_present(readme_source_path): readme_file_target_path = os.path.join(package_dir, DEFAULT_README_FILE) shutil.copy(readme_source_path, readme_file_target_path) _compress(output_tar, package_dir) data = { "name": name, "description": ctx.agent_config.description, "version": ctx.agent_config.version, CONNECTIONS: ctx.agent_config.connections, CONTRACTS: ctx.agent_config.contracts, PROTOCOLS: ctx.agent_config.protocols, SKILLS: ctx.agent_config.skills, } files = {} try: files["file"] = open(output_tar, "rb") # pylint: disable=consider-using-with if is_readme_present(readme_source_path): files["readme"] = open( # pylint: disable=consider-using-with readme_source_path, "rb" ) path = "/agents/create" logger.debug("Publishing agent {} to Registry ...".format(name)) resp = cast( JSONLike, request_api("POST", path, data=data, is_auth=True, files=files) ) finally: for fd in files.values(): fd.close() click.echo( "Successfully published agent {} to the Registry. Public ID: {}".format( name, resp["public_id"] ) ) ================================================ FILE: aea/cli/registry/push.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Methods for CLI push functionality.""" import os import shutil import tarfile from typing import List, Tuple, cast import click from aea.cli.registry.utils import ( check_is_author_logged_in, clean_tarfiles, list_missing_packages, request_api, ) from aea.cli.utils.context import Context from aea.cli.utils.generic import is_readme_present, load_yaml from aea.cli.utils.loggers import logger from aea.common import JSONLike from aea.configurations.base import PublicId from aea.configurations.constants import ( CONNECTIONS, CONTRACTS, DEFAULT_README_FILE, ITEM_TYPE_PLURAL_TO_TYPE, PROTOCOLS, SKILLS, ) def _remove_pycache(source_dir: str) -> None: pycache_path = os.path.join(source_dir, "__pycache__") if os.path.exists(pycache_path): shutil.rmtree(pycache_path) def _compress_dir(output_filename: str, source_dir: str) -> None: _remove_pycache(source_dir) with tarfile.open(output_filename, "w:gz") as f: f.add(source_dir, arcname=os.path.basename(source_dir)) def load_component_public_id(source_path: str, item_type: str) -> PublicId: """Get component version from source path.""" config = load_yaml(os.path.join(source_path, item_type + ".yaml")) item_author = config.get("author", "") item_name = config.get("name", "") item_version = config.get("version", "") return PublicId(item_author, item_name, item_version) def check_package_public_id( source_path: str, item_type: str, item_id: PublicId ) -> PublicId: """ Check component version is corresponds to specified version. :param source_path: the source path :param item_type: str type of item (connection/protocol/skill). :param item_id: item public id. :return: actual package public id """ # we load only based on item_name, hence also check item_version and item_author match. actual_item_id = load_component_public_id(source_path, item_type) if not actual_item_id.same_prefix(item_id) or ( not item_id.package_version.is_latest and item_id.version != actual_item_id.version ): raise click.ClickException( "Version, name or author does not match. Expected '{}', found '{}'".format( item_id, actual_item_id.author + "/" + actual_item_id.name + ":" + actual_item_id.version, ) ) return actual_item_id @clean_tarfiles def push_item(ctx: Context, item_type: str, item_id: PublicId) -> None: """ Push item to the Registry. :param ctx: click context :param item_type: str type of item (connection/protocol/skill). :param item_id: item public id. """ item_type_plural = item_type + "s" items_folder = os.path.join(ctx.cwd, item_type_plural) item_path = os.path.join(items_folder, item_id.name) if not os.path.exists(item_path): raise click.ClickException( '{} "{}" not found in {}. Make sure you run push command ' "from a correct folder.".format( item_type.title(), item_id.name, items_folder ) ) check_package_public_id(item_path, item_type, item_id) item_config_filepath = os.path.join(item_path, "{}.yaml".format(item_type)) logger.debug("Reading {} {} config ...".format(item_id.name, item_type)) item_config = load_yaml(item_config_filepath) check_is_author_logged_in(item_config["author"]) logger.debug( "Searching for {} {} in {} ...".format(item_id.name, item_type, items_folder) ) output_filename = "{}.tar.gz".format(item_id.name) logger.debug( "Compressing {} {} to {} ...".format(item_id.name, item_type, output_filename) ) _compress_dir(output_filename, item_path) output_filepath = os.path.join(ctx.cwd, output_filename) data = { "name": item_id.name, "description": item_config["description"], "version": item_config["version"], } # dependencies dependencies: List[Tuple[str, PublicId]] = [] for key in [CONNECTIONS, CONTRACTS, PROTOCOLS, SKILLS]: deps_list = item_config.get(key, []) if deps_list: data.update({key: deps_list}) for dep in deps_list: dependencies.append((ITEM_TYPE_PLURAL_TO_TYPE[key], PublicId.from_str(dep))) missing_dependencies = list_missing_packages(dependencies) if missing_dependencies: for package_type, package_id in missing_dependencies: click.echo(f"Error: Cannot find {package_type} {package_id} in registry!") raise click.ClickException("Found missing dependencies! Push canceled!") try: files = { "file": open(output_filepath, "rb") # pylint: disable=consider-using-with } readme_path = os.path.join(item_path, DEFAULT_README_FILE) if is_readme_present(readme_path): files["readme"] = open( # pylint: disable=consider-using-with readme_path, "rb" ) path = "/{}/create".format(item_type_plural) logger.debug("Pushing {} {} to Registry ...".format(item_id.name, item_type)) resp = cast( JSONLike, request_api("POST", path, data=data, is_auth=True, files=files) ) click.echo( "Successfully pushed {} {} to the Registry. Public ID: {}".format( item_type, item_id.name, resp["public_id"] ) ) finally: for fd in files.values(): fd.close() ================================================ FILE: aea/cli/registry/registration.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with methods for new user registration.""" from typing import Dict, List, cast from click import ClickException from aea.cli.registry.utils import request_api def register( username: str, email: str, password: str, password_confirmation: str ) -> str: """ Register new Registry account and automatically login if successful. :param username: str username. :param email: str email. :param password: str password. :param password_confirmation: str password confirmation. :return: str auth token. """ data = { "username": username, "email": email, "password1": password, "password2": password_confirmation, } resp_json, status_code = request_api( "POST", "/rest-auth/registration/", data=data, handle_400=False, return_code=True, ) resp_json = cast(Dict, resp_json) if status_code == 400: errors: List[str] = [] for key in ("username", "email", "password1", "password2"): param_errors = cast(str, resp_json.get(key)) if param_errors: errors.extend(param_errors) raise ClickException( "Errors occured during registration.\n" + "\n".join(errors) ) return cast(str, resp_json["key"]) ================================================ FILE: aea/cli/registry/settings.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Settings for operating Registry with CLI.""" REGISTRY_API_URL = "https://agents-registry.prod.fetch-ai.com/api/v1" # we ignore issue B105 because this is not an hard-coded authentication token, # but the name of the field in the configuration file. AUTH_TOKEN_KEY = "auth_token" # nosec ================================================ FILE: aea/cli/registry/utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Utils used for operating Registry with CLI.""" import os import tarfile from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast import click from aea.cli.registry.settings import AUTH_TOKEN_KEY, REGISTRY_API_URL from aea.cli.utils.config import get_or_create_cli_config from aea.cli.utils.context import Context from aea.cli.utils.loggers import logger from aea.cli.utils.package_utils import find_item_locally from aea.common import JSONLike from aea.configurations.base import PublicId from aea.configurations.constants import ITEM_TYPE_TO_PLURAL from aea.helpers import http_requests as requests FILE_DOWNLOAD_TIMEOUT = ( 180 # quite big number case possible slow channels and package can be quite big ) def get_auth_token() -> str: """ Get current auth token. :return: str auth token """ config = get_or_create_cli_config() return config.get(AUTH_TOKEN_KEY, None) def request_api( method: str, path: str, params: Optional[Dict] = None, data: Optional[Dict] = None, is_auth: bool = False, files: Optional[Dict] = None, handle_400: bool = True, return_code: bool = False, ) -> Union[JSONLike, Tuple[JSONLike, int]]: """ Request Registry API. :param method: str request method ('GET, 'POST', 'PUT', etc.). :param path: str URL path. :param params: dict GET params. :param data: dict POST data. :param is_auth: bool is auth required (default False). :param files: optional dict {file_field_name: open(filepath, "rb")} (default None). :param handle_400: whether or not to handle 400 response :param return_code: whether or not to return return_code :return: dict response from Registry API or tuple (dict response, status code). """ headers = {} if is_auth: token = get_auth_token() if token is None: raise click.ClickException( "Unable to read authentication config. " 'Please sign in with "aea login" command.' ) headers.update({"Authorization": "Token {}".format(token)}) try: resp = _perform_registry_request(method, path, params, data, files, headers) resp_json = resp.json() except requests.exceptions.ConnectionError: raise click.ClickException("Registry server is not responding.") except requests.JSONDecodeError: resp_json = None if resp.status_code == 200: pass elif resp.status_code == 201: logger.debug("Successfully created!") elif resp.status_code == 403: raise click.ClickException( """You are not authenticated. 'Please sign in with "aea login" command.""" ) elif resp.status_code == 500: raise click.ClickException( "Registry internal server error: {}".format(resp_json["detail"]) ) elif resp.status_code == 404: raise click.ClickException("Not found in Registry.") elif resp.status_code == 409: raise click.ClickException( "Conflict in Registry. {}".format(resp_json["detail"]) ) elif resp.status_code == 400: if handle_400: raise click.ClickException(resp_json) elif resp_json is None: raise click.ClickException( "Wrong server response. Status code: {}: Response text: {}".format( resp.status_code, resp.text ) ) else: raise click.ClickException( "Wrong server response. Status code: {}: Error detail: {}".format( resp.status_code, resp_json.get("detail", resp_json) ) ) if return_code: return resp_json, resp.status_code return resp_json def _perform_registry_request( method: str, path: str, params: Optional[Dict] = None, data: Optional[Dict] = None, files: Optional[Dict] = None, headers: Optional[Dict] = None, ) -> requests.Response: """Perform HTTP request and resturn response object.""" request_kwargs = dict( method=method, url="{}{}".format(REGISTRY_API_URL, path), params=params, files=files, data=data, headers=headers, ) resp = requests.request(**request_kwargs) return resp def download_file(url: str, cwd: str, timeout: float = FILE_DOWNLOAD_TIMEOUT) -> str: """ Download file from URL and save it in CWD (current working directory). :param url: str url of the file to download. :param cwd: str path to current working directory. :param timeout: float. timeout to download a file :return: str path to downloaded file """ local_filename = url.split("/")[-1] filepath = os.path.join(cwd, local_filename) # NOTE the stream=True parameter below response = requests.get(url, stream=True, timeout=timeout) if response.status_code == 200: with open(filepath, "wb") as f: f.write(response.raw.read()) else: raise click.ClickException( "Wrong response from server when downloading package." ) return filepath def extract(source: str, target: str) -> None: """ Extract tarball and remove source file. :param source: str path to a source tarball file. :param target: str path to target directory. """ if source.endswith("tar.gz"): tar = tarfile.open(source, "r:gz") # pylint: disable=consider-using-with tar.extractall(path=target) tar.close() else: raise ValueError("Unknown file type: {}".format(source)) os.remove(source) def _rm_tarfiles() -> None: cwd = os.getcwd() for filename in os.listdir(cwd): if filename.endswith(".tar.gz"): filepath = os.path.join(cwd, filename) os.remove(filepath) def clean_tarfiles(func: Callable) -> Callable: """Decorate func to clean tarfiles after executing.""" def wrapper(*args: Any, **kwargs: Any) -> Callable: try: result = func(*args, **kwargs) except Exception as e: _rm_tarfiles() raise e else: _rm_tarfiles() return result return wrapper def check_is_author_logged_in(author_name: str) -> None: """ Check if current user's name equals to item's author. :param author_name: str item author username. :raise ClickException: if username and author's name are not equal. """ resp = cast(JSONLike, request_api("GET", "/rest-auth/user/", is_auth=True)) if not author_name == resp["username"]: raise click.ClickException( "Author username is not equal to current logged in username " "(logged in: {}, author: {}). Please logout and then login correctly.".format( resp["username"], author_name ) ) def is_auth_token_present() -> bool: """ Check if any user is currently logged in. :return: bool is logged in. """ return get_auth_token() is not None def get_package_meta( obj_type: str, public_id: PublicId, aea_version: Optional[str] = None ) -> JSONLike: """ Get package meta data from remote registry. Optionally filter by AEA version. :param obj_type: str. component type :param public_id: component public id :param aea_version: the AEA version (e.g. "0.1.0") or None. :return: dict with package details """ params = dict(aea_version=aea_version) if aea_version else None api_path = f"/{obj_type}s/{public_id.author}/{public_id.name}/{public_id.version}" resp = cast(JSONLike, request_api("GET", api_path, params=params)) return resp def get_latest_public_id_mixed( ctx: Context, item_type: str, item_public_id: PublicId, aea_version: Optional[str] = None, ) -> PublicId: """ Get latest public id of the message, mixed mode. That is, give priority to local registry, and fall back to remote registry in case of failure. :param ctx: the CLI context. :param item_type: the item type. :param item_public_id: the item public id. :param aea_version: the AEA version constraint, or None :return: the path to the found package. """ try: _, item_config = find_item_locally(ctx, item_type, item_public_id) latest_item_public_id = item_config.public_id except click.ClickException: logger.debug( "Get latest public id from local registry failed, trying remote registry..." ) # the following might raise exception, but we don't catch it this time package_meta = get_package_meta( item_type, item_public_id, aea_version=aea_version ) latest_item_public_id = PublicId.from_str(cast(str, package_meta["public_id"])) return latest_item_public_id def get_latest_version_available_in_registry( ctx: Context, item_type: str, item_public_id: PublicId, aea_version: Optional[str] = None, ) -> PublicId: """ Get latest available package version public id. Optionally consider AEA version through the `aea_version` parameter. :param ctx: Context object. :param item_type: the item type. :param item_public_id: the item public id. :param aea_version: the AEA version (e.g. "0.1.0") or None. :return: the latest public id. """ is_local = ctx.config.get("is_local") is_mixed = ctx.config.get("is_mixed") try: if is_mixed: latest_item_public_id = get_latest_public_id_mixed( ctx, item_type, item_public_id, aea_version ) elif is_local: _, item_config = find_item_locally(ctx, item_type, item_public_id) latest_item_public_id = item_config.public_id else: package_meta = get_package_meta(item_type, item_public_id, aea_version) latest_item_public_id = PublicId.from_str( cast(str, package_meta["public_id"]) ) except Exception: # pylint: disable=broad-except raise click.ClickException( f"Package {item_public_id} details can not be fetched from the registry!" ) return latest_item_public_id def list_missing_packages( packages: List[Tuple[str, PublicId]] ) -> List[Tuple[str, PublicId]]: """Get list of packages not currently present in registry.""" result: List[Tuple[str, PublicId]] = [] for package_type, package_id in packages: api_path = f"/{ITEM_TYPE_TO_PLURAL[package_type]}/{package_id.author}/{package_id.name}/{package_id.version}" resp = _perform_registry_request("GET", api_path) if resp.status_code == 404: result.append((package_type, package_id)) elif resp.status_code == 200: pass else: # pragma: nocover raise ValueError("Error on registry request") return result ================================================ FILE: aea/cli/remove.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea remove' subcommand.""" import os import shutil from collections import defaultdict from contextlib import contextmanager from pathlib import Path from typing import Dict, Generator, Optional, Set, Tuple, cast import click from aea.cli.utils.click_utils import PublicIdParameter from aea.cli.utils.config import load_item_config, try_to_load_agent_config from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, pass_ctx from aea.cli.utils.loggers import logger from aea.cli.utils.package_utils import ( get_item_public_id_by_author_name, is_item_present, ) from aea.configurations.base import ( AgentConfig, ComponentId, ComponentType, PackageConfiguration, PackageId, PublicId, ) from aea.configurations.constants import ( CONNECTION, CONTRACT, DEFAULT_AEA_CONFIG_FILE, PROTOCOL, SKILL, ) from aea.configurations.manager import find_component_directory_from_component_id from aea.helpers.io import open_file @click.group() @click.option( "-w", "--with-dependencies", is_flag=True, help="Remove obsolete dependencies not required anymore.", ) @click.pass_context @check_aea_project(check_aea_version=False) # type: ignore # pylint: disable=no-value-for-parameter def remove( click_context: click.Context, with_dependencies: bool ) -> None: # pylint: disable=unused-argument """Remove a package from the agent.""" ctx = cast(Context, click_context.obj) if with_dependencies: ctx.set_config("with_dependencies", True) @remove.command() @click.argument("connection_id", type=PublicIdParameter(), required=True) @pass_ctx def connection(ctx: Context, connection_id: PublicId) -> None: """Remove a connection from the agent.""" remove_item(ctx, CONNECTION, connection_id) @remove.command() @click.argument("contract_id", type=PublicIdParameter(), required=True) @pass_ctx def contract(ctx: Context, contract_id: PublicId) -> None: """Remove a contract from the agent.""" remove_item(ctx, CONTRACT, contract_id) @remove.command() @click.argument("protocol_id", type=PublicIdParameter(), required=True) @pass_ctx def protocol(ctx: Context, protocol_id: PublicId) -> None: """Remove a protocol from the agent.""" remove_item(ctx, PROTOCOL, protocol_id) @remove.command() @click.argument("skill_id", type=PublicIdParameter(), required=True) @pass_ctx def skill(ctx: Context, skill_id: PublicId) -> None: """Remove a skill from the agent.""" remove_item(ctx, SKILL, skill_id) class ItemRemoveHelper: """Helper to check dependencies on removing component from agent config.""" def __init__(self, ctx: Context, ignore_non_vendor: bool = False) -> None: """Init helper.""" self._ctx = ctx self._agent_config = ctx.agent_config self._ignore_non_vendor = ignore_non_vendor def get_agent_dependencies_with_reverse_dependencies( self, ) -> Dict[PackageId, Set[PackageId]]: """ Get all reverse dependencies in agent. :return: dict with PackageId: and set of PackageIds that uses this package Return example: { PackageId(protocol, fetchai/pck1:0.1.0): { PackageId(skill, fetchai/pck2:0.2.0), PackageId(skill, fetchai/pck3:0.3.0) }, PackageId(connection, fetchai/pck4:0.1.0): set(), PackageId(skill, fetchai/pck5:0.1.0): set(), PackageId(skill, fetchai/pck6:0.2.0): set()} ) """ return self.get_item_dependencies_with_reverse_dependencies( self._agent_config, None ) @classmethod def get_item_config(cls, package_id: PackageId) -> PackageConfiguration: """Get item config for item,_type and public_id.""" item_config = load_item_config( str(package_id.package_type), package_path=cls.get_component_directory(package_id), ) if (package_id.author != item_config.author) or ( package_id.name != item_config.name ): raise click.ClickException( f"Error loading {package_id} configuration, author/name do not match: {item_config.public_id}" ) return item_config @staticmethod def get_component_directory(package_id: PackageId) -> Path: """Return path for package.""" try: return find_component_directory_from_component_id( Path("."), ComponentId(str(package_id.package_type), package_id.public_id), ) except ValueError: raise click.ClickException( f"Can not find folder for the package: {package_id.package_type} {package_id.public_id}" ) def _get_item_requirements( self, item: PackageConfiguration, ignore_non_vendor: bool = False ) -> Generator[PackageId, None, None]: """ List all the requirements for item provided. :param item: the item package configuration :param ignore_non_vendor: whether or not to ignore vendor packages :yield: package ids: (type, public_id) """ for item_type in map(str, ComponentType): items: Set[PublicId] = getattr(item, f"{item_type}s", set()) for item_public_id in items: if ignore_non_vendor and is_item_present( self._ctx.cwd, self._ctx.agent_config, item_type, item_public_id, is_vendor=False, ): continue yield PackageId(item_type, item_public_id) def get_item_dependencies_with_reverse_dependencies( self, item: PackageConfiguration, package_id: Optional[PackageId] = None ) -> Dict[PackageId, Set[PackageId]]: """ Get item dependencies. It's recursive and provides all the sub dependencies. :param item: the item package configuration :param package_id: the package id. :return: dict with PackageId: and set of PackageIds that uses this package """ result: defaultdict = defaultdict(set) for dep_package_id in self._get_item_requirements( item, self._ignore_non_vendor ): if package_id is None: _ = result[dep_package_id] # init default dict value else: result[dep_package_id].add(package_id) if not self.is_present_in_agent_config(dep_package_id): # pragma: nocover continue dep_item = self.get_item_config(dep_package_id) for item_key, deps in self.get_item_dependencies_with_reverse_dependencies( dep_item, dep_package_id ).items(): result[item_key] = result[item_key].union(deps) return result def is_present_in_agent_config(self, package_id: PackageId) -> bool: """Check item is in agent config.""" current_item = get_item_public_id_by_author_name( self._agent_config, str(package_id.package_type), package_id.public_id.author, package_id.public_id.name, ) return bool(current_item) def check_remove( self, item_type: str, item_public_id: PublicId ) -> Tuple[Set[PackageId], Set[PackageId], Dict[PackageId, Set[PackageId]]]: """ Check item can be removed from agent. required by - set of components that requires this component can be deleted - set of dependencies used only by component so can be deleted can not be deleted - dict - keys - packages can not be deleted, values are set of packages required by. :param item_type: the item type. :param item_public_id: the item public id. :return: Tuple[required by, can be deleted, can not be deleted.] """ package_id = PackageId(item_type, item_public_id) item = self.get_item_config(package_id) agent_deps = self.get_agent_dependencies_with_reverse_dependencies() item_deps = self.get_item_dependencies_with_reverse_dependencies( item, package_id ) can_be_removed = set() can_not_be_removed = {} for dep_key, deps in item_deps.items(): if agent_deps[dep_key] == deps: can_be_removed.add(dep_key) else: can_not_be_removed[dep_key] = agent_deps[dep_key] - deps return agent_deps[package_id], can_be_removed, can_not_be_removed @contextmanager def remove_unused_component_configurations(ctx: Context) -> Generator: """ Remove all component configurations for items not registered and dump agent config. Context manager! Clean all configurations on enter, restore actual configurations and dump agent config. :param ctx: click context :yield: None """ saved_configuration = ctx.agent_config.component_configurations ctx.agent_config.component_configurations = {} try: yield finally: saved_configuration_by_component_prefix = { key.component_prefix: value for key, value in saved_configuration.items() } # need to reload agent configuration with the updated references try_to_load_agent_config(ctx) for component_id in ctx.agent_config.package_dependencies: if component_id.component_prefix in saved_configuration_by_component_prefix: ctx.agent_config.component_configurations[ component_id ] = saved_configuration_by_component_prefix[ component_id.component_prefix ] with open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as f: ctx.agent_loader.dump(ctx.agent_config, f) class RemoveItem: """Implementation of item remove from the project.""" def __init__( self, ctx: Context, item_type: str, item_id: PublicId, with_dependencies: bool, force: bool = False, ignore_non_vendor: bool = False, ) -> None: """ Init remove item tool. :param ctx: click context. :param item_type: str, package type :param item_id: PublicId of the item to remove. :param with_dependencies: whether or not to remove dependencies. :param force: bool. if True remove even required by another package. :param ignore_non_vendor: bool. if True, ignore non-vendor packages when computing inverse dependencies. The effect of this flag is ignored if force = True """ self.ctx = ctx self.force = force self.ignore_non_vendor = ignore_non_vendor self.item_type = item_type self.item_id = item_id self.with_dependencies = with_dependencies self.item_type_plural = "{}s".format(item_type) self.item_name = item_id.name self.current_item = self.get_current_item() self.required_by: Set[PackageId] = set() self.dependencies_can_be_removed: Set[PackageId] = set() try: ( self.required_by, self.dependencies_can_be_removed, *_, ) = ItemRemoveHelper( self.ctx, ignore_non_vendor=self.ignore_non_vendor ).check_remove( self.item_type, self.current_item ) except FileNotFoundError: # pragma: nocover pass # item registered but not present on filesystem def get_current_item(self) -> PublicId: """Return public id of the item already presents in agent config.""" current_item = get_item_public_id_by_author_name( self.ctx.agent_config, self.item_type, self.item_id.author, self.item_id.name, ) if not current_item: # pragma: nocover # actually checked in check_item_present raise click.ClickException( "The {} '{}' is not supported.".format(self.item_type, self.item_id) ) return current_item def remove(self) -> None: """Remove item and it's dependencies if specified.""" click.echo(f"Removing {self.item_type} '{self.current_item}'...") self.remove_item() if self.with_dependencies: self.remove_dependencies() click.echo(f"Successfully removed {self.item_type} '{self.current_item}'.") @property def agent_items(self) -> Set[PublicId]: """Return items registered with agent of the same type as item.""" return getattr(self.agent_config, self.item_type_plural, set()) @property def is_required_by(self) -> bool: """Is required by any other registered component in the agent.""" return bool(self.required_by) def remove_item(self) -> None: """ Remove item. Removed from the filesystem. Removed from the agent configuration Does not remove dependencies, please use `remove_dependencies`. """ if (not self.force) and self.is_required_by: raise click.ClickException( f"Package {self.item_type} {self.item_id} can not be removed because it is required by {','.join(map(str, self.required_by))}" ) self._remove_package() self._remove_from_config() @property def cwd(self) -> str: """Get current workdir.""" return self.ctx.cwd @property def agent_config(self) -> AgentConfig: """Get agent config from context.""" return self.ctx.agent_config @property def agent_name(self) -> str: # pragma: nocover """Get agent name.""" return self.ctx.agent_config.agent_name def _get_item_folder(self) -> Path: """Get item package folder.""" return Path(self.cwd) / ItemRemoveHelper.get_component_directory( PackageId(self.item_type, self.item_id) ) def _remove_package(self) -> None: """Remove package from filesystem.""" item_folder = self._get_item_folder() try: shutil.rmtree(item_folder) except BaseException: raise click.ClickException( f"An error occurred during {item_folder} removing." ) def _remove_from_config(self) -> None: """Remove item from agent config.""" current_item = self.get_current_item() logger.debug( "Removing the {} from {}".format(self.item_type, DEFAULT_AEA_CONFIG_FILE) ) self.agent_items.remove(current_item) self.agent_config.component_configurations.pop( ComponentId(self.item_type, current_item), None ) self.ctx.dump_agent_config() def remove_dependencies(self) -> None: """Remove all the dependencies related only to the package.""" if not self.dependencies_can_be_removed: return for dependency in self.dependencies_can_be_removed: click.echo( f"Removing obsolete dependency {str(dependency.package_type)} '{str(dependency.public_id)}'..." ) RemoveItem( self.ctx, str(dependency.package_type), dependency.public_id, with_dependencies=False, force=True, ).remove_item() click.echo( f"Successfully removed {str(dependency.package_type)} '{dependency.public_id}'." ) def remove_item(ctx: Context, item_type: str, item_id: PublicId) -> None: """ Remove an item from the configuration file and agent, given the public id. :param ctx: Context object. :param item_type: type of item. :param item_id: item public ID. """ with remove_unused_component_configurations(ctx): RemoveItem( ctx, item_type, item_id, cast(bool, ctx.config.get("with_dependencies")) ).remove() ================================================ FILE: aea/cli/remove_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea remove_key' subcommand.""" import os from typing import cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE from aea.crypto.registries import crypto_registry from aea.helpers.io import open_file @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice(list(crypto_registry.supported_ids)), required=True, ) @click.option( "--connection", is_flag=True, help="For removing a private key for connections." ) @click.pass_context @check_aea_project def remove_key(click_context: click.Context, type_: str, connection: bool) -> None: """Remove a private key from the wallet of the agent.""" _remove_private_key(click_context, type_, connection) def _remove_private_key( click_context: click.core.Context, type_: str, connection: bool = False, ) -> None: """ Remove private key to the wallet. :param click_context: click context object. :param type_: type. :param connection: whether or not it is a private key for a connection """ ctx = cast(Context, click_context.obj) _try_remove_key(ctx, type_, connection) def _try_remove_key(ctx: Context, type_: str, connection: bool = False) -> None: private_keys = ( ctx.agent_config.connection_private_key_paths if connection else ctx.agent_config.private_key_paths ) existing_keys = private_keys.keys() if type_ not in existing_keys: raise click.ClickException( f"There is no {'connection ' if connection else ''}key registered with id {type_}." ) private_keys.delete(type_) ctx.agent_loader.dump( ctx.agent_config, open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") ) ================================================ FILE: aea/cli/reset_password.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea reset_password' subcommand.""" import click from aea.cli.registry.login import registry_reset_password @click.command( name="reset_password", help="Reset the password of the registry account." ) @click.argument("email", type=str, required=True) def reset_password(email: str) -> None: """Command to request Registry to reset password.""" _do_password_reset(email) def _do_password_reset(email: str) -> None: """ Request Registry to reset password. :param email: str email. """ registry_reset_password(email) click.echo("An email with a password reset link was sent to {}".format(email)) ================================================ FILE: aea/cli/run.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea run' subcommand.""" from contextlib import contextmanager from pathlib import Path from typing import Generator, List, Optional, cast import click from aea import __version__ from aea.aea import AEA from aea.aea_builder import AEABuilder, DEFAULT_ENV_DOTFILE from aea.cli.install import do_install from aea.cli.utils.click_utils import ConnectionsOption, password_option from aea.cli.utils.constants import AEA_LOGO, REQUIREMENTS from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.configurations.base import PublicId from aea.configurations.manager import AgentConfigManager from aea.connections.base import Connection from aea.contracts.base import Contract from aea.exceptions import AEAWalletNoAddressException from aea.helpers.base import load_env_file from aea.helpers.profiling import Profiling from aea.protocols.base import Message, Protocol from aea.protocols.dialogue.base import Dialogue from aea.skills.base import Behaviour, Handler, Model, Skill @click.command() @password_option() @click.option( "--connections", "connection_ids", cls=ConnectionsOption, required=False, default=None, help="The connection names to use for running the agent. Must be declared in the agent's configuration file.", ) @click.option( "--env", "env_file", type=click.Path(), required=False, default=DEFAULT_ENV_DOTFILE, help="Specify an environment file (default: .env)", ) @click.option( "--install-deps", "is_install_deps", is_flag=True, required=False, default=False, help="Install all the dependencies before running the agent.", ) @click.option( "--profiling", "profiling", required=False, default=0, help="Enable profiling, print profiling every amount of seconds", ) @click.option( "--exclude-connections", "exclude_connection_ids", cls=ConnectionsOption, required=False, default=None, help="The connection names to disable for running the agent. Must be declared in the agent's configuration file.", ) @click.pass_context @check_aea_project def run( click_context: click.Context, connection_ids: List[PublicId], exclude_connection_ids: List[PublicId], env_file: str, is_install_deps: bool, profiling: int, password: str, ) -> None: """Run the agent.""" if connection_ids and exclude_connection_ids: raise click.ClickException( "Please use only one of --connections or --exclude-connections, not both!" ) ctx = cast(Context, click_context.obj) profiling = int(profiling) if exclude_connection_ids: connection_ids = _calculate_connection_ids(ctx, exclude_connection_ids) if profiling > 0: with _profiling_context(period=profiling): run_aea(ctx, connection_ids, env_file, is_install_deps, password) return run_aea(ctx, connection_ids, env_file, is_install_deps, password) def _calculate_connection_ids( ctx: Context, exclude_connections: List[PublicId] ) -> List[PublicId]: """Calculate resulting list of connection ids to run.""" agent_config_manager = AgentConfigManager.load(ctx.cwd) not_existing_connections = ( set(exclude_connections) - agent_config_manager.agent_config.connections ) if not_existing_connections: raise ValueError( f"Connections to exclude: {', '.join(map(str, not_existing_connections))} are not defined in agent configuration!" ) connection_ids = list( agent_config_manager.agent_config.connections - set(exclude_connections) ) return connection_ids @contextmanager def _profiling_context(period: int) -> Generator: """Start profiling context.""" OBJECTS_INSTANCES = [ Message, Dialogue, Handler, Model, Behaviour, Skill, Connection, Contract, Protocol, ] OBJECTS_CREATED = [Message, Dialogue] profiler = Profiling( period=period, objects_instances_to_count=OBJECTS_INSTANCES, objects_created_to_count=OBJECTS_CREATED, ) profiler.start() try: yield None except Exception: # pylint: disable=try-except-raise # pragma: nocover raise finally: profiler.stop() profiler.wait_completed(sync=True, timeout=10) # hack to address faulty garbage collection output being printed import os # pylint: disable=import-outside-toplevel import sys # pylint: disable=import-outside-toplevel sys.stderr = open(os.devnull, "w", encoding="utf-8") def run_aea( ctx: Context, connection_ids: List[PublicId], env_file: str, is_install_deps: bool, password: Optional[str] = None, ) -> None: """ Prepare and run an agent. :param ctx: a context object. :param connection_ids: list of connections public IDs. :param env_file: a path to env file. :param is_install_deps: bool flag is install dependencies. :param password: the password to encrypt/decrypt the private key. :raises ClickException: if any Exception occurs. """ skip_consistency_check = ctx.config["skip_consistency_check"] _prepare_environment(ctx, env_file, is_install_deps) aea = _build_aea(connection_ids, skip_consistency_check, password) click.echo(AEA_LOGO + "v" + __version__ + "\n") click.echo( "Starting AEA '{}' in '{}' mode...".format(aea.name, aea.runtime.loop_mode) ) try: aea.start() except KeyboardInterrupt: # pragma: no cover click.echo(" AEA '{}' interrupted!".format(aea.name)) # pragma: no cover except Exception as e: # pragma: no cover raise click.ClickException(str(e)) finally: click.echo("Stopping AEA '{}' ...".format(aea.name)) aea.stop() click.echo("AEA '{}' stopped.".format(aea.name)) def _prepare_environment(ctx: Context, env_file: str, is_install_deps: bool) -> None: """ Prepare the AEA project environment. :param ctx: a context object. :param env_file: the path to the environment file. :param is_install_deps: whether to install the dependencies """ load_env_file(env_file) if is_install_deps: requirements_path = REQUIREMENTS if Path(REQUIREMENTS).exists() else None do_install(ctx, requirement=requirements_path) def _build_aea( connection_ids: Optional[List[PublicId]], skip_consistency_check: bool, password: Optional[str] = None, ) -> AEA: """Build the AEA.""" try: builder = AEABuilder.from_aea_project( Path("."), skip_consistency_check=skip_consistency_check, password=password ) aea = builder.build(connection_ids=connection_ids, password=password) return aea except AEAWalletNoAddressException: error_msg = ( "You haven't specified any private key for the AEA project.\n" "Please add one by using the commands `aea generate-key` and `aea add-key` for the ledger of your choice.\n" ) raise click.ClickException(error_msg) except Exception as e: raise click.ClickException(str(e)) ================================================ FILE: aea/cli/scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea scaffold' subcommand.""" import os import re import shutil from pathlib import Path from typing import cast import click from jsonschema import ValidationError from aea import AEA_DIR from aea.cli.fingerprint import fingerprint_item from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, clean_after, pass_ctx from aea.cli.utils.loggers import logger from aea.cli.utils.package_utils import ( create_symlink_packages_to_vendor, create_symlink_vendor_to_local, validate_package_name, ) from aea.configurations.base import PublicId from aea.configurations.constants import ( # noqa: F401 # pylint: disable=unused-import CONNECTION, CONTRACT, DEFAULT_AEA_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, DEFAULT_VERSION, DOTTED_PATH_MODULE_ELEMENT_SEPARATOR, PROTOCOL, SCAFFOLD_PUBLIC_ID, SKILL, ) from aea.helpers.io import open_file @click.group() @click.option( "--with-symlinks", is_flag=True, help="Add symlinks from vendor to non-vendor and packages to vendor folders.", ) @click.pass_context @check_aea_project def scaffold( click_context: click.core.Context, with_symlinks: bool ) -> None: # pylint: disable=unused-argument """Scaffold a package for the agent.""" ctx = cast(Context, click_context.obj) ctx.set_config("with_symlinks", with_symlinks) @scaffold.command() @click.argument("connection_name", type=str, required=True) @pass_ctx def connection(ctx: Context, connection_name: str) -> None: """Add a connection scaffolding to the configuration file and agent.""" scaffold_item(ctx, CONNECTION, connection_name) @scaffold.command() @click.argument("contract_name", type=str, required=True) @pass_ctx def contract(ctx: Context, contract_name: str) -> None: """Add a contract scaffolding to the configuration file and agent.""" scaffold_item(ctx, CONTRACT, contract_name) @scaffold.command() @click.argument("protocol_name", type=str, required=True) @click.option("-y", "--yes", is_flag=True, default=False) @pass_ctx def protocol(ctx: Context, protocol_name: str, yes: bool) -> None: """Add a protocol scaffolding to the configuration file and agent.""" if yes or click.confirm( "We highly recommend auto-generating protocols with the aea generate command. Do you really want to continue scaffolding?" ): scaffold_item(ctx, PROTOCOL, protocol_name) else: click.echo("Aborted. Exit") # pragma: nocover @scaffold.command() @click.argument("skill_name", type=str, required=True) @pass_ctx def skill(ctx: Context, skill_name: str) -> None: """Add a skill scaffolding to the configuration file and agent.""" scaffold_item(ctx, SKILL, skill_name) @scaffold.command() @pass_ctx def decision_maker_handler(ctx: Context) -> None: """Add a decision maker scaffolding to the configuration file and agent.""" _scaffold_dm_handler(ctx) @scaffold.command() @pass_ctx def error_handler(ctx: Context) -> None: """Add an error scaffolding to the configuration file and agent.""" _scaffold_error_handler(ctx) @clean_after def scaffold_item(ctx: Context, item_type: str, item_name: str) -> None: """ Add an item scaffolding to the configuration file and agent. :param ctx: Context object. :param item_type: type of item. :param item_name: item name. :raises ClickException: if some error occurs. """ validate_package_name(item_name) author_name = ctx.agent_config.author loader = getattr(ctx, f"{item_type}_loader") default_config_filename = globals()[f"DEFAULT_{item_type.upper()}_CONFIG_FILE"] item_type_plural = item_type + "s" existing_ids = getattr(ctx.agent_config, f"{item_type}s") existing_ids_only_author_and_name = map(lambda x: (x.author, x.name), existing_ids) # check if we already have an item with the same public id if (author_name, item_name) in existing_ids_only_author_and_name: raise click.ClickException( f"A {item_type} with name '{item_name}' already exists. Aborting..." ) agent_name = ctx.agent_config.agent_name click.echo( f"Adding {item_type} scaffold '{item_name}' to the agent '{agent_name}'..." ) # create the item folder Path(item_type_plural).mkdir(exist_ok=True) dest = os.path.join(item_type_plural, item_name) if os.path.exists(dest): raise click.ClickException( f"A {item_type} with this name already exists. Please choose a different name and try again." ) ctx.clean_paths.append(str(dest)) try: # copy the item package into the agent project. src = Path(os.path.join(AEA_DIR, item_type_plural, "scaffold")) logger.debug(f"Copying {item_type} modules. src={src} dst={dest}") shutil.copytree(src, dest) # add the item to the configurations. logger.debug(f"Registering the {item_type} into {DEFAULT_AEA_CONFIG_FILE}") new_public_id = PublicId(author_name, item_name, DEFAULT_VERSION) existing_ids.add(new_public_id) with open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as fp: ctx.agent_loader.dump(ctx.agent_config, fp) # ensure the name in the yaml and the name of the folder are the same config_filepath = Path( ctx.cwd, item_type_plural, item_name, default_config_filename ) with open_file(config_filepath) as fp: config = loader.load(fp) config.name = item_name config.author = author_name with open_file(config_filepath, "w") as fp: loader.dump(config, fp) # update 'PUBLIC_ID' variable with the right public id in connection.py! for file_name in ["__init__.py", "connection.py"]: file_path = Path(dest) / file_name if not file_path.exists(): continue py_file = Path(file_path) py_file.write_text( # pylint: disable=unspecified-encoding re.sub( SCAFFOLD_PUBLIC_ID, str(new_public_id), py_file.read_text(), # pylint: disable=unspecified-encoding ) ) # fingerprint item. fingerprint_item(ctx, item_type, new_public_id) if ctx.config.get("with_symlinks", False): click.echo( "Adding symlinks from vendor to non-vendor and packages to vendor folders." ) create_symlink_vendor_to_local(ctx, item_type, new_public_id) create_symlink_packages_to_vendor(ctx) except ValidationError: raise click.ClickException( f"Error when validating the {item_type} configuration file." ) except Exception as e: raise click.ClickException(str(e)) def _scaffold_dm_handler(ctx: Context) -> None: """Scaffold the decision maker handler.""" _scaffold_non_package_item( ctx, "decision_maker_handler", "decision maker handler", "DecisionMakerHandler", "decision_maker", ) def _scaffold_error_handler(ctx: Context) -> None: """Scaffold the error handler.""" _scaffold_non_package_item( ctx, "error_handler", "error handler", "ErrorHandler", "error_handler" ) def _scaffold_non_package_item( ctx: Context, item_type: str, type_name: str, class_name: str, aea_dir: str ) -> None: """ Scaffold a non-package item (e.g. decision maker handler, or error handler). :param ctx: the CLI context. :param item_type: the item type (e.g. 'decision_maker_handler') :param type_name: the type name (e.g. "decision maker") :param class_name: the class name (e.g. "DecisionMakerHandler") :param aea_dir: the AEA directory that contains the scaffold module """ existing_item = getattr(ctx.agent_config, item_type) if existing_item != {}: raise click.ClickException( f"A {type_name} specification already exists. Aborting..." ) dest = Path(f"{item_type}.py") agent_name = ctx.agent_config.agent_name click.echo(f"Adding {type_name} scaffold to the agent '{agent_name}'...") # create the file name dotted_path = f".{item_type}{DOTTED_PATH_MODULE_ELEMENT_SEPARATOR}{class_name}" try: # copy the item package into the agent project. src = Path(os.path.join(AEA_DIR, aea_dir, "scaffold.py")) logger.debug(f"Copying {type_name}. src={src} dst={dest}") shutil.copyfile(src, dest) # add the item to the configurations. logger.debug(f"Registering the {type_name} into {DEFAULT_AEA_CONFIG_FILE}") setattr( ctx.agent_config, item_type, { "dotted_path": str(dotted_path), "file_path": str(os.path.join(".", dest)), "config": {}, }, ) ctx.agent_loader.dump( ctx.agent_config, open_file( os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w", encoding="utf-8" ), ) except Exception as e: os.remove(dest) raise click.ClickException(str(e)) ================================================ FILE: aea/cli/search.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea search' subcommand.""" from pathlib import Path from typing import Dict, List, Tuple, cast import click from aea import AEA_DIR from aea.cli.registry.utils import request_api from aea.cli.utils.context import Context from aea.cli.utils.decorators import pass_ctx from aea.cli.utils.formatting import format_items, retrieve_details from aea.common import JSONLike from aea.configurations.constants import ( AGENT, AGENTS, CONNECTION, CONNECTIONS, CONTRACT, CONTRACTS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, PROTOCOL, PROTOCOLS, SKILL, SKILLS, ) from aea.configurations.loader import ConfigLoader @click.group() @click.option("--local", is_flag=True, help="For local search.") @click.pass_context def search(click_context: click.Context, local: bool) -> None: """Search for packages in the registry.""" ctx = cast(Context, click_context.obj) if local: ctx.set_config("is_local", True) @search.command() @click.option("--query", default="", help="Query string to search Connections by name.") @click.option("--page", type=int, default=1, help="Page number to display.") @pass_ctx def connections(ctx: Context, query: str, page: int) -> None: """Search for Connections.""" item_type = CONNECTION _output_search_results(item_type, *search_items(ctx, item_type, query, page), page) @search.command() @click.option("--query", default="", help="Query string to search Contracts by name.") @click.option("--page", type=int, default=1, help="Page number to display.") @pass_ctx def contracts(ctx: Context, query: str, page: int) -> None: """Search for Contracts.""" item_type = CONTRACT _output_search_results(item_type, *search_items(ctx, item_type, query, page), page) @search.command() @click.option("--query", default="", help="Query string to search Protocols by name.") @click.option("--page", type=int, default=1, help="Page number to display.") @pass_ctx def protocols(ctx: Context, query: str, page: int) -> None: """Search for Protocols.""" item_type = PROTOCOL _output_search_results(item_type, *search_items(ctx, item_type, query, page), page) @search.command() @click.option("--query", default="", help="Query string to search Skills by name.") @click.option("--page", type=int, default=1, help="Page number to display.") @pass_ctx def skills(ctx: Context, query: str, page: int) -> None: """Search for Skills.""" item_type = SKILL _output_search_results(item_type, *search_items(ctx, item_type, query, page), page) @search.command() @click.option("--query", default="", help="Query string to search Agents by name.") @click.option("--page", type=int, default=1, help="Page number to display.") @pass_ctx def agents(ctx: Context, query: str, page: int) -> None: """Search for Agents.""" item_type = AGENT _output_search_results(item_type, *search_items(ctx, item_type, query, page), page) def _is_invalid_item(name: str, dir_path: Path, config_path: Path) -> bool: """Return true if this protocol, connection or skill should not be returned in the list.""" return ( name == "scaffold" or not Path(dir_path).is_dir() or not Path(config_path).is_file() ) def _get_details_from_dir( loader: ConfigLoader, root_path: str, sub_dir_glob_pattern: str, config_filename: str, results: List[Dict], ) -> None: for dir_path in Path(root_path).glob(sub_dir_glob_pattern + "/*/"): config_path = dir_path / config_filename if _is_invalid_item(dir_path.name, dir_path, config_path): continue details = retrieve_details(dir_path.name, loader, str(config_path)) results.append(details) def _search_items_locally(ctx: Context, item_type_plural: str) -> List[Dict]: result = [] # type: List[Dict] configs = { AGENTS: {"loader": ctx.agent_loader, "config_file": DEFAULT_AEA_CONFIG_FILE}, CONNECTIONS: { "loader": ctx.connection_loader, "config_file": DEFAULT_CONNECTION_CONFIG_FILE, }, CONTRACTS: { "loader": ctx.contract_loader, "config_file": DEFAULT_CONTRACT_CONFIG_FILE, }, PROTOCOLS: { "loader": ctx.protocol_loader, "config_file": DEFAULT_PROTOCOL_CONFIG_FILE, }, SKILLS: {"loader": ctx.skill_loader, "config_file": DEFAULT_SKILL_CONFIG_FILE}, } if item_type_plural != AGENTS: # look in aea distribution for default packages _get_details_from_dir( cast(ConfigLoader, configs[item_type_plural]["loader"]), AEA_DIR, item_type_plural, cast(str, configs[item_type_plural]["config_file"]), result, ) try: registry_path = ctx.registry_path except ValueError as e: # pragma: nocover raise click.ClickException(str(e)) # look in packages dir for all other packages _get_details_from_dir( cast(ConfigLoader, configs[item_type_plural]["loader"]), registry_path, "*/{}".format(item_type_plural), cast(str, configs[item_type_plural]["config_file"]), result, ) return sorted(result, key=lambda k: k["name"]) def search_items( ctx: Context, item_type: str, query: str, page: int ) -> Tuple[List[Dict], int]: """ Search items by query and click.echo results. :param ctx: Context object. :param item_type: item type. :param query: query string. :param page: page. :return: (List of items, int items total count). """ click.echo('Searching for "{}"...'.format(query)) item_type_plural = item_type + "s" if ctx.config.get("is_local"): results = _search_items_locally(ctx, item_type_plural) count = len(results) else: resp = cast( JSONLike, request_api( "GET", "/{}".format(item_type_plural), params={"search": query, "page": page}, ), ) results = cast(List[Dict], resp["results"]) count = cast(int, resp["count"]) return results, count def _output_search_results( item_type: str, results: List[Dict], count: int, page: int ) -> None: """ Output search results. :param item_type: str item type. :param results: list of found items. :param count: items total count. :param page: page. """ item_type_plural = item_type + "s" len_results = len(results) if len_results == 0: click.echo("No {} found.".format(item_type_plural)) # pragma: no cover else: click.echo("{} found:\n".format(item_type_plural.title())) click.echo(format_items(results)) if count > len_results: click.echo( "{} {} out of {}.\nPage {}".format( len_results, item_type_plural, count, page ) ) # pragma: no cover ================================================ FILE: aea/cli/transfer.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea transfer' subcommand.""" import time from typing import Optional, cast import click from aea.cli.get_address import _try_get_address from aea.cli.utils.click_utils import password_option from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.package_utils import ( _override_ledger_configurations, get_wallet_from_context, try_get_balance, ) from aea.common import Address from aea.crypto.ledger_apis import LedgerApis from aea.crypto.registries import ledger_apis_registry DEFAULT_SETTLE_TIMEOUT = 60 @click.command() @click.argument( "type_", metavar="TYPE", type=click.Choice(list(ledger_apis_registry.supported_ids)), required=True, ) @click.argument( "address", type=str, required=True, ) @click.argument( "amount", type=int, required=True, ) @click.argument("fee", type=int, required=False, default=100) @password_option() @click.option("-y", "--yes", type=bool, is_flag=True, default=False) @click.option("--settle-timeout", type=int, default=DEFAULT_SETTLE_TIMEOUT) @click.option("--sync", type=bool, is_flag=True, default=False) @click.pass_context @check_aea_project def transfer( click_context: click.Context, type_: str, address: str, amount: int, fee: int, password: Optional[str], yes: bool, settle_timeout: int, sync: bool, ) -> None: """Transfer wealth associated with a private key of the agent to another account.""" ctx = cast(Context, click_context.obj) try: own_address = _try_get_address(ctx, type_, password) except KeyError: raise click.ClickException( f"No private key registered for `{type_}` in wallet!" ) if not yes: click.confirm( f"You are about to transfer from {own_address} to {address} on ledger {type_} the amount {amount} with fee {fee}. Do you want to continue?", abort=True, ) tx_digest = do_transfer(ctx, type_, address, amount, fee, password) if not tx_digest: raise click.ClickException("Failed to send a transaction!") if sync: click.echo("Transaction set. Waiting to be settled...") wait_tx_settled(type_, tx_digest, timeout=settle_timeout) click.echo( f"Transaction successfully settled. Sent {amount} with fee {fee} to {address}, transaction digest: {tx_digest}" ) else: click.echo( f"Transaction successfully submitted. Sending {amount} with fee {fee} to {address}, transaction digest: {tx_digest}" ) def wait_tx_settled( identifier: str, tx_digest: str, timeout: float = DEFAULT_SETTLE_TIMEOUT ) -> None: """ Wait transaction is settled successfully. :param identifier: str, ledger id :param tx_digest: str, transaction digest :param timeout: int, timeout in seconds before timeout error raised :raises TimeoutError: on timeout """ t = time.time() while True: if time.time() - t > timeout: raise TimeoutError() if LedgerApis.is_transaction_settled(identifier, tx_digest): return time.sleep(1) def do_transfer( ctx: Context, identifier: str, address: Address, amount: int, tx_fee: int, password: Optional[str] = None, ) -> Optional[str]: """ Perform wealth transfer to another account. :param ctx: click context :param identifier: str, ledger id to perform transfer operation :param address: address of the recipient :param amount: int, amount of wealth to transfer :param tx_fee: int, fee for transaction :param password: the password to encrypt/decrypt the private key :return: str, transaction digest or None if failed. """ click.echo("Starting transfer ...") wallet = get_wallet_from_context(ctx, password=password) source_address = wallet.addresses[identifier] _override_ledger_configurations(ctx.agent_config) balance = int(try_get_balance(ctx.agent_config, wallet, identifier)) total_payable = amount + tx_fee if total_payable > balance: raise click.ClickException( f"Balance is not enough! Available={balance}, required={total_payable}!" ) tx_nonce = LedgerApis.generate_tx_nonce(identifier, source_address, address) transaction = LedgerApis.get_transfer_transaction( identifier, source_address, address, amount, tx_fee, tx_nonce ) tx_signed = wallet.sign_transaction(identifier, transaction) return LedgerApis.send_signed_transaction(identifier, tx_signed) ================================================ FILE: aea/cli/upgrade.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the 'aea upgrade' subcommand.""" import pprint import shutil from copy import deepcopy from pathlib import Path from typing import Dict, Iterable, List, Set, Tuple, cast import click import aea from aea.cli.add import add_item from aea.cli.eject import _eject_item from aea.cli.registry.fetch import fetch_agent from aea.cli.registry.utils import get_latest_version_available_in_registry from aea.cli.remove import ( ItemRemoveHelper, RemoveItem, remove_unused_component_configurations, ) from aea.cli.utils.click_utils import PublicIdParameter, registry_flag from aea.cli.utils.config import ( dump_item_config, get_non_vendor_package_path, load_item_config, set_cli_author, ) from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, clean_after, pass_ctx from aea.cli.utils.package_utils import ( get_item_public_id_by_author_name, is_item_present, update_aea_version_range, update_references, ) from aea.configurations.base import ComponentId, PackageId, PackageType, PublicId from aea.configurations.constants import ( CONNECTION, CONTRACT, DEFAULT_VERSION, PROTOCOL, SKILL, VENDOR, ) from aea.exceptions import enforce from aea.helpers.base import delete_directory_contents, find_topological_order @click.group(invoke_without_command=True) @click.option("-y", "--yes", is_flag=True) @registry_flag( help_local="For fetching packages only from local folder.", help_remote="For fetching packages only from remote registry.", ) @click.pass_context @check_aea_project( # pylint: disable=unused-argument,no-value-for-parameter check_aea_version=False ) def upgrade( click_context: click.Context, local: bool, remote: bool, yes: bool ) -> None: # pylint: disable=unused-argument """Upgrade the packages of the agent.""" ctx = cast(Context, click_context.obj) ctx.set_config("is_local", local and not remote) ctx.set_config("is_mixed", not (local or remote)) ctx.set_config("yes_by_default", yes) set_cli_author(click_context) if click_context.invoked_subcommand is None: upgrade_project(ctx) @upgrade.command() @click.argument("connection_public_id", type=PublicIdParameter(), required=True) @pass_ctx def connection(ctx: Context, connection_public_id: PublicId) -> None: """Upgrade a connection of the agent.""" upgrade_item(ctx, CONNECTION, connection_public_id) @upgrade.command() @click.argument("contract_public_id", type=PublicIdParameter(), required=True) @pass_ctx def contract(ctx: Context, contract_public_id: PublicId) -> None: """Upgrade a contract of the agent.""" upgrade_item(ctx, CONTRACT, contract_public_id) @upgrade.command() @click.argument("protocol_public_id", type=PublicIdParameter(), required=True) @pass_ctx def protocol(ctx: Context, protocol_public_id: PublicId) -> None: """Upgrade a protocol of the agent.""" upgrade_item(ctx, PROTOCOL, protocol_public_id) @upgrade.command() @click.argument("skill_public_id", type=PublicIdParameter(), required=True) @pass_ctx def skill(ctx: Context, skill_public_id: PublicId) -> None: """Upgrade a skill of the agent.""" upgrade_item(ctx, SKILL, skill_public_id) def update_agent_config(ctx: Context) -> None: """ Update agent configurations. In particular: - update aea_version in case current framework version is different - update author name if it is different :param ctx: the context. """ update_aea_version_range(ctx.agent_config) cli_author = ctx.config.get("cli_author") if cli_author and ctx.agent_config.author != cli_author: click.echo(f"Updating author from {ctx.agent_config.author} to {cli_author}") ctx.agent_config._author = cli_author # pylint: disable=protected-access ctx.agent_config.version = DEFAULT_VERSION ctx.dump_agent_config() def update_aea_version_in_nonvendor_packages(cwd: str) -> None: """ Update aea_version in non-vendor packages. :param cwd: the current working directory. """ for package_path in get_non_vendor_package_path(Path(cwd)): package_type = PackageType(package_path.parent.name[:-1]) package_config = load_item_config(package_type.value, package_path) update_aea_version_range(package_config) dump_item_config(package_config, package_path) @clean_after def upgrade_project(ctx: Context) -> None: # pylint: disable=unused-argument """Perform project upgrade.""" click.echo("Starting project upgrade...") yes_by_default = ctx.config.get("yes_by_default", False) # check if there is a newer version of the same project project_upgrader = ProjectUpgrader(ctx, yes_by_default=yes_by_default) if project_upgrader.upgrade(): click.echo("Upgrade completed.") return old_component_ids = ctx.agent_config.package_dependencies item_remover = ItemRemoveHelper(ctx, ignore_non_vendor=True) agent_items = item_remover.get_agent_dependencies_with_reverse_dependencies() eject_helper = InteractiveEjectHelper( ctx, agent_items, yes_by_default=yes_by_default ) eject_helper.get_latest_versions() if len(eject_helper.item_to_new_version) == 0: click.echo("Everything is already up to date!") return if not eject_helper.can_eject(): click.echo("Abort.") return eject_helper.eject() update_agent_config(ctx) update_aea_version_in_nonvendor_packages(ctx.cwd) # compute the upgraders and the shared dependencies. required_by_relation = eject_helper.get_updated_inverse_adjacency_list() item_to_new_version = eject_helper.item_to_new_version upgraders, shared_deps_to_remove = _compute_upgraders_and_shared_deps_to_remove( ctx, required_by_relation, item_to_new_version ) with remove_unused_component_configurations(ctx): if shared_deps_to_remove: for dep in shared_deps_to_remove: # pragma: nocover if ItemUpgrader( ctx, str(dep.package_type), dep.public_id ).is_non_vendor: # non vendor package, do not remove! continue click.echo( f"Removing shared dependency {str(dep.package_type)} '{dep.public_id}'..." ) RemoveItem( ctx, str(dep.package_type), dep.public_id, with_dependencies=False, force=True, ).remove_item() click.echo( f"Successfully removed {str(dep.package_type)} '{dep.public_id}'." ) for upgrader in upgraders.values(): upgrader.remove_item() upgrader.add_item() # load new package dependencies replacements = _compute_replacements(ctx, old_component_ids) update_references(ctx, replacements) click.echo("Finished project upgrade. Everything is up to date now!") class UpgraderException(Exception): """Base upgrader exception.""" class NotAddedException(UpgraderException): """Item was not found in agent cause not added.""" class AlreadyActualVersionException(UpgraderException): """Actual version already installed.""" def __init__(self, version: str) -> None: """Init exception.""" super().__init__(version) self.version = version class IsRequiredException(UpgraderException): """Package can not be upgraded cause required by another.""" def __init__(self, required_by: Iterable[PackageId]) -> None: """Init exception.""" super().__init__(required_by) self.required_by = required_by class ProjectUpgrader: """Helper class to upgrade agent project if was previously fetched from registry.""" _TEMP_ALIAS = "fetched_agent" def __init__(self, ctx: Context, yes_by_default: bool = False) -> None: """Initialize the class.""" self.ctx = ctx self.yes_by_default = yes_by_default self._current_aea_version = aea.__version__ def upgrade(self) -> bool: """ Upgrade the project by fetching from remote registry. :return: True if the upgrade succeeded, False otherwise. """ agent_config = self.ctx.agent_config agent_package_id = agent_config.package_id click.echo( f"Checking if there is a newer remote version of agent package '{agent_package_id.public_id}'..." ) try: new_item = get_latest_version_available_in_registry( self.ctx, str(agent_package_id.package_type), agent_package_id.public_id.to_latest(), aea_version=self._current_aea_version, ) except click.ClickException: click.echo("Package not found, continuing with normal upgrade.") return False if new_item.package_version <= agent_config.public_id.package_version: # type: ignore click.echo( f"Latest version found is '{new_item.version}' which is smaller or equal than current version '{agent_config.public_id.package_version}'. Continuing..." ) return False current_path = Path(self.ctx.cwd).absolute() user_wants_to_upgrade = self._ask_user_if_wants_to_upgrade( new_item, current_path ) if not user_wants_to_upgrade: return False click.echo(f"Upgrading project to version '{new_item.version}'") try: delete_directory_contents(current_path) except OSError as e: # pragma: nocover raise click.ClickException( f"Cannot remote path {current_path}. Error: {str(e)}." ) fetch_agent(self.ctx, new_item, alias=self._TEMP_ALIAS) self.ctx.cwd = str(current_path) self._unpack_fetched_agent() return True def _unpack_fetched_agent(self) -> None: """Unpack fetched agent in current directory and remove temporary directory.""" current_path = Path(self.ctx.cwd) fetched_agent_dir = current_path / self._TEMP_ALIAS for subpath in fetched_agent_dir.iterdir(): shutil.move(str(subpath), current_path) shutil.rmtree(str(fetched_agent_dir)) def _ask_user_if_wants_to_upgrade( self, new_item: PublicId, current_path: Path ) -> bool: """ Ask if the user wants to upgrade the project. :param new_item: the public id of the new item. :param current_path: the current path. :return: the user's answer (a boolean). """ message = ( f"Found a newer version of this project: {new_item.package_version}. " f"Would you like to replace this project with it? \n" f"Warning: the content in the current directory {current_path} will be removed" ) return _try_to_confirm(message, self.yes_by_default) class ItemUpgrader: """Tool to upgrade agent's item .""" def __init__(self, ctx: Context, item_type: str, item_public_id: PublicId) -> None: """ Init item upgrader. :param ctx: context :param item_type: str, type of the package :param item_public_id: item to upgrade. """ self.ctx = ctx self.ctx.set_config("with_dependencies", True) self.item_type = item_type self.item_public_id = item_public_id self.component_id = ComponentId(self.item_type, self.item_public_id) self.current_item_public_id = self.get_current_item() ( self.in_requirements, self.deps_can_be_removed, self.deps_can_not_be_removed, ) = self.get_dependencies() self.dependencies: Set[PackageId] = set() self.dependencies.update(self.deps_can_be_removed) self.dependencies.update(self.deps_can_not_be_removed) self._current_aea_version = aea.__version__ def get_current_item(self) -> PublicId: """Return public id of the item already presents in agent config.""" self.check_item_present() current_item = get_item_public_id_by_author_name( self.ctx.agent_config, self.item_type, self.item_public_id.author, self.item_public_id.name, ) if not current_item: # pragma: nocover # actually checked in check_item_present raise ValueError("Item not found!") return current_item def check_item_present(self) -> None: """Check item going to be upgraded already registered in agent.""" if not is_item_present( self.ctx.cwd, self.ctx.agent_config, self.item_type, self.item_public_id ) and not is_item_present( self.ctx.cwd, self.ctx.agent_config, self.item_type, self.item_public_id, is_vendor=False, ): raise NotAddedException() def get_dependencies( self, ) -> Tuple[Set[PackageId], Set[PackageId], Dict[PackageId, Set[PackageId]]]: """ Return dependency details for item. :return: same as for ItemRemoveHelper.check_remove """ return ItemRemoveHelper(self.ctx, ignore_non_vendor=True).check_remove( self.item_type, self.current_item_public_id ) @property def is_non_vendor(self) -> bool: """Check is package specified is non vendor.""" path = ItemRemoveHelper.get_component_directory( PackageId(self.item_type, self.item_public_id) ) return VENDOR not in Path(path).parts[:2] def check_in_requirements(self) -> None: """Check if we are trying to upgrade some component dependency.""" if self.in_requirements: raise IsRequiredException(self.in_requirements) def check_is_non_vendor(self) -> None: """Check the package is not a vendor package.""" if self.is_non_vendor: raise AlreadyActualVersionException(self.current_item_public_id.version) def check_not_at_latest_version(self) -> str: """ Check the package is not at the actual version. :return: the version number. """ if self.item_public_id.version != "latest": new_item = self.item_public_id else: new_item = get_latest_version_available_in_registry( self.ctx, self.item_type, self.item_public_id, aea_version=self._current_aea_version, ) if self.current_item_public_id.version == new_item.version: raise AlreadyActualVersionException(new_item.version) return new_item.version def check_upgrade_is_required(self) -> str: """ Check upgrade is required otherwise raise UpgraderException. :return: new version of the package. """ self.check_in_requirements() self.check_is_non_vendor() return self.check_not_at_latest_version() def remove_item(self) -> None: """Remove item from agent.""" remove_item = RemoveItem( self.ctx, self.item_type, self.item_public_id, with_dependencies=True, force=True, ignore_non_vendor=True, ) remove_item.remove() def add_item(self) -> None: """Add new package version to agent.""" add_item(self.ctx, str(self.item_type), self.item_public_id) class InteractiveEjectHelper: """ Helper class to interactively eject vendor packages. This is needed in the cases in which a vendor package prevents other packages to be upgraded. """ def __init__( self, ctx: Context, inverse_adjacency_list: Dict[PackageId, Set[PackageId]], yes_by_default: bool = False, ) -> None: """ Initialize the class. :param ctx: the CLI context. :param inverse_adjacency_list: adjacency list of inverse dependency graph. :param yes_by_default: if True, never ask the user for confirmation. """ self.ctx = ctx self.inverse_adjacency_list = deepcopy(inverse_adjacency_list) self.adjacency_list = self._reverse_adjacency_list(self.inverse_adjacency_list) self.yes_by_default = yes_by_default self._current_aea_version = aea.__version__ self.to_eject: List[PackageId] = [] self.item_to_new_version: Dict[PackageId, str] = {} def get_latest_versions(self) -> None: """ Get latest versions for every project package. Stores the result in 'item_to_new_version'. """ for package_id in self.adjacency_list.keys(): try: new_item = get_latest_version_available_in_registry( self.ctx, str(package_id.package_type), package_id.public_id.to_latest(), aea_version=self._current_aea_version, ) except click.ClickException: # pragma: nocover continue if package_id.public_id.version == new_item.version: continue new_version = new_item.version self.item_to_new_version[package_id] = new_version @staticmethod def _reverse_adjacency_list( adjacency_list: Dict[PackageId, Set[PackageId]] ) -> Dict[PackageId, Set[PackageId]]: """Compute the inverse of an adjacency list.""" inverse_adjacency_list: Dict[PackageId, Set[PackageId]] = {} for v, neighbors in adjacency_list.items(): inverse_adjacency_list.setdefault(v, set()) for u in neighbors: inverse_adjacency_list.setdefault(u, set()).add(v) return inverse_adjacency_list def eject(self) -> None: """Eject packages.""" for package_id in self.to_eject: click.echo(f"Ejecting {package_id}...") _eject_item(self.ctx, str(package_id.package_type), package_id.public_id) def get_updated_inverse_adjacency_list(self) -> Dict[PackageId, Set[PackageId]]: """Update inverse adjacency list by removing ejected packages.""" result = {} for package_id, deps in self.inverse_adjacency_list.items(): if package_id in self.to_eject: continue result[package_id] = deps.difference(self.to_eject) return result def can_eject(self) -> bool: """Ask to the user if packages can be ejected if needed.""" to_upgrade = set(self.item_to_new_version.keys()) order = find_topological_order(self.adjacency_list) for package_id in order: if package_id in self.item_to_new_version: # if dependency is going to be upgraded, # no need to do anything continue depends_on = self.adjacency_list[package_id] dependencies_to_upgrade = depends_on.intersection(to_upgrade) if len(dependencies_to_upgrade) == 0: # if dependencies of the package are not going to be upgraded, # no need to worry about its ejection. continue # pragma: nocover # if we are here, it means we need to eject the package. answer = self._prompt(package_id, dependencies_to_upgrade) should_eject = answer if not should_eject: return False click.echo(f"Package '{package_id}' scheduled for ejection.") self.to_eject.append(package_id) return True def _prompt( self, package_id: PackageId, dependencies_to_upgrade: Set[PackageId] ) -> bool: """ Ask the user permission for ejection of a package. :param package_id: the package id. :param dependencies_to_upgrade: the dependencies to upgrade. :return: True or False, depending on the answer of the user. """ package_type = str(package_id.package_type).capitalize() message = ( f"{package_type} {package_id.public_id} prevents the upgrade of " f"the following vendor packages:\n" f"{pprint.pformat(dependencies_to_upgrade)}\n" f"as there isn't a compatible version available on the AEA registry. " f"Would you like to eject it?" ) return _try_to_confirm(message, self.yes_by_default) def _try_to_confirm(message: str, yes_by_default: bool) -> bool: """ Try to prompt a question to the user. The actual effect of this function will be determined by "yes_by_default". In particular: - if "yes_by_default" is True, never prompt and return True. - if "yes_by_default" is False, ask to the user. :param message: the message :param yes_by_default: bool to override confirm :return: result """ return click.confirm(message) if not yes_by_default else True @clean_after def upgrade_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: """ Upgrade an item. :param ctx: Context object. :param item_type: the item type. :param item_public_id: the item public id. """ try: with remove_unused_component_configurations(ctx): old_component_ids = ctx.agent_config.package_dependencies item_upgrader = ItemUpgrader(ctx, item_type, item_public_id) click.echo( f"Upgrading {item_type} '{item_public_id.author}/{item_public_id.name}' from version '{item_upgrader.current_item_public_id.version}' to '{item_public_id.version}' for the agent '{ctx.agent_config.agent_name}'..." ) version = item_upgrader.check_upgrade_is_required() item_upgrader.remove_item() item_upgrader.add_item() replacements = _compute_replacements(ctx, old_component_ids) update_references(ctx, replacements) click.echo( f"The {item_type} '{item_public_id.author}/{item_public_id.name}' for the agent '{ctx.agent_config.agent_name}' has been successfully upgraded from version '{item_upgrader.current_item_public_id.version}' to '{version}'." ) except NotAddedException: raise click.ClickException( f"A {item_type} with id '{item_public_id.author}/{item_public_id.name}' is not registered. Please use the `add` command. Aborting..." ) except AlreadyActualVersionException as e: raise click.ClickException( f"The {item_type} with id '{item_public_id.author}/{item_public_id.name}' already has version '{e.version}'. Nothing to upgrade." ) except IsRequiredException as e: raise click.ClickException( f"Can not upgrade {item_type} '{item_public_id.author}/{item_public_id.name}' because it is required by '{', '.join(map(str, e.required_by))}'" ) def _compute_replacements( ctx: Context, old_component_ids: Set[ComponentId] ) -> Dict[ComponentId, ComponentId]: """Compute replacements from old component ids to new components ids.""" agent_config = load_item_config(PackageType.AGENT.value, Path(ctx.cwd)) new_component_ids = list(agent_config.package_dependencies) replacements: Dict[ComponentId, ComponentId] = {} for old_component_id in old_component_ids: same_prefix = list(filter(old_component_id.same_prefix, new_component_ids)) enforce(len(same_prefix) < 2, "More than one component id found.") if len(same_prefix) > 0: replacements[old_component_id] = same_prefix[0] return replacements def _compute_upgraders_and_shared_deps_to_remove( ctx: Context, required_by_relation: Dict[PackageId, Set[PackageId]], item_to_new_version: Dict[PackageId, str], ) -> Tuple[Dict[PackageId, ItemUpgrader], Set[PackageId]]: """ Compute upgraders and shared dependencies to remove. :param ctx: the CLI Context :param required_by_relation: from a package id to the package ids it is required by. :param item_to_new_version: from a package id to its new version available. :return: the list of upgraders and the shared dependencies to remove. """ upgraders: Dict[PackageId, ItemUpgrader] = {} shared_deps: Set[PackageId] = set() shared_deps_to_remove = set() items_to_upgrade_dependencies = set() for package_id, required_by in required_by_relation.items(): item_upgrader = ItemUpgrader( ctx, str(package_id.package_type), package_id.public_id.to_latest() ) # if the package is required by at least another package, don't upgrade. is_required_by_other = len(required_by) != 0 if is_required_by_other: continue is_not_in_requirements = not item_upgrader.in_requirements is_vendor = not item_upgrader.is_non_vendor to_be_upgraded = package_id in item_to_new_version if is_not_in_requirements and is_vendor and to_be_upgraded: upgraders[package_id] = item_upgrader items_to_upgrade_dependencies.add(package_id) items_to_upgrade_dependencies.update(item_upgrader.dependencies) shared_deps.update(item_upgrader.deps_can_not_be_removed.keys()) for dep in shared_deps: if required_by_relation[dep] - items_to_upgrade_dependencies: # shared dependencies not resolved, nothing to do next continue # pragma: nocover # add it to remove shared_deps_to_remove.add(dep) return upgraders, shared_deps_to_remove ================================================ FILE: aea/cli/utils/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """A module with utils of the aea cli.""" ================================================ FILE: aea/cli/utils/click_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with click utils of the aea cli.""" import os from collections import OrderedDict from pathlib import Path from typing import Any, Callable, List, Mapping, Optional, Tuple import click from click import Context, Option, UsageError, option from aea.cli.utils.config import try_to_load_agent_config from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE from aea.helpers.io import open_file class ConnectionsOption(click.Option): """Click option for the --connections option in 'aea run'.""" def type_cast_value( self, ctx: click.Context, value: str ) -> Optional[List[PublicId]]: """ Parse the list of string passed through command line. E.g. from 'stub,local' to ['stub', 'local']. :param ctx: the click context :param value: the list of connection names, as a string. :return: list of public ids """ if value is None: return None try: def arg_strip(s: str) -> str: return s.strip(" '\"") input_connection_ids = [ arg_strip(s) for s in value.split(",") if arg_strip(s) != "" ] # remove duplicates, while preserving the order result = OrderedDict() # type: OrderedDict[PublicId, None] for connection_id_string in input_connection_ids: connection_public_id = PublicId.from_str(connection_id_string) result[connection_public_id] = None return list(result.keys()) except Exception: # pragma: no cover raise click.BadParameter(value) class PublicIdParameter(click.ParamType): """Define a public id parameter for Click applications.""" def __init__( # pylint: disable=useless-super-delegation self, *args: Any, **kwargs: Any ) -> None: """ Initialize the Public Id parameter. Just forwards arguments to parent constructor. :param args: positional arguments :param kwargs: keyword arguments """ super().__init__(*args, **kwargs) # type: ignore def get_metavar(self, param: Any) -> str: """Return the metavar default for this param if it provides one.""" return "PUBLIC_ID" def convert( # pylint: disable=inconsistent-return-statements self, value: str, param: Any, ctx: Optional[click.Context] ) -> PublicId: """Convert the value. This is not invoked for values that are `None` (the missing value).""" try: return PublicId.from_str(value) except ValueError: self.fail(value, param, ctx) class AgentDirectory(click.Path): """A click.Path, but with further checks applications.""" def __init__(self) -> None: """Initialize the agent directory parameter.""" super().__init__( exists=True, file_okay=False, dir_okay=True, readable=True, writable=False ) def get_metavar(self, param: Any) -> str: """Return the metavar default for this param if it provides one.""" return "AGENT_DIRECTORY" # pragma: no cover def convert(self, value: str, param: Any, ctx: click.Context) -> str: # type: ignore """Convert the value. This is not invoked for values that are `None` (the missing value).""" cwd = os.getcwd() path = Path(value) try: # check that the target folder is an AEA project. os.chdir(path) with open_file(DEFAULT_AEA_CONFIG_FILE, mode="r", encoding="utf-8") as fp: ctx.obj.agent_config = ctx.obj.agent_loader.load(fp) try_to_load_agent_config(ctx.obj) # everything ok - return the parameter to the command return value except Exception: raise click.ClickException( "The name provided is not a path to an AEA project." ) finally: os.chdir(cwd) def registry_flag( help_local: str = "Use only local registry.", help_remote: str = "Use ony remote registry.", ) -> Callable: """Choice of one flag between: '--local/--remote'.""" def wrapper(f: Callable) -> Callable: f = option( "--local", is_flag=True, cls=MutuallyExclusiveOption, help=help_local, mutually_exclusive=["remote"], )(f) f = option( "--remote", is_flag=True, cls=MutuallyExclusiveOption, help=help_remote, mutually_exclusive=["local"], )(f) return f return wrapper def registry_path_option(f: Callable) -> Callable: """Add registry path aea option.""" return option( "--registry-path", type=click.Path(dir_okay=True, exists=True, file_okay=False), required=False, help="Provide a local registry directory full path.", )(f) class MutuallyExclusiveOption(Option): """Represent a mutually exclusive option.""" def __init__(self, *args: Any, **kwargs: Any) -> None: """Initialize the option.""" self.mutually_exclusive = set(kwargs.pop("mutually_exclusive", [])) help_ = kwargs.get("help", "") if self.mutually_exclusive: ex_str = ", ".join(self.mutually_exclusive) kwargs["help"] = help_ + ( " NOTE: This argument is mutually exclusive with " " arguments: [" + ex_str + "]." ) super().__init__(*args, **kwargs) def handle_parse_result( self, ctx: Context, opts: Mapping[str, Any], args: List[Any] ) -> Tuple[Any, List[str]]: """ Handle parse result. :param ctx: the click context. :param opts: the options. :param args: the list of arguments (to be forwarded to the parent class). :return: tuple of results """ if self.mutually_exclusive.intersection(opts) and self.name in opts: raise UsageError( f"Illegal usage: `{self.name}` is mutually exclusive with " f"arguments `{', '.join(self.mutually_exclusive)}`." ) return super().handle_parse_result(ctx, opts, args) # type: ignore def password_option(confirmation_prompt: bool = False, **kwargs) -> Callable: # type: ignore """Decorator to ask for input if -p flag was provided or use --password to set password value in command line.""" def callback(ctx, _, value: bool) -> bool: # type: ignore if value is True: ctx.params["password"] = ctx.params.get("password") or click.prompt( "Enter password", hide_input=True, confirmation_prompt=confirmation_prompt, ) return value def wrap(fn): # type: ignore return click.option( "-p", is_flag=True, type=bool, callback=callback, expose_value=False, help="Ask for password interactively", )( click.option( "--password", type=str, is_eager=True, metavar="PASSWORD", help="Set password for key encryption/decryption", **kwargs, )(fn) ) # type: ignore return wrap ================================================ FILE: aea/cli/utils/config.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """A module with config tools of the aea cli.""" import os from pathlib import Path from typing import Any, Dict, Optional, Set import click import jsonschema import yaml from aea.cli.utils.constants import AUTHOR_KEY, CLI_CONFIG_PATH from aea.cli.utils.context import Context from aea.cli.utils.exceptions import AEAConfigException from aea.cli.utils.generic import load_yaml from aea.configurations.base import ( ComponentType, PackageConfiguration, PackageType, _get_default_configuration_file_name_from_type, ) from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE, REGISTRY_PATH_KEY from aea.configurations.loader import ConfigLoader, ConfigLoaders from aea.configurations.validation import ExtraPropertiesError from aea.exceptions import AEAEnforceError, AEAValidationError from aea.helpers.io import open_file def try_to_load_agent_config( ctx: Context, is_exit_on_except: bool = True, agent_src_path: str = None ) -> None: """ Load agent config to a click context object. :param ctx: click command context object. :param is_exit_on_except: bool option to exit on exception (default = True). :param agent_src_path: path to an agent dir if needed to load a custom config. """ if agent_src_path is None: agent_src_path = ctx.cwd try: path = Path(os.path.join(agent_src_path, DEFAULT_AEA_CONFIG_FILE)) with open_file(path, mode="r", encoding="utf-8") as fp: ctx.agent_config = ctx.agent_loader.load(fp) ctx.agent_config.directory = Path(agent_src_path) except FileNotFoundError: if is_exit_on_except: raise click.ClickException( "Agent configuration file '{}' not found in the current directory.".format( DEFAULT_AEA_CONFIG_FILE ) ) except ( jsonschema.exceptions.ValidationError, ExtraPropertiesError, AEAValidationError, ) as e: if is_exit_on_except: raise click.ClickException( "Agent configuration file '{}' is invalid: `{}`. Please check the documentation.".format( DEFAULT_AEA_CONFIG_FILE, str(e) ) ) except AEAEnforceError as e: raise click.ClickException(str(e)) # pragma: nocover def _init_cli_config() -> None: """Create cli config folder and file.""" conf_dir = os.path.dirname(CLI_CONFIG_PATH) if not os.path.exists(conf_dir): os.makedirs(conf_dir) with open_file(CLI_CONFIG_PATH, "w+") as f: yaml.dump({}, f, default_flow_style=False) def update_cli_config(dict_conf: Dict) -> None: """ Update CLI config and write to yaml file. :param dict_conf: dict config to write. """ config = get_or_create_cli_config() config.update(dict_conf) with open_file(CLI_CONFIG_PATH, "w") as f: yaml.dump(config, f, default_flow_style=False) def get_or_create_cli_config() -> Dict: """ Read or create CLI config from yaml file. :return: dict CLI config. """ try: return load_yaml(CLI_CONFIG_PATH) except FileNotFoundError: _init_cli_config() return load_yaml(CLI_CONFIG_PATH) def set_cli_author(click_context: click.Context) -> None: """ Set CLI author in the CLI Context. The key of the new field is 'cli_author'. :param click_context: the Click context """ config = get_or_create_cli_config() cli_author = config.get(AUTHOR_KEY, None) if cli_author is None: raise click.ClickException( "The AEA configurations are not initialized. Use `aea init` before continuing." ) click_context.obj.set_config("cli_author", cli_author) def get_registry_path_from_cli_config() -> Optional[str]: """Get registry path from config.""" config = get_or_create_cli_config() return config.get(REGISTRY_PATH_KEY, None) def load_item_config(item_type: str, package_path: Path) -> PackageConfiguration: """ Load item configuration. :param item_type: type of item. :param package_path: path to package from which config should be loaded. :return: configuration object. """ configuration_file_name = _get_default_configuration_file_name_from_type(item_type) configuration_path = package_path / configuration_file_name configuration_loader = ConfigLoader.from_configuration_type(PackageType(item_type)) with open_file(configuration_path) as file_input: item_config = configuration_loader.load(file_input) return item_config def dump_item_config( package_configuration: PackageConfiguration, package_path: Path ) -> None: """ Dump item configuration. :param package_configuration: the package configuration. :param package_path: path to package from which config should be dumped. """ configuration_file_name = _get_default_configuration_file_name_from_type( package_configuration.package_type ) configuration_path = package_path / configuration_file_name configuration_loader = ConfigLoader.from_configuration_type( package_configuration.package_type ) with configuration_path.open("w") as file_output: configuration_loader.dump(package_configuration, file_output) # type: ignore def update_item_config(item_type: str, package_path: Path, **kwargs: Any) -> None: """ Update item config and item config file. :param item_type: type of item. :param package_path: path to a package folder. :param kwargs: pairs of config key-value to update. """ item_config = load_item_config(item_type, package_path) for key, value in kwargs.items(): setattr(item_config, key, value) config_filepath = os.path.join( package_path, item_config.default_configuration_filename ) loader = ConfigLoaders.from_package_type(item_type) with open_file(config_filepath, "w") as f: loader.dump(item_config, f) def validate_item_config(item_type: str, package_path: Path) -> None: """ Validate item configuration. :param item_type: type of item. :param package_path: path to a package folder. :raises AEAConfigException: if something is wrong with item configuration. """ item_config = load_item_config(item_type, package_path) loader = ConfigLoaders.from_package_type(item_type) for field_name in loader.required_fields: try: getattr(item_config, field_name) except AttributeError: raise AEAConfigException( "Parameter '{}' is missing from {} config.".format( field_name, item_type ) ) def get_non_vendor_package_path(aea_project_path: Path) -> Set[Path]: """ Get all the paths to non-vendor packages. :param aea_project_path: the path to an AEA project. :return: the set of paths, one for each non-vendor package configuration file. """ result: Set[Path] = set() for item_type_plural in ComponentType.plurals(): nonvendor_package_dir_of_type = aea_project_path / item_type_plural result = result.union( {p for p in nonvendor_package_dir_of_type.iterdir() if p.is_dir()} if nonvendor_package_dir_of_type.exists() else {} ) return result ================================================ FILE: aea/cli/utils/constants.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with constants of the aea cli.""" import os from pathlib import Path from aea.configurations.constants import ( CONNECTION, CONNECTIONS, CONTRACT, CONTRACTS, PACKAGES, PROTOCOL, PROTOCOLS, SKILL, SKILLS, VENDOR, ) from aea.helpers.constants import FROM_STRING_TO_TYPE AEA_DIR = str(Path(".")) ITEM_TYPES = (CONNECTION, CONTRACT, PROTOCOL, SKILL) AEA_LOGO = " _ _____ _ \r\n / \\ | ____| / \\ \r\n / _ \\ | _| / _ \\ \r\n / ___ \\ | |___ / ___ \\ \r\n/_/ \\_\\|_____|/_/ \\_\\\r\n \r\n" AUTHOR_KEY = "author" CLI_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".aea", "cli_config.yaml") NOT_PERMITTED_AUTHORS = [ CONNECTIONS, CONTRACTS, PROTOCOLS, SKILLS, VENDOR, PACKAGES, "aea", ] CONFIG_SUPPORTED_KEY_TYPES = list(FROM_STRING_TO_TYPE.keys()) REQUIREMENTS = "requirements.txt" STUB_CONNECTION = "fetchai/stub:latest" ================================================ FILE: aea/cli/utils/context.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """A module with context tools of the aea cli.""" import os from pathlib import Path from typing import Any, Dict, List, Optional, cast from aea.cli.utils.loggers import logger from aea.configurations.base import ( AgentConfig, Dependencies, PackageType, PublicId, _get_default_configuration_file_name_from_type, ) from aea.configurations.constants import ( CONNECTION, CONTRACT, DEFAULT_AEA_CONFIG_FILE, DEFAULT_REGISTRY_NAME, PROTOCOL, SKILL, VENDOR, ) from aea.configurations.loader import ConfigLoader from aea.configurations.pypi import merge_dependencies_list from aea.helpers.io import open_file class Context: """A class to keep configuration of the cli tool.""" agent_config: AgentConfig def __init__(self, cwd: str, verbosity: str, registry_path: Optional[str]) -> None: """Init the context.""" self.config = {} # type: Dict self.cwd = cwd self.verbosity = verbosity self.clean_paths: List = [] self._registry_path = registry_path @property def registry_path(self) -> str: """Get registry path specified or from config or default one with check is it present.""" # registry path is provided or in config or default if self._registry_path: registry_path = Path(self._registry_path) if not (registry_path.exists() and registry_path.is_dir()): raise ValueError( f"Registry path directory provided ({self._registry_path}) can not be found. Current work dir is {self.cwd}" ) return str(registry_path) registry_path = (Path(self.cwd) / DEFAULT_REGISTRY_NAME).absolute() if registry_path.is_dir(): return str(registry_path) registry_path = (Path(self.cwd) / ".." / DEFAULT_REGISTRY_NAME).absolute() if registry_path.is_dir(): return str(registry_path) raise ValueError( f"Registry path not provided and local registry `{DEFAULT_REGISTRY_NAME}` not found in current ({self.cwd}) and parent directory." ) @property def skip_aea_validation(self) -> bool: """ Get the 'skip_aea_validation' flag. If true, validation of the AEA version for loaded configuration file is skipped. :return: the 'skip_aea_validation' """ return self.config.get("skip_aea_validation", True) @property def agent_loader(self) -> ConfigLoader: """Get the agent loader.""" return ConfigLoader.from_configuration_type( PackageType.AGENT, skip_aea_validation=self.skip_aea_validation ) @property def protocol_loader(self) -> ConfigLoader: """Get the protocol loader.""" return ConfigLoader.from_configuration_type( PackageType.PROTOCOL, skip_aea_validation=self.skip_aea_validation ) @property def connection_loader(self) -> ConfigLoader: """Get the connection loader.""" return ConfigLoader.from_configuration_type( PackageType.CONNECTION, skip_aea_validation=self.skip_aea_validation ) @property def skill_loader(self) -> ConfigLoader: """Get the skill loader.""" return ConfigLoader.from_configuration_type( PackageType.SKILL, skip_aea_validation=self.skip_aea_validation ) @property def contract_loader(self) -> ConfigLoader: """Get the contract loader.""" return ConfigLoader.from_configuration_type( PackageType.CONTRACT, skip_aea_validation=self.skip_aea_validation ) def set_config(self, key: str, value: Any) -> None: """ Set a config. :param key: the key for the configuration. :param value: the value associated with the key. """ self.config[key] = value logger.debug(" config[{}] = {}".format(key, value)) @staticmethod def _get_item_dependencies(item_type: str, public_id: PublicId) -> Dependencies: """Get the dependencies from item type and public id.""" item_type_plural = item_type + "s" default_config_file_name = _get_default_configuration_file_name_from_type( item_type ) path = Path( VENDOR, public_id.author, item_type_plural, public_id.name, default_config_file_name, ) if not path.exists(): path = Path(item_type_plural, public_id.name, default_config_file_name) config_loader = ConfigLoader.from_configuration_type(item_type) with open_file(path) as fp: config = config_loader.load(fp) deps = cast(Dependencies, config.dependencies) return deps def get_dependencies(self) -> Dependencies: """ Aggregate the dependencies from every component. :return: a list of dependency version specification. e.g. ["gym >= 1.0.0"] """ protocol_dependencies = [ self._get_item_dependencies(PROTOCOL, protocol_id) for protocol_id in self.agent_config.protocols ] connection_dependencies = [ self._get_item_dependencies(CONNECTION, connection_id) for connection_id in self.agent_config.connections ] skill_dependencies = [ self._get_item_dependencies(SKILL, skill_id) for skill_id in self.agent_config.skills ] contract_dependencies = [ self._get_item_dependencies(CONTRACT, contract_id) for contract_id in self.agent_config.contracts ] all_dependencies = [ self.agent_config.dependencies, *protocol_dependencies, *connection_dependencies, *skill_dependencies, *contract_dependencies, ] result = merge_dependencies_list(*all_dependencies) return result def dump_agent_config(self) -> None: """Dump the current agent configuration.""" with open( os.path.join(self.cwd, DEFAULT_AEA_CONFIG_FILE), "w", encoding="utf-8" ) as f: self.agent_loader.dump(self.agent_config, f) ================================================ FILE: aea/cli/utils/decorators.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with decorators of the aea cli.""" import os import shutil from functools import update_wrapper from pathlib import Path from typing import Any, Callable, Dict, Tuple, Union, cast import click from jsonschema import ValidationError from aea.cli.utils.config import try_to_load_agent_config from aea.cli.utils.context import Context from aea.configurations.base import ( PackageType, PublicId, _check_aea_version, _compare_fingerprints, _get_default_configuration_file_name_from_type, ) from aea.configurations.constants import VENDOR from aea.configurations.loader import ConfigLoaders from aea.exceptions import AEAException, AEAValidationError, enforce from aea.helpers.base import decorator_with_optional_params from aea.helpers.io import open_file pass_ctx = click.make_pass_decorator(Context) def _validate_config_consistency(ctx: Context, check_aea_version: bool = True) -> None: """ Validate fingerprints for every agent component. :param ctx: the context :param check_aea_version: whether it should check also the AEA version. :raises ValueError: if there is a missing configuration file. or if the configuration file is not valid. or if the fingerprints do not match """ if check_aea_version: _check_aea_version(ctx.agent_config) packages_public_ids_to_types = dict( [ *map(lambda x: (x, PackageType.PROTOCOL), ctx.agent_config.protocols), *map( lambda x: (x, PackageType.CONNECTION), ctx.agent_config.connections, ), *map(lambda x: (x, PackageType.SKILL), ctx.agent_config.skills), *map(lambda x: (x, PackageType.CONTRACT), ctx.agent_config.contracts), ] ) # type: Dict[PublicId, PackageType] for public_id, item_type in packages_public_ids_to_types.items(): # find the configuration file. try: # either in vendor/ or in personal packages. # we give precedence to custom agent components (i.e. not vendorized). package_directory = Path(item_type.to_plural(), public_id.name) is_vendor = False if not package_directory.exists(): package_directory = Path( VENDOR, public_id.author, item_type.to_plural(), public_id.name ) is_vendor = True # we fail if none of the two alternative works. enforce(package_directory.exists(), "Package directory does not exist!") loader = ConfigLoaders.from_package_type(item_type) config_file_name = _get_default_configuration_file_name_from_type(item_type) configuration_file_path = package_directory / config_file_name enforce( configuration_file_path.exists(), "Configuration file path does not exist!", ) except Exception: raise ValueError("Cannot find {}: '{}'".format(item_type.value, public_id)) # load the configuration file. try: with open_file(configuration_file_path, "r") as fp: package_configuration = loader.load(fp) except (AEAValidationError, ValidationError) as e: raise ValueError( "{} configuration file '{}' not valid: {}".format( item_type.value.capitalize(), configuration_file_path, str(e) ) ) if check_aea_version: _check_aea_version(package_configuration) _compare_fingerprints( package_configuration, package_directory, is_vendor, item_type ) def _check_aea_project( args: Tuple[Any, ...], check_aea_version: bool = True, check_finger_prints: bool = False, ) -> None: try: click_context = args[0] ctx = cast(Context, click_context.obj) try_to_load_agent_config(ctx) skip_consistency_check = ctx.config["skip_consistency_check"] if not skip_consistency_check: _validate_config_consistency(ctx, check_aea_version=check_aea_version) if check_finger_prints: _compare_fingerprints( ctx.agent_config, Path(ctx.cwd), is_vendor=False, item_type=PackageType.AGENT, is_recursive=False, ) except Exception as e: # pylint: disable=broad-except raise click.ClickException(str(e)) from e @decorator_with_optional_params def check_aea_project( f: Callable, check_aea_version: bool = True, check_finger_prints: bool = False ) -> Callable: """ Check the consistency of the project as a decorator. - try to load agent configuration file - iterate over all the agent packages and check for consistency. :param f: callable :param check_aea_version: whether or not to check aea version :param check_finger_prints: whether or not to check fingerprints :return: callable """ def wrapper(*args: Any, **kwargs: Any) -> Callable: _check_aea_project( args, check_aea_version=check_aea_version, check_finger_prints=check_finger_prints, ) return f(*args, **kwargs) return update_wrapper(wrapper, f) def _rmdirs(*paths: str) -> None: """ Remove directories. :param paths: paths to folders to remove. """ for path in paths: if os.path.exists(path): shutil.rmtree(path) def _cast_ctx(context: Union[Context, click.core.Context]) -> Context: """ Cast a Context object from context if needed. :param context: Context or click.core.Context object. :return: context object. :raises: AEAException if context is none of Context and click.core.Context types. """ if isinstance(context, Context): return context if isinstance(context, click.core.Context): # pragma: no cover return cast(Context, context.obj) raise AEAException( # pragma: no cover "clean_after decorator should be used only on methods with Context " "or click.core.Context object as a first argument." ) def clean_after(func: Callable) -> Callable: """ Decorate a function to remove created folders after ClickException raise. :param func: a method to decorate. :return: decorated method. """ def wrapper( context: Union[Context, click.core.Context], *args: Any, **kwargs: Any ) -> Callable: """ Call a source method, remove dirs listed in ctx.clean_paths if ClickException is raised. :param context: context object. :param args: positional arguments. :param kwargs: keyword arguments. :raises ClickException: if caught re-raises it. # noqa: DAR402 :return: source method output. """ ctx = _cast_ctx(context) try: return func(context, *args, **kwargs) except click.ClickException as e: _rmdirs(*ctx.clean_paths) raise e return wrapper ================================================ FILE: aea/cli/utils/exceptions.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with exceptions of the aea cli.""" from aea.exceptions import AEAException class AEAConfigException(AEAException): """Exception about AEA configuration.""" class InterruptInputException(Exception): """An exception to mark an interruption event.""" ================================================ FILE: aea/cli/utils/formatting.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with formatting utils of the aea cli.""" from typing import Dict, List from aea.configurations.base import AgentConfig from aea.configurations.loader import ConfigLoader from aea.exceptions import enforce from aea.helpers.io import open_file def format_items(items: List[Dict]) -> str: """Format list of items (protocols/connections) to a string for CLI output.""" list_str = "" for item in items: list_str += ( "{line}\n" "Public ID: {public_id}\n" "Name: {name}\n" "Description: {description}\n" "Author: {author}\n" "Version: {version}\n" "{line}\n".format( name=item["name"], public_id=item["public_id"], description=item["description"], author=item["author"], version=item["version"], line="-" * 30, ) ) return list_str def retrieve_details(name: str, loader: ConfigLoader, config_filepath: str) -> Dict: """Return description of a protocol, skill, connection.""" with open_file(str(config_filepath)) as fp: config = loader.load(fp) item_name = config.agent_name if isinstance(config, AgentConfig) else config.name enforce(item_name == name, "Item names do not match!") return { "public_id": str(config.public_id), "name": item_name, "author": config.author, "description": config.description, "version": config.version, } def sort_items(items: List[Dict]) -> List[Dict]: """ Sort a list of dict items associated with packages. :param items: list of dicts that represent items. :return: sorted list. """ return sorted(items, key=lambda k: k["name"]) ================================================ FILE: aea/cli/utils/generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with generic utils of the aea cli.""" import os from typing import Dict import yaml from click import ClickException from aea.helpers.io import open_file def load_yaml(filepath: str) -> Dict: """ Read content from yaml file. :param filepath: str path to yaml file. :return: dict YAML content """ with open_file(filepath, "r") as f: try: result = yaml.safe_load(f) return result if result is not None else {} except yaml.YAMLError as e: raise ClickException( "Loading yaml config from {} failed: {}".format(filepath, e) ) def is_readme_present(readme_path: str) -> bool: """ Check is readme file present. This method is needed for proper testing. :param readme_path: path to readme file. :return: bool is readme file present. """ return os.path.exists(readme_path) ================================================ FILE: aea/cli/utils/loggers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Helpers for the logging module.""" import logging import sys from typing import Any, Callable import click OFF = 100 logging.addLevelName(OFF, "OFF") LOG_LEVELS = ["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "OFF"] class ColorFormatter(logging.Formatter): """The default formatter for cli output.""" colors = { "error": dict(fg="red"), "exception": dict(fg="red"), "critical": dict(fg="red"), "debug": dict(fg="blue"), "info": dict(fg="green"), "warning": dict(fg="yellow"), } def format(self, record: logging.LogRecord) -> str: """Format the log message.""" if not record.exc_info: level = record.levelname.lower() msg = record.getMessage() if level in self.colors: prefix = click.style("{}: ".format(level), **self.colors[level]) # type: ignore msg = "\n".join(prefix + x for x in msg.splitlines()) return msg return logging.Formatter.format(self, record) # pragma: no cover def simple_verbosity_option( logger_: logging.Logger, *names: str, **kwargs: Any ) -> Callable: # pylint: disable=redefined-outer-name,keyword-arg-before-vararg """Add a decorator that adds a `--verbosity, -v` option to the decorated command. Name can be configured through `*names`. Keyword arguments are passed to the underlying `click.option` decorator. :param logger_: the logger :param names: list of names :param kwargs: keyword arguments :return: callable """ if not names: names = ("--verbosity", "-v") kwargs.setdefault("default", "INFO") kwargs.setdefault("type", click.Choice(LOG_LEVELS, case_sensitive=False)) kwargs.setdefault("metavar", "LVL") kwargs.setdefault("expose_value", False) kwargs.setdefault("help", "One of {}".format(", ".join(LOG_LEVELS))) kwargs.setdefault("is_eager", True) def decorator(f: Callable) -> Callable: def _set_level( ctx: click.Context, param: Any, # pylint: disable=unused-argument value: str, ) -> None: level = logging.getLevelName(value) logger_.setLevel(level) # save verbosity option so it can be # read in the main CLI entrypoint ctx.meta["verbosity"] = value return click.option(*names, callback=_set_level, **kwargs)(f) return decorator def default_logging_config( logger_: logging.Logger, ) -> logging.Logger: # pylint: disable=redefined-outer-name """Set up the default handler and formatter on the given logger.""" default_handler = logging.StreamHandler(stream=sys.stdout) default_handler.formatter = ColorFormatter() logger_.handlers = [default_handler] logger_.propagate = True return logger_ logger = logging.getLogger("aea") logger = default_logging_config(logger) ================================================ FILE: aea/cli/utils/package_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with package utils of the aea cli.""" import os import re import shutil from pathlib import Path from typing import Any, Dict, Optional, Set, Tuple, Union import click from jsonschema import ValidationError from aea import AEA_DIR, get_current_aea_version from aea.cli.fingerprint import fingerprint_item from aea.cli.utils.config import ( dump_item_config, get_non_vendor_package_path, load_item_config, ) from aea.cli.utils.constants import NOT_PERMITTED_AUTHORS from aea.cli.utils.context import Context from aea.cli.utils.loggers import logger from aea.configurations.base import ( AgentConfig, ComponentConfiguration, ComponentId, ComponentType, PackageConfiguration, PackageType, PublicId, _compute_fingerprint, _get_default_configuration_file_name_from_type, ) from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE from aea.configurations.constants import ( DISTRIBUTED_PACKAGES as DISTRIBUTED_PACKAGES_STR, ) from aea.configurations.constants import ( IMPORT_TEMPLATE_1, IMPORT_TEMPLATE_2, LEDGER_CONNECTION, PACKAGES, PACKAGE_PUBLIC_ID_VAR_NAME, SKILL, VENDOR, ) from aea.configurations.loader import ConfigLoader from aea.configurations.manager import AgentConfigManager from aea.configurations.utils import replace_component_ids from aea.crypto.helpers import get_wallet_from_agent_config, private_key_verify from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet from aea.exceptions import AEAEnforceError from aea.helpers.base import compute_specifier_from_version, recursive_update from aea.helpers.io import open_file from aea.helpers.sym_link import create_symlink DISTRIBUTED_PACKAGES = [PublicId.from_str(dp) for dp in DISTRIBUTED_PACKAGES_STR] ROOT = Path(".") def verify_private_keys_ctx( ctx: Context, aea_project_path: Path = ROOT, password: Optional[str] = None, ) -> None: """ Verify private keys with ctx provided. :param ctx: Context :param aea_project_path: the path to the aea project :param password: the password to encrypt/decrypt the private key. """ try: AgentConfigManager.verify_private_keys( aea_project_path, private_key_helper=private_key_verify, substitude_env_vars=False, password=password, ).dump_config() agent_config = AgentConfigManager.verify_private_keys( aea_project_path, private_key_helper=private_key_verify, password=password, ).agent_config if ctx is not None: ctx.agent_config = agent_config except ValueError as e: # pragma: nocover raise click.ClickException(str(e)) def validate_package_name(package_name: str) -> None: """Check that the package name matches the pattern r"[a-zA-Z_][a-zA-Z0-9_]*". >>> validate_package_name("this_is_a_good_package_name") >>> validate_package_name("this-is-not") Traceback (most recent call last): ... click.exceptions.BadParameter: this-is-not is not a valid package name. :param package_name: the package name """ if re.fullmatch(PublicId.PACKAGE_NAME_REGEX, package_name) is None: raise click.BadParameter("{} is not a valid package name.".format(package_name)) def _is_valid_author_handle(author: str) -> bool: """ Check that the author matches the pattern r"[a-zA-Z_][a-zA-Z0-9_]*". >>> _is_valid_author_handle("this_is_a_good_author_name") ... True >>> _is_valid_author_handle("this-is-not") ... False :param author: author name :return: bool indicating whether author name is valid """ if re.fullmatch(PublicId.AUTHOR_REGEX, author) is None: return False return True def _is_permitted_author_handle(author: str) -> bool: """ Check that the author handle is permitted. :param author: the author :return: bool """ result = author not in NOT_PERMITTED_AUTHORS return result # mostly for tests def is_path_exist(path: Union[str, Path]) -> bool: """ Check path provided exists. :param path: str or Path :return: bool """ return os.path.exists(path) def try_get_item_source_path( path: str, author_name: Optional[str], item_type_plural: str, item_name: str ) -> str: """ Get the item source path. :param path: the source path root :param author_name: the name of the author of the item :param item_type_plural: the item type (plural) :param item_name: the item name :return: the item source path """ if author_name is None: source_path = os.path.join(path, item_type_plural, item_name) else: source_path = os.path.join(path, author_name, item_type_plural, item_name) if not is_path_exist(source_path): raise click.ClickException( f'Item "{author_name}/{item_name}" not found in source folder "{source_path}".' ) return source_path def try_get_item_target_path( path: str, author_name: str, item_type_plural: str, item_name: str ) -> str: """ Get the item target path. :param path: the target path root :param author_name: the author name :param item_type_plural: the item type (plural) :param item_name: the item name :return: the item target path """ target_path = os.path.join(path, author_name, item_type_plural, item_name) if is_path_exist(target_path): path_ = Path(target_path) raise click.ClickException( f'Item "{path_.name}" already exists in target folder "{path_.parent}".' ) return target_path def get_package_path( project_directory: str, item_type: str, public_id: PublicId, is_vendor: bool = True ) -> str: """ Get a vendorized path for a package. :param project_directory: path to search packages :param item_type: item type. :param public_id: item public ID. :param is_vendor: flag for vendorized path (True by default). :return: vendorized destination path for package. """ item_type_plural = item_type + "s" if is_vendor: return os.path.join( project_directory, VENDOR, public_id.author, item_type_plural, public_id.name, ) return os.path.join(project_directory, item_type_plural, public_id.name) def get_package_path_unified( project_directory: str, agent_config: AgentConfig, item_type: str, public_id: PublicId, ) -> str: """ Get a path for a package, either vendor or not. That is: - if the author in the public id is not the same of the AEA project author, just look into vendor/ - Otherwise, first look into local packages, then into vendor/. :param project_directory: directory to look for packages. :param agent_config: agent configuration. :param item_type: item type. :param public_id: item public ID. :return: vendorized destination path for package. """ vendor_path = get_package_path( project_directory, item_type, public_id, is_vendor=True ) if agent_config.author != public_id.author or not is_item_present( project_directory, agent_config, item_type, public_id, is_vendor=False ): return vendor_path return get_package_path(project_directory, item_type, public_id, is_vendor=False) def get_dotted_package_path_unified( project_directory: str, agent_config: AgentConfig, *args: Any ) -> str: """ Get a *dotted* path for a package, either vendor or not. :param project_directory: base dir for package lookup. :param agent_config: AgentConfig. :param args: arguments for 'get_package_path_unified' :return: the dotted path to the package. """ path = get_package_path_unified(project_directory, agent_config, *args) path_relative_to_cwd = Path(path).relative_to(Path(project_directory)) relative_path_str = str(path_relative_to_cwd).replace(os.sep, ".") return relative_path_str def copy_package_directory(src: Path, dst: str) -> Path: """ Copy a package directory to the agent vendor resources. :param src: source path to the package to be added. :param dst: str package destination path. :return: copied folder target path. :raises ClickException: if the copy raises an exception. """ # copy the item package into the agent's supported packages. src_path = str(src.absolute()) logger.debug("Copying modules. src={} dst={}".format(src_path, dst)) try: shutil.copytree(src_path, dst) except Exception as e: # pylint: disable=broad-except raise click.ClickException(str(e)) items_folder = os.path.split(dst)[0] Path(items_folder, "__init__.py").touch() return Path(dst) def find_item_locally( ctx: Context, item_type: str, item_public_id: PublicId ) -> Tuple[Path, ComponentConfiguration]: """ Find an item in the local registry. :param ctx: the CLI context. :param item_type: the type of the item to load. One of: protocols, connections, skills :param item_public_id: the public id of the item to find. :return: tuple of path to the package directory (either in registry or in aea directory) and component configuration :raises ClickException: if the search fails. """ item_type_plural = item_type + "s" item_name = item_public_id.name try: registry_path = ctx.registry_path except ValueError as e: raise click.ClickException(str(e)) # check in registry package_path = Path( registry_path, item_public_id.author, item_type_plural, item_name ) config_file_name = _get_default_configuration_file_name_from_type(item_type) item_configuration_filepath = package_path / config_file_name if not item_configuration_filepath.exists(): raise click.ClickException( "Cannot find {}: '{}'.".format(item_type, item_public_id) ) # try to load the item configuration file try: item_configuration_loader = ConfigLoader.from_configuration_type( PackageType(item_type) ) with open_file(item_configuration_filepath) as fp: item_configuration = item_configuration_loader.load(fp) except ValidationError as e: raise click.ClickException( "{} configuration file not valid: {}".format(item_type.capitalize(), str(e)) ) # check that the configuration file of the found package matches the expected author and version. version = item_configuration.version author = item_configuration.author if item_public_id.author != author or ( not item_public_id.package_version.is_latest and item_public_id.version != version ): raise click.ClickException( "Cannot find {} with author and version specified.".format(item_type) ) return package_path, item_configuration def find_item_in_distribution( # pylint: disable=unused-argument ctx: Context, item_type: str, item_public_id: PublicId ) -> Path: """ Find an item in the AEA directory. :param ctx: the CLI context. :param item_type: the type of the item to load. One of: protocols, connections, skills :param item_public_id: the public id of the item to find. :return: path to the package directory (either in registry or in aea directory). :raises ClickException: if the search fails. """ item_type_plural = item_type + "s" item_name = item_public_id.name # check in aea dir package_path = Path(AEA_DIR, item_type_plural, item_name) config_file_name = _get_default_configuration_file_name_from_type(item_type) item_configuration_filepath = package_path / config_file_name if not item_configuration_filepath.exists(): raise click.ClickException( "Cannot find {}: '{}'.".format(item_type, item_public_id) ) # try to load the item configuration file try: item_configuration_loader = ConfigLoader.from_configuration_type( PackageType(item_type) ) with open_file(item_configuration_filepath) as fp: item_configuration = item_configuration_loader.load(fp) except ValidationError as e: raise click.ClickException( "{} configuration file not valid: {}".format(item_type.capitalize(), str(e)) ) # check that the configuration file of the found package matches the expected author and version. version = item_configuration.version author = item_configuration.author if item_public_id.author != author or ( not item_public_id.package_version.is_latest and item_public_id.version != version ): raise click.ClickException( "Cannot find {} with author and version specified.".format(item_type) ) return package_path # pragma: no cover def validate_author_name(author: Optional[str] = None) -> str: """ Validate an author name. :param author: the author name (optional) :return: validated author name """ is_acceptable_author = False if ( author is not None and _is_valid_author_handle(author) and _is_permitted_author_handle(author) ): is_acceptable_author = True valid_author = author while not is_acceptable_author: author_prompt = click.prompt( "Please enter the author handle you would like to use", type=str ) valid_author = author_prompt if _is_valid_author_handle(author_prompt) and _is_permitted_author_handle( author_prompt ): is_acceptable_author = True elif not _is_valid_author_handle(author_prompt): is_acceptable_author = False click.echo( "Not a valid author handle. Please try again. " "Author handles must satisfy the following regex: {}".format( PublicId.AUTHOR_REGEX ) ) elif not _is_permitted_author_handle(author_prompt): is_acceptable_author = False click.echo( "Not a permitted author handle. The following author handles are not allowed: {}".format( NOT_PERMITTED_AUTHORS ) ) return valid_author def is_fingerprint_correct( package_path: Path, item_config: PackageConfiguration ) -> bool: """ Validate fingerprint of item before adding. :param package_path: path to a package folder. :param item_config: item configuration. :return: bool indicating correctness of fingerprint. """ fingerprint = _compute_fingerprint( package_path, ignore_patterns=item_config.fingerprint_ignore_patterns ) return item_config.fingerprint == fingerprint def register_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: """ Register item in agent configuration. :param ctx: click context object. :param item_type: type of item. :param item_public_id: PublicId of item. """ logger.debug( "Registering the {} into {}".format(item_type, DEFAULT_AEA_CONFIG_FILE) ) supported_items = get_items(ctx.agent_config, item_type) supported_items.add(item_public_id) with open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as fp: ctx.agent_loader.dump(ctx.agent_config, fp) def is_item_present_unified( ctx: Context, item_type: str, item_public_id: PublicId ) -> bool: """ Check if item is present, either vendor or not. That is: - if the author in the public id is not the same of the AEA project author, just look into vendor/ - Otherwise, first look into local packages, then into vendor/. :param ctx: context object. :param item_type: type of an item. :param item_public_id: PublicId of an item. :return: True if the item is present, False otherwise. """ is_in_vendor = is_item_present( ctx.cwd, ctx.agent_config, item_type, item_public_id, is_vendor=True ) if item_public_id.author != ctx.agent_config.author: return is_in_vendor return is_in_vendor or is_item_present( ctx.cwd, ctx.agent_config, item_type, item_public_id, is_vendor=False ) def is_item_present( path: str, agent_config: AgentConfig, item_type: str, item_public_id: PublicId, is_vendor: bool = True, with_version: bool = False, ) -> bool: """ Check if item is already present in AEA. Optionally, consider the check also with the version. :param path: path to look for packages. :param agent_config: agent config :param item_type: type of an item. :param item_public_id: PublicId of an item. :param is_vendor: flag for vendorized path (True by default). :param with_version: if true, consider also the package version. :return: boolean is item present. """ item_path = Path( get_package_path(path, item_type, item_public_id, is_vendor=is_vendor) ) registered_item_public_id = get_item_public_id_by_author_name( agent_config, item_type, item_public_id.author, item_public_id.name ) is_item_registered_no_version = registered_item_public_id is not None does_path_exist = Path(item_path).exists() if item_public_id.package_version.is_latest or not with_version: return is_item_registered_no_version and does_path_exist # the following makes sense because public id is not latest component_id = ComponentId(ComponentType(item_type), item_public_id) component_is_registered = component_id in agent_config.package_dependencies return component_is_registered and does_path_exist def get_item_id_present( agent_config: AgentConfig, item_type: str, item_public_id: PublicId ) -> PublicId: """ Get the item present in AEA. :param agent_config: AgentConfig. :param item_type: type of an item. :param item_public_id: PublicId of an item. :return: boolean is item present. :raises: AEAEnforceError """ registered_item_public_id = get_item_public_id_by_author_name( agent_config, item_type, item_public_id.author, item_public_id.name ) if registered_item_public_id is None: raise AEAEnforceError("Cannot find item.") # pragma: nocover return registered_item_public_id def get_item_public_id_by_author_name( agent_config: AgentConfig, item_type: str, author: str, name: str ) -> Optional[PublicId]: """ Get component public_id by author and name. :param agent_config: AgentConfig :param item_type: str. component type: connection, skill, contract, protocol :param author: str. author name :param name: str. component name :return: PublicId """ items_in_config = { (x.author, x.name): x for x in get_items(agent_config, item_type) } return items_in_config.get((author, name), None) def get_items(agent_config: AgentConfig, item_type: str) -> Set[PublicId]: """ Get all items of certain type registered in AgentConfig. :param agent_config: AgentConfig :param item_type: str. component type: connection, skill, contract, protocol :return: set of public ids """ item_type_plural = item_type + "s" return getattr(agent_config, item_type_plural) def is_distributed_item(item_public_id: PublicId) -> bool: """ Check whether the item public id correspond to a package in the distribution. If the provided item has version 'latest', only the prefixes are compared. Otherwise, the function will try to match the exact version occurrence among the distributed packages. :param item_public_id: public id of the item :return: bool, indicating whether distributed or not """ if item_public_id.package_version.is_latest: return any(item_public_id.same_prefix(other) for other in DISTRIBUTED_PACKAGES) return item_public_id in DISTRIBUTED_PACKAGES def _override_ledger_configurations(agent_config: AgentConfig) -> None: """Override LedgerApis configurations with agent override configurations.""" ledger_component_id = ComponentId( ComponentType.CONNECTION, PublicId.from_str(LEDGER_CONNECTION) ) prefix_to_component_configuration = { key.component_prefix: value for key, value in agent_config.component_configurations.items() } if ledger_component_id.component_prefix not in prefix_to_component_configuration: return ledger_apis_config = prefix_to_component_configuration[ ledger_component_id.component_prefix ]["config"].get("ledger_apis", {}) recursive_update(LedgerApis.ledger_api_configs, ledger_apis_config) def try_get_balance( # pylint: disable=unused-argument agent_config: AgentConfig, wallet: Wallet, type_: str ) -> int: """ Try to get wallet balance. :param agent_config: agent config object. :param wallet: wallet object. :param type_: type of ledger API. :return: token balance. """ try: if not LedgerApis.has_ledger(type_): # pragma: no cover raise ValueError("No ledger api config for {} available.".format(type_)) address = wallet.addresses.get(type_) if address is None: # pragma: no cover raise ValueError("No key '{}' in wallet.".format(type_)) balance = LedgerApis.get_balance(type_, address) if balance is None: # pragma: no cover raise ValueError("No balance returned!") return balance except ValueError as e: # pragma: no cover raise click.ClickException(str(e)) def get_wallet_from_context(ctx: Context, password: Optional[str] = None) -> Wallet: """ Get wallet from current click Context. :param ctx: click context :param password: the password to encrypt/decrypt private keys :return: wallet """ verify_private_keys_ctx(ctx=ctx, password=password) wallet = get_wallet_from_agent_config(ctx.agent_config, password=password) return wallet def update_item_public_id_in_init( item_type: str, package_path: Path, item_id: PublicId ) -> None: """ Update item config and item config file. :param item_type: type of item. :param package_path: path to a package folder. :param item_id: public_id """ if item_type != SKILL: return init_filepath = os.path.join(package_path, "__init__.py") with open_file(init_filepath, "r") as f: file_content = f.readlines() with open_file(init_filepath, "w") as f: for line in file_content: if PACKAGE_PUBLIC_ID_VAR_NAME in line: f.write( f'{PACKAGE_PUBLIC_ID_VAR_NAME} = PublicId.from_str("{str(item_id)}")' ) else: f.write(line) def update_references( ctx: Context, replacements: Dict[ComponentId, ComponentId] ) -> None: """ Update references across an AEA project. Caveat: the update is done in a sequential manner. There is no check of multiple updates, due to the occurrence of transitive relations. E.g. replacements as {c1: c2, c2: c3} might lead to c1 replaced with c3 instead of c2. :param ctx: the context. :param replacements: mapping from old component ids to new component ids. """ # preprocess replacement so to index them by component type replacements_by_type: Dict[ComponentType, Dict[PublicId, PublicId]] = {} for old, new in replacements.items(): replacements_by_type.setdefault(old.component_type, {})[ old.public_id ] = new.public_id aea_project_root = Path(ctx.cwd) # update agent configuration agent_config = load_item_config(PackageType.AGENT.value, aea_project_root) replace_component_ids(agent_config, replacements_by_type) dump_item_config(agent_config, aea_project_root) # update every (non-vendor) AEA package. for package_path in get_non_vendor_package_path(aea_project_root): package_type = PackageType(package_path.parent.name[:-1]) package_config = load_item_config(package_type.value, package_path) replace_component_ids(package_config, replacements_by_type) dump_item_config(package_config, package_path) def create_symlink_vendor_to_local( ctx: Context, item_type: str, public_id: PublicId ) -> None: """ Creates a symlink from the vendor to the local folder. :param ctx: click context :param item_type: item type :param public_id: public_id of the item """ vendor_path_str = get_package_path(ctx.cwd, item_type, public_id, is_vendor=True) local_path = get_package_path(ctx.cwd, item_type, public_id, is_vendor=False) vendor_path = Path(vendor_path_str) if not os.path.exists(vendor_path.parent): os.makedirs(vendor_path.parent) create_symlink(vendor_path, Path(local_path), Path(ctx.cwd)) def create_symlink_packages_to_vendor(ctx: Context) -> None: """ Creates a symlink from a local packages to the vendor folder. :param ctx: click context """ if not os.path.exists(PACKAGES): create_symlink(Path(PACKAGES), Path(VENDOR), Path(ctx.cwd)) def replace_all_import_statements( aea_project_path: Path, item_type: ComponentType, old_public_id: PublicId, new_public_id: PublicId, ) -> None: """ Replace all import statements in Python modules of all the non-vendor packages. The function looks for two patterns: - from packages... - import packages... :param aea_project_path: path to the AEA project. :param item_type: the item type. :param old_public_id: the old public id. :param new_public_id: the new public id. """ old_formats = dict( author=old_public_id.author, type=item_type.to_plural(), name=old_public_id.name ) new_formats = dict( author=new_public_id.author, type=item_type.to_plural(), name=new_public_id.name ) old_import_1 = IMPORT_TEMPLATE_1.format(**old_formats) old_import_2 = IMPORT_TEMPLATE_2.format(**old_formats) new_import_1 = IMPORT_TEMPLATE_1.format(**new_formats) new_import_2 = IMPORT_TEMPLATE_2.format(**new_formats) pattern_1 = re.compile(rf"^{old_import_1}", re.MULTILINE) pattern_2 = re.compile(rf"^{old_import_2}", re.MULTILINE) for package_path in get_non_vendor_package_path(aea_project_path): for python_module in package_path.rglob("*.py"): content = python_module.read_text() content = pattern_1.sub(new_import_1, content) content = pattern_2.sub(new_import_2, content) python_module.write_text(content) def fingerprint_all(ctx: Context) -> None: """ Fingerprint all non-vendor packages. :param ctx: the CLI context. """ aea_project_path = Path(ctx.cwd) for package_path in get_non_vendor_package_path(aea_project_path): item_type = package_path.parent.name[:-1] config = load_item_config(item_type, package_path) fingerprint_item(ctx, item_type, config.public_id) def update_aea_version_range(package_configuration: PackageConfiguration) -> None: """Update 'aea_version' range.""" version = get_current_aea_version() if not package_configuration.aea_version_specifiers.contains(version): new_aea_version = compute_specifier_from_version(version) old_aea_version = package_configuration.aea_version click.echo( f"Updating AEA version specifier from {old_aea_version} to {new_aea_version}." ) package_configuration.aea_version = new_aea_version ================================================ FILE: aea/common.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the common types and interfaces used in the aea framework.""" import os from pathlib import Path from typing import Any, Dict, List, Optional, Union Address = str Primitive = Union[str, int, bool, float] _JSONDict = Dict[Any, Any] # temporary placeholder _JSONList = List[Any] # temporary placeholder _JSONType = Optional[Union[Primitive, _JSONDict, _JSONList]] JSONLike = Dict[str, _JSONType] PathLike = Union[os.PathLike, Path, str] ================================================ FILE: aea/components/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains utilities for AEA components.""" ================================================ FILE: aea/components/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains definitions of agent components.""" import importlib.util import logging import sys import types from abc import ABC from pathlib import Path from typing import Any, Optional from aea.configurations.base import ( ComponentConfiguration, ComponentId, ComponentType, PublicId, ) from aea.configurations.constants import PACKAGES from aea.exceptions import AEAEnforceError from aea.helpers.logging import WithLogger _default_logger = logging.getLogger(__name__) class Component(ABC, WithLogger): """Abstract class for an agent component.""" __slots__ = ("_configuration", "_directory", "_is_vendor") def __init__( self, configuration: Optional[ComponentConfiguration] = None, is_vendor: bool = False, **kwargs: Any, ) -> None: """ Initialize a package. :param configuration: the package configuration. :param is_vendor: whether the package is vendorized. :param kwargs: the keyword arguments for the logger. """ WithLogger.__init__(self, **kwargs) self._configuration = configuration self._directory = None # type: Optional[Path] self._is_vendor = is_vendor @property def component_type(self) -> ComponentType: """Get the component type.""" return self.configuration.component_type @property def is_vendor(self) -> bool: """Get whether the component is vendorized or not.""" return self._is_vendor @property def prefix_import_path(self) -> str: """Get the prefix import path for this component.""" return self.configuration.prefix_import_path @property def component_id(self) -> ComponentId: """Ge the package id.""" return self.configuration.component_id @property def public_id(self) -> PublicId: """Get the public id.""" return self.configuration.public_id @property def configuration(self) -> ComponentConfiguration: """Get the component configuration.""" if self._configuration is None: # pragma: nocover raise ValueError("The component is not associated with a configuration.") return self._configuration @property def directory(self) -> Path: """Get the directory. Raise error if it has not been set yet.""" if self._directory is None: raise ValueError("Directory not set yet.") return self._directory @directory.setter def directory(self, path: Path) -> None: """Set the directory. Raise error if already set.""" if self._directory is not None: # pragma: nocover raise ValueError("Directory already set.") self._directory = path @property def build_directory(self) -> Optional[str]: """Get build directory for the component.""" return self.configuration.build_directory def load_aea_package(configuration: ComponentConfiguration) -> None: """ Load the AEA package from configuration. It adds all the __init__.py modules into `sys.modules`. :param configuration: the configuration object. """ dir_ = configuration.directory if dir_ is None: # pragma: nocover raise ValueError("configuration's directory is None.") author = configuration.author package_type_plural = configuration.component_type.to_plural() package_name = configuration.name perform_load_aea_package(dir_, author, package_type_plural, package_name) def perform_load_aea_package( dir_: Path, author: str, package_type_plural: str, package_name: str ) -> None: """ Load the AEA package from values provided. It adds all the __init__.py modules into `sys.modules`. :param dir_: path of the component. :param author: str :param package_type_plural: str :param package_name: str """ if dir_ is None or not dir_.exists(): # pragma: nocover raise AEAEnforceError(f"configuration directory `{dir_}` does not exists.") prefix_root = PACKAGES prefix_author = prefix_root + f".{author}" prefix_pkg_type = prefix_author + f".{package_type_plural}" prefix_root_module = types.ModuleType(prefix_root) prefix_root_module.__path__ = None # type: ignore sys.modules[prefix_root] = sys.modules.get(prefix_root, prefix_root_module) author_module = types.ModuleType(prefix_author) author_module.__path__ = None # type: ignore sys.modules[prefix_author] = sys.modules.get(prefix_author, author_module) prefix_pkg_type_module = types.ModuleType(prefix_pkg_type) prefix_pkg_type_module.__path__ = None # type: ignore sys.modules[prefix_pkg_type] = sys.modules.get( prefix_pkg_type, prefix_pkg_type_module ) prefix_pkg = prefix_pkg_type + f".{package_name}" for subpackage_init_file in dir_.rglob("__init__.py"): parent_dir = subpackage_init_file.parent relative_parent_dir = parent_dir.relative_to(dir_) if relative_parent_dir == Path("."): # this handles the case when 'subpackage_init_file' # is path/to/package/__init__.py import_path = prefix_pkg else: # pragma: nocover import_path = prefix_pkg + "." + ".".join(relative_parent_dir.parts) spec = importlib.util.spec_from_file_location(import_path, subpackage_init_file) if spec is None: raise RuntimeError(f"Error load module from {subpackage_init_file}") module = importlib.util.module_from_spec(spec) sys.modules[import_path] = module _default_logger.debug(f"loading {import_path}: {module}") spec.loader.exec_module(module) # type: ignore ================================================ FILE: aea/components/loader.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains utilities for loading components.""" import re from typing import Dict, Type from aea.components.base import Component from aea.configurations.base import ComponentConfiguration, ComponentType from aea.configurations.constants import PACKAGES from aea.connections.base import Connection from aea.contracts.base import Contract from aea.exceptions import ( AEAComponentLoadException, AEAInstantiationException, AEAPackageLoadingError, enforce, parse_exception, ) from aea.protocols.base import Protocol from aea.skills.base import Skill def component_type_to_class(component_type: ComponentType) -> Type[Component]: """ Get the component class from the component type. :param component_type: the component type :return: the component class """ type_to_class: Dict[ComponentType, Type[Component]] = { ComponentType.PROTOCOL: Protocol, ComponentType.CONTRACT: Contract, ComponentType.CONNECTION: Connection, ComponentType.SKILL: Skill, } return type_to_class[component_type] def load_component_from_config( # type: ignore # pylint: disable=inconsistent-return-statements #pylint mistake configuration: ComponentConfiguration, *args, **kwargs ) -> Component: """ Load a component from a directory. :param configuration: the component configuration. :param args: the positional arguments. :param kwargs: the keyword arguments. :return: the component instance. """ component_type = configuration.component_type component_class = component_type_to_class(component_type) try: return component_class.from_config(*args, configuration=configuration, **kwargs) # type: ignore except AEAInstantiationException as e: raise e except AEAComponentLoadException as e: raise AEAPackageLoadingError( "Package loading error: An error occurred while loading {} {}: {}".format( str(configuration.component_type), configuration.public_id, e ) ) except ModuleNotFoundError as e: _handle_error_while_loading_component_module_not_found(configuration, e) except Exception as e: # pylint: disable=broad-except _handle_error_while_loading_component_generic_error(configuration, e) class AEAPackageNotFound(Exception): """Exception when failed to import package, cause not exists.""" def _handle_error_while_loading_component_module_not_found( configuration: ComponentConfiguration, e: ModuleNotFoundError ) -> None: """ Handle ModuleNotFoundError for AEA packages. It will rewrite the error message only if the import path starts with 'packages'. To do that, it will extract the wrong import path from the error message. Depending on the import path, the possible error messages can be: - "No AEA package found with author name '{}', type '{}', name '{}'" - "'{}' is not a valid type name, choose one of ['protocols', 'connections', 'skills', 'contracts']" - "The package '{}/{}' of type '{}' exists, but cannot find module '{}'" :param configuration: the configuration :param e: the exception :raises ModuleNotFoundError: if it is not # noqa: DAR402 :raises AEAPackageLoadingError: the same exception, but prepending an informative message. """ error_message = str(e) match = re.match(r"No module named '([\w.]+)'", error_message) if match is None: # if for some reason we cannot extract the import path, just re-raise the error raise e from e import_path = match.group(1) parts = import_path.split(".") nb_parts = len(parts) if parts[0] != PACKAGES or nb_parts < 2: # if the first part of the import path is not 'packages', # the error is due for other reasons - just re-raise the error raise e from e def get_new_error_message_no_package_found() -> str: """Create a new error message in case the package is not found.""" enforce(nb_parts <= 4, "More than 4 parts!") author = parts[1] new_message = "No AEA package found with author name '{}'".format(author) if nb_parts >= 3: pkg_type = parts[2] try: ComponentType(pkg_type[:-1]) except ValueError: return "'{}' is not a valid type name, choose one of {}".format( pkg_type, list(map(lambda x: x.to_plural(), ComponentType)) ) new_message += ", type '{}'".format(pkg_type) if nb_parts == 4: pkg_name = parts[3] new_message += ", name '{}'".format(pkg_name) return new_message def get_new_error_message_with_package_found() -> str: """Create a new error message in case the package is found.""" enforce(nb_parts >= 5, "Less than 5 parts!") author, pkg_name, pkg_type = parts[:3] the_rest = ".".join(parts[4:]) return "The package '{}/{}' of type '{}' exists, but cannot find module '{}'".format( author, pkg_name, pkg_type, the_rest ) if nb_parts < 5: new_message = get_new_error_message_no_package_found() else: new_message = get_new_error_message_with_package_found() new_exc = AEAPackageNotFound(new_message) new_exc.__traceback__ = e.__traceback__ e_str = parse_exception(new_exc) raise AEAPackageLoadingError( "Package loading error: An error occurred while loading {} {}:\n{}".format( str(configuration.component_type), configuration.public_id, e_str, ) ) def _handle_error_while_loading_component_generic_error( configuration: ComponentConfiguration, e: Exception ) -> None: """ Handle Exception for AEA packages. :param configuration: the configuration :param e: the exception :raises AEAPackageLoadingError: the same exception, but prepending an informative message. """ e_str = parse_exception(e) raise AEAPackageLoadingError( "Package loading error: An error occurred while loading {} {}: {}".format( str(configuration.component_type), configuration.public_id, e_str ) ) from e ================================================ FILE: aea/components/utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the component loading utils.""" import re import sys from pathlib import Path from typing import Dict, List from aea.components.base import perform_load_aea_package from aea.configurations.constants import CONNECTIONS, CONTRACTS, PROTOCOLS, SKILLS PACKAGES_RE = re.compile(r"^packages\.(\w+)\.(\w+)\.(\w+)$", re.I) def _enlist_component_packages() -> Dict[str, List[Dict[str, str]]]: """List all components packages already loaded.""" result: Dict[str, List[Dict[str, str]]] = {} for name, mod in sys.modules.items(): match = PACKAGES_RE.match(name) if not match: continue author, package_type, package_name = match.groups() packages = result.get(package_type, []) package_data = { "author": author, "package_type": package_type, "package_name": package_name, "dir": mod.__dict__["__path__"][0], } packages.append(package_data) result[package_type] = packages return result # no cover cause executed in a subprocess, not possible to track def _populate_packages(packages: dict) -> None: # pragma: nocover """Load packages as python modules.""" for package_type in [PROTOCOLS, CONTRACTS, CONNECTIONS, SKILLS]: for package in packages.get(package_type, []): # load package print(11, package) perform_load_aea_package( dir_=Path(package["dir"]), author=package["author"], package_type_plural=package["package_type"], package_name=package["package_name"], ) ================================================ FILE: aea/configurations/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the configuration modules.""" ================================================ FILE: aea/configurations/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Classes to handle AEA configurations.""" import pprint from abc import ABC from collections import OrderedDict from copy import copy, deepcopy from operator import attrgetter from pathlib import Path from typing import ( Any, Collection, Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast, ) import packaging from packaging.specifiers import SpecifierSet from packaging.version import Version from aea.__version__ import __version__ as __aea_version__ from aea.configurations.constants import ( CONNECTIONS, CONTRACTS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_FINGERPRINT_IGNORE_PATTERNS, DEFAULT_LICENSE, DEFAULT_LOGGING_CONFIG, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, DEFAULT_VERSION, PACKAGE_PUBLIC_ID_VAR_NAME, PROTOCOLS, SKILLS, ) from aea.configurations.data_types import ( CRUDCollection, ComponentId, ComponentType, Dependencies, Dependency, JSONSerializable, PackageId, PackageType, PackageVersion, PublicId, ) from aea.configurations.validation import ConfigValidator, validate_data_with_pattern from aea.exceptions import enforce from aea.helpers.base import ( CertRequest, SimpleId, SimpleIdOrStr, load_module, recursive_update, ) from aea.helpers.ipfs.base import IPFSHashOnly # for tests _ = [PackageId, PackageVersion] T = TypeVar("T") def dependencies_from_json(obj: Dict[str, Dict]) -> Dependencies: """ Parse a JSON object to get an instance of Dependencies. :param obj: a dictionary whose keys are package names and values are dictionary with package specifications. :return: a Dependencies object. """ return {key: Dependency.from_json({key: value}) for key, value in obj.items()} def dependencies_to_json(dependencies: Dependencies) -> Dict[str, Dict]: """ Transform a Dependencies object into a JSON object. :param dependencies: an instance of "Dependencies" type. :return: a dictionary whose keys are package names and values are the JSON version of a Dependency object. """ result = {} for key, value in dependencies.items(): dep_to_json = value.to_json() package_name = list(dep_to_json.items())[0][0] enforce( key == package_name, f"Names of dependency differ: {key} != {package_name}" ) result[key] = dep_to_json[key] return result def _get_default_configuration_file_name_from_type( item_type: Union[str, PackageType] ) -> str: """Get the default configuration file name from item type.""" item_type = PackageType(item_type) if item_type == PackageType.AGENT: return DEFAULT_AEA_CONFIG_FILE if item_type == PackageType.PROTOCOL: return DEFAULT_PROTOCOL_CONFIG_FILE if item_type == PackageType.CONNECTION: return DEFAULT_CONNECTION_CONFIG_FILE if item_type == PackageType.SKILL: return DEFAULT_SKILL_CONFIG_FILE if item_type == PackageType.CONTRACT: return DEFAULT_CONTRACT_CONFIG_FILE raise ValueError( # pragma: no cover "Item type not valid: {}".format(str(item_type)) ) class ProtocolSpecificationParseError(Exception): """Exception for parsing a protocol specification file.""" class Configuration(JSONSerializable, ABC): """Configuration class.""" __slots__ = ("_key_order",) def __init__(self) -> None: """Initialize a configuration object.""" # a list of keys that remembers the key order of the configuration file. # this is set by the configuration loader. self._key_order: List[str] = [] @classmethod def from_json(cls, obj: Dict) -> "Configuration": """Build from a JSON object.""" @property def ordered_json(self) -> OrderedDict: """ Reorder the dictionary according to a key ordering. This method takes all the keys in the key_order list and get the associated value in the dictionary (if present). For the remaining keys not considered in the order, it will use alphanumerical ordering. In particular, if key_order is an empty sequence, this reduces to alphanumerical sorting. It does not do side-effect. :return: the ordered dictionary. """ data = self.json result = OrderedDict() # type: OrderedDict # parse all the known keys. This might ignore some keys in the dictionary. seen_keys = set() for key in self._key_order: enforce(key not in result, "Key in results!") value = data.get(key) if value is not None: result[key] = value seen_keys.add(key) # Now process the keys in the dictionary that were not covered before. for key, value in data.items(): if key not in seen_keys: result[key] = value return result class PackageConfiguration(Configuration, ABC): """ This class represent a package configuration. A package can be one of: - agents - protocols - connections - skills - contracts """ __slots__ = ( "_name", "_author", "version", "license", "fingerprint", "fingerprint_ignore_patterns", "build_entrypoint", "_aea_version", "_aea_version_specifiers", "_directory", ) default_configuration_filename: str package_type: PackageType FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["build_directory"]) schema: str CHECK_EXCLUDES: List[Tuple[str]] = [] def __init__( self, name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, ) -> None: """ Initialize a package configuration. :param name: the name of the package. :param author: the author of the package. :param version: the version of the package (SemVer format). :param license_: the license. :param aea_version: either a fixed version, or a set of specifiers describing the AEA versions allowed. (default: empty string - no constraint). The fixed version is interpreted with the specifier '=='. :param fingerprint: the fingerprint. :param fingerprint_ignore_patterns: a list of file patterns to ignore files to fingerprint. :param build_entrypoint: path to a script to execute at build time. """ super().__init__() if name is None or author is None: # pragma: nocover raise ValueError("Name and author must be set on the configuration!") self._name = SimpleId(name) self._author = SimpleId(author) self.version = version if version != "" else DEFAULT_VERSION self.license = license_ if license_ != "" else DEFAULT_LICENSE self.fingerprint = fingerprint if fingerprint is not None else {} self.fingerprint_ignore_patterns = ( fingerprint_ignore_patterns if fingerprint_ignore_patterns is not None else [] ) self.build_entrypoint = build_entrypoint self._aea_version = aea_version if aea_version != "" else __aea_version__ self._aea_version_specifiers = self.parse_aea_version_specifier(aea_version) self._directory = None # type: Optional[Path] @property def name(self) -> str: """Get the name.""" return str(self._name) @name.setter def name(self, value: SimpleIdOrStr) -> None: """Set the name.""" self._name = SimpleId(value) @property def author(self) -> str: """Get the author.""" return str(self._author) @author.setter def author(self, value: SimpleIdOrStr) -> None: """Set the author.""" self._author = SimpleId(value) @property def aea_version(self) -> str: """Get the 'aea_version' attribute.""" return self._aea_version @aea_version.setter def aea_version(self, new_aea_version: str) -> None: """Set the 'aea_version' attribute.""" self._aea_version_specifiers = self.parse_aea_version_specifier(new_aea_version) self._aea_version = new_aea_version def check_aea_version(self) -> None: """ Check that the AEA version matches the specifier set. :raises ValueError if the version of the aea framework falls within a specifier. """ _check_aea_version(self) @property def directory(self) -> Optional[Path]: """Get the path to the configuration file associated to this file, if any.""" return self._directory @directory.setter def directory(self, directory: Path) -> None: """Set directory if not already set.""" if self._directory is not None: # pragma: nocover raise ValueError("Directory already set") self._directory = directory @property def package_id(self) -> PackageId: """Get package id.""" return PackageId(package_type=self.package_type, public_id=self.public_id) @staticmethod def parse_aea_version_specifier(aea_version_specifiers: str) -> SpecifierSet: """ Parse an 'aea_version' field. If 'aea_version' is a version, then output the specifier set "==${version}" Else, interpret it as specifier set. :param aea_version_specifiers: the AEA version, or a specifier set. :return: A specifier set object. """ try: Version(aea_version_specifiers) return SpecifierSet("==" + aea_version_specifiers) except packaging.version.InvalidVersion: pass return SpecifierSet(aea_version_specifiers) @property def aea_version_specifiers(self) -> SpecifierSet: """Get the AEA version set specifier.""" return self._aea_version_specifiers @property def public_id(self) -> PublicId: """Get the public id.""" return PublicId(self.author, self.name, self.version) @property def package_dependencies(self) -> Set[ComponentId]: """Get the package dependencies.""" return set() def update(self, data: Dict, env_vars_friendly: bool = False) -> None: """ Update configuration with other data. :param data: the data to replace. :param env_vars_friendly: whether or not it is env vars friendly. """ if not data: # do nothing if nothing to update return self.check_overrides_valid(data, env_vars_friendly=env_vars_friendly) self._create_or_update_from_json( obj=self.make_resulting_config_data(data), instance=self ) @classmethod def validate_config_data( cls, json_data: Dict, env_vars_friendly: bool = False ) -> None: """Perform config validation.""" ConfigValidator(cls.schema, env_vars_friendly=env_vars_friendly).validate( json_data ) @classmethod def from_json(cls, obj: Dict) -> "PackageConfiguration": """Initialize from a JSON object.""" return cls._create_or_update_from_json(obj=obj, instance=None) @classmethod def _create_or_update_from_json( cls, obj: Dict, instance: Any = None ) -> "PackageConfiguration": """Create new config object or updates existing one from json data.""" raise NotImplementedError # pragma: nocover def make_resulting_config_data(self, overrides: Dict) -> Dict: """ Make config data with overrides applied. Does not update config, just creates json representation. :param overrides: the overrides :return: config with overrides applied """ current_config = self.json recursive_update(current_config, overrides, allow_new_values=True) return current_config def check_overrides_valid( self, overrides: Dict, env_vars_friendly: bool = False ) -> None: """Check overrides is correct, return list of errors if present.""" # check for permitted overrides self._check_overrides_corresponds_to_overridable( overrides, env_vars_friendly=env_vars_friendly ) # check resulting config with applied overrides passes validation result_config = self.make_resulting_config_data(overrides) self.validate_config_data(result_config, env_vars_friendly=env_vars_friendly) def _check_overrides_corresponds_to_overridable( self, overrides: Dict, env_vars_friendly: bool = False ) -> None: """Check overrides is correct, return list of errors if present.""" errors_list = validate_data_with_pattern( overrides, self.get_overridable(), excludes=self.CHECK_EXCLUDES, skip_env_vars=env_vars_friendly, ) if errors_list: raise ValueError(errors_list[0]) def get_overridable(self) -> dict: """Get dictionary of values that can be updated for this config.""" return {k: self.json.get(k) for k in self.FIELDS_ALLOWED_TO_UPDATE} @classmethod def _apply_params_to_instance( cls, params: dict, instance: Optional["PackageConfiguration"] ) -> "PackageConfiguration": """Constructs or update instance with params provided.""" directory = ( instance.directory if instance and hasattr(instance, "directory") else None ) if instance is None: instance = cls(**params) else: instance.__init__(**params) # type: ignore # pylint: disable=unnecessary-dunder-call if directory and not instance.directory: instance.directory = directory return instance class ComponentConfiguration(PackageConfiguration, ABC): """Class to represent an agent component configuration.""" package_type: PackageType __slots__ = ("pypi_dependencies", "_build_directory") def __init__( self, name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, build_directory: Optional[str] = None, dependencies: Optional[Dependencies] = None, ) -> None: """Set component configuration.""" super().__init__( name, author, version, license_, aea_version, fingerprint, fingerprint_ignore_patterns, build_entrypoint, ) self.pypi_dependencies: Dependencies = ( dependencies if dependencies is not None else {} ) self._build_directory = build_directory @property def build_directory(self) -> Optional[str]: """Get the component type.""" return self._build_directory @build_directory.setter def build_directory(self, value: Optional[str]) -> None: """Get the component type.""" self._build_directory = value @property def component_type(self) -> ComponentType: """Get the component type.""" return ComponentType(self.package_type.value) @property def component_id(self) -> ComponentId: """Get the component id.""" return ComponentId(self.component_type, self.public_id) @property def prefix_import_path(self) -> str: """Get the prefix import path for this component.""" return "packages.{}.{}.{}".format( self.public_id.author, self.component_type.to_plural(), self.public_id.name ) @property def is_abstract_component(self) -> bool: """Check whether the component is abstract.""" return False def _check_configuration_consistency(self, directory: Path) -> None: """Check that the configuration file is consistent against a directory.""" self.check_fingerprint(directory) self.check_aea_version() self.check_public_id_consistency(directory) def check_fingerprint(self, directory: Path) -> None: """ Check that the fingerprint are correct against a directory path. :param directory: the directory path. :raises ValueError: if - the argument is not a valid package directory - the fingerprints do not match. """ if not directory.exists() or not directory.is_dir(): raise ValueError("Directory {} is not valid.".format(directory)) _compare_fingerprints( self, directory, False, self.component_type.to_package_type() ) def check_public_id_consistency(self, directory: Path) -> None: """ Check that the public ids in the init file match the config. :param directory: the directory path. :raises ValueError: if - the argument is not a valid package directory - the public ids do not match. """ if not directory.exists() or not directory.is_dir(): raise ValueError("Directory {} is not valid.".format(directory)) _compare_public_ids(self, directory) class ConnectionConfig(ComponentConfiguration): """Handle connection configuration.""" default_configuration_filename = DEFAULT_CONNECTION_CONFIG_FILE package_type = PackageType.CONNECTION schema = "connection-config_schema.json" FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset( ["config", "cert_requests", "is_abstract", "build_directory"] ) __slots__ = ( "class_name", "protocols", "connections", "restricted_to_protocols", "excluded_protocols", "dependencies", "description", "config", "is_abstract", "cert_requests", ) def __init__( self, name: SimpleIdOrStr = "", author: SimpleIdOrStr = "", version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, build_directory: Optional[str] = None, class_name: str = "", protocols: Optional[Set[PublicId]] = None, connections: Optional[Set[PublicId]] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, dependencies: Optional[Dependencies] = None, description: str = "", connection_id: Optional[PublicId] = None, is_abstract: bool = False, cert_requests: Optional[List[CertRequest]] = None, **config: Any, ) -> None: """Initialize a connection configuration object.""" if connection_id is None: enforce(name != "", "Name or connection_id must be set.") enforce(author != "", "Author or connection_id must be set.") enforce(version != "", "Version or connection_id must be set.") else: enforce( name in ( "", connection_id.name, ), "Non matching name in ConnectionConfig name and public id.", ) name = connection_id.name enforce( author in ( "", connection_id.author, ), "Non matching author in ConnectionConfig author and public id.", ) author = connection_id.author enforce( version in ( "", connection_id.version, ), "Non matching version in ConnectionConfig version and public id.", ) version = connection_id.version super().__init__( name, author, version, license_, aea_version, fingerprint, fingerprint_ignore_patterns, build_entrypoint, build_directory, dependencies, ) self.class_name = class_name self.protocols = protocols if protocols is not None else set() self.connections = connections if connections is not None else set() self.restricted_to_protocols = ( restricted_to_protocols if restricted_to_protocols is not None else set() ) self.excluded_protocols = ( excluded_protocols if excluded_protocols is not None else set() ) self.dependencies = dependencies if dependencies is not None else {} self.description = description self.config = config if len(config) > 0 else {} self.is_abstract = is_abstract self.cert_requests = cert_requests @property def package_dependencies(self) -> Set[ComponentId]: """Get the connection dependencies.""" return { ComponentId(ComponentType.PROTOCOL, protocol_id) for protocol_id in self.protocols }.union( { ComponentId(ComponentType.CONNECTION, connection_id) for connection_id in self.connections } ) @property def is_abstract_component(self) -> bool: """Check whether the component is abstract.""" return self.is_abstract @property def json(self) -> Dict: """Return the JSON representation.""" result = OrderedDict( { "name": self.name, "author": self.author, "version": self.version, "type": self.component_type.value, "description": self.description, "license": self.license, "aea_version": self.aea_version, "fingerprint": self.fingerprint, "fingerprint_ignore_patterns": self.fingerprint_ignore_patterns, PROTOCOLS: sorted(map(str, self.protocols)), CONNECTIONS: sorted(map(str, self.connections)), "class_name": self.class_name, "config": self.config, "excluded_protocols": sorted(map(str, self.excluded_protocols)), "restricted_to_protocols": sorted( map(str, self.restricted_to_protocols) ), "dependencies": dependencies_to_json(self.dependencies), "is_abstract": self.is_abstract, } ) if self.cert_requests is not None: result["cert_requests"] = list(map(attrgetter("json"), self.cert_requests)) if self.build_entrypoint: result["build_entrypoint"] = self.build_entrypoint if self.build_directory: result["build_directory"] = self.build_directory return result @classmethod def _create_or_update_from_json( cls, obj: Dict, instance: Optional["ConnectionConfig"] = None ) -> "ConnectionConfig": """Create new config object or updates existing one from json data.""" obj = {**(instance.json if instance else {}), **copy(obj)} restricted_to_protocols = obj.get("restricted_to_protocols", set()) restricted_to_protocols = { PublicId.from_str(id_) for id_ in restricted_to_protocols } excluded_protocols = obj.get("excluded_protocols", set()) excluded_protocols = {PublicId.from_str(id_) for id_ in excluded_protocols} dependencies = dependencies_from_json(obj.get("dependencies", {})) protocols = {PublicId.from_str(id_) for id_ in obj.get(PROTOCOLS, set())} connections = {PublicId.from_str(id_) for id_ in obj.get(CONNECTIONS, set())} cert_requests = ( [ # notice: yaml.load resolves datetime strings to datetime.datetime objects CertRequest.from_json(cert_request_json) for cert_request_json in obj["cert_requests"] ] if "cert_requests" in obj else None ) params = dict( name=cast(str, obj.get("name")), author=cast(str, obj.get("author")), version=cast(str, obj.get("version")), license_=cast(str, obj.get("license")), aea_version=cast(str, obj.get("aea_version", "")), fingerprint=cast(Dict[str, str], obj.get("fingerprint")), fingerprint_ignore_patterns=cast( Sequence[str], obj.get("fingerprint_ignore_patterns") ), build_entrypoint=cast(Optional[str], obj.get("build_entrypoint")), build_directory=cast(Optional[str], obj.get("build_directory")), class_name=cast(str, obj.get("class_name")), protocols=cast(Set[PublicId], protocols), connections=cast(Set[PublicId], connections), restricted_to_protocols=cast(Set[PublicId], restricted_to_protocols), excluded_protocols=cast(Set[PublicId], excluded_protocols), dependencies=cast(Dependencies, dependencies), description=cast(str, obj.get("description", "")), is_abstract=obj.get("is_abstract", False), cert_requests=cert_requests, **cast(dict, obj.get("config", {})), ) instance = cast( ConnectionConfig, cls._apply_params_to_instance(params, instance) ) return instance class ProtocolConfig(ComponentConfiguration): """Handle protocol configuration.""" default_configuration_filename = DEFAULT_PROTOCOL_CONFIG_FILE package_type = PackageType.PROTOCOL schema = "protocol-config_schema.json" FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset() __slots__ = ("dependencies", "description", "protocol_specification_id") def __init__( self, name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, build_directory: Optional[str] = None, aea_version: str = "", dependencies: Optional[Dependencies] = None, description: str = "", protocol_specification_id: Optional[str] = None, ) -> None: """Initialize a connection configuration object.""" super().__init__( name, author, version, license_, aea_version, fingerprint, fingerprint_ignore_patterns, build_entrypoint, build_directory, dependencies, ) self.dependencies = dependencies if dependencies is not None else {} self.description = description if protocol_specification_id is None: raise ValueError( # pragma: nocover "protocol_specification_id not provided!" ) self.protocol_specification_id = PublicId.from_str( str(protocol_specification_id) ) @property def json(self) -> Dict: """Return the JSON representation.""" result = OrderedDict( { "name": self.name, "author": self.author, "version": self.version, "protocol_specification_id": str(self.protocol_specification_id), "type": self.component_type.value, "description": self.description, "license": self.license, "aea_version": self.aea_version, "fingerprint": self.fingerprint, "fingerprint_ignore_patterns": self.fingerprint_ignore_patterns, "dependencies": dependencies_to_json(self.dependencies), } ) if self.build_entrypoint: result["build_entrypoint"] = self.build_entrypoint if self.build_directory: result["build_directory"] = self.build_directory return result @classmethod def _create_or_update_from_json( cls, obj: Dict, instance: Optional["ProtocolConfig"] = None ) -> "ProtocolConfig": """Initialize from a JSON object.""" obj = {**(instance.json if instance else {}), **copy(obj)} dependencies = dependencies_from_json(obj.get("dependencies", {})) params = dict( name=cast(str, obj.get("name")), author=cast(str, obj.get("author")), protocol_specification_id=cast(str, obj.get("protocol_specification_id")), version=cast(str, obj.get("version")), license_=cast(str, obj.get("license")), aea_version=cast(str, obj.get("aea_version", "")), fingerprint=cast(Dict[str, str], obj.get("fingerprint")), fingerprint_ignore_patterns=cast( Sequence[str], obj.get("fingerprint_ignore_patterns") ), build_entrypoint=cast(Optional[str], obj.get("build_entrypoint")), build_directory=cast(Optional[str], obj.get("build_directory")), dependencies=dependencies, description=cast(str, obj.get("description", "")), ) instance = cast(ProtocolConfig, cls._apply_params_to_instance(params, instance)) return instance class SkillComponentConfiguration: """This class represent a skill component configuration.""" __slots__ = ("class_name", "file_path", "args") def __init__( self, class_name: str, file_path: Optional[str] = None, **args: Any ) -> None: """ Initialize a skill component configuration. :param class_name: the class name of the component. :param file_path: the file path. :param args: keyword arguments. """ self.class_name = class_name self.file_path: Optional[Path] = Path(file_path) if file_path else None self.args = args @property def json(self) -> Dict: """Return the JSON representation.""" result = {"class_name": self.class_name, "args": self.args} if self.file_path is not None: result["file_path"] = str(self.file_path.as_posix()) return result @classmethod def from_json(cls, obj: Dict) -> "SkillComponentConfiguration": """Initialize from a JSON object.""" return cls._create_or_update_from_json(obj, instance=None) @classmethod def _create_or_update_from_json( cls, obj: Dict, instance: Optional["SkillComponentConfiguration"] = None ) -> "SkillComponentConfiguration": """Initialize from a JSON object.""" obj = {**(instance.json if instance else {}), **copy(obj)} class_name = cast(str, obj.get("class_name")) file_path = cast(Optional[str], obj.get("file_path")) params = dict(class_name=class_name, file_path=file_path, **obj.get("args", {})) instance = cast( SkillComponentConfiguration, cls._apply_params_to_instance(params, instance) ) return instance @classmethod def _apply_params_to_instance( cls, params: dict, instance: Optional["SkillComponentConfiguration"] ) -> "SkillComponentConfiguration": """Constructs or update instance with params provided.""" if instance is None: instance = cls(**params) else: # pragma: nocover instance.__init__(**params) # type: ignore # pylint: disable=unnecessary-dunder-call return instance class SkillConfig(ComponentConfiguration): """Class to represent a skill configuration file.""" default_configuration_filename = DEFAULT_SKILL_CONFIG_FILE package_type = PackageType.SKILL schema = "skill-config_schema.json" abstract_field_name = "is_abstract" FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset( ["behaviours", "handlers", "models", "is_abstract", "build_directory"] ) FIELDS_WITH_NESTED_FIELDS: FrozenSet[str] = frozenset( ["behaviours", "handlers", "models"] ) NESTED_FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["args"]) __slots__ = ( "connections", "protocols", "contracts", "skills", "dependencies", "description", "handlers", "behaviours", "models", "is_abstract", ) def __init__( self, name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, build_directory: Optional[str] = None, connections: Optional[Set[PublicId]] = None, protocols: Optional[Set[PublicId]] = None, contracts: Optional[Set[PublicId]] = None, skills: Optional[Set[PublicId]] = None, dependencies: Optional[Dependencies] = None, description: str = "", is_abstract: bool = False, ) -> None: """Initialize a skill configuration.""" super().__init__( name, author, version, license_, aea_version, fingerprint, fingerprint_ignore_patterns, build_entrypoint, build_directory, dependencies, ) self.connections = connections if connections is not None else set() self.protocols = protocols if protocols is not None else set() self.contracts = contracts if contracts is not None else set() self.skills = skills if skills is not None else set() self.dependencies = dependencies if dependencies is not None else {} self.description = description self.handlers: CRUDCollection[SkillComponentConfiguration] = CRUDCollection() self.behaviours: CRUDCollection[SkillComponentConfiguration] = CRUDCollection() self.models: CRUDCollection[SkillComponentConfiguration] = CRUDCollection() self.is_abstract = is_abstract @property def package_dependencies(self) -> Set[ComponentId]: """Get the skill dependencies.""" return ( { ComponentId(ComponentType.PROTOCOL, protocol_id) for protocol_id in self.protocols } .union( { ComponentId(ComponentType.CONTRACT, contract_id) for contract_id in self.contracts } ) .union( {ComponentId(ComponentType.SKILL, skill_id) for skill_id in self.skills} ) .union( { ComponentId(ComponentType.CONNECTION, connection_id) for connection_id in self.connections } ) ) @property def is_abstract_component(self) -> bool: """Check whether the component is abstract.""" return self.is_abstract # pragma: nocover @property def json(self) -> Dict: """Return the JSON representation.""" result = OrderedDict( { "name": self.name, "author": self.author, "version": self.version, "type": self.component_type.value, "description": self.description, "license": self.license, "aea_version": self.aea_version, "fingerprint": self.fingerprint, "fingerprint_ignore_patterns": self.fingerprint_ignore_patterns, CONNECTIONS: sorted(map(str, self.connections)), CONTRACTS: sorted(map(str, self.contracts)), PROTOCOLS: sorted(map(str, self.protocols)), SKILLS: sorted(map(str, self.skills)), "behaviours": {key: b.json for key, b in self.behaviours.read_all()}, "handlers": {key: h.json for key, h in self.handlers.read_all()}, "models": {key: m.json for key, m in self.models.read_all()}, "dependencies": dependencies_to_json(self.dependencies), "is_abstract": self.is_abstract, } ) if self.build_entrypoint: result["build_entrypoint"] = self.build_entrypoint if self.build_directory: result["build_directory"] = self.build_directory return result @classmethod def _create_or_update_from_json( cls, obj: Dict, instance: Optional["SkillConfig"] = None ) -> "SkillConfig": """Initialize from a JSON object.""" obj = {**(instance.json if instance else {}), **copy(obj)} name = cast(str, obj.get("name")) author = cast(str, obj.get("author")) version = cast(str, obj.get("version")) license_ = cast(str, obj.get("license")) aea_version_specifiers = cast(str, obj.get("aea_version", "")) fingerprint = cast(Dict[str, str], obj.get("fingerprint")) fingerprint_ignore_patterns = cast( Sequence[str], obj.get("fingerprint_ignore_patterns") ) build_entrypoint = cast(Optional[str], obj.get("build_entrypoint")) connections = {PublicId.from_str(id_) for id_ in obj.get(CONNECTIONS, set())} protocols = {PublicId.from_str(id_) for id_ in obj.get(PROTOCOLS, set())} contracts = {PublicId.from_str(id_) for id_ in obj.get(CONTRACTS, set())} skills = {PublicId.from_str(id_) for id_ in obj.get(SKILLS, set())} dependencies = dependencies_from_json(obj.get("dependencies", {})) description = cast(str, obj.get("description", "")) params = dict( name=name, author=author, version=version, license_=license_, aea_version=aea_version_specifiers, fingerprint=fingerprint, fingerprint_ignore_patterns=fingerprint_ignore_patterns, build_entrypoint=build_entrypoint, connections=connections, protocols=protocols, contracts=contracts, skills=skills, dependencies=dependencies, description=description, is_abstract=obj.get("is_abstract", False), build_directory=obj.get("build_directory"), ) instance = cast(SkillConfig, cls._apply_params_to_instance(params, instance)) for behaviour_id, behaviour_data in obj.get("behaviours", {}).items(): behaviour_config = SkillComponentConfiguration.from_json(behaviour_data) instance.behaviours.create(behaviour_id, behaviour_config) for handler_id, handler_data in obj.get("handlers", {}).items(): handler_config = SkillComponentConfiguration.from_json(handler_data) instance.handlers.create(handler_id, handler_config) for model_id, model_data in obj.get("models", {}).items(): model_config = SkillComponentConfiguration.from_json(model_data) instance.models.create(model_id, model_config) return instance def get_overridable(self) -> dict: """Get overridable configuration data.""" result = {} current_config_data = self.json if self.abstract_field_name in current_config_data: result[self.abstract_field_name] = current_config_data[ self.abstract_field_name ] for field in self.FIELDS_WITH_NESTED_FIELDS: if not current_config_data.get(field, {}): continue result[field] = {} for name in current_config_data[field].keys(): result[field][name] = {} for nested_field in self.NESTED_FIELDS_ALLOWED_TO_UPDATE: result[field][name][nested_field] = current_config_data[field][ name ][nested_field] return result class AgentConfig(PackageConfiguration): """Class to represent the agent configuration file.""" default_configuration_filename = DEFAULT_AEA_CONFIG_FILE package_type = PackageType.AGENT schema = "aea-config_schema.json" FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset( [ "description", "logging_config", "private_key_paths", "connection_private_key_paths", "loop_mode", "runtime_mode", "task_manager_mode", "execution_timeout", "timeout", "period", "max_reactions", "skill_exception_policy", "connection_exception_policy", "default_connection", "default_ledger", "required_ledgers", "default_routing", "storage_uri", ] ) CHECK_EXCLUDES = [ ("private_key_paths",), ("connection_private_key_paths",), ("error_handler",), ("decision_maker_handler",), ("default_routing",), ("dependencies",), ("logging_config",), ] __slots__ = ( "agent_name", "description", "private_key_paths", "connection_private_key_paths", "logging_config", "default_ledger", "required_ledgers", "currency_denominations", "default_connection", "connections", "protocols", "skills", "contracts", "period", "execution_timeout", "max_reactions", "skill_exception_policy", "connection_exception_policy", "error_handler", "decision_maker_handler", "default_routing", "loop_mode", "runtime_mode", "storage_uri", "data_dir", "_component_configurations", "dependencies", ) def __init__( # pylint: disable=too-many-arguments self, agent_name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, description: str = "", logging_config: Optional[Dict] = None, period: Optional[float] = None, execution_timeout: Optional[float] = None, max_reactions: Optional[int] = None, error_handler: Optional[Dict] = None, decision_maker_handler: Optional[Dict] = None, skill_exception_policy: Optional[str] = None, connection_exception_policy: Optional[str] = None, default_ledger: Optional[str] = None, required_ledgers: Optional[List[str]] = None, currency_denominations: Optional[Dict[str, str]] = None, default_connection: Optional[str] = None, default_routing: Optional[Dict[str, str]] = None, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, task_manager_mode: Optional[str] = None, storage_uri: Optional[str] = None, data_dir: Optional[str] = None, component_configurations: Optional[Dict[ComponentId, Dict]] = None, dependencies: Optional[Dependencies] = None, ) -> None: """Instantiate the agent configuration object.""" super().__init__( agent_name, author, version, license_, aea_version, fingerprint, fingerprint_ignore_patterns, build_entrypoint, ) self.agent_name = self.name self.description = description self.private_key_paths = CRUDCollection[str]() self.connection_private_key_paths = CRUDCollection[str]() self.logging_config = logging_config or DEFAULT_LOGGING_CONFIG self.default_ledger = ( str(SimpleId(default_ledger)) if default_ledger is not None else None ) self.required_ledgers = ( [str(SimpleId(ledger)) for ledger in required_ledgers] if required_ledgers is not None else None ) self.currency_denominations = ( currency_denominations if currency_denominations is not None else {} ) self.default_connection = ( PublicId.from_str(default_connection) if default_connection is not None else None ) self.connections = set() # type: Set[PublicId] self.contracts = set() # type: Set[PublicId] self.protocols = set() # type: Set[PublicId] self.skills = set() # type: Set[PublicId] self.period: Optional[float] = period self.execution_timeout: Optional[float] = execution_timeout self.max_reactions: Optional[int] = max_reactions self.skill_exception_policy: Optional[str] = skill_exception_policy self.connection_exception_policy: Optional[str] = connection_exception_policy self.error_handler = error_handler if error_handler is not None else {} self.decision_maker_handler = ( decision_maker_handler if decision_maker_handler is not None else {} ) self.default_routing = ( { PublicId.from_str(key): PublicId.from_str(value) for key, value in default_routing.items() } if default_routing is not None else {} ) # type: Dict[PublicId, PublicId] self.loop_mode = loop_mode self.runtime_mode = runtime_mode self.task_manager_mode = task_manager_mode self.storage_uri = storage_uri self.data_dir = data_dir # this attribute will be set through the setter below self._component_configurations: Dict[ComponentId, Dict] = {} self.component_configurations = ( component_configurations if component_configurations is not None else {} ) self.dependencies = dependencies or {} @property def component_configurations(self) -> Dict[ComponentId, Dict]: """Get the custom component configurations.""" return self._component_configurations @component_configurations.setter def component_configurations(self, d: Dict[ComponentId, Dict]) -> None: """Set the component configurations.""" package_type_to_set = { PackageType.PROTOCOL: self.protocols, PackageType.CONNECTION: self.connections, PackageType.CONTRACT: self.contracts, PackageType.SKILL: self.skills, } for component_id, component_configuration in d.items(): enforce( component_id.public_id in package_type_to_set[component_id.package_type], f"Component {component_id} not declared in the agent configuration.", ) ConfigValidator.validate_component_configuration( component_id, component_configuration ) self._component_configurations = d @property def package_dependencies(self) -> Set[ComponentId]: """Get the package dependencies.""" protocols = set( ComponentId(ComponentType.PROTOCOL, public_id) for public_id in self.protocols ) connections = set( ComponentId(ComponentType.CONNECTION, public_id) for public_id in self.connections ) skills = set( ComponentId(ComponentType.SKILL, public_id) for public_id in self.skills ) contracts = set( ComponentId(ComponentType.CONTRACT, public_id) for public_id in self.contracts ) return set.union(protocols, contracts, connections, skills) @property def private_key_paths_dict(self) -> Dict[str, str]: """Get dictionary version of private key paths.""" return { # pylint: disable=unnecessary-comprehension key: path for key, path in self.private_key_paths.read_all() } @property def connection_private_key_paths_dict(self) -> Dict[str, str]: """Get dictionary version of connection private key paths.""" return { # pylint: disable=unnecessary-comprehension key: path for key, path in self.connection_private_key_paths.read_all() } def component_configurations_json(self) -> List[OrderedDict]: """Get the component configurations in JSON format.""" result: List[OrderedDict] = [] for component_id, config in self.component_configurations.items(): result.append( OrderedDict( public_id=str(component_id.public_id), type=str(component_id.component_type), **config, ) ) return result @property def json(self) -> Dict: """Return the JSON representation.""" config = OrderedDict( { "agent_name": self.agent_name, "author": self.author, "version": self.version, "license": self.license, "description": self.description, "aea_version": self.aea_version, "fingerprint": self.fingerprint, "fingerprint_ignore_patterns": self.fingerprint_ignore_patterns, CONNECTIONS: sorted(map(str, self.connections)), CONTRACTS: sorted(map(str, self.contracts)), PROTOCOLS: sorted(map(str, self.protocols)), SKILLS: sorted(map(str, self.skills)), "default_connection": str(self.default_connection) if self.default_connection is not None else None, "default_ledger": self.default_ledger, "required_ledgers": self.required_ledgers or [], "default_routing": { str(key): str(value) for key, value in self.default_routing.items() }, "connection_private_key_paths": self.connection_private_key_paths_dict, "private_key_paths": self.private_key_paths_dict, "logging_config": self.logging_config, "component_configurations": self.component_configurations_json(), "dependencies": dependencies_to_json(self.dependencies), } ) # type: Dict[str, Any] if self.build_entrypoint: config["build_entrypoint"] = self.build_entrypoint # framework optional configs are only printed if defined. if self.period is not None: config["period"] = self.period if self.execution_timeout is not None: config["execution_timeout"] = self.execution_timeout if self.max_reactions is not None: config["max_reactions"] = self.max_reactions if self.error_handler != {}: config["error_handler"] = self.error_handler if self.decision_maker_handler != {}: config["decision_maker_handler"] = self.decision_maker_handler if self.skill_exception_policy is not None: config["skill_exception_policy"] = self.skill_exception_policy if self.connection_exception_policy is not None: config["connection_exception_policy"] = self.connection_exception_policy if self.loop_mode is not None: config["loop_mode"] = self.loop_mode if self.runtime_mode is not None: config["runtime_mode"] = self.runtime_mode if self.task_manager_mode is not None: config["task_manager_mode"] = self.task_manager_mode if self.storage_uri is not None: config["storage_uri"] = self.storage_uri if self.data_dir is not None: config["data_dir"] = self.data_dir if self.currency_denominations != {}: config["currency_denominations"] = self.currency_denominations return config @classmethod def _create_or_update_from_json( cls, obj: Dict, instance: Optional[Any] = None ) -> "AgentConfig": """Create new config object or updates existing one from json data.""" obj = {**(instance.json if instance else {}), **copy(obj)} params = dict( agent_name=cast(str, obj.get("agent_name")), author=cast(str, obj.get("author")), version=cast(str, obj.get("version")), license_=cast(str, obj.get("license")), aea_version=cast(str, obj.get("aea_version", "")), description=cast(str, obj.get("description", "")), fingerprint=cast(Dict[str, str], obj.get("fingerprint", {})), fingerprint_ignore_patterns=cast( Sequence[str], obj.get("fingerprint_ignore_patterns") ), build_entrypoint=cast(Optional[str], obj.get("build_entrypoint")), logging_config=cast(Dict, obj.get("logging_config", {})), period=cast(float, obj.get("period")), execution_timeout=cast(float, obj.get("execution_timeout")), max_reactions=cast(int, obj.get("max_reactions")), error_handler=cast(Dict, obj.get("error_handler", {})), decision_maker_handler=cast(Dict, obj.get("decision_maker_handler", {})), skill_exception_policy=cast(str, obj.get("skill_exception_policy")), connection_exception_policy=cast( str, obj.get("connection_exception_policy") ), default_ledger=cast(str, obj.get("default_ledger")), required_ledgers=cast(Optional[List[str]], obj.get("required_ledgers")), currency_denominations=cast(Dict, obj.get("currency_denominations", {})), default_connection=cast(str, obj.get("default_connection")), default_routing=cast(Dict, obj.get("default_routing", {})), loop_mode=cast(str, obj.get("loop_mode")), runtime_mode=cast(str, obj.get("runtime_mode")), task_manager_mode=cast(str, obj.get("task_manager_mode")), storage_uri=cast(str, obj.get("storage_uri")), data_dir=cast(str, obj.get("data_dir")), component_configurations=None, dependencies=cast( Dependencies, dependencies_from_json(obj.get("dependencies", {})) ), ) instance = cast(AgentConfig, cls._apply_params_to_instance(params, instance)) agent_config = instance # Parse private keys for crypto_id, path in obj.get("private_key_paths", {}).items(): agent_config.private_key_paths.create(crypto_id, path) for crypto_id, path in obj.get("connection_private_key_paths", {}).items(): agent_config.connection_private_key_paths.create(crypto_id, path) # parse connection public ids agent_config.connections = set( map( PublicId.from_str, obj.get(CONNECTIONS, []), ) ) # parse contracts public ids agent_config.contracts = set( map( PublicId.from_str, obj.get(CONTRACTS, []), ) ) # parse protocol public ids agent_config.protocols = set( map( PublicId.from_str, obj.get(PROTOCOLS, []), ) ) # parse skills public ids agent_config.skills = set( map( PublicId.from_str, obj.get(SKILLS, []), ) ) # parse component configurations component_configurations = {} for config in obj.get("component_configurations", []): tmp = deepcopy(config) public_id = PublicId.from_str(tmp.pop("public_id")) type_ = tmp.pop("type") component_id = ComponentId(ComponentType(type_), public_id) component_configurations[component_id] = tmp agent_config.component_configurations = component_configurations return agent_config @property def all_components_id(self) -> List[ComponentId]: """Get list of the all components for this agent config.""" component_type_to_set = { ComponentType.PROTOCOL: self.protocols, ComponentType.CONNECTION: self.connections, ComponentType.CONTRACT: self.contracts, ComponentType.SKILL: self.skills, } result = [] for component_type, public_ids in component_type_to_set.items(): for public_id in public_ids: result.append(ComponentId(component_type, public_id)) return result def update(self, data: Dict, env_vars_friendly: bool = False) -> None: """ Update configuration with other data. To update the component parts, populate the field "component_configurations" as a mapping from ComponentId to configurations. :param data: the data to replace. :param env_vars_friendly: whether or not it is env vars friendly. """ data = copy(data) # update component parts new_component_configurations: Dict = data.pop("component_configurations", {}) updated_component_configurations: Dict[ComponentId, Dict] = copy( self.component_configurations ) for component_id, obj in new_component_configurations.items(): if component_id not in updated_component_configurations: updated_component_configurations[component_id] = obj else: recursive_update( updated_component_configurations[component_id], obj, allow_new_values=True, ) self.check_overrides_valid(data, env_vars_friendly=env_vars_friendly) super().update(data, env_vars_friendly=env_vars_friendly) self.validate_config_data(self.json, env_vars_friendly=env_vars_friendly) self.component_configurations = updated_component_configurations class SpeechActContentConfig(Configuration): """Handle a speech_act content configuration.""" __slots__ = ("args",) def __init__(self, **args: Any) -> None: """Initialize a speech_act content configuration.""" super().__init__() self.args = args # type: Dict[str, str] @property def json(self) -> Dict: """Return the JSON representation.""" return self.args @classmethod def from_json(cls, obj: Dict) -> "SpeechActContentConfig": """Initialize from a JSON object.""" return SpeechActContentConfig(**obj) class ProtocolSpecification(ProtocolConfig): """Handle protocol specification.""" __slots__ = ("speech_acts", "_protobuf_snippets", "_dialogue_config") def __init__( self, name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", description: str = "", protocol_specification_id: Optional[str] = None, ) -> None: """Initialize a protocol specification configuration object.""" super().__init__( name, author, version, license_, aea_version=aea_version, description=description, protocol_specification_id=protocol_specification_id, ) self.speech_acts = CRUDCollection[SpeechActContentConfig]() self._protobuf_snippets = {} # type: Dict self._dialogue_config = {} # type: Dict @property def protobuf_snippets(self) -> Dict: """Get the protobuf snippets.""" return self._protobuf_snippets @protobuf_snippets.setter def protobuf_snippets(self, protobuf_snippets: Dict) -> None: """Set the protobuf snippets.""" self._protobuf_snippets = protobuf_snippets @property def dialogue_config(self) -> Dict: """Get the dialogue config.""" return self._dialogue_config @dialogue_config.setter def dialogue_config(self, dialogue_config: Dict) -> None: """Set the dialogue config.""" self._dialogue_config = dialogue_config @property def json(self) -> Dict: """Return the JSON representation.""" result: Dict[str, Any] = OrderedDict( { "name": self.name, "author": self.author, "version": self.version, "description": self.description, "license": self.license, "aea_version": self.aea_version, "protocol_specification_id": str(self.protocol_specification_id), "speech_acts": { key: speech_act.json for key, speech_act in self.speech_acts.read_all() }, } ) return result @classmethod def _create_or_update_from_json( # type: ignore cls, obj: Dict, instance: Optional["ProtocolSpecification"] = None ) -> "ProtocolSpecification": """Initialize from a JSON object.""" obj = {**(instance.json if instance else {}), **copy(obj)} params = dict( name=cast(str, obj.get("name")), author=cast(str, obj.get("author")), protocol_specification_id=cast(str, obj.get("protocol_specification_id")), version=cast(str, obj.get("version")), license_=cast(str, obj.get("license")), aea_version=cast(str, obj.get("aea_version", "")), description=cast(str, obj.get("description", "")), ) instance = cast( ProtocolSpecification, cls._apply_params_to_instance(params, instance) ) protocol_specification = instance for speech_act, speech_act_content in obj.get("speech_acts", {}).items(): speech_act_content_config = SpeechActContentConfig.from_json( speech_act_content ) protocol_specification.speech_acts.create( speech_act, speech_act_content_config ) return protocol_specification class ContractConfig(ComponentConfiguration): """Handle contract configuration.""" default_configuration_filename = DEFAULT_CONTRACT_CONFIG_FILE package_type = PackageType.CONTRACT schema = "contract-config_schema.json" FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["build_directory"]) __slots__ = ( "dependencies", "description", "contract_interface_paths", "class_name", ) def __init__( self, name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, build_directory: Optional[str] = None, dependencies: Optional[Dependencies] = None, description: str = "", contract_interface_paths: Optional[Dict[str, str]] = None, class_name: str = "", ) -> None: """Initialize a protocol configuration object.""" super().__init__( name, author, version, license_, aea_version, fingerprint, fingerprint_ignore_patterns, build_entrypoint, build_directory, dependencies, ) self.dependencies = dependencies if dependencies is not None else {} self.description = description self.contract_interface_paths = ( contract_interface_paths if contract_interface_paths is not None else {} ) self.class_name = class_name @property def json(self) -> Dict: """Return the JSON representation.""" result = OrderedDict( { "name": self.name, "author": self.author, "version": self.version, "type": self.component_type.value, "description": self.description, "license": self.license, "aea_version": self.aea_version, "fingerprint": self.fingerprint, "fingerprint_ignore_patterns": self.fingerprint_ignore_patterns, "class_name": self.class_name, "contract_interface_paths": self.contract_interface_paths, "dependencies": dependencies_to_json(self.dependencies), } ) if self.build_entrypoint: result["build_entrypoint"] = self.build_entrypoint if self.build_directory: result["build_directory"] = self.build_directory return result @classmethod def _create_or_update_from_json( cls, obj: Dict, instance: Optional["ContractConfig"] = None ) -> "ContractConfig": """Initialize from a JSON object.""" obj = {**(instance.json if instance else {}), **copy(obj)} dependencies = cast( Dependencies, dependencies_from_json(obj.get("dependencies", {})) ) params = dict( name=cast(str, obj.get("name")), author=cast(str, obj.get("author")), version=cast(str, obj.get("version")), license_=cast(str, obj.get("license")), aea_version=cast(str, obj.get("aea_version", "")), fingerprint=cast(Dict[str, str], obj.get("fingerprint", {})), fingerprint_ignore_patterns=cast( Sequence[str], obj.get("fingerprint_ignore_patterns") ), build_entrypoint=cast(Optional[str], obj.get("build_entrypoint")), build_directory=cast(Optional[str], obj.get("build_directory")), dependencies=dependencies, description=cast(str, obj.get("description", "")), contract_interface_paths=cast( Dict[str, str], obj.get("contract_interface_paths", {}) ), class_name=obj.get("class_name", ""), ) instance = cast(ContractConfig, cls._apply_params_to_instance(params, instance)) return instance """The following functions are called from aea.cli.utils.""" def _compute_fingerprint( # pylint: disable=unsubscriptable-object package_directory: Path, ignore_patterns: Optional[Collection[str]] = None, is_recursive: bool = True, ignore_directories: Optional[Collection[str]] = None, ) -> Dict[str, str]: ignore_patterns = ignore_patterns if ignore_patterns is not None else [] ignore_directories = ignore_directories if ignore_directories is not None else [] ignore_patterns = set(ignore_patterns).union(DEFAULT_FINGERPRINT_IGNORE_PATTERNS) hasher = IPFSHashOnly() fingerprints = {} # type: Dict[str, str] # find all valid files of the package all_files = [ x for x in package_directory.glob("**/*" if is_recursive else "*") if x.is_file() and not any(x.match(pattern) for pattern in ignore_patterns) and not (x.parts[0] in ignore_directories) ] for file in all_files: file_hash = hasher.get(str(file)) key = str(file.relative_to(package_directory)) enforce(key not in fingerprints, "Key in fingerprints!") # nosec # use '/' as path separator normalized_path = Path(key).as_posix() fingerprints[normalized_path] = file_hash return fingerprints def _compare_fingerprints( package_configuration: PackageConfiguration, package_directory: Path, is_vendor: bool, item_type: PackageType, is_recursive: bool = True, ) -> None: """ Check fingerprints of a package directory against the fingerprints declared in the configuration file. :param package_configuration: the package configuration object. :param package_directory: the directory of the package. :param is_vendor: whether the package is vendorized or not. :param item_type: the type of the item. :param is_recursive: look up sub directories for files to fingerprint :raises ValueError: if the fingerprints do not match. """ expected_fingerprints = package_configuration.fingerprint ignore_patterns = package_configuration.fingerprint_ignore_patterns actual_fingerprints = _compute_fingerprint( package_directory, ignore_patterns, is_recursive=is_recursive ) if expected_fingerprints != actual_fingerprints: if is_vendor: raise ValueError( ( "Fingerprints for package {} do not match:\nExpected: {}\nActual: {}\n" "Vendorized projects should not be tampered with, please revert any changes to {} {}" ).format( package_directory, pprint.pformat(expected_fingerprints), pprint.pformat(actual_fingerprints), str(item_type), package_configuration.public_id, ) ) if item_type == PackageType.AGENT: raise ValueError( ( "Fingerprints for package {} do not match:\nExpected: {}\nActual: {}\n" "Please fingerprint the package before continuing: 'aea fingerprint'" ).format( package_directory, pprint.pformat(expected_fingerprints), pprint.pformat(actual_fingerprints), ) ) raise ValueError( ( "Fingerprints for package {} do not match:\nExpected: {}\nActual: {}\n" "Please fingerprint the package before continuing: 'aea fingerprint {} {}'" ).format( package_directory, pprint.pformat(expected_fingerprints), pprint.pformat(actual_fingerprints), str(item_type), package_configuration.public_id, ) ) class AEAVersionError(ValueError): """Special Exception for version error.""" def __init__( self, package_id: PublicId, aea_version_specifiers: SpecifierSet ) -> None: """Init exception.""" self.package_id = package_id self.aea_version_specifiers = aea_version_specifiers self.current_aea_version = Version(__aea_version__) super().__init__( f"The CLI version is {self.current_aea_version}, but package {self.package_id} requires version {self.aea_version_specifiers}" ) def _check_aea_version(package_configuration: PackageConfiguration) -> None: """Check the package configuration version against the version of the framework.""" current_aea_version = Version(__aea_version__) version_specifiers = package_configuration.aea_version_specifiers if current_aea_version not in version_specifiers: raise AEAVersionError( package_configuration.public_id, package_configuration.aea_version_specifiers, ) def _compare_public_ids( component_configuration: ComponentConfiguration, package_directory: Path ) -> None: """Compare the public ids in config and init file.""" if component_configuration.package_type != PackageType.SKILL: return filename = "__init__.py" public_id_in_init = _get_public_id_from_file( component_configuration, package_directory, filename ) if ( public_id_in_init is not None and public_id_in_init != component_configuration.public_id ): raise ValueError( # pragma: nocover f"The public id specified in {filename} for package {package_directory} does not match the one specific in {component_configuration.package_type.value}.yaml" ) def _get_public_id_from_file( component_configuration: ComponentConfiguration, package_directory: Path, filename: str, ) -> Optional[PublicId]: """ Get the public id from an init if present. :param component_configuration: the component configuration. :param package_directory: the path to the package directory. :param filename: the file :return: the public id, if found. """ path_to_file = Path(package_directory, filename) module = load_module(component_configuration.prefix_import_path, path_to_file) package_public_id = getattr(module, PACKAGE_PUBLIC_ID_VAR_NAME, None) return package_public_id PACKAGE_TYPE_TO_CONFIG_CLASS: Dict[PackageType, Type[PackageConfiguration]] = { PackageType.AGENT: AgentConfig, PackageType.PROTOCOL: ProtocolConfig, PackageType.CONNECTION: ConnectionConfig, PackageType.SKILL: SkillConfig, PackageType.CONTRACT: ContractConfig, } ================================================ FILE: aea/configurations/constants.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module to declare constants.""" from typing import Dict, List _FETCHAI_IDENTIFIER = "fetchai" _ETHEREUM_IDENTIFIER = "ethereum" _COSMOS_IDENTIFIER = "cosmos" DEFAULT_PROTOCOL = "fetchai/default:latest" SIGNING_PROTOCOL = "fetchai/signing:latest" STATE_UPDATE_PROTOCOL = "fetchai/state_update:latest" LEDGER_CONNECTION = "fetchai/ledger:latest" DEFAULT_LEDGER = _FETCHAI_IDENTIFIER PRIVATE_KEY_PATH_SCHEMA = "{}_private_key.txt" DEFAULT_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(DEFAULT_LEDGER) DEFAULT_LICENSE = "Apache-2.0" DISTRIBUTED_PACKAGES: List[str] = [] DEFAULT_SEARCH_SERVICE_ADDRESS = "fetchai/soef:any" DEFAULT_INPUT_FILE_NAME = "./input_file" DEFAULT_OUTPUT_FILE_NAME = "./output_file" SCAFFOLD_PUBLIC_ID = "fetchai/scaffold:0.1.0" PACKAGES = "packages" REGISTRY_PATH_KEY = "registry_path" DEFAULT_REGISTRY_NAME = PACKAGES VENDOR = "vendor" AGENT = "agent" AGENTS = "agents" CONNECTION = "connection" CONNECTIONS = "connections" CONTRACT = "contract" CONTRACTS = "contracts" PROTOCOL = "protocol" PROTOCOLS = "protocols" SKILL = "skill" SKILLS = "skills" DEFAULT_README_FILE = "README.md" DEFAULT_VERSION = "0.1.0" DEFAULT_AEA_CONFIG_FILE = "aea-config.yaml" DEFAULT_SKILL_CONFIG_FILE = "skill.yaml" DEFAULT_CONNECTION_CONFIG_FILE = "connection.yaml" DEFAULT_CONTRACT_CONFIG_FILE = "contract.yaml" DEFAULT_PROTOCOL_CONFIG_FILE = "protocol.yaml" PACKAGE_PUBLIC_ID_VAR_NAME = "PUBLIC_ID" DEFAULT_FINGERPRINT_IGNORE_PATTERNS = [ ".DS_Store", "*__pycache__/*", "*__pycache__", "*.pyc", DEFAULT_AEA_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, PRIVATE_KEY_PATH_SCHEMA.format("*"), ] DEFAULT_PYPI_INDEX_URL = "https://pypi.org/simple" DEFAULT_GIT_REF = "master" DEFAULT_LOGGING_CONFIG = {"version": 1, "disable_existing_loggers": False} IMPORT_TEMPLATE_1 = "from packages.{author}.{type}.{name}" IMPORT_TEMPLATE_2 = "import packages.{author}.{type}.{name}" DEFAULT_ENV_DOTFILE = ".env" DOTTED_PATH_MODULE_ELEMENT_SEPARATOR = ":" DEFAULT_BUILD_DIR_NAME = ".build" DEFAULT_DEPENDENCIES: Dict[str, Dict] = {"aea-ledger-fetchai": {}} CONFIG_FILE_TO_PACKAGE_TYPE = { DEFAULT_SKILL_CONFIG_FILE: SKILL, DEFAULT_PROTOCOL_CONFIG_FILE: PROTOCOL, DEFAULT_CONNECTION_CONFIG_FILE: CONNECTION, DEFAULT_CONTRACT_CONFIG_FILE: CONTRACT, DEFAULT_AEA_CONFIG_FILE: AGENT, } # type: Dict[str, str] CRYPTO_PLUGIN_GROUP = "aea.cryptos" LEDGER_APIS_PLUGIN_GROUP = "aea.ledger_apis" FAUCET_APIS_PLUGIN_GROUP = "aea.faucet_apis" ALLOWED_GROUPS = { CRYPTO_PLUGIN_GROUP, LEDGER_APIS_PLUGIN_GROUP, FAUCET_APIS_PLUGIN_GROUP, } AEA_MANAGER_DATA_DIRNAME = "data" LAUNCH_SUCCEED_MESSAGE = "Start processing messages..." PROTOCOL_LANGUAGE_PYTHON = "python" PROTOCOL_LANGUAGE_GO = "go" PROTOCOL_LANGUAGE_CPP = "cpp" PROTOCOL_LANGUAGE_JAVA = "java" PROTOCOL_LANGUAGE_CSHARP = "csharp" PROTOCOL_LANGUAGE_RUBY = "ruby" PROTOCOL_LANGUAGE_OBJC = "objc" PROTOCOL_LANGUAGE_JS = "js" SUPPORTED_PROTOCOL_LANGUAGES = [ PROTOCOL_LANGUAGE_PYTHON, PROTOCOL_LANGUAGE_GO, PROTOCOL_LANGUAGE_CPP, PROTOCOL_LANGUAGE_JAVA, PROTOCOL_LANGUAGE_CSHARP, PROTOCOL_LANGUAGE_RUBY, PROTOCOL_LANGUAGE_OBJC, PROTOCOL_LANGUAGE_JS, ] DEFAULT_CERTS_DIR_NAME = ".certs" DEFAULT_IGNORE_DIRS_AGENT_FINGERPRINT = [ SKILLS, PROTOCOLS, CONTRACTS, CONNECTIONS, VENDOR, DEFAULT_BUILD_DIR_NAME, DEFAULT_CERTS_DIR_NAME, ] ITEM_TYPE_TO_PLURAL = { PROTOCOL: PROTOCOLS, AGENT: AGENTS, CONTRACT: CONTRACTS, CONNECTION: CONNECTIONS, SKILL: SKILLS, } ITEM_TYPE_PLURAL_TO_TYPE = {v: k for k, v in ITEM_TYPE_TO_PLURAL.items()} ================================================ FILE: aea/configurations/data_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Base config data types.""" import functools import re from abc import ABC, abstractmethod from enum import Enum from typing import ( Any, Collection, Dict, Generic, List, Optional, Set, Tuple, TypeVar, Union, cast, ) import semver from packaging.specifiers import SpecifierSet from aea.configurations.constants import ( AGENT, CONNECTION, CONTRACT, DEFAULT_GIT_REF, PROTOCOL, SKILL, ) from aea.exceptions import enforce from aea.helpers.base import ( RegexConstrainedString, SIMPLE_ID_REGEX, SimpleId, SimpleIdOrStr, ) T = TypeVar("T") VersionInfoClass = semver.VersionInfo PackageVersionLike = Union[str, semver.VersionInfo] class JSONSerializable(ABC): """Interface for JSON-serializable objects.""" @property @abstractmethod def json(self) -> Dict: """Compute the JSON representation.""" @classmethod @abstractmethod def from_json(cls, obj: Dict) -> "JSONSerializable": """Build from a JSON object.""" @functools.total_ordering class PackageVersion: """A package version.""" _version: PackageVersionLike def __init__(self, version_like: PackageVersionLike) -> None: """ Initialize a package version. :param version_like: a string, os a semver.VersionInfo object. """ if isinstance(version_like, str) and version_like == "latest": self._version = version_like elif isinstance(version_like, str) and version_like == "any": self._version = version_like elif isinstance(version_like, str): self._version = VersionInfoClass.parse(version_like) elif isinstance(version_like, VersionInfoClass): self._version = version_like else: raise ValueError("Version type not valid.") @property def is_latest(self) -> bool: """Check whether the version is 'latest'.""" return isinstance(self._version, str) and self._version == "latest" def __str__(self) -> str: """Get the string representation.""" return str(self._version) def __eq__(self, other: Any) -> bool: """Check equality.""" return isinstance(other, PackageVersion) and self._version == other._version def __lt__(self, other: Any) -> bool: """Compare with another object.""" enforce( isinstance(other, PackageVersion), f"Cannot compare {type(self)} with type {type(other)}.", ) other = cast(PackageVersion, other) if self.is_latest or other.is_latest: return self.is_latest < other.is_latest return str(self) < str(other) class PackageType(Enum): """Package types.""" AGENT = AGENT PROTOCOL = PROTOCOL CONNECTION = CONNECTION CONTRACT = CONTRACT SKILL = SKILL def to_plural(self) -> str: """ Get the plural name. >>> PackageType.AGENT.to_plural() 'agents' >>> PackageType.PROTOCOL.to_plural() 'protocols' >>> PackageType.CONNECTION.to_plural() 'connections' >>> PackageType.SKILL.to_plural() 'skills' >>> PackageType.CONTRACT.to_plural() 'contracts' :return: pluralised package type """ return self.value + "s" def __str__(self) -> str: """Convert to string.""" return str(self.value) class ComponentType(Enum): """Enum of component types supported.""" PROTOCOL = PROTOCOL CONNECTION = CONNECTION SKILL = SKILL CONTRACT = CONTRACT def to_package_type(self) -> PackageType: """Get package type for component type.""" return PackageType(self.value) @staticmethod def plurals() -> Collection[str]: # pylint: disable=unsubscriptable-object """ Get the collection of type names, plural. >>> ComponentType.plurals() ['protocols', 'connections', 'skills', 'contracts'] :return: list of all pluralised component types """ return list(map(lambda x: x.to_plural(), ComponentType)) def to_plural(self) -> str: """ Get the plural version of the component type. >>> ComponentType.PROTOCOL.to_plural() 'protocols' >>> ComponentType.CONNECTION.to_plural() 'connections' >>> ComponentType.SKILL.to_plural() 'skills' >>> ComponentType.CONTRACT.to_plural() 'contracts' :return: pluralised component type """ return self.value + "s" def __str__(self) -> str: """Get the string representation.""" return str(self.value) PackageIdPrefix = Tuple[ComponentType, str, str] class PublicId(JSONSerializable): """This class implement a public identifier. A public identifier is composed of three elements: - author - name - version The concatenation of those three elements gives the public identifier: author/name:version >>> public_id = PublicId("author", "my_package", "0.1.0") >>> assert public_id.author == "author" >>> assert public_id.name == "my_package" >>> assert public_id.version == "0.1.0" >>> another_public_id = PublicId("author", "my_package", "0.1.0") >>> assert hash(public_id) == hash(another_public_id) >>> assert public_id == another_public_id >>> latest_public_id = PublicId("author", "my_package", "latest") >>> latest_public_id >>> latest_public_id.package_version.is_latest True """ __slots__ = ("_author", "_name", "_package_version") AUTHOR_REGEX = SIMPLE_ID_REGEX PACKAGE_NAME_REGEX = SIMPLE_ID_REGEX VERSION_NUMBER_PART_REGEX = r"(0|[1-9]\d*)" VERSION_REGEX = rf"(any|latest|({VERSION_NUMBER_PART_REGEX})\.({VERSION_NUMBER_PART_REGEX})\.({VERSION_NUMBER_PART_REGEX})(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)" PUBLIC_ID_REGEX = rf"^({AUTHOR_REGEX})/({PACKAGE_NAME_REGEX})(:({VERSION_REGEX}))?$" PUBLIC_ID_URI_REGEX = ( rf"^({AUTHOR_REGEX})/({PACKAGE_NAME_REGEX})/({VERSION_REGEX})$" ) ANY_VERSION = "any" LATEST_VERSION = "latest" def __init__( self, author: SimpleIdOrStr, name: SimpleIdOrStr, version: Optional[PackageVersionLike] = None, ) -> None: """Initialize the public identifier.""" self._author = SimpleId(author) self._name = SimpleId(name) self._package_version = ( PackageVersion(version) if version is not None else PackageVersion(self.LATEST_VERSION) ) @property def author(self) -> str: """Get the author.""" return str(self._author) @property def name(self) -> str: """Get the name.""" return str(self._name) @property def version(self) -> str: """Get the version string.""" return str(self._package_version) @property def package_version(self) -> PackageVersion: """Get the package version object.""" return self._package_version def to_any(self) -> "PublicId": """Return the same public id, but with any version.""" return PublicId(self.author, self.name, self.ANY_VERSION) def same_prefix(self, other: "PublicId") -> bool: """Check if the other public id has the same author and name of this.""" return self.name == other.name and self.author == other.author def to_latest(self) -> "PublicId": """Return the same public id, but with latest version.""" return PublicId(self.author, self.name, self.LATEST_VERSION) @classmethod def is_valid_str(cls, public_id_string: str) -> bool: """ Check if a string is a public id. :param public_id_string: the public id in string format. :return: bool indicating validity """ match = re.match(cls.PUBLIC_ID_REGEX, public_id_string) return match is not None @classmethod def from_str(cls, public_id_string: str) -> "PublicId": """ Initialize the public id from the string. >>> str(PublicId.from_str("author/package_name:0.1.0")) 'author/package_name:0.1.0' A bad formatted input raises value error: >>> PublicId.from_str("bad/formatted:input") Traceback (most recent call last): ... ValueError: Input 'bad/formatted:input' is not well formatted. :param public_id_string: the public id in string format. :return: the public id object. :raises ValueError: if the string in input is not well formatted. """ match = re.match(cls.PUBLIC_ID_REGEX, public_id_string) if match is None: raise ValueError( "Input '{}' is not well formatted.".format(public_id_string) ) username = match.group(1) package_name = match.group(2) version = match.group(3)[1:] if ":" in public_id_string else None return PublicId(username, package_name, version) @classmethod def try_from_str(cls, public_id_string: str) -> Optional["PublicId"]: """ Safely try to get public id from string. :param public_id_string: the public id in string format. :return: the public id object or None """ result: Optional[PublicId] = None try: result = cls.from_str(public_id_string) except ValueError: pass return result @classmethod def from_uri_path(cls, public_id_uri_path: str) -> "PublicId": """ Initialize the public id from the string. >>> str(PublicId.from_uri_path("author/package_name/0.1.0")) 'author/package_name:0.1.0' A bad formatted input raises value error: >>> PublicId.from_uri_path("bad/formatted:input") Traceback (most recent call last): ... ValueError: Input 'bad/formatted:input' is not well formatted. :param public_id_uri_path: the public id in uri path string format. :return: the public id object. :raises ValueError: if the string in input is not well formatted. """ if not re.match(cls.PUBLIC_ID_URI_REGEX, public_id_uri_path): raise ValueError( "Input '{}' is not well formatted.".format(public_id_uri_path) ) username, package_name, version = re.findall( cls.PUBLIC_ID_URI_REGEX, public_id_uri_path )[0][:3] return PublicId(username, package_name, version) @property def to_uri_path(self) -> str: """ Turn the public id into a uri path string. :return: uri path string """ return "{author}/{name}/{version}".format( author=self.author, name=self.name, version=self.version ) @property def json(self) -> Dict: """Compute the JSON representation.""" return {"author": self.author, "name": self.name, "version": self.version} @classmethod def from_json(cls, obj: Dict) -> "PublicId": """Build from a JSON object.""" return PublicId( obj["author"], obj["name"], obj["version"], ) def __hash__(self) -> int: """Get the hash.""" return hash((self.author, self.name, self.version)) def __str__(self) -> str: """Get the string representation.""" return "{author}/{name}:{version}".format( author=self.author, name=self.name, version=self.version ) def __repr__(self) -> str: """Get the representation.""" return f"<{self}>" def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, PublicId) and self.author == other.author and self.name == other.name and self.version == other.version ) def __lt__(self, other: Any) -> bool: """ Compare two public ids. >>> public_id_1 = PublicId("author_1", "name_1", "0.1.0") >>> public_id_2 = PublicId("author_1", "name_1", "0.1.1") >>> public_id_3 = PublicId("author_1", "name_2", "0.1.0") >>> public_id_1 > public_id_2 False >>> public_id_1 < public_id_2 True >>> public_id_1 < public_id_3 Traceback (most recent call last): ... ValueError: The public IDs author_1/name_1:0.1.0 and author_1/name_2:0.1.0 cannot be compared. Their author or name attributes are different. :param other: the object to compate to :raises ValueError: if the public ids cannot be confirmed :return: whether or not the inequality is satisfied """ if ( isinstance(other, PublicId) and self.author == other.author and self.name == other.name ): return self.package_version < other.package_version raise ValueError( "The public IDs {} and {} cannot be compared. Their author or name attributes are different.".format( self, other ) ) class PackageId: """A package identifier.""" PACKAGE_TYPE_REGEX = r"({}|{}|{}|{}|{})".format( PackageType.AGENT, PackageType.PROTOCOL, PackageType.SKILL, PackageType.CONNECTION, PackageType.CONTRACT, ) PACKAGE_ID_URI_REGEX = r"{}/{}".format( PACKAGE_TYPE_REGEX, PublicId.PUBLIC_ID_URI_REGEX[1:-1] ) __slots__ = ("_package_type", "_public_id") def __init__( self, package_type: Union[PackageType, str], public_id: PublicId ) -> None: """ Initialize the package id. :param package_type: the package type. :param public_id: the public id. """ self._package_type = PackageType(package_type) self._public_id = public_id @property def package_type(self) -> PackageType: """Get the package type.""" return self._package_type @property def public_id(self) -> PublicId: """Get the public id.""" return self._public_id @property def author(self) -> str: """Get the author of the package.""" return self.public_id.author @property def name(self) -> str: """Get the name of the package.""" return self.public_id.name @property def version(self) -> str: """Get the version of the package.""" return self.public_id.version @property def package_prefix(self) -> Tuple[PackageType, str, str]: """Get the package identifier without the version.""" return self.package_type, self.author, self.name @classmethod def from_uri_path(cls, package_id_uri_path: str) -> "PackageId": """ Initialize the package id from the string. >>> str(PackageId.from_uri_path("skill/author/package_name/0.1.0")) '(skill, author/package_name:0.1.0)' A bad formatted input raises value error: >>> PackageId.from_uri_path("very/bad/formatted:input") Traceback (most recent call last): ... ValueError: Input 'very/bad/formatted:input' is not well formatted. :param package_id_uri_path: the package id in uri path string format. :return: the package id object. :raises ValueError: if the string in input is not well formatted. """ if not re.match(cls.PACKAGE_ID_URI_REGEX, package_id_uri_path): raise ValueError( "Input '{}' is not well formatted.".format(package_id_uri_path) ) package_type_str, username, package_name, version = re.findall( cls.PACKAGE_ID_URI_REGEX, package_id_uri_path )[0][:4] package_type = PackageType(package_type_str) public_id = PublicId(username, package_name, version) return PackageId(package_type, public_id) @property def to_uri_path(self) -> str: """ Turn the package id into a uri path string. :return: uri path string """ return f"{str(self.package_type)}/{self.author}/{self.name}/{self.version}" def __hash__(self) -> int: """Get the hash.""" return hash((self.package_type, self.public_id)) def __str__(self) -> str: """Get the string representation.""" return "({package_type}, {public_id})".format( package_type=self.package_type.value, public_id=self.public_id, ) def __repr__(self) -> str: """Get the object representation in string.""" return f"PackageId{self.__str__()}" def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, PackageId) and self.package_type == other.package_type and self.public_id == other.public_id ) def __lt__(self, other: Any) -> bool: """Compare two public ids.""" return str(self) < str(other) class ComponentId(PackageId): """ Class to represent a component identifier. A component id is a package id, but excludes the case when the package is an agent. >>> pacakge_id = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) >>> component_id = ComponentId(ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0")) >>> pacakge_id == component_id True >>> component_id2 = ComponentId(ComponentType.PROTOCOL, PublicId("author", "name", "0.1.1")) >>> pacakge_id == component_id2 False """ def __init__( self, component_type: Union[ComponentType, str], public_id: PublicId ) -> None: """ Initialize the component id. :param component_type: the component type. :param public_id: the public id. """ component_type = ComponentType(component_type) super().__init__(component_type.to_package_type(), public_id) @property def component_type(self) -> ComponentType: """Get the component type.""" return ComponentType(self.package_type.value) @property def component_prefix(self) -> PackageIdPrefix: """Get the component identifier without the version.""" package_prefix = super().package_prefix package_type, author, name = package_prefix return ComponentType(package_type.value), author, name def same_prefix(self, other: "ComponentId") -> bool: """Check if the other component id has the same type, author and name of this.""" return ( self.component_type == other.component_type and self.public_id.same_prefix(other.public_id) ) @property def prefix_import_path(self) -> str: """Get the prefix import path for this component.""" return "packages.{}.{}.{}".format( self.public_id.author, self.component_type.to_plural(), self.public_id.name ) @property def json(self) -> Dict: """Get the JSON representation.""" return dict(**self.public_id.json, type=str(self.component_type)) @classmethod def from_json(cls, json_data: Dict) -> "ComponentId": """Create component id from json data.""" return cls( component_type=json_data["type"], public_id=PublicId.from_json(json_data) ) class PyPIPackageName(RegexConstrainedString): """A PyPI Package name.""" REGEX = re.compile(r"^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9])$") class GitRef(RegexConstrainedString): """ A Git reference. It can be a branch name, a commit hash or a tag. """ REGEX = re.compile(r"^[A-Za-z0-9/.\-_]+$") class Dependency: """ This class represents a PyPI dependency. It contains the following information: - version: a version specifier(s) (e.g. '==0.1.0'). - index: the PyPI index where to download the package from (default: https://pypi.org) - git: the URL to the Git repository (e.g. https://github.com/fetchai/agents-aea.git) - ref: either the branch name, the tag, the commit number or a Git reference (default: 'master'.) If the 'git' field is set, the 'version' field will be ignored. These fields will be forwarded to the 'pip' command. """ __slots__ = ("_name", "_version", "_index", "_git", "_ref") def __init__( self, name: Union[PyPIPackageName, str], version: Union[str, SpecifierSet] = "", index: Optional[str] = None, git: Optional[str] = None, ref: Optional[Union[GitRef, str]] = None, ) -> None: """ Initialize a PyPI dependency. :param name: the package name. :param version: the specifier set object :param index: the URL to the PyPI server. :param git: the URL to a git repository. :param ref: the Git reference (branch/commit/tag). """ self._name: PyPIPackageName = PyPIPackageName(name) self._version: SpecifierSet = self._parse_version(version) self._index: Optional[str] = index self._git: Optional[str] = git self._ref: Optional[GitRef] = GitRef(ref) if ref is not None else None @property def name(self) -> str: """Get the name.""" return str(self._name) @property def version(self) -> str: """Get the version.""" return str(self._version) @property def index(self) -> Optional[str]: """Get the index.""" return str(self._index) if self._index else None @property def git(self) -> Optional[str]: """Get the git.""" return str(self._git) if self._git else None @property def ref(self) -> Optional[str]: """Get the ref.""" return str(self._ref) if self._ref else None @staticmethod def _parse_version(version: Union[str, SpecifierSet]) -> SpecifierSet: """ Parse a version specifier set. :param version: the version, a string or a SpecifierSet instance. :return: the SpecifierSet instance. """ return version if isinstance(version, SpecifierSet) else SpecifierSet(version) @classmethod def from_json(cls, obj: Dict[str, Dict[str, str]]) -> "Dependency": """Parse a dependency object from a dictionary.""" if len(obj) != 1: raise ValueError(f"Only one key allowed, found {set(obj.keys())}") name, attributes = list(obj.items())[0] allowed_keys = {"version", "index", "git", "ref"} not_allowed_keys = set(attributes.keys()).difference(allowed_keys) if len(not_allowed_keys) > 0: raise ValueError(f"Not allowed keys: {not_allowed_keys}") version = attributes.get("version", "") index = attributes.get("index", None) git = attributes.get("git", None) ref = attributes.get("ref", None) return Dependency(name=name, version=version, index=index, git=git, ref=ref) def to_json(self) -> Dict[str, Dict[str, str]]: """Transform the object to JSON.""" result = {} if self.version != "": result["version"] = self.version if self.index is not None: result["index"] = self.index if self.git is not None: result["git"] = cast(str, self.git) if self.ref is not None: result["ref"] = cast(str, self.ref) return {self.name: result} def get_pip_install_args(self) -> List[str]: """Get 'pip install' arguments.""" name = self.name index = self.index git_url = self.git revision = self.ref if self.ref is not None else DEFAULT_GIT_REF version_constraint = str(self.version) command: List[str] = [] if index is not None: command += ["-i", index] if git_url is not None: command += ["git+" + git_url + "@" + revision + "#egg=" + name] else: command += [name + version_constraint] return command def __str__(self) -> str: """Get the string representation.""" return f"{self.__class__.__name__}(name='{self.name}', version='{self.version}', index='{self.index}', git='{self.git}', ref='{self.ref}')" def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, Dependency) and self._name == other._name and self._version == other._version and self._index == other._index and self._git == other._git and self._ref == other._ref ) Dependencies = Dict[str, Dependency] """ A dictionary from package name to dependency data structure (see above). The package name must satisfy the constraints on Python packages names. The main advantage of having a dictionary is that we implicitly filter out dependency duplicates. We cannot have two items with the same package name since the keys of a YAML object form a set. """ class CRUDCollection(Generic[T]): """Interface of a CRUD collection.""" __slots__ = ("_items_by_id",) def __init__(self) -> None: """Instantiate a CRUD collection.""" self._items_by_id = {} # type: Dict[str, T] def create(self, item_id: str, item: T) -> None: """ Add an item. :param item_id: the item id. :param item: the item to be added. :raises ValueError: if the item with the same id is already in the collection. """ if item_id in self._items_by_id: raise ValueError("Item with name {} already present!".format(item_id)) self._items_by_id[item_id] = item def read(self, item_id: str) -> Optional[T]: """ Get an item by its name. :param item_id: the item id. :return: the associated item, or None if the item id is not present. """ return self._items_by_id.get(item_id, None) def update(self, item_id: str, item: T) -> None: """ Update an existing item. :param item_id: the item id. :param item: the item to be added. """ self._items_by_id[item_id] = item def delete(self, item_id: str) -> None: """Delete an item.""" if item_id in self._items_by_id.keys(): del self._items_by_id[item_id] def read_all(self) -> List[Tuple[str, T]]: """Read all the items.""" return [ # pylint: disable=unnecessary-comprehension (k, v) for k, v in self._items_by_id.items() ] def keys(self) -> Set[str]: """Get the set of keys.""" return set(self._items_by_id.keys()) ================================================ FILE: aea/configurations/loader.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the parser for configuration file.""" from pathlib import Path from typing import Any, Dict, Generic, List, TextIO, Type, TypeVar, Union, cast import yaml import aea from aea.configurations.base import ( AgentConfig, ComponentConfiguration, ComponentId, ComponentType, ConnectionConfig, ContractConfig, PACKAGE_TYPE_TO_CONFIG_CLASS, PackageConfiguration, PackageType, ProtocolConfig, ProtocolSpecification, SkillConfig, ) from aea.configurations.validation import ConfigValidator, make_jsonschema_base_uri from aea.exceptions import enforce from aea.helpers.io import open_file from aea.helpers.yaml_utils import yaml_dump, yaml_dump_all, yaml_load, yaml_load_all _STARTING_INDEX_CUSTOM_CONFIGS = 1 _ = make_jsonschema_base_uri # for tests compatibility T = TypeVar( "T", AgentConfig, SkillConfig, ConnectionConfig, ContractConfig, ProtocolConfig, ProtocolSpecification, ) class BaseConfigLoader: """Base class for configuration loader classes.""" def __init__(self, schema_filename: str) -> None: """ Initialize the parser for configuration files. :param schema_filename: the path to the JSON-schema file in 'aea/configurations/schemas'. """ self._validator = ConfigValidator(schema_filename) @property def validator(self) -> ConfigValidator: """Get the json schema validator.""" return self._validator def validate(self, json_data: Dict) -> None: """ Validate a JSON object. :param json_data: the JSON data. """ self.validator.validate(json_data) @property def required_fields(self) -> List[str]: """ Get the required fields. :return: list of required fields. """ return self.validator.required_fields class ConfigLoader(Generic[T], BaseConfigLoader): """Parsing, serialization and validation for package configuration files.""" def __init__( self, schema_filename: str, configuration_class: Type[T], skip_aea_validation: bool = True, ) -> None: """ Initialize the parser for configuration files. :param schema_filename: the path to the JSON-schema file in 'aea/configurations/schemas'. :param configuration_class: the configuration class (e.g. AgentConfig, SkillConfig etc.) :param skip_aea_validation: if True, the validation of the AEA version is skipped. """ super().__init__(schema_filename) self._configuration_class = configuration_class # type: Type[T] self._skip_aea_validation = skip_aea_validation @property def configuration_class(self) -> Type[T]: """Get the configuration class of the loader.""" return self._configuration_class def validate(self, json_data: Dict) -> None: """ Validate a JSON representation of an AEA package. First, checks whether the AEA version is compatible with the configuration file. Then, validates the JSON object against the specific schema. :param json_data: the JSON data. """ if not self._skip_aea_validation: aea_version_specifier_set = AgentConfig.parse_aea_version_specifier( json_data["aea_version"] ) aea_version = aea.__version__ enforce( aea_version_specifier_set.contains(aea_version), f"AEA version in use '{aea_version}' is not compatible with the specifier set '{aea_version_specifier_set}'.", ) super().validate(json_data) def load_protocol_specification( self, file_pointer: TextIO ) -> ProtocolSpecification: """ Load an agent configuration file. :param file_pointer: the file pointer to the configuration file :return: the configuration object. :raises """ yaml_data = yaml.safe_load_all(file_pointer) yaml_documents = list(yaml_data) configuration_file_json = yaml_documents[0] if len(yaml_documents) == 1: protobuf_snippets_json = {} dialogue_configuration = {} # type: Dict elif len(yaml_documents) == 2: protobuf_snippets_json = ( {} if "initiation" in yaml_documents[1] else yaml_documents[1] ) dialogue_configuration = ( yaml_documents[1] if "initiation" in yaml_documents[1] else {} ) elif len(yaml_documents) == 3: protobuf_snippets_json = yaml_documents[1] dialogue_configuration = yaml_documents[2] else: raise ValueError( "Incorrect number of Yaml documents in the protocol specification." ) self.validate(configuration_file_json) protocol_specification = cast( ProtocolSpecification, self.configuration_class.from_json(configuration_file_json), ) protocol_specification.protobuf_snippets = protobuf_snippets_json protocol_specification.dialogue_config = dialogue_configuration return protocol_specification def load(self, file_pointer: TextIO) -> T: """ Load a configuration file. :param file_pointer: the file pointer to the configuration file :return: the configuration object. """ if self.configuration_class.package_type == PackageType.AGENT: return cast(T, self._load_agent_config(file_pointer)) return self._load_component_config(file_pointer) def dump(self, configuration: T, file_pointer: TextIO) -> None: """Dump a configuration. :param configuration: the configuration to be dumped. :param file_pointer: the file pointer to the configuration file """ if self.configuration_class.package_type == PackageType.AGENT: self._dump_agent_config(cast(AgentConfig, configuration), file_pointer) else: self._dump_component_config(configuration, file_pointer) @classmethod def from_configuration_type( cls, configuration_type: Union[PackageType, str], **kwargs: Any ) -> "ConfigLoader": """ Get the configuration loader from the type. :param configuration_type: the type of configuration :param kwargs: keyword arguments to the configuration loader constructor. :return: the configuration loader """ configuration_type = PackageType(configuration_type) return ConfigLoaders.from_package_type(configuration_type, **kwargs) def _load_component_config(self, file_pointer: TextIO) -> T: """Load a component configuration.""" configuration_file_json = yaml_load(file_pointer) return self._load_from_json(configuration_file_json) def _load_from_json(self, configuration_file_json: Dict) -> T: """Load component configuration from JSON object.""" self.validate(configuration_file_json) key_order = list(configuration_file_json.keys()) configuration_obj = cast( T, self.configuration_class.from_json(configuration_file_json) ) configuration_obj._key_order = key_order # pylint: disable=protected-access return configuration_obj def load_agent_config_from_json( self, configuration_json: List[Dict], validate: bool = True ) -> AgentConfig: """ Load agent configuration from configuration json data. :param configuration_json: list of dicts with aea configuration :param validate: whether or not to validate :return: AgentConfig instance """ if len(configuration_json) == 0: raise ValueError("Agent configuration file was empty.") agent_config_json = configuration_json[0] if validate: self.validate(agent_config_json) key_order = list(agent_config_json.keys()) agent_configuration_obj = cast( AgentConfig, self.configuration_class.from_json(agent_config_json) ) agent_configuration_obj._key_order = ( # pylint: disable=protected-access key_order ) component_configurations = self._get_component_configurations( configuration_json ) agent_configuration_obj.component_configurations = component_configurations return agent_configuration_obj def _get_component_configurations( self, configuration_file_jsons: List[Dict] ) -> Dict[ComponentId, Dict]: """ Get the component configurations from the tail pages of the aea-config.yaml file. :param configuration_file_jsons: the JSON objects of the custom configurations of a aea-config.yaml file. :return: a dictionary whose keys are component ids and values are the configurations. """ component_configurations: Dict[ComponentId, Dict] = {} # load the other components. for i, component_configuration_json in enumerate( configuration_file_jsons[_STARTING_INDEX_CUSTOM_CONFIGS:] ): component_id = self._process_component_section( i, component_configuration_json ) if component_id in component_configurations: raise ValueError( f"Configuration of component {component_id} occurs more than once." ) component_configurations[component_id] = component_configuration_json return component_configurations def _load_agent_config(self, file_pointer: TextIO) -> AgentConfig: """Load an agent configuration.""" configuration_file_jsons = yaml_load_all(file_pointer) return self.load_agent_config_from_json(configuration_file_jsons) def _dump_agent_config( self, configuration: AgentConfig, file_pointer: TextIO ) -> None: """Dump agent configuration.""" agent_config_part = configuration.ordered_json self.validate(agent_config_part) agent_config_part.pop("component_configurations") result = [agent_config_part] + configuration.component_configurations_json() yaml_dump_all(result, file_pointer) def _dump_component_config(self, configuration: T, file_pointer: TextIO) -> None: """Dump component configuration.""" result = configuration.ordered_json self.validate(result) yaml_dump(result, file_pointer) def _process_component_section( self, component_index: int, component_configuration_json: Dict ) -> ComponentId: """ Process a component configuration in an agent configuration file. It breaks down in: - extract the component id - validate the component configuration - check that there are only configurable fields :param component_index: the index of the component in the file. :param component_configuration_json: the JSON object. :return: the processed component configuration. """ component_id = self.validator.split_component_id_and_config( component_index, component_configuration_json ) self.validator.validate_component_configuration( component_id, component_configuration_json ) return component_id class ConfigLoaders: """Configuration Loader class to load any package type.""" @classmethod def from_package_type( cls, configuration_type: Union[PackageType, str], **kwargs: Any ) -> "ConfigLoader": """ Get a config loader from the configuration type. :param configuration_type: the configuration type. :param kwargs: keyword arguments to the configuration loader constructor. :return: configuration loader """ config_class: Type[PackageConfiguration] = PACKAGE_TYPE_TO_CONFIG_CLASS[ PackageType(configuration_type) ] return ConfigLoader(config_class.schema, config_class, **kwargs) def load_component_configuration( component_type: ComponentType, directory: Path, skip_consistency_check: bool = False, skip_aea_validation: bool = True, ) -> ComponentConfiguration: """ Load configuration and check that it is consistent against the directory. :param component_type: the component type. :param directory: the root of the package :param skip_consistency_check: if True, the consistency check are skipped. :param skip_aea_validation: if True, the validation of the AEA version is skipped. :return: the configuration object. """ package_type = component_type.to_package_type() configuration_object = load_package_configuration( package_type, directory, skip_consistency_check, skip_aea_validation ) configuration_object = cast(ComponentConfiguration, configuration_object) return configuration_object def load_package_configuration( package_type: PackageType, directory: Path, skip_consistency_check: bool = False, skip_aea_validation: bool = True, ) -> PackageConfiguration: """ Load configuration and check that it is consistent against the directory. :param package_type: the package type. :param directory: the root of the package :param skip_consistency_check: if True, the consistency check are skipped. :param skip_aea_validation: if True, the validation of the AEA version is skipped. :return: the configuration object. """ configuration_object = _load_configuration_object( package_type, directory, skip_aea_validation ) if not skip_consistency_check and isinstance( configuration_object, ComponentConfiguration ): configuration_object._check_configuration_consistency( # pylint: disable=protected-access directory ) return configuration_object def _load_configuration_object( package_type: PackageType, directory: Path, skip_aea_validation: bool = True ) -> PackageConfiguration: """ Load the configuration object, without consistency checks. :param package_type: the package type. :param directory: the directory of the configuration. :param skip_aea_validation: if True, the validation of the AEA version is skipped. :return: the configuration object. :raises FileNotFoundError: if the configuration file is not found. """ configuration_loader = ConfigLoader.from_configuration_type( package_type, skip_aea_validation=skip_aea_validation ) configuration_filename = ( configuration_loader.configuration_class.default_configuration_filename ) configuration_filepath = directory / configuration_filename try: with open_file(configuration_filepath) as fp: configuration_object = configuration_loader.load(fp) except FileNotFoundError: raise FileNotFoundError( "{} configuration not found: {}".format( package_type.value.capitalize(), configuration_filepath ) ) return configuration_object ================================================ FILE: aea/configurations/manager.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the AgentConfigManager.""" import json import os from copy import deepcopy from pathlib import Path from typing import Callable, Dict, List, NewType, Optional, Set, Tuple, Union, cast from aea.configurations.base import ( AgentConfig, ComponentConfiguration, ComponentId, DEFAULT_AEA_CONFIG_FILE, PackageType, ) from aea.configurations.constants import ( AGENT, AGENTS, CONNECTIONS, CONTRACTS, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, PROTOCOLS, SKILLS, VENDOR, ) from aea.configurations.data_types import ComponentType, PackageIdPrefix, PublicId from aea.configurations.loader import ConfigLoader, load_component_configuration from aea.configurations.validation import SAME_MARK, filter_data from aea.exceptions import AEAException, enforce from aea.helpers.env_vars import apply_env_variables from aea.helpers.io import open_file from aea.helpers.storage.backends.base import JSON_TYPES from aea.helpers.yaml_utils import yaml_load_all ALLOWED_PATH_ROOTS = [ AGENT, CONNECTIONS, CONTRACTS, PROTOCOLS, SKILLS, VENDOR, ] RESOURCE_TYPE_TO_CONFIG_FILE = { SKILLS: DEFAULT_SKILL_CONFIG_FILE, PROTOCOLS: DEFAULT_PROTOCOL_CONFIG_FILE, CONNECTIONS: DEFAULT_CONNECTION_CONFIG_FILE, CONTRACTS: DEFAULT_CONTRACT_CONFIG_FILE, } # type: Dict[str, str] JsonPath = List[str] VariablePath = Union[str, JsonPath] class VariableDoesNotExist(ValueError): """Variable does not exist in a config exception.""" NotExistsType = NewType("NotExistsType", object) NotExists = NotExistsType(None) def _try_get_configuration_object_from_aea_config( agent_config: AgentConfig, component_id: ComponentId ) -> Optional[Dict]: """ Try to get the configuration object in the AEA config. The result is not guaranteed because there might not be any :param agent_config: the agent configuration. :param component_id: the component id whose prefix points to the relevant custom configuration in the AEA configuration file. :return: the configuration object to get/set an attribute. """ if component_id is None: # this is the case when the prefix of the json path is 'agent'. return None # pragma: nocover type_, author, name = ( component_id.component_type, component_id.author, component_id.name, ) component_ids = set(agent_config.component_configurations.keys()) true_component_id = _try_get_component_id_from_prefix( component_ids, (type_, author, name) ) if true_component_id is not None: return agent_config.component_configurations.get(true_component_id) return None def _try_get_component_id_from_prefix( component_ids: Set[ComponentId], component_prefix: PackageIdPrefix ) -> Optional[ComponentId]: """ Find the component id matching a component prefix. :param component_ids: the set of component id. :param component_prefix: the component prefix. :return: the component id that matches the prefix. :raises AEAEnforceError: if there are more than two components as candidate results. # noqa: DAR402 """ type_, author, name = component_prefix results = list( filter( lambda x: x.component_type == type_ and x.author == author and x.name == name, component_ids, ) ) if len(results) == 0: return None enforce(len(results) == 1, f"Expected only one component, found {len(results)}.") return results[0] def handle_dotted_path( value: str, author: str, aea_project_path: Union[str, Path] = ".", ) -> Tuple[List[str], Path, ConfigLoader, Optional[ComponentId]]: """Separate the path between path to resource and json path to attribute. Allowed values: 'agent.an_attribute_name' 'protocols.my_protocol.an_attribute_name' 'connections.my_connection.an_attribute_name' 'contracts.my_contract.an_attribute_name' 'skills.my_skill.an_attribute_name' 'vendor.author.[protocols|contracts|connections|skills].package_name.attribute_name We also return the component id to retrieve the configuration of a specific component. Notice that at this point we don't know the version, so we put 'latest' as version, but later we will ignore it because we will filter with only the component prefix (i.e. the triple type, author and name). :param value: dotted path. :param author: the author string. :param aea_project_path: project path :return: Tuple[list of settings dict keys, filepath, config loader, component id]. """ parts = value.split(".") aea_project_path = Path(aea_project_path) root = parts[0] if root not in ALLOWED_PATH_ROOTS: raise AEAException( "The root of the dotted path must be one of: {}".format(ALLOWED_PATH_ROOTS) ) if ( len(parts) < 2 or parts[0] == AGENT and len(parts) < 2 or parts[0] == VENDOR and len(parts) < 5 or parts[0] != AGENT and len(parts) < 3 ): raise AEAException( "The path is too short. Please specify a path up to an attribute name." ) # if the root is 'agent', stop. if root == AGENT: resource_type_plural = AGENTS path_to_resource_configuration = Path(DEFAULT_AEA_CONFIG_FILE) json_path = parts[1:] component_id = None elif root == VENDOR: # parse json path resource_author = parts[1] resource_type_plural = parts[2] resource_name = parts[3] # extract component id resource_type_singular = resource_type_plural[:-1] try: component_type = ComponentType(resource_type_singular) except ValueError as e: raise AEAException( f"'{resource_type_plural}' is not a valid component type. Please use one of {ComponentType.plurals()}." ) from e component_id = ComponentId( component_type, PublicId(resource_author, resource_name) ) # find path to the resource directory path_to_resource_directory = ( aea_project_path / VENDOR / resource_author / resource_type_plural / resource_name ) path_to_resource_configuration = ( path_to_resource_directory / RESOURCE_TYPE_TO_CONFIG_FILE[resource_type_plural] ) json_path = parts[4:] if not path_to_resource_directory.exists(): raise AEAException( # pragma: nocover "Resource vendor/{}/{}/{} does not exist.".format( resource_author, resource_type_plural, resource_name ) ) else: # navigate the resources of the agent to reach the target configuration file. resource_type_plural = root resource_name = parts[1] # extract component id resource_type_singular = resource_type_plural[:-1] component_type = ComponentType(resource_type_singular) resource_author = author component_id = ComponentId( component_type, PublicId(resource_author, resource_name) ) # find path to the resource directory path_to_resource_directory = Path(".") / resource_type_plural / resource_name path_to_resource_configuration = ( path_to_resource_directory / RESOURCE_TYPE_TO_CONFIG_FILE[resource_type_plural] ) json_path = parts[2:] if not path_to_resource_directory.exists(): raise AEAException( "Resource {}/{} does not exist.".format( resource_type_plural, resource_name ) ) config_loader = ConfigLoader.from_configuration_type(resource_type_plural[:-1]) return json_path, path_to_resource_configuration, config_loader, component_id def find_component_directory_from_component_id( aea_project_directory: Path, component_id: ComponentId ) -> Path: """Find a component directory from component id.""" # search in vendor first vendor_package_path = ( aea_project_directory / VENDOR / component_id.public_id.author / component_id.component_type.to_plural() / component_id.public_id.name ) if vendor_package_path.exists() and vendor_package_path.is_dir(): return vendor_package_path # search in custom packages. custom_package_path = ( aea_project_directory / component_id.component_type.to_plural() / component_id.public_id.name ) if custom_package_path.exists() and custom_package_path.is_dir(): return custom_package_path raise ValueError("Package {} not found.".format(component_id)) class AgentConfigManager: """AeaConfig manager.""" component_configurations = "component_configurations" _loader = ConfigLoader.from_configuration_type(PackageType.AGENT) def __init__( self, agent_config: AgentConfig, aea_project_directory: Union[str, Path], env_vars_friendly: bool = False, ) -> None: """ Init manager. :param agent_config: AgentConfig to manage. :param aea_project_directory: directory where project for agent_config placed. :param env_vars_friendly: whether or not it is env vars friendly """ self.agent_config = agent_config self.aea_project_directory = aea_project_directory self.env_vars_friendly = env_vars_friendly def load_component_configuration( self, component_id: ComponentId, skip_consistency_check: bool = True, ) -> ComponentConfiguration: """ Load component configuration from the project directory. :param component_id: Id of the component to load config for. :param skip_consistency_check: bool. :return: ComponentConfiguration """ path = find_component_directory_from_component_id( aea_project_directory=Path(self.aea_project_directory), component_id=component_id, ) return load_component_configuration( component_type=component_id.component_type, directory=path, skip_consistency_check=skip_consistency_check, ) @property def agent_config_file_path(self) -> Path: """Return agent config file path.""" return self._get_agent_config_file_path(self.aea_project_directory) @classmethod def _get_agent_config_file_path(cls, aea_project_path: Union[str, Path]) -> Path: """Get agent config file path for aea project path.""" return Path(aea_project_path) / DEFAULT_AEA_CONFIG_FILE @classmethod def load( cls, aea_project_path: Union[Path, str], substitude_env_vars: bool = False ) -> "AgentConfigManager": """Create AgentConfigManager instance from agent project path.""" data = cls._load_config_data(Path(aea_project_path)) if substitude_env_vars: data = cast(List[Dict], apply_env_variables(data, os.environ)) agent_config = cls._loader.load_agent_config_from_json(data, validate=False) instance = cls( agent_config, aea_project_path, env_vars_friendly=not substitude_env_vars ) instance.validate_current_config() return instance @classmethod def _load_config_data(cls, aea_project_path: Path) -> List[Dict]: with open_file(cls._get_agent_config_file_path(aea_project_path)) as fp: data = yaml_load_all(fp) return data def set_variable(self, path: VariablePath, value: JSON_TYPES) -> None: """ Set config variable. :param path: str dotted path or List[Union[ComponentId, str]] :param value: one of the json friendly objects. """ component_id, json_path = self._parse_path(path) data = self._make_dict_for_path_and_value(json_path, value) overrides = {} if component_id: overrides[self.component_configurations] = {component_id: data} else: # agent overrides.update(data) self.update_config(overrides) @staticmethod def _make_dict_for_path_and_value(json_path: JsonPath, value: JSON_TYPES) -> Dict: """ Turn json_path and value into overrides dict. :param json_path: List[str] represents config variable path: :param value: json friendly value :return: dict of overrides """ data: Dict = {} nested = data for key in json_path[:-1]: nested[key] = {} nested = nested[key] nested[json_path[-1]] = value return data def get_variable(self, path: VariablePath) -> JSON_TYPES: """ Set config variable. :param path: str dotted path or List[Union[ComponentId, str]] :return: json friendly value. """ component_id, json_path = self._parse_path(path) if component_id: configrations_data = [ _try_get_configuration_object_from_aea_config( self.agent_config, component_id ) or {}, self.load_component_configuration(component_id).json, ] else: configrations_data = [self.agent_config.json] for data in configrations_data: value = self._get_value_for_json_path(data, json_path) if value is not NotExists: return cast(JSON_TYPES, value) raise VariableDoesNotExist( f"Attribute `{'.'.join(json_path)}` for {'{}({}) config'.format(component_id.component_type,component_id.public_id) if component_id else 'AgentConfig'} does not exist" ) @staticmethod def _get_value_for_json_path( data: Dict, json_path: JsonPath ) -> Union[NotExistsType, JSON_TYPES]: """ Get value by json path from the dict object. :param data: dict to get value from :param json_path: List[str] :return: one of the json values of NotExists if value not presents in data dict. """ value = json.loads(json.dumps(data)) # in case of ordered dict doing copy prev_key = "" for key in json_path: if not isinstance(value, dict): raise ValueError( f"Attribute '{prev_key}' is not a dictionary." ) # pragma: nocover if key not in value: return NotExists value = value[key] prev_key = key return value def _parse_path(self, path: VariablePath) -> Tuple[Optional[ComponentId], JsonPath]: """ Get component_id and json path from dotted path or list of str with first optional component id. :param path: dotted path str, list of str with first optional component id :return: Tuple of optional component id if path related to component and List[str] """ if isinstance(path, str): json_path, *_, component_id = handle_dotted_path( path, self.agent_config.author, aea_project_path=self.aea_project_directory, ) else: # pragma: nocover if isinstance(path[0], ComponentId): json_path = path[1:] component_id = path[0] else: component_id = None json_path = path if component_id: component_id = _try_get_component_id_from_prefix( set(self.agent_config.all_components_id), component_id.component_prefix ) return component_id, json_path def update_config(self, overrides: Dict) -> None: """ Apply overrides for agent config. Validates and applies agent config and component overrides. Does not save it on the disc! :param overrides: overridden values dictionary :return: None """ if not overrides: # nothing to update return # pragma: nocover overrides = self._filter_overrides(overrides) if overrides is SAME_MARK: # nothing to update return for component_id, obj in overrides.get("component_configurations", {}).items(): component_configuration = self.load_component_configuration(component_id) component_configuration.check_overrides_valid( obj, env_vars_friendly=self.env_vars_friendly ) self.agent_config.update(overrides, env_vars_friendly=self.env_vars_friendly) def _filter_overrides(self, overrides: Dict) -> Dict: """Stay only updated values for agent config.""" agent_overridable, components_overridables = self.get_overridables() agent_overridable["component_configurations"] = components_overridables filtered_overrides = filter_data(agent_overridable, overrides) return filtered_overrides def validate_current_config(self) -> None: """Check is current config valid.""" for component_id, obj in self.agent_config.component_configurations.items(): component_configuration = self.load_component_configuration(component_id) component_configuration.check_overrides_valid( obj, env_vars_friendly=self.env_vars_friendly ) self.agent_config.validate_config_data( self.agent_config.json, env_vars_friendly=self.env_vars_friendly ) @property def json(self) -> Dict: """Return current agent config json representation.""" return self.agent_config.json def dump_config(self) -> None: """Save agent config on the disc.""" config_data = self.json self.agent_config.validate_config_data( config_data, env_vars_friendly=self.env_vars_friendly ) with open_file(self.agent_config_file_path, "w") as file_pointer: ConfigLoader.from_configuration_type(PackageType.AGENT).dump( self.agent_config, file_pointer ) @classmethod def verify_private_keys( cls, aea_project_path: Union[Path, str], private_key_helper: Callable[[AgentConfig, Path, Optional[str]], None], substitude_env_vars: bool = False, password: Optional[str] = None, ) -> "AgentConfigManager": """ Verify private keys. Does not saves the config! Use AgentConfigManager.dump_config() :param aea_project_path: path to an AEA project. :param private_key_helper: private_key_helper is a function that use agent config to check the keys :param substitude_env_vars: replace env vars with values, does not dump config :param password: the password to encrypt/decrypt the private key. :return: the agent configuration manager. """ aea_project_path = Path(aea_project_path) agent_config_manager = cls.load( aea_project_path, substitude_env_vars=substitude_env_vars ) aea_conf = agent_config_manager.agent_config private_key_helper(aea_conf, Path(aea_project_path), password) return agent_config_manager def get_overridables(self) -> Tuple[Dict, Dict[ComponentId, Dict]]: """Get config overridables.""" agent_overridable = self.agent_config.get_overridable() components_overridables: Dict[ComponentId, Dict] = {} for component_id in self.agent_config.all_components_id: obj = {} component_config = self.load_component_configuration( component_id, skip_consistency_check=True ) obj.update(component_config.get_overridable()) obj.update( deepcopy( self.agent_config.component_configurations.get(component_id, {}) ) ) if obj: components_overridables[component_id] = obj return agent_overridable, components_overridables ================================================ FILE: aea/configurations/pypi.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a checker for PyPI version consistency.""" import operator from collections import defaultdict from copy import deepcopy from functools import reduce from typing import Dict, List, Set, cast from packaging.specifiers import Specifier, SpecifierSet from packaging.version import InvalidVersion, Version from aea.configurations.base import Dependencies, Dependency def and_(s1: SpecifierSet, s2: SpecifierSet) -> SpecifierSet: """Do the and between two specifier sets.""" return operator.and_(s1, s2) def _handle_compatibility_operator( all_specifiers: List[Specifier], operator_to_specifiers: Dict[str, Set[Specifier]], specifier: Specifier, ) -> None: """ Handle a specifier with operator '~='. Split specifier of the form "~=" in two specifiers: - >= - < Also handle micro-numbers, i.e. "~=" (see last examples of https://www.python.org/dev/peps/pep-0440/#compatible-release) :param all_specifiers: the list of all specifiers (to be populated). :param operator_to_specifiers: a mapping from operator to specifiers (to be populated). :param specifier: the specifier to process. """ spec_version = Version(specifier.version) base_version = spec_version.base_version parts = base_version.split(".") index_to_update = -2 if ( spec_version.is_prerelease or spec_version.is_devrelease or spec_version.is_postrelease ): # if it is a pre-release, ignore the suffix. index_to_update += 1 parts = parts[:-1] # bump second-to-last part parts[index_to_update] = str(int(parts[index_to_update]) + 1) upper_version = Version(".".join(parts)) spec_1 = Specifier(">=" + str(spec_version)) spec_2 = Specifier("<" + str(upper_version)) all_specifiers.extend([spec_1, spec_2]) operator_to_specifiers[spec_1.operator].add(spec_1) operator_to_specifiers[spec_2.operator].add(spec_2) def is_satisfiable(specifier_set: SpecifierSet) -> bool: """ Check if the specifier set is satisfiable. Satisfiable means that there exists a version number that satisfies all the constraints. It is worth noticing that it doesn't mean that that version number with that package actually exists. >>> from packaging.specifiers import SpecifierSet The specifier set ">0.9, ==1.0" is satisfiable: the version number "1.0" satisfies the constraints >>> s1 = SpecifierSet(">0.9,==1.0") >>> "1.0" in s1 True >>> is_satisfiable(s1) True The specifier set "==1.0, >1.1" is not satisfiable: >>> s1 = SpecifierSet("==1.0,>1.1") >>> is_satisfiable(s1) False For other details, please refer to PEP440: https://www.python.org/dev/peps/pep-0440 :param specifier_set: the specifier set. :return: False if the constraints are surely non-satisfiable, True if we don't know. """ # group single specifiers by operator all_specifiers: List[Specifier] = [] operator_to_specifiers: Dict[str, Set[Specifier]] = defaultdict(set) # pre-processing for specifier in list(specifier_set): specifier = cast(Specifier, specifier) try: # if we can't parse the version number, ignore that specifier. # Even if it follows a legacy version format (e.g. "2.*") Version(specifier.version) except InvalidVersion: continue # handle specifiers with '~=' operators. if specifier.operator == "~=": _handle_compatibility_operator( all_specifiers, operator_to_specifiers, specifier ) else: all_specifiers.append(specifier) operator_to_specifiers[specifier.operator].add(specifier) # end of pre-processing. Start evaluation # if there are two different "==" specifier, return False if len(operator_to_specifiers["=="]) >= 2: return False if len(operator_to_specifiers["=="]) == 1: eq_specifier = operator_to_specifiers["=="].pop() eq_version = eq_specifier.version # notice: we implicitly handle the "!=" constraints. return eq_version in specifier_set # group all the "<" or "<=" together. They are interpreted as conjunction. # simplify: the spec with the lowest version number is the strictest constraint less_than_strict_specs = operator_to_specifiers["<"] less_than_equal_specs = operator_to_specifiers["<="] less_than_specs = set.union(less_than_equal_specs, less_than_strict_specs) # sort less-than constraints in the following way: ["<1.0", "<=1.0", "<2.0", "<=2.0"] # the first element is the strictest constraint. sorted_less_than_specs = sorted( less_than_specs, key=lambda x: (Version(x.version), len(x.operator)) ) lowest_less_than = ( sorted_less_than_specs[0] if len(sorted_less_than_specs) > 0 else None ) # group all the ">" or ">=" together. They are interpreted as conjunction. # simplify: the spec with the greatest version number is the strictest constraint greater_than_strict_specs = operator_to_specifiers[">"] greater_than_equal_specs = operator_to_specifiers[">="] greater_than_specs = set.union(greater_than_strict_specs, greater_than_equal_specs) # sort greater-than constraints in the following way: [">=1.0", ">1.0", ">=2.0", ">2.0"] # the last element is the strictest constraint. sorted_greater_than_specs = sorted( greater_than_specs, key=lambda x: (Version(x.version), -len(x.operator)) ) greatest_greater_than = ( sorted_greater_than_specs[-1] if len(sorted_greater_than_specs) > 0 else None ) # if there exist two range constraints, check satisfiability. # otherwise, we can't say much. if lowest_less_than is not None and greatest_greater_than is not None: return _handle_range_constraints(lowest_less_than, greatest_greater_than) return True def _handle_range_constraints( lowest_less_than: Specifier, greatest_greater_than: Specifier ) -> bool: """ Check whether two specifiers of the following type are compatible. It is a helper method for the is_satisfiable function and checks: - "<=" - ">=" The equality might be optional. The pseudo-code is the following: - is the version number of the lower-than constraint smaller than the one of the greater-than constraint? E.g. we are in cases like "<=1.0" and ">1.1". - If yes, then the constraints are unsatisfiable. - Otherwise, they are satisfiable. - are the version numbers the same? E.g. "<=1.0,>=1.0" - If yes, if at least one of them is a strict comparison, then return False, otherwise True. - otherwise, return True. :param lowest_less_than: the less-than constraint. :param greatest_greater_than: the greater than constraint. :return: False if we are sure the two constraints are not satisfiable, True if we don't know. """ version_less_than = Version(lowest_less_than.version) version_greater_than = Version(greatest_greater_than.version) if version_less_than < version_greater_than: return False if version_greater_than == version_less_than: # check if one of them has NOT the equality one_of_them_is_a_strict_comparison = ( greatest_greater_than.operator == ">" ) or (lowest_less_than.operator == "<") return not one_of_them_is_a_strict_comparison return True def is_simple_dep(dep: Dependency) -> bool: """ Check if it is a simple dependency. Namely, if it has no field specified, or only the 'version' field set. :param dep: the dependency :return: whether it is a simple dependency or not """ return dep.index is None and dep.git is None def to_set_specifier(dep: Dependency) -> SpecifierSet: """Get the set specifier. It assumes to be a simple dependency (see above).""" return SpecifierSet(dep.version) def merge_dependencies(dep1: Dependencies, dep2: Dependencies) -> Dependencies: """ Merge two groups of dependencies. If some of them are not "simple" (see above), and there is no risk of conflict because there is no other package with the same name, we leave them; otherwise we raise an error. :param dep1: the first operand :param dep2: the second operand. :return: the merged dependencies. """ result: Dependencies result = deepcopy(dep1) for pkg_name, info in dep2.items(): old_dep = result.get(pkg_name) old_dep_exists = old_dep is not None new_dep_is_simple = is_simple_dep(info) old_dep_is_simple = is_simple_dep(info) if old_dep_exists else False # if the new dependency already exists in the list, ignore if old_dep == info: continue # if one of the operands does not have a dependency, # we add it untouched to the final result and continue if not old_dep_exists: result[pkg_name] = info continue # in case a dependency exists in both operands and one of them is not simple: if (new_dep_is_simple and old_dep_exists and not old_dep_is_simple) or ( not new_dep_is_simple and old_dep_exists ): # we raise error because we can't trivially merge the specifier sets (unless they are equal, see above) raise ValueError( f"cannot trivially merge these two PyPI dependencies:\n- {pkg_name}: {old_dep}\n- {pkg_name}: {info}" ) # if we are here, it means both in the first and second operand # there are two 'simple' dependencies with the same name # therefore, we need to merge them new_specifier = SpecifierSet(info.version) old_specifier = ( SpecifierSet(result[pkg_name].version) if pkg_name in result else SpecifierSet("") ) combined_specifier = and_(new_specifier, old_specifier) new_info = Dependency( name=info.name, version=combined_specifier, index=info.index, git=info.git, ref=info.ref, ) result[pkg_name] = new_info return result def merge_dependencies_list(*deps: Dependencies) -> Dependencies: """ Merge a list of dependencies. :param deps: the list of dependencies :return: the merged dependencies. """ result: Dependencies = reduce(merge_dependencies, deps, {}) return result ================================================ FILE: aea/configurations/schemas/aea-config_schema.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the agent configuration file.", "additionalProperties": false, "type": "object", "required": [ "aea_version", "agent_name", "author", "description", "version", "license", "fingerprint", "fingerprint_ignore_patterns", "private_key_paths", "default_ledger", "required_ledgers", "connections", "contracts", "default_connection", "protocols", "skills", "dependencies" ], "properties": { "aea_version": { "$ref": "definitions.json#/definitions/aea_version" }, "agent_name": { "$ref": "definitions.json#/definitions/resource_name" }, "author": { "$ref": "definitions.json#/definitions/author" }, "description": { "$ref": "definitions.json#/definitions/description" }, "version": { "$ref": "definitions.json#/definitions/package_version" }, "license": { "$ref": "definitions.json#/definitions/license" }, "fingerprint": { "$ref": "definitions.json#/definitions/fingerprint" }, "fingerprint_ignore_patterns": { "$ref": "definitions.json#/definitions/fingerprint_ignore_patterns" }, "build_entrypoint": { "$ref": "definitions.json#/definitions/build_entrypoint" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" }, "private_key_paths": { "type": "object", "uniqueItems": true, "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "definitions.json#/definitions/private_key_path" } } }, "connection_private_key_paths": { "type": "object", "uniqueItems": true, "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "definitions.json#/definitions/private_key_path" } } }, "default_ledger": { "$ref": "definitions.json#/definitions/ledger_id" }, "required_ledgers": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/ledger_id" } }, "currency_denominations": { "type": "object" }, "connections": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "contracts": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "default_connection": { "type": ["string", "null"] }, "protocols": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "skills": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "logging_config" : { "$ref": "definitions.json#/definitions/logging_config" }, "period": { "$ref": "definitions.json#/definitions/period" }, "execution_timeout": { "$ref": "definitions.json#/definitions/execution_timeout" }, "max_reactions": { "$ref": "definitions.json#/definitions/max_reactions" }, "decision_maker_handler": { "$ref": "definitions.json#/definitions/framework_handler" }, "error_handler": { "$ref": "definitions.json#/definitions/framework_handler" }, "skill_exception_policy": { "$ref": "definitions.json#/definitions/skill_exception_policy" }, "connection_exception_policy": { "$ref": "definitions.json#/definitions/connection_exception_policy" }, "default_routing": { "type": "object", "uniqueItems": true, "patternProperties": { "^[a-zA-Z_][a-zA-Z0-9_]{0, 127}/[a-zA-Z_][a-zA-Z0-9_]{0, 127}:(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$": { "$ref": "definitions.json#/definitions/public_id" } } }, "loop_mode": { "$ref": "definitions.json#/definitions/loop_mode" }, "runtime_mode": { "$ref": "definitions.json#/definitions/runtime_mode" }, "task_manager_mode": { "$ref": "definitions.json#/definitions/task_manager_mode" }, "storage_uri": { "$ref": "definitions.json#/definitions/storage_uri" }, "data_dir": { "type": "string" }, "dependencies": { "$ref": "definitions.json#/definitions/dependencies" } } } ================================================ FILE: aea/configurations/schemas/configurable_parts/base-custom_config.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Base schema for a custom component configuration in the agent configuration file.", "additionalProperties": false, "required": [ "public_id", "type" ], "properties": { "public_id": { "$ref": "definitions.json#/definitions/public_id" }, "type": { "$ref": "definitions.json#/definitions/component_type" } } } ================================================ FILE: aea/configurations/schemas/configurable_parts/connection-custom_config.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the configurable part of a connection configuration in the agent configuration file.", "additionalProperties": false, "required": [ "public_id", "type" ], "properties": { "public_id": { "$ref": "definitions.json#/definitions/public_id" }, "type": { "$ref": "definitions.json#/definitions/component_type" }, "config": { "type": "object" }, "is_abstract": { "$ref": "connection-config_schema.json#/properties/is_abstract" }, "cert_requests": { "$ref": "connection-config_schema.json#/properties/cert_requests" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" } } } ================================================ FILE: aea/configurations/schemas/configurable_parts/contract-custom_config.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the configurable part of a contract configuration in the agent configuration file.", "additionalProperties": false, "required": [ "public_id", "type" ], "properties": { "public_id": { "$ref": "definitions.json#/definitions/public_id" }, "type": { "$ref": "definitions.json#/definitions/component_type" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" } } } ================================================ FILE: aea/configurations/schemas/configurable_parts/protocol-custom_config.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the configurable part of a protocol configuration in the agent configuration file.", "additionalProperties": false, "required": [ "public_id", "type" ], "properties": { "public_id": { "$ref": "definitions.json#/definitions/public_id" }, "type": { "$ref": "definitions.json#/definitions/component_type" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" } } } ================================================ FILE: aea/configurations/schemas/configurable_parts/skill-custom_config.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the configurable part of a skill configuration in the agent configuration file.", "additionalProperties": false, "required": [ "public_id", "type" ], "properties": { "public_id": { "$ref": "definitions.json#/definitions/public_id" }, "type": { "$ref": "definitions.json#/definitions/component_type" }, "handlers": { "$ref": "#/definitions/handlers" }, "behaviours": { "$ref": "#/definitions/behaviours" }, "models": { "$ref": "#/definitions/models" }, "is_abstract": { "$ref": "skill-config_schema.json#/properties/is_abstract" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" } }, "definitions": { "handlers": { "type": "object", "additionalProperties": false, "uniqueItems": true, "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "#/definitions/handler" } } }, "behaviours": { "type": "object", "uniqueItems": true, "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "#/definitions/behaviour" } } }, "models": { "type": "object", "uniqueItems": true, "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "#/definitions/model" } } }, "behaviour": { "type": "object", "additionalProperties": false, "required": [ "args" ], "properties": { "args": { "type": "object" } } }, "handler": { "type": "object", "additionalProperties": false, "required": [ "args" ], "properties": { "args": { "type": "object" } } }, "model": { "type": "object", "additionalProperties": false, "required": [ "args" ], "properties": { "args": { "type": "object" } } } } } ================================================ FILE: aea/configurations/schemas/connection-config_schema.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the connection configuration file.", "additionalProperties": false, "type": "object", "required": [ "name", "author", "version", "type", "description", "license", "aea_version", "fingerprint", "fingerprint_ignore_patterns", "class_name", "config", "connections", "protocols", "restricted_to_protocols", "excluded_protocols", "dependencies", "is_abstract" ], "properties": { "name": { "$ref": "definitions.json#/definitions/resource_name" }, "author": { "$ref": "definitions.json#/definitions/author" }, "version": { "$ref": "definitions.json#/definitions/package_version" }, "type": { "enum": ["connection"] }, "license": { "$ref": "definitions.json#/definitions/license" }, "aea_version": { "$ref": "definitions.json#/definitions/aea_version" }, "fingerprint": { "$ref": "definitions.json#/definitions/fingerprint" }, "fingerprint_ignore_patterns": { "$ref": "definitions.json#/definitions/fingerprint_ignore_patterns" }, "build_entrypoint": { "$ref": "definitions.json#/definitions/build_entrypoint" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" }, "class_name": { "type": "string" }, "protocols": { "type": "array", "additionalProperties": false, "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "connections": { "type": "array", "additionalProperties": false, "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "restricted_to_protocols": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "excluded_protocols": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "config": { "type": "object" }, "dependencies": { "$ref": "definitions.json#/definitions/dependencies" }, "description": { "$ref": "definitions.json#/definitions/description" }, "is_abstract": { "$ref": "skill-config_schema.json#/properties/is_abstract" }, "cert_requests": { "$ref": "definitions.json#/definitions/cert_requests" } } } ================================================ FILE: aea/configurations/schemas/contract-config_schema.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the contract configuration file.", "additionalProperties": false, "type": "object", "required": [ "name", "author", "version", "type", "license", "aea_version", "fingerprint", "fingerprint_ignore_patterns", "class_name", "description", "contract_interface_paths", "dependencies" ], "properties": { "name": { "$ref": "definitions.json#/definitions/resource_name" }, "author": { "$ref": "definitions.json#/definitions/author" }, "version": { "$ref": "definitions.json#/definitions/package_version" }, "type": { "enum": ["contract"] }, "license": { "$ref": "definitions.json#/definitions/license" }, "aea_version": { "$ref": "definitions.json#/definitions/aea_version" }, "fingerprint": { "$ref": "definitions.json#/definitions/fingerprint" }, "fingerprint_ignore_patterns": { "$ref": "definitions.json#/definitions/fingerprint_ignore_patterns" }, "build_entrypoint": { "$ref": "definitions.json#/definitions/build_entrypoint" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" }, "dependencies": { "$ref": "definitions.json#/definitions/dependencies" }, "description": { "$ref": "definitions.json#/definitions/description" }, "contract_interface_paths": { "type": "object", "uniqueItems": true, "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "definitions.json#/definitions/contract_interface_path" } } }, "class_name": { "type": "string" } } } ================================================ FILE: aea/configurations/schemas/definitions.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Definitions", "type": "object", "additionalProperties": false, "definitions": { "dependencies": { "type": "object", "additionalProperties": false, "patternProperties": { "^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9])$": { "type": "object", "additionalProperties": false, "properties": { "index": { "type": "string", "pattern": "^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$" }, "git": { "type": "string", "pattern": "^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$" }, "ref": { "type": "string", "pattern": "^[A-Za-z0-9/\\.\\-_]+$" }, "version": { "$ref": "#/definitions/version_specifiers" } } } } }, "resource_name": { "type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]{0,127}$" }, "component_type": { "type": "string", "enum": ["protocol", "connection", "contract", "skill"] }, "private_key_path": { "type": "string" }, "contract_interface_path": { "type": "string" }, "license": { "type": "string" }, "description": { "type": "string" }, "ledger_api": { "type": "object" }, "author": { "type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]{0,127}$" }, "package_version": { "$ref": "definitions.json#/definitions/semantic_version" }, "semantic_version": { "type": "string", "description": "A semantic version number. See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" }, "pep440_version": { "type": "string", "description": "Version number that matches PEP 440 version schemes. Differently from 'version_specifiers', this type matches only one version number, without comparison operators. See: https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes", "pattern": "^(([1-9][0-9]*!)?(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\\.post(0|[1-9][0-9]*))?(\\.dev(0|[1-9][0-9]*))?)(, *(( *(~=|==|>=|<=|!=|<|>) *)([1-9][0-9]*!)?(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\\.post(0|[1-9][0-9]*))?(\\.dev(0|[1-9][0-9]*))?))*$" }, "fingerprint": { "type": "object" }, "public_id": { "type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]{0,127}/[a-zA-Z_][a-zA-Z0-9_]{0,127}:(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" }, "version_specifiers": { "type": "string", "description": "A comma-separated list of PEP 440 version specifiers. See https://www.python.org/dev/peps/pep-0440/#version-specifiers", "pattern": "^(( *(~=|==|>=|<=|!=|<|>) *)([1-9][0-9]*!)?(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\\.post(0|[1-9][0-9]*))?(\\.dev(0|[1-9][0-9]*))?)(, *(( *(~=|==|>=|<=|!=|<|>) *)([1-9][0-9]*!)?(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\\.post(0|[1-9][0-9]*))?(\\.dev(0|[1-9][0-9]*))?))*$" }, "aea_version": { "type": "string", "description": "The version of AEA framework to use. It can be either a list of version specifiers (e.g. >0.2.0,<=0.2.3), or just a version number interpreted with the equality operator (e.g. 0.2.0, interpreted as ==0.2.0) (according to PEP 440).", "oneOf": [ {"$ref": "#/definitions/version_specifiers"}, {"$ref": "#/definitions/pep440_version"} ] }, "class_name": { "type": "string", "description": "The class name of a skill component.", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$" }, "fingerprint_ignore_patterns": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/ignore_pattern" } }, "build_entrypoint": { "type": "string" }, "build_directory": { "type": "string" }, "ledger_id": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$" }, "ignore_pattern": { "type": "string" }, "max_reactions": { "type": ["integer", "null"], "minimum": 1 }, "period": { "type": ["number", "null"], "minimum": 0, "exclusiveMinimum": true }, "execution_timeout": { "type": ["number", "null"], "minimum": 0 }, "skill_exception_policy": { "type": "string", "enum": ["propagate", "just_log", "stop_and_exit"] }, "connection_exception_policy": { "type": "string", "enum": ["propagate", "just_log", "stop_and_exit"] }, "loop_mode": { "type": "string", "enum": ["async", "sync"] }, "runtime_mode": { "type": "string", "enum": ["async", "threaded"] }, "task_manager_mode": { "type": "string", "enum": ["threaded", "multiprocess"] }, "storage_uri": { "type": "string" }, "keep_terminal_state_dialogues": { "type": "boolean" }, "logging_config": { "type": "object", "additionalProperties": true, "properties": { "disable_existing_loggers": { "type": ["boolean", "null"] }, "version": { "type": "number" }, "handlers": {"type": "object"}, "formatters": {"type": "object"}, "loggers": {"type": "object"} } }, "framework_handler": { "type": "object", "additionalProperties": false, "required": [ "dotted_path", "file_path", "config" ], "properties": { "dotted_path": { "type": "string" }, "file_path": { "type": ["string", "null"] }, "config": {"type": "object"} } }, "cert_requests": { "type": "array", "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/cert_request" } }, "cert_request": { "type": "object", "additionalProperties": false, "required": [ "public_key", "identifier", "ledger_id", "not_before", "not_after", "message_format", "save_path" ], "properties": { "public_key": { "type": "string" }, "identifier": { "$ref": "definitions.json#/definitions/resource_name" }, "ledger_id": { "$ref": "definitions.json#/definitions/ledger_id" }, "not_before": { "type": "string" }, "not_after": { "type": "string" }, "message_format": { "type": "string" }, "save_path": { "type": "string" } } } } } ================================================ FILE: aea/configurations/schemas/protocol-config_schema.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the protocol configuration file.", "additionalProperties": false, "type": "object", "required": [ "name", "author", "version", "type", "license", "aea_version", "fingerprint", "fingerprint_ignore_patterns", "description", "dependencies", "protocol_specification_id" ], "properties": { "name": { "$ref": "definitions.json#/definitions/resource_name" }, "author": { "$ref": "definitions.json#/definitions/author" }, "version": { "$ref": "definitions.json#/definitions/package_version" }, "type": { "enum": ["protocol"] }, "license": { "$ref": "definitions.json#/definitions/license" }, "aea_version": { "$ref": "definitions.json#/definitions/aea_version" }, "fingerprint": { "$ref": "definitions.json#/definitions/fingerprint" }, "fingerprint_ignore_patterns": { "$ref": "definitions.json#/definitions/fingerprint_ignore_patterns" }, "build_entrypoint": { "$ref": "definitions.json#/definitions/build_entrypoint" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" }, "dependencies": { "$ref": "definitions.json#/definitions/dependencies" }, "description": { "$ref": "definitions.json#/definitions/description" }, "protocol_specification_id": { "$ref": "definitions.json#/definitions/public_id" } } } ================================================ FILE: aea/configurations/schemas/protocol-specification_schema.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the protocol-specification file.", "additionalProperties": false, "type": "object", "required": [ "name", "author", "version", "license", "aea_version", "speech_acts", "protocol_specification_id", "description" ], "properties": { "name": { "$ref": "definitions.json#/definitions/resource_name" }, "author": { "$ref": "definitions.json#/definitions/author" }, "version": { "$ref": "definitions.json#/definitions/package_version" }, "license": { "$ref": "definitions.json#/definitions/license" }, "aea_version": { "$ref": "definitions.json#/definitions/aea_version" }, "protocol_specification_id": { "$ref": "definitions.json#/definitions/public_id" }, "speech_acts": { "type": "object", "additionalProperties": false, "uniqueItems": true, "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "#/definitions/speech_act" } } }, "description": { "$ref": "definitions.json#/definitions/description" } }, "definitions": { "speech_act": { "type": "object" } } } ================================================ FILE: aea/configurations/schemas/skill-config_schema.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for the skill configuration file.", "additionalProperties": false, "type": "object", "required": [ "name", "author", "version", "type", "license", "aea_version", "fingerprint", "fingerprint_ignore_patterns", "connections", "protocols", "contracts", "skills", "handlers", "behaviours", "models", "dependencies", "description", "is_abstract" ], "properties": { "name": { "$ref": "definitions.json#/definitions/resource_name" }, "author": { "$ref": "definitions.json#/definitions/author" }, "version": { "$ref": "definitions.json#/definitions/package_version" }, "type": { "enum": ["skill"] }, "license": { "$ref": "definitions.json#/definitions/license" }, "aea_version": { "$ref": "definitions.json#/definitions/aea_version" }, "fingerprint": { "$ref": "definitions.json#/definitions/fingerprint" }, "fingerprint_ignore_patterns": { "$ref": "definitions.json#/definitions/fingerprint_ignore_patterns" }, "build_entrypoint": { "$ref": "definitions.json#/definitions/build_entrypoint" }, "build_directory": { "$ref": "definitions.json#/definitions/build_directory" }, "connections": { "type": "array", "additionalProperties": false, "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "protocols": { "type": "array", "additionalProperties": false, "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "contracts": { "type": "array", "additionalProperties": false, "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "skills": { "type": "array", "additionalProperties": false, "uniqueItems": true, "items": { "$ref": "definitions.json#/definitions/public_id" } }, "handlers": { "$ref": "#/definitions/skill_component_list" }, "behaviours": { "$ref": "#/definitions/skill_component_list" }, "models": { "$ref": "#/definitions/skill_component_list" }, "dependencies": { "$ref": "definitions.json#/definitions/dependencies" }, "description": { "$ref": "definitions.json#/definitions/description" }, "is_abstract": { "type": "boolean" } }, "definitions": { "skill_component_list": { "type": "object", "patternProperties": { "^[^\\d\\W]\\w*\\Z": { "$ref": "#/definitions/skill_component_configuration" } } }, "skill_component_configuration": { "type": "object", "required": [ "class_name" ], "properties": { "class_name": { "$ref": "definitions.json#/definitions/class_name" }, "args": { "type": "object" }, "file_path": { "type": "string" } } } } } ================================================ FILE: aea/configurations/utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """AEA configuration utils.""" from functools import singledispatch from typing import Dict, Optional, Set from aea.configurations.base import ( AgentConfig, ComponentId, ComponentType, ConnectionConfig, ContractConfig, PackageConfiguration, ProtocolConfig, PublicId, SkillConfig, ) from aea.configurations.data_types import PackageIdPrefix @singledispatch def replace_component_ids( _arg: PackageConfiguration, _replacements: Dict[ComponentType, Dict[PublicId, PublicId]], ) -> None: """ Update public id references in a package configuration. This depends on the actual configuration being considered. """ @replace_component_ids.register(AgentConfig) # type: ignore def _( arg: AgentConfig, replacements: Dict[ComponentType, Dict[PublicId, PublicId]], ) -> None: """ Replace references in agent configuration. It breaks down in: 1) replace public ids in 'protocols', 'connections', 'contracts' and 'skills'; 2) replace public ids in default routing; 3) replace public id of default connection; 4) replace custom component configurations. :param arg: the agent configuration. :param replacements: the replacement mapping. """ _replace_component_id( arg, { ComponentType.PROTOCOL, ComponentType.CONNECTION, ComponentType.CONTRACT, ComponentType.SKILL, }, replacements, ) # update default routing protocol_replacements = replacements.get(ComponentType.PROTOCOL, {}) connection_replacements = replacements.get(ComponentType.CONNECTION, {}) for protocol_id, connection_id in list(arg.default_routing.items()): # update protocol (if replacements provides it) new_protocol_id = protocol_replacements.get(protocol_id, protocol_id) old_value = arg.default_routing.pop(protocol_id) arg.default_routing[new_protocol_id] = old_value # in case needs to be used below protocol_id = new_protocol_id # update connection (if replacements provides it) new_connection_id = connection_replacements.get(connection_id, connection_id) arg.default_routing[protocol_id] = new_connection_id # update default connection if arg.default_connection is not None: default_connection_public_id = arg.default_connection new_default_connection_public_id = replacements.get( ComponentType.CONNECTION, {} ).get(default_connection_public_id, default_connection_public_id) arg.default_connection = new_default_connection_public_id for component_id in set(arg.component_configurations.keys()): replacements_by_type = replacements.get(component_id.component_type, {}) if component_id.public_id in replacements_by_type: new_component_id = ComponentId( component_id.component_type, replacements_by_type[component_id.public_id], ) old_config = arg.component_configurations.pop(component_id) arg.component_configurations[new_component_id] = old_config @replace_component_ids.register(ProtocolConfig) # type: ignore def _( _arg: ProtocolConfig, _replacements: Dict[ComponentType, Dict[PublicId, PublicId]], ) -> None: """Do nothing - protocols have no references.""" @replace_component_ids.register(ConnectionConfig) # type: ignore def _( arg: ConnectionConfig, replacements: Dict[ComponentType, Dict[PublicId, PublicId]], ) -> None: """Replace references in a connection configuration.""" _replace_component_id( arg, {ComponentType.PROTOCOL, ComponentType.CONNECTION}, replacements ) protocol_replacements = replacements.get(ComponentType.PROTOCOL, {}) for old_protocol_id in set(arg.restricted_to_protocols): new_protocol_id = protocol_replacements.get(old_protocol_id, old_protocol_id) arg.restricted_to_protocols.remove(old_protocol_id) arg.restricted_to_protocols.add(new_protocol_id) for old_protocol_id in set(arg.excluded_protocols): new_protocol_id = protocol_replacements.get(old_protocol_id, old_protocol_id) arg.excluded_protocols.remove(old_protocol_id) arg.excluded_protocols.add(new_protocol_id) @replace_component_ids.register(ContractConfig) # type: ignore def _( # type: ignore _arg: ContractConfig, _replacements: Dict[ComponentType, Dict[PublicId, PublicId]], ) -> None: """Do nothing - contracts have no references.""" @replace_component_ids.register(SkillConfig) # type: ignore def _( arg: SkillConfig, replacements: Dict[ComponentType, Dict[PublicId, PublicId]], ) -> None: """Replace references in a skill configuration.""" _replace_component_id( arg, { ComponentType.PROTOCOL, ComponentType.CONNECTION, ComponentType.CONTRACT, ComponentType.SKILL, }, replacements, ) def _replace_component_id( config: PackageConfiguration, types_to_update: Set[ComponentType], replacements: Dict[ComponentType, Dict[PublicId, PublicId]], ) -> None: """ Replace a component id. :param config: the component configuration to update. :param types_to_update: the types to update. :param replacements: the replacements. """ for component_type in types_to_update: public_id_set: Set[PublicId] = getattr( config, component_type.to_plural(), set() ) replacements_given_type = replacements.get(component_type, {}) for old_public_id in list(public_id_set): new_public_id = replacements_given_type.get(old_public_id, old_public_id) public_id_set.remove(old_public_id) public_id_set.add(new_public_id) def get_latest_component_id_from_prefix( agent_config: AgentConfig, component_prefix: PackageIdPrefix ) -> Optional[ComponentId]: """ Get component id with the greatest version in an agent configuration given its prefix. :param agent_config: the agent configuration. :param component_prefix: the package prefix. :return: the package id with the greatest version, or None if not found. """ all_dependencies = agent_config.package_dependencies chosen_component_ids = [ c for c in all_dependencies if c.component_prefix == component_prefix ] nb_results = len(chosen_component_ids) return chosen_component_ids[0] if nb_results == 1 else None ================================================ FILE: aea/configurations/validation.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the configuration validation.""" import inspect import json import os from copy import deepcopy from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple import jsonschema from jsonschema import Draft4Validator from jsonschema._types import TypeChecker from jsonschema._utils import find_additional_properties from jsonschema._validators import additionalProperties from jsonschema.validators import extend from aea.configurations.constants import AGENT from aea.configurations.data_types import ComponentId, ComponentType, PublicId from aea.exceptions import AEAValidationError from aea.helpers.base import dict_to_path_value from aea.helpers.env_vars import is_env_variable from aea.helpers.io import open_file _CUR_DIR = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore _SCHEMAS_DIR = os.path.join(_CUR_DIR, "schemas") _PREFIX_BASE_CONFIGURABLE_PARTS = "base" _SCHEMAS_CONFIGURABLE_PARTS_DIRNAME = "configurable_parts" _POSTFIX_CUSTOM_CONFIG = "-custom_config.json" def make_jsonschema_base_uri(base_uri_path: Path) -> str: """ Make the JSONSchema base URI, cross-platform. :param base_uri_path: the path to the base directory. :return: the string in URI form. """ if os.name == "nt": # pragma: nocover # cause platform depended root_path = "file:///{}/".format("/".join(base_uri_path.absolute().parts)) else: # pragma: nocover # cause platform depended root_path = "file://{}/".format(base_uri_path.absolute()) return root_path def _get_path_to_custom_config_schema_from_type(component_type: ComponentType) -> str: """ Get the path to the custom config schema :param component_type: a component type. :return: the path to the JSON schema file. """ path_prefix: Path = Path(_SCHEMAS_DIR) / _SCHEMAS_CONFIGURABLE_PARTS_DIRNAME if component_type in {ComponentType.SKILL, ComponentType.CONNECTION}: filename_prefix = component_type.value else: filename_prefix = _PREFIX_BASE_CONFIGURABLE_PARTS full_path = path_prefix / (filename_prefix + _POSTFIX_CUSTOM_CONFIG) return str(full_path) class ExtraPropertiesError(ValueError): """Extra properties exception.""" def __str__(self) -> str: # pragma: nocover """Get string representation of the object.""" return ( f"ExtraPropertiesError: properties not expected: {', '.join(self.args[0])}" ) def __repr__(self) -> str: # pragma: nocover """Get string representation of the object.""" return str(self) class CustomTypeChecker(TypeChecker): """Custom type checker to handle env variables.""" def is_type(self, instance, type) -> bool: # type: ignore # pylint: disable=redefined-builtin """Check is instance of type.""" if is_env_variable(instance): return True return super().is_type(instance, type) def own_additional_properties(validator, aP, instance, schema) -> Iterator: # type: ignore """Additional properties validator.""" for _ in additionalProperties(validator, aP, instance, schema): raise ExtraPropertiesError(list(find_additional_properties(instance, schema))) return iter(()) OwnDraft4Validator = extend( validator=Draft4Validator, validators={"additionalProperties": own_additional_properties}, ) EnvVarsFriendlyDraft4Validator = extend( validator=Draft4Validator, type_checker=CustomTypeChecker( Draft4Validator.TYPE_CHECKER._type_checkers # pylint: disable=protected-access ), ) class ConfigValidator: """Configuration validator implementation.""" def __init__(self, schema_filename: str, env_vars_friendly: bool = False) -> None: """ Initialize the parser for configuration files. :param schema_filename: the path to the JSON-schema file in 'aea/configurations/schemas'. :param env_vars_friendly: whether or not it is env var friendly. """ base_uri = Path(_SCHEMAS_DIR) with open_file(base_uri / schema_filename) as fp: self._schema = json.load(fp) root_path = make_jsonschema_base_uri(base_uri) self._resolver = jsonschema.RefResolver(root_path, self._schema) self.env_vars_friendly = env_vars_friendly if env_vars_friendly: self._validator = EnvVarsFriendlyDraft4Validator( self._schema, resolver=self._resolver ) else: self._validator = OwnDraft4Validator(self._schema, resolver=self._resolver) @staticmethod def split_component_id_and_config( component_index: int, component_configuration_json: Dict ) -> ComponentId: """ Split component id and configuration. :param component_index: the position of the component configuration in the agent config file.. :param component_configuration_json: the JSON object to process. :return: the component id and the configuration object. :raises ValueError: if the component id cannot be extracted. """ # author, name, version, type are mandatory fields missing_fields = {"public_id", "type"}.difference( component_configuration_json.keys() ) if len(missing_fields) > 0: raise ValueError( f"There are missing fields in component id {component_index + 1}: {missing_fields}." ) public_id_str = component_configuration_json.pop("public_id") component_type = ComponentType(component_configuration_json.pop("type")) component_public_id = PublicId.from_str(public_id_str) component_id = ComponentId(component_type, component_public_id) return component_id @classmethod def validate_component_configuration( cls, component_id: ComponentId, configuration: Dict, env_vars_friendly: bool = False, ) -> None: """ Validate the component configuration of an agent configuration file. This check is to detect inconsistencies in the specified fields. :param component_id: the component id. :param configuration: the configuration dictionary. :param env_vars_friendly: bool, if set True, will not raise errors over the env variable definitions. :raises ValueError: if the configuration is not valid. """ schema_file = _get_path_to_custom_config_schema_from_type( component_id.component_type ) try: cls(schema_file, env_vars_friendly=env_vars_friendly).validate( dict( type=str(component_id.component_type), public_id=str(component_id.public_id), **configuration, ) ) except Exception as e: raise ValueError( f"Configuration of component ({str(component_id.component_type)}, {component_id.public_id}) is not valid. {str(e)}" ) def validate(self, json_data: Dict) -> None: """ Validate a JSON object against the right JSON schema. :param json_data: the JSON data. """ if json_data.get("type", AGENT) == AGENT: json_data_copy = deepcopy(json_data) # validate component_configurations self.validate_agent_components_configuration( json_data_copy.pop("component_configurations", []) ) # validate agent config self._validate(json_data_copy) else: self._validate(json_data) def _validate(self, instance: Dict) -> None: """Validate an instance using the current validator.""" errors: List[jsonschema.ValidationError] = list( self._validator.iter_errors(instance=instance) ) if len(errors) > 0: error_msg = self._build_message_from_errors(errors) raise AEAValidationError(f"{error_msg}") @staticmethod def _build_message_from_errors(errors: List[jsonschema.ValidationError]) -> str: """Build an error message from validation errors.""" def path(error): # type: ignore return ".".join(list(error.path)) result = [f"{path(error)}: {error.message}" for error in errors] return "The following errors occurred during validation:\n - " + "\n - ".join( result ) def validate_agent_components_configuration( self, component_configurations: Dict ) -> None: """ Validate agent component configurations overrides. :param component_configurations: the component configurations to validate. """ for idx, component_configuration_json in enumerate(component_configurations): component_id = self.split_component_id_and_config( idx, component_configuration_json ) self.validate_component_configuration( component_id, component_configuration_json ) @property def required_fields(self) -> List[str]: """ Get the required fields. :return: list of required fields. """ return self._schema["required"] def validate_data_with_pattern( data: dict, pattern: dict, excludes: Optional[List[Tuple[str]]] = None, skip_env_vars: bool = False, ) -> List[str]: """ Validate data dict with pattern dict for attributes present and type match. :param data: data dict to validate :param pattern: dict with pattern to check over :param excludes: list of tuples of str of paths to be skipped during the check :param skip_env_vars: is set True will not check data type over env variables. :return: list of str with error descriptions """ if excludes is None: excludes_: List[Tuple[str]] = [] else: excludes_ = excludes pattern_path_value = { tuple(path): value for path, value in dict_to_path_value(pattern) } data_path_value = {tuple(path): value for path, value in dict_to_path_value(data)} errors = [] def check_excludes(path: Tuple[str, ...]) -> bool: for exclude in excludes_: if len(exclude) > len(path): # pragma: nocover continue if path[: len(exclude)] == exclude: return True return False for path, new_value in data_path_value.items(): if check_excludes(path): continue if path not in pattern_path_value: errors.append(f"Attribute `{'.'.join(path)}` is not allowed to be updated!") continue pattern_value = pattern_path_value[path] if pattern_value is None: # not possible to determine data type for optional value not set # it will be checked with jsonschema later continue # pragma: nocover if skip_env_vars and ( is_env_variable(pattern_value) or is_env_variable(new_value) ): # one of the values is env variable: skip data type check continue if ( not issubclass(type(new_value), type(pattern_value)) and new_value is not None ): errors.append( f"For attribute `{'.'.join(path)}` `{type(pattern_value).__name__}` data type is expected, but `{type(new_value).__name__}` was provided!" ) return errors SAME_MARK = object() def filter_data(base: Any, updates: Any) -> Any: """Return difference in values or `SAME_MARK` object if values are the same.""" if not isinstance(updates, type(base)): return updates if not isinstance(base, dict): if base == updates: return SAME_MARK return updates new_keys = set(updates.keys()) - set(base.keys()) common_keys = set(updates.keys()).intersection(set(base.keys())) new_data = {key: updates[key] for key in new_keys} for key in common_keys: value = filter_data(base[key], updates[key]) if value is SAME_MARK: continue new_data[key] = value if not new_data: return SAME_MARK return new_data ================================================ FILE: aea/connections/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the connection modules.""" ================================================ FILE: aea/connections/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The base connection package.""" import asyncio import inspect import re from abc import ABC, abstractmethod from concurrent.futures.thread import ThreadPoolExecutor from contextlib import contextmanager from enum import Enum from pathlib import Path from typing import Any, Callable, Generator, Optional, Set, TYPE_CHECKING, cast from aea.components.base import Component, load_aea_package from aea.configurations.base import ComponentType, ConnectionConfig, PublicId from aea.configurations.loader import load_component_configuration from aea.crypto.wallet import CryptoStore from aea.exceptions import ( AEAComponentLoadException, AEAInstantiationException, enforce, parse_exception, ) from aea.helpers.async_utils import AsyncState from aea.helpers.base import load_module from aea.helpers.logging import get_logger from aea.identity.base import Identity if TYPE_CHECKING: from aea.mail.base import Address, Envelope # pragma: no cover class ConnectionStates(Enum): """Connection states enum.""" connected = "connected" connecting = "connecting" disconnecting = "disconnecting" disconnected = "disconnected" class Connection(Component, ABC): """Abstract definition of a connection.""" connection_id = None # type: PublicId def __init__( self, configuration: ConnectionConfig, data_dir: str, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, **kwargs: Any, ) -> None: """ Initialize the connection. The configuration must be specified if and only if the following parameters are None: connection_id, excluded_protocols or restricted_to_protocols. :param configuration: the connection configuration. :param data_dir: directory where to put local files. :param identity: the identity object held by the agent. :param crypto_store: the crypto store for encrypted communication. :param restricted_to_protocols: the set of protocols ids of the only supported protocols for this connection. :param excluded_protocols: the set of protocols ids that we want to exclude for this connection. :param kwargs: keyword arguments passed to component base """ enforce(configuration is not None, "The configuration must be provided.") super().__init__(configuration, **kwargs) enforce( super().public_id == self.connection_id, "Connection ids in configuration and class not matching.", ) self._state = AsyncState(ConnectionStates.disconnected) self._identity = identity self._crypto_store = crypto_store self._data_dir = data_dir self._restricted_to_protocols = ( restricted_to_protocols if restricted_to_protocols is not None else set() ) self._excluded_protocols = ( excluded_protocols if excluded_protocols is not None else set() ) @property def loop(self) -> asyncio.AbstractEventLoop: """Get the event loop.""" enforce(asyncio.get_event_loop().is_running(), "Event loop is not running.") return asyncio.get_event_loop() def _ensure_connected(self) -> None: # pragma: nocover """Raise exception if connection is not connected.""" if not self.is_connected: raise ConnectionError("Connection is not connected! Connect first!") @staticmethod def _ensure_valid_envelope_for_external_comms(envelope: "Envelope") -> None: """ Ensure the envelope sender and to are valid addresses for agent-to-agent communication. :param envelope: the envelope """ enforce( not envelope.is_sender_public_id and not envelope.is_to_public_id, f"Sender and to field of envelope is public id, needs to be address. Found: sender={envelope.sender}, to={envelope.to}", ) @contextmanager def _connect_context(self) -> Generator: """Set state connecting, disconnecting, disconnected during connect method.""" with self._state.transit( initial=ConnectionStates.connecting, success=ConnectionStates.connected, fail=ConnectionStates.disconnected, ): yield @property def address(self) -> "Address": # pragma: nocover """Get the address.""" if self._identity is None: raise ValueError( "You must provide the identity in order to retrieve the address." ) return self._identity.address @property def crypto_store(self) -> CryptoStore: # pragma: nocover """Get the crypto store.""" if self._crypto_store is None: raise ValueError("CryptoStore not available.") return self._crypto_store @property def has_crypto_store(self) -> bool: # pragma: nocover """Check if the connection has the crypto store.""" return self._crypto_store is not None @property def data_dir(self) -> str: # pragma: nocover """Get the data directory.""" return self._data_dir @property def component_type(self) -> ComponentType: # pragma: nocover """Get the component type.""" return ComponentType.CONNECTION @property def configuration(self) -> ConnectionConfig: """Get the connection configuration.""" if self._configuration is None: # pragma: nocover raise ValueError("Configuration not set.") return cast(ConnectionConfig, super().configuration) @property def restricted_to_protocols(self) -> Set[PublicId]: # pragma: nocover """Get the ids of the protocols this connection is restricted to.""" if self._configuration is None: return self._restricted_to_protocols return self.configuration.restricted_to_protocols @property def excluded_protocols(self) -> Set[PublicId]: # pragma: nocover """Get the ids of the excluded protocols for this connection.""" if self._configuration is None: return self._excluded_protocols return self.configuration.excluded_protocols @property def state(self) -> ConnectionStates: """Get the connection status.""" return self._state.get() @state.setter def state(self, value: ConnectionStates) -> None: """Set the connection status.""" if not isinstance(value, ConnectionStates): raise ValueError(f"Incorrect state: `{value}`") self._state.set(value) @abstractmethod async def connect(self) -> None: """Set up the connection.""" @abstractmethod async def disconnect(self) -> None: """Tear down the connection.""" @abstractmethod async def send(self, envelope: "Envelope") -> None: """ Send an envelope. :param envelope: the envelope to send. :return: None """ @abstractmethod async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. :param args: positional arguments :param kwargs: keyword arguments :return: the received envelope, or None if an error occurred. """ @classmethod def from_dir( cls, directory: str, identity: Identity, crypto_store: CryptoStore, data_dir: str, **kwargs: Any, ) -> "Connection": """ Load the connection from a directory. :param directory: the directory to the connection package. :param identity: the identity object. :param crypto_store: object to access the connection crypto objects. :param data_dir: the assets directory. :param kwargs: keyword arguments passed to connection base :return: the connection object. """ configuration = cast( ConnectionConfig, load_component_configuration(ComponentType.CONNECTION, Path(directory)), ) configuration.directory = Path(directory) return Connection.from_config( configuration, identity, crypto_store, data_dir, **kwargs ) @classmethod def from_config( cls, configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, data_dir: str, **kwargs: Any, ) -> "Connection": """ Load a connection from a configuration. :param configuration: the connection configuration. :param identity: the identity object. :param crypto_store: object to access the connection crypto objects. :param data_dir: the directory of the AEA project data. :param kwargs: keyword arguments passed to component base :return: an instance of the concrete connection class. """ configuration = cast(ConnectionConfig, configuration) directory = cast(Path, configuration.directory) connection_module_path = directory / "connection.py" if not (connection_module_path.exists() and connection_module_path.is_file()): raise AEAComponentLoadException( "Connection module '{}' not found.".format(connection_module_path) ) load_aea_package(configuration) connection_module = load_module( "connection_module", directory / "connection.py" ) classes = inspect.getmembers(connection_module, inspect.isclass) connection_class_name = cast(str, configuration.class_name) connection_classes = list( filter(lambda x: re.match(connection_class_name, x[0]), classes) ) name_to_class = dict(connection_classes) logger = get_logger(__name__, identity.name) logger.debug("Processing connection {}".format(connection_class_name)) connection_class = name_to_class.get(connection_class_name, None) if connection_class is None: raise AEAComponentLoadException( "Connection class '{}' not found.".format(connection_class_name) ) try: connection = connection_class( configuration=configuration, data_dir=data_dir, identity=identity, crypto_store=crypto_store, **kwargs, ) except Exception as e: # pragma: nocover # pylint: disable=broad-except e_str = parse_exception(e) raise AEAInstantiationException( f"An error occurred during instantiation of connection {configuration.public_id}/{configuration.class_name}:\n{e_str}" ) return connection @property def is_connected(self) -> bool: # pragma: nocover """Return is connected state.""" return self.state == ConnectionStates.connected @property def is_connecting(self) -> bool: # pragma: nocover """Return is connecting state.""" return self.state == ConnectionStates.connecting @property def is_disconnected(self) -> bool: # pragma: nocover """Return is disconnected state.""" return self.state == ConnectionStates.disconnected class BaseSyncConnection(Connection): """Base sync connection class to write connections with sync code.""" MAX_WORKER_THREADS = 5 _executor_pool: ThreadPoolExecutor _incoming_messages_queue: asyncio.Queue # [Optional[Envelope]] _loop: asyncio.AbstractEventLoop def __init__( self, configuration: ConnectionConfig, data_dir: str, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, **kwargs: Any, ) -> None: """ Initialize the connection. The configuration must be specified if and only if the following parameters are None: connection_id, excluded_protocols or restricted_to_protocols. :param configuration: the connection configuration. :param data_dir: directory where to put local files. :param identity: the identity object held by the agent. :param crypto_store: the crypto store for encrypted communication. :param restricted_to_protocols: the set of protocols ids of the only supported protocols for this connection. :param excluded_protocols: the set of protocols ids that we want to exclude for this connection. :param kwargs: keyword arguments passed to connection base """ super().__init__( configuration=configuration, data_dir=data_dir, identity=identity, crypto_store=crypto_store, restricted_to_protocols=restricted_to_protocols, excluded_protocols=excluded_protocols, **kwargs, ) self._tasks: Set[asyncio.Task] = set() def _set_executor_pool(self, max_workers: Optional[int] = None) -> None: """Set executors pool.""" max_workers = self.configuration.config.get( "max_thread_workers", self.MAX_WORKER_THREADS ) thread_name_prefix = f"conn:{self.connection_id}:" self._executor_pool = ThreadPoolExecutor( max_workers=max_workers, thread_name_prefix=thread_name_prefix ) def _task_done_callback(self, task: asyncio.Task) -> None: """Remove task on done, log error if happened.""" self._tasks.remove(task) if task.exception(): # cause task.get_name for python3.8+ task_name = getattr(task, "task_name", "TASK NAME NOT SET") self.logger.exception( f"in task `{task_name}`, exception occurred", exc_info=task.exception(), ) async def _run_in_pool(self, fn: Callable, *args: Any) -> Any: """Run sync function in threaded executor pool.""" return await self._loop.run_in_executor(self._executor_pool, fn, *args) def put_envelope(self, envelope: Optional["Envelope"]) -> None: """Put envelope in to the incoming queue.""" self._ensure_connected() self._loop.call_soon_threadsafe( self._incoming_messages_queue.put_nowait, envelope ) async def connect(self) -> None: """Connect connection.""" with self._connect_context(): self._loop = asyncio.get_event_loop() self._incoming_messages_queue = asyncio.Queue() self._set_executor_pool() await self._run_in_pool(self.on_connect) self.start_main() async def disconnect(self) -> None: """Disconnect connection.""" self._ensure_connected() with self._state.transit( initial=ConnectionStates.disconnecting, success=ConnectionStates.disconnected, fail=ConnectionStates.disconnected, ): if self._tasks: await asyncio.wait(self._tasks) await self._run_in_pool(self.on_disconnect) self._executor_pool.shutdown(wait=False) async def send(self, envelope: "Envelope") -> None: """Send envelope to connection.""" self._ensure_connected() task = self._loop.create_task(self._run_in_pool(self._send_wrapper, envelope)) # cause task.set_name for python3.8+ setattr(task, "task_name", f"On send({envelope})") # noqa task.add_done_callback(self._task_done_callback) self._tasks.add(task) def _send_wrapper(self, *args: Any) -> None: """Check is connected and call on_send method.""" self._ensure_connected() self.on_send(*args) async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """Get an envelope from the connection.""" self._ensure_connected() return await self._incoming_messages_queue.get() def start_main(self) -> None: """Start main function of the connection.""" def _() -> None: task = self._loop.create_task(self._run_in_pool(self.main)) # cause task.set_name for python3.8+ setattr(task, "task_name", "Connection main") # noqa task.add_done_callback(self._task_done_callback) self._tasks.add(task) self._loop.call_soon_threadsafe(_) def main(self) -> None: """Run main body of the connection in dedicated thread.""" @abstractmethod def on_connect(self) -> None: """Run on connect method called.""" @abstractmethod def on_disconnect(self) -> None: """Run on disconnect method called.""" @abstractmethod def on_send(self, envelope: "Envelope") -> None: """Run on send method called.""" ================================================ FILE: aea/connections/scaffold/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Scaffold of a connection.""" ================================================ FILE: aea/connections/scaffold/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Scaffold connection and channel.""" from typing import Any, Optional from aea.configurations.base import PublicId from aea.connections.base import BaseSyncConnection, Connection from aea.mail.base import Envelope """ Choose one of the possible implementations: Sync (inherited from BaseSyncConnection) or Async (inherited from Connection) connection and remove unused one. """ CONNECTION_ID = PublicId.from_str("fetchai/scaffold:0.1.0") class MyScaffoldAsyncConnection(Connection): """Proxy to the functionality of the SDK or API.""" connection_id = CONNECTION_ID def __init__(self, **kwargs: Any) -> None: """ Initialize the connection. The configuration must be specified if and only if the following parameters are None: connection_id, excluded_protocols or restricted_to_protocols. Possible keyword arguments: - configuration: the connection configuration. - data_dir: directory where to put local files. - identity: the identity object held by the agent. - crypto_store: the crypto store for encrypted communication. - restricted_to_protocols: the set of protocols ids of the only supported protocols for this connection. - excluded_protocols: the set of protocols ids that we want to exclude for this connection. :param kwargs: keyword arguments passed to component base """ super().__init__(**kwargs) # pragma: no cover async def connect(self) -> None: """ Set up the connection. In the implementation, remember to update 'connection_status' accordingly. """ raise NotImplementedError # pragma: no cover async def disconnect(self) -> None: """ Tear down the connection. In the implementation, remember to update 'connection_status' accordingly. """ raise NotImplementedError # pragma: no cover async def send(self, envelope: Envelope) -> None: """ Send an envelope. :param envelope: the envelope to send. """ raise NotImplementedError # pragma: no cover async def receive(self, *args: Any, **kwargs: Any) -> Optional[Envelope]: """ Receive an envelope. Blocking. :param args: arguments to receive :param kwargs: keyword arguments to receive :return: the envelope received, if present. # noqa: DAR202 """ raise NotImplementedError # pragma: no cover class MyScaffoldSyncConnection(BaseSyncConnection): """Proxy to the functionality of the SDK or API.""" MAX_WORKER_THREADS = 5 connection_id = CONNECTION_ID def __init__(self, *args: Any, **kwargs: Any) -> None: # pragma: no cover """ Initialize the connection. The configuration must be specified if and only if the following parameters are None: connection_id, excluded_protocols or restricted_to_protocols. Possible arguments: - configuration: the connection configuration. - data_dir: directory where to put local files. - identity: the identity object held by the agent. - crypto_store: the crypto store for encrypted communication. - restricted_to_protocols: the set of protocols ids of the only supported protocols for this connection. - excluded_protocols: the set of protocols ids that we want to exclude for this connection. :param args: arguments passed to component base :param kwargs: keyword arguments passed to component base """ super().__init__(*args, **kwargs) raise NotImplementedError def main(self) -> None: """ Run synchronous code in background. SyncConnection `main()` usage: The idea of the `main` method in the sync connection is to provide for a way to actively generate messages by the connection via the `put_envelope` method. A simple example is the generation of a message every second: ``` while self.is_connected: envelope = make_envelope_for_current_time() self.put_enevelope(envelope) time.sleep(1) ``` In this case, the connection will generate a message every second regardless of envelopes sent to the connection by the agent. For instance, this way one can implement periodically polling some internet resources and generate envelopes for the agent if some updates are available. Another example is the case where there is some framework that runs blocking code and provides a callback on some internal event. This blocking code can be executed in the main function and new envelops can be created in the event callback. """ raise NotImplementedError # pragma: no cover def on_send(self, envelope: Envelope) -> None: """ Send an envelope. :param envelope: the envelope to send. """ raise NotImplementedError # pragma: no cover def on_connect(self) -> None: """ Tear down the connection. Connection status set automatically. """ raise NotImplementedError # pragma: no cover def on_disconnect(self) -> None: """ Tear down the connection. Connection status set automatically. """ raise NotImplementedError # pragma: no cover ================================================ FILE: aea/connections/scaffold/connection.yaml ================================================ name: scaffold author: fetchai version: 0.1.0 type: connection description: The scaffold connection provides a scaffold for a connection to be implemented by the developer. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmaA7o9G1hT3fHtPDq6UYUyS5KY51uDkwMUGUc96odzSCX connection.py: QmWUqCmfD65KqnGjk2XWLDN3KAU32nC6uYgGCvjmSLWi7D readme.md: QmRFgpKrtPPTJSAEaXoNNKcYFiTAhVFKuiNZdjrjmAw8d1 fingerprint_ignore_patterns: [] connections: [] protocols: [] class_name: MyScaffoldAsyncConnection config: foo: bar excluded_protocols: [] restricted_to_protocols: [] dependencies: {} is_abstract: false cert_requests: [] ================================================ FILE: aea/connections/scaffold/readme.md ================================================ # Scaffold connection The scaffold connection acts as a boilerplate for a newly created connection. ## Usage Create a scaffold connection with the `aea scaffold connection {NAME}` command and implement your own connection. ================================================ FILE: aea/context/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the context modules.""" ================================================ FILE: aea/context/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the agent context class.""" from queue import Queue from types import SimpleNamespace from typing import Any, Callable, Dict, Optional, Union from aea.common import Address from aea.configurations.base import PublicId from aea.helpers.storage.generic_storage import Storage from aea.identity.base import Identity from aea.mail.base import Envelope, EnvelopeContext from aea.multiplexer import MultiplexerStatus, OutBox from aea.protocols.base import Message from aea.skills.tasks import TaskManager class AgentContext: """Provide read access to relevant objects of the agent for the skills.""" __slots__ = ( "_shared_state", "_identity", "_connection_status", "_outbox", "_decision_maker_message_queue", "_decision_maker_handler_context", "_task_manager", "_search_service_address", "_decision_maker_address", "_default_ledger_id", "_currency_denominations", "_default_connection", "_default_routing", "_storage_callable", "_data_dir", "_namespace", "_send_to_skill", ) def __init__( self, identity: Identity, connection_status: MultiplexerStatus, outbox: OutBox, decision_maker_message_queue: Queue, decision_maker_handler_context: SimpleNamespace, task_manager: TaskManager, default_ledger_id: str, currency_denominations: Dict[str, str], default_connection: Optional[PublicId], default_routing: Dict[PublicId, PublicId], search_service_address: Address, decision_maker_address: Address, data_dir: str, storage_callable: Callable[[], Optional[Storage]] = lambda: None, send_to_skill: Optional[Callable] = None, **kwargs: Any, ) -> None: """ Initialize an agent context. :param identity: the identity object :param connection_status: the connection status of the multiplexer :param outbox: the outbox :param decision_maker_message_queue: the (in) queue of the decision maker :param decision_maker_handler_context: the decision maker's name space :param task_manager: the task manager :param default_ledger_id: the default ledger id :param currency_denominations: mapping from ledger ids to currency denominations :param default_connection: the default connection :param default_routing: the default routing :param search_service_address: the address of the search service :param decision_maker_address: the address of the decision maker :param data_dir: directory where to put local files. :param storage_callable: function that returns optional storage attached to agent. :param send_to_skill: callable for sending envelopes to skills. :param kwargs: keyword arguments to be attached in the agent context namespace. """ self._shared_state = {} # type: Dict[str, Any] self._identity = identity self._connection_status = connection_status self._outbox = outbox self._decision_maker_message_queue = decision_maker_message_queue self._decision_maker_handler_context = decision_maker_handler_context self._task_manager = task_manager self._search_service_address = search_service_address self._decision_maker_address = decision_maker_address self._default_ledger_id = default_ledger_id self._currency_denominations = currency_denominations self._default_connection = default_connection self._default_routing = default_routing self._storage_callable = storage_callable self._data_dir = data_dir self._namespace = SimpleNamespace(**kwargs) self._send_to_skill = send_to_skill def send_to_skill( self, message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None, ) -> None: """ Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. :param message_or_envelope: envelope to send to another skill. :param context: the optional envelope context """ if self._send_to_skill is None: # pragma: nocover raise ValueError("Send to skill feature is not supported") self._send_to_skill(message_or_envelope, context) @property def storage(self) -> Optional[Storage]: """Return storage instance if enabled in AEA.""" return self._storage_callable() @property def data_dir(self) -> str: """Return assets directory.""" return self._data_dir @property def shared_state(self) -> Dict[str, Any]: """ Get the shared state dictionary. The shared state is the only object which skills can use to exchange state directly. It is accessible (read and write) from all skills. :return: dictionary of the shared state. """ return self._shared_state @property def identity(self) -> Identity: """Get the identity.""" return self._identity @property def agent_name(self) -> str: """Get agent name.""" return self.identity.name @property def addresses(self) -> Dict[str, Address]: """Get addresses.""" return self.identity.addresses @property def public_keys(self) -> Dict[str, str]: """Get public keys.""" return self.identity.public_keys @property def address(self) -> Address: """Get the default address.""" return self.identity.address @property def public_key(self) -> str: """Get the default public key.""" return self.identity.public_key @property def connection_status(self) -> MultiplexerStatus: """Get connection status of the multiplexer.""" return self._connection_status @property def outbox(self) -> OutBox: """Get outbox.""" return self._outbox @property def decision_maker_message_queue(self) -> Queue: """Get decision maker queue.""" return self._decision_maker_message_queue @property def decision_maker_handler_context(self) -> SimpleNamespace: """Get the decision maker handler context.""" return self._decision_maker_handler_context @property def task_manager(self) -> TaskManager: """Get the task manager.""" return self._task_manager @property def search_service_address(self) -> Address: """Get the address of the search service.""" return self._search_service_address @property def decision_maker_address(self) -> Address: """Get the address of the decision maker.""" return self._decision_maker_address @property def default_ledger_id(self) -> str: """Get the default ledger id.""" return self._default_ledger_id @property def currency_denominations(self) -> Dict[str, str]: """Get a dictionary mapping ledger ids to currency denominations.""" return self._currency_denominations @property def default_connection(self) -> Optional[PublicId]: """Get the default connection.""" return self._default_connection @property def default_routing(self) -> Dict[PublicId, PublicId]: """Get the default routing.""" return self._default_routing @property def namespace(self) -> SimpleNamespace: """Get the agent context namespace.""" return self._namespace ================================================ FILE: aea/contracts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the contract modules.""" from aea.contracts.base import Contract, contract_registry # noqa: F401 ================================================ FILE: aea/contracts/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The base contract.""" import inspect import logging import re from pathlib import Path from typing import Any, Dict, Optional, cast from aea.common import JSONLike from aea.components.base import Component, load_aea_package from aea.configurations.base import ComponentType, ContractConfig, PublicId from aea.configurations.constants import CONTRACTS from aea.configurations.loader import load_component_configuration from aea.crypto.base import LedgerApi from aea.crypto.registries import Registry, ledger_apis_registry, make_ledger_api_cls from aea.exceptions import AEAComponentLoadException, AEAException from aea.helpers.base import load_module contract_registry: Registry["Contract"] = Registry["Contract"]() _default_logger = logging.getLogger(__name__) class Contract(Component): """Abstract definition of a contract.""" contract_id = None # type: PublicId contract_interface: Any = None def __init__(self, contract_config: ContractConfig, **kwargs: Any) -> None: """ Initialize the contract. :param contract_config: the contract configurations. :param kwargs: the keyword arguments. """ super().__init__(contract_config, **kwargs) @property def id(self) -> PublicId: """Get the name.""" return self.public_id @property def configuration(self) -> ContractConfig: """Get the configuration.""" if self._configuration is None: # pragma: nocover raise ValueError("Configuration not set.") return cast(ContractConfig, super().configuration) @classmethod def get_instance( cls, ledger_api: LedgerApi, contract_address: Optional[str] = None ) -> Any: """ Get the instance. :param ledger_api: the ledger api we are using. :param contract_address: the contract address. :return: the contract instance """ contract_interface = cls.contract_interface.get(ledger_api.identifier, {}) instance = ledger_api.get_contract_instance( contract_interface, contract_address ) return instance @classmethod def from_dir(cls, directory: str, **kwargs: Any) -> "Contract": """ Load the protocol from a directory. :param directory: the directory to the skill package. :param kwargs: the keyword arguments. :return: the contract object. """ configuration = cast( ContractConfig, load_component_configuration(ComponentType.CONTRACT, Path(directory)), ) configuration.directory = Path(directory) return Contract.from_config(configuration, **kwargs) @classmethod def from_config(cls, configuration: ContractConfig, **kwargs: Any) -> "Contract": """ Load contract from configuration. :param configuration: the contract configuration. :param kwargs: the keyword arguments. :return: the contract object. """ if configuration.directory is None: # pragma: nocover raise ValueError("Configuration must be associated with a directory.") directory = configuration.directory load_aea_package(configuration) contract_module = load_module(CONTRACTS, directory / "contract.py") classes = inspect.getmembers(contract_module, inspect.isclass) contract_class_name = cast(str, configuration.class_name) contract_classes = list( filter(lambda x: re.match(contract_class_name, x[0]), classes) ) name_to_class = dict(contract_classes) _default_logger.debug(f"Processing contract {contract_class_name}") contract_class = name_to_class.get(contract_class_name, None) if contract_class is None: raise AEAComponentLoadException( f"Contract class '{contract_class_name}' not found." ) _try_to_register_contract(configuration) contract = contract_registry.make(str(configuration.public_id), **kwargs) return contract @classmethod def get_deploy_transaction( cls, ledger_api: LedgerApi, deployer_address: str, **kwargs: Any ) -> Optional[JSONLike]: """ Handler method for the 'GET_DEPLOY_TRANSACTION' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param deployer_address: The address that will deploy the contract. :param kwargs: keyword arguments. :return: the tx """ contract_interface = cls.contract_interface.get(ledger_api.identifier, {}) tx = ledger_api.get_deploy_transaction( contract_interface, deployer_address, **kwargs ) return tx @classmethod def get_raw_transaction( cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any ) -> Optional[JSONLike]: """ Handler method for the 'GET_RAW_TRANSACTION' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param kwargs: the keyword arguments. :return: the tx # noqa: DAR202 """ raise NotImplementedError @classmethod def get_raw_message( cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any ) -> Optional[bytes]: """ Handler method for the 'GET_RAW_MESSAGE' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param kwargs: the keyword arguments. :return: the tx # noqa: DAR202 """ raise NotImplementedError @classmethod def get_state( cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any ) -> Optional[JSONLike]: """ Handler method for the 'GET_STATE' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param kwargs: the keyword arguments. :return: the tx # noqa: DAR202 """ raise NotImplementedError def _try_to_register_contract(configuration: ContractConfig) -> None: """Register a contract to the registry.""" if str(configuration.public_id) in contract_registry.specs: # pragma: nocover _default_logger.warning( f"Skipping registration of contract {configuration.public_id} since already registered." ) return _default_logger.debug( f"Registering contract {configuration.public_id}" ) # pragma: nocover contract_interfaces = _load_contract_interfaces(configuration) try: # pragma: nocover contract_registry.register( id_=str(configuration.public_id), entry_point=f"{configuration.prefix_import_path}.contract:{configuration.class_name}", class_kwargs={"contract_interface": contract_interfaces}, contract_config=configuration, ) except AEAException as e: # pragma: nocover if "Cannot re-register id:" in str(e): _default_logger.warning( "Already registered: {}".format(configuration.class_name) ) else: raise e def _load_contract_interfaces( configuration: ContractConfig, ) -> Dict[str, Dict[str, str]]: """Get the contract interfaces.""" if configuration.directory is None: # pragma: nocover raise ValueError("Set contract configuration directory before calling.") contract_interfaces = {} # type: Dict[str, Dict[str, str]] for identifier, path in configuration.contract_interface_paths.items(): full_path = Path(configuration.directory, path) if identifier not in ledger_apis_registry.supported_ids: raise ValueError( # pragma: nocover f"No ledger api registered for identifier {identifier}." ) ledger_api = make_ledger_api_cls(identifier) contract_interface = ledger_api.load_contract_interface(full_path) contract_interfaces[identifier] = contract_interface return contract_interfaces ================================================ FILE: aea/contracts/scaffold/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the support resources for the scaffold contract.""" ================================================ FILE: aea/contracts/scaffold/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the scaffold contract definition.""" from typing import Any from aea.common import JSONLike from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi class MyScaffoldContract(Contract): """The scaffold contract class for a smart contract.""" contract_id = PublicId.from_str("fetchai/scaffold:0.1.0") @classmethod def get_raw_transaction( cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any ) -> JSONLike: """ Handler method for the 'GET_RAW_TRANSACTION' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param kwargs: the keyword arguments. :return: the tx # noqa: DAR202 """ raise NotImplementedError @classmethod def get_raw_message( cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any ) -> bytes: """ Handler method for the 'GET_RAW_MESSAGE' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param kwargs: the keyword arguments. :return: the tx # noqa: DAR202 """ raise NotImplementedError @classmethod def get_state( cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any ) -> JSONLike: """ Handler method for the 'GET_STATE' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param kwargs: the keyword arguments. :return: the tx # noqa: DAR202 """ raise NotImplementedError ================================================ FILE: aea/contracts/scaffold/contract.yaml ================================================ name: scaffold author: fetchai version: 0.1.0 type: contract description: The scaffold contract scaffolds a contract to be implemented by the developer. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmbqCg6ZKdTA5kLdmaMKpLtuTDhkwNZTSfyH9c8AUA2GCy contract.py: QmUvREspzKHP81G3u5xgu98dz4a1J8vGYL4xjrf2kv9nGB fingerprint_ignore_patterns: [] class_name: MyScaffoldContract contract_interface_paths: {} dependencies: {} ================================================ FILE: aea/crypto/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the crypto modules.""" from aea.crypto.registries import ( # noqa register_crypto, register_faucet_api, register_ledger_api, ) ================================================ FILE: aea/crypto/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Abstract module wrapping the public and private key cryptography and ledger api.""" from abc import ABC, abstractmethod from pathlib import Path from typing import Any, Dict, Generic, Optional, Tuple, TypeVar from aea.common import Address, JSONLike from aea.helpers.io import open_file EntityClass = TypeVar("EntityClass") class Crypto(Generic[EntityClass], ABC): """Base class for a crypto object.""" identifier = "base" def __init__( self, private_key_path: Optional[str] = None, password: Optional[str] = None, **kwargs: Any, ) -> None: # pylint: disable=unused-argument """ Initialize the crypto object. The actual behaviour of this constructor is determined by the abstract methods 'generate_private_key()' and 'load_private_key_from_path(). Either way, the entity object will be accessible as a property. :param private_key_path: the path to the private key. If None, the key will be generated by 'generate_private_key()'. If not None, the path will be processed by 'load_private_key_from_path()'. :param password: the password to encrypt/decrypt the private key. :param kwargs: keyword arguments. """ self._kwargs = kwargs self._entity = ( self.generate_private_key() if private_key_path is None else self.load_private_key_from_path(private_key_path, password) ) @classmethod @abstractmethod def generate_private_key(cls) -> EntityClass: """ Generate a private key. :return: the entity object. Implementation dependent. """ @classmethod @abstractmethod def load_private_key_from_path( cls, file_name: str, password: Optional[str] = None ) -> EntityClass: """ Load a private key in hex format for raw private key and json format for encrypted private key from a file. :param file_name: the path to the hex/json file. :param password: the password to encrypt/decrypt the private key. :return: the entity object. """ @property def entity(self) -> EntityClass: """ Return an entity object. :return: an entity object """ return self._entity @property @abstractmethod def private_key(self) -> str: """ Return a private key. :return: a private key string """ @property @abstractmethod def public_key(self) -> str: """ Return a public key. :return: a public key string """ @property @abstractmethod def address(self) -> str: """ Return the address. :return: an address string """ @abstractmethod def sign_message(self, message: bytes, is_deprecated_mode: bool = False) -> str: """ Sign a message in bytes string form. :param message: the message to be signed :param is_deprecated_mode: if the deprecated signing is used :return: signature of the message in string form """ @abstractmethod def sign_transaction(self, transaction: JSONLike) -> JSONLike: """ Sign a transaction in dict form. :param transaction: the transaction to be signed :return: signed transaction """ @classmethod def load(cls, private_key_file: str, password: Optional[str] = None) -> str: """ Load private key from file. :param private_key_file: the file where the key is stored. :param password: the password to encrypt/decrypt the private key. :return: private_key in hex string format """ path = Path(private_key_file) with open_file(path, "r") as key_file: data = key_file.read() if password is None: result = data else: result = cls.decrypt(data, password) return result def dump(self, private_key_file: str, password: Optional[str] = None) -> None: """ Dump private key to file. :param private_key_file: the file where the key is stored. :param password: the password to encrypt/decrypt the private key. """ if password is None: with open(private_key_file, "wb") as fpb: fpb.write(self.private_key.encode("utf-8")) else: with open_file(private_key_file, "w") as fp: encrypted = self.encrypt(password) fp.write(encrypted) @abstractmethod def encrypt(self, password: str) -> str: """ Encrypt the private key and return in json. :param password: the password to decrypt. :return: json string containing encrypted private key. """ @classmethod @abstractmethod def decrypt(cls, keyfile_json: str, password: str) -> str: """ Decrypt the private key and return in raw form. :param keyfile_json: json string containing encrypted private key. :param password: the password to decrypt. :return: the raw private key. """ class Helper(ABC): """Interface for helper class usable as Mixin for LedgerApi or as standalone class.""" @staticmethod @abstractmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool: """ Check whether a transaction is settled or not. :param tx_receipt: the receipt associated to the transaction. :return: True if the transaction has been settled, False o/w. """ @staticmethod @abstractmethod def is_transaction_valid( tx: JSONLike, seller: Address, client: Address, tx_nonce: str, amount: int, ) -> bool: """ Check whether a transaction is valid or not. :param tx: the transaction. :param seller: the address of the seller. :param client: the address of the client. :param tx_nonce: the transaction nonce. :param amount: the amount we expect to get from the transaction. :return: True if the random_message is equals to tx['input'] """ @staticmethod @abstractmethod def get_contract_address(tx_receipt: JSONLike) -> Optional[str]: """ Get the contract address from a transaction receipt. :param tx_receipt: the transaction digest :return: the contract address if successful """ @staticmethod @abstractmethod def generate_tx_nonce(seller: Address, client: Address) -> str: """ Generate a unique hash to distinguish transactions with the same terms. :param seller: the address of the seller. :param client: the address of the client. :return: return the hash in hex. """ @classmethod @abstractmethod def get_address_from_public_key(cls, public_key: str) -> str: """ Get the address from the public key. :param public_key: the public key :return: str """ @classmethod @abstractmethod def recover_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[Address, ...]: """ Recover the addresses from the hash. :param message: the message we expect :param signature: the transaction signature :param is_deprecated_mode: if the deprecated signing was used :return: the recovered addresses """ @classmethod @abstractmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[str, ...]: """ Get the public key used to produce the `signature` of the `message` :param message: raw bytes used to produce signature :param signature: signature of the message :param is_deprecated_mode: if the deprecated signing was used :return: the recovered public keys """ @staticmethod @abstractmethod def get_hash(message: bytes) -> str: """ Get the hash of a message. :param message: the message to be hashed. :return: the hash of the message. """ @classmethod @abstractmethod def is_valid_address(cls, address: Address) -> bool: """ Check if the address is valid. :param address: the address to validate """ @classmethod @abstractmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str]: """ Load contract interface. :param file_path: the file path to the interface :return: the interface """ class LedgerApi(Helper, ABC): """Interface for ledger APIs.""" identifier = "base" # type: str @property @abstractmethod def api(self) -> Any: """ Get the underlying API object. This can be used for low-level operations with the concrete ledger APIs. If there is no such object, return None. """ @abstractmethod def get_balance(self, address: Address) -> Optional[int]: """ Get the balance of a given account. This usually takes the form of a web request to be waited synchronously. :param address: the address. :return: the balance. """ @abstractmethod def get_state( self, callable_name: str, *args: Any, **kwargs: Any ) -> Optional[JSONLike]: """ Call a specified function on the underlying ledger API. This usually takes the form of a web request to be waited synchronously. :param callable_name: the name of the API function to be called. :param args: the positional arguments for the API function. :param kwargs: the keyword arguments for the API function. :return: the ledger API response. """ @abstractmethod def get_transfer_transaction( self, sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, **kwargs: Any, ) -> Optional[JSONLike]: """ Submit a transfer transaction to the ledger. :param sender_address: the sender address of the payer. :param destination_address: the destination address of the payee. :param amount: the amount of wealth to be transferred. :param tx_fee: the transaction fee. :param tx_nonce: verifies the authenticity of the tx :param kwargs: the keyword arguments. :return: the transfer transaction """ @abstractmethod def send_signed_transaction(self, tx_signed: JSONLike) -> Optional[str]: """ Send a signed transaction and wait for confirmation. Use keyword arguments for the specifying the signed transaction payload. :param tx_signed: the signed transaction """ @abstractmethod def get_transaction_receipt(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction receipt for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ @abstractmethod def get_transaction(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx, if present """ @abstractmethod def get_contract_instance( self, contract_interface: Dict[str, str], contract_address: Optional[str] = None ) -> Any: """ Get the instance of a contract. :param contract_interface: the contract interface. :param contract_address: the contract address. :return: the contract instance """ @abstractmethod def get_deploy_transaction( self, contract_interface: Dict[str, str], deployer_address: Address, **kwargs: Any, ) -> Optional[JSONLike]: """ Get the transaction to deploy the smart contract. :param contract_interface: the contract interface. :param deployer_address: The address that will deploy the contract. :param kwargs: the keyword arguments. :returns tx: the transaction dictionary. """ @abstractmethod def update_with_gas_estimate(self, transaction: JSONLike) -> JSONLike: """ Attempts to update the transaction with a gas estimate :param transaction: the transaction :return: the updated transaction """ class FaucetApi(ABC): """Interface for testnet faucet APIs.""" identifier = "base" # type: str network_name = "testnet" # type: str @abstractmethod def get_wealth(self, address: Address, url: Optional[str] = None) -> None: """ Get wealth from the faucet for the provided address. :param address: the address. :param url: the url :return: None """ ================================================ FILE: aea/crypto/helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module wrapping the helpers of public and private key cryptography.""" import logging import os from pathlib import Path from typing import Dict, Optional from aea.configurations.base import AgentConfig from aea.configurations.constants import PRIVATE_KEY_PATH_SCHEMA from aea.crypto.registries import crypto_registry, make_crypto, make_faucet_api from aea.crypto.wallet import Wallet from aea.helpers.base import ensure_dir from aea.helpers.env_vars import is_env_variable _default_logger = logging.getLogger(__name__) _ = PRIVATE_KEY_PATH_SCHEMA # some modules expect this here def try_validate_private_key_path( ledger_id: str, private_key_path: str, password: Optional[str] = None, ) -> None: """ Try validate a private key path. :param ledger_id: one of 'fetchai', 'ethereum' :param private_key_path: the path to the private key. :param password: the password to encrypt/decrypt the private key. :raises: ValueError if the identifier is invalid. """ try: # to validate the file, we just try to create a crypto object # with private_key_path as parameter make_crypto(ledger_id, private_key_path=private_key_path, password=password) except Exception as e: # pylint: disable=broad-except # thats ok, reraise error_msg = ( "This is not a valid private key file: '{}'\n Exception: '{}'".format( private_key_path, e ) ) _default_logger.error(error_msg) raise def create_private_key( ledger_id: str, private_key_file: str, password: Optional[str] = None ) -> None: """ Create a private key for the specified ledger identifier. :param ledger_id: the ledger identifier. :param private_key_file: the private key file. :param password: the password to encrypt/decrypt the private key. :raises: ValueError if the identifier is invalid. """ crypto = make_crypto(ledger_id) crypto.dump(private_key_file, password) def try_generate_testnet_wealth( identifier: str, address: str, url: Optional[str] = None, _sync: bool = True ) -> None: """ Try generate wealth on a testnet. :param identifier: the identifier of the ledger :param address: the address to check for :param url: the url :param _sync: whether to wait to sync or not; currently unused """ faucet_api = make_faucet_api(identifier) if faucet_api is not None: faucet_api.get_wealth(address, url) def private_key_verify( aea_conf: AgentConfig, aea_project_path: Path, password: Optional[str] = None, ) -> None: """ Check key. :param aea_conf: AgentConfig :param aea_project_path: Path, where project placed. :param password: the password to encrypt/decrypt the private key. """ for identifier, _ in aea_conf.private_key_paths.read_all(): if identifier not in crypto_registry.supported_ids: # pragma: nocover raise ValueError( "Unsupported identifier `{}` in private key paths. Supported identifiers: {}.".format( identifier, sorted(crypto_registry.supported_ids) ) ) for identifier in crypto_registry.supported_ids: config_private_key_path = aea_conf.private_key_paths.read(identifier) if is_env_variable(config_private_key_path): # config_private_key_path is env variable to be used, skip it. check will be performed after substitution continue if config_private_key_path is None: continue try: try_validate_private_key_path( identifier, str(aea_project_path / config_private_key_path), password=password, ) except FileNotFoundError: # pragma: no cover raise ValueError( "File {} for private key {} not found.".format( repr(config_private_key_path), identifier, ) ) def make_certificate( ledger_id: str, crypto_private_key_path: str, message: bytes, output_path: str, password: Optional[str] = None, ) -> str: """ Create certificate. :param ledger_id: the ledger id :param crypto_private_key_path: the path to the private key. :param message: the message to be signed. :param output_path: the location where to save the certificate. :param password: the password to encrypt/decrypt the private keys. :return: the signature/certificate """ crypto = crypto_registry.make( ledger_id, private_key_path=crypto_private_key_path, password=password ) signature = crypto.sign_message(message).encode("ascii").hex() ensure_dir(os.path.dirname(output_path)) Path(output_path).write_bytes(signature.encode("ascii")) return signature def get_wallet_from_agent_config( agent_config: AgentConfig, password: Optional[str] = None ) -> Wallet: """ Get wallet from agent_cofig provided. :param agent_config: the agent configuration object :param password: the password to encrypt/decrypt the private keys. :return: wallet """ private_key_paths: Dict[str, Optional[str]] = { config_pair[0]: config_pair[1] for config_pair in agent_config.private_key_paths.read_all() } connections_private_key_paths: Dict[str, Optional[str]] = { config_pair[0]: config_pair[1] for config_pair in agent_config.connection_private_key_paths.read_all() } wallet = Wallet(private_key_paths, connections_private_key_paths, password=password) return wallet class DecryptError(ValueError): """Error on bytes decryption with password.""" msg = "Decrypt error! Bad password?" def __init__(self, msg: Optional[str] = None) -> None: """Init exception.""" super().__init__(msg or self.msg) class KeyIsIncorrect(ValueError): """Error decoding hex string to bytes for private key.""" def hex_to_bytes_for_key(data: str) -> bytes: """Convert hex string to bytes with error handling.""" try: return bytes.fromhex(data) except ValueError as e: raise KeyIsIncorrect(str(e)) from e ================================================ FILE: aea/crypto/ledger_apis.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module wrapping all the public and private keys cryptography.""" from typing import Any, Dict, Optional, Tuple, Union from aea.common import Address from aea.configurations.constants import ( DEFAULT_LEDGER, _COSMOS_IDENTIFIER, _ETHEREUM_IDENTIFIER, _FETCHAI_IDENTIFIER, ) from aea.crypto.base import LedgerApi from aea.crypto.registries import ( ledger_apis_registry, make_ledger_api, make_ledger_api_cls, ) from aea.exceptions import enforce COSMOS_DEFAULT_ADDRESS = "INVALID_URL" COSMOS_DEFAULT_CURRENCY_DENOM = "INVALID_CURRENCY_DENOM" COSMOS_DEFAULT_CHAIN_ID = "INVALID_CHAIN_ID" ETHEREUM_DEFAULT_ADDRESS = "http://127.0.0.1:8545" ETHEREUM_DEFAULT_CHAIN_ID = 1337 ETHEREUM_DEFAULT_CURRENCY_DENOM = "wei" FETCHAI_DEFAULT_ADDRESS = "https://rest-dorado.fetch.ai:443" FETCHAI_DEFAULT_CURRENCY_DENOM = "atestfet" FETCHAI_DEFAULT_CHAIN_ID = "dorado-1" DEFAULT_LEDGER_CONFIGS: Dict[str, Dict[str, Union[str, int]]] = { _COSMOS_IDENTIFIER: { "address": COSMOS_DEFAULT_ADDRESS, "chain_id": COSMOS_DEFAULT_CHAIN_ID, "denom": COSMOS_DEFAULT_CURRENCY_DENOM, }, _ETHEREUM_IDENTIFIER: { "address": ETHEREUM_DEFAULT_ADDRESS, "chain_id": ETHEREUM_DEFAULT_CHAIN_ID, "denom": ETHEREUM_DEFAULT_CURRENCY_DENOM, }, _FETCHAI_IDENTIFIER: { "address": FETCHAI_DEFAULT_ADDRESS, "chain_id": FETCHAI_DEFAULT_CHAIN_ID, "denom": FETCHAI_DEFAULT_CURRENCY_DENOM, }, } DEFAULT_CURRENCY_DENOMINATIONS = { _COSMOS_IDENTIFIER: COSMOS_DEFAULT_CURRENCY_DENOM, _ETHEREUM_IDENTIFIER: ETHEREUM_DEFAULT_CURRENCY_DENOM, _FETCHAI_IDENTIFIER: FETCHAI_DEFAULT_CURRENCY_DENOM, } class LedgerApis: """Store all the ledger apis we initialise.""" ledger_api_configs: Dict[str, Dict[str, Union[str, int]]] = DEFAULT_LEDGER_CONFIGS @staticmethod def has_ledger(identifier: str) -> bool: """Check if it has the api.""" return identifier in ledger_apis_registry.supported_ids @classmethod def get_api(cls, identifier: str) -> LedgerApi: """Get the ledger API.""" enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) return api @classmethod def get_balance(cls, identifier: str, address: str) -> Optional[int]: """ Get the token balance. :param identifier: the identifier of the ledger :param address: the address to check for :return: the token balance """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) balance = api.get_balance(address) return balance @classmethod def get_transfer_transaction( cls, identifier: str, sender_address: str, destination_address: str, amount: int, tx_fee: int, tx_nonce: str, **kwargs: Any, ) -> Optional[Any]: """ Get a transaction to transfer from self to destination. :param identifier: the identifier of the ledger :param sender_address: the address of the sender :param destination_address: the address of the receiver :param amount: the amount :param tx_nonce: verifies the authenticity of the tx :param tx_fee: the tx fee :param kwargs: the keyword arguments. :return: tx """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx = api.get_transfer_transaction( sender_address, destination_address, amount, tx_fee, tx_nonce, **kwargs, ) return tx @classmethod def send_signed_transaction(cls, identifier: str, tx_signed: Any) -> Optional[str]: """ Send a signed transaction and wait for confirmation. :param identifier: the identifier of the ledger :param tx_signed: the signed transaction :return: the tx_digest, if present """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx_digest = api.send_signed_transaction(tx_signed) return tx_digest @classmethod def get_transaction_receipt(cls, identifier: str, tx_digest: str) -> Optional[Any]: """ Get the transaction receipt for a transaction digest. :param identifier: the identifier of the ledger :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx_receipt = api.get_transaction_receipt(tx_digest) return tx_receipt @classmethod def get_transaction(cls, identifier: str, tx_digest: str) -> Optional[Any]: """ Get the transaction for a transaction digest. :param identifier: the identifier of the ledger :param tx_digest: the digest associated to the transaction. :return: the tx, if present """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx = api.get_transaction(tx_digest) return tx @staticmethod def get_contract_address(identifier: str, tx_receipt: Any) -> Optional[Address]: """ Get the contract address from a transaction receipt. :param identifier: the identifier of the ledger :param tx_receipt: the transaction receipt :return: the contract address if successful """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api_class = make_ledger_api_cls(identifier) address = api_class.get_contract_address(tx_receipt) return address @staticmethod def is_transaction_settled(identifier: str, tx_receipt: Any) -> bool: """ Check whether the transaction is settled and correct. :param identifier: the identifier of the ledger :param tx_receipt: the transaction digest :return: True if correctly settled, False otherwise """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api_class = make_ledger_api_cls(identifier) is_settled = api_class.is_transaction_settled(tx_receipt) return is_settled @staticmethod def is_transaction_valid( identifier: str, tx: Any, seller: Address, client: Address, tx_nonce: str, amount: int, ) -> bool: """ Check whether the transaction is valid. :param identifier: Ledger identifier :param tx: the transaction :param seller: the address of the seller. :param client: the address of the client. :param tx_nonce: the transaction nonce. :param amount: the amount we expect to get from the transaction. :return: True if is valid , False otherwise """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api_class = make_ledger_api_cls(identifier) is_valid = api_class.is_transaction_valid(tx, seller, client, tx_nonce, amount) return is_valid @staticmethod def generate_tx_nonce(identifier: str, seller: Address, client: Address) -> str: """ Generate a random str message. :param identifier: ledger identifier. :param seller: the address of the seller. :param client: the address of the client. :return: return the hash in hex. """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api_class = make_ledger_api_cls(identifier) tx_nonce = api_class.generate_tx_nonce(seller=seller, client=client) return tx_nonce @staticmethod def recover_message( identifier: str, message: bytes, signature: str, is_deprecated_mode: bool = False, ) -> Tuple[Address, ...]: """ Recover the addresses from the hash. :param identifier: ledger identifier. :param message: the message we expect :param signature: the transaction signature :param is_deprecated_mode: if the deprecated signing was used :return: the recovered addresses """ enforce( identifier in ledger_apis_registry.supported_ids, "Not a registered ledger api identifier.", ) api_class = make_ledger_api_cls(identifier) addresses = api_class.recover_message( message=message, signature=signature, is_deprecated_mode=is_deprecated_mode ) return addresses @staticmethod def get_hash(identifier: str, message: bytes) -> str: """ Get the hash of a message. :param identifier: ledger identifier. :param message: the message to be hashed. :return: the hash of the message. """ identifier = ( identifier if identifier in ledger_apis_registry.supported_ids else DEFAULT_LEDGER ) api_class = make_ledger_api_cls(identifier) digest = api_class.get_hash(message=message) return digest @staticmethod def is_valid_address(identifier: str, address: Address) -> bool: """ Check if the address is valid. :param identifier: ledger identifier. :param address: the address to validate. :return: whether it is a valid address or not. """ identifier = ( identifier if identifier in ledger_apis_registry.supported_ids else DEFAULT_LEDGER ) api_class = make_ledger_api_cls(identifier) result = api_class.is_valid_address(address=address) return result ================================================ FILE: aea/crypto/plugin.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of plug-in mechanism for cryptos.""" import itertools import pprint from typing import Iterator, List, Set from pkg_resources import EntryPoint, WorkingSet from aea.configurations.constants import ( ALLOWED_GROUPS, CRYPTO_PLUGIN_GROUP, DOTTED_PATH_MODULE_ELEMENT_SEPARATOR, FAUCET_APIS_PLUGIN_GROUP, LEDGER_APIS_PLUGIN_GROUP, ) from aea.crypto import register_crypto, register_faucet_api, register_ledger_api from aea.crypto.registries.base import EntryPoint as EntryPointString from aea.crypto.registries.base import ItemId from aea.exceptions import AEAException, AEAPluginError, enforce _from_group_to_register_callable = { CRYPTO_PLUGIN_GROUP: register_crypto, LEDGER_APIS_PLUGIN_GROUP: register_ledger_api, FAUCET_APIS_PLUGIN_GROUP: register_faucet_api, } class Plugin: """Class that implements an AEA plugin.""" __slots__ = ("_group", "_entry_point") def __init__(self, group: str, entry_point: EntryPoint): """ Initialize the plugin. :param group: the group the plugin belongs to. :param entry_point: the entrypoint. """ self._group = group self._entry_point = entry_point self._check_consistency() def _check_consistency(self) -> None: """ Check consistency of input. :raises AEAPluginError: if some input is not correct. # noqa: DAR402 """ _error_message_prefix = f"Error with plugin '{self._entry_point.name}':" enforce( self.group in ALLOWED_GROUPS, f"{_error_message_prefix} '{self.group}' is not in the allowed groups: {pprint.pformat(ALLOWED_GROUPS)}", AEAPluginError, ) enforce( ItemId.REGEX.match(self._entry_point.name) is not None, f"{_error_message_prefix} '{self._entry_point.name}' is not a valid identifier for a plugin.", AEAPluginError, ) enforce( len(self._entry_point.attrs) == 1, f"{_error_message_prefix} Nested attributes currently not supported.", AEAPluginError, ) enforce( len(self._entry_point.extras) == 0, f"{_error_message_prefix} Extras currently not supported.", AEAPluginError, ) enforce( EntryPointString.REGEX.match(self.entry_point_path) is not None, f"{_error_message_prefix} Entry point path '{self.entry_point_path}' is not valid.", ) @property def name(self) -> str: """Get the plugin identifier.""" return self._entry_point.name @property def group(self) -> str: """Get the group.""" return self._group @property def attr(self) -> str: """Get the class name.""" return self._entry_point.attrs[0] @property def entry_point_path(self) -> str: """Get the entry point path.""" class_name = self.attr return f"{self._entry_point.module_name}{DOTTED_PATH_MODULE_ELEMENT_SEPARATOR}{class_name}" def _check_no_duplicates(plugins: List[EntryPoint]) -> None: """Check there are no two plugins with the same id.""" seen: Set[str] = set() duplicate_plugins = [p for p in plugins if p.name in seen or seen.add(p.name)] # type: ignore error_msg = f"Found plugins with the same id: {pprint.pformat(duplicate_plugins)}" enforce(len(duplicate_plugins) == 0, error_msg, AEAPluginError) def _get_plugins(group: str) -> List[Plugin]: """ Return a dict of all installed plugins, by name. :param group: the plugin group. :return: a mapping from plugin name to Plugin objects. """ entry_points: List[EntryPoint] = list(WorkingSet().iter_entry_points(group=group)) _check_no_duplicates(entry_points) return [Plugin(group, entry_point) for entry_point in entry_points] def _get_cryptos() -> List[Plugin]: """Get cryptos plugins.""" return _get_plugins(CRYPTO_PLUGIN_GROUP) def _get_ledger_apis() -> List[Plugin]: """Get ledgers plugins.""" return _get_plugins(LEDGER_APIS_PLUGIN_GROUP) def _get_faucet_apis() -> List[Plugin]: """Get faucets plugins.""" return _get_plugins(FAUCET_APIS_PLUGIN_GROUP) def _iter_plugins() -> Iterator[Plugin]: """Iterate over all the plugins.""" for plugin in itertools.chain( _get_cryptos(), _get_ledger_apis(), _get_faucet_apis() ): yield plugin def _register_plugin(plugin: Plugin, is_raising_exception: bool = True) -> None: """Register a plugin to the right registry.""" register_function = _from_group_to_register_callable[plugin.group] try: register_function(plugin.name, entry_point=plugin.entry_point_path) except AEAException: # pragma: nocover if is_raising_exception: raise def load_all_plugins(is_raising_exception: bool = True) -> None: """Load all plugins.""" for plugin in _iter_plugins(): _register_plugin(plugin, is_raising_exception) ================================================ FILE: aea/crypto/registries/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the crypto and the ledger APIs registries.""" from typing import Callable, Type from aea.crypto.base import Crypto, FaucetApi, LedgerApi from aea.crypto.registries.base import Registry crypto_registry: Registry[Crypto] = Registry[Crypto]() register_crypto = crypto_registry.register make_crypto: Callable[..., Crypto] = crypto_registry.make ledger_apis_registry: Registry[LedgerApi] = Registry[LedgerApi]() register_ledger_api = ledger_apis_registry.register make_ledger_api: Callable[..., LedgerApi] = ledger_apis_registry.make make_ledger_api_cls: Callable[..., Type[LedgerApi]] = ledger_apis_registry.make_cls faucet_apis_registry: Registry[FaucetApi] = Registry[FaucetApi]() register_faucet_api = faucet_apis_registry.register make_faucet_api: Callable[..., FaucetApi] = faucet_apis_registry.make make_faucet_api_cls: Callable[..., Type[FaucetApi]] = faucet_apis_registry.make_cls ================================================ FILE: aea/crypto/registries/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module implements the base registry.""" import importlib import re from typing import Any, Dict, Generic, Optional, Set, Type, TypeVar, Union from aea.configurations.base import PublicId from aea.configurations.constants import DOTTED_PATH_MODULE_ELEMENT_SEPARATOR from aea.exceptions import AEAException from aea.helpers.base import RegexConstrainedString, SIMPLE_ID_REGEX """A regex to match a Python identifier (i.e. a module/class name).""" PY_ID_REGEX = r"[^\d\W]\w*" ITEM_ID_REGEX = rf"({SIMPLE_ID_REGEX})|{PublicId.PUBLIC_ID_REGEX}" ItemType = TypeVar("ItemType") def _handle_malformed_string(class_name: str, malformed_id: str) -> None: raise AEAException( "Malformed {}: '{}'. It must be of the form '{}'.".format( class_name, malformed_id, ItemId.REGEX.pattern ) ) class ItemId(RegexConstrainedString): """The identifier of an item class.""" REGEX = re.compile(r"^({})$".format(ITEM_ID_REGEX)) @property def name(self) -> str: """Get the id name.""" return self.data def _handle_no_match(self) -> None: _handle_malformed_string(ItemId.__name__, self.data) class EntryPoint(Generic[ItemType], RegexConstrainedString): """ The entry point for a resource. The regular expression matches the strings in the following format: path.to.module:className """ REGEX = re.compile( r"^({pyid}(?:\.{pyid})*){sep}({pyid})$".format( pyid=PY_ID_REGEX, sep=DOTTED_PATH_MODULE_ELEMENT_SEPARATOR ) ) def __init__(self, seq: Union["EntryPoint", str]) -> None: """Initialize the entrypoint.""" super().__init__(seq) match = self.REGEX.match(self.data) if match is None: # actual match done in base class raise ValueError("No match found!") # pragma: nocover self._import_path = match.group(1) self._class_name = match.group(2) @property def import_path(self) -> str: """Get the import path.""" return self._import_path @property def class_name(self) -> str: """Get the class name.""" return self._class_name def _handle_no_match(self) -> None: _handle_malformed_string(EntryPoint.__name__, self.data) def load(self) -> Type[ItemType]: """ Load the item object. :return: the crypto object, loaded following the spec. """ mod_name, attr_name = self.import_path, self.class_name mod = importlib.import_module(mod_name) fn = getattr(mod, attr_name) return fn class ItemSpec(Generic[ItemType]): """A specification for a particular instance of an object.""" def __init__( self, id_: ItemId, entry_point: EntryPoint[ItemType], class_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Dict, ) -> None: """ Initialize an item specification. :param id_: the id associated to this specification :param entry_point: The Python entry_point of the environment class (e.g. module.name:Class). :param class_kwargs: keyword arguments to be attached on the class as class variables. :param kwargs: other custom keyword arguments. """ self.id = ItemId(id_) self.entry_point = EntryPoint[ItemType](entry_point) self._class_kwargs = {} if class_kwargs is None else class_kwargs self._kwargs = {} if kwargs is None else kwargs def make(self, **kwargs: Any) -> ItemType: """ Instantiate an instance of the item object with appropriate arguments. :param kwargs: the key word arguments :return: an item """ _kwargs = self._kwargs.copy() _kwargs.update(kwargs) cls = self.get_class() item = cls(**_kwargs) # type: ignore return item def get_class(self) -> Type[ItemType]: """ Get the class of the item with class variables instantiated. :return: an item class """ cls = self.entry_point.load() for key, value in self._class_kwargs.items(): setattr(cls, key, value) return cls class Registry(Generic[ItemType]): """Registry for generic classes.""" def __init__(self) -> None: """Initialize the registry.""" self.specs = {} # type: Dict[ItemId, ItemSpec[ItemType]] @property def supported_ids(self) -> Set[str]: """Get the supported item ids.""" return {str(id_) for id_ in self.specs.keys()} def register( self, id_: Union[ItemId, str], entry_point: Union[EntryPoint[ItemType], str], class_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> None: """ Register an item type. :param id_: the identifier for the crypto type. :param entry_point: the entry point to load the crypto object. :param class_kwargs: keyword arguments to be attached on the class as class variables. :param kwargs: arguments to provide to the crypto class. """ item_id = ItemId(id_) entry_point = EntryPoint[ItemType](entry_point) if item_id in self.specs: raise AEAException("Cannot re-register id: '{}'".format(item_id)) self.specs[item_id] = ItemSpec[ItemType]( item_id, entry_point, class_kwargs, **kwargs ) def make( self, id_: Union[ItemId, str], module: Optional[str] = None, **kwargs: Any ) -> ItemType: """ Create an instance of the associated type item id. :param id_: the id of the item class. Make sure it has been registered earlier before calling this function. :param module: dotted path to a module. whether a module should be loaded before creating the object. this argument is useful when the item might not be registered beforehand, and loading the specified module will make the registration. E.g. suppose the call to 'register' for a custom object is located in some_package/__init__.py. By providing module="some_package", the call to 'register' in such module gets triggered and the make can then find the identifier. :param kwargs: keyword arguments to be forwarded to the object. :return: the new item instance. """ item_id = ItemId(id_) spec = self._get_spec(item_id, module=module) item = spec.make(**kwargs) return item def make_cls( self, id_: Union[ItemId, str], module: Optional[str] = None ) -> Type[ItemType]: """ Load a class of the associated type item id. :param id_: the id of the item class. Make sure it has been registered earlier before calling this function. :param module: dotted path to a module. whether a module should be loaded before creating the object. this argument is useful when the item might not be registered beforehand, and loading the specified module will make the registration. E.g. suppose the call to 'register' for a custom object is located in some_package/__init__.py. By providing module="some_package", the call to 'register' in such module gets triggered and the make can then find the identifier. :return: the new item class. """ item_id = ItemId(id_) spec = self._get_spec(item_id, module=module) cls = spec.get_class() return cls def has_spec(self, item_id: ItemId) -> bool: """ Check whether there exist a spec associated with an item id. :param item_id: the item identifier. :return: True if it is registered, False otherwise. """ return item_id in self.specs.keys() def _get_spec( self, item_id: ItemId, module: Optional[str] = None ) -> ItemSpec[ItemType]: """Get the item spec.""" if module is not None: try: importlib.import_module(module) except ImportError: raise AEAException( "A module ({}) was specified for the item but was not found, " "make sure the package is installed with `pip install` before calling `aea.crypto.make()`".format( module ) ) if item_id not in self.specs: raise AEAException("Item not registered with id '{}'.".format(item_id)) return self.specs[item_id] ================================================ FILE: aea/crypto/wallet.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module wrapping all the public and private keys cryptography.""" import logging from typing import Any, Dict, Optional, cast from aea.common import JSONLike from aea.crypto.base import Crypto from aea.crypto.registries import make_crypto _default_logger = logging.getLogger(__name__) class CryptoStore: """Utility class to store and retrieve crypto objects.""" __slots__ = ("_crypto_objects", "_public_keys", "_addresses", "_private_keys") def __init__( self, crypto_id_to_path: Optional[Dict[str, Optional[str]]] = None, password: Optional[str] = None, ) -> None: """ Initialize the crypto store. :param crypto_id_to_path: dictionary from crypto id to an (optional) path to the private key. :param password: the password to encrypt/decrypt the private key. """ if crypto_id_to_path is None: crypto_id_to_path = {} crypto_objects = {} # type: Dict[str, Crypto] public_keys = {} # type: Dict[str, str] addresses = {} # type: Dict[str, str] private_keys = {} # type: Dict[str, str] for identifier, path in crypto_id_to_path.items(): crypto = make_crypto(identifier, private_key_path=path, password=password) crypto_objects[identifier] = crypto public_keys[identifier] = cast(str, crypto.public_key) addresses[identifier] = cast(str, crypto.address) private_keys[identifier] = cast(str, crypto.private_key) self._crypto_objects = crypto_objects self._public_keys = public_keys self._addresses = addresses self._private_keys = private_keys @property def public_keys(self) -> Dict[str, str]: """Get the public_key dictionary.""" return self._public_keys @property def crypto_objects(self) -> Dict[str, Crypto]: """Get the crypto objects (key pair).""" return self._crypto_objects @property def addresses(self) -> Dict[str, str]: """Get the crypto addresses.""" return self._addresses @property def private_keys(self) -> Dict[str, str]: """Get the crypto addresses.""" return self._private_keys class Wallet: """ Container for crypto objects. The cryptos are separated into two categories: - main cryptos: used by the AEA for the economic side (i.e. signing transaction) - connection cryptos: exposed to the connection objects for encrypted communication. """ def __init__( self, private_key_paths: Dict[str, Optional[str]], connection_private_key_paths: Optional[Dict[str, Optional[str]]] = None, password: Optional[str] = None, ): """ Instantiate a wallet object. :param private_key_paths: the private key paths :param connection_private_key_paths: the private key paths for the connections. :param password: the password to encrypt/decrypt the private key. """ self._main_cryptos = CryptoStore(private_key_paths, password=password) self._connection_cryptos = CryptoStore( connection_private_key_paths, password=password ) @property def public_keys(self) -> Dict[str, str]: """Get the public_key dictionary.""" return self._main_cryptos.public_keys @property def crypto_objects(self) -> Dict[str, Crypto]: """Get the crypto objects (key pair).""" return self._main_cryptos.crypto_objects @property def addresses(self) -> Dict[str, str]: """Get the crypto addresses.""" return self._main_cryptos.addresses @property def private_keys(self) -> Dict[str, str]: """Get the crypto addresses.""" return self._main_cryptos.private_keys @property def main_cryptos(self) -> CryptoStore: """Get the main crypto store.""" return self._main_cryptos @property def connection_cryptos(self) -> CryptoStore: """Get the connection crypto store.""" return self._connection_cryptos def sign_message( self, crypto_id: str, message: bytes, is_deprecated_mode: bool = False ) -> Optional[str]: """ Sign a message. :param crypto_id: the id of the crypto :param message: the message to be signed :param is_deprecated_mode: what signing mode to use :return: the signature of the message """ crypto_object = self.crypto_objects.get(crypto_id, None) if crypto_object is None: _default_logger.warning( "No crypto object for crypto_id={} in wallet!".format(crypto_id) ) signature = None # type: Optional[str] else: signature = crypto_object.sign_message(message, is_deprecated_mode) return signature def sign_transaction(self, crypto_id: str, transaction: Any) -> Optional[JSONLike]: """ Sign a tx. :param crypto_id: the id of the crypto :param transaction: the transaction to be signed :return: the signed tx """ crypto_object = self.crypto_objects.get(crypto_id, None) if crypto_object is None: _default_logger.warning( "No crypto object for crypto_id={} in wallet!".format(crypto_id) ) signed_transaction = None # type: Optional[Any] else: signed_transaction = crypto_object.sign_transaction(transaction) return signed_transaction ================================================ FILE: aea/decision_maker/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the decision maker modules.""" ================================================ FILE: aea/decision_maker/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the decision maker class.""" import hashlib import threading from abc import ABC, abstractmethod from queue import Queue from threading import Thread from types import SimpleNamespace from typing import Any, Dict, List, Optional from uuid import uuid4 from aea.crypto.wallet import Wallet from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.helpers.logging import WithLogger, get_logger from aea.helpers.transaction.base import Terms from aea.identity.base import Identity from aea.protocols.base import Message def _hash(access_code: str) -> str: """ Get the hash of the access code. :param access_code: the access code :return: the hash """ result = hashlib.sha224(access_code.encode("utf-8")).hexdigest() return result class OwnershipState(ABC): """Represent the ownership state of an agent (can proxy a ledger).""" @abstractmethod def set(self, **kwargs: Any) -> None: """ Set values on the ownership state. :param kwargs: the relevant keyword arguments """ @abstractmethod def apply_delta(self, **kwargs: Any) -> None: """ Apply a state update to the ownership state. This method is used to apply a raw state update without a transaction. :param kwargs: the relevant keyword arguments """ @property @abstractmethod def is_initialized(self) -> bool: """Get the initialization status.""" @abstractmethod def is_affordable_transaction(self, terms: Terms) -> bool: """ Check if the transaction is affordable (and consistent). :param terms: the transaction terms :return: True if the transaction is legal wrt the current state, false otherwise. """ @abstractmethod def apply_transactions(self, list_of_terms: List[Terms]) -> "OwnershipState": """ Apply a list of transactions to (a copy of) the current state. :param list_of_terms: the sequence of transaction terms. :return: the final state. """ @abstractmethod def __copy__(self) -> "OwnershipState": """Copy the object.""" class Preferences(ABC): """Class to represent the preferences.""" @abstractmethod def set(self, **kwargs: Any) -> None: """ Set values on the preferences. :param kwargs: the relevant key word arguments """ @property @abstractmethod def is_initialized(self) -> bool: """ Get the initialization status. Returns True if exchange_params_by_currency_id and utility_params_by_good_id are not None. """ @abstractmethod def marginal_utility(self, ownership_state: OwnershipState, **kwargs: Any) -> float: """ Compute the marginal utility. :param ownership_state: the ownership state against which to compute the marginal utility. :param kwargs: optional keyword arguments :return: the marginal utility score """ @abstractmethod def utility_diff_from_transaction( self, ownership_state: OwnershipState, terms: Terms ) -> float: """ Simulate a transaction and get the resulting utility difference (taking into account the fee). :param ownership_state: the ownership state against which to apply the transaction. :param terms: the transaction terms. :return: the score. """ @abstractmethod def __copy__(self) -> "Preferences": """Copy the object.""" class ProtectedQueue(Queue): """A wrapper of a queue to protect which object can read from it.""" def __init__(self, access_code: str) -> None: """ Initialize the protected queue. :param access_code: the access code to read from the queue """ super().__init__() self._access_code_hash = _hash(access_code) def put( # pylint: disable=arguments-differ,arguments-renamed self, internal_message: Optional[Message], block: bool = True, timeout: Optional[float] = None, ) -> None: """ Put an internal message on the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Full exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise the Full exception (timeout is ignored in that case). :param internal_message: the internal message to put on the queue :param block: whether to block or not :param timeout: timeout on block :raises: ValueError, if the item is not an internal message """ if not (isinstance(internal_message, Message) or internal_message is None): raise ValueError("Only messages are allowed!") super().put(internal_message, block=True, timeout=None) def put_nowait( # pylint: disable=arguments-differ,arguments-renamed self, internal_message: Optional[Message] ) -> None: """ Put an internal message on the queue. Equivalent to put(item, False). :param internal_message: the internal message to put on the queue :raises: ValueError, if the item is not an internal message """ if not (isinstance(internal_message, Message) or internal_message is None): raise ValueError("Only messages are allowed!") super().put_nowait(internal_message) def get(self, block: bool = True, timeout: Optional[float] = None) -> None: """ Inaccessible get method. :param block: whether to block or not :param timeout: timeout on block :raises: ValueError, access not permitted. """ raise ValueError("Access not permitted!") def get_nowait(self) -> None: """ Inaccessible get_nowait method. :raises: ValueError, access not permitted. """ raise ValueError("Access not permitted!") def protected_get( self, access_code: str, block: bool = True, timeout: Optional[float] = None ) -> Optional[Message]: """ Access protected get method. :param access_code: the access code :param block: If optional args block is true and timeout is None (the default), block if necessary until an item is available. :param timeout: If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. :raises: ValueError, if caller is not permitted :return: internal message """ if self._access_code_hash != _hash(access_code): raise ValueError("Wrong code, access not permitted!") internal_message = super().get( block=block, timeout=timeout ) # type: Optional[Message] return internal_message class DecisionMakerHandler(WithLogger, ABC): """This class implements the decision maker.""" __slots__ = ("_identity", "_wallet", "_config", "_context", "_message_out_queue") self_address: str = "decision_maker" def __init__( self, identity: Identity, wallet: Wallet, config: Dict[str, Any], **kwargs: Any ) -> None: """ Initialize the decision maker handler. :param identity: the identity :param wallet: the wallet :param config: the user defined configuration of the handler :param kwargs: the key word arguments """ logger = get_logger(__name__, identity.name) WithLogger.__init__(self, logger=logger) self._identity = identity self._wallet = wallet self._config = config self._context = SimpleNamespace(**kwargs) self._message_out_queue = AsyncFriendlyQueue() # type: AsyncFriendlyQueue @property def agent_name(self) -> str: """Get the agent name.""" return self.identity.name @property def identity(self) -> Identity: """Get identity of the agent.""" return self._identity @property def wallet(self) -> Wallet: """Get wallet of the agent.""" return self._wallet @property def config(self) -> Dict[str, Any]: """Get user defined configuration""" return self._config @property def context(self) -> SimpleNamespace: """Get the context.""" return self._context @property def message_out_queue(self) -> AsyncFriendlyQueue: """Get (out) queue.""" return self._message_out_queue @abstractmethod def handle(self, message: Message) -> None: """ Handle an internal message from the skills. :param message: the internal message """ class DecisionMaker(WithLogger): """This class implements the decision maker.""" __slots__ = ( "_queue_access_code", "_message_in_queue", "_decision_maker_handler", "_thread", "_lock", "_message_out_queue", "_stopped", ) def __init__( self, decision_maker_handler: DecisionMakerHandler, ) -> None: """ Initialize the decision maker. :param decision_maker_handler: the decision maker handler """ WithLogger.__init__(self, logger=decision_maker_handler.logger) self._queue_access_code = uuid4().hex self._message_in_queue = ProtectedQueue( self._queue_access_code ) # type: ProtectedQueue self._decision_maker_handler = decision_maker_handler self._thread = None # type: Optional[Thread] self._lock = threading.Lock() self._message_out_queue = decision_maker_handler.message_out_queue self._stopped = True @property def agent_name(self) -> str: """Get the agent name.""" return self.decision_maker_handler.identity.name @property def message_in_queue(self) -> ProtectedQueue: """Get (in) queue.""" return self._message_in_queue @property def message_out_queue(self) -> AsyncFriendlyQueue: """Get (out) queue.""" return self._message_out_queue @property def decision_maker_handler(self) -> DecisionMakerHandler: """Get the decision maker handler.""" return self._decision_maker_handler def start(self) -> None: """Start the decision maker.""" with self._lock: if not self._stopped: # pragma: no cover self.logger.debug( "[{}]: Decision maker already started.".format(self.agent_name) ) return self._stopped = False self._thread = Thread(target=self.execute, name=self.__class__.__name__) self._thread.start() def stop(self) -> None: """Stop the decision maker.""" with self._lock: self._stopped = True self.message_in_queue.put(None) if self._thread is not None: self._thread.join() self.logger.debug("[{}]: Decision Maker stopped.".format(self.agent_name)) self._thread = None def execute(self) -> None: """ Execute the decision maker. Performs the following while not stopped: - gets internal messages from the in queue and calls handle() on them """ while not self._stopped: message = self.message_in_queue.protected_get( self._queue_access_code, block=True ) # type: Optional[Message] if message is None: self.logger.debug( "[{}]: Received empty message. Quitting the processing loop...".format( self.agent_name ) ) continue self.handle(message) def handle(self, message: Message) -> None: """ Handle an internal message from the skills. :param message: the internal message """ self.decision_maker_handler.handle(message) ================================================ FILE: aea/decision_maker/default.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the decision maker class.""" from typing import Any, Dict from aea.common import Address from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMakerHandler as BaseDecisionMakerHandler from aea.helpers.transaction.base import SignedMessage, SignedTransaction from aea.identity.base import Identity from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue class DecisionMakerHandler(BaseDecisionMakerHandler): """This class implements the decision maker.""" # pylint: disable=import-outside-toplevel from packages.fetchai.protocols.signing.dialogues import ( # noqa: F811 SigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( # noqa: F811 SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage # noqa: F811 class SigningDialogues(BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: Address, **kwargs: Any) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param kwargs: the keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ from packages.fetchai.protocols.signing.dialogues import ( # pylint: disable=import-outside-toplevel SigningDialogue, ) return SigningDialogue.Role.DECISION_MAKER # pylint: disable=import-outside-toplevel from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, **kwargs, ) signing_dialogue_class = SigningDialogue signing_msg_class = SigningMessage __slots__ = ("signing_dialogues",) def __init__( self, identity: Identity, wallet: Wallet, config: Dict[str, Any] ) -> None: """ Initialize the decision maker. :param identity: the identity :param wallet: the wallet :param config: the user defined configuration of the handler """ kwargs: Dict[str, Any] = {} super().__init__( identity=identity, wallet=wallet, config=config, **kwargs, ) self.signing_dialogues = DecisionMakerHandler.SigningDialogues( self.self_address ) def handle(self, message: Message) -> None: """ Handle an internal message from the skills. :param message: the internal message """ if isinstance(message, self.signing_msg_class): self._handle_signing_message(message) else: # pragma: no cover self.logger.error( "[{}]: cannot handle message={} of type={}".format( self.agent_name, message, type(message) ) ) def _handle_signing_message(self, signing_msg: SigningMessage) -> None: """ Handle a signing message. :param signing_msg: the transaction message """ signing_dialogue = self.signing_dialogues.update(signing_msg) # type: ignore if signing_dialogue is None or not isinstance( signing_dialogue, self.signing_dialogue_class ): # pragma: no cover self.logger.error( "[{}]: Could not construct signing dialogue. Aborting!".format( self.agent_name ) ) return if signing_msg.performative == self.signing_msg_class.Performative.SIGN_MESSAGE: self._handle_message_signing(signing_msg, signing_dialogue) elif ( signing_msg.performative == self.signing_msg_class.Performative.SIGN_TRANSACTION ): self._handle_transaction_signing(signing_msg, signing_dialogue) else: # pragma: no cover self.logger.error( "[{}]: Unexpected transaction message performative".format( self.agent_name ) ) def _handle_message_signing( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a message for signing. :param signing_msg: the signing message :param signing_dialogue: the signing dialogue """ performative = self.signing_msg_class.Performative.ERROR kwargs = { "error_code": self.signing_msg_class.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, } # type: Dict[str, Any] signed_message = self.wallet.sign_message( signing_msg.raw_message.ledger_id, signing_msg.raw_message.body, signing_msg.raw_message.is_deprecated_mode, ) if signed_message is not None: performative = self.signing_msg_class.Performative.SIGNED_MESSAGE kwargs.pop("error_code") kwargs["signed_message"] = SignedMessage( signing_msg.raw_message.ledger_id, signed_message, signing_msg.raw_message.is_deprecated_mode, ) signing_msg_response = signing_dialogue.reply( performative=performative, target_message=signing_msg, **kwargs, ) self.message_out_queue.put(signing_msg_response) def _handle_transaction_signing( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a transaction for signing. :param signing_msg: the signing message :param signing_dialogue: the signing dialogue """ performative = self.signing_msg_class.Performative.ERROR kwargs = { "error_code": self.signing_msg_class.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, } # type: Dict[str, Any] signed_tx = self.wallet.sign_transaction( signing_msg.raw_transaction.ledger_id, signing_msg.raw_transaction.body ) if signed_tx is not None: performative = self.signing_msg_class.Performative.SIGNED_TRANSACTION kwargs.pop("error_code") kwargs["signed_transaction"] = SignedTransaction( signing_msg.raw_transaction.ledger_id, signed_tx ) signing_msg_response = signing_dialogue.reply( performative=performative, target_message=signing_msg, **kwargs, ) self.message_out_queue.put(signing_msg_response) ================================================ FILE: aea/decision_maker/gop.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the decision maker class.""" import copy import logging from enum import Enum from typing import Any, Dict, List, Optional, cast from aea.common import Address from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMakerHandler as BaseDecisionMakerHandler from aea.decision_maker.base import OwnershipState as BaseOwnershipState from aea.decision_maker.base import Preferences as BasePreferences from aea.exceptions import enforce from aea.helpers.preference_representations.base import ( linear_utility, logarithmic_utility, ) from aea.helpers.transaction.base import SignedMessage, SignedTransaction, Terms from aea.identity.base import Identity from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue CurrencyHoldings = Dict[str, int] # a map from identifier to quantity GoodHoldings = Dict[str, int] # a map from identifier to quantity UtilityParams = Dict[str, float] # a map from identifier to quantity ExchangeParams = Dict[str, float] # a map from identifier to quantity _default_logger = logging.getLogger(__name__) class GoalPursuitReadiness: """The goal pursuit readiness.""" __slots__ = ("_status",) class Status(Enum): """ The enum of the readiness status. In particular, it can be one of the following: - Status.READY: when the agent is ready to pursuit its goal - Status.NOT_READY: when the agent is not ready to pursuit its goal """ READY = "ready" NOT_READY = "not_ready" def __init__(self) -> None: """Instantiate the goal pursuit readiness.""" self._status = GoalPursuitReadiness.Status.NOT_READY @property def is_ready(self) -> bool: """Get the readiness.""" return self._status.value == GoalPursuitReadiness.Status.READY.value def update(self, new_status: Status) -> None: """ Update the goal pursuit readiness. :param new_status: the new status """ self._status = new_status class OwnershipState(BaseOwnershipState): """Represent the ownership state of an agent (can proxy a ledger).""" __slots__ = ("_amount_by_currency_id", "_quantities_by_good_id") def __init__(self) -> None: """Instantiate an ownership state object.""" self._amount_by_currency_id = None # type: Optional[CurrencyHoldings] self._quantities_by_good_id = None # type: Optional[GoodHoldings] def set( # pylint: disable=arguments-differ,arguments-renamed self, amount_by_currency_id: CurrencyHoldings = None, quantities_by_good_id: GoodHoldings = None, **kwargs: Any, ) -> None: """ Set values on the ownership state. :param amount_by_currency_id: the currency endowment of the agent in this state. :param quantities_by_good_id: the good endowment of the agent in this state. :param kwargs: the keyword arguments. """ if amount_by_currency_id is None: # pragma: nocover raise ValueError("Must provide amount_by_currency_id.") if quantities_by_good_id is None: # pragma: nocover raise ValueError("Must provide quantities_by_good_id.") enforce( not self.is_initialized, "Cannot apply state update, current state is already initialized!", ) self._amount_by_currency_id = copy.copy(amount_by_currency_id) self._quantities_by_good_id = copy.copy(quantities_by_good_id) def apply_delta( # pylint: disable=arguments-differ,arguments-renamed self, delta_amount_by_currency_id: Dict[str, int] = None, delta_quantities_by_good_id: Dict[str, int] = None, **kwargs: Any, ) -> None: """ Apply a state update to the ownership state. This method is used to apply a raw state update without a transaction. :param delta_amount_by_currency_id: the delta in the currency amounts :param delta_quantities_by_good_id: the delta in the quantities by good :param kwargs: the keyword arguments """ if delta_amount_by_currency_id is None: # pragma: nocover raise ValueError("Must provide delta_amount_by_currency_id.") if delta_quantities_by_good_id is None: # pragma: nocover raise ValueError("Must provide delta_quantities_by_good_id.") if self._amount_by_currency_id is None or self._quantities_by_good_id is None: raise ValueError( # pragma: nocover "Cannot apply state update, current state is not initialized!" ) enforce( all( ( key in self._amount_by_currency_id for key in delta_amount_by_currency_id.keys() ) ), "Invalid keys present in delta_amount_by_currency_id.", ) enforce( all( ( key in self._quantities_by_good_id for key in delta_quantities_by_good_id.keys() ) ), "Invalid keys present in delta_quantities_by_good_id.", ) for currency_id, amount_delta in delta_amount_by_currency_id.items(): self._amount_by_currency_id[currency_id] += amount_delta for good_id, quantity_delta in delta_quantities_by_good_id.items(): self._quantities_by_good_id[good_id] += quantity_delta @property def is_initialized(self) -> bool: """Get the initialization status.""" return ( self._amount_by_currency_id is not None and self._quantities_by_good_id is not None ) @property def amount_by_currency_id(self) -> CurrencyHoldings: """Get currency holdings in this state.""" if self._amount_by_currency_id is None: raise ValueError("amount_by_currency_id is not set!") return copy.copy(self._amount_by_currency_id) @property def quantities_by_good_id(self) -> GoodHoldings: """Get good holdings in this state.""" if self._quantities_by_good_id is None: raise ValueError("quantities_by_good_id is not set!") return copy.copy(self._quantities_by_good_id) def is_affordable_transaction(self, terms: Terms) -> bool: """ Check if the transaction is affordable (and consistent). E.g. check that the agent state has enough money if it is a buyer or enough holdings if it is a seller. Note, the agent is the sender of the transaction message by design. :param terms: the transaction terms :return: True if the transaction is legal wrt the current state, false otherwise. """ if all(amount == 0 for amount in terms.amount_by_currency_id.values()) and all( quantity == 0 for quantity in terms.quantities_by_good_id.values() ): # reject the transaction when there is no wealth exchange result = False elif all( amount <= 0 for amount in terms.amount_by_currency_id.values() ) and all(quantity >= 0 for quantity in terms.quantities_by_good_id.values()): # check if the agent has the money to cover the sender_amount (the agent=sender is the buyer) result = all( self.amount_by_currency_id[currency_id] >= -amount for currency_id, amount in terms.amount_by_currency_id.items() ) elif all( amount >= 0 for amount in terms.amount_by_currency_id.values() ) and all(quantity <= 0 for quantity in terms.quantities_by_good_id.values()): # check if the agent has the goods (the agent=sender is the seller). result = all( self.quantities_by_good_id[good_id] >= -quantity for good_id, quantity in terms.quantities_by_good_id.items() ) else: result = False return result def is_affordable(self, terms: Terms) -> bool: """ Check if the tx is affordable. :param terms: the transaction terms :return: whether the transaction is affordable or not """ if self.is_initialized: is_affordable = self.is_affordable_transaction(terms) else: _default_logger.debug( "Cannot verify whether transaction is affordable as ownership state is not initialized. Assuming it is!" ) is_affordable = True return is_affordable def update(self, terms: Terms) -> None: """ Update the agent state from a transaction. :param terms: the transaction terms """ if self._amount_by_currency_id is None or self._quantities_by_good_id is None: raise ValueError( # pragma: nocover "Cannot apply state update, current state is not initialized!" ) for currency_id, amount_delta in terms.amount_by_currency_id.items(): self._amount_by_currency_id[currency_id] += amount_delta for good_id, quantity_delta in terms.quantities_by_good_id.items(): self._quantities_by_good_id[good_id] += quantity_delta def apply_transactions(self, list_of_terms: List[Terms]) -> "OwnershipState": """ Apply a list of transactions to (a copy of) the current state. :param list_of_terms: the sequence of transaction terms. :return: the final state. """ new_state = copy.copy(self) for terms in list_of_terms: new_state.update(terms) return new_state def __copy__(self) -> "OwnershipState": """Copy the object.""" state = OwnershipState() if self.is_initialized: state._amount_by_currency_id = self.amount_by_currency_id state._quantities_by_good_id = self.quantities_by_good_id return state class Preferences(BasePreferences): """Class to represent the preferences.""" __slots__ = ("_exchange_params_by_currency_id", "_utility_params_by_good_id") def __init__(self) -> None: """Instantiate an agent preference object.""" self._exchange_params_by_currency_id = None # type: Optional[ExchangeParams] self._utility_params_by_good_id = None # type: Optional[UtilityParams] def set( # pylint: disable=arguments-differ,arguments-renamed self, exchange_params_by_currency_id: ExchangeParams = None, utility_params_by_good_id: UtilityParams = None, **kwargs: Any, ) -> None: """ Set values on the preferences. :param exchange_params_by_currency_id: the exchange params. :param utility_params_by_good_id: the utility params for every asset. :param kwargs: the keyword arguments. """ if exchange_params_by_currency_id is None: # pragma: nocover raise ValueError("Must provide exchange_params_by_currency_id.") if utility_params_by_good_id is None: # pragma: nocover raise ValueError("Must provide utility_params_by_good_id.") enforce( not self.is_initialized, "Cannot apply preferences update, preferences already initialized!", ) self._exchange_params_by_currency_id = copy.copy(exchange_params_by_currency_id) self._utility_params_by_good_id = copy.copy(utility_params_by_good_id) @property def is_initialized(self) -> bool: """ Get the initialization status. :return: True if exchange_params_by_currency_id and utility_params_by_good_id are not None. """ return (self._exchange_params_by_currency_id is not None) and ( self._utility_params_by_good_id is not None ) @property def exchange_params_by_currency_id(self) -> ExchangeParams: """Get exchange parameter for each currency.""" if self._exchange_params_by_currency_id is None: raise ValueError("ExchangeParams not set!") return self._exchange_params_by_currency_id @property def utility_params_by_good_id(self) -> UtilityParams: """Get utility parameter for each good.""" if self._utility_params_by_good_id is None: raise ValueError("UtilityParams not set!") return self._utility_params_by_good_id def logarithmic_utility(self, quantities_by_good_id: GoodHoldings) -> float: """ Compute agent's utility given her utility function params and a good bundle. :param quantities_by_good_id: the good holdings (dictionary) with the identifier (key) and quantity (value) for each good :return: utility value """ enforce(self.is_initialized, "Preferences params not set!") result = logarithmic_utility( self.utility_params_by_good_id, quantities_by_good_id, ) return result def linear_utility(self, amount_by_currency_id: CurrencyHoldings) -> float: """ Compute agent's utility given her utility function params and a currency bundle. :param amount_by_currency_id: the currency holdings (dictionary) with the identifier (key) and quantity (value) for each currency :return: utility value """ enforce(self.is_initialized, "Preferences params not set!") result = linear_utility( self.exchange_params_by_currency_id, amount_by_currency_id ) return result def utility( self, quantities_by_good_id: GoodHoldings, amount_by_currency_id: CurrencyHoldings, ) -> float: """ Compute the utility given the good and currency holdings. :param quantities_by_good_id: the good holdings :param amount_by_currency_id: the currency holdings :return: the utility value. """ enforce(self.is_initialized, "Preferences params not set!") goods_score = self.logarithmic_utility(quantities_by_good_id) currency_score = self.linear_utility(amount_by_currency_id) score = goods_score + currency_score return score def marginal_utility( # pylint: disable=arguments-differ,arguments-renamed self, ownership_state: BaseOwnershipState, delta_quantities_by_good_id: Optional[GoodHoldings] = None, delta_amount_by_currency_id: Optional[CurrencyHoldings] = None, **kwargs: Any, ) -> float: """ Compute the marginal utility. :param ownership_state: the ownership state against which to compute the marginal utility. :param delta_quantities_by_good_id: the change in good holdings :param delta_amount_by_currency_id: the change in money holdings :param kwargs: the keyword arguments :return: the marginal utility score """ enforce(self.is_initialized, "Preferences params not set!") ownership_state = cast(OwnershipState, ownership_state) current_goods_score = self.logarithmic_utility( ownership_state.quantities_by_good_id ) current_currency_score = self.linear_utility( ownership_state.amount_by_currency_id ) new_goods_score = current_goods_score new_currency_score = current_currency_score if delta_quantities_by_good_id is not None: new_quantities_by_good_id = { good_id: quantity + delta_quantities_by_good_id[good_id] for good_id, quantity in ownership_state.quantities_by_good_id.items() } new_goods_score = self.logarithmic_utility(new_quantities_by_good_id) if delta_amount_by_currency_id is not None: new_amount_by_currency_id = { currency: amount + delta_amount_by_currency_id[currency] for currency, amount in ownership_state.amount_by_currency_id.items() } new_currency_score = self.linear_utility(new_amount_by_currency_id) marginal_utility = ( new_goods_score + new_currency_score - current_goods_score - current_currency_score ) return marginal_utility def utility_diff_from_transaction( self, ownership_state: BaseOwnershipState, terms: Terms ) -> float: """ Simulate a transaction and get the resulting utility difference (taking into account the fee). :param ownership_state: the ownership state against which to apply the transaction. :param terms: the transaction terms. :return: the score. """ enforce(self.is_initialized, "Preferences params not set!") ownership_state = cast(OwnershipState, ownership_state) current_score = self.utility( quantities_by_good_id=ownership_state.quantities_by_good_id, amount_by_currency_id=ownership_state.amount_by_currency_id, ) new_ownership_state = ownership_state.apply_transactions([terms]) new_score = self.utility( quantities_by_good_id=new_ownership_state.quantities_by_good_id, amount_by_currency_id=new_ownership_state.amount_by_currency_id, ) score_difference = new_score - current_score return score_difference def is_utility_enhancing( self, ownership_state: BaseOwnershipState, terms: Terms ) -> bool: """ Check if the tx is utility enhancing. :param ownership_state: the ownership state against which to apply the transaction. :param terms: the transaction terms :return: whether the transaction is utility enhancing or not """ if self.is_initialized and ownership_state.is_initialized: is_utility_enhancing = ( self.utility_diff_from_transaction(ownership_state, terms) >= 0.0 ) else: _default_logger.debug( "Cannot verify whether transaction improves utility as preferences are not initialized. Assuming it does!" ) is_utility_enhancing = True return is_utility_enhancing def __copy__(self) -> "Preferences": """Copy the object.""" preferences = Preferences() if self.is_initialized: preferences._exchange_params_by_currency_id = ( self.exchange_params_by_currency_id ) preferences._utility_params_by_good_id = self.utility_params_by_good_id return preferences class DecisionMakerHandler(BaseDecisionMakerHandler): """This class implements the decision maker.""" # pylint: disable=import-outside-toplevel from packages.fetchai.protocols.signing.dialogues import ( # noqa: F811 SigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( # noqa: F811 SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage # noqa: F811 from packages.fetchai.protocols.state_update.dialogues import ( # noqa: F811 StateUpdateDialogue, ) from packages.fetchai.protocols.state_update.dialogues import ( # noqa: F811 StateUpdateDialogues as BaseStateUpdateDialogues, ) from packages.fetchai.protocols.state_update.message import ( # noqa: F811 StateUpdateMessage, ) signing_dialogues_class = SigningDialogue signing_msg_class = SigningMessage state_update_dialogue_class = StateUpdateDialogue state_update_msg_class = StateUpdateMessage class SigningDialogues(BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: Address, **kwargs: Any) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param kwargs: the keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ from packages.fetchai.protocols.signing.dialogues import ( # pylint: disable=import-outside-toplevel SigningDialogue, ) return SigningDialogue.Role.DECISION_MAKER # pylint: disable=import-outside-toplevel from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, **kwargs, ) class StateUpdateDialogues(BaseStateUpdateDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: Address, **kwargs: Any) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param kwargs: the keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ from packages.fetchai.protocols.state_update.dialogues import ( # noqa: F811 # pylint: disable=import-outside-toplevel StateUpdateDialogue, ) return StateUpdateDialogue.Role.DECISION_MAKER # pylint: disable=import-outside-toplevel from packages.fetchai.protocols.state_update.dialogues import ( # noqa: F401 StateUpdateDialogues as BaseStateUpdateDialogues, ) BaseStateUpdateDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, **kwargs, ) __slots__ = ("signing_dialogues", "state_update_dialogues") def __init__( self, identity: Identity, wallet: Wallet, config: Dict[str, Any] ) -> None: """ Initialize the decision maker. :param identity: the identity :param wallet: the wallet :param config: the user defined configuration of the handler """ kwargs: Dict[str, Any] = { "goal_pursuit_readiness": GoalPursuitReadiness(), "ownership_state": OwnershipState(), "preferences": Preferences(), } super().__init__(identity=identity, wallet=wallet, config=config, **kwargs) self.signing_dialogues = DecisionMakerHandler.SigningDialogues( self.self_address ) self.state_update_dialogues = DecisionMakerHandler.StateUpdateDialogues( self.self_address ) def handle(self, message: Message) -> None: """ Handle an internal message from the skills. :param message: the internal message """ if isinstance(message, self.signing_msg_class): self._handle_signing_message(message) elif isinstance(message, self.state_update_msg_class): self._handle_state_update_message(message) else: # pragma: no cover self.logger.error( "[{}]: cannot handle message={} of type={}".format( self.agent_name, message, type(message) ) ) def _handle_signing_message(self, signing_msg: SigningMessage) -> None: """ Handle a signing message. :param signing_msg: the transaction message """ if not self.context.goal_pursuit_readiness.is_ready: self.logger.debug( "[{}]: Preferences and ownership state not initialized!".format( self.agent_name ) ) signing_dialogue = self.signing_dialogues.update(signing_msg) if signing_dialogue is None or not isinstance( signing_dialogue, self.signing_dialogues_class ): # pragma: no cover self.logger.error( "[{}]: Could not construct signing dialogue. Aborting!".format( self.agent_name ) ) return # check if the transaction is acceptable and process it accordingly if signing_msg.performative == self.signing_msg_class.Performative.SIGN_MESSAGE: self._handle_message_signing(signing_msg, signing_dialogue) elif ( signing_msg.performative == self.signing_msg_class.Performative.SIGN_TRANSACTION ): self._handle_transaction_signing(signing_msg, signing_dialogue) else: # pragma: no cover self.logger.error( "[{}]: Unexpected transaction message performative".format( self.agent_name ) ) def _handle_message_signing( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a message for signing. :param signing_msg: the signing message :param signing_dialogue: the signing dialogue """ performative = self.signing_msg_class.Performative.ERROR kwargs = { "error_code": self.signing_msg_class.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, } # type: Dict[str, Any] if self._is_acceptable_for_signing(signing_msg): signed_message = self.wallet.sign_message( signing_msg.raw_message.ledger_id, signing_msg.raw_message.body, signing_msg.raw_message.is_deprecated_mode, ) if signed_message is not None: performative = self.signing_msg_class.Performative.SIGNED_MESSAGE kwargs.pop("error_code") kwargs["signed_message"] = SignedMessage( signing_msg.raw_message.ledger_id, signed_message, signing_msg.raw_message.is_deprecated_mode, ) signing_msg_response = signing_dialogue.reply( performative=performative, target_message=signing_msg, **kwargs, ) self.message_out_queue.put(signing_msg_response) def _handle_transaction_signing( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a transaction for signing. :param signing_msg: the signing message :param signing_dialogue: the signing dialogue """ performative = self.signing_msg_class.Performative.ERROR kwargs = { "error_code": self.signing_msg_class.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, } # type: Dict[str, Any] if self._is_acceptable_for_signing(signing_msg): signed_tx = self.wallet.sign_transaction( signing_msg.raw_transaction.ledger_id, signing_msg.raw_transaction.body ) if signed_tx is not None: performative = self.signing_msg_class.Performative.SIGNED_TRANSACTION kwargs.pop("error_code") kwargs["signed_transaction"] = SignedTransaction( signing_msg.raw_transaction.ledger_id, signed_tx ) signing_msg_response = signing_dialogue.reply( performative=performative, target_message=signing_msg, **kwargs, ) self.message_out_queue.put(signing_msg_response) def _is_acceptable_for_signing(self, signing_msg: SigningMessage) -> bool: """ Check if the tx message is acceptable for signing. :param signing_msg: the transaction message :return: whether the transaction is acceptable or not """ result = self.context.preferences.is_utility_enhancing( self.context.ownership_state, signing_msg.terms ) and self.context.ownership_state.is_affordable(signing_msg.terms) return result def _handle_state_update_message( self, state_update_msg: StateUpdateMessage ) -> None: """ Handle a state update message. :param state_update_msg: the state update message """ state_update_dialogue = self.state_update_dialogues.update(state_update_msg) if state_update_dialogue is None or not isinstance( state_update_dialogue, self.state_update_dialogue_class ): # pragma: no cover self.logger.error( "[{}]: Could not construct state_update dialogue. Aborting!".format( self.agent_name ) ) return if ( state_update_msg.performative == self.state_update_msg_class.Performative.INITIALIZE ): self.logger.info( "[{}]: Applying ownership_state and preferences initialization!".format( self.agent_name ) ) self.context.ownership_state.set( amount_by_currency_id=state_update_msg.amount_by_currency_id, quantities_by_good_id=state_update_msg.quantities_by_good_id, ) self.context.preferences.set( exchange_params_by_currency_id=state_update_msg.exchange_params_by_currency_id, utility_params_by_good_id=state_update_msg.utility_params_by_good_id, ) self.context.goal_pursuit_readiness.update( GoalPursuitReadiness.Status.READY ) elif ( state_update_msg.performative == self.state_update_msg_class.Performative.APPLY ): self.logger.info("[{}]: Applying state update!".format(self.agent_name)) self.context.ownership_state.apply_delta( delta_amount_by_currency_id=state_update_msg.amount_by_currency_id, delta_quantities_by_good_id=state_update_msg.quantities_by_good_id, ) ================================================ FILE: aea/decision_maker/scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a scaffold of the decision maker class and auxiliary classes.""" from typing import Any, Dict from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMakerHandler as BaseDecisionMakerHandler from aea.identity.base import Identity from aea.protocols.base import Message class DecisionMakerHandler(BaseDecisionMakerHandler): """This class implements the decision maker.""" def __init__( self, identity: Identity, wallet: Wallet, config: Dict[str, Any] ) -> None: """ Initialize the decision maker. :param identity: the identity :param wallet: the wallet :param config: the user defined configuration of the handler """ kwargs = { # Add your objects here, they will be accessible in the `handle` method via `self.context`. # They will also be accessible from the skill context. } # type: Dict[str, Any] # You MUST NOT modify the constructor below: super().__init__( identity=identity, wallet=wallet, config=config, **kwargs, ) def handle(self, message: Message) -> None: """ Handle an internal message from the skills. This method is used to: - update the ownership state - check transactions satisfy the preferences :param message: the message """ raise NotImplementedError ================================================ FILE: aea/error_handler/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the error handler modules.""" ================================================ FILE: aea/error_handler/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the abstract error handler class.""" from abc import ABC, abstractmethod from logging import Logger from typing import Any, Dict from aea.mail.base import Envelope class AbstractErrorHandler(ABC): """Error handler class for handling problematic envelopes.""" __slots__ = ("_config",) def __init__(self, **kwargs: Any): """Instantiate error handler.""" self._config = kwargs @property def config(self) -> Dict[str, Any]: """Get handler config.""" return self._config @abstractmethod def send_unsupported_protocol(self, envelope: Envelope, logger: Logger) -> None: """ Handle the received envelope in case the protocol is not supported. :param envelope: the envelope :param logger: the logger :return: None """ @abstractmethod def send_decoding_error( self, envelope: Envelope, exception: Exception, logger: Logger ) -> None: """ Handle a decoding error. :param envelope: the envelope :param exception: the exception raised during decoding :param logger: the logger :return: None """ @abstractmethod def send_no_active_handler( self, envelope: Envelope, reason: str, logger: Logger ) -> None: """ Handle the received envelope in case the handler is not supported. :param envelope: the envelope :param reason: the reason for the failure :param logger: the logger :return: None """ ================================================ FILE: aea/error_handler/default.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the default error handler class.""" from logging import Logger from typing import Any from aea.error_handler.base import AbstractErrorHandler from aea.mail.base import Envelope class ErrorHandler(AbstractErrorHandler): """Error handler class for handling problematic envelopes.""" __slots__ = ( "unsupported_protocol_count", "no_active_handler_count", "decoding_error_count", ) def __init__(self, **kwargs: Any): """Instantiate error handler.""" super().__init__(**kwargs) self.unsupported_protocol_count = 0 self.no_active_handler_count = 0 self.decoding_error_count = 0 def send_unsupported_protocol(self, envelope: Envelope, logger: Logger) -> None: """ Handle the received envelope in case the protocol is not supported. :param envelope: the envelope :param logger: the logger """ self.unsupported_protocol_count += 1 logger.warning( f"Unsupported protocol: protocol_specification_id={envelope.protocol_specification_id}. You might want to add a handler for a protocol implementing this specification. Sender={envelope.sender}, to={envelope.sender}." ) def send_decoding_error( self, envelope: Envelope, exception: Exception, logger: Logger ) -> None: """ Handle a decoding error. :param envelope: the envelope :param exception: the exception raised during decoding :param logger: the logger """ self.decoding_error_count += 1 logger.warning( f"Decoding error for envelope: {envelope}. Protocol_specification_id='{envelope.protocol_specification_id}' and message are inconsistent. Sender={envelope.sender}, to={envelope.sender}. Exception={exception}." ) def send_no_active_handler( self, envelope: Envelope, reason: str, logger: Logger ) -> None: """ Handle the received envelope in case the handler is not supported. :param envelope: the envelope :param reason: the reason for the failure :param logger: the logger """ self.no_active_handler_count += 1 logger.warning( f"Cannot handle envelope: {reason}. Sender={envelope.sender}, to={envelope.sender}." ) ================================================ FILE: aea/error_handler/scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a scaffold of the error handler class.""" from logging import Logger from aea.error_handler.base import AbstractErrorHandler from aea.mail.base import Envelope class ErrorHandler(AbstractErrorHandler): """This class implements the error handler.""" def send_unsupported_protocol(self, envelope: Envelope, logger: Logger) -> None: """ Handle the received envelope in case the protocol is not supported. :param envelope: the envelope :param logger: the logger """ raise NotImplementedError def send_decoding_error( self, envelope: Envelope, exception: Exception, logger: Logger ) -> None: """ Handle a decoding error. :param envelope: the envelope :param exception: the exception raised during decoding :param logger: the logger """ raise NotImplementedError def send_no_active_handler( self, envelope: Envelope, reason: str, logger: Logger ) -> None: """ Handle the received envelope in case the handler is not supported. :param envelope: the envelope :param reason: the reason for the failure :param logger: the logger """ raise NotImplementedError ================================================ FILE: aea/exceptions.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Exceptions for the AEA package.""" import traceback from typing import Optional, Type class AEAException(Exception): """User-defined exception for the AEA framework.""" class AEAPackageLoadingError(AEAException): """Class for exceptions that are raised for loading errors of AEA packages.""" class AEASetupError(AEAException): """Class for exceptions that are raised for setup errors of AEA packages.""" class AEATeardownError(AEAException): """Class for exceptions that are raised for teardown errors of AEA packages.""" class AEAActException(AEAException): """Class for exceptions that are raised for act errors of AEA packages.""" class AEAHandleException(AEAException): """Class for exceptions that are raised for handler errors of AEA packages.""" class AEAInstantiationException(AEAException): """Class for exceptions that are raised for instantiation errors of AEA packages.""" class AEAPluginError(AEAException): """Class for exceptions that are raised for wrong plugin setup of the working set.""" class AEAEnforceError(AEAException): """Class for enforcement errors.""" class AEAValidationError(AEAException): """Class for validation errors of an AEA.""" class AEAComponentLoadException(AEAException): """Class for component loading errors of an AEA.""" class AEAWalletNoAddressException(AEAException): """Class for attempts to instantiate a wallet without addresses.""" class _StopRuntime(Exception): """ Exception to stop runtime. For internal usage only! Used to perform asyncio call from sync callbacks. """ def __init__(self, reraise: Optional[Exception] = None) -> None: """ Init _StopRuntime exception. :param reraise: exception to reraise. """ self.reraise = reraise super().__init__("Stop runtime exception.") def enforce( is_valid_condition: bool, exception_text: str, exception_class: Type[Exception] = AEAEnforceError, ) -> None: """ Evaluate a condition and raise an exception with the provided text if it is not satisfied. :param is_valid_condition: the valid condition :param exception_text: the exception to be raised :param exception_class: the class of exception """ if not is_valid_condition: raise exception_class(exception_text) def parse_exception(exception: Exception, limit: int = -1) -> str: """ Parse an exception to get the relevant lines. :param exception: the exception to be parsed :param limit: the limit :return: exception as string """ if isinstance(exception, AEAEnforceError): limit = -2 msgs = traceback.format_exception( type(exception), exception, exception.__traceback__, limit=limit ) e_str = "\n".join(msgs) return e_str ================================================ FILE: aea/helpers/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains helper methods and classes for the 'aea' package.""" ================================================ FILE: aea/helpers/acn/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains types and helpers used by libp2p connections.""" ================================================ FILE: aea/helpers/acn/agent_record.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains types and helpers for ACN Proof-of-Representation.""" from typing import Optional from aea.common import PathLike from aea.crypto.registries import make_ledger_api from aea.helpers.base import CertRequest, SimpleId, SimpleIdOrStr class AgentRecord: """Agent Proof-of-Representation to representative.""" __slots__ = ( "_address", "_representative_public_key", "_identifier", "_ledger_id", "_not_before", "_not_after", "_message_format", "_signature", "_message", "_public_key", ) def __init__( self, address: str, representative_public_key: str, identifier: SimpleIdOrStr, ledger_id: SimpleIdOrStr, not_before: str, not_after: str, message_format: str, signature: str, ) -> None: """ Initialize the AgentRecord :param address: agent address :param representative_public_key: representative's public key :param identifier: certificate identifier. :param ledger_id: ledger identifier the request is referring to. :param not_before: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. :param not_after: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. :param message_format: message format used for signing :param signature: proof-of-representation of this AgentRecord """ self._address = address self._representative_public_key = representative_public_key self._identifier = str(SimpleId(identifier)) self._ledger_id = str(SimpleId(ledger_id)) self._not_before = not_before self._not_after = not_after self._message_format = message_format self._signature = signature self._message = CertRequest.construct_message( self.representative_public_key, self.identifier, self.not_before, self.not_after, self.message_format, ) self._public_key = self._check_validity() def _check_validity(self) -> str: """ Checks validity of record. Specifically: - if ledger_id is valid - if agent signed the message :return: agent public key """ ledger_api = make_ledger_api(self.ledger_id) public_keys = ledger_api.recover_public_keys_from_message( self.message, self.signature ) if len(public_keys) == 0: raise ValueError("Malformed signature!") # pragma: no cover public_key: Optional[str] = None for public_key_ in public_keys: address = ledger_api.get_address_from_public_key(public_key_) if address == self.address: public_key = public_key_ break if public_key is None: raise ValueError( "Invalid signature for provided representative_public_key and agent address!" ) return public_key @property def address(self) -> str: """Get agent address""" return self._address @property def public_key(self) -> str: """Get agent public key""" return self._public_key @property def representative_public_key(self) -> str: """Get agent representative's public key""" return self._representative_public_key @property def signature(self) -> str: """Get record signature""" return self._signature @property def message(self) -> bytes: """Get the message.""" return self._message @property def identifier(self) -> SimpleIdOrStr: """Get the identifier.""" return self._identifier @property def ledger_id(self) -> SimpleIdOrStr: """Get ledger id.""" return self._ledger_id @property def not_before(self) -> str: """Get the not_before field.""" return self._not_before @property def not_after(self) -> str: """Get the not_after field.""" return self._not_after @property def message_format(self) -> str: """Get the message format.""" return self._message_format def __str__(self) -> str: # pragma: no cover """Get string representation.""" return f"(address={self.address}, public_key={self.public_key}, representative_public_key={self.representative_public_key}, signature={self.signature}, ledger_id={self.ledger_id})" @classmethod def from_cert_request( cls, cert_request: CertRequest, address: str, representative_public_key: str, data_dir: Optional[PathLike] = None, ) -> "AgentRecord": """Get agent record from cert request.""" signature = cert_request.get_signature(data_dir) record = cls( address, representative_public_key, cert_request.identifier, cert_request.ledger_id, cert_request.not_before_string, cert_request.not_after_string, cert_request.message_format, signature, ) return record ================================================ FILE: aea/helpers/acn/uri.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains types and helpers for libp2p connections Uris.""" from random import randint from typing import Optional class Uri: """Holds a node address in format "host:port".""" __slots__ = ("_host", "_port") def __init__( self, uri: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, ) -> None: """Initialise Uri.""" if uri is not None: split = uri.split(":", 1) self._host = split[0] self._port = int(split[1]) elif host is not None and port is not None: self._host = host self._port = port else: self._host = "127.0.0.1" self._port = randint(5000, 10000) # nosec def __str__(self) -> str: """Get string representation.""" return "{}:{}".format(self._host, self._port) def __repr__(self) -> str: # pragma: no cover """Get object representation.""" return self.__str__() @property def host(self) -> str: """Get host.""" return self._host @property def port(self) -> int: """Get port.""" return self._port ================================================ FILE: aea/helpers/async_friendly_queue.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of AsyncFriendlyQueue.""" import asyncio import queue from collections import deque from contextlib import suppress from typing import Any, Deque class AsyncFriendlyQueue(queue.Queue): """queue.Queue with async_get method.""" def __init__(self, *args: Any, **kwargs: Any) -> None: """Init queue.""" super().__init__(*args, **kwargs) self._non_empty_waiters: Deque = deque() def put( # pylint: disable=signature-differs self, item: Any, *args: Any, **kwargs: Any ) -> None: """ Put an item into the queue. :param item: item to put in the queue :param args: similar to queue.Queue.put :param kwargs: similar to queue.Queue.put """ super().put(item, *args, **kwargs) if self._non_empty_waiters: waiter = self._non_empty_waiters.popleft() waiter._loop.call_soon_threadsafe( # pylint: disable=protected-access self._set_waiter, waiter ) @staticmethod def _set_waiter(waiter: Any) -> None: """Set waiter result.""" if waiter.done(): # pragma: nocover return waiter.set_result(True) def get( # pylint: disable=signature-differs self, *args: Any, **kwargs: Any ) -> Any: """ Get an item into the queue. :param args: similar to queue.Queue.get :param kwargs: similar to queue.Queue.get :return: similar to queue.Queue.get """ return super().get(*args, **kwargs) async def async_wait(self) -> None: """ Wait an item appears in the queue. :return: None """ if not self.empty(): return waiter = asyncio.Future() # type: ignore self._non_empty_waiters.append(waiter) try: await waiter finally: try: self._non_empty_waiters.remove(waiter) except ValueError: pass async def async_get(self) -> Any: """ Wait and get an item from the queue. :return: item from queue """ while True: await self.async_wait() with suppress(queue.Empty): item = self.get_nowait() return item ================================================ FILE: aea/helpers/async_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the misc utils for async code.""" import asyncio import datetime import logging import time from abc import ABC, abstractmethod from asyncio import CancelledError from asyncio.events import AbstractEventLoop, TimerHandle from asyncio.futures import Future from collections.abc import Iterable from contextlib import contextmanager from threading import Thread from typing import ( Any, Awaitable, Callable, Container, Coroutine, Generator, List, Optional, Sequence, Set, Tuple, Union, cast, ) try: from asyncio import create_task # pylint: disable=ungrouped-imports,unused-import except ImportError: # pragma: no cover # for python3.6! from asyncio import ( # type: ignore # noqa: F401 # pylint: disable=ungrouped-imports,unused-import ensure_future as create_task, ) _default_logger = logging.getLogger(__file__) def ensure_list(value: Any) -> List: """Return [value] or list(value) if value is a sequence.""" if isinstance(value, list): return value if isinstance(value, Iterable): return list(value) return [value] not_set = object() class AsyncState: """Awaitable state.""" def __init__( self, initial_state: Any = None, states_enum: Optional[Container[Any]] = None ) -> None: """Init async state. :param initial_state: state to set on start. :param states_enum: container of valid states if not provided state not checked on set. """ self._state = initial_state self._watchers: Set[Future] = set() self._callbacks: List[Callable[[Any], None]] = [] self._states_enum = states_enum def set(self, state: Any) -> None: """Set state.""" if self._states_enum is not None and state not in self._states_enum: raise ValueError( f"Unsupported state: {state}. Valid states are {self._states_enum}" ) if self._state == state: # pragma: no cover return self._state_changed(state) self._state = state def add_callback(self, callback_fn: Callable[[Any], None]) -> None: """ Add callback to track state changes. :param callback_fn: callable object to be called on state changed. """ self._callbacks.append(callback_fn) def get(self) -> Any: """Get state.""" return self._state def _state_changed(self, state: Any) -> None: """Fulfill watchers for state.""" for callback_fn in self._callbacks: try: callback_fn(state) except Exception: # pylint: disable=broad-except _default_logger.exception(f"Exception on calling {callback_fn}") for watcher in list(self._watchers): if state not in watcher._states: # type: ignore # pylint: disable=protected-access # pragma: nocover continue if not watcher.done(): watcher._loop.call_soon_threadsafe( # pylint: disable=protected-access self._watcher_result_callback(watcher), (self._state, state) ) self._remove_watcher(watcher) def _remove_watcher(self, watcher: Future) -> None: """Remove watcher for state wait.""" try: self._watchers.remove(watcher) except KeyError: pass @staticmethod def _watcher_result_callback(watcher: Future) -> Callable: """Create callback for watcher result.""" # docstyle. def _callback(result: Any) -> None: if watcher.done(): # pragma: nocover return watcher.set_result(result) return _callback async def wait(self, state_or_states: Union[Any, Sequence[Any]]) -> Tuple[Any, Any]: """Wait state to be set. :param state_or_states: state or list of states. :return: tuple of previous state and new state. """ states = ensure_list(state_or_states) if self._state in states: return (None, self._state) watcher: Future = Future() watcher._states = states # type: ignore # pylint: disable=protected-access self._watchers.add(watcher) try: return await watcher finally: self._remove_watcher(watcher) @contextmanager def transit( self, initial: Any = not_set, success: Any = not_set, fail: Any = not_set ) -> Generator: """ Change state context according to success or not. :param initial: set state on context enter, not_set by default :param success: set state on context block done, not_set by default :param fail: set state on context block raises exception, not_set by default :yield: generator """ try: if initial is not not_set: self.set(initial) yield if success is not not_set: self.set(success) except BaseException: if fail is not not_set: self.set(fail) raise class PeriodicCaller: """ Schedule a periodic call of callable using event loop. Used for periodic function run using asyncio. """ def __init__( self, callback: Callable, period: float, start_at: Optional[datetime.datetime] = None, exception_callback: Optional[Callable[[Callable, Exception], None]] = None, loop: Optional[AbstractEventLoop] = None, ) -> None: """ Init periodic caller. :param callback: function to call periodically :param period: period in seconds. :param start_at: optional first call datetime :param exception_callback: optional handler to call on exception raised. :param loop: optional asyncio event loop """ self._loop = loop or asyncio.get_event_loop() self._periodic_callable = callback self._start_at = start_at or datetime.datetime.now() self._period = period self._timerhandle: Optional[TimerHandle] = None self._exception_callback = exception_callback def _callback(self) -> None: """Call on each scheduled call.""" self._schedule_call() try: self._periodic_callable() except Exception as exception: # pylint: disable=broad-except self.stop() if not self._exception_callback: # pragma: nocover raise self._exception_callback(self._periodic_callable, exception) def _schedule_call(self) -> None: """Set schedule for call.""" if self._timerhandle is None: ts = time.mktime(self._start_at.timetuple()) delay = max(0, ts - time.time()) self._timerhandle = self._loop.call_later(delay, self._callback) else: self._timerhandle = self._loop.call_later(self._period, self._callback) def start(self) -> None: """Activate period calls.""" if self._timerhandle: # pragma: nocover return self._schedule_call() def stop(self) -> None: """Remove from schedule.""" if not self._timerhandle: # pragma: nocover return self._timerhandle.cancel() self._timerhandle = None class AnotherThreadTask: """ Schedule a task to run on the loop in another thread. Provides better cancel behaviour: on cancel it will wait till cancelled completely. """ def __init__(self, coro: Coroutine[Any, Any, Any], loop: AbstractEventLoop) -> None: """ Init the task. :param coro: coroutine to schedule :param loop: an event loop to schedule on. """ self._loop = loop self._coro = coro self._task: Optional[asyncio.Task] = None self._future = asyncio.run_coroutine_threadsafe(self._get_task_result(), loop) async def _get_task_result(self) -> Any: """ Get task result, should be run in target loop. :return: task result value or raise an exception if task failed """ self._task = self._loop.create_task(self._coro) return await self._task def result(self, timeout: Optional[float] = None) -> Any: """ Wait for coroutine execution result. :param timeout: optional timeout to wait in seconds. :return: result """ return self._future.result(timeout) def cancel(self) -> None: """Cancel coroutine task execution in a target loop.""" if self._task is None: self._loop.call_soon_threadsafe(self._future.cancel) else: self._loop.call_soon_threadsafe(self._task.cancel) def done(self) -> bool: """Check task is done.""" return self._future.done() class ThreadedAsyncRunner(Thread): """Util to run thread with event loop and execute coroutines inside.""" def __init__(self, loop: Optional[AbstractEventLoop] = None) -> None: """ Init threaded runner. :param loop: optional event loop. is it's running loop, threaded runner will use it. """ self._loop = loop or asyncio.new_event_loop() if self._loop.is_closed(): raise ValueError("Event loop closed.") # pragma: nocover super().__init__(daemon=True) def start(self) -> None: """Start event loop in dedicated thread.""" if self.is_alive() or self._loop.is_running(): # pragma: nocover return super().start() self.call(asyncio.sleep(0.001)).result(1) def run(self) -> None: """Run code inside thread.""" _default_logger.debug("Starting threaded asyncio loop...") asyncio.set_event_loop(self._loop) self._loop.run_forever() _default_logger.debug("Asyncio loop has been stopped.") def call(self, coro: Coroutine[Any, Any, Any]) -> Any: """ Run a coroutine inside the event loop. :param coro: a coroutine to run. :return: task """ return AnotherThreadTask(coro, self._loop) def stop(self) -> None: """Stop event loop in thread.""" _default_logger.debug("Stopping...") if not self.is_alive(): # pragma: nocover return if self._loop.is_running(): _default_logger.debug("Stopping loop...") self._loop.call_soon_threadsafe(self._loop.stop) _default_logger.debug("Wait thread to join...") self.join(10) _default_logger.debug("Stopped.") ready_future: Future = Future() ready_future.set_result(None) class Runnable(ABC): """ Abstract Runnable class. Use to run async task in same event loop or in dedicated thread. Provides: start, stop sync methods to start and stop task Use wait_completed to await task was completed. """ def __init__( self, loop: asyncio.AbstractEventLoop = None, threaded: bool = False ) -> None: """ Init runnable. :param loop: asyncio event loop to use. :param threaded: bool. start in thread if True. """ if loop and threaded: raise ValueError( "You can not set a loop in threaded mode. A dedicated loop will be created for each thread." ) self._loop = loop self._threaded = threaded self._task: Optional[asyncio.Task] = None self._thread: Optional[Thread] = None self._got_result = False self._was_cancelled = False self._is_running: bool = False self._stop_called = 0 def start(self) -> bool: """ Start runnable. :return: bool started or not. """ if self._task and not self._task.done(): _default_logger.debug(f"{self} already running") return False self._is_running = False self._got_result = False self._set_loop() self._was_cancelled = False if self._stop_called > 0: # used in case of race when stop called before start! _default_logger.debug(f"{self} was already stopped before started!") self._stop_called = 0 return True self._set_task() if self._threaded: self._thread = Thread( target=self._thread_target, name=self.__class__.__name__, # type: ignore # loop was set in set_loop daemon=True, ) self._thread.start() self._stop_called = 0 return True def _thread_target(self) -> None: """Start event loop and task in the dedicated thread.""" if not self._loop: raise ValueError("Call _set_loop() first!") # pragma: nocover if not self._task: raise ValueError("Call _set_task() first!") # pragma: nocover try: self._loop.run_until_complete(self._task) except BaseException: # pylint: disable=broad-except) logging.exception(f"Exception raised in {self}") self._loop.stop() self._loop.close() def _set_loop(self) -> None: """Select and set loop.""" if self._threaded: self._loop = asyncio.new_event_loop() else: try: self._loop = self._loop or asyncio.get_event_loop() except RuntimeError: self._loop = asyncio.new_event_loop() asyncio.set_event_loop(self._loop) def _set_task(self) -> None: """Create task.""" if not self._loop: # pragma: nocover raise ValueError("Loop was not set.") self._task = self._loop.create_task(self._run_wrapper()) _default_logger.debug(f"{self} task set") async def _run_wrapper(self) -> None: """Wrap run() method.""" if not self._loop: # pragma: nocover raise ValueError("Start was not called!") self._is_running = True try: return await self.run() except CancelledError: if not self._was_cancelled: raise finally: self._is_running = False @property def is_running(self) -> bool: # pragma: nocover """Get running state.""" return self._is_running @abstractmethod async def run(self) -> Any: """Implement run logic respectful to CancelError on termination.""" def wait_completed( self, sync: bool = False, timeout: float = None, force_result: bool = False ) -> Union[Coroutine, Awaitable]: """ Wait runnable execution completed. :param sync: bool. blocking wait :param timeout: float seconds :param force_result: check result even it was waited. :return: awaitable if sync is False, otherwise None """ if not self._task: _default_logger.warning("Runnable is not started") return ready_future if self._got_result and not force_result: return ready_future if sync: self._wait_sync(timeout) return ready_future return asyncio.wait_for(self._wait_async(timeout), timeout=timeout) def _wait_sync(self, timeout: Optional[float] = None) -> None: """Wait task completed in sync manner.""" if self._task is None or not self._loop: # pragma: nocover raise ValueError("task is not set!") if self._threaded or self._loop.is_running(): start_time = time.time() while not self._task.done(): time.sleep(0.01) if timeout is not None and time.time() - start_time > timeout: raise asyncio.TimeoutError() if self._thread: self._thread.join(timeout) self._got_result = True if self._task.exception(): raise cast(Exception, self._task.exception()) else: self._loop.run_until_complete( asyncio.wait_for(self._wait(), timeout=timeout) ) def _wait_async(self, timeout: Optional[float] = None) -> Awaitable: if not self._threaded: return asyncio.wait_for(self._wait(), timeout=timeout) if self._task is None: # pragma: nocover raise ValueError("task is not set!") # for threaded mode create a future and bind it to task loop = asyncio.get_event_loop() fut = loop.create_future() def done(task: Future) -> None: try: if fut.done(): # pragma: nocover return if task.exception(): fut.set_exception(task.exception()) # type: ignore else: # pragma: nocover fut.set_result(None) finally: self._got_result = True if self._task.done(): done(self._task) else: self._task.add_done_callback( lambda task: loop.call_soon_threadsafe(lambda: done(task)) ) return fut async def _wait(self) -> None: """Wait internal method.""" if not self._task: # pragma: nocover raise ValueError("Not started") try: await self._task except CancelledError: if not self._was_cancelled: raise finally: self._got_result = True def stop(self, force: bool = False) -> None: """Stop runnable.""" _default_logger.debug(f"{self} is going to be stopped {self._task}") if not self._task or not self._loop: # pragma: nocover self._stop_called += 1 return if self._task.done(): return self._loop.call_soon_threadsafe(self._task_cancel, force) def _task_cancel(self, force: bool = False) -> None: """Cancel task internal method.""" if self._task is None: return if self._was_cancelled and not force: return self._was_cancelled = True self._task.cancel() def start_and_wait_completed( self, *args: Any, **kwargs: Any ) -> Union[Coroutine, Awaitable]: """Alias for start and wait methods.""" self.start() return self.wait_completed(*args, **kwargs) ================================================ FILE: aea/helpers/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Miscellaneous helpers.""" import builtins import contextlib import datetime import importlib.util import logging import os import platform import re import shutil import signal import subprocess # nosec import sys import time import types from collections import OrderedDict, UserString, defaultdict, deque from collections.abc import Mapping from copy import copy from functools import wraps from importlib.machinery import ModuleSpec from pathlib import Path from threading import RLock from typing import ( Any, Callable, Deque, Dict, Generator, Iterable, List, Optional, Set, Tuple, TypeVar, Union, cast, ) from dotenv import load_dotenv from packaging.version import Version from aea.common import PathLike from aea.exceptions import enforce STRING_LENGTH_LIMIT = 128 SIMPLE_ID_REGEX = rf"[a-zA-Z_][a-zA-Z0-9_]{{0,{STRING_LENGTH_LIMIT - 1}}}" ISO_8601_DATE_FORMAT = "%Y-%m-%d" _default_logger = logging.getLogger(__name__) def _get_module(spec: ModuleSpec) -> Optional[types.ModuleType]: """Try to execute a module. Return None if the attempt fail.""" try: module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # type: ignore return module except Exception: # pylint: disable=broad-except return None def locate(path: str) -> Any: """Locate an object by name or dotted save_path, importing as necessary.""" parts = [part for part in path.split(".") if part] module, n = None, 0 while n < len(parts): file_location = os.path.join(*parts[: n + 1]) spec_name = ".".join(parts[: n + 1]) module_location = os.path.join(file_location, "__init__.py") spec = importlib.util.spec_from_file_location(spec_name, module_location) _default_logger.debug("Trying to import {}".format(module_location)) if not spec: raise RuntimeError(f"Error loading module by path {module_location}") nextmodule = _get_module(spec) if nextmodule is None: module_location = file_location + ".py" spec = importlib.util.spec_from_file_location(spec_name, module_location) if not spec: raise RuntimeError(f"Error loading module by path {module_location}") _default_logger.debug("Trying to import {}".format(module_location)) nextmodule = _get_module(spec) if nextmodule: module, n = nextmodule, n + 1 else: # pragma: nocover break if module: object_ = module else: object_ = builtins for part in parts[n:]: try: object_ = getattr(object_, part) except AttributeError: return None return object_ def load_module(dotted_path: str, filepath: Path) -> types.ModuleType: """ Load a module. :param dotted_path: the dotted save_path of the package/module. :param filepath: the file to the package/module. :return: module type :raises ValueError: if the filepath provided is not a module. # noqa: DAR402 :raises Exception: if the execution of the module raises exception. # noqa: DAR402 """ spec = importlib.util.spec_from_file_location(dotted_path, str(filepath)) if not spec: raise RuntimeError(f"Error loading module by path {filepath}") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # type: ignore return module def load_env_file(env_file: str) -> None: """ Load the content of the environment file into the process environment. :param env_file: save_path to the env file. """ load_dotenv(dotenv_path=Path(env_file), override=False) def sigint_crossplatform(process: subprocess.Popen) -> None: # pragma: nocover """ Send a SIGINT, cross-platform. The reason is because the subprocess module doesn't have an API to send a SIGINT-like signal both on Posix and Windows with a single method. However, a subprocess.Popen class has the method 'send_signal' that gives more flexibility in this terms. :param process: the process to send the signal to. """ if os.name == "posix": process.send_signal(signal.SIGINT) # pylint: disable=no-member elif os.name == "nt": process.send_signal(signal.CTRL_C_EVENT) # type: ignore # pylint: disable=no-member else: raise ValueError("Other platforms not supported.") def win_popen_kwargs() -> dict: """ Return kwargs to start a process in windows with new process group. Help to handle ctrl c properly. Return empty dict if platform is not win32 :return: windows popen kwargs """ kwargs: dict = {} if sys.platform == "win32": # pragma: nocover startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW kwargs["startupinfo"] = startupinfo kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP return kwargs def send_control_c( process: subprocess.Popen, kill_group: bool = False ) -> None: # pragma: nocover # cause platform dependent """ Send ctrl-C cross-platform to terminate a subprocess. :param process: the process to send the signal to. :param kill_group: whether or not to kill group """ if platform.system() == "Windows": if process.stdin: # cause ctrl-c event will be handled with stdin process.stdin.close() os.kill(process.pid, signal.CTRL_C_EVENT) # type: ignore # pylint: disable=no-member elif kill_group: pgid = os.getpgid(process.pid) os.killpg(pgid, signal.SIGINT) else: os.kill(process.pid, signal.SIGINT) class RegexConstrainedString(UserString): """ A string that is constrained by a regex. The default behaviour is to match anything. Subclass this class and change the 'REGEX' class attribute to implement a different behaviour. """ REGEX = re.compile(".*", flags=re.DOTALL) def __init__(self, seq: Union[UserString, str]) -> None: """Initialize a regex constrained string.""" super().__init__(seq) if not self.REGEX.fullmatch(self.data): self._handle_no_match() def _handle_no_match(self) -> None: raise ValueError( "Value {data} does not match the regular expression {regex}".format( data=self.data, regex=self.REGEX ) ) class SimpleId(RegexConstrainedString): """ A simple identifier. The allowed strings are all the strings that: - have at least length 1 - have at most length 128 - the first character must be between a-z,A-Z or underscore - the other characters must be either the above or digits. Examples of allowed strings: >>> SimpleId("an_identifier") 'an_identifier' Examples of not allowed strings: >>> SimpleId("0an_identifier") Traceback (most recent call last): ... ValueError: Value 0an_identifier does not match the regular expression re.compile('[a-zA-Z_][a-zA-Z0-9_]{0,127}') >>> SimpleId("an identifier") Traceback (most recent call last): ... ValueError: Value an identifier does not match the regular expression re.compile('[a-zA-Z_][a-zA-Z0-9_]{0,127}') >>> SimpleId("") Traceback (most recent call last): ... ValueError: Value does not match the regular expression re.compile('[a-zA-Z_][a-zA-Z0-9_]{0,127}') """ REGEX = re.compile(SIMPLE_ID_REGEX) SimpleIdOrStr = Union[SimpleId, str] @contextlib.contextmanager def cd(path: PathLike) -> Generator: # pragma: nocover """Change working directory temporarily.""" old_path = os.getcwd() os.chdir(path) try: yield finally: os.chdir(old_path) def get_logger_method(fn: Callable, logger_method: Union[str, Callable]) -> Callable: """ Get logger method for function. Get logger in `fn` definition module or creates logger is module.__name__. Or return logger_method if it's callable. :param fn: function to get logger for. :param logger_method: logger name or callable. :return: callable to write log with """ if callable(logger_method): # pragma: nocover return logger_method logger_ = fn.__globals__.get("logger", logging.getLogger(fn.__globals__["__name__"])) # type: ignore return getattr(logger_, logger_method) def try_decorator( error_message: str, default_return: Callable = None, logger_method: Any = "error" ) -> Callable: """ Run function, log and return default value on exception. Does not support async or coroutines! :param error_message: message template with one `{}` for exception :param default_return: value to return on exception, by default None :param logger_method: name of the logger method or callable to print logs :return: the callable """ # for pydocstyle def decorator(fn: Callable) -> Callable: @wraps(fn) def wrapper(*args: Any, **kwargs: Any) -> Callable: try: return fn(*args, **kwargs) except Exception as e: # pylint: disable=broad-except # pragma: no cover # generic code if len(args) > 0 and getattr(args[0], "raise_on_try", False): raise e if error_message: log = get_logger_method(fn, logger_method) log(error_message.format(e)) return cast(Callable, default_return) return wrapper return decorator class MaxRetriesError(Exception): """Exception for retry decorator.""" def retry_decorator( number_of_retries: int, error_message: str, delay: float = 0, logger_method: str = "error", ) -> Callable: """ Run function with several attempts. Does not support async or coroutines! :param number_of_retries: amount of attempts :param error_message: message template with one `{}` for exception :param delay: number of seconds to sleep between retries. default 0 :param logger_method: name of the logger method or callable to print logs :return: the callable """ # for pydocstyle def decorator(fn: Callable) -> Callable: @wraps(fn) def wrapper(*args: Any, **kwargs: Any) -> Callable: log = get_logger_method(fn, logger_method) for retry in range(number_of_retries): try: return fn(*args, **kwargs) except Exception as e: # pylint: disable=broad-except # pragma: no cover # generic code if error_message: log(error_message.format(retry=retry + 1, error=e)) if delay: time.sleep(delay) raise MaxRetriesError(number_of_retries) return wrapper return decorator @contextlib.contextmanager def exception_log_and_reraise(log_method: Callable, message: str) -> Generator: """ Run code in context to log and re raise exception. :param log_method: function to print log :param message: message template to add error text. :yield: the generator """ try: yield except BaseException as e: # pylint: disable=broad-except # pragma: no cover # generic code log_method(message.format(e)) raise def _is_dict_like(obj: Any) -> bool: """ Check whether an object is dict-like (i.e. either dict or OrderedDict). :param obj: the object to test. :return: True if the object is dict-like, False otherwise. """ return type(obj) in (dict, OrderedDict) def recursive_update( to_update: Dict, new_values: Dict, allow_new_values: bool = False, ) -> None: """ Update a dictionary by replacing conflicts with the new values. It does side-effects to the first dictionary. >>> to_update = dict(a=1, b=2, subdict=dict(subfield1=1)) >>> new_values = dict(b=3, subdict=dict(subfield1=2)) >>> recursive_update(to_update, new_values) >>> to_update {'a': 1, 'b': 3, 'subdict': {'subfield1': 2}} :param to_update: the dictionary to update. :param new_values: the dictionary of new values to replace. :param allow_new_values: whether or not to allow new values. """ for key, value in new_values.items(): if (not allow_new_values) and key not in to_update: raise ValueError( f"Key '{key}' is not contained in the dictionary to update." ) if key not in to_update and allow_new_values: to_update[key] = value continue value_to_update = to_update[key] value_type = type(value) value_to_update_type = type(value_to_update) both_are_dict = _is_dict_like(value) and _is_dict_like(value_to_update) if ( not both_are_dict and value_type != value_to_update_type and value is not None and value_to_update is not None ): raise ValueError( f"Trying to replace value '{value_to_update}' with value '{value}' which is of different type." ) if both_are_dict: recursive_update(value_to_update, value, allow_new_values) else: to_update[key] = value def _get_aea_logger_name_prefix(module_name: str, agent_name: str) -> str: """ Get the logger name prefix. It consists of a dotted save_path with: - the name of the package, 'aea'; - the agent name; - the rest of the dotted save_path. >>> _get_aea_logger_name_prefix("aea.save_path.to.package", "myagent") 'aea.myagent.save_path.to.package' :param module_name: the module name. :param agent_name: the agent name. :return: the logger name prefix. """ module_name_parts = module_name.split(".") root = module_name_parts[0] postfix = module_name_parts[1:] return ".".join([root, agent_name, *postfix]) T = TypeVar("T") def find_topological_order(adjacency_list: Dict[T, Set[T]]) -> List[T]: """ Compute the topological order of a graph (using Kahn's algorithm). :param adjacency_list: the adjacency list of the graph. :return: the topological order for the graph (as a sequence of nodes) :raises ValueError: if the graph contains a cycle. """ # compute inverse adjacency list and the roots of the DAG. adjacency_list = copy(adjacency_list) visited: Set[T] = set() roots: Set[T] = set() inverse_adjacency_list: Dict[T, Set[T]] = defaultdict(set) # compute both roots and inverse adjacent list in one pass. for start_node, end_nodes in adjacency_list.items(): if start_node not in visited: roots.add(start_node) visited.update([start_node, *end_nodes]) for end_node in end_nodes: roots.discard(end_node) inverse_adjacency_list[end_node].add(start_node) # compute the topological order queue: Deque[T] = deque() order = [] queue.extendleft(sorted(roots)) # type: ignore while len(queue) > 0: current = queue.pop() order.append(current) next_nodes = adjacency_list.get(current, set()) for node in next_nodes: inverse_adjacency_list[node].discard(current) if len(inverse_adjacency_list[node]) == 0: queue.append(node) # remove all the edges adjacency_list[current] = set() if any(len(edges) > 0 for edges in inverse_adjacency_list.values()): raise ValueError("Graph has at least one cycle.") return order def reachable_nodes( adjacency_list: Dict[T, Set[T]], starting_nodes: Set[T] ) -> Dict[T, Set[T]]: """ Find the reachable subgraph induced by a set of starting nodes. :param adjacency_list: the adjacency list of the full graph. :param starting_nodes: the starting nodes of the new graph. :return: the adjacency list of the subgraph. """ all_nodes = set() for node, nodes in adjacency_list.items(): all_nodes.add(node) all_nodes.update(nodes) enforce( all(s in all_nodes for s in starting_nodes), f"These starting nodes are not in the set of nodes: {starting_nodes.difference(all_nodes)}", ) visited: Set[T] = set() result: Dict[T, Set[T]] = {start_node: set() for start_node in starting_nodes} queue: Deque[T] = deque() queue.extend(starting_nodes) while len(queue) > 0: current = queue.pop() if current in visited or current not in adjacency_list: continue successors = adjacency_list.get(current, set()) result.setdefault(current, set()).update(successors) queue.extendleft(successors) visited.add(current) return result _NOT_FOUND = object() # copied from python3.8 functools class cached_property: # pragma: nocover """Cached property from python3.8 functools.""" def __init__(self, func: Callable) -> None: """Init cached property.""" self.func = func self.attrname = None self.__doc__ = func.__doc__ self.lock = RLock() def __set_name__(self, _: Any, name: Any) -> None: """Set name.""" if self.attrname is None: self.attrname = name elif name != self.attrname: raise TypeError( "Cannot assign the same cached_property to two different names " f"({self.attrname!r} and {name!r})." ) def __get__(self, instance: Any, _: Optional[Any] = None) -> Any: """Get instance.""" if instance is None: return self if self.attrname is None: raise TypeError( "Cannot use cached_property instance without calling __set_name__ on it." ) try: cache = instance.__dict__ except AttributeError: # not all objects have __dict__ (e.g. class defines slots) msg = ( f"No '__dict__' attribute on {type(instance).__name__!r} " f"instance to cache {self.attrname!r} property." ) raise TypeError(msg) from None val = cache.get(self.attrname, _NOT_FOUND) if val is _NOT_FOUND: with self.lock: # check if another thread filled cache while we awaited lock val = cache.get(self.attrname, _NOT_FOUND) if val is _NOT_FOUND: val = self.func(instance) try: cache[self.attrname] = val except TypeError: msg = ( f"The '__dict__' attribute on {type(instance).__name__!r} instance " f"does not support item assignment for caching {self.attrname!r} property." ) raise TypeError(msg) from None return val def ensure_dir(dir_path: str) -> None: """Check if dir_path is a directory or create it.""" if not os.path.exists(dir_path): os.makedirs(dir_path) else: enforce(os.path.isdir(dir_path), f"{dir_path} is not a directory!") def dict_to_path_value( data: Mapping, path: Optional[List] = None ) -> Iterable[Tuple[List[str], Any]]: """Convert dict to sequence of terminal path build of keys and value.""" path = path or [] for key, value in data.items(): if isinstance(value, Mapping) and value: # terminal value for p, v in dict_to_path_value(value, path + [key]): yield p, v else: yield path + [key], value def parse_datetime_from_str(date_string: str) -> datetime.datetime: """Parse datetime from string.""" result = datetime.datetime.strptime(date_string, ISO_8601_DATE_FORMAT) result = result.replace(tzinfo=datetime.timezone.utc) return result class CertRequest: """Certificate request for proof of representation.""" def __init__( self, public_key: str, identifier: SimpleIdOrStr, ledger_id: SimpleIdOrStr, not_before: str, not_after: str, message_format: str, save_path: str, ) -> None: """ Initialize the certificate request. :param public_key: the public key, or the key id. :param identifier: certificate identifier. :param ledger_id: ledger identifier the request is referring to. :param not_before: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. :param not_after: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. :param message_format: message format used for signing :param save_path: the save_path where to save the certificate. """ self._key_identifier: Optional[str] = None self._public_key: Optional[str] = None self._identifier = str(SimpleId(identifier)) self._ledger_id = str(SimpleId(ledger_id)) self._not_before_string = not_before self._not_after_string = not_after self._not_before = self._parse_datetime(not_before) self._not_after = self._parse_datetime(not_after) self._message_format = message_format self._save_path = Path(save_path) self._parse_public_key(public_key) self._check_validation_boundaries() @classmethod def _parse_datetime(cls, obj: Union[str, datetime.datetime]) -> datetime.datetime: """ Parse datetime string. It is expected to follow ISO 8601. :param obj: the input to parse. :return: a datetime.datetime instance. """ result = ( parse_datetime_from_str(obj) # type: ignore if isinstance(obj, str) else obj ) enforce(result.microsecond == 0, "Microsecond field not allowed.") return result def _check_validation_boundaries(self) -> None: """ Check the validation boundaries are consistent. Namely, that not_before < not_after. """ enforce( self._not_before < self._not_after, f"Inconsistent certificate validity period: 'not_before' field '{self._not_before_string}' is not before than 'not_after' field '{self._not_after_string}'", ValueError, ) def _parse_public_key(self, public_key_str: str) -> None: """ Parse public key from string. It first tries to parse it as an identifier, and in case of failure as a sequence of hexadecimals, starting with "0x". :param public_key_str: the public key """ with contextlib.suppress(ValueError): # if this raises ValueError, we don't return self._key_identifier = str(SimpleId(public_key_str)) return with contextlib.suppress(ValueError): # this raises ValueError if the input is not a valid hexadecimal string. int(public_key_str, 16) self._public_key = public_key_str return enforce( False, f"Public key field '{public_key_str}' is neither a valid identifier nor an address.", exception_class=ValueError, ) @property def public_key(self) -> Optional[str]: """Get the public key.""" return self._public_key @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def key_identifier(self) -> Optional[str]: """Get the key identifier.""" return self._key_identifier @property def identifier(self) -> str: """Get the identifier.""" return self._identifier @property def not_before_string(self) -> str: """Get the not_before field as string.""" return self._not_before_string @property def not_after_string(self) -> str: """Get the not_after field as string.""" return self._not_after_string @property def not_before(self) -> datetime.datetime: """Get the not_before field.""" return self._not_before @property def not_after(self) -> datetime.datetime: """Get the not_after field.""" return self._not_after @property def message_format(self) -> str: """Get the message format.""" return self._message_format @property def save_path(self) -> Path: """ Get the save path for the certificate. Note: if the path is *not* absolute, then the actual save path might depend on the context. :return: the save path """ return self._save_path def get_absolute_save_path(self, path_prefix: Optional[PathLike] = None) -> Path: """ Get the absolute save path. If save_path is an absolute path, then the prefix is ignored. Otherwise, the path prefix is prepended. :param path_prefix: the (absolute) path to prepend to the save path. :return: the actual save path. """ path_prefix = ( Path(path_prefix).absolute() if path_prefix is not None else Path.cwd() ) return ( self.save_path if self.save_path.is_absolute() else path_prefix / self.save_path ) @property def public_key_or_identifier(self) -> str: """Get the public key or identifier.""" if (self.public_key is None and self.key_identifier is None) or ( self.public_key is not None and self.key_identifier is not None ): raise ValueError( # pragma: nocover "Exactly one of key_identifier or public_key can be specified." ) if self.public_key is not None: result = self.public_key elif self.key_identifier is not None: result = self.key_identifier return result def get_message(self, public_key: str) -> bytes: """Get the message to sign.""" message = self.construct_message( public_key, self.identifier, self.not_before_string, self.not_after_string, self.message_format, ) return message @classmethod def construct_message( cls, public_key: str, identifier: SimpleIdOrStr, not_before_string: str, not_after_string: str, message_format: str, ) -> bytes: """ Construct message for singning. :param public_key: the public key :param identifier: identifier to be signed :param not_before_string: signature not valid before :param not_after_string: signature not valid after :param message_format: message format used for signing :return: the message """ message = message_format.format( public_key=public_key, identifier=str(identifier), not_before=not_before_string, not_after=not_after_string, ) return message.encode("ascii") def get_signature(self, path_prefix: Optional[PathLike] = None) -> str: """ Get signature from save_path. :param path_prefix: the path prefix to be prepended to save_path. Defaults to cwd. :return: the signature. """ save_path = self.get_absolute_save_path(path_prefix) if not Path(save_path).is_file(): raise Exception( # pragma: no cover f"cert_request 'save_path' field {save_path} is not a file. " "Please ensure that 'issue-certificates' command is called beforehand." ) signature = bytes.fromhex(Path(save_path).read_bytes().decode("ascii")).decode( "ascii" ) return signature @property def json(self) -> Dict: """Compute the JSON representation.""" result = dict( identifier=self.identifier, ledger_id=self.ledger_id, not_before=self._not_before_string, not_after=self._not_after_string, public_key=self.public_key_or_identifier, message_format=self.message_format, save_path=str(self.save_path), ) return result @classmethod def from_json(cls, obj: Dict) -> "CertRequest": """Compute the JSON representation.""" if "message_format" not in obj: # pragma: nocover # for backwards compatibility obj["message_format"] = "{public_key}" return cls(**obj) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, CertRequest) and self.identifier == other.identifier and self.ledger_id == other.ledger_id and self.public_key == other.public_key and self.key_identifier == other.key_identifier and self.not_after == other.not_after and self.not_before == other.not_before and self.message_format == other.message_format and self.save_path == other.save_path ) def compute_specifier_from_version(version: Version) -> str: """ Compute the specifier set from a version. version specifier is: >=major.minor.0, ={lower_bound}, <{upper_bound}" return specifier_set def decorator_with_optional_params(decorator: Callable) -> Callable: """ Make a decorator usable either with or without parameters. In other words, if a decorator "mydecorator" is decorated with this decorator, It can be used both as: @mydecorator def myfunction(): ... or as: @mydecorator(arg1, kwarg1="value") def myfunction(): ... :param decorator: a decorator callable :return: a decorator callable """ @wraps(decorator) def new_decorator(*args: Any, **kwargs: Any) -> Callable: if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): return decorator(args[0]) def final_decorator(real_function: Callable) -> Callable: return decorator(real_function, *args, **kwargs) return final_decorator return new_decorator def delete_directory_contents(directory: Path) -> None: """Delete the content of a directory, without deleting it.""" enforce(directory.is_dir(), f"Path '{directory}' must be a directory.") for filename in directory.iterdir(): if filename.is_file() or filename.is_symlink(): filename.unlink() elif filename.is_dir(): shutil.rmtree(str(filename), ignore_errors=False) def prepend_if_not_absolute(path: PathLike, prefix: PathLike) -> PathLike: """ Prepend a path with a prefix, but only if not absolute :param path: the path to process. :param prefix: the path prefix. :return: the same path if absolute, else the prepended path. """ return path if Path(path).is_absolute() else Path(prefix) / path ================================================ FILE: aea/helpers/constants.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with helpers constants.""" from typing import Dict, List, Union FALSE_EQUIVALENTS = ["f", "false", "False", "0"] FROM_STRING_TO_TYPE = dict( str=str, int=int, bool=bool, float=float, dict=dict, list=list, none=None, ) JSON_TYPES = Union[Dict, str, List, None, int, float] NETWORK_REQUEST_DEFAULT_TIMEOUT = 60.0 # in seconds ================================================ FILE: aea/helpers/env_vars.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the environment variables support.""" import json import re from collections.abc import Mapping as MappingType from typing import Any, Dict, List, Mapping, Union from aea.helpers.constants import FALSE_EQUIVALENTS, FROM_STRING_TO_TYPE, JSON_TYPES ENV_VARIABLE_RE = re.compile( r"^\$\{(?P\w+)(:(?P\w+)(:(?P\w+))?)?\}$", re.I ) def is_env_variable(value: Any) -> bool: """Check is variable string with env variable pattern.""" return isinstance(value, str) and bool(ENV_VARIABLE_RE.match(value)) NotSet = object() def replace_with_env_var( value: str, env_variables: dict, default_value: Any = NotSet ) -> JSON_TYPES: """Replace env var with value.""" result = ENV_VARIABLE_RE.match(value) if not result: return value var_name = result.groupdict()["name"] type_str = result.groupdict()["type"] default = result.groupdict()["default"] if var_name in env_variables: var_value = env_variables[var_name] elif default is not None: var_value = default elif default_value is not NotSet: var_value = default_value else: raise ValueError( f"`{var_name}` not found in env variables and no default value set! Please ensure a .env file is provided." ) if type_str is not None: var_value = convert_value_str_to_type(var_value, type_str) return var_value def apply_env_variables( data: Union[Dict, List[Dict]], env_variables: Mapping[str, Any], default_value: Any = NotSet, ) -> JSON_TYPES: """Create new resulting dict with env variables applied.""" if isinstance(data, (list, tuple)): result = [] for i in data: result.append(apply_env_variables(i, env_variables, default_value)) return result if isinstance(data, MappingType): return { apply_env_variables(k, env_variables, default_value): apply_env_variables( v, env_variables, default_value ) for k, v in data.items() } if is_env_variable(data): return replace_with_env_var(data, env_variables, default_value) return data def convert_value_str_to_type(value: str, type_str: str) -> JSON_TYPES: """Convert value by type name to native python type.""" try: type_ = FROM_STRING_TO_TYPE[type_str] if type_ == bool: return value not in FALSE_EQUIVALENTS if type_ is None: return None if type_ in (dict, list): return json.loads(value) return type_(value) except (ValueError, json.decoder.JSONDecodeError): # pragma: no cover raise ValueError(f"Cannot convert string `{value}` to type `{type_.__name__}`") # type: ignore ================================================ FILE: aea/helpers/exception_policy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains enum of aea exception policies.""" from enum import Enum class ExceptionPolicyEnum(Enum): """AEA Exception policies.""" propagate = "propagate" # just bubble up exception raised. run loop interrupted. just_log = ( "just_log" # write details in log file, skip exception. continue running. ) stop_and_exit = "stop_and_exit" # log exception and stop agent with raising AEAException to show it was terminated ================================================ FILE: aea/helpers/exec_timeout.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Python code execution time limit tools.""" import asyncio import concurrent import ctypes import logging import signal import threading from abc import ABC, abstractmethod from asyncio import Future from asyncio.events import AbstractEventLoop from threading import Lock from types import TracebackType from typing import Any, Optional, Type _default_logger = logging.getLogger(__file__) class TimeoutResult: """Result of ExecTimeout context manager.""" def __init__(self) -> None: """Init.""" self._cancelled_by_timeout = False def set_cancelled_by_timeout(self) -> None: """Set code was terminated cause timeout.""" self._cancelled_by_timeout = True def is_cancelled_by_timeout(self) -> bool: """ Return True if code was terminated by ExecTimeout cause timeout. :return: bool """ return self._cancelled_by_timeout class TimeoutException(BaseException): """ TimeoutException raised by ExecTimeout context managers in thread with limited execution time. Used internally, does not propagated outside of context manager """ class BaseExecTimeout(ABC): """ Base class for implementing context managers to limit python code execution time. exception_class - is exception type to raise in code controlled in case of timeout. """ exception_class: Type[BaseException] = TimeoutException def __init__(self, timeout: float = 0.0) -> None: """ Init. :param timeout: number of seconds to execute code before interruption """ self.timeout = timeout self.result = TimeoutResult() def _on_timeout(self, *args: Any, **kwargs: Any) -> None: """Raise exception on timeout.""" raise self.exception_class() def __enter__(self) -> TimeoutResult: """ Enter context manager. :return: TimeoutResult """ if self.timeout: self._set_timeout_watch() return self.result def __exit__( self, exc_type: Type[Exception], exc_val: Exception, exc_tb: TracebackType ) -> None: """ Exit context manager. :param exc_type: the exception type :param exc_val: the exception :param exc_tb: the traceback """ if self.timeout: self._remove_timeout_watch() if isinstance(exc_val, TimeoutException): self.result.set_cancelled_by_timeout() @abstractmethod def _set_timeout_watch(self) -> None: """ Start control over execution time. Should be implemented in concrete class. """ raise NotImplementedError # pragma: nocover @abstractmethod def _remove_timeout_watch(self) -> None: """ Stop control over execution time. Should be implemented in concrete class. """ raise NotImplementedError # pragma: nocover class ExecTimeoutSigAlarm(BaseExecTimeout): # pylint: disable=too-few-public-methods """ ExecTimeout context manager implementation using signals and SIGALARM. Does not support threads, have to be used only in main thread. """ def _set_timeout_watch(self) -> None: """Start control over execution time.""" signal.setitimer(signal.ITIMER_REAL, self.timeout, 0) signal.signal(signal.SIGALRM, self._on_timeout) def _remove_timeout_watch(self) -> None: """Stop control over execution time.""" signal.setitimer(signal.ITIMER_REAL, 0, 0) class ExecTimeoutThreadGuard(BaseExecTimeout): """ ExecTimeout context manager implementation using threads and PyThreadState_SetAsyncExc. Support threads. Requires supervisor thread start/stop to control execution time control. Possible will be not accurate in case of long c functions used inside code controlled. """ _supervisor_thread: Optional[threading.Thread] = None _loop: Optional[AbstractEventLoop] = None _stopped_future: Optional[Future] = None _start_count: int = 0 _lock: Lock = Lock() _thread_started_event = threading.Event() def __init__(self, timeout: float = 0.0) -> None: """ Init ExecTimeoutThreadGuard variables. :param timeout: number of seconds to execute code before interruption """ super().__init__(timeout=timeout) self._future_guard_task: Optional[concurrent.futures._base.Future[None]] = None self._thread_id: Optional[int] = None @classmethod def start(cls) -> None: """ Start supervisor thread to check timeouts. Supervisor starts once but number of start counted. """ with cls._lock: cls._start_count += 1 if cls._supervisor_thread: # pragma: nocover return cls._loop = asyncio.new_event_loop() cls._supervisor_thread = threading.Thread( target=cls._supervisor_event_loop, daemon=True, name="ExecTimeout" ) cls._supervisor_thread.start() cls._thread_started_event.wait() @classmethod def stop(cls, force: bool = False) -> None: """ Stop supervisor thread. Actual stop performed on force == True or if number of stops == number of starts :param force: force stop regardless number of start. """ with cls._lock: if not cls._supervisor_thread: # pragma: nocover return cls._start_count -= 1 if cls._start_count <= 0 or force: cls._loop.call_soon_threadsafe(cls._set_stopped_future) # type: ignore if cls._supervisor_thread and cls._supervisor_thread.is_alive(): cls._supervisor_thread.join() cls._supervisor_thread = None cls._start_count = 0 @classmethod def _set_stopped_future(cls) -> None: """Set stopped future result.""" if not cls._stopped_future or cls._stopped_future.done(): # pragma: nocover return cls._stopped_future.set_result(True) @classmethod def _supervisor_event_loop(cls) -> None: """Start supervisor thread to execute asyncio task controlling execution time.""" # pydocstyle: noqa # cause black reformats with pydocstyle conflict # noqa: E800 async def wait_stopped() -> None: cls._stopped_future = Future() cls._thread_started_event.set() await cls._stopped_future # type: ignore cls._loop.run_until_complete(wait_stopped()) # type: ignore async def _guard_task(self) -> None: """Task to terminate thread on timeout.""" await asyncio.sleep(self.timeout) self._set_thread_exception(self._thread_id, self.exception_class) # type: ignore @staticmethod def _set_thread_exception(thread_id: int, exception_class: Type[Exception]) -> None: """Terminate code execution in specific thread by setting exception.""" ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread_id), ctypes.py_object(exception_class) ) def _set_timeout_watch(self) -> None: """ Start control over execution time. Set task checking code execution time. ExecTimeoutThreadGuard.start is required at least once in project before usage! """ if not self._supervisor_thread: _default_logger.warning( "ExecTimeoutThreadGuard is used but not started! No timeout wil be applied!" ) return self._thread_id = threading.current_thread().ident self._future_guard_task = asyncio.run_coroutine_threadsafe( self._guard_task(), self._loop # type: ignore ) def _remove_timeout_watch(self) -> None: """ Stop control over execution time. Cancel task checking code execution time. """ if self._future_guard_task and not self._future_guard_task.done(): self._future_guard_task.cancel() self._future_guard_task = None ================================================ FILE: aea/helpers/file_io.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Read to and write from file with envelopes.""" import codecs import logging from contextlib import contextmanager from logging import Logger from typing import Generator, IO, Optional, Union from aea.configurations.base import PublicId from aea.helpers import file_lock from aea.helpers.base import exception_log_and_reraise from aea.mail.base import Envelope SEPARATOR = b"," _default_logger = logging.getLogger(__name__) def _encode(e: Envelope, separator: bytes = SEPARATOR) -> bytes: result = b"" result += e.to.encode("utf-8") result += separator result += e.sender.encode("utf-8") result += separator result += str(e.protocol_specification_id).encode("utf-8") result += separator result += e.message_bytes result += separator return result def _decode(e: bytes, separator: bytes = SEPARATOR) -> Envelope: split = e.split(separator) if len(split) < 5 or split[-1] not in [b"", b"\n"]: raise ValueError( "Expected at least 5 values separated by commas and last value being empty or new line, got {}".format( len(split) ) ) to = split[0].decode("utf-8").strip().lstrip("\x00") sender = split[1].decode("utf-8").strip() protocol_specification_id = PublicId.from_str(split[2].decode("utf-8").strip()) # protobuf messages cannot be delimited as they can contain an arbitrary byte sequence; however # we know everything remaining constitutes the protobuf message. message = SEPARATOR.join(split[3:-1]) if b"\\x" in message: # pragma: nocover # hack to account for manual usage of `echo` message = codecs.decode(message, "unicode-escape").encode("utf-8") return Envelope( to=to, sender=sender, protocol_specification_id=protocol_specification_id, message=message, ) @contextmanager def lock_file( file_descriptor: IO[bytes], logger: Logger = _default_logger ) -> Generator: """Lock file in context manager. :param file_descriptor: file descriptor of file to lock. :param logger: the logger. :yield: generator """ with exception_log_and_reraise( logger.error, f"Couldn't acquire lock for file {file_descriptor.name}: {{}}", ): file_lock.lock(file_descriptor, file_lock.LOCK_EX) try: yield finally: file_lock.unlock(file_descriptor) def write_envelope( envelope: Envelope, file_pointer: IO[bytes], separator: bytes = SEPARATOR, logger: Logger = _default_logger, ) -> None: """Write envelope to file.""" encoded_envelope = _encode(envelope, separator=separator) logger.debug("write {!r}: to {}".format(encoded_envelope, file_pointer.name)) write_with_lock(file_pointer, encoded_envelope, logger) def write_with_lock( file_pointer: IO[bytes], data: Union[bytes], logger: Logger = _default_logger ) -> None: """Write bytes to file protected with file lock.""" with lock_file(file_pointer, logger): file_pointer.write(data) file_pointer.flush() def envelope_from_bytes( bytes_: bytes, separator: bytes = SEPARATOR, logger: Logger = _default_logger ) -> Optional[Envelope]: """ Decode bytes to get the envelope. :param bytes_: the encoded envelope :param separator: the separator used :param logger: the logger :return: Envelope """ logger.debug("processing: {!r}".format(bytes_)) envelope = None # type: Optional[Envelope] try: envelope = _decode(bytes_, separator=separator) except ValueError as e: logger.error("Bad formatted input: {!r}. {}".format(bytes_, e)) except Exception as e: # pragma: nocover # pylint: disable=broad-except logger.exception("Error when processing a input. Message: {}".format(str(e))) return envelope ================================================ FILE: aea/helpers/file_lock.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Patch of 'fnctl' to make it compatible with Windows.""" import os from typing import IO # needs win32all to work on Windows if os.name == "nt": # pragma: nocover # cause platform dependent! import pywintypes # pylint: disable=import-error import win32con # pylint: disable=import-error import win32file # pylint: disable=import-error LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK LOCK_SH = 0 # the default LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY __overlapped = pywintypes.OVERLAPPED() def lock(file: IO, flags: int) -> None: """Lock a file with flags.""" hfile = win32file._get_osfhandle( # pylint: disable=protected-access file.fileno() ) win32file.LockFileEx(hfile, flags, 0, 0xFFFF0000, __overlapped) def unlock(file: IO) -> None: """Unlock a file.""" hfile = win32file._get_osfhandle( # pylint: disable=protected-access file.fileno() ) win32file.UnlockFileEx(hfile, 0, 0xFFFF0000, __overlapped) elif os.name == "posix": # pragma: nocover # cause platform dependent! import fcntl from fcntl import LOCK_EX, LOCK_NB, LOCK_SH # noqa # pylint: disable=unused-import def lock(file: IO, flags: int) -> None: """Lock a file with flags.""" fcntl.flock(file.fileno(), flags) def unlock(file: IO) -> None: """Unlock a file.""" fcntl.flock(file.fileno(), fcntl.LOCK_UN) else: # pragma: nocover raise RuntimeError("This module only works for nt and posix platforms") ================================================ FILE: aea/helpers/http_requests.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Wrapper over requests library.""" from functools import wraps from typing import Any, Callable import requests from aea.helpers.constants import NETWORK_REQUEST_DEFAULT_TIMEOUT DEFAULT_TIMEOUT = NETWORK_REQUEST_DEFAULT_TIMEOUT # requests can use one of these try: from simplejson.errors import ( # type: ignore # pylint: disable=unused-import JSONDecodeError, ) except ModuleNotFoundError: # pragma: nocover from json.decoder import ( # type: ignore # noqa # pylint: disable=unused-import JSONDecodeError, ) def add_default_timeout(fn: Callable, timeout: float) -> Callable: """Add default timeout for requests methods.""" @wraps(fn) def wrapper(*args: Any, **kwargs: Any) -> Callable: # pragma: nocover kwargs["timeout"] = kwargs.get("timeout", timeout) return fn(*args, **kwargs) return wrapper get = add_default_timeout(requests.get, DEFAULT_TIMEOUT) post = add_default_timeout(requests.post, DEFAULT_TIMEOUT) request = add_default_timeout(requests.request, DEFAULT_TIMEOUT) head = add_default_timeout(requests.head, DEFAULT_TIMEOUT) exceptions = requests.exceptions Response = requests.Response ConnectionError = requests.ConnectionError # pylint: disable=redefined-builtin ================================================ FILE: aea/helpers/install_dependency.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Helper to install python dependencies.""" import subprocess # nosec import sys from itertools import chain from logging import Logger from subprocess import PIPE # nosec from typing import List from aea.configurations.base import Dependency from aea.exceptions import AEAException, enforce def install_dependency( dependency_name: str, dependency: Dependency, logger: Logger, install_timeout: float = 300, ) -> None: """ Install python dependency to the current python environment. :param dependency_name: name of the python package :param dependency: Dependency specification :param logger: the logger :param install_timeout: timeout to wait pip to install """ try: pip_args = dependency.get_pip_install_args() logger.debug("Calling 'pip install {}'".format(" ".join(pip_args))) call_pip(["install", *pip_args], timeout=install_timeout, retry=True) except Exception as e: raise AEAException( f"An error occurred while installing {dependency_name}, {dependency}: {e}" ) def install_dependencies( dependencies: List[Dependency], logger: Logger, install_timeout: float = 300, ) -> None: """ Install python dependencies to the current python environment. :param dependencies: dict of dependency name and specification :param logger: the logger :param install_timeout: timeout to wait pip to install """ try: pip_args = list(chain(*[d.get_pip_install_args() for d in dependencies])) pip_args = [("--extra-index" if i == "-i" else i) for i in pip_args] logger.debug("Calling 'pip install {}'".format(" ".join(pip_args))) call_pip(["install", *pip_args], timeout=install_timeout, retry=True) except Exception as e: raise AEAException( f"An error occurred while installing with pip install {' '.join(pip_args)}: {e}" ) def call_pip(pip_args: List[str], timeout: float = 300, retry: bool = False) -> None: """ Run pip install command. :param pip_args: list strings of the command :param timeout: timeout to wait pip to install :param retry: bool, try one more time if command failed """ command = [sys.executable, "-m", "pip", *pip_args] result = subprocess.run( # nosec command, stdout=PIPE, stderr=PIPE, timeout=timeout, check=False ) if result.returncode == 1 and retry: # try a second time result = subprocess.run( # nosec command, stdout=PIPE, stderr=PIPE, timeout=timeout, check=False ) enforce( result.returncode == 0, f"pip install failed. Return code != 0: stderr is {str(result.stderr)}", ) def run_install_subprocess( install_command: List[str], install_timeout: float = 300 ) -> int: # pragma: nocover """ Try executing install command. :param install_command: list strings of the command :param install_timeout: timeout to wait pip to install :return: the return code of the subprocess """ with subprocess.Popen(install_command) as subp: # nosec subp.wait(install_timeout) return subp.returncode ================================================ FILE: aea/helpers/io.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ r""" Wrapper over built-in "open" function. This module contains a wrapper to the built-in 'open' function, the 'open_file' function, that fixes the keyword argument 'newline' to be equal to "\n" (the UNIX line separator). This will force the line separator to be "\n" both for incoming and outgoing data. The reason of this is that files written in an AEA package need to have "\n" as line separator, on all platforms. Otherwise, the fingerprint of the packages involved would change across platforms just because the line separators are replaced. For instance, the 'open' function on Windows, by default (newline=None), would replace the line separators "\n" with "\r\n". This has an impact in the computation of the fingerprint. Hence, any usage of file system functionalities should either use 'open_file', or set 'newline="\n"' when calling the 'open' or the 'pathlib.Path.open' functions. """ from functools import partial from pathlib import Path from typing import Callable, Optional, TextIO, Union UNIX_LINESEP = "\n" _open_file_builtin: Callable = partial(open, newline=UNIX_LINESEP) _open_file_pathlib: Callable = partial(Path.open, newline=UNIX_LINESEP) PathNameTypes = Union[int, str, bytes, Path] def open_file( file: PathNameTypes, mode: str = "r", buffering: int = -1, encoding: Optional[str] = None, errors: Optional[str] = None, ) -> TextIO: r""" Open a file. Behaviour, kwargs and return type are the same for built-in 'open' and pathlib.Path.open, except for 'newline', which is fixed to '\n'. For more details on the keyword arguments, please refer to the documentation for the built-in 'open': https://docs.python.org/3/library/functions.html#open :param file: either a pathlib.Path object or the type accepted by 'open', i.e. a string, bytes or integer. :param mode: the mode in which the file is opened. :param buffering: the buffering policy. :param encoding: the name of the encoding used to decode or encode the file. :param errors: how encoding errors are to be handled :return: the IO object. """ if "b" in mode: raise ValueError("This function can only work in text mode.") actual_wrapped_function = _open_file_builtin if isinstance(file, Path): actual_wrapped_function = _open_file_pathlib return actual_wrapped_function( file, mode=mode, buffering=buffering, encoding=encoding, errors=errors ) ================================================ FILE: aea/helpers/ipfs/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains helper methods and classes for the 'aea' package.""" from aea.helpers.ipfs.utils import _protobuf_python_implementation # fix for ipfs hashes, preload protobuf classes with protobuf python implementation with _protobuf_python_implementation(): from aea.helpers.ipfs.pb import ( # noqa: F401 # pylint: disable=import-outside-toplevel,unused-import merkledag_pb2, unixfs_pb2, ) from aea.helpers.ipfs.pb.merkledag_pb2 import ( # noqa: F401 # pylint: disable=import-outside-toplevel,unused-import PBNode, ) ================================================ FILE: aea/helpers/ipfs/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains helper methods and classes for the 'aea' package.""" import codecs import hashlib import io import re from typing import Generator, Sized, cast import base58 from aea.helpers.io import open_file from aea.helpers.ipfs.utils import _protobuf_python_implementation # https://github.com/multiformats/multicodec/blob/master/table.csv SHA256_ID = "12" # 0x12 LEN_SHA256 = "20" # 0x20 with _protobuf_python_implementation(): # pylint: disable=import-outside-toplevel from aea.helpers.ipfs.pb import merkledag_pb2, unixfs_pb2 from aea.helpers.ipfs.pb.merkledag_pb2 import PBNode def _dos2unix(file_content: bytes) -> bytes: """ Replace occurrences of Windows line terminator CR/LF with only LF. :param file_content: the content of the file. :return: the same content but with the line terminator """ return re.sub(b"\r\n", b"\n", file_content, flags=re.M) def _is_text(file_path: str) -> bool: """Check if a file can be read as text or not.""" try: with open_file(file_path, "r") as f: f.read() return True except UnicodeDecodeError: return False def _read(file_path: str) -> bytes: """Read a file, replacing Windows line endings if it is a text file.""" is_text = _is_text(file_path) with open(file_path, "rb") as file: file_b = file.read() if is_text: file_b = _dos2unix(file_b) return file_b def chunks(data: Sized, size: int) -> Generator: """Yield successivesize chunks from data.""" for i in range(0, len(data), size): yield data[i : i + size] # type: ignore class IPFSHashOnly: """A helper class which allows construction of an IPFS hash without interacting with an IPFS daemon.""" DEFAULT_CHUNK_SIZE = 262144 # according to https://pkg.go.dev/github.com/ipfs/go-ipfs-chunker#pkg-constants def get(self, file_path: str) -> str: """ Get the IPFS hash for a single file. :param file_path: the file path :return: the ipfs hash """ file_b = _read(file_path) file_pb = self._pb_serialize_file(file_b) ipfs_hash = self._generate_multihash(file_pb) return ipfs_hash @classmethod def _make_unixfs_pb2(cls, data: bytes) -> bytes: if len(data) > cls.DEFAULT_CHUNK_SIZE: # pragma: nocover raise ValueError("Data is too big! use chunks!") data_pb = unixfs_pb2.Data() # type: ignore data_pb.Type = unixfs_pb2.Data.File # type: ignore # pylint: disable=no-member data_pb.Data = data data_pb.filesize = len(data) serialized_data = data_pb.SerializeToString(deterministic=True) return serialized_data @classmethod def _pb_serialize_data(cls, data: bytes) -> bytes: outer_node = PBNode() # type: ignore outer_node.Data = cls._make_unixfs_pb2(data) result = cls._serialize(outer_node) return result @classmethod def _pb_serialize_file(cls, data: bytes) -> bytes: """ Serialize a bytes object representing a file. :param data: a bytes string representing a file :return: a bytes string representing a file in protobuf serialization """ if len(data) > cls.DEFAULT_CHUNK_SIZE: outer_node = PBNode() # type: ignore data_pb = unixfs_pb2.Data() # type: ignore data_pb.Type = unixfs_pb2.Data.File # type: ignore # pylint: disable=no-member data_pb.filesize = len(data) for chunk in chunks(data, cls.DEFAULT_CHUNK_SIZE): link = merkledag_pb2.PBLink() block = cls._pb_serialize_data(chunk) link.Hash = cls._generate_multihash_bytes(block) link.Tsize = len(block) link.Name = "" outer_node.Links.append(link) # type: ignore # pylint: disable=no-member data_pb.blocksizes.append(len(chunk)) # type: ignore # pylint: disable=no-member outer_node.Data = data_pb.SerializeToString(deterministic=True) return cls._serialize(outer_node) return cls._pb_serialize_data(data) @staticmethod def _generate_multihash_bytes(pb_data: bytes) -> bytes: sha256_hash = hashlib.sha256(pb_data).hexdigest() multihash_hex = SHA256_ID + LEN_SHA256 + sha256_hash multihash_bytes = codecs.decode(str.encode(multihash_hex), "hex") return cast(bytes, multihash_bytes) @classmethod def _generate_multihash(cls, pb_data: bytes) -> str: """ Generate an IPFS multihash. Uses the default IPFS hashing function: sha256 :param pb_data: the data to be hashed :return: string representing the hash """ multihash_bytes = cls._generate_multihash_bytes(pb_data) ipfs_hash = base58.b58encode(multihash_bytes) return str(ipfs_hash, "utf-8") @classmethod def _generate_hash(cls, data: bytes) -> str: """Generate hash for data.""" pb_data = cls._pb_serialize_file(data) return cls._generate_multihash(pb_data) @classmethod def _serialize(cls, pb_node: PBNode) -> bytes: # type: ignore """Serialize PBNode instance with fixed fields sequence.""" f = io.BytesIO() for field_descriptor, field_value in reversed(pb_node.ListFields()): # type: ignore field_descriptor._encoder( # pylint: disable=protected-access f.write, field_value, True ) return f.getvalue() ================================================ FILE: aea/helpers/ipfs/pb/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains helper methods and classes for the 'aea' package.""" ================================================ FILE: aea/helpers/ipfs/pb/merkledag.proto ================================================ // source: https://github.com/ipfs/go-merkledag/blob/master/pb/merkledag.proto syntax = "proto2"; package merkledag.pb; // An IPFS MerkleDAG Link message PBLink { // multihash of the target object optional bytes Hash = 1; // utf string name. should be unique per object optional string Name = 2; // cumulative size of target object optional uint64 Tsize = 3; } // An IPFS MerkleDAG Node message PBNode { // refs to other objects repeated PBLink Links = 2; // opaque user data optional bytes Data = 1; } ================================================ FILE: aea/helpers/ipfs/pb/merkledag_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: merkledag.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( name="merkledag.proto", package="merkledag.pb", syntax="proto2", serialized_options=None, create_key=_descriptor._internal_create_key, serialized_pb=b'\n\x0fmerkledag.proto\x12\x0cmerkledag.pb"3\n\x06PBLink\x12\x0c\n\x04Hash\x18\x01 \x01(\x0c\x12\x0c\n\x04Name\x18\x02 \x01(\t\x12\r\n\x05Tsize\x18\x03 \x01(\x04";\n\x06PBNode\x12#\n\x05Links\x18\x02 \x03(\x0b\x32\x14.merkledag.pb.PBLink\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c', ) _PBLINK = _descriptor.Descriptor( name="PBLink", full_name="merkledag.pb.PBLink", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="Hash", full_name="merkledag.pb.PBLink.Hash", index=0, number=1, type=12, cpp_type=9, label=1, has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="Name", full_name="merkledag.pb.PBLink.Name", index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="Tsize", full_name="merkledag.pb.PBLink.Tsize", index=2, number=3, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto2", extension_ranges=[], oneofs=[], serialized_start=33, serialized_end=84, ) _PBNODE = _descriptor.Descriptor( name="PBNode", full_name="merkledag.pb.PBNode", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="Links", full_name="merkledag.pb.PBNode.Links", index=0, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="Data", full_name="merkledag.pb.PBNode.Data", index=1, number=1, type=12, cpp_type=9, label=1, has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto2", extension_ranges=[], oneofs=[], serialized_start=86, serialized_end=145, ) _PBNODE.fields_by_name["Links"].message_type = _PBLINK DESCRIPTOR.message_types_by_name["PBLink"] = _PBLINK DESCRIPTOR.message_types_by_name["PBNode"] = _PBNODE _sym_db.RegisterFileDescriptor(DESCRIPTOR) PBLink = _reflection.GeneratedProtocolMessageType( "PBLink", (_message.Message,), { "DESCRIPTOR": _PBLINK, "__module__": "merkledag_pb2" # @@protoc_insertion_point(class_scope:merkledag.pb.PBLink) }, ) _sym_db.RegisterMessage(PBLink) PBNode = _reflection.GeneratedProtocolMessageType( "PBNode", (_message.Message,), { "DESCRIPTOR": _PBNODE, "__module__": "merkledag_pb2" # @@protoc_insertion_point(class_scope:merkledag.pb.PBNode) }, ) _sym_db.RegisterMessage(PBNode) # @@protoc_insertion_point(module_scope) ================================================ FILE: aea/helpers/ipfs/pb/unixfs.proto ================================================ // source: https://github.com/ipfs/go-unixfs/blob/master/pb/unixfs.proto syntax = "proto2"; package unixfs.pb; message Data { enum DataType { Raw = 0; Directory = 1; File = 2; Metadata = 3; Symlink = 4; HAMTShard = 5; } required DataType Type = 1; optional bytes Data = 2; optional uint64 filesize = 3; repeated uint64 blocksizes = 4; optional uint64 hashType = 5; optional uint64 fanout = 6; } message Metadata { optional string MimeType = 1; } ================================================ FILE: aea/helpers/ipfs/pb/unixfs_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: unixfs.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( name="unixfs.proto", package="unixfs.pb", syntax="proto2", serialized_options=None, create_key=_descriptor._internal_create_key, serialized_pb=b'\n\x0cunixfs.proto\x12\tunixfs.pb"\xdc\x01\n\x04\x44\x61ta\x12&\n\x04Type\x18\x01 \x02(\x0e\x32\x18.unixfs.pb.Data.DataType\x12\x0c\n\x04\x44\x61ta\x18\x02 \x01(\x0c\x12\x10\n\x08\x66ilesize\x18\x03 \x01(\x04\x12\x12\n\nblocksizes\x18\x04 \x03(\x04\x12\x10\n\x08hashType\x18\x05 \x01(\x04\x12\x0e\n\x06\x66\x61nout\x18\x06 \x01(\x04"V\n\x08\x44\x61taType\x12\x07\n\x03Raw\x10\x00\x12\r\n\tDirectory\x10\x01\x12\x08\n\x04\x46ile\x10\x02\x12\x0c\n\x08Metadata\x10\x03\x12\x0b\n\x07Symlink\x10\x04\x12\r\n\tHAMTShard\x10\x05"\x1c\n\x08Metadata\x12\x10\n\x08MimeType\x18\x01 \x01(\t', ) _DATA_DATATYPE = _descriptor.EnumDescriptor( name="DataType", full_name="unixfs.pb.Data.DataType", filename=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name="Raw", index=0, number=0, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="Directory", index=1, number=1, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="File", index=2, number=2, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="Metadata", index=3, number=3, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="Symlink", index=4, number=4, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="HAMTShard", index=5, number=5, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), ], containing_type=None, serialized_options=None, serialized_start=162, serialized_end=248, ) _sym_db.RegisterEnumDescriptor(_DATA_DATATYPE) _DATA = _descriptor.Descriptor( name="Data", full_name="unixfs.pb.Data", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="Type", full_name="unixfs.pb.Data.Type", index=0, number=1, type=14, cpp_type=8, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="Data", full_name="unixfs.pb.Data.Data", index=1, number=2, type=12, cpp_type=9, label=1, has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="filesize", full_name="unixfs.pb.Data.filesize", index=2, number=3, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="blocksizes", full_name="unixfs.pb.Data.blocksizes", index=3, number=4, type=4, cpp_type=4, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="hashType", full_name="unixfs.pb.Data.hashType", index=4, number=5, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="fanout", full_name="unixfs.pb.Data.fanout", index=5, number=6, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[ _DATA_DATATYPE, ], serialized_options=None, is_extendable=False, syntax="proto2", extension_ranges=[], oneofs=[], serialized_start=28, serialized_end=248, ) _METADATA = _descriptor.Descriptor( name="Metadata", full_name="unixfs.pb.Metadata", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="MimeType", full_name="unixfs.pb.Metadata.MimeType", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto2", extension_ranges=[], oneofs=[], serialized_start=250, serialized_end=278, ) _DATA.fields_by_name["Type"].enum_type = _DATA_DATATYPE _DATA_DATATYPE.containing_type = _DATA DESCRIPTOR.message_types_by_name["Data"] = _DATA DESCRIPTOR.message_types_by_name["Metadata"] = _METADATA _sym_db.RegisterFileDescriptor(DESCRIPTOR) Data = _reflection.GeneratedProtocolMessageType( "Data", (_message.Message,), { "DESCRIPTOR": _DATA, "__module__": "unixfs_pb2" # @@protoc_insertion_point(class_scope:unixfs.pb.Data) }, ) _sym_db.RegisterMessage(Data) Metadata = _reflection.GeneratedProtocolMessageType( "Metadata", (_message.Message,), { "DESCRIPTOR": _METADATA, "__module__": "unixfs_pb2" # @@protoc_insertion_point(class_scope:unixfs.pb.Metadata) }, ) _sym_db.RegisterMessage(Metadata) # @@protoc_insertion_point(module_scope) ================================================ FILE: aea/helpers/ipfs/utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains utility methods for ipfs helpers.""" import contextlib import os import sys from typing import Generator @contextlib.contextmanager def _protobuf_python_implementation() -> Generator: """ Makes a context manager to force usage of the python implementation of the protobuf modules. By default cpp version of the protobuf library is loaded. This library does not provide all the needed tools to customize fields serialization order. Python verrsion allows to use internal methods of the protobuf objects to serialize. Custom serializations is required cause ipfs uses own version to serialize data to calculate data hash. # noqa: DAR301 """ PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION = "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION" # unload modules saved_mods = {} for mod_name, mod in list(sys.modules.items()): if mod_name.startswith("google.protobuf"): saved_mods[mod_name] = mod del sys.modules[mod_name] prev_os_env = os.environ.get(PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION) os.environ[PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION] = "python" yield if prev_os_env is None: # pragma: nocover del os.environ[PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION] else: # pragma: nocover os.environ[PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION] = prev_os_env for mod_name in list(sys.modules.keys()): if mod_name.startswith("google.protobuf"): del sys.modules[mod_name] for mod_name, mod in saved_mods.items(): sys.modules[mod_name] = mod ================================================ FILE: aea/helpers/logging.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Logging helpers.""" import logging from logging import Logger, LoggerAdapter from typing import Any, MutableMapping, Optional, Tuple, cast from aea.helpers.base import _get_aea_logger_name_prefix def get_logger(module_path: str, agent_name: str) -> Logger: """Get the logger based on a module path and agent name.""" logger = logging.getLogger(_get_aea_logger_name_prefix(module_path, agent_name)) return logger class AgentLoggerAdapter(LoggerAdapter): """This class is a logger adapter that prepends the agent name to log messages.""" def __init__(self, logger: Logger, agent_name: str) -> None: """ Initialize the logger adapter. :param logger: the logger. :param agent_name: the agent name. """ super().__init__(logger, dict(agent_name=agent_name)) def process( self, msg: Any, kwargs: MutableMapping[str, Any] ) -> Tuple[Any, MutableMapping[str, Any]]: """Prepend the agent name to every log message.""" return f"[{self.extra['agent_name']}] {msg}", kwargs class WithLogger: """Interface to endow subclasses with a logger.""" __slots__ = ("_logger", "_default_logger_name") def __init__( self, logger: Optional[Logger] = None, default_logger_name: str = "aea", ) -> None: """ Initialize the logger. :param logger: the logger object. :param default_logger_name: the default logger name, if a logger is not provided. """ self._logger: Optional[Logger] = logger self._default_logger_name = default_logger_name @property def logger(self) -> Logger: """Get the component logger.""" if self._logger is None: # if not set (e.g. programmatic instantiation) # return a default one with the default logger name. return logging.getLogger(self._default_logger_name) return cast(Logger, self._logger) @logger.setter def logger(self, logger: Optional[Logger]) -> None: """Set the logger.""" self._logger = logger ================================================ FILE: aea/helpers/multiaddr/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains multiaddress class.""" ================================================ FILE: aea/helpers/multiaddr/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains multiaddress class.""" from binascii import unhexlify from typing import Optional import base58 import multihash # type: ignore from ecdsa import VerifyingKey, curves, keys from aea.helpers.multiaddr.crypto_pb2 import KeyType, PublicKey # NOTE: # - Reference: https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#keys # - Implementation inspired from https://github.com/libp2p/py-libp2p # - On inlining see: https://github.com/libp2p/specs/issues/138 # - Enabling inlining to be interoperable w/ the Go implementation ENABLE_INLINING = True MAX_INLINE_KEY_LENGTH = 42 IDENTITY_MULTIHASH_CODE = 0x00 KEY_SIZE = 32 ZERO = b"\x00" if ENABLE_INLINING: class IdentityHash: """Neutral hashing implementation for inline multihashing.""" _digest: bytes def __init__(self) -> None: """Initialize IdentityHash object.""" self._digest = bytearray() def update(self, input_data: bytes) -> None: """ Update data to hash. :param input_data: the data """ self._digest += input_data def digest(self) -> bytes: """ Get hash of input data. :return: the hash """ return self._digest multihash.FuncReg.register( IDENTITY_MULTIHASH_CODE, "identity", hash_new=IdentityHash ) def _pad_scalar(scalar: bytes) -> bytes: """Pad scalar.""" return (ZERO * (KEY_SIZE - len(scalar))) + scalar def _pad_hex(hexed: str) -> str: """Pad odd-length hex strings.""" return hexed if not len(hexed) & 1 else "0" + hexed def _hex_to_bytes(hexed: str) -> bytes: """Hex to bytes.""" return _pad_scalar(unhexlify(_pad_hex(hexed))) class MultiAddr: """Protocol Labs' Multiaddress representation of a network address.""" def __init__( self, host: str, port: int, public_key: Optional[str] = None, multihash_id: Optional[str] = None, ) -> None: """ Initialize a multiaddress. :param host: ip host of the address :param port: port number of the address :param public_key: hex encoded public key. Must conform to Bitcoin EC encoding standard for Secp256k1 :param multihash_id: a multihash of the public key """ self._host = host self._port = port if public_key is not None: try: VerifyingKey._from_compressed( _hex_to_bytes(public_key), curves.SECP256k1 ) except keys.MalformedPointError as e: # pragma: no cover raise ValueError( "Malformed public key '{}': {}".format(public_key, str(e)) ) self._public_key = public_key self._peerid = self.compute_peerid(self._public_key) elif multihash_id is not None: try: multihash.decode(base58.b58decode(multihash_id)) except Exception as e: # pylint: disable=broad-except raise ValueError( "Malformed multihash '{}': {}".format(multihash_id, str(e)) ) self._public_key = "" self._peerid = multihash_id else: raise ValueError( # pragma: no cover "MultiAddr requires either public_key or multihash_id to be provided." ) @staticmethod def compute_peerid(public_key: str) -> str: """ Compute the peer id from a public key. In particular, compute the base58 representation of libp2p PeerID from Bitcoin EC encoded Secp256k1 public key. :param public_key: the public key. :return: the peer id. """ key_protobuf = PublicKey( key_type=KeyType.Secp256k1, data=_hex_to_bytes(public_key) # type: ignore ) key_serialized = key_protobuf.SerializeToString() algo = multihash.Func.sha2_256 if ENABLE_INLINING and len(key_serialized) <= MAX_INLINE_KEY_LENGTH: algo = IDENTITY_MULTIHASH_CODE key_mh = multihash.digest(key_serialized, algo) return base58.b58encode(key_mh.encode()).decode() @classmethod def from_string(cls, maddr: str) -> "MultiAddr": """ Construct a MultiAddr object from its string format :param maddr: multiaddress string :return: multiaddress object """ parts = maddr.split("/") if len(parts) != 7 or not parts[4].isdigit(): raise ValueError("Malformed multiaddress '{}'".format(maddr)) return cls(host=parts[2], port=int(parts[4]), multihash_id=parts[6]) @property def public_key(self) -> str: """Get the public key.""" return self._public_key @property def peer_id(self) -> str: """Get the peer id.""" return self._peerid @property def host(self) -> str: """Get the peer host.""" return self._host @property def port(self) -> int: """Get the peer port.""" return self._port def format(self) -> str: """Canonical representation of a multiaddress.""" return f"/dns4/{self._host}/tcp/{self._port}/p2p/{self._peerid}" def __str__(self) -> str: """Default string representation of a multiaddress.""" return self.format() ================================================ FILE: aea/helpers/multiaddr/crypto.proto ================================================ syntax = "proto2"; package crypto.pb; enum KeyType { RSA = 0; Ed25519 = 1; Secp256k1 = 2; ECDSA = 3; } message PublicKey { required KeyType key_type = 1; required bytes data = 2; } message PrivateKey { required KeyType key_type = 1; required bytes data = 2; } ================================================ FILE: aea/helpers/multiaddr/crypto_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: crypto.proto """Generated protocol buffer code.""" from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( name="crypto.proto", package="crypto.pb", syntax="proto2", serialized_options=None, create_key=_descriptor._internal_create_key, serialized_pb=b'\n\x0c\x63rypto.proto\x12\tcrypto.pb"?\n\tPublicKey\x12$\n\x08key_type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c"@\n\nPrivateKey\x12$\n\x08key_type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c*9\n\x07KeyType\x12\x07\n\x03RSA\x10\x00\x12\x0b\n\x07\x45\x64\x32\x35\x35\x31\x39\x10\x01\x12\r\n\tSecp256k1\x10\x02\x12\t\n\x05\x45\x43\x44SA\x10\x03', ) _KEYTYPE = _descriptor.EnumDescriptor( name="KeyType", full_name="crypto.pb.KeyType", filename=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name="RSA", index=0, number=0, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="Ed25519", index=1, number=1, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="Secp256k1", index=2, number=2, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="ECDSA", index=3, number=3, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), ], containing_type=None, serialized_options=None, serialized_start=158, serialized_end=215, ) _sym_db.RegisterEnumDescriptor(_KEYTYPE) KeyType = enum_type_wrapper.EnumTypeWrapper(_KEYTYPE) RSA = 0 Ed25519 = 1 Secp256k1 = 2 ECDSA = 3 _PUBLICKEY = _descriptor.Descriptor( name="PublicKey", full_name="crypto.pb.PublicKey", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="key_type", full_name="crypto.pb.PublicKey.key_type", index=0, number=1, type=14, cpp_type=8, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="data", full_name="crypto.pb.PublicKey.data", index=1, number=2, type=12, cpp_type=9, label=2, has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto2", extension_ranges=[], oneofs=[], serialized_start=27, serialized_end=90, ) _PRIVATEKEY = _descriptor.Descriptor( name="PrivateKey", full_name="crypto.pb.PrivateKey", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="key_type", full_name="crypto.pb.PrivateKey.key_type", index=0, number=1, type=14, cpp_type=8, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="data", full_name="crypto.pb.PrivateKey.data", index=1, number=2, type=12, cpp_type=9, label=2, has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto2", extension_ranges=[], oneofs=[], serialized_start=92, serialized_end=156, ) _PUBLICKEY.fields_by_name["key_type"].enum_type = _KEYTYPE _PRIVATEKEY.fields_by_name["key_type"].enum_type = _KEYTYPE DESCRIPTOR.message_types_by_name["PublicKey"] = _PUBLICKEY DESCRIPTOR.message_types_by_name["PrivateKey"] = _PRIVATEKEY DESCRIPTOR.enum_types_by_name["KeyType"] = _KEYTYPE _sym_db.RegisterFileDescriptor(DESCRIPTOR) PublicKey = _reflection.GeneratedProtocolMessageType( "PublicKey", (_message.Message,), { "DESCRIPTOR": _PUBLICKEY, "__module__": "crypto_pb2" # @@protoc_insertion_point(class_scope:crypto.pb.PublicKey) }, ) _sym_db.RegisterMessage(PublicKey) PrivateKey = _reflection.GeneratedProtocolMessageType( "PrivateKey", (_message.Message,), { "DESCRIPTOR": _PRIVATEKEY, "__module__": "crypto_pb2" # @@protoc_insertion_point(class_scope:crypto.pb.PrivateKey) }, ) _sym_db.RegisterMessage(PrivateKey) # @@protoc_insertion_point(module_scope) ================================================ FILE: aea/helpers/multiple_executor.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the helpers to run multiple stoppable tasks in different modes: async, threaded, multiprocess .""" import asyncio import logging from abc import ABC, abstractmethod from asyncio.events import AbstractEventLoop from asyncio.tasks import FIRST_EXCEPTION, Task from concurrent.futures._base import Executor, Future from concurrent.futures.process import ProcessPoolExecutor from concurrent.futures.thread import ThreadPoolExecutor from enum import Enum from threading import Thread from typing import ( Any, Callable, Dict, Optional, Sequence, Set, Tuple, Type, Union, cast, ) _default_logger = logging.getLogger(__name__) TaskAwaitable = Union[Task, Future] class ExecutorExceptionPolicies(Enum): """Runner exception policy modes.""" stop_all = "stop_all" # stop all agents on one agent's failure, log exception propagate = "propagate" # log exception and reraise it to upper level log_only = "log_only" # log exception and skip it class AbstractExecutorTask(ABC): """Abstract task class to create Task classes.""" def __init__(self) -> None: """Init task.""" self._future: Optional[TaskAwaitable] = None @property def future(self) -> Optional[TaskAwaitable]: """Return awaitable to get result of task execution.""" return self._future @future.setter def future(self, future: TaskAwaitable) -> None: """Set awaitable to get result of task execution.""" self._future = future @abstractmethod def start(self) -> Tuple[Callable, Sequence[Any]]: """Implement start task function here.""" @abstractmethod def stop(self) -> None: """Implement stop task function here.""" @abstractmethod def create_async_task(self, loop: AbstractEventLoop) -> TaskAwaitable: """ Create asyncio task for task run in asyncio loop. :param loop: the event loop :return: task to run in asyncio loop. """ @property def id(self) -> Any: # pragma: nocover """Return task id.""" return id(self) @property def failed(self) -> bool: """ Return was exception failed or not. If it's running it's not failed. :return: bool """ if not self._future: return False if not self._future.done(): return False if not self._future.exception(): return False if isinstance(self._future.exception(), KeyboardInterrupt): return False return True class AbstractMultiprocessExecutorTask(AbstractExecutorTask): """Task for multiprocess executor.""" @abstractmethod def start(self) -> Tuple[Callable, Sequence[Any]]: """Return function and arguments to call within subprocess.""" def create_async_task( self, loop: AbstractEventLoop ) -> TaskAwaitable: # pragma: nocover """ Create asyncio task for task run in asyncio loop. Raise error, cause async mode is not supported, cause this task for multiprocess executor only. :param loop: the event loop :raises ValueError: async task construction not possible """ raise ValueError( "This task was designed only for multiprocess executor, not for async!" ) class AbstractMultipleExecutor(ABC): # pragma: nocover """Abstract class to create multiple executors classes.""" def __init__( self, tasks: Sequence[AbstractExecutorTask], task_fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies.propagate, ) -> None: """ Init executor. :param tasks: sequence of AbstractExecutorTask instances to run. :param task_fail_policy: the exception policy of all the tasks """ self._task_fail_policy: ExecutorExceptionPolicies = task_fail_policy self._tasks: Sequence[AbstractExecutorTask] = tasks self._is_running: bool = False self._future_task: Dict[TaskAwaitable, AbstractExecutorTask] = {} self._loop: AbstractEventLoop = asyncio.new_event_loop() self._executor_pool: Optional[Executor] = None self._set_executor_pool() @property def is_running(self) -> bool: """Return running state of the executor.""" return self._is_running def start(self) -> None: """Start tasks.""" self._start_tasks() self._loop.run_until_complete(self._wait_tasks_complete()) self._is_running = False def stop(self) -> None: """Stop tasks.""" self._is_running = False for task in self._tasks: self._stop_task(task) if not self._loop.is_running(): self._loop.run_until_complete( self._wait_tasks_complete(skip_exceptions=True, on_stop=True) ) if self._executor_pool: self._executor_pool.shutdown(wait=True) def _start_tasks(self) -> None: """Schedule tasks.""" for task in self._tasks: future = self._start_task(task) task.future = future self._future_task[future] = task async def _wait_tasks_complete( self, skip_exceptions: bool = False, on_stop: bool = False ) -> None: """ Wait tasks execution to complete. :param skip_exceptions: skip exceptions if raised in tasks :param on_stop: bool, indicating if stopping """ if not on_stop: self._is_running = True pending = cast(Set[asyncio.futures.Future], set(self._future_task.keys())) async def wait_future(future: asyncio.futures.Future) -> None: try: await future except KeyboardInterrupt: # pragma: nocover _default_logger.exception("KeyboardInterrupt in task!") if not skip_exceptions: raise except Exception as e: # pylint: disable=broad-except # handle any exception with own code. _default_logger.exception("Exception in task!") if not skip_exceptions: await self._handle_exception( self._future_task[cast(TaskAwaitable, future)], e ) while pending: done, pending = await asyncio.wait(pending, return_when=FIRST_EXCEPTION) for future in done: await wait_future(future) async def _handle_exception( self, task: AbstractExecutorTask, exc: Exception ) -> None: """ Handle exception raised during task execution. Log exception and process according to selected policy. :param task: task exception handled in :param exc: Exception raised """ _default_logger.exception(f"Exception raised during {task.id} running.") _default_logger.info(f"Exception raised during {task.id} running.") if self._task_fail_policy == ExecutorExceptionPolicies.propagate: raise exc if self._task_fail_policy == ExecutorExceptionPolicies.log_only: pass elif self._task_fail_policy == ExecutorExceptionPolicies.stop_all: _default_logger.info( "Stopping executor according to fail policy cause exception raised in task" ) self.stop() await self._wait_tasks_complete(skip_exceptions=True, on_stop=True) else: # pragma: nocover raise ValueError(f"Unknown fail policy: {self._task_fail_policy}") @abstractmethod def _start_task(self, task: AbstractExecutorTask) -> TaskAwaitable: """ Start particular task. :param task: AbstractExecutorTask instance to start. :return: awaitable object(future) to get result or exception """ @abstractmethod def _set_executor_pool(self) -> None: """Set executor pool to be used.""" @staticmethod def _stop_task(task: AbstractExecutorTask) -> None: """ Stop particular task. :param task: AbstractExecutorTask instance to stop. """ task.stop() @property def num_failed(self) -> int: # pragma: nocover """Return number of failed tasks.""" return len(self.failed_tasks) @property def failed_tasks(self) -> Sequence[AbstractExecutorTask]: # pragma: nocover """Return sequence failed tasks.""" return [task for task in self._tasks if task.failed] @property def not_failed_tasks(self) -> Sequence[AbstractExecutorTask]: # pragma: nocover """Return sequence successful tasks.""" return [task for task in self._tasks if not task.failed] class ThreadExecutor(AbstractMultipleExecutor): # pragma: nocover """Thread based executor to run multiple agents in threads.""" def _set_executor_pool(self) -> None: """Set thread pool pool to be used.""" self._executor_pool = ThreadPoolExecutor(max_workers=len(self._tasks)) def _start_task(self, task: AbstractExecutorTask) -> TaskAwaitable: """ Start particular task. :param task: AbstractExecutorTask instance to start. :return: awaitable object(future) to get result or exception """ return cast( TaskAwaitable, self._loop.run_in_executor(self._executor_pool, task.start) ) class ProcessExecutor(ThreadExecutor): # pragma: nocover """Subprocess based executor to run multiple agents in threads.""" def _set_executor_pool(self) -> None: """Set thread pool pool to be used.""" self._executor_pool = ProcessPoolExecutor(max_workers=len(self._tasks)) def _start_task(self, task: AbstractExecutorTask) -> TaskAwaitable: """ Start particular task. :param task: AbstractExecutorTask instance to start. :return: awaitable object(future) to get result or exception """ fn, args = task.start() return cast( TaskAwaitable, self._loop.run_in_executor(self._executor_pool, fn, *args) ) class AsyncExecutor(AbstractMultipleExecutor): # pragma: nocover """Thread based executor to run multiple agents in threads.""" def _set_executor_pool(self) -> None: """Do nothing, cause we run tasks in asyncio event loop and do not need an executor pool.""" def _start_task(self, task: AbstractExecutorTask) -> TaskAwaitable: """ Start particular task. :param task: AbstractExecutorTask instance to start. :return: awaitable object(future) to get result or exception """ return task.create_async_task(self._loop) class AbstractMultipleRunner: # pragma: nocover """Abstract multiple runner to create classes to launch tasks with selected mode.""" SUPPORTED_MODES: Dict[str, Type[AbstractMultipleExecutor]] = {} def __init__( self, mode: str, fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies.propagate, ) -> None: """ Init with selected executor mode. :param mode: one of supported executor modes :param fail_policy: one of ExecutorExceptionPolicies to be used with Executor """ if mode not in self.SUPPORTED_MODES: # pragma: nocover raise ValueError(f"Unsupported mode: {mode}") self._mode: str = mode self._executor: AbstractMultipleExecutor = self._make_executor( mode, fail_policy ) self._thread: Optional[Thread] = None @property def is_running(self) -> bool: """Return state of the executor.""" return self._executor.is_running def start(self, threaded: bool = False) -> None: """ Run agents. :param threaded: run in dedicated thread without blocking current thread. """ if threaded: self._thread = Thread(target=self._executor.start, daemon=True) self._thread.start() else: self._executor.start() def stop(self, timeout: Optional[float] = None) -> None: """ Stop agents. :param timeout: timeout in seconds to wait thread stopped, only if started in thread mode. """ self._executor.stop() if self._thread is not None: self._thread.join(timeout=timeout) def _make_executor( self, mode: str, fail_policy: ExecutorExceptionPolicies ) -> AbstractMultipleExecutor: """ Make an executor instance to run agents with. :param mode: executor mode to use. :param fail_policy: one of ExecutorExceptionPolicies to be used with Executor :return: aea executor instance """ executor_cls = self.SUPPORTED_MODES[mode] return executor_cls(tasks=self._make_tasks(), task_fail_policy=fail_policy) @abstractmethod def _make_tasks(self) -> Sequence[AbstractExecutorTask]: """Make tasks to run with executor.""" @property def num_failed(self) -> int: # pragma: nocover """Return number of failed tasks.""" return self._executor.num_failed @property def failed(self) -> Sequence[Task]: # pragma: nocover """Return sequence failed tasks.""" return [i.id for i in self._executor.failed_tasks] @property def not_failed(self) -> Sequence[Task]: # pragma: nocover """Return sequence successful tasks.""" return [i.id for i in self._executor.not_failed_tasks] def try_join_thread(self) -> None: # pragma: nocover """Try to join thread if running in thread mode.""" if self._thread is None: raise ValueError("Not started in thread mode.") # do not block with join, helpful to catch KeyboardInterrupt exception while self._thread.is_alive(): self._thread.join(0.1) ================================================ FILE: aea/helpers/pipe.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Portable pipe implementation for Linux, MacOS, and Windows.""" import asyncio import errno import logging import os import socket import struct import tempfile from abc import ABC, abstractmethod from asyncio import AbstractEventLoop from asyncio.streams import StreamWriter from shutil import rmtree from typing import IO, Optional from aea.exceptions import enforce _default_logger = logging.getLogger(__name__) PIPE_CONN_TIMEOUT = 10.0 PIPE_CONN_ATTEMPTS = 10 TCP_SOCKET_PIPE_CLIENT_CONN_ATTEMPTS = 5 class IPCChannelClient(ABC): """Multi-platform interprocess communication channel for the client side.""" @abstractmethod async def connect(self, timeout: float = PIPE_CONN_TIMEOUT) -> bool: """ Connect to communication channel :param timeout: timeout for other end to connect :return: connection status """ @abstractmethod async def write(self, data: bytes) -> None: """ Write `data` bytes to the other end of the channel Will first write the size than the actual data :param data: bytes to write """ @abstractmethod async def read(self) -> Optional[bytes]: """ Read bytes from the other end of the channel Will first read the size than the actual data :return: read bytes """ @abstractmethod async def close(self) -> None: """Close the communication channel.""" class IPCChannel(IPCChannelClient): """Multi-platform interprocess communication channel.""" @property @abstractmethod def in_path(self) -> str: """ Rendezvous point for incoming communication. :return: path """ @property @abstractmethod def out_path(self) -> str: """ Rendezvous point for outgoing communication. :return: path """ class PosixNamedPipeProtocol: """Posix named pipes async wrapper communication protocol.""" def __init__( self, in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, ) -> None: """ Initialize a new posix named pipe. :param in_path: rendezvous point for incoming data :param out_path: rendezvous point for outgoing data :param logger: the logger :param loop: the event loop """ self.logger = logger self._loop = loop self._in_path = in_path self._out_path = out_path self._in = -1 self._out = -1 self._stream_reader = None # type: Optional[asyncio.StreamReader] self._reader_protocol = None # type: Optional[asyncio.StreamReaderProtocol] self._fileobj = None # type: Optional[IO[str]] self._connection_attempts = PIPE_CONN_ATTEMPTS self._connection_timeout = PIPE_CONN_TIMEOUT async def connect(self, timeout: float = PIPE_CONN_TIMEOUT) -> bool: """ Connect to the other end of the pipe :param timeout: timeout before failing :return: connection success """ if self._loop is None: self._loop = asyncio.get_event_loop() self._connection_timeout = timeout / PIPE_CONN_ATTEMPTS if timeout > 0 else 0 if self._connection_attempts <= 1: # pragma: no cover return False self._connection_attempts -= 1 self.logger.debug( "Attempt opening pipes {}, {}...".format(self._in_path, self._out_path) ) self._in = os.open(self._in_path, os.O_RDONLY | os.O_NONBLOCK | os.O_SYNC) try: self._out = os.open(self._out_path, os.O_WRONLY | os.O_NONBLOCK) except OSError as e: # pragma: no cover if e.errno == errno.ENXIO: self.logger.debug("Sleeping for {}...".format(self._connection_timeout)) await asyncio.sleep(self._connection_timeout) return await self.connect(timeout) raise e # setup reader enforce( self._in != -1 and self._out != -1 and self._loop is not None, "Incomplete initialization.", ) self._stream_reader = asyncio.StreamReader(loop=self._loop) self._reader_protocol = asyncio.StreamReaderProtocol( self._stream_reader, loop=self._loop ) self._fileobj = os.fdopen(self._in, "r") await self._loop.connect_read_pipe( lambda: self.__reader_protocol, self._fileobj ) return True @property def __reader_protocol(self) -> asyncio.StreamReaderProtocol: """Get reader protocol.""" if self._reader_protocol is None: raise ValueError("reader protocol not set!") # pragma: nocover return self._reader_protocol async def write(self, data: bytes) -> None: """ Write to pipe. :param data: bytes to write to pipe """ self.logger.debug("writing {}...".format(len(data))) size = struct.pack("!I", len(data)) os.write(self._out, size + data) await asyncio.sleep(0.0) async def read(self) -> Optional[bytes]: """ Read from pipe. :return: read bytes """ if self._stream_reader is None: # pragma: nocover raise ValueError("StreamReader not set, call connect first!") try: self.logger.debug("waiting for messages (in={})...".format(self._in_path)) buf = await self._stream_reader.readexactly(4) if not buf: # pragma: no cover return None size = struct.unpack("!I", buf)[0] if size <= 0: # pragma: no cover return None data = await self._stream_reader.readexactly(size) if not data: # pragma: no cover return None return data except asyncio.IncompleteReadError as e: # pragma: no cover self.logger.info( "Connection disconnected while reading from pipe ({}/{})".format( len(e.partial), e.expected ) ) return None except asyncio.CancelledError: # pragma: no cover return None async def close(self) -> None: """Disconnect pipe.""" self.logger.debug("closing pipe (in={})...".format(self._in_path)) if self._fileobj is None: raise ValueError("Pipe not connected") # pragma: nocover try: # hack for MacOSX size = struct.pack("!I", 0) os.write(self._out, size) os.close(self._out) self._fileobj.close() except OSError: # pragma: no cover pass await asyncio.sleep(0) class TCPSocketProtocol: """TCP socket communication protocol.""" def __init__( self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, ) -> None: """ Initialize the tcp socket protocol. :param reader: established asyncio reader :param writer: established asyncio writer :param logger: the logger :param loop: the event loop """ self.logger = logger self.loop = loop if loop is not None else asyncio.get_event_loop() self._reader = reader self._writer = writer @property def writer(self) -> StreamWriter: """Get a writer associated with protocol.""" return self._writer async def write(self, data: bytes) -> None: """ Write to socket. :param data: bytes to write """ if self._writer is None: raise ValueError("writer not set!") # pragma: nocover self.logger.debug("writing {}...".format(len(data))) size = struct.pack("!I", len(data)) self._writer.write(size + data) await self._writer.drain() async def read(self) -> Optional[bytes]: """ Read from socket. :return: read bytes """ try: self.logger.debug("waiting for messages...") buf = await self._reader.readexactly(4) if not buf: # pragma: no cover return None size = struct.unpack("!I", buf)[0] data = await self._reader.readexactly(size) if not data: # pragma: no cover return None if len(data) != size: # pragma: no cover raise ValueError( f"Incomplete Read Error! Expected size={size}, got: {len(data)}" ) return data except asyncio.IncompleteReadError as e: # pragma: no cover self.logger.info( "Connection disconnected while reading from pipe ({}/{})".format( len(e.partial), e.expected ) ) return None except asyncio.CancelledError: # pragma: no cover return None async def close(self) -> None: """Disconnect socket.""" if self._writer.can_write_eof(): self._writer.write_eof() await self._writer.drain() self._writer.close() wait_closed = getattr(self._writer, "wait_closed", None) if wait_closed: # in py3.6 writer does not have the coroutine await wait_closed() # pragma: nocover class TCPSocketChannel(IPCChannel): """Interprocess communication channel implementation using tcp sockets.""" def __init__( self, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, ) -> None: """Initialize tcp socket interprocess communication channel.""" self.logger = logger self._loop = loop self._server = None # type: Optional[asyncio.AbstractServer] self._connected = None # type: Optional[asyncio.Event] self._sock = None # type: Optional[TCPSocketProtocol] s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 0)) s.listen(1) self._port = s.getsockname()[1] s.close() async def connect(self, timeout: float = PIPE_CONN_TIMEOUT) -> bool: """ Setup communication channel and wait for other end to connect. :param timeout: timeout for the connection to be established :return: connection status """ if self._loop is None: self._loop = asyncio.get_event_loop() self._connected = asyncio.Event() self._server = await asyncio.start_server( self._handle_connection, host="127.0.0.1", port=self._port ) if self._server.sockets is None: raise ValueError("Server sockets is None!") # pragma: nocover self._port = self._server.sockets[0].getsockname()[1] self.logger.debug("socket pipe rdv point: {}".format(self._port)) try: await asyncio.wait_for(self._connected.wait(), timeout) except asyncio.TimeoutError: # pragma: no cover return False self._server.close() await self._server.wait_closed() return True async def _handle_connection( self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter ) -> None: """Handle connection.""" if self._connected is None: raise ValueError("Connected is None!") # pragma: nocover self._connected.set() self._sock = TCPSocketProtocol( reader, writer, logger=self.logger, loop=self._loop ) async def write(self, data: bytes) -> None: """ Write to channel. :param data: bytes to write """ if self._sock is None: raise ValueError("Socket pipe not connected.") # pragma: nocover await self._sock.write(data) async def read(self) -> Optional[bytes]: """ Read from channel. :return: read bytes """ if self._sock is None: raise ValueError("Socket pipe not connected.") # pragma: nocover return await self._sock.read() async def close(self) -> None: """Disconnect from channel and clean it up.""" if self._sock is None: raise ValueError("Socket pipe not connected.") # pragma: nocover await self._sock.close() @property def in_path(self) -> str: """Rendezvous point for incoming communication.""" return str(self._port) @property def out_path(self) -> str: """Rendezvous point for outgoing communication.""" return str(self._port) class PosixNamedPipeChannel(IPCChannel): """Interprocess communication channel implementation using Posix named pipes.""" def __init__( self, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, ) -> None: """Initialize posix named pipe interprocess communication channel.""" self.logger = logger self._loop = loop self._pipe_dir = tempfile.mkdtemp() self._in_path = "{}/process_to_aea".format(self._pipe_dir) self._out_path = "{}/aea_to_process".format(self._pipe_dir) # setup fifos self.logger.debug( "Creating pipes ({}, {})...".format(self._in_path, self._out_path) ) if os.path.exists(self._in_path): os.remove(self._in_path) # pragma: no cover if os.path.exists(self._out_path): os.remove(self._out_path) # pragma: no cover os.mkfifo(self._in_path) os.mkfifo(self._out_path) self._pipe = PosixNamedPipeProtocol( self._in_path, self._out_path, logger=logger, loop=loop ) async def connect(self, timeout: float = PIPE_CONN_TIMEOUT) -> bool: """ Setup communication channel and wait for other end to connect. :param timeout: timeout for connection to be established :return: bool, indicating success """ if self._loop is None: self._loop = asyncio.get_event_loop() return await self._pipe.connect(timeout) async def write(self, data: bytes) -> None: """ Write to the channel. :param data: data to write to channel """ await self._pipe.write(data) async def read(self) -> Optional[bytes]: """ Read from the channel. :return: read bytes """ return await self._pipe.read() async def close(self) -> None: """Close the channel and clean it up.""" await self._pipe.close() rmtree(self._pipe_dir) @property def in_path(self) -> str: """Rendezvous point for incoming communication.""" return self._in_path @property def out_path(self) -> str: """Rendezvous point for outgoing communication.""" return self._out_path class TCPSocketChannelClient(IPCChannelClient): """Interprocess communication channel client using tcp sockets.""" def __init__( # pylint: disable=unused-argument self, in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, ) -> None: """ Initialize a tcp socket communication channel client. :param in_path: rendezvous point for incoming data :param out_path: rendezvous point for outgoing data :param logger: the logger :param loop: the event loop """ self.logger = logger self._loop = loop parts = in_path.split(":") if len(parts) == 1: self._port = int(in_path) self._host = "127.0.0.1" else: # pragma: nocover self._port = int(parts[1]) self._host = parts[0] self._sock = None # type: Optional[TCPSocketProtocol] self._attempts = TCP_SOCKET_PIPE_CLIENT_CONN_ATTEMPTS self._timeout = PIPE_CONN_TIMEOUT / self._attempts self.last_exception: Optional[Exception] = None async def connect(self, timeout: float = PIPE_CONN_TIMEOUT) -> bool: """ Connect to the other end of the communication channel. :param timeout: timeout for connection to be established :return: connection status """ if self._loop is None: self._loop = asyncio.get_event_loop() self._timeout = timeout / TCP_SOCKET_PIPE_CLIENT_CONN_ATTEMPTS self.logger.debug( "Attempting to connect to {}:{}.....".format("127.0.0.1", self._port) ) connected = False while self._attempts > 0: self._attempts -= 1 try: self._sock = await self._open_connection() connected = True break except ConnectionRefusedError: await asyncio.sleep(self._timeout) except Exception as e: # pylint: disable=broad-except # pragma: nocover self.last_exception = e return False return connected async def _open_connection(self) -> TCPSocketProtocol: reader, writer = await asyncio.open_connection( self._host, self._port, # pylint: disable=protected-access ) return TCPSocketProtocol(reader, writer, logger=self.logger, loop=self._loop) async def write(self, data: bytes) -> None: """ Write data to channel. :param data: bytes to write """ if self._sock is None: raise ValueError("Socket pipe not connected.") # pragma: nocover await self._sock.write(data) async def read(self) -> Optional[bytes]: """ Read data from channel. :return: read bytes """ if self._sock is None: raise ValueError("Socket pipe not connected.") # pragma: nocover return await self._sock.read() async def close(self) -> None: """Disconnect from communication channel.""" if self._sock is None: raise ValueError("Socket pipe not connected.") # pragma: nocover await self._sock.close() class PosixNamedPipeChannelClient(IPCChannelClient): """Interprocess communication channel client using Posix named pipes.""" def __init__( self, in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, ) -> None: """ Initialize a posix named pipe communication channel client. :param in_path: rendezvous point for incoming data :param out_path: rendezvous point for outgoing data :param logger: the logger :param loop: the event loop """ self.logger = logger self._loop = loop self._in_path = in_path self._out_path = out_path self._pipe = None # type: Optional[PosixNamedPipeProtocol] self.last_exception: Optional[Exception] = None async def connect(self, timeout: float = PIPE_CONN_TIMEOUT) -> bool: """ Connect to the other end of the communication channel. :param timeout: timeout for connection to be established :return: connection status """ if self._loop is None: self._loop = asyncio.get_event_loop() self._pipe = PosixNamedPipeProtocol( self._in_path, self._out_path, logger=self.logger, loop=self._loop ) try: return await self._pipe.connect() except Exception as e: # pragma: nocover # pylint: disable=broad-except self.last_exception = e return False async def write(self, data: bytes) -> None: """ Write data to channel. :param data: bytes to write """ if self._pipe is None: raise ValueError("Pipe not connected.") # pragma: nocover await self._pipe.write(data) async def read(self) -> Optional[bytes]: """ Read data from channel. :return: read bytes """ if self._pipe is None: raise ValueError("Pipe not connected.") # pragma: nocover return await self._pipe.read() async def close(self) -> None: """Disconnect from communication channel.""" if self._pipe is None: raise ValueError("Pipe not connected.") # pragma: nocover return await self._pipe.close() def make_ipc_channel( logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None ) -> IPCChannel: """ Build a portable bidirectional InterProcess Communication channel :param logger: the logger :param loop: the loop :return: IPCChannel """ if os.name == "posix": return PosixNamedPipeChannel(logger=logger, loop=loop) if os.name == "nt": # pragma: nocover return TCPSocketChannel(logger=logger, loop=loop) raise NotImplementedError( # pragma: nocover "make ipc channel is not supported on platform {}".format(os.name) ) def make_ipc_channel_client( in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, ) -> IPCChannelClient: """ Build a portable bidirectional InterProcess Communication client channel :param in_path: rendezvous point for incoming communication :param out_path: rendezvous point for outgoing outgoing :param logger: the logger :param loop: the loop :return: IPCChannel """ if os.name == "posix": return PosixNamedPipeChannelClient(in_path, out_path, logger=logger, loop=loop) if os.name == "nt": # pragma: nocover return TCPSocketChannelClient(in_path, out_path, logger=logger, loop=loop) raise NotImplementedError( # pragma: nocover "make ip channel client is not supported on platform {}".format(os.name) ) ================================================ FILE: aea/helpers/preference_representations/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains helper methods and classes for preference representations.""" ================================================ FILE: aea/helpers/preference_representations/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Preference representation helpers.""" import math from typing import Dict from aea.exceptions import enforce def logarithmic_utility( utility_params_by_good_id: Dict[str, float], quantities_by_good_id: Dict[str, int], quantity_shift: int = 100, ) -> float: """ Compute agent's utility given her utility function params and a good bundle. :param utility_params_by_good_id: utility params by good identifier :param quantities_by_good_id: quantities by good identifier :param quantity_shift: a non-negative factor to shift the quantities in the utility function (to ensure the natural logarithm can be used on the entire range of quantities) :return: utility value """ enforce( quantity_shift >= 0, "The quantity_shift argument must be a non-negative integer.", ) goodwise_utility = [ utility_params_by_good_id[good_id] * math.log(quantity + quantity_shift) if quantity + quantity_shift > 0 else -10000 for good_id, quantity in quantities_by_good_id.items() ] return sum(goodwise_utility) def linear_utility( exchange_params_by_currency_id: Dict[str, float], balance_by_currency_id: Dict[str, int], ) -> float: """ Compute agent's utility given her utility function params and a good bundle. :param exchange_params_by_currency_id: exchange params by currency :param balance_by_currency_id: balance by currency :return: utility value """ money_utility = [ exchange_params_by_currency_id[currency_id] * balance for currency_id, balance in balance_by_currency_id.items() ] return sum(money_utility) ================================================ FILE: aea/helpers/profiling.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of background profiling daemon.""" import asyncio import datetime import gc import logging import platform import textwrap import threading import time from collections import Counter from concurrent.futures._base import CancelledError from functools import wraps from typing import Any, Callable, Dict, List, Type from aea.helpers.async_utils import Runnable lock = threading.Lock() _default_logger = logging.getLogger(__file__) if platform.system() == "Windows": # pragma: nocover import win32process # type: ignore # pylint: disable=import-error WIN32_PROCESS_TIMES_TICKS_PER_SECOND = 1e7 def get_current_process_memory_usage() -> float: """Get current process memory usage in MB.""" d = win32process.GetProcessMemoryInfo(win32process.GetCurrentProcess()) # type: ignore return 1.0 * d["WorkingSetSize"] / 1024**2 def get_current_process_cpu_time() -> float: """Get current process cpu time in seconds.""" d = win32process.GetProcessTimes(win32process.GetCurrentProcess()) # type: ignore return d["UserTime"] / WIN32_PROCESS_TIMES_TICKS_PER_SECOND else: import resource _MAC_MEM_STATS_MB = 1024**2 _LINUX_MEM_STATS_MB = 1024 def get_current_process_memory_usage() -> float: """Get current process memory usage in MB.""" if platform.system() == "Darwin": # pragma: nocover divider = _MAC_MEM_STATS_MB else: divider = _LINUX_MEM_STATS_MB return 1.0 * resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / divider def get_current_process_cpu_time() -> float: """Get current process cpu time in seconds.""" return resource.getrusage(resource.RUSAGE_SELF).ru_utime class Profiling(Runnable): """Profiling service.""" def __init__( self, period: int = 0, objects_instances_to_count: List[Type] = None, objects_created_to_count: List[Type] = None, output_function: Callable[[str], None] = lambda x: print(x, flush=True), ) -> None: """ Init profiler. :param period: delay between profiling output in seconds. :param objects_instances_to_count: object to count :param objects_created_to_count: object created to count :param output_function: function to display output, one str argument. """ if period < 1: # pragma: nocover raise ValueError("Period should be at least 1 second!") super().__init__(threaded=True) self._period = period self._start_ts = time.time() self._objects_instances_to_count = objects_instances_to_count or [] self._objects_created_to_count = objects_created_to_count or [] self._output_function = output_function self._counter: Dict[Type, int] = Counter() def set_counters(self) -> None: """Modify obj.__new__ to count objects created created.""" for obj in self._objects_created_to_count: self._counter[obj] = 0 def make_fn(obj: Any) -> Callable: orig_new = obj.__new__ # pylint: disable=protected-access # type: ignore obj_copy = obj @wraps(orig_new) def new(*args: Any, **kwargs: Any) -> Callable: self._counter[obj_copy] += 1 if orig_new is object.__new__: return orig_new(args[0]) # pragma: nocover return orig_new(*args, **kwargs) # pragma: nocover return new obj.__new__ = make_fn(obj) # type: ignore async def run(self) -> None: """Run profiling.""" try: self.set_counters() while True: await asyncio.sleep(self._period) self.output_profile_data() except CancelledError: # pragma: nocover pass except Exception: # pragma: nocover _default_logger.exception("Exception in Profiling") raise def output_profile_data(self) -> None: """Render profiling data and call output_function.""" data = self.get_profile_data() text = ( textwrap.dedent( f""" Profiling details for current AEA process: {datetime.datetime.now()} ============================================= Run time: {data["run_time"]:.6f} seconds Cpu time: {data["cpu_time"]:.6f} seconds, Cpu/Run time: {100*data["cpu_time"]/data["run_time"]:.6f}% Memory: {data["mem"]:.6f} MB Threads: {data["threads"]['amount']} {data["threads"]['names']} Objects present: """ ) + "\n".join([f" * {i}: {c}" for i, c in data["objects_present"].items()]) + "\n" + """Objects created:\n""" + "\n".join( [f" * {i.__name__}: {c}" for i, c in data["objects_created"].items()] ) + "\n" ) self._output_function(text) def get_profile_data(self) -> Dict: """Get profiling data dict.""" return { "run_time": time.time() - self._start_ts, "cpu_time": get_current_process_cpu_time(), "mem": get_current_process_memory_usage(), "threads": { "amount": threading.active_count(), "names": [i.name for i in threading.enumerate()], }, "objects_present": self.get_objects_instances(), "objects_created": self.get_objecst_created(), } def get_objects_instances(self) -> Dict: """Return dict with counted object instances present now.""" result: Dict = Counter() with lock: for obj_type in self._objects_instances_to_count: result[obj_type.__name__] += 0 for obj in gc.get_objects(): for obj_type in self._objects_instances_to_count: if isinstance(obj, obj_type): result[obj_type.__name__] += 1 return result def get_objecst_created(self) -> Dict: """Return dict with counted object instances created.""" return self._counter ================================================ FILE: aea/helpers/search/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains search related classes.""" ================================================ FILE: aea/helpers/search/generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a generic data model.""" from typing import Any, Dict, List from aea.exceptions import enforce from aea.helpers.search.models import Attribute, DataModel, Location SUPPORTED_TYPES = {"str": str, "int": int, "float": float, "bool": bool} class GenericDataModel(DataModel): # pylint: disable=too-few-public-methods """Generic data model.""" def __init__( self, data_model_name: str, data_model_attributes: Dict[str, Any] ) -> None: """Initialise the dataModel.""" attributes = [] # type: List[Attribute] for values in data_model_attributes.values(): enforce( values["type"] in SUPPORTED_TYPES.keys(), "Type is not supported. Use str, int, float or bool", ) enforce(isinstance(values["name"], str), "Name must be a string!") enforce( isinstance(values["is_required"], bool), "Wrong type for is_required. Must be bool!", ) attributes.append( Attribute( name=values["name"], # type: ignore type_=SUPPORTED_TYPES[values["type"]], is_required=values["is_required"], ) ) super().__init__(data_model_name, attributes) AGENT_LOCATION_MODEL = DataModel( "location_agent", [Attribute("location", Location, True, "The location where the agent is.")], "A data model to describe location of an agent.", ) AGENT_PERSONALITY_MODEL = DataModel( "personality_agent", [ Attribute("piece", str, True, "The personality piece key."), Attribute("value", str, True, "The personality piece value."), ], "A data model to describe the personality of an agent.", ) AGENT_SET_SERVICE_MODEL = DataModel( "set_service_key", [ Attribute("key", str, True, "Service key name."), Attribute("value", str, True, "Service key value."), ], "A data model to set service key.", ) SIMPLE_SERVICE_MODEL = DataModel( "simple_service", [Attribute("seller_service", str, True, "Service key name.")], "A data model to represent a search for a service.", ) SIMPLE_DATA_MODEL = DataModel( "simple_data", [Attribute("dataset_id", str, True, "Data set key name.")], "A data model to represent a search for a data set.", ) AGENT_REMOVE_SERVICE_MODEL = DataModel( "remove_service_key", [Attribute("key", str, True, "Service key name.")], "A data model to remove service key.", ) ================================================ FILE: aea/helpers/search/models.proto ================================================ syntax = "proto3"; package aea.helpers.search.models; message Query { message Attribute { enum Type { DOUBLE = 0; INT = 1; BOOL = 2; STRING = 3; LOCATION = 4; } string name = 1; Type type = 2; bool required = 3; string description = 4; } message DataModel { string name = 1; repeated Attribute attributes = 2; string description = 3; } message Location { double lon = 1; double lat = 2; } message Value { oneof value { string string = 1; double double = 2; bool boolean = 3; int64 integer = 4; Location location = 5; } } message KeyValue { string key = 1; Value value = 2; } message Instance { DataModel model = 1; repeated KeyValue values = 2; } message StringPair { string first = 1; string second = 2; } message IntPair { int64 first = 1; int64 second = 2; } message DoublePair { double first = 1; double second = 2; } message LocationPair { Location first = 1; Location second = 2; } message Range { oneof pair { StringPair string_pair = 1; IntPair integer_pair = 2; DoublePair double_pair = 3; LocationPair location_pair = 4; } } message Distance { Location center = 1; double distance = 2; } message Relation { enum Operator { EQ = 0; // = LT = 1; // < LTEQ = 2; // <= GT = 3; // > GTEQ = 4; // >= NOTEQ = 5; // !=, <> } Operator operator = 1; Value value = 2; } message Set { message Values { message Ints { repeated int64 values = 1; } message Doubles { repeated double values = 1; } message Strings { repeated string values = 1; } message Bools { repeated bool values = 1; } message Locations { repeated Location values = 1; } oneof values { Strings string = 1; Doubles double = 2; Bools boolean = 3; Ints integer = 4; Locations location = 5; } } enum Operator { IN = 0; NOTIN = 1; } Operator operator = 1; Values values = 2; } message ConstraintExpr { message Or { repeated ConstraintExpr expression = 1; } message And { repeated ConstraintExpr expression = 1; } message Not { ConstraintExpr expression = 1; } message Constraint { string attribute_name = 1; oneof constraint { Set set_ = 2; Range range_ = 3; Relation relation = 4; Distance distance = 5; } } oneof expression { Or or_ = 1; And and_ = 2; Not not_ = 3; Constraint constraint = 4; } } message Model { repeated ConstraintExpr constraints = 1; DataModel model = 2; } } // option optimize_for = LITE_RUNTIME; option optimize_for = SPEED; ================================================ FILE: aea/helpers/search/models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ # pylint: disable=no-member """Useful classes for the OEF search.""" import logging from abc import ABC, abstractmethod from copy import deepcopy from enum import Enum from math import asin, cos, radians, sin, sqrt from typing import ( Any, Dict, Iterator, List, Mapping, Optional, Tuple, Type, Union, cast, ) from aea.exceptions import enforce from aea.helpers.search import models_pb2 _default_logger = logging.getLogger(__name__) proto_value = { "string": "string", "double": "double", "boolean": "boolean", "integer": "integer", "location": "location", } proto_range_pairs = { "string": "string_pair", "integer": "integer_pair", "double": "double_pair", "location": "location_pair", } proto_set_values = { "string": "string", "double": "double", "boolean": "boolean", "integer": "integer", "location": "location", } proto_constraint = { "set": "set_", "range": "range_", "relation": "relation", "distance": "distance", } proto_expression = { "or": "or_", "and": "and_", "not": "not_", "constraint": "constraint", } CONSTRAINT_CATEGORY_RELATION = "relation" CONSTRAINT_CATEGORY_RANGE = "range" CONSTRAINT_CATEGORY_SET = "set" CONSTRAINT_CATEGORY_DISTANCE = "distance" CONSTRAINT_CATEGORIES = [ CONSTRAINT_CATEGORY_RELATION, CONSTRAINT_CATEGORY_RANGE, CONSTRAINT_CATEGORY_SET, CONSTRAINT_CATEGORY_DISTANCE, ] class Location: """Data structure to represent locations (i.e. a pair of latitude and longitude).""" __slots__ = ("latitude", "longitude") def __init__(self, latitude: float, longitude: float) -> None: """ Initialize a location. :param latitude: the latitude of the location. :param longitude: the longitude of the location. """ self.latitude = latitude self.longitude = longitude @property def tuple(self) -> Tuple[float, float]: """Get the tuple representation of a location.""" return self.latitude, self.longitude def distance(self, other: "Location") -> float: """ Get the distance to another location. :param other: the other location :return: the distance """ return haversine(self.latitude, self.longitude, other.latitude, other.longitude) def __eq__(self, other: Any) -> bool: """Compare equality of two locations.""" if not isinstance(other, Location): return False # pragma: nocover return self.latitude == other.latitude and self.longitude == other.longitude def __str__(self) -> str: """Get the string representation of the data model.""" return "Location(latitude={},longitude={})".format( self.latitude, self.longitude ) def encode(self) -> models_pb2.Query.Location: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ location_pb = models_pb2.Query.Location() # type: ignore location_pb.lat = self.latitude location_pb.lon = self.longitude return location_pb @classmethod def decode(cls, location_pb: Any) -> "Location": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param location_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ latitude = location_pb.lat longitude = location_pb.lon return cls(latitude, longitude) """ The allowable types that an Attribute can have """ ATTRIBUTE_TYPES = Union[float, str, bool, int, Location] ALLOWED_ATTRIBUTE_TYPES = [float, str, bool, int, Location] class AttributeInconsistencyException(Exception): """ Raised when the attributes in a Description are inconsistent. Inconsistency is defined when values do not meet their respective schema, or if the values are not of an allowed type. """ class Attribute: """Implements an attribute for an OEF data model.""" _attribute_type_to_pb = { bool: models_pb2.Query.Attribute.BOOL, # type: ignore int: models_pb2.Query.Attribute.INT, # type: ignore float: models_pb2.Query.Attribute.DOUBLE, # type: ignore str: models_pb2.Query.Attribute.STRING, # type: ignore Location: models_pb2.Query.Attribute.LOCATION, # type: ignore } __slots__ = ("name", "type", "is_required", "description") def __init__( self, name: str, type_: Type[ATTRIBUTE_TYPES], is_required: bool, description: str = "", ) -> None: """ Initialize an attribute. :param name: the name of the attribute. :param type_: the type of the attribute. :param is_required: whether the attribute is required by the data model. :param description: an (optional) human-readable description for the attribute. """ self.name = name self.type = type_ self.is_required = is_required self.description = description def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, Attribute) and self.name == other.name and self.type == other.type and self.is_required == other.is_required ) def __str__(self) -> str: """Get the string representation of the data model.""" return "Attribute(name={},type={},is_required={})".format( self.name, self.type, self.is_required ) def encode(self) -> models_pb2.Query.Attribute: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ attribute = models_pb2.Query.Attribute() # type: ignore attribute.name = self.name attribute.type = self._attribute_type_to_pb[self.type] attribute.required = self.is_required if self.description is not None: attribute.description = self.description return attribute @classmethod def decode(cls, attribute_pb: models_pb2.Query.Attribute) -> "Attribute": # type: ignore """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param attribute_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ _pb_to_attribute_type = {v: k for k, v in cls._attribute_type_to_pb.items()} return cls( attribute_pb.name, _pb_to_attribute_type[attribute_pb.type], attribute_pb.required, attribute_pb.description if attribute_pb.description else None, ) class DataModel: """Implements an OEF data model.""" __slots__ = ("name", "attributes", "description") def __init__( self, name: str, attributes: List[Attribute], description: str = "" ) -> None: """ Initialize a data model. :param name: the name of the data model. :param attributes: the attributes of the data model. :param description: the data model description. """ self.name: str = name self.attributes = sorted( attributes, key=lambda x: x.name ) # type: List[Attribute] self._check_validity() self.description = description @property def attributes_by_name(self) -> Dict[str, Attribute]: """Get the attributes by name.""" return {a.name: a for a in self.attributes} def _check_validity(self) -> None: # check if there are duplicated attribute names attribute_names = [attribute.name for attribute in self.attributes] if len(attribute_names) != len(set(attribute_names)): raise ValueError( "Invalid input value for type '{}': duplicated attribute name.".format( type(self).__name__ ) ) def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, DataModel) and self.name == other.name and self.attributes == other.attributes ) def __str__(self) -> str: """Get the string representation of the data model.""" return "DataModel(name={},attributes={},description={})".format( self.name, {a.name: str(a) for a in self.attributes}, self.description ) def encode(self) -> models_pb2.Query.DataModel: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ model = models_pb2.Query.DataModel() # type: ignore model.name = self.name model.attributes.extend([attr.encode() for attr in self.attributes]) if self.description is not None: model.description = self.description return model @classmethod def decode(cls, data_model_pb: Any) -> "DataModel": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param data_model_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ name = data_model_pb.name attributes = [Attribute.decode(attr_pb) for attr_pb in data_model_pb.attributes] description = data_model_pb.description return cls(name, attributes, description) def generate_data_model( model_name: str, attribute_values: Mapping[str, ATTRIBUTE_TYPES] ) -> DataModel: """ Generate a data model that matches the values stored in this description. That is, for each attribute (name, value), generate an Attribute. It is assumed that each attribute is required. :param model_name: the name of the model. :param attribute_values: the values of each attribute :return: the schema compliant with the values specified. """ return DataModel( model_name, [Attribute(key, type(value), True) for key, value in attribute_values.items()], ) class Description: """Implements an OEF description.""" __slots__ = ("_values", "data_model") def __init__( self, values: Mapping[str, ATTRIBUTE_TYPES], data_model: Optional[DataModel] = None, data_model_name: str = "", ) -> None: """ Initialize the description object. :param values: the values in the description. :param data_model: the data model (optional) :param data_model_name: the data model name if a datamodel is created on the fly. """ _values = deepcopy(values) self._values = _values if data_model is not None: self.data_model = data_model else: self.data_model = generate_data_model(data_model_name, values) self._check_consistency() @property def values(self) -> Dict: """Get the values.""" return cast(Dict, self._values) def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, Description) and self.values == other.values and self.data_model == other.data_model ) def __iter__(self) -> Iterator: """Create an iterator.""" return iter(self.values) def _check_consistency(self) -> None: """ Check the consistency of the values of this description. If an attribute has been provided, values are checked against that. If no attribute schema has been provided then minimal checking is performed based on the values in the provided attribute_value dictionary. :raises AttributeInconsistencyException: if values do not meet the schema, or if no schema is present | if they have disallowed types. """ # check that all required attributes in the schema are contained in the description required_attributes = [ attribute.name for attribute in self.data_model.attributes if attribute.is_required ] if not all( attribute_name in self.values for attribute_name in required_attributes ): raise AttributeInconsistencyException("Missing required attribute.") # check that all values are defined in the data model all_attributes = [attribute.name for attribute in self.data_model.attributes] if not all(key in all_attributes for key in self.values.keys()): raise AttributeInconsistencyException( "Have extra attribute not in data model." ) # check that each of the provided values are consistent with that specified in the data model for key, value in self.values.items(): attribute = next( ( attribute for attribute in self.data_model.attributes if attribute.name == key ), None, ) if attribute is None: # pragma: nocover # looks like this check is redundant, cause checks done above for all attributes raise ValueError("Attribute {} not found!".format(key)) if not isinstance(value, attribute.type): # values does not match type in data model raise AttributeInconsistencyException( "Attribute {} has incorrect type: {}".format( attribute.name, attribute.type ) ) if not type(value) in ALLOWED_ATTRIBUTE_TYPES: # value type matches data model, but it is not an allowed type raise AttributeInconsistencyException( "Attribute {} has unallowed type: {}. Allowed types: {}".format( attribute.name, type(value), ALLOWED_ATTRIBUTE_TYPES, ) ) def __str__(self) -> str: """Get the string representation of the description.""" return "Description(values={},data_model={})".format( self._values, self.data_model ) @staticmethod def _to_key_value_pb(key: str, value: ATTRIBUTE_TYPES) -> models_pb2.Query.KeyValue: # type: ignore """ From a (key, attribute value) pair to the associated Protobuf object. :param key: the key of the attribute. :param value: the value of the attribute. :return: the associated Protobuf object. """ kv = models_pb2.Query.KeyValue() # type: ignore kv.key = key if type(value) == bool: # pylint: disable=unidiomatic-typecheck kv.value.boolean = value elif type(value) == int: # pylint: disable=unidiomatic-typecheck kv.value.integer = value elif type(value) == float: # pylint: disable=unidiomatic-typecheck kv.value.double = value elif type(value) == str: # pylint: disable=unidiomatic-typecheck kv.value.string = value elif type(value) == Location: # pylint: disable=unidiomatic-typecheck kv.value.location.CopyFrom(value.encode()) # type: ignore return kv def _encode(self) -> models_pb2.Query.Instance: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ instance = models_pb2.Query.Instance() # type: ignore instance.model.CopyFrom(self.data_model.encode()) instance.values.extend( [self._to_key_value_pb(key, value) for key, value in self.values.items()] ) return instance @classmethod def encode(cls, description_pb: Any, description: "Description") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the description_protobuf_object argument must be matched with the instance of this class in the 'description_object' argument. :param description_pb: the protocol buffer object whose type corresponds with this class. :param description: an instance of this class to be encoded in the protocol buffer object. """ description_bytes_pb = description._encode() # pylint: disable=protected-access description_bytes_bytes = description_bytes_pb.SerializeToString() description_pb.description_bytes = description_bytes_bytes @staticmethod def _extract_value(value: models_pb2.Query.Value) -> ATTRIBUTE_TYPES: # type: ignore """ From a Protobuf query value object to attribute type. :param value: an instance of models_pb2.Query.Value. :return: the associated attribute type. """ value_case = value.WhichOneof("value") if value_case == proto_value["string"]: result = value.string elif value_case == proto_value["boolean"]: result = bool(value.boolean) elif value_case == proto_value["integer"]: result = value.integer elif value_case == proto_value["double"]: result = value.double elif value_case == proto_value["location"]: result = Location.decode(value.location) else: raise ValueError( # pragma: nocover f"Incorrect value. Expected either of {list(proto_value.values())}. Found {value_case}." ) return result @classmethod def _decode(cls, description_pb: Any) -> "Description": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param description_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ model = DataModel.decode(description_pb.model) values = { attr.key: cls._extract_value(attr.value) for attr in description_pb.values } return cls(values, model) @classmethod def decode(cls, description_pb: Any) -> "Description": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'description_protobuf_object' argument. :param description_pb: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'description_protobuf_object' argument. """ description_bytes_pb = models_pb2.Query.Instance() # type: ignore description_bytes_pb.ParseFromString(description_pb.description_bytes) description = cls._decode(description_bytes_pb) return description class ConstraintTypes(Enum): """Types of constraint.""" EQUAL = "==" NOT_EQUAL = "!=" LESS_THAN = "<" LESS_THAN_EQ = "<=" GREATER_THAN = ">" GREATER_THAN_EQ = ">=" WITHIN = "within" IN = "in" NOT_IN = "not_in" DISTANCE = "distance" def __str__(self) -> str: # pragma: nocover """Get the string representation.""" return str(self.value) class ConstraintType: """ Type of constraint. Used with the Constraint class, this class allows to specify constraint over attributes. Examples: Equal to three >>> equal_3 = ConstraintType(ConstraintTypes.EQUAL, 3) You can also specify a type of constraint by using its string representation, e.g.: >>> equal_3 = ConstraintType("==", 3) >>> not_equal_london = ConstraintType("!=", "London") >>> less_than_pi = ConstraintType("<", 3.14) >>> within_range = ConstraintType("within", (-10.0, 10.0)) >>> in_a_set = ConstraintType("in", (1, 2, 3)) >>> not_in_a_set = ConstraintType("not_in", ("C", "Java", "Python")) """ __slots__ = ("type", "value") def __init__(self, type_: Union[ConstraintTypes, str], value: Any) -> None: """ Initialize a constraint type. :param type_: the type of the constraint. | Either an instance of the ConstraintTypes enum, | or a string representation associated with the type. :param value: the value that defines the constraint. :raises AEAEnforceError: if the type of the constraint is not # noqa: DAR402 """ self.type = ConstraintTypes(type_) self.value = value enforce(self.check_validity(), "ConstraintType initialization inconsistent.") def check_validity(self) -> bool: """ Check the validity of the input provided. :return: boolean to indicate validity :raises AEAEnforceError: if the value is not valid wrt the constraint type. # noqa: DAR402 """ try: if self.type == ConstraintTypes.EQUAL: enforce( isinstance(self.value, (int, float, str, bool)), f"Expected one of type in (int, float, str, bool), got {self.value}", ) elif self.type == ConstraintTypes.NOT_EQUAL: enforce( isinstance(self.value, (int, float, str, bool)), f"Expected one of type in (int, float, str, bool), got {self.value}", ) elif self.type == ConstraintTypes.LESS_THAN: enforce( isinstance(self.value, (int, float, str)), f"Expected one of type in (int, float, str), got {self.value}", ) elif self.type == ConstraintTypes.LESS_THAN_EQ: enforce( isinstance(self.value, (int, float, str)), f"Expected one of type in (int, float, str), got {self.value}", ) elif self.type == ConstraintTypes.GREATER_THAN: enforce( isinstance(self.value, (int, float, str)), f"Expected one of type in (int, float, str), got {self.value}", ) elif self.type == ConstraintTypes.GREATER_THAN_EQ: enforce( isinstance(self.value, (int, float, str)), f"Expected one of type in (int, float, str), got {self.value}", ) elif self.type == ConstraintTypes.WITHIN: allowed_sub_types = (int, float, str) enforce( isinstance(self.value, tuple), f"Expected tuple, got {type(self.value)}", ) enforce( len(self.value) == 2, f"Expected length=2, got {len(self.value)}" ) enforce( isinstance(self.value[0], type(self.value[1])), "Invalid types." ) enforce( isinstance(self.value[1], type(self.value[0])), "Invalid types." ) enforce( isinstance(self.value[0], allowed_sub_types), f"Invalid type for first element. Expected either of {allowed_sub_types}. Found {type(self.value[0])}.", ) enforce( isinstance(self.value[1], allowed_sub_types), f"Invalid type for second element. Expected either of {allowed_sub_types}. Found {type(self.value[1])}.", ) elif self.type == ConstraintTypes.IN: enforce( isinstance(self.value, tuple), f"Expected tuple, got {type(self.value)}", ) if len(self.value) > 0: _type = type(next(iter(self.value))) enforce( all(isinstance(obj, _type) for obj in self.value), "Invalid types.", ) elif self.type == ConstraintTypes.NOT_IN: enforce( isinstance(self.value, tuple), f"Expected tuple, got {type(self.value)}", ) if len(self.value) > 0: _type = type(next(iter(self.value))) enforce( all(isinstance(obj, _type) for obj in self.value), "Invalid types.", ) elif self.type == ConstraintTypes.DISTANCE: enforce( isinstance(self.value, tuple), f"Expected tuple, got {type(self.value)}", ) enforce( len(self.value) == 2, f"Expected length=2, got {len(self.value)}" ) enforce( isinstance(self.value[0], Location), "Invalid type, expected Location.", ) enforce( isinstance(self.value[1], float), "Invalid type, expected Location." ) else: # pragma: nocover raise ValueError("Type not recognized.") except ValueError: return False # pragma: nocover return True def is_valid(self, attribute: Attribute) -> bool: """ Check if the constraint type is valid wrt a given attribute. A constraint type is valid wrt an attribute if the type of its operand(s) is the same of the attribute type. >>> attribute = Attribute("year", int, True) >>> valid_constraint_type = ConstraintType(ConstraintTypes.GREATER_THAN, 2000) >>> valid_constraint_type.is_valid(attribute) True >>> valid_constraint_type = ConstraintType(ConstraintTypes.WITHIN, (2000, 2001)) >>> valid_constraint_type.is_valid(attribute) True The following constraint is invalid: the year is in a string variable, whereas the attribute is defined over integers. >>> invalid_constraint_type = ConstraintType(ConstraintTypes.GREATER_THAN, "2000") >>> invalid_constraint_type.is_valid(attribute) False :param attribute: the data model used to check the validity of the constraint type. :return: ``True`` if the constraint type is valid wrt the attribute, ``False`` otherwise. """ return self.get_data_type() == attribute.type def get_data_type(self) -> Type[ATTRIBUTE_TYPES]: """ Get the type of the data used to define the constraint type. For instance: >>> c = ConstraintType(ConstraintTypes.EQUAL, 1) >>> c.get_data_type() :return: data type """ if isinstance(self.value, (list, tuple, set)): value = next(iter(self.value)) else: value = self.value value = cast(ATTRIBUTE_TYPES, value) return type(value) def check(self, value: ATTRIBUTE_TYPES) -> bool: """ Check if an attribute value satisfies the constraint. The implementation depends on the constraint type. :param value: the value to check. :return: True if the value satisfy the constraint, False otherwise. :raises ValueError: if the constraint type is not recognized. """ if self.type == ConstraintTypes.EQUAL: return value == self.value if self.type == ConstraintTypes.NOT_EQUAL: return value != self.value if self.type == ConstraintTypes.LESS_THAN: return value < self.value if self.type == ConstraintTypes.LESS_THAN_EQ: return value <= self.value if self.type == ConstraintTypes.GREATER_THAN: return value > self.value if self.type == ConstraintTypes.GREATER_THAN_EQ: return value >= self.value if self.type == ConstraintTypes.WITHIN: low = self.value[0] high = self.value[1] return low <= value <= high if self.type == ConstraintTypes.IN: return value in self.value if self.type == ConstraintTypes.NOT_IN: return value not in self.value if self.type == ConstraintTypes.DISTANCE: if not isinstance(value, Location): # pragma: nocover raise ValueError("Value must be of type Location.") location = cast(Location, self.value[0]) distance = self.value[1] return location.distance(value) <= distance raise ValueError("Constraint type not recognized.") # pragma: nocover def __eq__(self, other: Any) -> bool: """Check equality with another object.""" return ( isinstance(other, ConstraintType) and self.value == other.value and self.type == other.type ) def __str__(self) -> str: """Get the string representation of the constraint type.""" return "ConstraintType(value={},type={})".format(self.value, self.type) def encode(self) -> Optional[Any]: """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ encoding: Optional[Any] = None if self.type in ( ConstraintTypes.EQUAL, ConstraintTypes.NOT_EQUAL, ConstraintTypes.LESS_THAN, ConstraintTypes.LESS_THAN_EQ, ConstraintTypes.GREATER_THAN, ConstraintTypes.GREATER_THAN_EQ, ): relation = models_pb2.Query.Relation() # type: ignore if self.type == ConstraintTypes.EQUAL: relation.operator = models_pb2.Query.Relation.EQ # type: ignore elif self.type == ConstraintTypes.NOT_EQUAL: relation.operator = models_pb2.Query.Relation.NOTEQ # type: ignore elif self.type == ConstraintTypes.LESS_THAN: relation.operator = models_pb2.Query.Relation.LT # type: ignore elif self.type == ConstraintTypes.LESS_THAN_EQ: relation.operator = models_pb2.Query.Relation.LTEQ # type: ignore elif self.type == ConstraintTypes.GREATER_THAN: relation.operator = models_pb2.Query.Relation.GT # type: ignore elif self.type == ConstraintTypes.GREATER_THAN_EQ: relation.operator = models_pb2.Query.Relation.GTEQ # type: ignore query_value = models_pb2.Query.Value() # type: ignore if isinstance(self.value, bool): query_value.boolean = self.value elif isinstance(self.value, int): query_value.integer = self.value elif isinstance(self.value, float): query_value.double = self.value elif isinstance(self.value, str): query_value.string = self.value relation.value.CopyFrom(query_value) encoding = relation elif self.type == ConstraintTypes.WITHIN: range_ = models_pb2.Query.Range() # type: ignore if type(self.value[0]) == str: # pylint: disable=unidiomatic-typecheck values = models_pb2.Query.StringPair() # type: ignore values.first = self.value[0] values.second = self.value[1] range_.string_pair.CopyFrom(values) elif type(self.value[0]) == int: # pylint: disable=unidiomatic-typecheck values = models_pb2.Query.IntPair() # type: ignore values.first = self.value[0] values.second = self.value[1] range_.integer_pair.CopyFrom(values) elif type(self.value[0]) == float: # pylint: disable=unidiomatic-typecheck values = models_pb2.Query.DoublePair() # type: ignore values.first = self.value[0] values.second = self.value[1] range_.double_pair.CopyFrom(values) encoding = range_ elif self.type in (ConstraintTypes.IN, ConstraintTypes.NOT_IN): set_ = models_pb2.Query.Set() # type: ignore if self.type == ConstraintTypes.IN: set_.operator = models_pb2.Query.Set.IN # type: ignore elif self.type == ConstraintTypes.NOT_IN: set_.operator = models_pb2.Query.Set.NOTIN # type: ignore value_type = type(self.value[0]) if len(self.value) > 0 else str if value_type == str: values = models_pb2.Query.Set.Values.Strings() # type: ignore values.values.extend(self.value) set_.values.string.CopyFrom(values) elif value_type == bool: values = models_pb2.Query.Set.Values.Bools() # type: ignore values.values.extend(self.value) set_.values.boolean.CopyFrom(values) elif value_type == int: values = models_pb2.Query.Set.Values.Ints() # type: ignore values.values.extend(self.value) set_.values.integer.CopyFrom(values) elif value_type == float: values = models_pb2.Query.Set.Values.Doubles() # type: ignore values.values.extend(self.value) set_.values.double.CopyFrom(values) elif value_type == Location: values = models_pb2.Query.Set.Values.Locations() # type: ignore values.values.extend([value.encode() for value in self.value]) set_.values.location.CopyFrom(values) encoding = set_ elif self.type == ConstraintTypes.DISTANCE: distance_pb = models_pb2.Query.Distance() # type: ignore distance_pb.distance = self.value[1] distance_pb.center.CopyFrom(self.value[0].encode()) encoding = distance_pb return encoding @classmethod def decode(cls, constraint_type_pb: Any, category: str) -> "ConstraintType": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param constraint_type_pb: the protocol buffer object corresponding with this class. :param category: the category of the constraint ('relation', 'set', 'range', 'distance). :return: A new instance of this class matching the protocol buffer object """ decoding: ConstraintType relation_type_from_pb = { models_pb2.Query.Relation.Operator.GTEQ: ConstraintTypes.GREATER_THAN_EQ, # type: ignore models_pb2.Query.Relation.Operator.GT: ConstraintTypes.GREATER_THAN, # type: ignore models_pb2.Query.Relation.Operator.LTEQ: ConstraintTypes.LESS_THAN_EQ, # type: ignore models_pb2.Query.Relation.Operator.LT: ConstraintTypes.LESS_THAN, # type: ignore models_pb2.Query.Relation.Operator.NOTEQ: ConstraintTypes.NOT_EQUAL, # type: ignore models_pb2.Query.Relation.Operator.EQ: ConstraintTypes.EQUAL, # type: ignore } set_type_from_pb = { models_pb2.Query.Set.Operator.IN: ConstraintTypes.IN, # type: ignore models_pb2.Query.Set.Operator.NOTIN: ConstraintTypes.NOT_IN, # type: ignore } if category == CONSTRAINT_CATEGORY_RELATION: relation_enum = relation_type_from_pb[constraint_type_pb.operator] value_case = constraint_type_pb.value.WhichOneof("value") if value_case == proto_value["string"]: decoding = ConstraintType( relation_enum, constraint_type_pb.value.string ) elif value_case == proto_value["boolean"]: decoding = ConstraintType( relation_enum, constraint_type_pb.value.boolean ) elif value_case == proto_value["integer"]: decoding = ConstraintType( relation_enum, constraint_type_pb.value.integer ) elif value_case == proto_value["double"]: decoding = ConstraintType( relation_enum, constraint_type_pb.value.double ) elif category == CONSTRAINT_CATEGORY_RANGE: range_enum = ConstraintTypes.WITHIN range_case = constraint_type_pb.WhichOneof("pair") if range_case == proto_range_pairs["string"]: decoding = ConstraintType( range_enum, ( constraint_type_pb.string_pair.first, constraint_type_pb.string_pair.second, ), ) elif range_case == proto_range_pairs["integer"]: decoding = ConstraintType( range_enum, ( constraint_type_pb.integer_pair.first, constraint_type_pb.integer_pair.second, ), ) elif range_case == proto_range_pairs["double"]: decoding = ConstraintType( range_enum, ( constraint_type_pb.double_pair.first, constraint_type_pb.double_pair.second, ), ) elif category == CONSTRAINT_CATEGORY_SET: set_enum = set_type_from_pb[constraint_type_pb.operator] value_case = constraint_type_pb.values.WhichOneof("values") if value_case == proto_set_values["string"]: decoding = ConstraintType( set_enum, tuple(constraint_type_pb.values.string.values), ) elif value_case == proto_set_values["boolean"]: decoding = ConstraintType( set_enum, tuple(constraint_type_pb.values.boolean.values), ) elif value_case == proto_set_values["integer"]: decoding = ConstraintType( set_enum, tuple(constraint_type_pb.values.integer.values), ) elif value_case == proto_set_values["double"]: decoding = ConstraintType( set_enum, tuple(constraint_type_pb.values.double.values), ) elif value_case == proto_set_values["location"]: locations = [ Location.decode(loc) for loc in constraint_type_pb.values.location.values ] location_tuple = tuple(locations) decoding = ConstraintType(set_enum, location_tuple) elif category == CONSTRAINT_CATEGORY_DISTANCE: distance_enum = ConstraintTypes.DISTANCE center = Location.decode(constraint_type_pb.center) distance = constraint_type_pb.distance decoding = ConstraintType(distance_enum, (center, distance)) else: raise ValueError( f"Incorrect category. Expected either of {CONSTRAINT_CATEGORIES}. Found {category}." ) return decoding class ConstraintExpr(ABC): """Implementation of the constraint language to query the OEF node.""" @abstractmethod def check(self, description: Description) -> bool: """ Check if a description satisfies the constraint expression. :param description: the description to check. :return: True if the description satisfy the constraint expression, False otherwise. """ @abstractmethod def is_valid(self, data_model: DataModel) -> bool: """ Check whether a constraint expression is valid wrt a data model. Specifically, check the following conditions: - If all the attributes referenced by the constraints are correctly associated with the Data Model attributes. :param data_model: the data model used to check the validity of the constraint expression. :return: ``True`` if the constraint expression is valid wrt the data model, ``False`` otherwise. """ def check_validity( # noqa: B027 self, ) -> None: # pragma: nocover """ Check whether a Constraint Expression satisfies some basic requirements. :raises AEAEnforceError: if the object does not satisfy some requirements. # noqa: DAR402 """ @staticmethod def _encode(expression: Any) -> models_pb2.Query.ConstraintExpr: # type: ignore """ Encode an instance of this class into a protocol buffer object. :param expression: an expression :return: the matching protocol buffer object """ constraint_expression_pb = models_pb2.Query.ConstraintExpr() # type: ignore expression_pb = expression.encode() if isinstance(expression, And): constraint_expression_pb.and_.CopyFrom(expression_pb) elif isinstance(expression, Or): constraint_expression_pb.or_.CopyFrom(expression_pb) elif isinstance(expression, Not): constraint_expression_pb.not_.CopyFrom(expression_pb) elif isinstance(expression, Constraint): constraint_expression_pb.constraint.CopyFrom(expression_pb) else: raise ValueError( f"Invalid expression type. Expected either of 'And', 'Or', 'Not', 'Constraint'. Found {type(expression)}." ) return constraint_expression_pb @staticmethod def _decode(constraint_expression_pb: Any) -> "ConstraintExpr": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param constraint_expression_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ expression = constraint_expression_pb.WhichOneof("expression") result: Optional[Union[And, Or, Not, Constraint]] = None if expression == proto_expression["and"]: result = And.decode(constraint_expression_pb.and_) elif expression == proto_expression["or"]: result = Or.decode(constraint_expression_pb.or_) elif expression == proto_expression["not"]: result = Not.decode(constraint_expression_pb.not_) elif expression == proto_expression["constraint"]: result = Constraint.decode(constraint_expression_pb.constraint) else: # pragma: nocover raise ValueError( f"Incorrect argument. Expected either of {list(proto_expression.keys())}. Found {expression}." ) return result class And(ConstraintExpr): """Implementation of the 'And' constraint expression.""" __slots__ = ("constraints",) def __init__(self, constraints: List[ConstraintExpr]) -> None: """ Initialize an 'And' expression. :param constraints: the list of constraints expression (in conjunction). """ self.constraints = constraints self.check_validity() def check(self, description: Description) -> bool: """ Check if a value satisfies the 'And' constraint expression. :param description: the description to check. :return: True if the description satisfy the constraint expression, False otherwise. """ return all(expression.check(description) for expression in self.constraints) def is_valid(self, data_model: DataModel) -> bool: """ Check whether the constraint expression is valid wrt a data model. :param data_model: the data model used to check the validity of the constraint expression. :return: ``True`` if the constraint expression is valid wrt the data model, ``False`` otherwise. """ return all(constraint.is_valid(data_model) for constraint in self.constraints) def check_validity(self) -> None: """ Check whether the Constraint Expression satisfies some basic requirements. :return ``None`` :raises ValueError: if the object does not satisfy some requirements. """ if len(self.constraints) < 2: # pragma: nocover raise ValueError( "Invalid input value for type '{}': number of " "subexpression must be at least 2.".format(type(self).__name__) ) for constraint in self.constraints: constraint.check_validity() def __eq__(self, other: Any) -> bool: # pragma: nocover """Compare with another object.""" return isinstance(other, And) and self.constraints == other.constraints def encode(self) -> models_pb2.Query.ConstraintExpr.And: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ and_pb = models_pb2.Query.ConstraintExpr.And() # type: ignore constraint_expression_pbs = [ ConstraintExpr._encode(constraint) for constraint in self.constraints ] and_pb.expression.extend(constraint_expression_pbs) return and_pb @classmethod def decode(cls, and_pb: Any) -> "And": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param and_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ expression = [cls._decode(c) for c in and_pb.expression] return cls(expression) class Or(ConstraintExpr): """Implementation of the 'Or' constraint expression.""" __slots__ = ("constraints",) def __init__(self, constraints: List[ConstraintExpr]) -> None: """ Initialize an 'Or' expression. :param constraints: the list of constraints expressions (in disjunction). """ self.constraints = constraints self.check_validity() def check(self, description: Description) -> bool: """ Check if a value satisfies the 'Or' constraint expression. :param description: the description to check. :return: True if the description satisfy the constraint expression, False otherwise. """ return any(expression.check(description) for expression in self.constraints) def is_valid(self, data_model: DataModel) -> bool: """ Check whether the constraint expression is valid wrt a data model. :param data_model: the data model used to check the validity of the constraint expression. :return: ``True`` if the constraint expression is valid wrt the data model, ``False`` otherwise. """ return all(constraint.is_valid(data_model) for constraint in self.constraints) def check_validity(self) -> None: """ Check whether the Constraint Expression satisfies some basic requirements. :return ``None`` :raises ValueError: if the object does not satisfy some requirements. """ if len(self.constraints) < 2: # pragma: nocover raise ValueError( "Invalid input value for type '{}': number of " "subexpression must be at least 2.".format(type(self).__name__) ) for constraint in self.constraints: constraint.check_validity() def __eq__(self, other: Any) -> bool: # pragma: nocover """Compare with another object.""" return isinstance(other, Or) and self.constraints == other.constraints def encode(self) -> models_pb2.Query.ConstraintExpr.Or: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ or_pb = models_pb2.Query.ConstraintExpr.Or() # type: ignore constraint_expression_pbs = [ ConstraintExpr._encode(constraint) for constraint in self.constraints ] or_pb.expression.extend(constraint_expression_pbs) return or_pb @classmethod def decode(cls, or_pb: Any) -> "Or": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param or_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ expression = [ConstraintExpr._decode(c) for c in or_pb.expression] return cls(expression) class Not(ConstraintExpr): """Implementation of the 'Not' constraint expression.""" __slots__ = ("constraint",) def __init__(self, constraint: ConstraintExpr) -> None: """ Initialize a 'Not' expression. :param constraint: the constraint expression to negate. """ self.constraint = constraint def check(self, description: Description) -> bool: """ Check if a value satisfies the 'Not' constraint expression. :param description: the description to check. :return: True if the description satisfy the constraint expression, False otherwise. """ return not self.constraint.check(description) def is_valid(self, data_model: DataModel) -> bool: """ Check whether the constraint expression is valid wrt a data model. :param data_model: the data model used to check the validity of the constraint expression. :return: ``True`` if the constraint expression is valid wrt the data model, ``False`` otherwise. """ return self.constraint.is_valid(data_model) def __eq__(self, other: Any) -> bool: # pragma: nocover """Compare with another object.""" return isinstance(other, Not) and self.constraint == other.constraint def encode(self) -> models_pb2.Query.ConstraintExpr.Not: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ not_pb = models_pb2.Query.ConstraintExpr.Not() # type: ignore constraint_expression_pb = ConstraintExpr._encode(self.constraint) not_pb.expression.CopyFrom(constraint_expression_pb) return not_pb @classmethod def decode(cls, not_pb: Any) -> "Not": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param not_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ expression = ConstraintExpr._decode(not_pb.expression) return cls(expression) class Constraint(ConstraintExpr): """The atomic component of a constraint expression.""" __slots__ = ("attribute_name", "constraint_type") def __init__(self, attribute_name: str, constraint_type: ConstraintType) -> None: """ Initialize a constraint. :param attribute_name: the name of the attribute to be constrained. :param constraint_type: the constraint type. """ self.attribute_name = attribute_name self.constraint_type = constraint_type def check(self, description: Description) -> bool: """ Check if a description satisfies the constraint. The implementation depends on the type of the constraint. :param description: the description to check. :return: True if the description satisfies the constraint, False otherwise. Examples: >>> attr_author = Attribute("author" , str, True, "The author of the book.") >>> attr_year = Attribute("year", int, True, "The year of publication of the book.") >>> attr_genre = Attribute("genre", str, True, "The genre of the book.") >>> c1 = Constraint("author", ConstraintType("==", "Stephen King")) >>> c2 = Constraint("year", ConstraintType(">", 1990)) >>> c3 = Constraint("genre", ConstraintType("in", ("horror", "science_fiction"))) >>> book_1 = Description({"author": "Stephen King", "year": 1991, "genre": "horror"}) >>> book_2 = Description({"author": "George Orwell", "year": 1948, "genre": "horror"}) The "author" attribute instantiation satisfies the constraint, so the result is True. >>> c1.check(book_1) True Here, the "author" does not satisfy the constraints. Hence, the result is False. >>> c1.check(book_2) False In this case, there is a missing field specified by the query, that is "year" So the result is False, even in the case it is not required by the schema: >>> c2.check(Description({"author": "Stephen King"})) False If the type of some attribute of the description is not correct, the result is False. In this case, the field "year" has a string instead of an integer: >>> c2.check(Description({"author": "Stephen King", "year": "1991"})) False >>> c3.check(Description({"author": "Stephen King", "genre": False})) False """ # if the name of the attribute is not present, return false. name = self.attribute_name if name not in description.values: return False # if the type of the value is different from the type of the attribute, return false. value = description.values[name] if type(self.constraint_type.value) in {list, tuple, set} and not isinstance( value, type(next(iter(self.constraint_type.value))) ): return False if type(self.constraint_type.value) not in { list, tuple, set, } and not isinstance(value, type(self.constraint_type.value)): return False # dispatch the check to the right implementation for the concrete constraint type. return self.constraint_type.check(value) def is_valid(self, data_model: DataModel) -> bool: """ Check whether the constraint expression is valid wrt a data model. :param data_model: the data model used to check the validity of the constraint expression. :return: ``True`` if the constraint expression is valid wrt the data model, ``False`` otherwise. """ # if the attribute name of the constraint is not present in the data model, the constraint is not valid. if self.attribute_name not in data_model.attributes_by_name: return False attribute = data_model.attributes_by_name[self.attribute_name] return self.constraint_type.is_valid(attribute) def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, Constraint) and self.attribute_name == other.attribute_name and self.constraint_type == other.constraint_type ) def __str__(self) -> str: """Get the string representation of the constraint.""" return "Constraint(attribute_name={},constraint_type={})".format( self.attribute_name, self.constraint_type ) def encode(self) -> models_pb2.Query.ConstraintExpr.Constraint: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ constraint = models_pb2.Query.ConstraintExpr.Constraint() # type: ignore constraint.attribute_name = self.attribute_name if self.constraint_type.type in ( ConstraintTypes.EQUAL, ConstraintTypes.NOT_EQUAL, ConstraintTypes.LESS_THAN, ConstraintTypes.LESS_THAN_EQ, ConstraintTypes.GREATER_THAN, ConstraintTypes.GREATER_THAN_EQ, ): constraint.relation.CopyFrom(self.constraint_type.encode()) elif self.constraint_type.type == ConstraintTypes.WITHIN: constraint.range_.CopyFrom(self.constraint_type.encode()) elif self.constraint_type.type in (ConstraintTypes.IN, ConstraintTypes.NOT_IN): constraint.set_.CopyFrom(self.constraint_type.encode()) elif self.constraint_type.type == ConstraintTypes.DISTANCE: constraint.distance.CopyFrom(self.constraint_type.encode()) else: # pragma: nocover raise ValueError( f"Incorrect constraint type. Expected a ConstraintTypes. Found {self.constraint_type.type}." ) return constraint @classmethod def decode(cls, constraint_pb: Any) -> "Constraint": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param constraint_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ constraint_case = constraint_pb.WhichOneof("constraint") if constraint_case == proto_constraint["relation"]: constraint_type = ConstraintType.decode(constraint_pb.relation, "relation") elif constraint_case == proto_constraint["set"]: constraint_type = ConstraintType.decode(constraint_pb.set_, "set") elif constraint_case == proto_constraint["range"]: constraint_type = ConstraintType.decode(constraint_pb.range_, "range") elif constraint_case == proto_constraint["distance"]: constraint_type = ConstraintType.decode(constraint_pb.distance, "distance") else: raise ValueError( # pragma: nocover f"Incorrect argument. Expected either of ['relation', 'set_', 'range_', 'distance']. Found {constraint_case}." ) return cls(constraint_pb.attribute_name, constraint_type) class Query: """This class lets you build a query for the OEF.""" __slots__ = ("constraints", "model") def __init__( self, constraints: List[ConstraintExpr], model: Optional[DataModel] = None ) -> None: """ Initialize a query. :param constraints: a list of constraint expressions. :param model: the data model that the query refers to. """ self.constraints = constraints self.model = model self.check_validity() def check(self, description: Description) -> bool: """ Check if a description satisfies the constraints of the query. The constraints are interpreted as conjunction. :param description: the description to check. :return: True if the description satisfies all the constraints, False otherwise. """ return all(c.check(description) for c in self.constraints) def is_valid(self, data_model: Optional[DataModel]) -> bool: """ Given a data model, check whether the query is valid for that data model. :param data_model: optional datamodel :return: ``True`` if the query is compliant with the data model, ``False`` otherwise. """ if data_model is None: return True return all(c.is_valid(data_model) for c in self.constraints) def check_validity(self) -> None: """ Check whether the` object is valid. :return ``None`` :raises ValueError: if the query does not satisfy some sanity requirements. """ if not isinstance(self.constraints, list): raise ValueError( "Constraints must be a list (`List[Constraint]`). Instead is of type '{}'.".format( type(self.constraints).__name__ ) ) if len(self.constraints) < 1: _default_logger.warning( "DEPRECATION WARNING: " "Invalid input value for type '{}': empty list of constraints. The number of " "constraints must be at least 1.".format(type(self).__name__) ) if not self.is_valid(self.model): raise ValueError( "Invalid input value for type '{}': the query is not valid " "for the given data model.".format(type(self).__name__) ) def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, Query) and self.constraints == other.constraints and self.model == other.model ) def __str__(self) -> str: """Get the string representation of the constraint.""" return "Query(constraints={},model={})".format( [str(c) for c in self.constraints], self.model ) def _encode(self) -> models_pb2.Query.Model: # type: ignore """ Encode an instance of this class into a protocol buffer object. :return: the matching protocol buffer object """ query = models_pb2.Query.Model() # type: ignore constraint_expression_pbs = [ ConstraintExpr._encode(constraint) # pylint: disable=protected-access for constraint in self.constraints ] query.constraints.extend(constraint_expression_pbs) if self.model is not None: query.model.CopyFrom(self.model.encode()) return query @classmethod def encode(cls, query_pb: Any, query: "Query") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the query_protobuf_object argument must be matched with the instance of this class in the 'query_object' argument. :param query_pb: the protocol buffer object wrapping an object that corresponds with this class. :param query: an instance of this class to be encoded in the protocol buffer object. """ query_bytes_pb = query._encode() # pylint: disable=protected-access query_bytes_bytes = query_bytes_pb.SerializeToString() query_pb.query_bytes = query_bytes_bytes @classmethod def _decode(cls, query_pb: Any) -> "Query": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. :param query_pb: the protocol buffer object corresponding with this class. :return: A new instance of this class matching the protocol buffer object """ constraints = [ ConstraintExpr._decode(c) # pylint: disable=protected-access for c in query_pb.constraints ] data_model = DataModel.decode(query_pb.model) return cls( constraints, data_model if query_pb.HasField("model") else None, ) @classmethod def decode(cls, query_pb: Any) -> "Query": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'query_protobuf_object' argument. :param query_pb: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'query_protobuf_object' argument. """ query_bytes_pb = models_pb2.Query.Model() # type: ignore query_bytes_pb.ParseFromString(query_pb.query_bytes) query = cls._decode(query_bytes_pb) return query def haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float: """ Compute the Haversine distance between two locations (i.e. two pairs of latitude and longitude). :param lat1: the latitude of the first location. :param lon1: the longitude of the first location. :param lat2: the latitude of the second location. :param lon2: the longitude of the second location. :return: the Haversine distance. """ ( lat1, lon1, lat2, lon2, ) = map(radians, [lat1, lon1, lat2, lon2]) earth_radius = 6372.8 # average earth radius dlat = lat2 - lat1 dlon = lon2 - lon1 sin_lat_squared = sin(dlat * 0.5) * sin(dlat * 0.5) sin_lon_squared = sin(dlon * 0.5) * sin(dlon * 0.5) computation = asin(sqrt(sin_lat_squared + sin_lon_squared * cos(lat1) * cos(lat2))) distance = 2 * earth_radius * computation return distance ================================================ FILE: aea/helpers/search/models_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: models.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( name="models.proto", package="aea.helpers.search.models", syntax="proto3", serialized_options=b"H\001", create_key=_descriptor._internal_create_key, serialized_pb=b'\n\x0cmodels.proto\x12\x19\x61\x65\x61.helpers.search.models"\xc1\x19\n\x05Query\x1a\xc0\x01\n\tAttribute\x12\x0c\n\x04name\x18\x01 \x01(\t\x12=\n\x04type\x18\x02 \x01(\x0e\x32/.aea.helpers.search.models.Query.Attribute.Type\x12\x10\n\x08required\x18\x03 \x01(\x08\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t"?\n\x04Type\x12\n\n\x06\x44OUBLE\x10\x00\x12\x07\n\x03INT\x10\x01\x12\x08\n\x04\x42OOL\x10\x02\x12\n\n\x06STRING\x10\x03\x12\x0c\n\x08LOCATION\x10\x04\x1an\n\tDataModel\x12\x0c\n\x04name\x18\x01 \x01(\t\x12>\n\nattributes\x18\x02 \x03(\x0b\x32*.aea.helpers.search.models.Query.Attribute\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x1a$\n\x08Location\x12\x0b\n\x03lon\x18\x01 \x01(\x01\x12\x0b\n\x03lat\x18\x02 \x01(\x01\x1a\x99\x01\n\x05Value\x12\x10\n\x06string\x18\x01 \x01(\tH\x00\x12\x10\n\x06\x64ouble\x18\x02 \x01(\x01H\x00\x12\x11\n\x07\x62oolean\x18\x03 \x01(\x08H\x00\x12\x11\n\x07integer\x18\x04 \x01(\x03H\x00\x12=\n\x08location\x18\x05 \x01(\x0b\x32).aea.helpers.search.models.Query.LocationH\x00\x42\x07\n\x05value\x1aN\n\x08KeyValue\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x35\n\x05value\x18\x02 \x01(\x0b\x32&.aea.helpers.search.models.Query.Value\x1a\x80\x01\n\x08Instance\x12\x39\n\x05model\x18\x01 \x01(\x0b\x32*.aea.helpers.search.models.Query.DataModel\x12\x39\n\x06values\x18\x02 \x03(\x0b\x32).aea.helpers.search.models.Query.KeyValue\x1a+\n\nStringPair\x12\r\n\x05\x66irst\x18\x01 \x01(\t\x12\x0e\n\x06second\x18\x02 \x01(\t\x1a(\n\x07IntPair\x12\r\n\x05\x66irst\x18\x01 \x01(\x03\x12\x0e\n\x06second\x18\x02 \x01(\x03\x1a+\n\nDoublePair\x12\r\n\x05\x66irst\x18\x01 \x01(\x01\x12\x0e\n\x06second\x18\x02 \x01(\x01\x1a\x83\x01\n\x0cLocationPair\x12\x38\n\x05\x66irst\x18\x01 \x01(\x0b\x32).aea.helpers.search.models.Query.Location\x12\x39\n\x06second\x18\x02 \x01(\x0b\x32).aea.helpers.search.models.Query.Location\x1a\xa1\x02\n\x05Range\x12\x42\n\x0bstring_pair\x18\x01 \x01(\x0b\x32+.aea.helpers.search.models.Query.StringPairH\x00\x12@\n\x0cinteger_pair\x18\x02 \x01(\x0b\x32(.aea.helpers.search.models.Query.IntPairH\x00\x12\x42\n\x0b\x64ouble_pair\x18\x03 \x01(\x0b\x32+.aea.helpers.search.models.Query.DoublePairH\x00\x12\x46\n\rlocation_pair\x18\x04 \x01(\x0b\x32-.aea.helpers.search.models.Query.LocationPairH\x00\x42\x06\n\x04pair\x1aW\n\x08\x44istance\x12\x39\n\x06\x63\x65nter\x18\x01 \x01(\x0b\x32).aea.helpers.search.models.Query.Location\x12\x10\n\x08\x64istance\x18\x02 \x01(\x01\x1a\xca\x01\n\x08Relation\x12\x44\n\x08operator\x18\x01 \x01(\x0e\x32\x32.aea.helpers.search.models.Query.Relation.Operator\x12\x35\n\x05value\x18\x02 \x01(\x0b\x32&.aea.helpers.search.models.Query.Value"A\n\x08Operator\x12\x06\n\x02\x45Q\x10\x00\x12\x06\n\x02LT\x10\x01\x12\x08\n\x04LTEQ\x10\x02\x12\x06\n\x02GT\x10\x03\x12\x08\n\x04GTEQ\x10\x04\x12\t\n\x05NOTEQ\x10\x05\x1a\xca\x05\n\x03Set\x12?\n\x08operator\x18\x01 \x01(\x0e\x32-.aea.helpers.search.models.Query.Set.Operator\x12;\n\x06values\x18\x02 \x01(\x0b\x32+.aea.helpers.search.models.Query.Set.Values\x1a\xa5\x04\n\x06Values\x12\x45\n\x06string\x18\x01 \x01(\x0b\x32\x33.aea.helpers.search.models.Query.Set.Values.StringsH\x00\x12\x45\n\x06\x64ouble\x18\x02 \x01(\x0b\x32\x33.aea.helpers.search.models.Query.Set.Values.DoublesH\x00\x12\x44\n\x07\x62oolean\x18\x03 \x01(\x0b\x32\x31.aea.helpers.search.models.Query.Set.Values.BoolsH\x00\x12\x43\n\x07integer\x18\x04 \x01(\x0b\x32\x30.aea.helpers.search.models.Query.Set.Values.IntsH\x00\x12I\n\x08location\x18\x05 \x01(\x0b\x32\x35.aea.helpers.search.models.Query.Set.Values.LocationsH\x00\x1a\x16\n\x04Ints\x12\x0e\n\x06values\x18\x01 \x03(\x03\x1a\x19\n\x07\x44oubles\x12\x0e\n\x06values\x18\x01 \x03(\x01\x1a\x19\n\x07Strings\x12\x0e\n\x06values\x18\x01 \x03(\t\x1a\x17\n\x05\x42ools\x12\x0e\n\x06values\x18\x01 \x03(\x08\x1a\x46\n\tLocations\x12\x39\n\x06values\x18\x01 \x03(\x0b\x32).aea.helpers.search.models.Query.LocationB\x08\n\x06values"\x1d\n\x08Operator\x12\x06\n\x02IN\x10\x00\x12\t\n\x05NOTIN\x10\x01\x1a\xc3\x06\n\x0e\x43onstraintExpr\x12\x41\n\x03or_\x18\x01 \x01(\x0b\x32\x32.aea.helpers.search.models.Query.ConstraintExpr.OrH\x00\x12\x43\n\x04\x61nd_\x18\x02 \x01(\x0b\x32\x33.aea.helpers.search.models.Query.ConstraintExpr.AndH\x00\x12\x43\n\x04not_\x18\x03 \x01(\x0b\x32\x33.aea.helpers.search.models.Query.ConstraintExpr.NotH\x00\x12P\n\nconstraint\x18\x04 \x01(\x0b\x32:.aea.helpers.search.models.Query.ConstraintExpr.ConstraintH\x00\x1aI\n\x02Or\x12\x43\n\nexpression\x18\x01 \x03(\x0b\x32/.aea.helpers.search.models.Query.ConstraintExpr\x1aJ\n\x03\x41nd\x12\x43\n\nexpression\x18\x01 \x03(\x0b\x32/.aea.helpers.search.models.Query.ConstraintExpr\x1aJ\n\x03Not\x12\x43\n\nexpression\x18\x01 \x01(\x0b\x32/.aea.helpers.search.models.Query.ConstraintExpr\x1a\xa0\x02\n\nConstraint\x12\x16\n\x0e\x61ttribute_name\x18\x01 \x01(\t\x12\x34\n\x04set_\x18\x02 \x01(\x0b\x32$.aea.helpers.search.models.Query.SetH\x00\x12\x38\n\x06range_\x18\x03 \x01(\x0b\x32&.aea.helpers.search.models.Query.RangeH\x00\x12=\n\x08relation\x18\x04 \x01(\x0b\x32).aea.helpers.search.models.Query.RelationH\x00\x12=\n\x08\x64istance\x18\x05 \x01(\x0b\x32).aea.helpers.search.models.Query.DistanceH\x00\x42\x0c\n\nconstraintB\x0c\n\nexpression\x1a\x88\x01\n\x05Model\x12\x44\n\x0b\x63onstraints\x18\x01 \x03(\x0b\x32/.aea.helpers.search.models.Query.ConstraintExpr\x12\x39\n\x05model\x18\x02 \x01(\x0b\x32*.aea.helpers.search.models.Query.DataModelB\x02H\x01\x62\x06proto3', ) _QUERY_ATTRIBUTE_TYPE = _descriptor.EnumDescriptor( name="Type", full_name="aea.helpers.search.models.Query.Attribute.Type", filename=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name="DOUBLE", index=0, number=0, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="INT", index=1, number=1, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="BOOL", index=2, number=2, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="STRING", index=3, number=3, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="LOCATION", index=4, number=4, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), ], containing_type=None, serialized_options=None, serialized_start=183, serialized_end=246, ) _sym_db.RegisterEnumDescriptor(_QUERY_ATTRIBUTE_TYPE) _QUERY_RELATION_OPERATOR = _descriptor.EnumDescriptor( name="Operator", full_name="aea.helpers.search.models.Query.Relation.Operator", filename=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name="EQ", index=0, number=0, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="LT", index=1, number=1, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="LTEQ", index=2, number=2, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="GT", index=3, number=3, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="GTEQ", index=4, number=4, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="NOTEQ", index=5, number=5, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), ], containing_type=None, serialized_options=None, serialized_start=1550, serialized_end=1615, ) _sym_db.RegisterEnumDescriptor(_QUERY_RELATION_OPERATOR) _QUERY_SET_OPERATOR = _descriptor.EnumDescriptor( name="Operator", full_name="aea.helpers.search.models.Query.Set.Operator", filename=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name="IN", index=0, number=0, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), _descriptor.EnumValueDescriptor( name="NOTIN", index=1, number=1, serialized_options=None, type=None, create_key=_descriptor._internal_create_key, ), ], containing_type=None, serialized_options=None, serialized_start=2303, serialized_end=2332, ) _sym_db.RegisterEnumDescriptor(_QUERY_SET_OPERATOR) _QUERY_ATTRIBUTE = _descriptor.Descriptor( name="Attribute", full_name="aea.helpers.search.models.Query.Attribute", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="name", full_name="aea.helpers.search.models.Query.Attribute.name", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="type", full_name="aea.helpers.search.models.Query.Attribute.type", index=1, number=2, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="required", full_name="aea.helpers.search.models.Query.Attribute.required", index=2, number=3, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="description", full_name="aea.helpers.search.models.Query.Attribute.description", index=3, number=4, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[ _QUERY_ATTRIBUTE_TYPE, ], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=54, serialized_end=246, ) _QUERY_DATAMODEL = _descriptor.Descriptor( name="DataModel", full_name="aea.helpers.search.models.Query.DataModel", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="name", full_name="aea.helpers.search.models.Query.DataModel.name", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="attributes", full_name="aea.helpers.search.models.Query.DataModel.attributes", index=1, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="description", full_name="aea.helpers.search.models.Query.DataModel.description", index=2, number=3, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=248, serialized_end=358, ) _QUERY_LOCATION = _descriptor.Descriptor( name="Location", full_name="aea.helpers.search.models.Query.Location", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="lon", full_name="aea.helpers.search.models.Query.Location.lon", index=0, number=1, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="lat", full_name="aea.helpers.search.models.Query.Location.lat", index=1, number=2, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=360, serialized_end=396, ) _QUERY_VALUE = _descriptor.Descriptor( name="Value", full_name="aea.helpers.search.models.Query.Value", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="string", full_name="aea.helpers.search.models.Query.Value.string", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="double", full_name="aea.helpers.search.models.Query.Value.double", index=1, number=2, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="boolean", full_name="aea.helpers.search.models.Query.Value.boolean", index=2, number=3, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="integer", full_name="aea.helpers.search.models.Query.Value.integer", index=3, number=4, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="location", full_name="aea.helpers.search.models.Query.Value.location", index=4, number=5, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[ _descriptor.OneofDescriptor( name="value", full_name="aea.helpers.search.models.Query.Value.value", index=0, containing_type=None, create_key=_descriptor._internal_create_key, fields=[], ), ], serialized_start=399, serialized_end=552, ) _QUERY_KEYVALUE = _descriptor.Descriptor( name="KeyValue", full_name="aea.helpers.search.models.Query.KeyValue", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="key", full_name="aea.helpers.search.models.Query.KeyValue.key", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="value", full_name="aea.helpers.search.models.Query.KeyValue.value", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=554, serialized_end=632, ) _QUERY_INSTANCE = _descriptor.Descriptor( name="Instance", full_name="aea.helpers.search.models.Query.Instance", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="model", full_name="aea.helpers.search.models.Query.Instance.model", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="values", full_name="aea.helpers.search.models.Query.Instance.values", index=1, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=635, serialized_end=763, ) _QUERY_STRINGPAIR = _descriptor.Descriptor( name="StringPair", full_name="aea.helpers.search.models.Query.StringPair", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="first", full_name="aea.helpers.search.models.Query.StringPair.first", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="second", full_name="aea.helpers.search.models.Query.StringPair.second", index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=765, serialized_end=808, ) _QUERY_INTPAIR = _descriptor.Descriptor( name="IntPair", full_name="aea.helpers.search.models.Query.IntPair", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="first", full_name="aea.helpers.search.models.Query.IntPair.first", index=0, number=1, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="second", full_name="aea.helpers.search.models.Query.IntPair.second", index=1, number=2, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=810, serialized_end=850, ) _QUERY_DOUBLEPAIR = _descriptor.Descriptor( name="DoublePair", full_name="aea.helpers.search.models.Query.DoublePair", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="first", full_name="aea.helpers.search.models.Query.DoublePair.first", index=0, number=1, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="second", full_name="aea.helpers.search.models.Query.DoublePair.second", index=1, number=2, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=852, serialized_end=895, ) _QUERY_LOCATIONPAIR = _descriptor.Descriptor( name="LocationPair", full_name="aea.helpers.search.models.Query.LocationPair", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="first", full_name="aea.helpers.search.models.Query.LocationPair.first", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="second", full_name="aea.helpers.search.models.Query.LocationPair.second", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=898, serialized_end=1029, ) _QUERY_RANGE = _descriptor.Descriptor( name="Range", full_name="aea.helpers.search.models.Query.Range", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="string_pair", full_name="aea.helpers.search.models.Query.Range.string_pair", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="integer_pair", full_name="aea.helpers.search.models.Query.Range.integer_pair", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="double_pair", full_name="aea.helpers.search.models.Query.Range.double_pair", index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="location_pair", full_name="aea.helpers.search.models.Query.Range.location_pair", index=3, number=4, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[ _descriptor.OneofDescriptor( name="pair", full_name="aea.helpers.search.models.Query.Range.pair", index=0, containing_type=None, create_key=_descriptor._internal_create_key, fields=[], ), ], serialized_start=1032, serialized_end=1321, ) _QUERY_DISTANCE = _descriptor.Descriptor( name="Distance", full_name="aea.helpers.search.models.Query.Distance", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="center", full_name="aea.helpers.search.models.Query.Distance.center", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="distance", full_name="aea.helpers.search.models.Query.Distance.distance", index=1, number=2, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=1323, serialized_end=1410, ) _QUERY_RELATION = _descriptor.Descriptor( name="Relation", full_name="aea.helpers.search.models.Query.Relation", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="operator", full_name="aea.helpers.search.models.Query.Relation.operator", index=0, number=1, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="value", full_name="aea.helpers.search.models.Query.Relation.value", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[ _QUERY_RELATION_OPERATOR, ], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=1413, serialized_end=1615, ) _QUERY_SET_VALUES_INTS = _descriptor.Descriptor( name="Ints", full_name="aea.helpers.search.models.Query.Set.Values.Ints", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="values", full_name="aea.helpers.search.models.Query.Set.Values.Ints.values", index=0, number=1, type=3, cpp_type=2, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2118, serialized_end=2140, ) _QUERY_SET_VALUES_DOUBLES = _descriptor.Descriptor( name="Doubles", full_name="aea.helpers.search.models.Query.Set.Values.Doubles", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="values", full_name="aea.helpers.search.models.Query.Set.Values.Doubles.values", index=0, number=1, type=1, cpp_type=5, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2142, serialized_end=2167, ) _QUERY_SET_VALUES_STRINGS = _descriptor.Descriptor( name="Strings", full_name="aea.helpers.search.models.Query.Set.Values.Strings", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="values", full_name="aea.helpers.search.models.Query.Set.Values.Strings.values", index=0, number=1, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2169, serialized_end=2194, ) _QUERY_SET_VALUES_BOOLS = _descriptor.Descriptor( name="Bools", full_name="aea.helpers.search.models.Query.Set.Values.Bools", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="values", full_name="aea.helpers.search.models.Query.Set.Values.Bools.values", index=0, number=1, type=8, cpp_type=7, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2196, serialized_end=2219, ) _QUERY_SET_VALUES_LOCATIONS = _descriptor.Descriptor( name="Locations", full_name="aea.helpers.search.models.Query.Set.Values.Locations", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="values", full_name="aea.helpers.search.models.Query.Set.Values.Locations.values", index=0, number=1, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2221, serialized_end=2291, ) _QUERY_SET_VALUES = _descriptor.Descriptor( name="Values", full_name="aea.helpers.search.models.Query.Set.Values", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="string", full_name="aea.helpers.search.models.Query.Set.Values.string", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="double", full_name="aea.helpers.search.models.Query.Set.Values.double", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="boolean", full_name="aea.helpers.search.models.Query.Set.Values.boolean", index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="integer", full_name="aea.helpers.search.models.Query.Set.Values.integer", index=3, number=4, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="location", full_name="aea.helpers.search.models.Query.Set.Values.location", index=4, number=5, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[ _QUERY_SET_VALUES_INTS, _QUERY_SET_VALUES_DOUBLES, _QUERY_SET_VALUES_STRINGS, _QUERY_SET_VALUES_BOOLS, _QUERY_SET_VALUES_LOCATIONS, ], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[ _descriptor.OneofDescriptor( name="values", full_name="aea.helpers.search.models.Query.Set.Values.values", index=0, containing_type=None, create_key=_descriptor._internal_create_key, fields=[], ), ], serialized_start=1752, serialized_end=2301, ) _QUERY_SET = _descriptor.Descriptor( name="Set", full_name="aea.helpers.search.models.Query.Set", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="operator", full_name="aea.helpers.search.models.Query.Set.operator", index=0, number=1, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="values", full_name="aea.helpers.search.models.Query.Set.values", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[ _QUERY_SET_VALUES, ], enum_types=[ _QUERY_SET_OPERATOR, ], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=1618, serialized_end=2332, ) _QUERY_CONSTRAINTEXPR_OR = _descriptor.Descriptor( name="Or", full_name="aea.helpers.search.models.Query.ConstraintExpr.Or", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="expression", full_name="aea.helpers.search.models.Query.ConstraintExpr.Or.expression", index=0, number=1, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2640, serialized_end=2713, ) _QUERY_CONSTRAINTEXPR_AND = _descriptor.Descriptor( name="And", full_name="aea.helpers.search.models.Query.ConstraintExpr.And", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="expression", full_name="aea.helpers.search.models.Query.ConstraintExpr.And.expression", index=0, number=1, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2715, serialized_end=2789, ) _QUERY_CONSTRAINTEXPR_NOT = _descriptor.Descriptor( name="Not", full_name="aea.helpers.search.models.Query.ConstraintExpr.Not", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="expression", full_name="aea.helpers.search.models.Query.ConstraintExpr.Not.expression", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=2791, serialized_end=2865, ) _QUERY_CONSTRAINTEXPR_CONSTRAINT = _descriptor.Descriptor( name="Constraint", full_name="aea.helpers.search.models.Query.ConstraintExpr.Constraint", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="attribute_name", full_name="aea.helpers.search.models.Query.ConstraintExpr.Constraint.attribute_name", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="set_", full_name="aea.helpers.search.models.Query.ConstraintExpr.Constraint.set_", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="range_", full_name="aea.helpers.search.models.Query.ConstraintExpr.Constraint.range_", index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="relation", full_name="aea.helpers.search.models.Query.ConstraintExpr.Constraint.relation", index=3, number=4, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="distance", full_name="aea.helpers.search.models.Query.ConstraintExpr.Constraint.distance", index=4, number=5, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[ _descriptor.OneofDescriptor( name="constraint", full_name="aea.helpers.search.models.Query.ConstraintExpr.Constraint.constraint", index=0, containing_type=None, create_key=_descriptor._internal_create_key, fields=[], ), ], serialized_start=2868, serialized_end=3156, ) _QUERY_CONSTRAINTEXPR = _descriptor.Descriptor( name="ConstraintExpr", full_name="aea.helpers.search.models.Query.ConstraintExpr", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="or_", full_name="aea.helpers.search.models.Query.ConstraintExpr.or_", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="and_", full_name="aea.helpers.search.models.Query.ConstraintExpr.and_", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="not_", full_name="aea.helpers.search.models.Query.ConstraintExpr.not_", index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="constraint", full_name="aea.helpers.search.models.Query.ConstraintExpr.constraint", index=3, number=4, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[ _QUERY_CONSTRAINTEXPR_OR, _QUERY_CONSTRAINTEXPR_AND, _QUERY_CONSTRAINTEXPR_NOT, _QUERY_CONSTRAINTEXPR_CONSTRAINT, ], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[ _descriptor.OneofDescriptor( name="expression", full_name="aea.helpers.search.models.Query.ConstraintExpr.expression", index=0, containing_type=None, create_key=_descriptor._internal_create_key, fields=[], ), ], serialized_start=2335, serialized_end=3170, ) _QUERY_MODEL = _descriptor.Descriptor( name="Model", full_name="aea.helpers.search.models.Query.Model", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="constraints", full_name="aea.helpers.search.models.Query.Model.constraints", index=0, number=1, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="model", full_name="aea.helpers.search.models.Query.Model.model", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=3173, serialized_end=3309, ) _QUERY = _descriptor.Descriptor( name="Query", full_name="aea.helpers.search.models.Query", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[], extensions=[], nested_types=[ _QUERY_ATTRIBUTE, _QUERY_DATAMODEL, _QUERY_LOCATION, _QUERY_VALUE, _QUERY_KEYVALUE, _QUERY_INSTANCE, _QUERY_STRINGPAIR, _QUERY_INTPAIR, _QUERY_DOUBLEPAIR, _QUERY_LOCATIONPAIR, _QUERY_RANGE, _QUERY_DISTANCE, _QUERY_RELATION, _QUERY_SET, _QUERY_CONSTRAINTEXPR, _QUERY_MODEL, ], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=44, serialized_end=3309, ) _QUERY_ATTRIBUTE.fields_by_name["type"].enum_type = _QUERY_ATTRIBUTE_TYPE _QUERY_ATTRIBUTE.containing_type = _QUERY _QUERY_ATTRIBUTE_TYPE.containing_type = _QUERY_ATTRIBUTE _QUERY_DATAMODEL.fields_by_name["attributes"].message_type = _QUERY_ATTRIBUTE _QUERY_DATAMODEL.containing_type = _QUERY _QUERY_LOCATION.containing_type = _QUERY _QUERY_VALUE.fields_by_name["location"].message_type = _QUERY_LOCATION _QUERY_VALUE.containing_type = _QUERY _QUERY_VALUE.oneofs_by_name["value"].fields.append( _QUERY_VALUE.fields_by_name["string"] ) _QUERY_VALUE.fields_by_name["string"].containing_oneof = _QUERY_VALUE.oneofs_by_name[ "value" ] _QUERY_VALUE.oneofs_by_name["value"].fields.append( _QUERY_VALUE.fields_by_name["double"] ) _QUERY_VALUE.fields_by_name["double"].containing_oneof = _QUERY_VALUE.oneofs_by_name[ "value" ] _QUERY_VALUE.oneofs_by_name["value"].fields.append( _QUERY_VALUE.fields_by_name["boolean"] ) _QUERY_VALUE.fields_by_name["boolean"].containing_oneof = _QUERY_VALUE.oneofs_by_name[ "value" ] _QUERY_VALUE.oneofs_by_name["value"].fields.append( _QUERY_VALUE.fields_by_name["integer"] ) _QUERY_VALUE.fields_by_name["integer"].containing_oneof = _QUERY_VALUE.oneofs_by_name[ "value" ] _QUERY_VALUE.oneofs_by_name["value"].fields.append( _QUERY_VALUE.fields_by_name["location"] ) _QUERY_VALUE.fields_by_name["location"].containing_oneof = _QUERY_VALUE.oneofs_by_name[ "value" ] _QUERY_KEYVALUE.fields_by_name["value"].message_type = _QUERY_VALUE _QUERY_KEYVALUE.containing_type = _QUERY _QUERY_INSTANCE.fields_by_name["model"].message_type = _QUERY_DATAMODEL _QUERY_INSTANCE.fields_by_name["values"].message_type = _QUERY_KEYVALUE _QUERY_INSTANCE.containing_type = _QUERY _QUERY_STRINGPAIR.containing_type = _QUERY _QUERY_INTPAIR.containing_type = _QUERY _QUERY_DOUBLEPAIR.containing_type = _QUERY _QUERY_LOCATIONPAIR.fields_by_name["first"].message_type = _QUERY_LOCATION _QUERY_LOCATIONPAIR.fields_by_name["second"].message_type = _QUERY_LOCATION _QUERY_LOCATIONPAIR.containing_type = _QUERY _QUERY_RANGE.fields_by_name["string_pair"].message_type = _QUERY_STRINGPAIR _QUERY_RANGE.fields_by_name["integer_pair"].message_type = _QUERY_INTPAIR _QUERY_RANGE.fields_by_name["double_pair"].message_type = _QUERY_DOUBLEPAIR _QUERY_RANGE.fields_by_name["location_pair"].message_type = _QUERY_LOCATIONPAIR _QUERY_RANGE.containing_type = _QUERY _QUERY_RANGE.oneofs_by_name["pair"].fields.append( _QUERY_RANGE.fields_by_name["string_pair"] ) _QUERY_RANGE.fields_by_name[ "string_pair" ].containing_oneof = _QUERY_RANGE.oneofs_by_name["pair"] _QUERY_RANGE.oneofs_by_name["pair"].fields.append( _QUERY_RANGE.fields_by_name["integer_pair"] ) _QUERY_RANGE.fields_by_name[ "integer_pair" ].containing_oneof = _QUERY_RANGE.oneofs_by_name["pair"] _QUERY_RANGE.oneofs_by_name["pair"].fields.append( _QUERY_RANGE.fields_by_name["double_pair"] ) _QUERY_RANGE.fields_by_name[ "double_pair" ].containing_oneof = _QUERY_RANGE.oneofs_by_name["pair"] _QUERY_RANGE.oneofs_by_name["pair"].fields.append( _QUERY_RANGE.fields_by_name["location_pair"] ) _QUERY_RANGE.fields_by_name[ "location_pair" ].containing_oneof = _QUERY_RANGE.oneofs_by_name["pair"] _QUERY_DISTANCE.fields_by_name["center"].message_type = _QUERY_LOCATION _QUERY_DISTANCE.containing_type = _QUERY _QUERY_RELATION.fields_by_name["operator"].enum_type = _QUERY_RELATION_OPERATOR _QUERY_RELATION.fields_by_name["value"].message_type = _QUERY_VALUE _QUERY_RELATION.containing_type = _QUERY _QUERY_RELATION_OPERATOR.containing_type = _QUERY_RELATION _QUERY_SET_VALUES_INTS.containing_type = _QUERY_SET_VALUES _QUERY_SET_VALUES_DOUBLES.containing_type = _QUERY_SET_VALUES _QUERY_SET_VALUES_STRINGS.containing_type = _QUERY_SET_VALUES _QUERY_SET_VALUES_BOOLS.containing_type = _QUERY_SET_VALUES _QUERY_SET_VALUES_LOCATIONS.fields_by_name["values"].message_type = _QUERY_LOCATION _QUERY_SET_VALUES_LOCATIONS.containing_type = _QUERY_SET_VALUES _QUERY_SET_VALUES.fields_by_name["string"].message_type = _QUERY_SET_VALUES_STRINGS _QUERY_SET_VALUES.fields_by_name["double"].message_type = _QUERY_SET_VALUES_DOUBLES _QUERY_SET_VALUES.fields_by_name["boolean"].message_type = _QUERY_SET_VALUES_BOOLS _QUERY_SET_VALUES.fields_by_name["integer"].message_type = _QUERY_SET_VALUES_INTS _QUERY_SET_VALUES.fields_by_name["location"].message_type = _QUERY_SET_VALUES_LOCATIONS _QUERY_SET_VALUES.containing_type = _QUERY_SET _QUERY_SET_VALUES.oneofs_by_name["values"].fields.append( _QUERY_SET_VALUES.fields_by_name["string"] ) _QUERY_SET_VALUES.fields_by_name[ "string" ].containing_oneof = _QUERY_SET_VALUES.oneofs_by_name["values"] _QUERY_SET_VALUES.oneofs_by_name["values"].fields.append( _QUERY_SET_VALUES.fields_by_name["double"] ) _QUERY_SET_VALUES.fields_by_name[ "double" ].containing_oneof = _QUERY_SET_VALUES.oneofs_by_name["values"] _QUERY_SET_VALUES.oneofs_by_name["values"].fields.append( _QUERY_SET_VALUES.fields_by_name["boolean"] ) _QUERY_SET_VALUES.fields_by_name[ "boolean" ].containing_oneof = _QUERY_SET_VALUES.oneofs_by_name["values"] _QUERY_SET_VALUES.oneofs_by_name["values"].fields.append( _QUERY_SET_VALUES.fields_by_name["integer"] ) _QUERY_SET_VALUES.fields_by_name[ "integer" ].containing_oneof = _QUERY_SET_VALUES.oneofs_by_name["values"] _QUERY_SET_VALUES.oneofs_by_name["values"].fields.append( _QUERY_SET_VALUES.fields_by_name["location"] ) _QUERY_SET_VALUES.fields_by_name[ "location" ].containing_oneof = _QUERY_SET_VALUES.oneofs_by_name["values"] _QUERY_SET.fields_by_name["operator"].enum_type = _QUERY_SET_OPERATOR _QUERY_SET.fields_by_name["values"].message_type = _QUERY_SET_VALUES _QUERY_SET.containing_type = _QUERY _QUERY_SET_OPERATOR.containing_type = _QUERY_SET _QUERY_CONSTRAINTEXPR_OR.fields_by_name[ "expression" ].message_type = _QUERY_CONSTRAINTEXPR _QUERY_CONSTRAINTEXPR_OR.containing_type = _QUERY_CONSTRAINTEXPR _QUERY_CONSTRAINTEXPR_AND.fields_by_name[ "expression" ].message_type = _QUERY_CONSTRAINTEXPR _QUERY_CONSTRAINTEXPR_AND.containing_type = _QUERY_CONSTRAINTEXPR _QUERY_CONSTRAINTEXPR_NOT.fields_by_name[ "expression" ].message_type = _QUERY_CONSTRAINTEXPR _QUERY_CONSTRAINTEXPR_NOT.containing_type = _QUERY_CONSTRAINTEXPR _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name["set_"].message_type = _QUERY_SET _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name["range_"].message_type = _QUERY_RANGE _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name[ "relation" ].message_type = _QUERY_RELATION _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name[ "distance" ].message_type = _QUERY_DISTANCE _QUERY_CONSTRAINTEXPR_CONSTRAINT.containing_type = _QUERY_CONSTRAINTEXPR _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"].fields.append( _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name["set_"] ) _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name[ "set_" ].containing_oneof = _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"] _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"].fields.append( _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name["range_"] ) _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name[ "range_" ].containing_oneof = _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"] _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"].fields.append( _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name["relation"] ) _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name[ "relation" ].containing_oneof = _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"] _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"].fields.append( _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name["distance"] ) _QUERY_CONSTRAINTEXPR_CONSTRAINT.fields_by_name[ "distance" ].containing_oneof = _QUERY_CONSTRAINTEXPR_CONSTRAINT.oneofs_by_name["constraint"] _QUERY_CONSTRAINTEXPR.fields_by_name["or_"].message_type = _QUERY_CONSTRAINTEXPR_OR _QUERY_CONSTRAINTEXPR.fields_by_name["and_"].message_type = _QUERY_CONSTRAINTEXPR_AND _QUERY_CONSTRAINTEXPR.fields_by_name["not_"].message_type = _QUERY_CONSTRAINTEXPR_NOT _QUERY_CONSTRAINTEXPR.fields_by_name[ "constraint" ].message_type = _QUERY_CONSTRAINTEXPR_CONSTRAINT _QUERY_CONSTRAINTEXPR.containing_type = _QUERY _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"].fields.append( _QUERY_CONSTRAINTEXPR.fields_by_name["or_"] ) _QUERY_CONSTRAINTEXPR.fields_by_name[ "or_" ].containing_oneof = _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"] _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"].fields.append( _QUERY_CONSTRAINTEXPR.fields_by_name["and_"] ) _QUERY_CONSTRAINTEXPR.fields_by_name[ "and_" ].containing_oneof = _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"] _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"].fields.append( _QUERY_CONSTRAINTEXPR.fields_by_name["not_"] ) _QUERY_CONSTRAINTEXPR.fields_by_name[ "not_" ].containing_oneof = _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"] _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"].fields.append( _QUERY_CONSTRAINTEXPR.fields_by_name["constraint"] ) _QUERY_CONSTRAINTEXPR.fields_by_name[ "constraint" ].containing_oneof = _QUERY_CONSTRAINTEXPR.oneofs_by_name["expression"] _QUERY_MODEL.fields_by_name["constraints"].message_type = _QUERY_CONSTRAINTEXPR _QUERY_MODEL.fields_by_name["model"].message_type = _QUERY_DATAMODEL _QUERY_MODEL.containing_type = _QUERY DESCRIPTOR.message_types_by_name["Query"] = _QUERY _sym_db.RegisterFileDescriptor(DESCRIPTOR) Query = _reflection.GeneratedProtocolMessageType( "Query", (_message.Message,), { "Attribute": _reflection.GeneratedProtocolMessageType( "Attribute", (_message.Message,), { "DESCRIPTOR": _QUERY_ATTRIBUTE, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Attribute) }, ), "DataModel": _reflection.GeneratedProtocolMessageType( "DataModel", (_message.Message,), { "DESCRIPTOR": _QUERY_DATAMODEL, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.DataModel) }, ), "Location": _reflection.GeneratedProtocolMessageType( "Location", (_message.Message,), { "DESCRIPTOR": _QUERY_LOCATION, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Location) }, ), "Value": _reflection.GeneratedProtocolMessageType( "Value", (_message.Message,), { "DESCRIPTOR": _QUERY_VALUE, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Value) }, ), "KeyValue": _reflection.GeneratedProtocolMessageType( "KeyValue", (_message.Message,), { "DESCRIPTOR": _QUERY_KEYVALUE, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.KeyValue) }, ), "Instance": _reflection.GeneratedProtocolMessageType( "Instance", (_message.Message,), { "DESCRIPTOR": _QUERY_INSTANCE, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Instance) }, ), "StringPair": _reflection.GeneratedProtocolMessageType( "StringPair", (_message.Message,), { "DESCRIPTOR": _QUERY_STRINGPAIR, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.StringPair) }, ), "IntPair": _reflection.GeneratedProtocolMessageType( "IntPair", (_message.Message,), { "DESCRIPTOR": _QUERY_INTPAIR, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.IntPair) }, ), "DoublePair": _reflection.GeneratedProtocolMessageType( "DoublePair", (_message.Message,), { "DESCRIPTOR": _QUERY_DOUBLEPAIR, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.DoublePair) }, ), "LocationPair": _reflection.GeneratedProtocolMessageType( "LocationPair", (_message.Message,), { "DESCRIPTOR": _QUERY_LOCATIONPAIR, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.LocationPair) }, ), "Range": _reflection.GeneratedProtocolMessageType( "Range", (_message.Message,), { "DESCRIPTOR": _QUERY_RANGE, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Range) }, ), "Distance": _reflection.GeneratedProtocolMessageType( "Distance", (_message.Message,), { "DESCRIPTOR": _QUERY_DISTANCE, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Distance) }, ), "Relation": _reflection.GeneratedProtocolMessageType( "Relation", (_message.Message,), { "DESCRIPTOR": _QUERY_RELATION, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Relation) }, ), "Set": _reflection.GeneratedProtocolMessageType( "Set", (_message.Message,), { "Values": _reflection.GeneratedProtocolMessageType( "Values", (_message.Message,), { "Ints": _reflection.GeneratedProtocolMessageType( "Ints", (_message.Message,), { "DESCRIPTOR": _QUERY_SET_VALUES_INTS, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Set.Values.Ints) }, ), "Doubles": _reflection.GeneratedProtocolMessageType( "Doubles", (_message.Message,), { "DESCRIPTOR": _QUERY_SET_VALUES_DOUBLES, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Set.Values.Doubles) }, ), "Strings": _reflection.GeneratedProtocolMessageType( "Strings", (_message.Message,), { "DESCRIPTOR": _QUERY_SET_VALUES_STRINGS, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Set.Values.Strings) }, ), "Bools": _reflection.GeneratedProtocolMessageType( "Bools", (_message.Message,), { "DESCRIPTOR": _QUERY_SET_VALUES_BOOLS, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Set.Values.Bools) }, ), "Locations": _reflection.GeneratedProtocolMessageType( "Locations", (_message.Message,), { "DESCRIPTOR": _QUERY_SET_VALUES_LOCATIONS, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Set.Values.Locations) }, ), "DESCRIPTOR": _QUERY_SET_VALUES, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Set.Values) }, ), "DESCRIPTOR": _QUERY_SET, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Set) }, ), "ConstraintExpr": _reflection.GeneratedProtocolMessageType( "ConstraintExpr", (_message.Message,), { "Or": _reflection.GeneratedProtocolMessageType( "Or", (_message.Message,), { "DESCRIPTOR": _QUERY_CONSTRAINTEXPR_OR, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.ConstraintExpr.Or) }, ), "And": _reflection.GeneratedProtocolMessageType( "And", (_message.Message,), { "DESCRIPTOR": _QUERY_CONSTRAINTEXPR_AND, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.ConstraintExpr.And) }, ), "Not": _reflection.GeneratedProtocolMessageType( "Not", (_message.Message,), { "DESCRIPTOR": _QUERY_CONSTRAINTEXPR_NOT, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.ConstraintExpr.Not) }, ), "Constraint": _reflection.GeneratedProtocolMessageType( "Constraint", (_message.Message,), { "DESCRIPTOR": _QUERY_CONSTRAINTEXPR_CONSTRAINT, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.ConstraintExpr.Constraint) }, ), "DESCRIPTOR": _QUERY_CONSTRAINTEXPR, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.ConstraintExpr) }, ), "Model": _reflection.GeneratedProtocolMessageType( "Model", (_message.Message,), { "DESCRIPTOR": _QUERY_MODEL, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query.Model) }, ), "DESCRIPTOR": _QUERY, "__module__": "models_pb2" # @@protoc_insertion_point(class_scope:aea.helpers.search.models.Query) }, ) _sym_db.RegisterMessage(Query) _sym_db.RegisterMessage(Query.Attribute) _sym_db.RegisterMessage(Query.DataModel) _sym_db.RegisterMessage(Query.Location) _sym_db.RegisterMessage(Query.Value) _sym_db.RegisterMessage(Query.KeyValue) _sym_db.RegisterMessage(Query.Instance) _sym_db.RegisterMessage(Query.StringPair) _sym_db.RegisterMessage(Query.IntPair) _sym_db.RegisterMessage(Query.DoublePair) _sym_db.RegisterMessage(Query.LocationPair) _sym_db.RegisterMessage(Query.Range) _sym_db.RegisterMessage(Query.Distance) _sym_db.RegisterMessage(Query.Relation) _sym_db.RegisterMessage(Query.Set) _sym_db.RegisterMessage(Query.Set.Values) _sym_db.RegisterMessage(Query.Set.Values.Ints) _sym_db.RegisterMessage(Query.Set.Values.Doubles) _sym_db.RegisterMessage(Query.Set.Values.Strings) _sym_db.RegisterMessage(Query.Set.Values.Bools) _sym_db.RegisterMessage(Query.Set.Values.Locations) _sym_db.RegisterMessage(Query.ConstraintExpr) _sym_db.RegisterMessage(Query.ConstraintExpr.Or) _sym_db.RegisterMessage(Query.ConstraintExpr.And) _sym_db.RegisterMessage(Query.ConstraintExpr.Not) _sym_db.RegisterMessage(Query.ConstraintExpr.Constraint) _sym_db.RegisterMessage(Query.Model) DESCRIPTOR._options = None # @@protoc_insertion_point(module_scope) ================================================ FILE: aea/helpers/serializers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains Serializers that can be used for custom types.""" import copy from typing import Any, Dict, Tuple from google.protobuf.struct_pb2 import ListValue, Struct class DictProtobufStructSerializer: """ Serialize python dictionaries of type DictType = Dict[str, ValueType] recursively conserving their dynamic type, using google.protobuf.Struct ValueType = PrimitiveType | DictType | List[ValueType]] PrimitiveType = bool | int | float | str | bytes """ NEED_PATCH = "_need_patch" @classmethod def encode(cls, dictionary: Dict[str, Any]) -> bytes: """ Serialize compatible dictionary to bytes. Copies entire dictionary in the process. :param dictionary: the dictionary to serialize :return: serialized bytes string """ if not isinstance(dictionary, dict): raise TypeError( # pragma: nocover "dictionary must be of dict type, got type {}".format(type(dictionary)) ) patched_dict = copy.deepcopy(dictionary) cls._patch_dict(patched_dict) pstruct = Struct() pstruct.update(patched_dict) # pylint: disable=no-member return pstruct.SerializeToString(deterministic=True) @classmethod def decode(cls, buffer: bytes) -> Dict[str, Any]: """Deserialize a compatible dictionary""" pstruct = Struct() pstruct.ParseFromString(buffer) dictionary = dict(pstruct) cls._patch_dict_restore(dictionary) return dictionary @classmethod def _bytes_to_str(cls, value: bytes) -> str: return value.decode("utf-8") @classmethod def _str_to_bytes(cls, value: str) -> bytes: return value.encode("utf-8") @classmethod def _patch_dict(cls, dictionnary: Dict[str, Any]) -> None: need_patch: Dict[str, bool] = {} for key, value in dictionnary.items(): new_value, patch_needed = cls._patch_value(value) if patch_needed: need_patch[key] = True dictionnary[key] = new_value if need_patch: dict_need_patch = dictionnary.get(cls.NEED_PATCH, {}) dict_need_patch.update(need_patch) dictionnary[cls.NEED_PATCH] = dict_need_patch @classmethod def _patch_value(cls, value: Any) -> Tuple[Any, bool]: if isinstance(value, bytes): return cls._bytes_to_str(value), True if isinstance(value, int) and not isinstance(value, bool): return value, True if isinstance(value, list): result = [] patched = False types = set() for v in value: types.add(type(v)) v, need_patch = cls._patch_value(v) if need_patch or isinstance(v, dict): patched = True result.append(v) if len(types) > 1: raise ValueError(f"Mixed data types in list are not allowed!: {value}") return result, patched if isinstance(value, dict): cls._patch_dict(value) return value, False if isinstance(value, tuple([bool, float, str, Struct])): # do nothing for supported types return value, False if value is None: return None, False raise NotImplementedError( "DictProtobufStructSerializer doesn't support dict value type {}".format( type(value) ) ) @classmethod def _restore_value(cls, value: Any) -> Any: if isinstance(value, str): return cls._str_to_bytes(value) if isinstance(value, Struct): if value != Struct(): new_dict = dict(value) cls._patch_dict_restore(new_dict) return new_dict return {} if isinstance(value, float): return int(value) if isinstance(value, (list, ListValue)): return [cls._restore_value(v) for v in value] # type: ignore raise NotImplementedError( # pragma: nocover "DictProtobufStructSerializer doesn't support dict value type {}".format( type(value) ) ) @classmethod def _patch_dict_restore(cls, dictionary: Dict[str, Any]) -> None: # protobuf Struct doesn't recursively convert Struct to dict need_patch = dictionary.pop(cls.NEED_PATCH, {}) for key, value in dictionary.items(): # protobuf struct doesn't recursively convert Struct to dict if isinstance(value, Struct): dictionary[key] = cls._restore_value(value) if key in need_patch: dictionary[key] = cls._restore_value(value) elif isinstance(value, (list, ListValue)): # fix list of elementes not needed to be restored dictionary[key] = list(value) # type: ignore ================================================ FILE: aea/helpers/storage/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains generic storage code.""" ================================================ FILE: aea/helpers/storage/backends/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains storage abstract backend and implementations.""" ================================================ FILE: aea/helpers/storage/backends/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains storage abstract backend class.""" import re from abc import ABC, abstractmethod from typing import List, Optional, Tuple, Union from aea.helpers.constants import JSON_TYPES EQUALS_TYPE = Union[int, float, str, bool] OBJECT_ID_AND_BODY = Tuple[str, JSON_TYPES] class AbstractStorageBackend(ABC): """Abstract base class for storage backend.""" VALID_COL_NAME = re.compile("^[a-zA-Z0-9_]+$") def __init__(self, uri: str) -> None: """Init backend.""" self._uri = uri def _check_collection_name(self, collection_name: str) -> None: """ Check collection name is valid. :param collection_name: the collection name. :raises ValueError: if bad collection name provided. """ if not self.VALID_COL_NAME.match(collection_name): raise ValueError( f"Invalid collection name: {collection_name}, should contain only alpha-numeric characters and _" ) @abstractmethod async def connect(self) -> None: """Connect to backend.""" @abstractmethod async def disconnect(self) -> None: """Disconnect the backend.""" @abstractmethod async def ensure_collection(self, collection_name: str) -> None: """ Create collection if not exits. :param collection_name: str. :return: None """ @abstractmethod async def put( self, collection_name: str, object_id: str, object_body: JSON_TYPES ) -> None: """ Put object into collection. :param collection_name: str. :param object_id: str object id :param object_body: python dict, json compatible. :return: None """ @abstractmethod async def get(self, collection_name: str, object_id: str) -> Optional[JSON_TYPES]: """ Get object from the collection. :param collection_name: str. :param object_id: str object id :return: dict if object exists in collection otherwise None """ @abstractmethod async def remove(self, collection_name: str, object_id: str) -> None: """ Remove object from the collection. :param collection_name: str. :param object_id: str object id :return: None """ @abstractmethod async def find( self, collection_name: str, field: str, equals: EQUALS_TYPE ) -> List[OBJECT_ID_AND_BODY]: """ Get objects from the collection by filtering by field value. :param collection_name: str. :param field: field name to search: example "parent.field" :param equals: value field should be equal to :return: list of objects bodies """ @abstractmethod async def list(self, collection_name: str) -> List[OBJECT_ID_AND_BODY]: """ List all objects with keys from the collection. :param collection_name: str. :return: Tuple of objects keys, bodies. """ ================================================ FILE: aea/helpers/storage/backends/binaries/README.txt ================================================ json1.dll - is sqlite extension for windows python<3.9 ================================================ FILE: aea/helpers/storage/backends/sqlite.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains sqlite storage backend implementation.""" import asyncio import json import os import platform import sqlite3 import sys import threading from concurrent.futures.thread import ThreadPoolExecutor from pathlib import Path from typing import List, Optional, Tuple from urllib.parse import urlparse from aea.helpers.storage.backends.base import ( AbstractStorageBackend, EQUALS_TYPE, JSON_TYPES, OBJECT_ID_AND_BODY, ) class SqliteStorageBackend(AbstractStorageBackend): """Sqlite storage backend.""" def __init__(self, uri: str) -> None: """Init backend.""" super().__init__(uri) parsed = urlparse(self._uri) self._fname = parsed.netloc or parsed.path self._connection: Optional[sqlite3.Connection] = None self._loop: Optional[asyncio.AbstractEventLoop] = None self._lock = threading.Lock() self._executor = ThreadPoolExecutor(max_workers=1) def _execute_sql_sync(self, query: str, args: Optional[List] = None) -> List[Tuple]: """ Execute sql command and return results. :param query: sql query string :param args: optional arguments to set into sql query. :return: List of tuples with sql records """ if not self._connection: # pragma: nocover raise ValueError("Not connected") with self._lock: result = self._connection.execute(query, args or []).fetchall() self._connection.commit() return result async def _executute_sql( self, query: str, args: Optional[List] = None ) -> Optional[JSON_TYPES]: """ Execute sql command and return results in async executor. :param query: sql query string :param args: optional arguments to set into sql query. :return: List of tuples with sql records """ if not self._loop: # pragma: nocover raise ValueError("Not connected") return await self._loop.run_in_executor( self._executor, self._execute_sql_sync, query, args ) async def connect(self) -> None: """Connect to backend.""" self._loop = asyncio.get_event_loop() self._connection = await self._loop.run_in_executor( self._executor, self._do_connect, self._fname ) @staticmethod def _do_connect(fname: str) -> sqlite3.Connection: con = sqlite3.connect(fname) if ( platform.system() == "Windows" and sys.version_info.major == 3 and sys.version_info.minor < 9 ): # pragma: nocover con.enable_load_extension(True) path_ext = Path( os.path.join(os.path.dirname(__file__), "binaries", "json1.dll") ).as_posix() con.load_extension(path_ext) return con async def disconnect(self) -> None: """Disconnect the backend.""" if not self._loop or not self._connection: # pragma: nocover raise ValueError("Not connected") await self._loop.run_in_executor(self._executor, self._connection.close) self._connection = None self._loop = None async def ensure_collection(self, collection_name: str) -> None: """ Create collection if not exits. :param collection_name: name of the collection. """ self._check_collection_name(collection_name) sql = f"""CREATE TABLE IF NOT EXISTS {collection_name} ( object_id TEXT PRIMARY KEY, object_body JSON1 NOT NULL) """ # nosec await self._executute_sql(sql) async def put( self, collection_name: str, object_id: str, object_body: JSON_TYPES ) -> None: """ Put object into collection. :param collection_name: str. :param object_id: str object id :param object_body: python dict, json compatible. """ self._check_collection_name(collection_name) sql = f"""INSERT OR REPLACE INTO {collection_name} (object_id, object_body) VALUES (?, ?); """ # nosec await self._executute_sql(sql, [object_id, json.dumps(object_body)]) async def get(self, collection_name: str, object_id: str) -> Optional[JSON_TYPES]: """ Get object from the collection. :param collection_name: str. :param object_id: str object id :return: dict if object exists in collection otherwise None """ self._check_collection_name(collection_name) sql = f"""SELECT object_body FROM {collection_name} WHERE object_id = ? LIMIT 1;""" # nosec result = await self._executute_sql(sql, [object_id]) if ( result and isinstance(result, (list, tuple)) and len(result) > 0 and isinstance(result[0], (list, tuple)) and len(result[0]) > 0 ): return json.loads(result[0][0]) return None async def remove(self, collection_name: str, object_id: str) -> None: """ Remove object from the collection. :param collection_name: str. :param object_id: str object id """ self._check_collection_name(collection_name) sql = f"""DELETE FROM {collection_name} WHERE object_id = ?;""" # nosec await self._executute_sql(sql, [object_id]) async def find( self, collection_name: str, field: str, equals: EQUALS_TYPE ) -> List[OBJECT_ID_AND_BODY]: """ Get objects from the collection by filtering by field value. :param collection_name: str. :param field: field name to search: example "parent.field" :param equals: value field should be equal to :return: list of object ids and body """ self._check_collection_name(collection_name) sql = f"""SELECT object_id, object_body FROM {collection_name} WHERE json_extract(object_body, ?) = ?;""" # nosec if not field.startswith("$."): field = f"$.{field}" return [ (i[0], json.loads(i[1])) for i in await self._executute_sql(sql, [field, equals]) # type: ignore ] async def list(self, collection_name: str) -> List[OBJECT_ID_AND_BODY]: """ List all objects with keys from the collection. :param collection_name: str. :return: Tuple of objects keys, bodies. """ self._check_collection_name(collection_name) sql = f"""SELECT object_id, object_body FROM {collection_name};""" # nosec return [(i[0], json.loads(i[1])) for i in await self._executute_sql(sql)] # type: ignore ================================================ FILE: aea/helpers/storage/generic_storage.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the storage implementation.""" import asyncio from typing import Any, Coroutine, List, Optional from urllib.parse import urlparse from aea.helpers.async_utils import AsyncState, Runnable from aea.helpers.storage.backends.base import ( AbstractStorageBackend, EQUALS_TYPE, JSON_TYPES, OBJECT_ID_AND_BODY, ) from aea.helpers.storage.backends.sqlite import SqliteStorageBackend BACKENDS = {"sqlite": SqliteStorageBackend} class AsyncCollection: """Async collection.""" def __init__( self, storage_backend: AbstractStorageBackend, collection_name: str ) -> None: """ Init collection object. :param storage_backend: storage backed to use. :param collection_name: str """ self._storage_backend = storage_backend self._collection_name = collection_name async def put(self, object_id: str, object_body: JSON_TYPES) -> None: """ Put object into collection. :param object_id: str object id :param object_body: python dict, json compatible. :return: None """ return await self._storage_backend.put( self._collection_name, object_id, object_body ) async def get(self, object_id: str) -> Optional[JSON_TYPES]: """ Get object from the collection. :param object_id: str object id :return: dict if object exists in collection otherwise None """ return await self._storage_backend.get(self._collection_name, object_id) async def remove(self, object_id: str) -> None: """ Remove object from the collection. :param object_id: str object id :return: None """ return await self._storage_backend.remove(self._collection_name, object_id) async def find(self, field: str, equals: EQUALS_TYPE) -> List[OBJECT_ID_AND_BODY]: """ Get objects from the collection by filtering by field value. :param field: field name to search: example "parent.field" :param equals: value field should be equal to :return: None """ return await self._storage_backend.find(self._collection_name, field, equals) async def list(self) -> List[OBJECT_ID_AND_BODY]: """ List all objects with keys from the collection. :return: Tuple of objects keys, bodies. """ return await self._storage_backend.list(self._collection_name) class SyncCollection: """Async collection.""" def __init__( self, async_collection_coro: Coroutine, loop: asyncio.AbstractEventLoop ) -> None: """ Init collection object. :param async_collection_coro: coroutine returns async collection. :param loop: abstract event loop where storage is running. """ self._loop = loop self._async_collection = self._run_sync(async_collection_coro) def _run_sync(self, coro: Coroutine) -> Any: return asyncio.run_coroutine_threadsafe(coro, self._loop).result() def put(self, object_id: str, object_body: JSON_TYPES) -> None: """ Put object into collection. :param object_id: str object id :param object_body: python dict, json compatible. :return: None """ return self._run_sync(self._async_collection.put(object_id, object_body)) def get(self, object_id: str) -> Optional[JSON_TYPES]: """ Get object from the collection. :param object_id: str object id :return: dict if object exists in collection otherwise None """ return self._run_sync(self._async_collection.get(object_id)) def remove(self, object_id: str) -> None: """ Remove object from the collection. :param object_id: str object id :return: None """ return self._run_sync(self._async_collection.remove(object_id)) def find(self, field: str, equals: EQUALS_TYPE) -> List[OBJECT_ID_AND_BODY]: """ Get objects from the collection by filtering by field value. :param field: field name to search: example "parent.field" :param equals: value field should be equal to :return: List of object bodies """ return self._run_sync(self._async_collection.find(field, equals)) def list(self) -> List[OBJECT_ID_AND_BODY]: """ List all objects with keys from the collection. :return: Tuple of objects keys, bodies. """ return self._run_sync(self._async_collection.list()) class Storage(Runnable): """Generic storage.""" def __init__( self, storage_uri: str, loop: asyncio.AbstractEventLoop = None, threaded: bool = False, ) -> None: """ Init storage. :param storage_uri: configuration string for storage. :param loop: asyncio event loop to use. :param threaded: bool. start in thread if True. """ super().__init__(loop=loop, threaded=threaded) self._storage_uri = storage_uri self._backend: AbstractStorageBackend = self._get_backend_instance(storage_uri) self._is_connected = False self._connected_state = AsyncState(False) async def wait_connected(self) -> None: """Wait generic storage is connected.""" await self._connected_state.wait(True) @property def is_connected(self) -> bool: """Get running state of the storage.""" return self._is_connected async def run(self) -> None: """Connect storage.""" await self._backend.connect() self._is_connected = True self._connected_state.set(True) try: while True: await asyncio.sleep(1) finally: await self._backend.disconnect() self._is_connected = False @classmethod def _get_backend_instance(cls, uri: str) -> AbstractStorageBackend: """Construct backend instance.""" backend_name = urlparse(uri).scheme backend_class = BACKENDS.get(backend_name, None) if backend_class is None: raise ValueError( f"Backend `{backend_name}` is not supported. Supported are {', '.join(BACKENDS.keys())} " ) return backend_class(uri) async def get_collection(self, collection_name: str) -> AsyncCollection: """Get async collection.""" await self._backend.ensure_collection(collection_name) return AsyncCollection( collection_name=collection_name, storage_backend=self._backend ) def get_sync_collection(self, collection_name: str) -> SyncCollection: """Get sync collection.""" if not self._loop: # pragma: nocover raise ValueError("Storage not started!") return SyncCollection(self.get_collection(collection_name), self._loop) def __repr__(self) -> str: """Get string representation of the storage.""" return f"[GenericStorage({self._storage_uri}){'Connected' if self.is_connected else 'Not connected'}]" ================================================ FILE: aea/helpers/sym_link.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Sym link implementation for Linux, MacOS, and Windows.""" import contextlib import os from functools import reduce from pathlib import Path from typing import Generator def make_symlink(link_name: str, target: str) -> None: """ Make a symbolic link, cross platform. :param link_name: the link name. :param target: the target. """ try: Path(link_name).unlink() except FileNotFoundError: pass Path(link_name).symlink_to(target, target_is_directory=True) @contextlib.contextmanager def cd(path: Path) -> Generator: """Change directory with context manager.""" old_cwd = os.getcwd() try: os.chdir(path) yield os.chdir(old_cwd) except Exception as e: # pylint: disable=broad-except # pragma: nocover os.chdir(old_cwd) raise e from e def create_symlink(link_path: Path, target_path: Path, root_path: Path) -> int: """ Change directory and call the cross-platform script. The working directory must be the parent of the symbolic link name when executing 'create_symlink_crossplatform.sh'. Hence, we need to translate target_path into the relative path from the symbolic link directory to the target directory. So: 1) from link_path, extract the number of jumps to the parent directory in order to reach the repository root directory, and chain many "../" paths. 2) from target_path, compute the relative path to the root 3) relative_target_path is just the concatenation of the results from step (1) and (2). For instance, given - link_path: './directory_1//symbolic_link - target_path: './directory_2/target_path we want to compute: - link_path: 'symbolic_link' (just the last bit) - relative_target_path: '../../directory_1/target_path' The resulting command on UNIX systems will be: cd directory_1 && ln -s ../../directory_1/target_path symbolic_link :param link_path: the source path :param target_path: the target path :param root_path: the root path :return: exit code """ working_directory = link_path.parent target_relative_to_root = target_path.relative_to(root_path) cwd_relative_to_root = working_directory.relative_to(root_path) nb_parents = len(cwd_relative_to_root.parents) root_relative_to_cwd = reduce( lambda x, y: x / y, [Path("../")] * nb_parents, Path(".") ) link_name = link_path.name target = root_relative_to_cwd / target_relative_to_root with cd(working_directory.absolute()): make_symlink(str(link_name), str(target)) return 0 ================================================ FILE: aea/helpers/transaction/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains transaction related classes.""" ================================================ FILE: aea/helpers/transaction/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains terms related classes.""" import collections import copy from typing import Any, Dict, List, Optional, Tuple from aea.common import JSONLike from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import enforce from aea.helpers.serializers import DictProtobufStructSerializer Address = str class RawTransaction: """This class represents an instance of RawTransaction.""" __slots__ = ("_ledger_id", "_body") def __init__( self, ledger_id: str, body: JSONLike, ) -> None: """Initialise an instance of RawTransaction.""" self._ledger_id = ledger_id self._body = body self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._body, dict), "body must not be JSONLike") @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def body(self) -> JSONLike: """Get the body.""" return self._body @staticmethod def encode( raw_transaction_protobuf_object: Any, raw_transaction_object: "RawTransaction" ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the raw_transaction_protobuf_object argument must be matched with the instance of this class in the 'raw_transaction_object' argument. :param raw_transaction_protobuf_object: the protocol buffer object whose type corresponds with this class. :param raw_transaction_object: an instance of this class to be encoded in the protocol buffer object. """ raw_transaction_dict = { "ledger_id": raw_transaction_object.ledger_id, "body": raw_transaction_object.body, } raw_transaction_protobuf_object.raw_transaction = ( DictProtobufStructSerializer.encode(raw_transaction_dict) ) @classmethod def decode(cls, raw_transaction_protobuf_object: Any) -> "RawTransaction": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'raw_transaction_protobuf_object' argument. :param raw_transaction_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'raw_transaction_protobuf_object' argument. """ raw_transaction_dict = DictProtobufStructSerializer.decode( raw_transaction_protobuf_object.raw_transaction ) return cls(raw_transaction_dict["ledger_id"], raw_transaction_dict["body"]) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, RawTransaction) and self.ledger_id == other.ledger_id and self.body == other.body ) def __str__(self) -> str: """Get string representation.""" return "RawTransaction: ledger_id={}, body={}".format( self.ledger_id, self.body, ) class RawMessage: """This class represents an instance of RawMessage.""" __slots__ = ("_ledger_id", "_body", "_is_deprecated_mode") def __init__( self, ledger_id: str, body: bytes, is_deprecated_mode: bool = False, ) -> None: """Initialise an instance of RawMessage.""" self._ledger_id = ledger_id self._body = body self._is_deprecated_mode = is_deprecated_mode self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._body, bytes), "body must not be bytes") enforce( isinstance(self._is_deprecated_mode, bool), "is_deprecated_mode must be bool", ) @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def body(self) -> bytes: """Get the body.""" return self._body @property def is_deprecated_mode(self) -> bool: """Get the is_deprecated_mode.""" return self._is_deprecated_mode @staticmethod def encode( raw_message_protobuf_object: Any, raw_message_object: "RawMessage" ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the raw_message_protobuf_object argument must be matched with the instance of this class in the 'raw_message_object' argument. :param raw_message_protobuf_object: the protocol buffer object whose type corresponds with this class. :param raw_message_object: an instance of this class to be encoded in the protocol buffer object. """ raw_message_dict = { "ledger_id": raw_message_object.ledger_id, "body": raw_message_object.body, "is_deprecated_mode": raw_message_object.is_deprecated_mode, } raw_message_protobuf_object.raw_message = DictProtobufStructSerializer.encode( raw_message_dict ) @classmethod def decode(cls, raw_message_protobuf_object: Any) -> "RawMessage": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'raw_message_protobuf_object' argument. :param raw_message_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'raw_message_protobuf_object' argument. """ raw_message_dict = DictProtobufStructSerializer.decode( raw_message_protobuf_object.raw_message ) return cls( raw_message_dict["ledger_id"], raw_message_dict["body"], raw_message_dict["is_deprecated_mode"], ) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, RawMessage) and self.ledger_id == other.ledger_id and self.body == other.body and self.is_deprecated_mode == other.is_deprecated_mode ) def __str__(self) -> str: """Get string representation.""" return "RawMessage: ledger_id={}, body={!r}, is_deprecated_mode={}".format( self.ledger_id, self.body, self.is_deprecated_mode, ) class SignedTransaction: """This class represents an instance of SignedTransaction.""" __slots__ = ("_ledger_id", "_body") def __init__( self, ledger_id: str, body: JSONLike, ) -> None: """Initialise an instance of SignedTransaction.""" self._ledger_id = ledger_id self._body = body self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._body, dict), "body must not JSONLike") @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def body(self) -> JSONLike: """Get the body.""" return self._body @staticmethod def encode( signed_transaction_protobuf_object: Any, signed_transaction_object: "SignedTransaction", ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the signed_transaction_protobuf_object argument must be matched with the instance of this class in the 'signed_transaction_object' argument. :param signed_transaction_protobuf_object: the protocol buffer object whose type corresponds with this class. :param signed_transaction_object: an instance of this class to be encoded in the protocol buffer object. """ signed_transaction_dict = { "ledger_id": signed_transaction_object.ledger_id, "body": signed_transaction_object.body, } signed_transaction_protobuf_object.signed_transaction = ( DictProtobufStructSerializer.encode(signed_transaction_dict) ) @classmethod def decode(cls, signed_transaction_protobuf_object: Any) -> "SignedTransaction": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'signed_transaction_protobuf_object' argument. :param signed_transaction_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'signed_transaction_protobuf_object' argument. """ signed_transaction_dict = DictProtobufStructSerializer.decode( signed_transaction_protobuf_object.signed_transaction ) return cls( signed_transaction_dict["ledger_id"], signed_transaction_dict["body"] ) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, SignedTransaction) and self.ledger_id == other.ledger_id and self.body == other.body ) def __str__(self) -> str: """Get string representation.""" return "SignedTransaction: ledger_id={}, body={}".format( self.ledger_id, self.body, ) class SignedMessage: """This class represents an instance of RawMessage.""" __slots__ = ("_ledger_id", "_body", "_is_deprecated_mode") def __init__( self, ledger_id: str, body: str, is_deprecated_mode: bool = False, ) -> None: """Initialise an instance of SignedMessage.""" self._ledger_id = ledger_id self._body = body self._is_deprecated_mode = is_deprecated_mode self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._body, str), "body must be string") enforce( isinstance(self._is_deprecated_mode, bool), "is_deprecated_mode must be bool", ) @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def body(self) -> str: """Get the body.""" return self._body @property def is_deprecated_mode(self) -> bool: """Get the is_deprecated_mode.""" return self._is_deprecated_mode @staticmethod def encode( signed_message_protobuf_object: Any, signed_message_object: "SignedMessage" ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the signed_message_protobuf_object argument must be matched with the instance of this class in the 'signed_message_object' argument. :param signed_message_protobuf_object: the protocol buffer object whose type corresponds with this class. :param signed_message_object: an instance of this class to be encoded in the protocol buffer object. """ signed_message_dict = { "ledger_id": signed_message_object.ledger_id, "body": signed_message_object.body, "is_deprecated_mode": signed_message_object.is_deprecated_mode, } signed_message_protobuf_object.signed_message = ( DictProtobufStructSerializer.encode(signed_message_dict) ) @classmethod def decode(cls, signed_message_protobuf_object: Any) -> "SignedMessage": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'signed_message_protobuf_object' argument. :param signed_message_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'signed_message_protobuf_object' argument. """ signed_message_dict = DictProtobufStructSerializer.decode( signed_message_protobuf_object.signed_message ) return cls( signed_message_dict["ledger_id"], signed_message_dict["body"], signed_message_dict["is_deprecated_mode"], ) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, SignedMessage) and self.ledger_id == other.ledger_id and self.body == other.body and self.is_deprecated_mode == other.is_deprecated_mode ) def __str__(self) -> str: """Get string representation.""" return "SignedMessage: ledger_id={}, body={}, is_deprecated_mode={}".format( self.ledger_id, self.body, self.is_deprecated_mode, ) class State: """This class represents an instance of State.""" __slots__ = ("_ledger_id", "_body") def __init__(self, ledger_id: str, body: JSONLike) -> None: """Initialise an instance of State.""" self._ledger_id = ledger_id self._body = body self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._body, dict), "body must be dict") @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def body(self) -> JSONLike: """Get the body.""" return self._body @staticmethod def encode(state_protobuf_object: Any, state_object: "State") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the state_protobuf_object argument must be matched with the instance of this class in the 'state_object' argument. :param state_protobuf_object: the protocol buffer object whose type corresponds with this class. :param state_object: an instance of this class to be encoded in the protocol buffer object. """ state_dict = { "ledger_id": state_object.ledger_id, "body": state_object.body, } state_protobuf_object.state = DictProtobufStructSerializer.encode(state_dict) @classmethod def decode(cls, state_protobuf_object: Any) -> "State": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'state_protobuf_object' argument. :param state_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'state_protobuf_object' argument. """ state_dict = DictProtobufStructSerializer.decode(state_protobuf_object.state) return cls(state_dict["ledger_id"], state_dict["body"]) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, State) and self.ledger_id == other.ledger_id and self.body == other.body ) def __str__(self) -> str: """Get string representation.""" return "State: ledger_id={}, body={}".format(self.ledger_id, self.body) class Terms: """Class to represent the terms of a multi-currency & multi-token ledger transaction.""" __slots__ = ( "_ledger_id", "_sender_address", "_counterparty_address", "_amount_by_currency_id", "_quantities_by_good_id", "_is_sender_payable_tx_fee", "_nonce", "_fee_by_currency_id", "_is_strict", "_kwargs", "_good_ids", "_sender_supplied_quantities", "_counterparty_supplied_quantities", "_sender_hash", "_counterparty_hash", ) def __init__( self, ledger_id: str, sender_address: Address, counterparty_address: Address, amount_by_currency_id: Dict[str, int], quantities_by_good_id: Dict[str, int], nonce: str, is_sender_payable_tx_fee: bool = True, fee_by_currency_id: Optional[Dict[str, int]] = None, is_strict: bool = False, **kwargs: Any, ) -> None: """ Instantiate terms of a transaction. :param ledger_id: the ledger on which the terms are to be settled. :param sender_address: the sender address of the transaction. :param counterparty_address: the counterparty address of the transaction. :param amount_by_currency_id: the amount by the currency of the transaction. :param quantities_by_good_id: a map from good id to the quantity of that good involved in the transaction. :param nonce: nonce to be included in transaction to discriminate otherwise identical transactions. :param is_sender_payable_tx_fee: whether the sender or counterparty pays the tx fee. :param fee_by_currency_id: the fee associated with the transaction. :param is_strict: whether or not terms must have quantities and amounts of opposite signs. :param kwargs: keyword arguments """ self._ledger_id = ledger_id self._sender_address = sender_address self._counterparty_address = counterparty_address self._amount_by_currency_id = amount_by_currency_id self._quantities_by_good_id = quantities_by_good_id self._is_sender_payable_tx_fee = is_sender_payable_tx_fee self._nonce = nonce self._fee_by_currency_id = ( fee_by_currency_id if fee_by_currency_id is not None else {} ) self._is_strict = is_strict self._kwargs = kwargs if kwargs is not None else {} self._check_consistency() ( good_ids, sender_supplied_quantities, counterparty_supplied_quantities, ) = self._get_lists() self._good_ids = good_ids self._sender_supplied_quantities = sender_supplied_quantities self._counterparty_supplied_quantities = counterparty_supplied_quantities self._sender_hash = self.get_hash( self.ledger_id, sender_address=self.sender_address, counterparty_address=self.counterparty_address, good_ids=self.good_ids, sender_supplied_quantities=self.sender_supplied_quantities, counterparty_supplied_quantities=self.counterparty_supplied_quantities, sender_payable_amount=self.sender_payable_amount, counterparty_payable_amount=self.counterparty_payable_amount, nonce=self.nonce, ) self._counterparty_hash = self.get_hash( self.ledger_id, sender_address=self.counterparty_address, counterparty_address=self.sender_address, good_ids=self.good_ids, sender_supplied_quantities=self.counterparty_supplied_quantities, counterparty_supplied_quantities=self.sender_supplied_quantities, sender_payable_amount=self.counterparty_payable_amount, counterparty_payable_amount=self.sender_payable_amount, nonce=self.nonce, ) def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._sender_address, str), "sender_address must be str") enforce( isinstance(self._counterparty_address, str), "counterparty_address must be str", ) enforce( isinstance(self._amount_by_currency_id, dict) and all( ( isinstance(key, str) and isinstance(value, int) for key, value in self._amount_by_currency_id.items() ) ), "amount_by_currency_id must be a dictionary with str keys and int values.", ) enforce( isinstance(self._quantities_by_good_id, dict) and all( ( isinstance(key, str) and isinstance(value, int) for key, value in self._quantities_by_good_id.items() ) ), "quantities_by_good_id must be a dictionary with str keys and int values.", ) enforce( isinstance(self._is_sender_payable_tx_fee, bool), "is_sender_payable_tx_fee must be bool", ) enforce(isinstance(self._nonce, str), "nonce must be str") enforce( self._fee_by_currency_id is None or ( isinstance(self._fee_by_currency_id, dict) and all( ( isinstance(key, str) and isinstance(value, int) and value >= 0 for key, value in self._fee_by_currency_id.items() ) ) ), "fee must be None or Dict[str, int] with positive fees only.", ) enforce( all( ( key in self._amount_by_currency_id for key in self._fee_by_currency_id.keys() ) ), "Fee dictionary has keys which are not present in amount dictionary.", ) if self._is_strict: is_pos_amounts = all( (amount >= 0 for amount in self._amount_by_currency_id.values()) ) is_neg_amounts = all( (amount <= 0 for amount in self._amount_by_currency_id.values()) ) is_pos_quantities = all( (quantity >= 0 for quantity in self._quantities_by_good_id.values()) ) is_neg_quantities = all( (quantity <= 0 for quantity in self._quantities_by_good_id.values()) ) enforce( (is_pos_amounts and is_neg_quantities) or (is_neg_amounts and is_pos_quantities), "quantities and amounts do not constitute valid terms. All quantities must be of same sign. All amounts must be of same sign. Quantities and amounts must be of different sign.", ) @property def id(self) -> str: """Get hash of the terms.""" return self.sender_hash @property def sender_hash(self) -> str: """Get the sender hash.""" return self._sender_hash @property def counterparty_hash(self) -> str: """Get the sender hash.""" return self._counterparty_hash @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def sender_address(self) -> Address: """Get the sender address.""" return self._sender_address @property def counterparty_address(self) -> Address: """Get the counterparty address.""" return self._counterparty_address @counterparty_address.setter def counterparty_address(self, counterparty_address: Address) -> None: """Set the counterparty address.""" enforce( isinstance(counterparty_address, str), "counterparty_address must be str" ) self._counterparty_address = counterparty_address @property def amount_by_currency_id(self) -> Dict[str, int]: """Get the amount by currency id.""" return copy.copy(self._amount_by_currency_id) @property def is_sender_payable_tx_fee(self) -> bool: """Bool indicating whether the tx fee is paid by sender or counterparty.""" return self._is_sender_payable_tx_fee @property def is_single_currency(self) -> bool: """Check whether a single currency is used for payment.""" return ( len(self._amount_by_currency_id) == 1 and len(self._fee_by_currency_id) <= 1 ) @property def is_empty_currency(self) -> bool: """Check whether a single currency is used for payment.""" return len(self._amount_by_currency_id) == 0 @property def currency_id(self) -> str: """Get the amount the sender must pay.""" enforce(self.is_single_currency, "More than one currency id, cannot get id.") value = next(iter(self._amount_by_currency_id.keys())) return value @property def sender_payable_amount(self) -> int: """Get the amount the sender must pay.""" enforce( self.is_single_currency or self.is_empty_currency, "More than one currency id, cannot get amount.", ) value = ( next(iter(self._amount_by_currency_id.values())) if not self.is_empty_currency else 0 ) payable = -value if value <= 0 else 0 return payable @property def sender_payable_amount_incl_fee(self) -> int: """Get the amount the sender must pay inclusive fee.""" enforce( self.is_single_currency or self.is_empty_currency, "More than one currency id, cannot get amount.", ) payable = self.sender_payable_amount if self.is_sender_payable_tx_fee and len(self._fee_by_currency_id) == 1: payable += next(iter(self._fee_by_currency_id.values())) return payable @property def counterparty_payable_amount(self) -> int: """Get the amount the counterparty must pay.""" enforce( self.is_single_currency or self.is_empty_currency, "More than one currency id, cannot get amount.", ) value = ( next(iter(self._amount_by_currency_id.values())) if not self.is_empty_currency else 0 ) payable = value if value >= 0 else 0 return payable @property def counterparty_payable_amount_incl_fee(self) -> int: """Get the amount the counterparty must pay.""" enforce( self.is_single_currency or self.is_empty_currency, "More than one currency id, cannot get amount.", ) payable = self.counterparty_payable_amount if not self.is_sender_payable_tx_fee and len(self._fee_by_currency_id) == 1: payable += next(iter(self._fee_by_currency_id.values())) return payable @property def quantities_by_good_id(self) -> Dict[str, int]: """Get the quantities by good id.""" return copy.copy(self._quantities_by_good_id) @property def good_ids(self) -> List[str]: """Get the (ordered) good ids.""" return self._good_ids @property def sender_supplied_quantities(self) -> List[int]: """Get the (ordered) quantities supplied by the sender.""" return self._sender_supplied_quantities @property def counterparty_supplied_quantities(self) -> List[int]: """Get the (ordered) quantities supplied by the counterparty.""" return self._counterparty_supplied_quantities @property def nonce(self) -> str: """Get the nonce.""" return self._nonce @property def has_fee(self) -> bool: """Check if fee is set.""" return self.fee_by_currency_id != {} @property def fee(self) -> int: """Get the fee.""" enforce(self.has_fee, "fee_by_currency_id not set.") enforce( len(self.fee_by_currency_id) == 1, "More than one currency id, cannot get fee.", ) return next(iter(self._fee_by_currency_id.values())) @property def sender_fee(self) -> int: """Get the sender fee.""" value = self.fee if self.is_sender_payable_tx_fee else 0 return value @property def counterparty_fee(self) -> int: """Get the counterparty fee.""" value = 0 if self.is_sender_payable_tx_fee else self.fee return value @property def fee_by_currency_id(self) -> Dict[str, int]: """Get fee by currency.""" return copy.copy(self._fee_by_currency_id) @property def kwargs(self) -> JSONLike: """Get the kwargs.""" return self._kwargs @property def is_strict(self) -> bool: """Get is_strict.""" return self._is_strict def _get_lists(self) -> Tuple[List[str], List[int], List[int]]: ordered = collections.OrderedDict(sorted(self.quantities_by_good_id.items())) good_ids = [] # type: List[str] sender_supplied_quantities = [] # type: List[int] counterparty_supplied_quantities = [] # type: List[int] for good_id, quantity in ordered.items(): good_ids.append(good_id) if quantity >= 0: sender_supplied_quantities.append(quantity) counterparty_supplied_quantities.append(0) else: sender_supplied_quantities.append(0) counterparty_supplied_quantities.append(-quantity) return good_ids, sender_supplied_quantities, counterparty_supplied_quantities @staticmethod def get_hash( ledger_id: str, sender_address: str, counterparty_address: str, good_ids: List[str], sender_supplied_quantities: List[int], counterparty_supplied_quantities: List[int], sender_payable_amount: int, counterparty_payable_amount: int, nonce: str, ) -> str: """ Generate a hash from transaction information. :param ledger_id: the ledger id :param sender_address: the sender address :param counterparty_address: the counterparty address :param good_ids: the list of good ids :param sender_supplied_quantities: the quantities supplied by the sender (must all be positive) :param counterparty_supplied_quantities: the quantities supplied by the counterparty (must all be positive) :param sender_payable_amount: the amount payable by the sender :param counterparty_payable_amount: the amount payable by the counterparty :param nonce: the nonce of the transaction :return: the hash """ if len(good_ids) == 0: aggregate_hash = LedgerApis.get_hash(ledger_id, b"") else: aggregate_hash = LedgerApis.get_hash( ledger_id, b"".join( [ good_ids[0].encode("utf-8"), sender_supplied_quantities[0].to_bytes(32, "big"), counterparty_supplied_quantities[0].to_bytes(32, "big"), ] ), ) for idx, good_id in enumerate(good_ids): if idx == 0: continue aggregate_hash = LedgerApis.get_hash( ledger_id, b"".join( [ aggregate_hash.encode("utf-8"), good_id.encode("utf-8"), sender_supplied_quantities[idx].to_bytes(32, "big"), counterparty_supplied_quantities[idx].to_bytes(32, "big"), ] ), ) m_list = [] # type: List[bytes] m_list.append(sender_address.encode("utf-8")) m_list.append(counterparty_address.encode("utf-8")) m_list.append(aggregate_hash.encode("utf-8")) m_list.append(sender_payable_amount.to_bytes(32, "big")) m_list.append(counterparty_payable_amount.to_bytes(32, "big")) m_list.append(nonce.encode("utf-8")) digest = LedgerApis.get_hash(ledger_id, b"".join(m_list)) return digest @staticmethod def encode(terms_protobuf_object: Any, terms_object: "Terms") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the terms_protobuf_object argument must be matched with the instance of this class in the 'terms_object' argument. :param terms_protobuf_object: the protocol buffer object whose type corresponds with this class. :param terms_object: an instance of this class to be encoded in the protocol buffer object. """ terms_dict = { "ledger_id": terms_object.ledger_id, "sender_address": terms_object.sender_address, "counterparty_address": terms_object.counterparty_address, "amount_by_currency_id": terms_object.amount_by_currency_id, "quantities_by_good_id": terms_object.quantities_by_good_id, "nonce": terms_object.nonce, "is_sender_payable_tx_fee": terms_object.is_sender_payable_tx_fee, "fee_by_currency_id": terms_object.fee_by_currency_id, "is_strict": terms_object.is_strict, "kwargs": terms_object.kwargs, } terms_protobuf_object.terms = DictProtobufStructSerializer.encode(terms_dict) @classmethod def decode(cls, terms_protobuf_object: Any) -> "Terms": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'terms_protobuf_object' argument. :param terms_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'terms_protobuf_object' argument. """ terms_dict = DictProtobufStructSerializer.decode(terms_protobuf_object.terms) return cls( terms_dict["ledger_id"], terms_dict["sender_address"], terms_dict["counterparty_address"], terms_dict["amount_by_currency_id"], terms_dict["quantities_by_good_id"], terms_dict["nonce"], terms_dict["is_sender_payable_tx_fee"], dict(terms_dict["fee_by_currency_id"]), terms_dict["is_strict"], **dict(terms_dict["kwargs"]), ) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, Terms) and self.ledger_id == other.ledger_id and self.sender_address == other.sender_address and self.counterparty_address == other.counterparty_address and self.amount_by_currency_id == other.amount_by_currency_id and self.quantities_by_good_id == other.quantities_by_good_id and self.is_sender_payable_tx_fee == other.is_sender_payable_tx_fee and self.nonce == other.nonce and self.kwargs == other.kwargs and self.fee == other.fee if (self.has_fee and other.has_fee) else self.has_fee == other.has_fee ) def __str__(self) -> str: """Get string representation.""" return "Terms: ledger_id={}, sender_address={}, counterparty_address={}, amount_by_currency_id={}, quantities_by_good_id={}, is_sender_payable_tx_fee={}, nonce={}, fee_by_currency_id={}, kwargs={}".format( self.ledger_id, self.sender_address, self.counterparty_address, self.amount_by_currency_id, self.quantities_by_good_id, self.is_sender_payable_tx_fee, self.nonce, self._fee_by_currency_id, self.kwargs, ) class TransactionDigest: """This class represents an instance of TransactionDigest.""" __slots__ = ("_ledger_id", "_body") def __init__(self, ledger_id: str, body: str) -> None: """Initialise an instance of TransactionDigest.""" self._ledger_id = ledger_id self._body = body self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._body, str), "body must not be None") @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def body(self) -> str: """Get the receipt.""" return self._body @staticmethod def encode( transaction_digest_protobuf_object: Any, transaction_digest_object: "TransactionDigest", ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the transaction_digest_protobuf_object argument must be matched with the instance of this class in the 'transaction_digest_object' argument. :param transaction_digest_protobuf_object: the protocol buffer object whose type corresponds with this class. :param transaction_digest_object: an instance of this class to be encoded in the protocol buffer object. """ transaction_digest_dict = { "ledger_id": transaction_digest_object.ledger_id, "body": transaction_digest_object.body, } transaction_digest_protobuf_object.transaction_digest = ( DictProtobufStructSerializer.encode(transaction_digest_dict) ) @classmethod def decode(cls, transaction_digest_protobuf_object: Any) -> "TransactionDigest": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'transaction_digest_protobuf_object' argument. :param transaction_digest_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'transaction_digest_protobuf_object' argument. """ transaction_digest_dict = DictProtobufStructSerializer.decode( transaction_digest_protobuf_object.transaction_digest ) return cls( transaction_digest_dict["ledger_id"], transaction_digest_dict["body"] ) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, TransactionDigest) and self.ledger_id == other.ledger_id and self.body == other.body ) def __str__(self) -> str: """Get string representation.""" return "TransactionDigest: ledger_id={}, body={}".format( self.ledger_id, self.body ) class TransactionReceipt: """This class represents an instance of TransactionReceipt.""" __slots__ = ("_ledger_id", "_receipt", "_transaction") def __init__( self, ledger_id: str, receipt: JSONLike, transaction: JSONLike ) -> None: """Initialise an instance of TransactionReceipt.""" self._ledger_id = ledger_id self._receipt = receipt self._transaction = transaction self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce(isinstance(self._ledger_id, str), "ledger_id must be str") enforce(isinstance(self._receipt, dict), "receipt must be dict") enforce(isinstance(self._transaction, dict), "transaction must be dict") @property def ledger_id(self) -> str: """Get the id of the ledger on which the terms are to be settled.""" return self._ledger_id @property def receipt(self) -> JSONLike: """Get the receipt.""" return self._receipt @property def transaction(self) -> JSONLike: """Get the transaction.""" return self._transaction @staticmethod def encode( transaction_receipt_protobuf_object: Any, transaction_receipt_object: "TransactionReceipt", ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the transaction_receipt_protobuf_object argument must be matched with the instance of this class in the 'transaction_receipt_object' argument. :param transaction_receipt_protobuf_object: the protocol buffer object whose type corresponds with this class. :param transaction_receipt_object: an instance of this class to be encoded in the protocol buffer object. """ transaction_receipt_dict = { "ledger_id": transaction_receipt_object.ledger_id, "receipt": transaction_receipt_object.receipt, "transaction": transaction_receipt_object.transaction, } transaction_receipt_protobuf_object.transaction_receipt = ( DictProtobufStructSerializer.encode(transaction_receipt_dict) ) @classmethod def decode(cls, transaction_receipt_protobuf_object: Any) -> "TransactionReceipt": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'transaction_receipt_protobuf_object' argument. :param transaction_receipt_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'transaction_receipt_protobuf_object' argument. """ transaction_receipt_dict = DictProtobufStructSerializer.decode( transaction_receipt_protobuf_object.transaction_receipt ) return cls( transaction_receipt_dict["ledger_id"], transaction_receipt_dict["receipt"], transaction_receipt_dict["transaction"], ) def __eq__(self, other: Any) -> bool: """Check equality.""" return ( isinstance(other, TransactionReceipt) and self.ledger_id == other.ledger_id and self.receipt == other.receipt and self.transaction == other.transaction ) def __str__(self) -> str: """Get string representation.""" return "TransactionReceipt: ledger_id={}, receipt={}, transaction={}".format( self.ledger_id, self.receipt, self.transaction ) ================================================ FILE: aea/helpers/win32.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Helpers for Windows.""" import ctypes import logging import platform _default_logger = logging.getLogger(__name__) def enable_ctrl_c_support() -> None: # pragma: no cover """Enable ctrl+c support for aea.cli command to be tested on windows platform.""" if platform.system() != "Windows": return kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) # type: ignore if not kernel32.SetConsoleCtrlHandler(None, False): _default_logger.debug(f"SetConsoleCtrlHandler Error: {ctypes.get_last_error()}") # type: ignore ================================================ FILE: aea/helpers/yaml_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Helper functions related to YAML loading/dumping.""" from collections import OrderedDict from typing import Any, Dict, List, Optional, Sequence, TextIO import yaml from yaml import MappingNode class _AEAYamlLoader(yaml.SafeLoader): """ Custom yaml.SafeLoader for the AEA framework. It extends the default SafeLoader in two ways: - loads YAML configurations while *remembering the order of the fields*; - resolves the environment variables at loading time. This class is for internal usage only; please use the public functions of the module 'yaml_load' and 'yaml_load_all'. """ def __init__(self, *args: Any, **kwargs: Any) -> None: """ Initialize the AEAYamlLoader. It adds a YAML Loader constructor to use 'OderedDict' to load the files. :param args: the positional arguments. :param kwargs: the keyword arguments. """ super().__init__(*args, **kwargs) _AEAYamlLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, self._construct_mapping ) @staticmethod def _construct_mapping(loader: "_AEAYamlLoader", node: MappingNode) -> OrderedDict: """Construct a YAML mapping with OrderedDict.""" object_pairs_hook = OrderedDict loader.flatten_mapping(node) return object_pairs_hook(loader.construct_pairs(node)) class _AEAYamlDumper(yaml.SafeDumper): """ Custom yaml.SafeDumper for the AEA framework. It extends the default SafeDumper so to dump YAML configurations while *following the order of the fields*. This class is for internal usage only; please use the public functions of the module 'yaml_dump' and 'yaml_dump_all'. """ def __init__(self, *args: Any, **kwargs: Any) -> None: """ Initialize the AEAYamlDumper. It adds a YAML Dumper representer to use 'OderedDict' to dump the files. :param args: the positional arguments. :param kwargs: the keyword arguments. """ super().__init__(*args, **kwargs) _AEAYamlDumper.add_representer(OrderedDict, self._dict_representer) @staticmethod def _dict_representer(dumper: "_AEAYamlDumper", data: OrderedDict) -> MappingNode: """Use a custom representer.""" return dumper.represent_mapping( yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items() ) def yaml_load(stream: TextIO) -> Dict[str, Any]: """ Load a yaml from a file pointer in an ordered way. :param stream: file pointer to the input file. :return: the dictionary object with the YAML file content. """ result = yaml.load(stream, Loader=_AEAYamlLoader) # nosec return result if result is not None else {} def yaml_load_all(stream: TextIO) -> List[Dict[str, Any]]: """ Load a multi-paged yaml from a file pointer in an ordered way. :param stream: file pointer to the input file. :return: the list of dictionary objects with the (multi-paged) YAML file content. """ return list(yaml.load_all(stream, Loader=_AEAYamlLoader)) # nosec def yaml_dump(data: Dict, stream: Optional[TextIO] = None) -> None: """ Dump YAML data to a yaml file in an ordered way. :param data: the data to write. :param stream: (optional) the file to write on. """ yaml.dump(data, stream=stream, Dumper=_AEAYamlDumper) # nosec def yaml_dump_all(data: Sequence[Dict], stream: Optional[TextIO] = None) -> None: """ Dump YAML data to a yaml file in an ordered way. :param data: the data to write. :param stream: (optional) the file to write on. """ yaml.dump_all(data, stream=stream, Dumper=_AEAYamlDumper) # nosec ================================================ FILE: aea/identity/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the identity modules.""" ================================================ FILE: aea/identity/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the identity class.""" from typing import Dict, Optional from aea.common import Address from aea.configurations.constants import DEFAULT_LEDGER from aea.exceptions import enforce from aea.helpers.base import SimpleId, SimpleIdOrStr class Identity: """ The identity holds the public elements identifying an agent. It includes: - the agent name - the addresses, a map from address identifier to address (can be a single key-value pair) """ __slots__ = ( "_name", "_address", "_public_key", "_public_keys", "_addresses", "_default_address_key", ) def __init__( self, name: SimpleIdOrStr, address: Optional[str] = None, public_key: Optional[str] = None, addresses: Optional[Dict[str, Address]] = None, public_keys: Optional[Dict[str, str]] = None, default_address_key: str = DEFAULT_LEDGER, ) -> None: """ Instantiate the identity. :param name: the name of the agent. :param address: the default address of the agent. :param public_key: the public key of the agent. :param addresses: the addresses of the agent. :param public_keys: the public keys of the agent. :param default_address_key: the key for the default address. """ self._name = SimpleId(name) if default_address_key is None: raise ValueError( "Provide a key for the default address." ) # pragma: nocover if (address is None) == (addresses is None): raise ValueError( "Either provide a single address or a dictionary of addresses, and not both." ) if address is None: if addresses is None or len(addresses) == 0: # pragma: nocover raise ValueError("Provide at least one pair of addresses.") if public_key is not None: raise ValueError( "If you provide a dictionary of addresses, you must not provide a single public key." ) if public_keys is None: raise ValueError( "If you provide a dictionary of addresses, you must provide its corresponding dictionary of public keys." ) enforce( public_keys.keys() == addresses.keys(), "Keys in public keys and addresses dictionaries do not match. They must be identical.", ) enforce( default_address_key in addresses and default_address_key in public_keys, "The default address key must exist in both addresses and public keys dictionaries.", ) address = addresses[default_address_key] public_key = public_keys[default_address_key] if addresses is None: if public_keys is not None: raise ValueError( "If you provide a single address, you must not provide a dictionary of public keys." ) if public_key is None: raise ValueError( "If you provide a single address, you must provide its corresponding public key." ) addresses = {default_address_key: address} public_keys = {default_address_key: public_key} self._address = address self._addresses = addresses self._public_key = public_key self._public_keys = public_keys self._default_address_key = default_address_key @property def default_address_key(self) -> str: """Get the default address key.""" return self._default_address_key @property def name(self) -> str: """Get the agent name.""" return str(self._name) @property def addresses(self) -> Dict[str, Address]: """Get the addresses.""" return self._addresses @property def address(self) -> Address: """Get the default address.""" return self._address @property def public_keys(self) -> Dict[str, str]: """Get the public keys.""" return self._public_keys # type: ignore @property def public_key(self) -> str: """Get the default public key.""" return self._public_key # type: ignore ================================================ FILE: aea/launcher.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of multiple AEA configs launcher.""" import logging import multiprocessing from asyncio.events import AbstractEventLoop from concurrent.futures.process import BrokenProcessPool from multiprocessing.synchronize import Event from os import PathLike from threading import Thread from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, Union from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.exceptions import AEAException from aea.helpers.base import cd from aea.helpers.multiple_executor import ( AbstractExecutorTask, AbstractMultipleExecutor, AbstractMultipleRunner, AbstractMultiprocessExecutorTask, AsyncExecutor, ExecutorExceptionPolicies, ProcessExecutor, TaskAwaitable, ThreadExecutor, ) from aea.runtime import AsyncRuntime _default_logger = logging.getLogger(__name__) def load_agent(agent_dir: Union[PathLike, str], password: Optional[str] = None) -> AEA: """ Load AEA from directory. :param agent_dir: agent configuration directory :param password: the password to encrypt/decrypt the private key. :return: AEA instance """ with cd(agent_dir): return AEABuilder.from_aea_project(".", password=password).build( password=password ) def _set_logger( log_level: Optional[str], ) -> None: # pragma: nocover # used in spawned process and pytest does not see this code from aea.cli.utils.loggers import ( # pylint: disable=import-outside-toplevel default_logging_config, ) logger_ = logging.getLogger("aea") logger_ = default_logging_config(logger_) if log_level is not None: level = logging.getLevelName(log_level) logger_.setLevel(level) def _run_agent( agent_dir: Union[PathLike, str], stop_event: Event, log_level: Optional[str] = None, password: Optional[str] = None, ) -> None: """ Load and run agent in a dedicated process. :param agent_dir: agent configuration directory :param stop_event: multithreading Event to stop agent run. :param log_level: debug level applied for AEA in subprocess :param password: the password to encrypt/decrypt the private key. """ import asyncio # pylint: disable=import-outside-toplevel import select # pylint: disable=import-outside-toplevel import selectors # pylint: disable=import-outside-toplevel if hasattr(select, "kqueue"): # pragma: nocover # cause platform specific selector = selectors.SelectSelector() loop = asyncio.SelectorEventLoop(selector) # type: ignore asyncio.set_event_loop(loop) try: asyncio.get_event_loop() except Exception: # pylint: disable=broad-except asyncio.set_event_loop(asyncio.new_event_loop()) _set_logger(log_level=log_level) agent = load_agent(agent_dir, password=password) def stop_event_thread() -> None: try: stop_event.wait() except (KeyboardInterrupt, EOFError, BrokenPipeError) as e: # pragma: nocover _default_logger.debug( f"Exception raised in stop_event_thread {e} {type(e)}. Skip it, looks process is closed." ) finally: _default_logger.debug("_run_agent: stop event raised. call agent.stop") agent.runtime.stop() Thread(target=stop_event_thread, daemon=True).start() try: agent.start() except KeyboardInterrupt: # pragma: nocover _default_logger.debug("_run_agent: keyboard interrupt") except BaseException as e: # pragma: nocover _default_logger.exception("exception in _run_agent") exc = AEAException(f"Raised {type(e)}({e})") exc.__traceback__ = e.__traceback__ raise exc finally: _default_logger.debug("_run_agent: call agent.stop") agent.stop() class AEADirTask(AbstractExecutorTask): """Task to run agent from agent configuration directory.""" def __init__( self, agent_dir: Union[PathLike, str], password: Optional[str] = None ) -> None: """ Init aea config dir task. :param agent_dir: directory with aea config. :param password: the password to encrypt/decrypt the private key. """ self._agent_dir = agent_dir self._agent: AEA = load_agent(self._agent_dir, password=password) super().__init__() @property def id(self) -> Union[PathLike, str]: """Return agent_dir.""" return self._agent_dir def start(self) -> None: # type: ignore """Start task.""" self._agent.start() def stop(self) -> None: """Stop task.""" if not self._agent: # pragma: nocover raise ValueError("Task was not started!") self._agent.stop() def create_async_task(self, loop: AbstractEventLoop) -> TaskAwaitable: """Return asyncio Task for task run in asyncio loop.""" self._agent.runtime.set_loop(loop) if not isinstance(self._agent.runtime, AsyncRuntime): # pragma: nocover raise ValueError( "Agent runtime is not async compatible. Please use runtime_mode=async" ) return loop.create_task(self._agent.runtime.start_and_wait_completed()) # type: ignore class AEADirMultiprocessTask(AbstractMultiprocessExecutorTask): """ Task to run agent from agent configuration directory. Version for multiprocess executor mode. """ def __init__( self, agent_dir: Union[PathLike, str], log_level: Optional[str] = None, password: Optional[str] = None, ) -> None: """ Init aea config dir task. :param agent_dir: directory with aea config. :param log_level: debug level applied for AEA in subprocess :param password: the password to encrypt/decrypt the private key. """ self._agent_dir = agent_dir self._manager = multiprocessing.Manager() self._stop_event = self._manager.Event() self._log_level = log_level self._password = password super().__init__() @property def id(self) -> Union[PathLike, str]: """Return agent_dir.""" return self._agent_dir @property def failed(self) -> bool: """ Return was exception failed or not. If it's running it's not failed. :return: bool """ if not self._future: return False if ( self._future.done() and self._future.exception() and isinstance(self._future.exception(), BrokenProcessPool) ): # pragma: nocover return False return super().failed def start(self) -> Tuple[Callable, Sequence[Any]]: """Return function and arguments to call within subprocess.""" return ( _run_agent, (self._agent_dir, self._stop_event, self._log_level, self._password), ) def stop(self) -> None: """Stop task.""" if not self._future: # pragma: nocover _default_logger.debug("Stop called, but no future set.") return if self._future.done(): _default_logger.debug("Stop called, but task is already done.") return try: self._stop_event.set() except (FileNotFoundError, BrokenPipeError, EOFError) as e: # pragma: nocover _default_logger.debug( f"Exception raised in task.stop {e} {type(e)}. Skip it, looks process is closed." ) class AEALauncher(AbstractMultipleRunner): """Run multiple AEA instances.""" SUPPORTED_MODES: Dict[str, Type[AbstractMultipleExecutor]] = { "threaded": ThreadExecutor, "async": AsyncExecutor, "multiprocess": ProcessExecutor, } def __init__( self, agent_dirs: Sequence[Union[PathLike, str]], mode: str, fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies.propagate, log_level: Optional[str] = None, password: Optional[str] = None, ) -> None: """ Init AEALauncher. :param agent_dirs: sequence of AEA config directories. :param mode: executor name to use. :param fail_policy: one of ExecutorExceptionPolicies to be used with Executor :param log_level: debug level applied for AEA in subprocesses :param password: the password to encrypt/decrypt the private key. """ self._agent_dirs = agent_dirs self._log_level = log_level self._password = password super().__init__(mode=mode, fail_policy=fail_policy) def _make_tasks(self) -> Sequence[AbstractExecutorTask]: """Make tasks to run with executor.""" if self._mode == "multiprocess": return [ AEADirMultiprocessTask( agent_dir, log_level=self._log_level, password=self._password ) for agent_dir in self._agent_dirs ] return [ AEADirTask(agent_dir, password=self._password) for agent_dir in self._agent_dirs ] ================================================ FILE: aea/mail/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the mail modules.""" ================================================ FILE: aea/mail/base.proto ================================================ syntax = "proto3"; package aea.base.v0_1_0; import "google/protobuf/struct.proto"; message DialogueMessage { int32 message_id = 1; string dialogue_starter_reference = 2; string dialogue_responder_reference = 3; int32 target = 4; bytes content = 5; } message Message { oneof message { google.protobuf.Struct body = 1; DialogueMessage dialogue_message = 2; } } message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; bytes message = 4; string uri = 5; } ================================================ FILE: aea/mail/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Mail module abstract base classes.""" import logging from abc import ABC, abstractmethod from typing import Any, Optional, Union from urllib.parse import urlparse from aea.common import Address from aea.configurations.base import PublicId from aea.exceptions import enforce from aea.mail import base_pb2 from aea.protocols.base import Message _default_logger = logging.getLogger(__name__) class URI: """URI following RFC3986.""" __slots__ = ("_uri_raw",) def __init__(self, uri_raw: str) -> None: """ Initialize the URI. Must follow: https://tools.ietf.org/html/rfc3986.html :param uri_raw: the raw form uri """ self._uri_raw = uri_raw @property def scheme(self) -> str: """Get the scheme.""" parsed = urlparse(self._uri_raw) return parsed.scheme @property def netloc(self) -> str: """Get the netloc.""" parsed = urlparse(self._uri_raw) return parsed.netloc @property def path(self) -> str: """Get the path.""" parsed = urlparse(self._uri_raw) return parsed.path @property def params(self) -> str: """Get the params.""" parsed = urlparse(self._uri_raw) return parsed.params @property def query(self) -> str: """Get the query.""" parsed = urlparse(self._uri_raw) return parsed.query @property def fragment(self) -> str: """Get the fragment.""" parsed = urlparse(self._uri_raw) return parsed.fragment @property def username(self) -> Optional[str]: """Get the username.""" parsed = urlparse(self._uri_raw) return parsed.username @property def password(self) -> Optional[str]: """Get the password.""" parsed = urlparse(self._uri_raw) return parsed.password @property def host(self) -> Optional[str]: """Get the host.""" parsed = urlparse(self._uri_raw) return parsed.hostname @property def port(self) -> Optional[int]: """Get the port.""" parsed = urlparse(self._uri_raw) return parsed.port def __str__(self) -> str: """Get string representation.""" return self._uri_raw def __eq__(self, other: Any) -> bool: """Compare with another object.""" return isinstance(other, URI) and str(self) == str(other) class EnvelopeContext: """Contains context information of an envelope.""" __slots__ = ("_connection_id", "_uri") def __init__( self, connection_id: Optional[PublicId] = None, uri: Optional[URI] = None, ) -> None: """ Initialize the envelope context. :param connection_id: the connection id used for routing the outgoing envelope in the multiplexer. :param uri: the URI sent with the envelope. """ self._connection_id = connection_id self._uri = uri @property def uri(self) -> Optional[URI]: """Get the URI.""" return self._uri @property def connection_id(self) -> Optional[PublicId]: """Get the connection id to route the envelope.""" return self._connection_id @connection_id.setter def connection_id(self, connection_id: PublicId) -> None: """Set the 'via' connection id.""" if self._connection_id is not None: raise ValueError("connection_id already set!") # pragma: nocover self._connection_id = connection_id def __str__(self) -> str: """Get the string representation.""" return f"EnvelopeContext(connection_id={self.connection_id}, uri={self.uri})" def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, EnvelopeContext) and self.connection_id == other.connection_id and self.uri == other.uri ) class AEAConnectionError(Exception): """Exception class for connection errors.""" class Empty(Exception): """Exception for when the inbox is empty.""" class EnvelopeSerializer(ABC): """Abstract class to specify the serialization layer for the envelope.""" @abstractmethod def encode(self, envelope: "Envelope") -> bytes: """ Encode the envelope. :param envelope: the envelope to encode :return: the encoded envelope """ @abstractmethod def decode(self, envelope_bytes: bytes) -> "Envelope": """ Decode the envelope. :param envelope_bytes: the encoded envelope :return: the envelope """ class ProtobufEnvelopeSerializer(EnvelopeSerializer): """Envelope serializer using Protobuf.""" def encode(self, envelope: "Envelope") -> bytes: """ Encode the envelope. :param envelope: the envelope to encode :return: the encoded envelope """ envelope_pb = base_pb2.Envelope() envelope_pb.to = envelope.to envelope_pb.sender = envelope.sender envelope_pb.protocol_id = str(envelope.protocol_specification_id) envelope_pb.message = envelope.message_bytes if envelope.context is not None and envelope.context.uri is not None: envelope_pb.uri = str(envelope.context.uri) envelope_bytes = envelope_pb.SerializeToString() return envelope_bytes def decode(self, envelope_bytes: bytes) -> "Envelope": """ Decode the envelope. The default serializer doesn't decode the message field. :param envelope_bytes: the encoded envelope :return: the envelope """ envelope_pb = base_pb2.Envelope() envelope_pb.ParseFromString(envelope_bytes) to = envelope_pb.to # pylint: disable=no-member sender = envelope_pb.sender # pylint: disable=no-member raw_protocol_id = envelope_pb.protocol_id # pylint: disable=no-member protocol_specification_id = PublicId.from_str(raw_protocol_id) message = envelope_pb.message # pylint: disable=no-member uri_raw = envelope_pb.uri # pylint: disable=no-member if uri_raw != "": # empty string means this field is not set in proto3 uri = URI(uri_raw=uri_raw) context = EnvelopeContext(uri=uri) envelope = Envelope( to=to, sender=sender, protocol_specification_id=protocol_specification_id, message=message, context=context, ) else: envelope = Envelope( to=to, sender=sender, protocol_specification_id=protocol_specification_id, message=message, ) return envelope DefaultEnvelopeSerializer = ProtobufEnvelopeSerializer class Envelope: """The top level message class for agent to agent communication.""" default_serializer = DefaultEnvelopeSerializer() __slots__ = ("_to", "_sender", "_protocol_specification_id", "_message", "_context") def __init__( self, to: Address, sender: Address, message: Union[Message, bytes], context: Optional[EnvelopeContext] = None, protocol_specification_id: Optional[PublicId] = None, ) -> None: """ Initialize a Message object. :param to: the address of the receiver. :param sender: the address of the sender. :param message: the protocol-specific message. :param context: the optional envelope context. :param protocol_specification_id: the protocol specification id (wire id). """ enforce(isinstance(to, str), f"To must be string. Found '{type(to)}'") enforce( isinstance(sender, str), f"Sender must be string. Found '{type(sender)}'" ) enforce( isinstance(message, (Message, bytes)), "message should be a type of Message or bytes!", ) if isinstance(message, Message): message = self._check_consistency(message, to, sender) self._to = to self._sender = sender enforce( self.is_to_public_id == self.is_sender_public_id, "To and sender must either both be agent addresses or both be public ids of AEA components.", ) if isinstance(message, bytes): if protocol_specification_id is None: raise ValueError( "Message is bytes object, protocol_specification_id must be provided!" ) elif isinstance(message, Message): if message.protocol_id is None: raise ValueError( # pragma: nocover f"message class {type(message)} has no protocol_id specified!" ) protocol_specification_id = message.protocol_specification_id if protocol_specification_id is None: raise ValueError( "Message is Message object, protocol_specification_id could not be resolved! Ensure protocol is valid!" ) else: raise ValueError( f"Message type: {type(message)} is not supported!" ) # pragma: nocover self._protocol_specification_id: PublicId = protocol_specification_id self._message = message if self.is_component_to_component_message: enforce( context is None, "EnvelopeContext must be None for component to component messages.", ) self._context = context @property def to(self) -> Address: """Get address of receiver.""" return self._to @to.setter def to(self, to: Address) -> None: """Set address of receiver.""" enforce(isinstance(to, str), f"To must be string. Found '{type(to)}'") self._to = to @property def sender(self) -> Address: """Get address of sender.""" return self._sender @sender.setter def sender(self, sender: Address) -> None: """Set address of sender.""" enforce( isinstance(sender, str), f"Sender must be string. Found '{type(sender)}'" ) self._sender = sender @property def protocol_specification_id(self) -> PublicId: """Get protocol_specification_id.""" return self._protocol_specification_id @property def message(self) -> Union[Message, bytes]: """Get the protocol-specific message.""" return self._message @message.setter def message(self, message: Union[Message, bytes]) -> None: """Set the protocol-specific message.""" self._message = message @property def message_bytes(self) -> bytes: """Get the protocol-specific message.""" if isinstance(self._message, Message): return self._message.encode() return self._message @property def context(self) -> Optional[EnvelopeContext]: """Get the envelope context.""" return self._context @property def to_as_public_id(self) -> Optional[PublicId]: """Get to as public id.""" return PublicId.try_from_str(self.to) @property def is_sender_public_id(self) -> bool: """Check if sender is a public id.""" return PublicId.is_valid_str(self.sender) @property def is_to_public_id(self) -> bool: """Check if to is a public id.""" return PublicId.is_valid_str(self.to) @property def is_component_to_component_message(self) -> bool: """Whether or not the message contained is component to component.""" return self.is_to_public_id and self.is_sender_public_id @staticmethod def _check_consistency(message: Message, to: str, sender: str) -> Message: """Check consistency of sender and to.""" if message.has_to: enforce( message.to == to, "To specified on message does not match envelope." ) else: message.to = to if message.has_sender: enforce( message.sender == sender, "Sender specified on message does not match envelope.", ) else: message.sender = sender return message def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, Envelope) and self.to == other.to and self.sender == other.sender and self.protocol_specification_id == other.protocol_specification_id and self.message == other.message and self.context == other.context ) def encode( self, serializer: Optional[EnvelopeSerializer] = None, ) -> bytes: """ Encode the envelope. :param serializer: the serializer that implements the encoding procedure. :return: the encoded envelope. """ if serializer is None: serializer = self.default_serializer envelope_bytes = serializer.encode(self) return envelope_bytes @classmethod def decode( cls, envelope_bytes: bytes, serializer: Optional[EnvelopeSerializer] = None ) -> "Envelope": """ Decode the envelope. :param envelope_bytes: the bytes to be decoded. :param serializer: the serializer that implements the decoding procedure. :return: the decoded envelope. """ if serializer is None: serializer = cls.default_serializer envelope = serializer.decode(envelope_bytes) return envelope def __str__(self) -> str: """Get the string representation of an envelope.""" return "Envelope(to={to}, sender={sender}, protocol_specification_id={protocol_specification_id}, message={message})".format( to=self.to, sender=self.sender, protocol_specification_id=self.protocol_specification_id, message="{!r}".format(self.message) if isinstance(self.message, bytes) else self.message, ) ================================================ FILE: aea/mail/base_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: base.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 DESCRIPTOR = _descriptor.FileDescriptor( name="base.proto", package="aea.base.v0_1_0", syntax="proto3", serialized_options=None, create_key=_descriptor._internal_create_key, serialized_pb=b'\n\nbase.proto\x12\x0f\x61\x65\x61.base.v0_1_0\x1a\x1cgoogle/protobuf/struct.proto"\x90\x01\n\x0f\x44ialogueMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12\x0f\n\x07\x63ontent\x18\x05 \x01(\x0c"{\n\x07Message\x12\'\n\x04\x62ody\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12<\n\x10\x64ialogue_message\x18\x02 \x01(\x0b\x32 .aea.base.v0_1_0.DialogueMessageH\x00\x42\t\n\x07message"Y\n\x08\x45nvelope\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\x0c\x12\x0b\n\x03uri\x18\x05 \x01(\tb\x06proto3', dependencies=[ google_dot_protobuf_dot_struct__pb2.DESCRIPTOR, ], ) _DIALOGUEMESSAGE = _descriptor.Descriptor( name="DialogueMessage", full_name="aea.base.v0_1_0.DialogueMessage", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="message_id", full_name="aea.base.v0_1_0.DialogueMessage.message_id", index=0, number=1, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="dialogue_starter_reference", full_name="aea.base.v0_1_0.DialogueMessage.dialogue_starter_reference", index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="dialogue_responder_reference", full_name="aea.base.v0_1_0.DialogueMessage.dialogue_responder_reference", index=2, number=3, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="target", full_name="aea.base.v0_1_0.DialogueMessage.target", index=3, number=4, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="content", full_name="aea.base.v0_1_0.DialogueMessage.content", index=4, number=5, type=12, cpp_type=9, label=1, has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=62, serialized_end=206, ) _MESSAGE = _descriptor.Descriptor( name="Message", full_name="aea.base.v0_1_0.Message", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="body", full_name="aea.base.v0_1_0.Message.body", index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="dialogue_message", full_name="aea.base.v0_1_0.Message.dialogue_message", index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[ _descriptor.OneofDescriptor( name="message", full_name="aea.base.v0_1_0.Message.message", index=0, containing_type=None, create_key=_descriptor._internal_create_key, fields=[], ), ], serialized_start=208, serialized_end=331, ) _ENVELOPE = _descriptor.Descriptor( name="Envelope", full_name="aea.base.v0_1_0.Envelope", filename=None, file=DESCRIPTOR, containing_type=None, create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name="to", full_name="aea.base.v0_1_0.Envelope.to", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="sender", full_name="aea.base.v0_1_0.Envelope.sender", index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="protocol_id", full_name="aea.base.v0_1_0.Envelope.protocol_id", index=2, number=3, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="message", full_name="aea.base.v0_1_0.Envelope.message", index=3, number=4, type=12, cpp_type=9, label=1, has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), _descriptor.FieldDescriptor( name="uri", full_name="aea.base.v0_1_0.Envelope.uri", index=4, number=5, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key, ), ], extensions=[], nested_types=[], enum_types=[], serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], serialized_start=333, serialized_end=422, ) _MESSAGE.fields_by_name[ "body" ].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT _MESSAGE.fields_by_name["dialogue_message"].message_type = _DIALOGUEMESSAGE _MESSAGE.oneofs_by_name["message"].fields.append(_MESSAGE.fields_by_name["body"]) _MESSAGE.fields_by_name["body"].containing_oneof = _MESSAGE.oneofs_by_name["message"] _MESSAGE.oneofs_by_name["message"].fields.append( _MESSAGE.fields_by_name["dialogue_message"] ) _MESSAGE.fields_by_name["dialogue_message"].containing_oneof = _MESSAGE.oneofs_by_name[ "message" ] DESCRIPTOR.message_types_by_name["DialogueMessage"] = _DIALOGUEMESSAGE DESCRIPTOR.message_types_by_name["Message"] = _MESSAGE DESCRIPTOR.message_types_by_name["Envelope"] = _ENVELOPE _sym_db.RegisterFileDescriptor(DESCRIPTOR) DialogueMessage = _reflection.GeneratedProtocolMessageType( "DialogueMessage", (_message.Message,), { "DESCRIPTOR": _DIALOGUEMESSAGE, "__module__": "base_pb2" # @@protoc_insertion_point(class_scope:aea.base.v0_1_0.DialogueMessage) }, ) _sym_db.RegisterMessage(DialogueMessage) Message = _reflection.GeneratedProtocolMessageType( "Message", (_message.Message,), { "DESCRIPTOR": _MESSAGE, "__module__": "base_pb2" # @@protoc_insertion_point(class_scope:aea.base.v0_1_0.Message) }, ) _sym_db.RegisterMessage(Message) Envelope = _reflection.GeneratedProtocolMessageType( "Envelope", (_message.Message,), { "DESCRIPTOR": _ENVELOPE, "__module__": "base_pb2" # @@protoc_insertion_point(class_scope:aea.base.v0_1_0.Envelope) }, ) _sym_db.RegisterMessage(Envelope) # @@protoc_insertion_point(module_scope) ================================================ FILE: aea/manager/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the manager modules.""" from aea.manager.manager import MultiAgentManager __all__ = ["MultiAgentManager"] ================================================ FILE: aea/manager/manager.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of AEA agents manager.""" import asyncio import datetime import json import multiprocessing import os import threading from abc import ABC, abstractmethod from asyncio.tasks import FIRST_COMPLETED from collections import defaultdict from multiprocessing.synchronize import Event from shutil import rmtree from threading import Thread from traceback import format_exc from typing import Any, Callable, Dict, List, Optional, Set, Tuple from aea.aea import AEA from aea.configurations.base import AgentConfig from aea.configurations.constants import AEA_MANAGER_DATA_DIRNAME, DEFAULT_REGISTRY_NAME from aea.configurations.data_types import PackageIdPrefix, PublicId from aea.exceptions import enforce from aea.helpers.io import open_file from aea.manager.project import AgentAlias, Project from aea.manager.utils import ( get_venv_dir_for_project, project_check, project_install_and_build, run_in_venv, ) class ProjectNotFoundError(ValueError): """Project not found exception.""" class ProjectCheckError(ValueError): """Project check error exception.""" def __init__(self, msg: str, source_exception: Exception): """Init exception.""" super().__init__(msg) self.source_exception = source_exception class ProjectPackageConsistencyCheckError(ValueError): """Check consistency of package versions against already added project.""" def __init__( self, agent_project_id: PublicId, conflicting_packages: List[Tuple[PackageIdPrefix, str, str, Set[PublicId]]], ): """ Initialize the exception. :param agent_project_id: the agent project id whose addition has failed. :param conflicting_packages: the conflicting packages. """ self.agent_project_id = agent_project_id self.conflicting_packages = conflicting_packages super().__init__(self._build_error_message()) def _build_error_message(self) -> str: """Build the error message.""" conflicting_packages = sorted(self.conflicting_packages, key=str) message = f"cannot add project '{self.agent_project_id}': the following AEA dependencies have conflicts with previously added projects:\n" for ( (type_, author, name), existing_version, new_version, agents, ) in conflicting_packages: message += f"- '{author}/{name}' of type {type_}: the new version '{new_version}' conflicts with existing version '{existing_version}' of the same package required by agents: {list(agents)}\n" return message class BaseAgentRunTask(ABC): """Base abstract class for agent run tasks.""" @abstractmethod def start(self) -> None: """Start task.""" @abstractmethod def wait(self) -> asyncio.Future: """Return future to wait task completed.""" @abstractmethod def stop(self) -> None: """Stop task.""" @property @abstractmethod def is_running(self) -> bool: """Return is task running.""" class AgentRunAsyncTask(BaseAgentRunTask): """Async task wrapper for agent.""" def __init__(self, agent: AEA, loop: asyncio.AbstractEventLoop) -> None: """Init task with agent alias and loop.""" self.agent = agent self.run_loop: asyncio.AbstractEventLoop = loop self.caller_loop: asyncio.AbstractEventLoop = loop self._done_future: Optional[asyncio.Future] = None self.task: Optional[asyncio.Task] = None def create_run_loop(self) -> None: """Create run loop.""" def start(self) -> None: """Start task.""" self.create_run_loop() self._done_future = asyncio.Future() self.task = self.run_loop.create_task(self._run_wrapper()) def wait(self) -> asyncio.Future: """Return future to wait task completed.""" if not self._done_future: # pragma: nocover raise ValueError("Task not started!") return self._done_future def stop(self) -> None: """Stop task.""" if not self.run_loop or not self.task: # pragma: nocover raise ValueError("Task was not started!") self.run_loop.call_soon_threadsafe(self.task.cancel) async def _run_wrapper(self) -> None: """Run task internals.""" if not self._done_future: # pragma: nocover raise ValueError("Task was not started! please use start method") exc = None try: await self.run() except asyncio.CancelledError: # pragma: nocover pass except Exception as e: # pylint: disable=broad-except exc = e finally: self.caller_loop.call_soon_threadsafe(self._set_result, exc) def _set_result(self, exc: Optional[BaseException]) -> None: """Set result of task execution.""" if not self._done_future or self._done_future.done(): # pragma: nocover return if exc: self._done_future.set_exception(exc) else: self._done_future.set_result(None) async def run(self) -> None: """Run task body.""" self.agent.runtime.set_loop(self.run_loop) await self.agent.runtime.run() @property def is_running(self) -> bool: """Return is task running.""" return not self.wait().done() class AgentRunThreadTask(AgentRunAsyncTask): """Threaded wrapper to run agent.""" def __init__(self, agent: AEA, loop: asyncio.AbstractEventLoop) -> None: """Init task with agent alias and loop.""" AgentRunAsyncTask.__init__(self, agent, loop) self._thread: Optional[Thread] = None def create_run_loop(self) -> None: """Create run loop.""" self.run_loop = asyncio.new_event_loop() def start(self) -> None: """Run task in a dedicated thread.""" super().start() self._thread = threading.Thread( target=self.run_loop.run_until_complete, args=[self.task], daemon=True ) self._thread.start() def stop( self, ) -> None: """Stop the task.""" super().stop() if self._thread is not None: self._thread.join() class AgentRunProcessTask(BaseAgentRunTask): """Subprocess wrapper to run agent.""" PROCESS_JOIN_TIMEOUT = 20 # in seconds PROCESS_ALIVE_SLEEP_TIME = 0.005 # in seconds def __init__( # pylint: disable=super-init-not-called self, agent_alias: AgentAlias, loop: asyncio.AbstractEventLoop ) -> None: """Init task with agent alias and loop.""" self.caller_loop: asyncio.AbstractEventLoop = loop self._manager = multiprocessing.Manager() self._stop_event = self._manager.Event() self.agent_alias = agent_alias self.process: Optional[multiprocessing.Process] = None self._wait_task: Optional[asyncio.Future] = None self._result_queue = self._manager.Queue() def start(self) -> None: """Run task in a dedicated process.""" self._wait_task = asyncio.ensure_future( self._wait_for_result(), loop=self.caller_loop ) self.process = multiprocessing.Process( target=self._run_agent, args=(self.agent_alias, self._stop_event, self._result_queue), ) self.process.start() async def _wait_for_result(self) -> Any: """Wait for the result of the function call.""" if not self.process: raise ValueError("Task not started!") # pragma: nocover while self.process.is_alive(): await asyncio.sleep(self.PROCESS_ALIVE_SLEEP_TIME) result = self._result_queue.get_nowait() self.process.join(self.PROCESS_JOIN_TIMEOUT) if isinstance(result, Exception): raise result return result def wait(self) -> asyncio.Future: """Return future to wait task completed.""" if not self._wait_task: raise ValueError("Task not started") # pragma: nocover return self._wait_task @staticmethod def _run_agent( agent_alias: AgentAlias, stop_event: Event, result_queue: multiprocessing.Queue, ) -> None: """Start an agent in a child process.""" t: Optional[Thread] = None r: Optional[Exception] = None run_stop_thread: bool = True # set a new event loop, cause it's a new process asyncio.set_event_loop(asyncio.new_event_loop()) try: aea = agent_alias.get_aea_instance() def stop_event_thread() -> None: try: while run_stop_thread: if stop_event.wait(0.01) is True: break finally: aea.runtime.stop() t = Thread(target=stop_event_thread, daemon=True) t.start() loop = asyncio.get_event_loop() aea.runtime.set_loop(loop) aea.runtime.start() loop.run_until_complete(aea.runtime.wait_completed()) except BaseException as e: # pylint: disable=broad-except print( f"Exception in agent subprocess task at {datetime.datetime.now()}:\n{format_exc()}" ) r = Exception(str(e), repr(e)) finally: run_stop_thread = False if t: t.join(10) result_queue.put(r) aea.logger.debug("process task stopped") def stop(self) -> None: """Stop the task.""" if not self.process: raise ValueError("Task not started!") # pragma: nocover self._stop_event.set() self.process.join(self.PROCESS_JOIN_TIMEOUT) if self.is_running: # pragma: nocover self.process.terminate() self.process.join(5) raise ValueError( f"process was not stopped within timeout: {self.PROCESS_JOIN_TIMEOUT} and was terminated" ) @property def is_running(self) -> bool: """Is agent running.""" if not self.process: raise ValueError("Task not started!") # pragma: nocover return self.process.is_alive() ASYNC_MODE = "async" THREADED_MODE = "threaded" MULTIPROCESS_MODE = "multiprocess" class MultiAgentManager: """Multi agents manager.""" MODES = [ASYNC_MODE, THREADED_MODE, MULTIPROCESS_MODE] _MODE_TASK_CLASS = { ASYNC_MODE: AgentRunAsyncTask, THREADED_MODE: AgentRunThreadTask, MULTIPROCESS_MODE: AgentRunProcessTask, } DEFAULT_TIMEOUT_FOR_BLOCKING_OPERATIONS = 60 VENV_BUILD_TIMEOUT = 240 SAVE_FILENAME = "save.json" def __init__( self, working_dir: str, mode: str = "async", registry_path: str = DEFAULT_REGISTRY_NAME, auto_add_remove_project: bool = False, password: Optional[str] = None, ) -> None: """ Initialize manager. :param working_dir: directory to store base agents. :param mode: str. async or threaded :param registry_path: str. path to the local packages registry :param auto_add_remove_project: bool. add/remove project on the first agent add/last agent remove :param password: the password to encrypt/decrypt the private key. """ self.working_dir = working_dir self._auto_add_remove_project = auto_add_remove_project self._save_path = os.path.join(self.working_dir, self.SAVE_FILENAME) self.registry_path = registry_path self._was_working_dir_created = False self._is_running = False self._projects: Dict[PublicId, Project] = {} self._versionless_projects_set: Set[PublicId] = set() self._data_dir = os.path.abspath( os.path.join(self.working_dir, AEA_MANAGER_DATA_DIRNAME) ) self._agents: Dict[str, AgentAlias] = {} self._agents_tasks: Dict[str, BaseAgentRunTask] = {} self._thread: Optional[Thread] = None self._loop: Optional[asyncio.AbstractEventLoop] = None self._event: Optional[asyncio.Event] = None self._error_callbacks: List[Callable[[str, BaseException], None]] = [ self._default_error_callback ] self._custom_callback_added: bool = False self._last_start_status: Optional[ Tuple[ bool, Dict[PublicId, List[Dict]], List[Tuple[PublicId, List[Dict], Exception]], ] ] = None if mode not in self.MODES: raise ValueError( f'Invalid mode {mode}. Valid modes are {", ".join(self.MODES)}' ) self._started_event = threading.Event() self._mode = mode self._password = password # this flags will control whether we have already printed the warning message # for a certain agent self._warning_message_printed_for_agent: Dict[str, bool] = {} # the dictionary keeps track of the AEA packages used across # AEA projects in the same MAM. # It maps package prefixes to a pair: (version, agent_ids) # where agent_ids is the set of agent ids whose projects # have the package in the key at the specific version. self._package_id_prefix_to_version: Dict[ PackageIdPrefix, Tuple[str, Set[PublicId]] ] = {} @property def data_dir(self) -> str: """Get the certs directory.""" return self._data_dir def get_data_dir_of_agent(self, agent_name: str) -> str: """Get the data directory of a specific agent.""" return os.path.join(self.data_dir, agent_name) @property def is_running(self) -> bool: """Is manager running.""" return self._is_running @property def dict_state(self) -> Dict[str, Any]: """Create MultiAgentManager dist state.""" return { "projects": [str(public_id) for public_id in self._projects.keys()], "agents": [alias.dict for alias in self._agents.values()], } @property def projects(self) -> Dict[PublicId, Project]: """Get all projects.""" return self._projects def _run_thread(self) -> None: """Run internal thread with own event loop.""" self._loop = asyncio.new_event_loop() self._loop.run_until_complete(self._manager_loop()) async def _manager_loop(self) -> None: """Await and control running manager.""" self._event = asyncio.Event() self._started_event.set() while self._is_running: agents_run_tasks_futures = { task.wait(): agent_name for agent_name, task in self._agents_tasks.items() } wait_tasks = [ asyncio.ensure_future(i) for i in [*agents_run_tasks_futures.keys(), self._event.wait()] ] done, _ = await asyncio.wait(wait_tasks, return_when=FIRST_COMPLETED) if self._event.is_set(): self._event.clear() for task in done: if task not in agents_run_tasks_futures: # task not in agents_run_tasks_futures, so it's event_wait, skip it await task continue agent_name = agents_run_tasks_futures[task] self._agents_tasks.pop(agent_name) if task.exception(): for callback in self._error_callbacks: callback(agent_name, task.exception()) # type: ignore else: await task def add_error_callback( self, error_callback: Callable[[str, BaseException], None] ) -> "MultiAgentManager": """Add error callback to call on error raised.""" if len(self._error_callbacks) == 1 and not self._custom_callback_added: # only default callback present, reset before adding new callback self._custom_callback_added = True self._error_callbacks = [] self._error_callbacks.append(error_callback) return self def start_manager( self, local: bool = False, remote: bool = False ) -> "MultiAgentManager": """ Start manager. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). :param local: whether or not to fetch from local registry. :param remote: whether or not to fetch from remote registry. :return: the MultiAgentManager instance. """ if self._is_running: return self self._ensure_working_dir() self._last_start_status = self._load_state(local=local, remote=remote) self._started_event.clear() self._is_running = True self._thread = Thread(target=self._run_thread, daemon=True) self._thread.start() self._started_event.wait(self.DEFAULT_TIMEOUT_FOR_BLOCKING_OPERATIONS) return self @property def last_start_status( self, ) -> Tuple[ bool, Dict[PublicId, List[Dict]], List[Tuple[PublicId, List[Dict], Exception]], ]: """Get status of the last agents start loading state.""" if self._last_start_status is None: raise ValueError("Manager was not started") return self._last_start_status def stop_manager( self, cleanup: bool = True, save: bool = False ) -> "MultiAgentManager": """ Stop manager. Stops all running agents and stop agent. :param cleanup: bool is cleanup on stop. :param save: bool is save state to file on stop. :return: None """ if not self._is_running: return self if not self._loop or not self._event or not self._thread: # pragma: nocover raise ValueError("Manager was not started!") if not self._thread.is_alive(): # pragma: nocover return self self.stop_all_agents() if save: self._save_state() for agent_name in self.list_agents(): self.remove_agent(agent_name, skip_project_auto_remove=True) if cleanup: for project in list(self._projects.keys()): self.remove_project(project, keep_files=save) self._cleanup(only_data=save) self._is_running = False self._loop.call_soon_threadsafe(self._event.set) if self._thread.ident != threading.get_ident(): self._thread.join(self.DEFAULT_TIMEOUT_FOR_BLOCKING_OPERATIONS) self._thread = None self._warning_message_printed_for_agent = {} return self def _cleanup(self, only_data: bool = False) -> None: """Remove workdir if was created.""" if only_data: rmtree(self.data_dir) else: if self._was_working_dir_created and os.path.exists(self.working_dir): rmtree(self.working_dir) def add_project( self, public_id: PublicId, local: bool = False, remote: bool = False, restore: bool = False, ) -> "MultiAgentManager": """ Fetch agent project and all dependencies to working_dir. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). :param public_id: the public if of the agent project. :param local: whether or not to fetch from local registry. :param remote: whether or not to fetch from remote registry. :param restore: bool flag for restoring already fetched agent. :return: self """ if public_id.to_any() in self._versionless_projects_set: raise ValueError( f"The project ({public_id.author}/{public_id.name}) was already added!" ) project = Project.load( self.working_dir, public_id, local, remote, registry_path=self.registry_path, is_restore=restore, skip_aea_validation=False, ) if not restore: self._project_install_and_build(project) self._check_version_consistency(project.agent_config) try: self._check_project(project) except Exception as e: project.remove() raise ProjectCheckError( f"Failed to load project: {public_id} Error: {str(e)}", e ) self._add_new_package_versions(project.agent_config) self._versionless_projects_set.add(public_id.to_any()) self._projects[public_id] = project return self def _project_install_and_build(self, project: Project) -> None: """Build and install project dependencies.""" if self._mode == MULTIPROCESS_MODE: venv_dir = get_venv_dir_for_project(project) run_in_venv( venv_dir, project_install_and_build, self.VENV_BUILD_TIMEOUT, project ) else: venv_dir = get_venv_dir_for_project(project) project_install_and_build(project) def _check_project(self, project: Project) -> None: if self._mode == MULTIPROCESS_MODE: venv_dir = get_venv_dir_for_project(project) run_in_venv(venv_dir, project_check, 120, project) else: project_check(project) def remove_project( self, public_id: PublicId, keep_files: bool = False ) -> "MultiAgentManager": """Remove agent project.""" if public_id not in self._projects: raise ValueError(f"Project {public_id} is not present!") if self._projects[public_id].agents: raise ValueError( f"Can not remove projects with aliases exists: {self._projects[public_id].agents}" ) project = self._projects.pop(public_id) self._remove_package_versions(project.agent_config) self._versionless_projects_set.remove(public_id.to_any()) if not keep_files: project.remove() return self def list_projects(self) -> List[PublicId]: """ List all agents projects added. :return: list of public ids of projects """ return list(self._projects.keys()) def add_agent( self, public_id: PublicId, agent_name: Optional[str] = None, agent_overrides: Optional[dict] = None, component_overrides: Optional[List[dict]] = None, local: bool = False, remote: bool = False, restore: bool = False, ) -> "MultiAgentManager": """ Create new agent configuration based on project with config overrides applied. Alias is stored in memory only! :param public_id: base agent project public id :param agent_name: unique name for the agent :param agent_overrides: overrides for agent config. :param component_overrides: overrides for component section. :param local: whether or not to fetch from local registry. :param remote: whether or not to fetch from remote registry. :param restore: bool flag for restoring already fetched agent. :return: self """ agent_name = agent_name or public_id.name if agent_name in self._agents: raise ValueError(f"Agent with name {agent_name} already exists!") project = self._projects.get(public_id, None) if project is None and self._auto_add_remove_project: self.add_project(public_id, local, remote, restore) project = self._projects.get(public_id, None) if project is None: raise ProjectNotFoundError(f"{public_id} project is not added!") agent_alias = AgentAlias( project=project, agent_name=agent_name, data_dir=self.get_data_dir_of_agent(agent_name), password=self._password, ) agent_alias.set_overrides(agent_overrides, component_overrides) project.agents.add(agent_name) self._agents[agent_name] = agent_alias return self def add_agent_with_config( self, public_id: PublicId, config: List[dict], agent_name: Optional[str] = None, ) -> "MultiAgentManager": """ Create new agent configuration based on project with config provided. Alias is stored in memory only! :param public_id: base agent project public id :param agent_name: unique name for the agent :param config: agent config (used for agent re-creation). :return: manager """ agent_name = agent_name or public_id.name if agent_name in self._agents: # pragma: nocover raise ValueError(f"Agent with name {agent_name} already exists!") if public_id not in self._projects: # pragma: nocover raise ValueError(f"{public_id} project is not added!") project = self._projects[public_id] agent_alias = AgentAlias( project=project, agent_name=agent_name, data_dir=self.get_data_dir_of_agent(agent_name), password=self._password, ) agent_alias.set_agent_config_from_data(config) project.agents.add(agent_name) self._agents[agent_name] = agent_alias return self def get_agent_overridables(self, agent_name: str) -> Tuple[Dict, List[Dict]]: """ Get agent config overridables. :param agent_name: str :return: Tuple of agent overridables dict and and list of component overridables dict. """ if agent_name not in self._agents: # pragma: nocover raise ValueError(f"Agent with name {agent_name} does not exist!") return self._agents[agent_name].get_overridables() def set_agent_overrides( self, agent_name: str, agent_overides: Optional[Dict], components_overrides: Optional[List[Dict]], ) -> "MultiAgentManager": """ Set agent overrides. :param agent_name: str :param agent_overides: optional dict of agent config overrides :param components_overrides: optional list of dict of components overrides :return: self """ if agent_name not in self._agents: # pragma: nocover raise ValueError(f"Agent with name {agent_name} does not exist!") if self._is_agent_running(agent_name): # pragma: nocover raise ValueError("Agent is running. stop it first!") self._agents[agent_name].set_overrides(agent_overides, components_overrides) return self def list_agents_info(self) -> List[Dict[str, Any]]: """ List agents detailed info. :return: list of dicts that represents agent info: public_id, name, is_running. """ return [ { "agent_name": agent_name, "public_id": str(alias.project.public_id), "addresses": alias.get_addresses(), "is_running": self._is_agent_running(agent_name), } for agent_name, alias in self._agents.items() ] def list_agents(self, running_only: bool = False) -> List[str]: """ List all agents. :param running_only: returns only running if set to True :return: list of agents names """ if running_only: return [i for i in self._agents.keys() if self._is_agent_running(i)] return list(self._agents.keys()) def remove_agent( self, agent_name: str, skip_project_auto_remove: bool = False ) -> "MultiAgentManager": """ Remove agent alias definition from registry. :param agent_name: agent name to remove :param skip_project_auto_remove: disable auto project remove on last agent removed. :return: None """ if agent_name not in self._agents: raise ValueError(f"Agent with name {agent_name} does not exist!") if self._is_agent_running(agent_name): raise ValueError("Agent is running. stop it first!") agent_alias = self._agents.pop(agent_name) agent_alias.remove_from_project() project: Project = agent_alias.project if ( not project.agents and self._auto_add_remove_project and not skip_project_auto_remove ): self.remove_project(project.public_id, keep_files=False) return self def start_agent(self, agent_name: str) -> "MultiAgentManager": """ Start selected agent. :param agent_name: agent name to start :return: None """ if not self._loop or not self._event: # pragma: nocover raise ValueError("agent is not started!") agent_alias = self._agents.get(agent_name) if not agent_alias: raise ValueError(f"{agent_name} is not registered!") if self._is_agent_running(agent_name): raise ValueError(f"{agent_name} is already started!") event = threading.Event() self._loop.call_soon_threadsafe( self._make_agent_task, agent_name, agent_alias, event ) event.wait(30) # if something goes wrong del event self._loop.call_soon_threadsafe(self._event.set) return self def _make_agent_task( self, agent_name: str, agent_alias: AgentAlias, event: threading.Event ) -> None: """Create and start agent task.""" task_cls = self._MODE_TASK_CLASS[self._mode] if self._mode == MULTIPROCESS_MODE: task = task_cls(agent_alias, self._loop) else: agent = agent_alias.get_aea_instance() task = task_cls(agent, self._loop) self._agents_tasks[agent_name] = task task.start() event.set() def _is_agent_running(self, agent_name: str) -> bool: """Return is agent task in running state.""" if agent_name not in self._agents_tasks: return False task = self._agents_tasks[agent_name] return task.is_running def start_all_agents(self) -> "MultiAgentManager": """ Start all not started agents. :return: None """ self.start_agents( [ agent_name for agent_name in self.list_agents() if not self._is_agent_running(agent_name) ] ) return self def stop_agent(self, agent_name: str) -> "MultiAgentManager": """ Stop running agent. :param agent_name: agent name to stop :return: self """ if not self._is_agent_running(agent_name) or not self._thread or not self._loop: raise ValueError(f"{agent_name} is not running!") agent_task = self._agents_tasks[agent_name] if self._thread.ident == threading.get_ident(): # pragma: nocover # In same thread do not perform blocking operations! agent_task.stop() return self wait_future = agent_task.wait() event = threading.Event() def event_set(*args: Any) -> None: # pylint: disable=unused-argument event.set() def _add_cb() -> None: if wait_future.done(): event_set() # pragma: nocover else: wait_future.add_done_callback(event_set) # pragma: nocover self._loop.call_soon_threadsafe(_add_cb) agent_task.stop() event.wait(self.DEFAULT_TIMEOUT_FOR_BLOCKING_OPERATIONS) if agent_task.is_running: # pragma: nocover raise ValueError(f"cannot stop task of agent {agent_name}") return self def stop_all_agents(self) -> "MultiAgentManager": """ Stop all agents running. :return: self """ agents_list = self.list_agents(running_only=True) self.stop_agents(agents_list) return self def stop_agents(self, agent_names: List[str]) -> "MultiAgentManager": """ Stop specified agents. :param agent_names: names of agents :return: self """ for agent_name in agent_names: if not self._is_agent_running(agent_name): raise ValueError(f"{agent_name} is not running!") for agent_name in agent_names: self.stop_agent(agent_name) return self def start_agents(self, agent_names: List[str]) -> "MultiAgentManager": """ Stop specified agents. :param agent_names: names of agents :return: self """ for agent_name in agent_names: self.start_agent(agent_name) return self def get_agent_alias(self, agent_name: str) -> AgentAlias: """ Return details about agent alias definition. :param agent_name: name of agent :return: AgentAlias """ if agent_name not in self._agents: # pragma: nocover raise ValueError(f"Agent with name {agent_name} does not exist!") return self._agents[agent_name] def _ensure_working_dir(self) -> None: """Create working dir if needed.""" if not os.path.exists(self.working_dir): os.makedirs(self.working_dir) self._was_working_dir_created = True if not os.path.isdir(self.working_dir): # pragma: nocover raise ValueError(f"{self.working_dir} is not a directory!") if not os.path.exists(self.data_dir): os.makedirs(self.data_dir) def _load_state( self, local: bool, remote: bool ) -> Tuple[ bool, Dict[PublicId, List[Dict]], List[Tuple[PublicId, List[Dict], Exception]], ]: """ Load saved state from file. Fetch agent project and all dependencies to working_dir. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). :param local: whether or not to fetch from local registry. :param remote: whether or not to fetch from remote registry. :return: Tuple of bool indicating load success, settings of loaded, list of failed :raises: ValueError if failed to load state. """ if not os.path.exists(self._save_path): return False, {}, [] save_json = {} with open_file(self._save_path) as f: save_json = json.load(f) if not save_json: return False, {}, [] # pragma: nocover projects_agents: Dict[PublicId, List] = defaultdict(list) for agent_settings in save_json["agents"]: projects_agents[PublicId.from_str(agent_settings["public_id"])].append( agent_settings ) failed_to_load: List[Tuple[PublicId, List[Dict], Exception]] = [] loaded_ok: Dict[PublicId, List[Dict]] = {} for project_public_id, agents_settings in projects_agents.items(): try: self.add_project( project_public_id, local=local, remote=remote, restore=True, ) except ProjectCheckError as e: failed_to_load.append((project_public_id, agents_settings, e)) break for agent_settings in agents_settings: self.add_agent_with_config( public_id=PublicId.from_str(agent_settings["public_id"]), agent_name=agent_settings["agent_name"], config=agent_settings["config"], ) loaded_ok[project_public_id] = agents_settings return True, loaded_ok, failed_to_load def _save_state(self) -> None: """Save MultiAgentManager state.""" with open_file(self._save_path, "w") as f: json.dump(self.dict_state, f, indent=4, sort_keys=True) def _default_error_callback( self, agent_name: str, exception: BaseException ) -> None: """ Handle errors from running agents. This is the default error callback. To replace it with another one, use the method 'add_error_callback'. :param agent_name: the agent name :param exception: the caught exception """ self._print_exception_occurred_but_no_error_callback(agent_name, exception) def _print_exception_occurred_but_no_error_callback( self, agent_name: str, exception: BaseException ) -> None: """ Print a warning message when an exception occurred but no error callback is registered. :param agent_name: the agent name. :param exception: the caught exception. """ if self._warning_message_printed_for_agent.get(agent_name, False): return # pragma: nocover self._warning_message_printed_for_agent[agent_name] = True print( f"WARNING: An exception occurred during the execution of agent '{agent_name}':\n", str(exception), repr(exception), "\nHowever, since no error callback was found the exception is handled silently. Please " "add an error callback using the method 'add_error_callback' of the MultiAgentManager instance.", ) def _check_version_consistency(self, agent_config: AgentConfig) -> None: """ Check that the agent dependencies in input are consistent with the other projects. :param agent_config: the agent configuration we are going to add. :return: None :raises ProjectPackageConsistencyCheckError: if a version conflict is detected. """ existing_packages = set(self._package_id_prefix_to_version.keys()) prefix_to_version = { component_id.component_prefix: component_id.version for component_id in agent_config.package_dependencies } component_prefixes_to_be_added = set(prefix_to_version.keys()) potentially_conflicting_packages = existing_packages.intersection( component_prefixes_to_be_added ) if len(potentially_conflicting_packages) == 0: return # conflicting_packages is a list of tuples whose elements are: # - package id prefix: the triple (component type, author, name) # - current_version: the version currently present in the MAM, across all projects # - new_version: the version of the package in the new project # - agents: the set of agents in the MAM that have the package; # used to provide a better error message conflicting_packages: List[Tuple[PackageIdPrefix, str, str, Set[PublicId]]] = [] for package_prefix in potentially_conflicting_packages: existing_version, agents = self._package_id_prefix_to_version[ package_prefix ] new_version = prefix_to_version[package_prefix] if existing_version != new_version: conflicting_packages.append( (package_prefix, existing_version, new_version, agents) ) if len(conflicting_packages) == 0: return raise ProjectPackageConsistencyCheckError( agent_config.public_id, conflicting_packages ) def _add_new_package_versions(self, agent_config: AgentConfig) -> None: """ Add new package versions. This method is called whenever a project agent is added. It updates an internal data structure that it is used to check inconsistencies of AEA package versions across projects. In particular, all the AEA packages with the same "prefix" must be of the same version. :param agent_config: the agent configuration. """ for component_id in agent_config.package_dependencies: if component_id.component_prefix not in self._package_id_prefix_to_version: self._package_id_prefix_to_version[component_id.component_prefix] = ( component_id.version, set(), ) version, agents = self._package_id_prefix_to_version[ component_id.component_prefix ] enforce( version == component_id.version, f"internal consistency error: expected version '{version}', found {component_id.version}", ) agents.add(agent_config.public_id) def _remove_package_versions(self, agent_config: AgentConfig) -> None: """ Remove package versions. This method is called whenever a project agent is removed. It updates an internal data structure that it is used to check inconsistencies of AEA package versions across projects. In particular, all the AEA packages with the same "prefix" must be of the same version. :param agent_config: the agent configuration. """ package_prefix_to_remove = set() for ( package_prefix, (_version, agents), ) in self._package_id_prefix_to_version.items(): if agent_config.public_id in agents: agents.remove(agent_config.public_id) if len(agents) == 0: package_prefix_to_remove.add(package_prefix) for package_prefix in package_prefix_to_remove: self._package_id_prefix_to_version.pop(package_prefix) ================================================ FILE: aea/manager/project.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of AEA agents project configuration.""" import os from copy import deepcopy from pathlib import Path from shutil import rmtree from typing import Any, Dict, List, Optional, Set, Tuple, Union from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.cli.fetch import do_fetch from aea.cli.issue_certificates import issue_certificates_ from aea.cli.utils.context import Context from aea.configurations.base import AgentConfig, PublicId from aea.configurations.constants import DEFAULT_REGISTRY_NAME from aea.configurations.data_types import ComponentId from aea.configurations.manager import AgentConfigManager from aea.crypto.helpers import create_private_key, get_wallet_from_agent_config from aea.exceptions import AEAValidationError, enforce class _Base: """Base class to share some methods.""" @classmethod def _get_agent_config(cls, path: Union[Path, str]) -> AgentConfig: """Get agent config instance.""" agent_config = AEABuilder.try_to_load_agent_configuration_file(path) agent_config.check_aea_version() return agent_config @classmethod def _get_builder( cls, agent_config: AgentConfig, aea_project_path: Union[Path, str], skip_consistency_check: bool = False, ) -> AEABuilder: """Get AEABuilder instance.""" builder = AEABuilder( with_default_packages=False, build_dir_root=str(aea_project_path) ) builder.set_from_configuration( agent_config, Path(aea_project_path), skip_consistency_check ) return builder @property def builder(self) -> AEABuilder: """Get AEABuilder instance.""" raise NotImplementedError # pragma: nocover def install_pypi_dependencies(self) -> None: """Install python dependencies for the project.""" self.builder.install_pypi_dependencies() class Project(_Base): """Agent project representation.""" __slots__ = ("public_id", "path", "agents") def __init__(self, public_id: PublicId, path: str) -> None: """Init project with public_id and project's path.""" self.public_id: PublicId = public_id self.path: str = path self.agents: Set[str] = set() def build(self) -> None: """Call all build entry points.""" self.builder.call_all_build_entrypoints() @classmethod def load( cls, working_dir: str, public_id: PublicId, is_local: bool = False, is_remote: bool = False, is_restore: bool = False, cli_verbosity: str = "INFO", registry_path: str = DEFAULT_REGISTRY_NAME, skip_consistency_check: bool = False, skip_aea_validation: bool = False, ) -> "Project": """ Load project with given public_id to working_dir. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). :param working_dir: the working directory :param public_id: the public id :param is_local: whether to fetch from local :param is_remote: whether to fetch from remote :param is_restore: whether to restore or not :param cli_verbosity: the logging verbosity of the CLI :param registry_path: the path to the registry locally :param skip_consistency_check: consistency checks flag :param skip_aea_validation: aea validation flag :return: project """ ctx = Context( cwd=working_dir, verbosity=cli_verbosity, registry_path=registry_path ) ctx.set_config("skip_consistency_check", skip_consistency_check) ctx.set_config("skip_aea_validation", skip_aea_validation) path = os.path.join(working_dir, public_id.author, public_id.name) target_dir = os.path.join(public_id.author, public_id.name) if not is_restore and not os.path.exists(target_dir): do_fetch(ctx, public_id, is_local, is_remote, target_dir=target_dir) return cls(public_id, path) def remove(self) -> None: """Remove project, do cleanup.""" rmtree(self.path) @property def agent_config(self) -> AgentConfig: """Get the agent configuration.""" return self._get_agent_config(self.path) @property def builder(self) -> AEABuilder: """Get builder instance.""" return self._get_builder(self.agent_config, self.path) def check(self) -> None: """Check we can still construct an AEA from the project with builder.build.""" _ = self.builder class AgentAlias(_Base): """Agent alias representation.""" __slots__ = ("project", "agent_name", "_data_dir", "_agent_config") def __init__( self, project: Project, agent_name: str, data_dir: str, password: Optional[str] = None, ): """Init agent alias with project, config, name, agent, builder.""" self.project = project self.agent_name = agent_name self._data_dir = data_dir if not os.path.exists(self._data_dir): os.makedirs(self._data_dir) self._agent_config: AgentConfig = self._get_agent_config(project.path) self._password = password self._ensure_private_keys() def set_agent_config_from_data(self, json_data: List[Dict]) -> None: """ Set agent config instance constructed from json data. :param json_data: agent config json data """ self._agent_config = AEABuilder.loader.load_agent_config_from_json(json_data) self._ensure_private_keys() def _ensure_private_keys(self) -> None: """Add private keys if not present in the config.""" builder = self._get_builder(self.agent_config, self.project.path) default_ledger = builder.get_default_ledger() required_ledgers = builder.get_required_ledgers() enforce( default_ledger in required_ledgers, exception_text=f"Default ledger '{default_ledger}' not in required ledgers: {required_ledgers}", exception_class=AEAValidationError, ) available_private_keys = self.agent_config.private_key_paths.keys() available_connection_private_keys = ( self.agent_config.connection_private_key_paths.keys() ) for required_ledger in set(required_ledgers): if required_ledger not in available_private_keys: self.agent_config.private_key_paths.create( required_ledger, self._create_private_key(required_ledger) ) if required_ledger not in available_connection_private_keys: self.agent_config.connection_private_key_paths.create( required_ledger, self._create_private_key(required_ledger, is_connection=True), ) @property def builder(self) -> AEABuilder: """Get builder instance.""" builder = self._get_builder(self.agent_config, self.project.path) builder.set_name(self.agent_name) builder.set_runtime_mode("threaded") builder.set_data_dir(self._data_dir) return builder @property def agent_config(self) -> AgentConfig: """Get agent config.""" return self._agent_config def _create_private_key( self, ledger: str, replace: bool = False, is_connection: bool = False, ) -> str: """ Create new key for agent alias in working dir keys dir. If file exists, check `replace` option. :param ledger: the ledger id :param replace: whether or not to replace an existing key :param is_connection: whether or not it is a connection key :return: file path to private key """ file_name = ( f"{ledger}_connection_private.key" if is_connection else f"{ledger}_private.key" ) filepath = os.path.join(self._data_dir, file_name) if os.path.exists(filepath) and not replace: return filepath create_private_key(ledger, filepath, password=self._password) return filepath def remove_from_project(self) -> None: """Remove agent alias from project.""" self.project.agents.remove(self.agent_name) @property def dict(self) -> Dict[str, Any]: """Convert AgentAlias to dict.""" return { "public_id": str(self.project.public_id), "agent_name": self.agent_name, "config": self.config_json, } @property def config_json(self) -> List[Dict]: """Get agent config json data.""" json_data = self.agent_config.ordered_json result: List[Dict] = [json_data] + json_data.pop("component_configurations", {}) return result def get_aea_instance(self) -> AEA: """Build new aea instance.""" self.issue_certificates() aea = self.builder.build(password=self._password) # override build dir to project's one aea.DEFAULT_BUILD_DIR_NAME = os.path.join( self.project.path, aea.DEFAULT_BUILD_DIR_NAME ) return aea def issue_certificates(self) -> None: """Issue the certificates for this agent.""" issue_certificates_( self.project.path, self.agent_config_manager, path_prefix=self._data_dir, password=self._password, ) def set_overrides( self, agent_overrides: Optional[Dict] = None, component_overrides: Optional[List[Dict]] = None, ) -> None: """Set override for this agent alias's config.""" overrides = deepcopy(agent_overrides or {}) component_configurations: Dict[ComponentId, Dict] = {} for component_override in deepcopy(component_overrides or []): try: component_id = ComponentId.from_json( {"version": "any", **component_override} ) component_override.pop("author") component_override.pop("name") component_override.pop("type") component_override.pop("version") component_configurations[component_id] = component_override except (ValueError, KeyError) as e: # pragma: nocover raise ValueError( f"Component overrides are incorrect: {e} during process: {component_override}" ) overrides["component_configurations"] = component_configurations self.agent_config_manager.update_config(overrides) if agent_overrides: self._ensure_private_keys() @property def agent_config_manager(self) -> AgentConfigManager: """Get agent configuration manager instance for the config.""" return AgentConfigManager(self.agent_config, self.project.path) def get_overridables(self) -> Tuple[Dict, List[Dict]]: """Get all overridables for this agent alias's config.""" ( agent_overridables, components_overridables, ) = self.agent_config_manager.get_overridables() components_configurations = [] for component_id, obj in components_overridables.items(): if not obj: # pragma: nocover continue obj.update(component_id.json) components_configurations.append(obj) return agent_overridables, components_configurations def get_addresses(self) -> Dict[str, str]: """ Get addresses from private keys. :return: dict with crypto id str as key and address str as value """ wallet = get_wallet_from_agent_config( self.agent_config, password=self._password ) return wallet.addresses def get_connections_addresses(self) -> Dict[str, str]: """ Get connections addresses from connections private keys. :return: dict with crypto id str as key and address str as value """ wallet = get_wallet_from_agent_config( self.agent_config, password=self._password ) return wallet.connection_cryptos.addresses ================================================ FILE: aea/manager/utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Multiagent manager utils.""" import datetime import multiprocessing import os import sys import time import venv # type: ignore from traceback import format_exc from typing import Any, Callable from aea.crypto.plugin import load_all_plugins from aea.manager.project import Project def get_lib_path(env_dir: str) -> str: """Get librarty path for env dir.""" if sys.platform == "win32": # pragma: nocover libpath = os.path.join(env_dir, "Lib", "site-packages") else: # pragma: nocover libpath = os.path.join( env_dir, "lib", "python%d.%d" % sys.version_info[:2], "site-packages" ) return libpath def make_venv(env_dir: str, set_env: bool = False) -> None: """ Make venv and update variable to use it. :param env_dir: str, path for new env dir :param set_env: bool. use evn within this python process (update, sys.executable and sys.path) """ builder = venv.EnvBuilder(True, clear=True) ctx = builder.ensure_directories(env_dir) builder.create(env_dir) if set_env: # pragma: nocover sys.executable = ctx.env_exe sys.path.insert(0, get_lib_path(env_dir)) def project_install_and_build(project: Project) -> None: """Install project dependencies and build required components.""" project.install_pypi_dependencies() load_all_plugins(is_raising_exception=False) project.build() def get_venv_dir_for_project(project: Project) -> str: """Get virtual env directory for project specified.""" return os.path.join(project.path, "venv") def project_check(project: Project) -> None: """Perform project loads well.""" project.check() def run_in_venv(env_dir: str, fn: Callable, timeout: float, *args: Any) -> Any: """Run python function in a dedicated process with virtual env specified.""" manager = multiprocessing.Manager() result_queue = manager.Queue() process = multiprocessing.Process( target=_run_in_venv_handler, args=tuple([env_dir, fn, result_queue] + list(args)), ) process.start() start_time = time.time() while process.is_alive(): time.sleep(0.005) if timeout != 0 and (time.time() - start_time > timeout): # pragma: nocover process.terminate() process.join(5) raise TimeoutError() process.join(5) result = result_queue.get_nowait() if isinstance(result, BaseException): raise result return result def _run_in_venv_handler( env_dir: str, fn: Callable, queue: multiprocessing.Queue, *args: Any ) -> None: """Do actual function run in a dedicated process within virtual env.""" result = None try: make_venv(env_dir, set_env=True) result = fn(*args) except Exception as e: # pylint: disable=broad-except print( f"Exception in venv runner at {datetime.datetime.now()} for {fn}:\n{format_exc()}" ) result = e queue.put_nowait(result) ================================================ FILE: aea/multiplexer.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module for the multiplexer class and related classes.""" import asyncio import queue import threading from asyncio.events import AbstractEventLoop from concurrent.futures._base import CancelledError from concurrent.futures._base import TimeoutError as FuturesTimeoutError from contextlib import suppress from typing import ( Any, Callable, Collection, Dict, List, Optional, Sequence, Tuple, Union, cast, ) from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.exceptions import enforce from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.helpers.async_utils import AsyncState, Runnable, ThreadedAsyncRunner from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.helpers.logging import WithLogger, get_logger from aea.mail.base import AEAConnectionError, Empty, Envelope, EnvelopeContext from aea.protocols.base import Message, Protocol class MultiplexerStatus(AsyncState): """The connection status class.""" def __init__(self) -> None: """Initialize the connection status.""" super().__init__( initial_state=ConnectionStates.disconnected, states_enum=ConnectionStates ) @property def is_connected(self) -> bool: # pragma: nocover """Return is connected.""" return self.get() == ConnectionStates.connected @property def is_connecting(self) -> bool: # pragma: nocover """Return is connecting.""" return self.get() == ConnectionStates.connecting @property def is_disconnected(self) -> bool: # pragma: nocover """Return is disconnected.""" return self.get() == ConnectionStates.disconnected @property def is_disconnecting(self) -> bool: # pragma: nocover """Return is disconnected.""" return self.get() == ConnectionStates.disconnecting class AsyncMultiplexer(Runnable, WithLogger): """This class can handle multiple connections at once.""" DISCONNECT_TIMEOUT = 5 CONNECT_TIMEOUT = 60 SEND_TIMEOUT = 60 _lock: asyncio.Lock def __init__( self, connections: Optional[Sequence[Connection]] = None, default_connection_index: int = 0, loop: Optional[AbstractEventLoop] = None, exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum.propagate, threaded: bool = False, agent_name: str = "standalone", default_routing: Optional[Dict[PublicId, PublicId]] = None, default_connection: Optional[PublicId] = None, protocols: Optional[List[Union[Protocol, Message]]] = None, ) -> None: """ Initialize the connection multiplexer. :param connections: a sequence of connections. :param default_connection_index: the index of the connection to use as default. This information is used for envelopes which don't specify any routing context. If connections is None, this parameter is ignored. :param loop: the event loop to run the multiplexer. If None, a new event loop is created. :param exception_policy: the exception policy used for connections. :param threaded: if True, run in threaded mode, else async :param agent_name: the name of the agent that owns the multiplexer, for logging purposes. :param default_routing: default routing map :param default_connection: default connection :param protocols: protocols used """ self._exception_policy: ExceptionPolicyEnum = exception_policy logger = get_logger(__name__, agent_name) WithLogger.__init__(self, logger=logger) Runnable.__init__(self, loop=loop, threaded=threaded) self._connections: List[Connection] = [] self._id_to_connection: Dict[PublicId, Connection] = {} self._default_connection: Optional[Connection] = None connections = connections or [] if not default_connection and connections: enforce( len(connections) - 1 >= default_connection_index, "default_connection_index os out of connections range!", ) default_connection = connections[default_connection_index].connection_id if default_connection: enforce( bool( [ i.connection_id.same_prefix(default_connection) for i in connections ] ), f"Default connection {default_connection} does not present in connections list!", ) self._default_routing = {} # type: Dict[PublicId, PublicId] self._setup(connections or [], default_routing, default_connection) self._connection_status = MultiplexerStatus() self._specification_id_to_protocol_id = { p.protocol_specification_id: p.protocol_id for p in protocols or [] } self._routing_helper: Dict[Address, PublicId] = {} self._in_queue = AsyncFriendlyQueue() # type: AsyncFriendlyQueue self._out_queue = None # type: Optional[asyncio.Queue] self._recv_loop_task = None # type: Optional[asyncio.Task] self._send_loop_task = None # type: Optional[asyncio.Task] self._loop: asyncio.AbstractEventLoop = ( loop if loop is not None else asyncio.new_event_loop() ) self.set_loop(self._loop) @property def default_connection(self) -> Optional[Connection]: """Get the default connection.""" return self._default_connection @property def in_queue(self) -> AsyncFriendlyQueue: """Get the in queue.""" return self._in_queue @property def out_queue(self) -> asyncio.Queue: """Get the out queue.""" if self._out_queue is None: # pragma: nocover raise ValueError("Accessing out queue before loop is started.") return self._out_queue @property def connections(self) -> Tuple[Connection, ...]: """Get the connections.""" return tuple(self._connections) @property def is_connected(self) -> bool: """Check whether the multiplexer is processing envelopes.""" return self.connection_status.is_connected @property def default_routing(self) -> Dict[PublicId, PublicId]: """Get the default routing.""" return self._default_routing @default_routing.setter def default_routing(self, default_routing: Dict[PublicId, PublicId]) -> None: """Set the default routing.""" self._default_routing = default_routing @property def connection_status(self) -> MultiplexerStatus: """Get the connection status.""" return self._connection_status async def run(self) -> None: """Run multiplexer connect and receive/send tasks.""" self.set_loop(asyncio.get_event_loop()) try: await self.connect() if not self._recv_loop_task or not self._send_loop_task: raise ValueError("Multiplexer is not connected properly.") await asyncio.gather(self._recv_loop_task, self._send_loop_task) finally: await self.disconnect() def _get_protocol_id_for_envelope(self, envelope: Envelope) -> PublicId: """Get protocol id for envelope.""" if isinstance(envelope.message, Message): return cast(Message, envelope.message).protocol_id protocol_id = self._specification_id_to_protocol_id.get( envelope.protocol_specification_id ) if not protocol_id: raise ValueError( f"Can not resolve protocol id for {envelope}, pass protocols supported to multipelxer instance {self._specification_id_to_protocol_id}" ) return protocol_id def set_loop(self, loop: AbstractEventLoop) -> None: """ Set event loop and all event loop related objects. :param loop: asyncio event loop. """ self._loop = loop self._lock = asyncio.Lock() def _handle_exception(self, fn: Callable, exc: Exception) -> None: """ Handle exception raised. :param fn: a method where it raised .send .connect etc :param exc: exception """ if self._exception_policy == ExceptionPolicyEnum.just_log: self.logger.exception(f"Exception raised in {fn}") elif self._exception_policy == ExceptionPolicyEnum.propagate: raise exc elif self._exception_policy == ExceptionPolicyEnum.stop_and_exit: self._loop.create_task(AsyncMultiplexer.disconnect(self)) else: # pragma: nocover raise ValueError(f"Unknown exception policy: {self._exception_policy}") def add_connection(self, connection: Connection, is_default: bool = False) -> None: """ Add a connection to the multiplexer. :param connection: the connection to add. :param is_default: whether the connection added should be the default one. """ if connection.connection_id in self._id_to_connection: # pragma: nocover self.logger.warning( f"A connection with id {connection.connection_id} was already added. Replacing it..." ) self._connections.append(connection) self._id_to_connection[connection.connection_id] = connection if is_default: self._default_connection = connection def _connection_consistency_checks(self) -> None: """ Do some consistency checks on the multiplexer connections. :raise AEAEnforceError: if an inconsistency is found. """ if len(self.connections) == 0: self.logger.debug("List of connections is empty.") enforce( len(set(c.connection_id for c in self.connections)) == len(self.connections), "Connection names must be unique.", ) def _set_default_connection_if_none(self) -> None: """Set the default connection if it is none.""" if self._default_connection is None and bool(self.connections): self._default_connection = self.connections[0] async def connect(self) -> None: """Connect the multiplexer.""" self._loop = asyncio.get_event_loop() self.logger.debug("Multiplexer connecting...") self._connection_consistency_checks() self._set_default_connection_if_none() self._out_queue = asyncio.Queue() async with self._lock: if self.connection_status.is_connected: self.logger.debug("Multiplexer already connected.") return try: self.connection_status.set(ConnectionStates.connecting) await self._connect_all() if all(c.is_connected for c in self._connections): self.connection_status.set(ConnectionStates.connected) else: # pragma: nocover raise AEAConnectionError("Failed to connect the multiplexer.") self._recv_loop_task = self._loop.create_task(self._receiving_loop()) self._send_loop_task = self._loop.create_task(self._send_loop()) self.logger.debug("Multiplexer connected and running.") except (CancelledError, asyncio.CancelledError): # pragma: nocover await self._stop() raise asyncio.CancelledError() except AEAConnectionError: await self._stop() raise except Exception as e: self.logger.exception("Exception on connect:") await self._stop() raise AEAConnectionError( f"Failed to connect the multiplexer: Error: {repr(e)}" ) from e async def disconnect(self) -> None: """Disconnect the multiplexer.""" self.logger.debug("Multiplexer disconnecting...") async with self._lock: if self.connection_status.is_disconnected: self.logger.debug("Multiplexer already disconnected.") return try: self.connection_status.set(ConnectionStates.disconnecting) await asyncio.wait_for(self._stop(), timeout=60) self.logger.debug("Multiplexer disconnected.") except CancelledError: # pragma: nocover self.logger.debug("Multiplexer.disconnect cancellation!") raise except Exception as e: self.logger.exception("Exception on disconnect:") raise AEAConnectionError( f"Failed to disconnect the multiplexer: Error: {repr(e)}" ) from e async def _stop_receive_send_loops(self) -> None: """Stop receive and send loops.""" self.logger.debug("Stopping receive loop...") if self._recv_loop_task: self._recv_loop_task.cancel() with suppress(Exception, asyncio.CancelledError): await self._recv_loop_task self._recv_loop_task = None self.logger.debug("Receive loop stopped.") self.logger.debug("Stopping send loop...") if self._send_loop_task: # send a 'stop' token (a None value) to wake up the coroutine waiting for outgoing envelopes. await self.out_queue.put(None) self._send_loop_task.cancel() with suppress(Exception, asyncio.CancelledError): await self._send_loop_task self._send_loop_task = None self.logger.debug("Send loop stopped.") def _check_and_set_disconnected_state(self) -> None: """Check every connection is disconnected and set disconnected state.""" if all((c.is_disconnected for c in self.connections)): self.connection_status.set(ConnectionStates.disconnected) else: connections_left = [ str(c.connection_id) for c in self.connections if not c.is_disconnected ] raise AEAConnectionError( f"Failed to disconnect multiplexer, some connections are not disconnected within timeout: {', '.join(connections_left)}" ) async def _stop(self) -> None: """ Stop the multiplexer. Stops receive and send loops. Disconnect every connection. """ self.logger.debug("Stopping multiplexer...") await asyncio.wait_for(self._stop_receive_send_loops(), timeout=60) await asyncio.wait_for(self._disconnect_all(), timeout=60) self._check_and_set_disconnected_state() self.logger.debug("Multiplexer stopped.") async def _connect_all(self) -> None: """Set all the connection up.""" self.logger.debug("Starting multiplexer connections.") connected = [] # type: List[PublicId] for connection_id, connection in self._id_to_connection.items(): try: await asyncio.wait_for( self._connect_one(connection_id), timeout=self.CONNECT_TIMEOUT ) connected.append(connection_id) except Exception as e: # pylint: disable=broad-except if not isinstance(e, (asyncio.CancelledError, CancelledError)): self.logger.exception( "Error while connecting {}: {}".format( str(type(connection)), repr(e) ) ) raise self.logger.debug("Multiplexer connections are set.") async def _connect_one(self, connection_id: PublicId) -> None: """ Set a connection up. :param connection_id: the id of the connection. """ connection = self._id_to_connection[connection_id] self.logger.debug("Processing connection {}".format(connection.connection_id)) if connection.is_connected: self.logger.debug( "Connection {} already established.".format(connection.connection_id) ) else: await connection.connect() self.logger.debug( "Connection {} has been set up successfully.".format( connection.connection_id ) ) async def _disconnect_all(self) -> None: """Tear all the connections down.""" self.logger.debug("Tear the multiplexer connections down.") for connection_id, connection in self._id_to_connection.items(): try: await asyncio.wait_for( self._disconnect_one(connection_id), timeout=self.DISCONNECT_TIMEOUT ) except FuturesTimeoutError: self.logger.debug( # pragma: nocover f"Disconnection of `{connection_id}` timed out." ) except Exception as e: # pylint: disable=broad-except self.logger.exception( "Error while disconnecting {}: {}".format( str(type(connection)), str(e) ) ) async def _disconnect_one(self, connection_id: PublicId) -> None: """ Tear a connection down. :param connection_id: the id of the connection. """ connection = self._id_to_connection[connection_id] self.logger.debug("Processing connection {}".format(connection.connection_id)) if not connection.is_connected: self.logger.debug( "Connection {} already disconnected.".format(connection.connection_id) ) else: await connection.disconnect() self.logger.debug( "Connection {} has been disconnected successfully.".format( connection.connection_id ) ) async def _send_loop(self) -> None: """Process the outgoing envelopes.""" if not self.is_connected: self.logger.debug( "Sending loop not started. The multiplexer is not connected." ) return try: while self.is_connected: self.logger.debug("Waiting for outgoing envelopes...") envelope = await self.out_queue.get() if envelope is None: # pragma: nocover self.logger.debug( "Received empty envelope. Quitting the sending loop..." ) return None self.logger.debug("Sending envelope {}".format(str(envelope))) await self._send(envelope) except asyncio.CancelledError: self.logger.debug("Sending loop cancelled.") raise except Exception as e: # pylint: disable=broad-except # pragma: nocover self.logger.exception("Error in the sending loop: {}".format(str(e))) raise async def _receiving_loop(self) -> None: """Process incoming envelopes.""" self.logger.debug("Starting receving loop...") task_to_connection = { asyncio.ensure_future(conn.receive()): conn for conn in self.connections } try: while self.connection_status.is_connected and len(task_to_connection) > 0: done, _pending = await asyncio.wait( task_to_connection.keys(), return_when=asyncio.FIRST_COMPLETED ) # process completed receiving tasks. for task in done: connection = task_to_connection.pop(task) envelope = task.result() if envelope is not None: self._update_routing_helper(envelope, connection) self.in_queue.put_nowait(envelope) # reinstantiate receiving task, but only if the connection is still up. if connection.is_connected: new_task = asyncio.ensure_future(connection.receive()) task_to_connection[new_task] = connection except asyncio.CancelledError: # pragma: nocover self.logger.debug("Receiving loop cancelled.") raise except Exception as e: # pylint: disable=broad-except self.logger.exception("Error in the receiving loop: {}".format(str(e))) raise finally: # cancel all the receiving tasks. for t in task_to_connection.keys(): t.cancel() self.logger.debug("Receiving loop terminated.") async def _send(self, envelope: Envelope) -> None: """ Send an envelope. :param envelope: the envelope to send. """ envelope_protocol_id = self._get_protocol_id_for_envelope(envelope) connection_id = self._get_connection_id_from_envelope( envelope, envelope_protocol_id ) connection = ( self._get_connection(connection_id) if connection_id is not None else None ) if connection is None: # we don't raise on dropping envelope as this can be a configuration issue only! self.logger.warning( f"Dropping envelope, no connection available for sending: {envelope}" ) return if not self._is_connection_supported_protocol(connection, envelope_protocol_id): return try: await asyncio.wait_for(connection.send(envelope), timeout=self.SEND_TIMEOUT) except Exception as e: # pylint: disable=broad-except self._handle_exception(self._send, e) def _get_connection_id_from_envelope( self, envelope: Envelope, envelope_protocol_id: PublicId ) -> Optional[PublicId]: """ Get the connection id from an envelope. Applies the following rules: - component to component messages are routed by their component id - agent to agent messages, are routed following four rules: * first, try to route by envelope context connection id * second, try to route by routing helper * third, try to route by default routing * forth, using default connection :param envelope: the Envelope :param envelope_protocol_id: the protocol id of the message contained in the envelope :return: public id if found """ self.logger.debug(f"Routing envelope: {envelope}") # component to component messages are routed by their component id if envelope.is_component_to_component_message: connection_id = envelope.to_as_public_id self.logger.debug( "Using envelope `to` field as connection_id: {}".format(connection_id) ) enforce( connection_id is not None, "Connection id cannot be None by envelope construction.", ) return connection_id # agent to agent messages, are routed following four rules: # first, try to route by envelope context connection id if envelope.context is not None and envelope.context.connection_id is not None: connection_id = envelope.context.connection_id self.logger.debug( "Using envelope context connection_id: {}".format(connection_id) ) return connection_id # second, try to route by routing helper if envelope.to in self._routing_helper: connection_id = self._routing_helper[envelope.to] self.logger.debug( "Using routing helper with connection_id: {}".format(connection_id) ) return connection_id # third, try to route by default routing if envelope_protocol_id in self.default_routing: connection_id = self.default_routing[envelope_protocol_id] self.logger.debug("Using default routing: {}".format(connection_id)) return connection_id # forth, using default connection connection_id = ( self.default_connection.connection_id if self.default_connection is not None else None ) self.logger.debug("Using default connection: {}".format(connection_id)) return connection_id def _get_connection(self, connection_id: PublicId) -> Optional[Connection]: """Check if the connection id is registered.""" conn_ = self._id_to_connection.get(connection_id, None) if conn_ is not None: return conn_ for id_, conn_ in self._id_to_connection.items(): if id_.same_prefix(connection_id): return conn_ self.logger.error(f"No connection registered with id: {connection_id}") return None def _is_connection_supported_protocol( self, connection: Connection, protocol_id: PublicId ) -> bool: """Check protocol id is supported by the connection.""" if protocol_id in connection.excluded_protocols: self.logger.warning( f"Connection {connection.connection_id} does not support protocol {protocol_id}. It is explicitly excluded." ) return False if ( connection.restricted_to_protocols and protocol_id not in connection.restricted_to_protocols ): self.logger.warning( f"Connection {connection.connection_id} does not support protocol {protocol_id}. The connection is restricted to protocols in {connection.restricted_to_protocols}." ) return False return True def get( self, block: bool = False, timeout: Optional[float] = None ) -> Optional[Envelope]: """ Get an envelope within a timeout. :param block: make the call blocking (ignore the timeout). :param timeout: the timeout to wait until an envelope is received. :return: the envelope, or None if no envelope is available within a timeout. """ try: return self.in_queue.get(block=block, timeout=timeout) except queue.Empty: raise Empty async def async_get(self) -> Envelope: """ Get an envelope async way. :return: the envelope """ try: return await self.in_queue.async_get() except queue.Empty: # pragma: nocover raise Empty async def async_wait(self) -> None: """ Get an envelope async way. :return: the envelope """ return await self.in_queue.async_wait() async def _put(self, envelope: Envelope) -> None: """ Schedule an envelope for sending it. Notice that the output queue is an asyncio.Queue which uses an event loop running on a different thread than the one used in this function. :param envelope: the envelope to be sent. """ await self.out_queue.put(envelope) def put(self, envelope: Envelope) -> None: """ Schedule an envelope for sending it. Notice that the output queue is an asyncio.Queue which uses an event loop running on a different thread than the one used in this function. :param envelope: the envelope to be sent. """ if self._threaded: self._loop.call_soon_threadsafe(self.out_queue.put_nowait, envelope) else: self.out_queue.put_nowait(envelope) def _setup( self, connections: Collection[Connection], default_routing: Optional[Dict[PublicId, PublicId]] = None, default_connection: Optional[PublicId] = None, ) -> None: """ Set up the multiplexer. :param connections: the connections to use. It will replace the other ones. :param default_routing: the default routing. :param default_connection: the default connection. """ self.default_routing = default_routing or {} # replace connections self._connections = [] self._id_to_connection = {} for c in connections: self.add_connection(c, c.public_id == default_connection) def _update_routing_helper( self, envelope: Envelope, connection: Connection ) -> None: """ Update the routing helper. Saves the source (connection) of an agent-to-agent envelope. :param envelope: the envelope to be updated :param connection: the connection """ if envelope.is_component_to_component_message: return self._routing_helper[envelope.sender] = connection.public_id class Multiplexer(AsyncMultiplexer): """Transit sync multiplexer for compatibility.""" _thread_was_started: bool _is_connected: bool def __init__(self, *args: Any, **kwargs: Any) -> None: """ Initialize the connection multiplexer. :param args: arguments :param kwargs: keyword arguments """ super().__init__(*args, **kwargs) self._sync_lock = threading.Lock() self._init() def _init(self) -> None: """Set initial variables.""" self._thread_was_started = False self._is_connected = False def set_loop(self, loop: AbstractEventLoop) -> None: """ Set event loop and all event loop related objects. :param loop: asyncio event loop. """ super().set_loop(loop) self._thread_runner = ThreadedAsyncRunner(self._loop) def connect(self) -> None: # type: ignore # cause overrides coroutine # pylint: disable=invalid-overridden-method """ Connect the multiplexer. Synchronously in thread spawned if new loop created. """ with self._sync_lock: if not self._loop.is_running(): self._thread_runner.start() self._thread_was_started = True self._thread_runner.call(super().connect()).result(240) self._is_connected = True def disconnect(self) -> None: # type: ignore # cause overrides coroutine # pylint: disable=invalid-overridden-method """ Disconnect the multiplexer. Also stops a dedicated thread for event loop if spawned on connect. """ self.logger.debug("Disconnect called") with self._sync_lock: if not self._loop.is_running(): return if self._is_connected: self._thread_runner.call(super().disconnect()).result(240) self._is_connected = False self.logger.debug("Disconnect async method executed") if self._thread_runner.is_alive() and self._thread_was_started: self._thread_runner.stop() self.logger.debug("Thread stopped") self.logger.debug("Disconnected") # reset thread runner and init variables self._init() self.set_loop(self._loop) def put(self, envelope: Envelope) -> None: # type: ignore # cause overrides coroutine """ Schedule an envelope for sending it. Notice that the output queue is an asyncio.Queue which uses an event loop running on a different thread than the one used in this function. :param envelope: the envelope to be sent. """ self._thread_runner.call(super()._put(envelope)) # .result(240) class InBox: """A queue from where you can only consume envelopes.""" def __init__(self, multiplexer: AsyncMultiplexer) -> None: """ Initialize the inbox. :param multiplexer: the multiplexer """ super().__init__() self._multiplexer = multiplexer def empty(self) -> bool: """ Check for a envelope on the in queue. :return: boolean indicating whether there is an envelope or not """ return self._multiplexer.in_queue.empty() def get(self, block: bool = False, timeout: Optional[float] = None) -> Envelope: """ Check for a envelope on the in queue. :param block: make the call blocking (ignore the timeout). :param timeout: times out the block after timeout seconds. :return: the envelope object. :raises Empty: if the attempt to get an envelope fails. """ self._multiplexer.logger.debug("Checks for envelope from the in queue...") envelope = self._multiplexer.get(block=block, timeout=timeout) if envelope is None: # pragma: nocover raise Empty() self._multiplexer.logger.debug(f"Incoming {envelope}") return envelope def get_nowait(self) -> Optional[Envelope]: """ Check for a envelope on the in queue and wait for no time. :return: the envelope object """ try: return self.get() except Empty: # pragma: nocover return None async def async_get(self) -> Envelope: """ Check for a envelope on the in queue. :return: the envelope object. """ self._multiplexer.logger.debug( "Checks for envelope from the in queue async way..." ) envelope = await self._multiplexer.async_get() if envelope is None: # pragma: nocover raise Empty() self._multiplexer.logger.debug(f"Incoming envelope: {envelope}") return envelope async def async_wait(self) -> None: """Check for a envelope on the in queue.""" self._multiplexer.logger.debug( "Checks for envelope presents in queue async way..." ) await self._multiplexer.async_wait() class OutBox: """A queue from where you can only enqueue envelopes.""" def __init__(self, multiplexer: AsyncMultiplexer) -> None: """ Initialize the outbox. :param multiplexer: the multiplexer """ super().__init__() self._multiplexer = multiplexer def empty(self) -> bool: """ Check for a envelope on the in queue. :return: boolean indicating whether there is an envelope or not """ return self._multiplexer.out_queue.empty() # pragma: nocover def put(self, envelope: Envelope) -> None: """ Put an envelope into the queue. :param envelope: the envelope. """ self._multiplexer.logger.debug(f"Put an envelope in the queue: {envelope}.") if not isinstance(envelope.message, Message): raise ValueError( "Only Message type allowed in envelope message field when putting into outbox." ) message = cast(Message, envelope.message) if not message.has_to: # pragma: nocover raise ValueError("Provided message has message.to not set.") if not message.has_sender: # pragma: nocover raise ValueError("Provided message has message.sender not set.") self._multiplexer.put(envelope) def put_message( self, message: Message, context: Optional[EnvelopeContext] = None, ) -> None: """ Put a message in the outbox. This constructs an envelope with the input arguments. :param message: the message :param context: the envelope context """ if not isinstance(message, Message): raise ValueError("Provided message not of type Message.") if not message.has_to: raise ValueError("Provided message has message.to not set.") if not message.has_sender: raise ValueError("Provided message has message.sender not set.") envelope = Envelope( to=message.to, sender=message.sender, message=message, context=context, ) self.put(envelope) ================================================ FILE: aea/protocols/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the protocol modules.""" ================================================ FILE: aea/protocols/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the base message and serialization definition.""" import importlib import inspect import logging import re from abc import ABC, abstractmethod from base64 import b64decode, b64encode from copy import copy from enum import Enum from pathlib import Path from typing import Any, Dict, Optional, Set, Tuple, Type, cast from aea.components.base import Component, load_aea_package from aea.configurations.base import ComponentType, ProtocolConfig, PublicId from aea.configurations.loader import load_component_configuration from aea.exceptions import AEAComponentLoadException, enforce _default_logger = logging.getLogger(__name__) MAX_PRINT_INNER = 600 MAX_PRINT_OUTER = 2000 Address = str class Message: """This class implements a message.""" protocol_id = None # type: PublicId protocol_specification_id = None # type: PublicId serializer = None # type: Type["Serializer"] __slots__ = ("_slots", "_to", "_sender") class Performative(Enum): """Performatives for the base message.""" def __str__(self) -> str: """Get the string representation.""" return str(self.value) class _SlotsCls: # pylint: disable=too-few-public-methods __slots__: Tuple[str, ...] = ( "performative", "dialogue_reference", "message_id", "target", ) _performatives: Set[str] = set() def __init__(self, _body: Optional[Dict] = None, **kwargs: Any) -> None: """ Initialize a Message object. :param _body: the dictionary of values to hold. :param kwargs: any additional value to add to the body. It will overwrite the body values. """ self._slots = self._SlotsCls() self._to: Optional[Address] = None self._sender: Optional[Address] = None self._update_slots_from_dict(copy(_body) if _body else {}) self._update_slots_from_dict(kwargs) try: self._is_consistent() except Exception as e: # pylint: disable=broad-except _default_logger.error(e) def json(self) -> dict: """Get json friendly str representation of the message.""" return { "to": self._to, "sender": self._sender, "body": b64encode(self.encode()).decode("utf-8"), } @classmethod def from_json(cls, data: dict) -> "Message": """Construct message instance from json data.""" try: instance = cls.decode(b64decode(data["body"])) sender = data["sender"] if sender: instance.sender = sender to = data["to"] if to: instance.to = to return instance except KeyError: # pragma: nocover raise ValueError(f"Message representation is invalid: {data}") @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def has_sender(self) -> bool: """Check if it has a sender.""" return self._sender is not None @property def sender(self) -> Address: """Get the sender of the message in Address form.""" if self._sender is None: raise ValueError("Message's 'Sender' field must be set.") # pragma: nocover return self._sender @sender.setter def sender(self, sender: Address) -> None: """Set the sender of the message.""" enforce(self._sender is None, "Sender already set.") enforce( isinstance(sender, str), f"Sender must be string type. Found '{type(sender)}'", ) self._sender = sender @property def has_to(self) -> bool: """Check if it has a sender.""" return self._to is not None @property def to(self) -> Address: """Get address of receiver.""" if self._to is None: raise ValueError("Message's 'To' field must be set.") return self._to @to.setter def to(self, to: Address) -> None: """Set address of receiver.""" enforce(self._to is None, "To already set.") enforce(isinstance(to, str), f"To must be string type. Found '{type(to)}'") self._to = to @property def _body(self) -> Dict: """ Get the body of the message (in dictionary form). :return: the body """ return { key: self.get(key) for key in self._SlotsCls.__slots__ if self.is_set(key) } @_body.setter def _body(self, body: Dict) -> None: """ Set the body of the message. :param body: the body. """ self._slots = self._SlotsCls() # new instance to clean up all data self._update_slots_from_dict(body) @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" if not self.is_set("dialogue_reference"): raise ValueError("dialogue_reference is not set.") # pragma: nocover return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" if not self.is_set("message_id"): raise ValueError("message_id is not set.") # pragma: nocover return cast(int, self.get("message_id")) @property def performative(self) -> "Performative": """Get the performative of the message.""" if not self.is_set("performative"): raise ValueError("performative is not set.") # pragma: nocover return cast(Message.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" if not self.is_set("target"): raise ValueError("target is not set.") # pragma: nocover return cast(int, self.get("target")) def set(self, key: str, value: Any) -> None: """ Set key and value pair. :param key: the key. :param value: the value. """ try: setattr(self._slots, key, value) except AttributeError as e: # pragma: nocover raise ValueError(f"Field `{key}` is not supported {e}") def get(self, key: str) -> Optional[Any]: """Get value for key.""" return getattr(self._slots, key, None) def is_set(self, key: str) -> bool: """Check value is set for key.""" return hasattr(self._slots, key) def _update_slots_from_dict(self, data: dict) -> None: """Update slots value with data from dict.""" for key, value in data.items(): self.set(key, value) def _is_consistent(self) -> bool: """Check that the data is consistent.""" return True def __eq__(self, other: Any) -> bool: """Compare with another object.""" return ( isinstance(other, Message) and self._sender == other._sender and self._to == other._to and self._body == other._body ) def __repr__(self) -> str: """Get the representation of the message.""" body = ",".join( map( lambda key_value: f"{str(key_value[0])}={str(key_value[1])}", self._body.items(), ) ) return f"Message(sender={self._sender},to={self._to},{body})" def __str__(self) -> str: """Get the string representation of the message. Abbreviated to prevent spamming of logs.""" body = ",".join( map( lambda key_value: f"{str(key_value[0])[:MAX_PRINT_INNER]}={str(key_value[1])[:MAX_PRINT_INNER]}", self._body.items(), ) ) return f"Message(sender={self._sender},to={self._to},{body})"[:MAX_PRINT_OUTER] def encode(self) -> bytes: """Encode the message.""" return self.serializer.encode(self) @classmethod def decode(cls, data: bytes) -> "Message": """Decode the message.""" return cls.serializer.decode(data) @property def has_dialogue_info(self) -> bool: """ Check whether a message has the dialogue fields populated. More precisely, it checks whether the fields 'message_id', 'target' and 'dialogue_reference' are set. :return: True if the message has the dialogue fields set, False otherwise. """ return ( self.is_set("message_id") and self.is_set("target") and self.is_set("dialogue_reference") ) class Encoder(ABC): """Encoder interface.""" @staticmethod @abstractmethod def encode(msg: Message) -> bytes: """ Encode a message. :param msg: the message to be encoded. :return: the encoded message. """ class Decoder(ABC): """Decoder interface.""" @staticmethod @abstractmethod def decode(obj: bytes) -> Message: """ Decode a message. :param obj: the sequence of bytes to be decoded. :return: the decoded message. """ class Serializer(Encoder, Decoder, ABC): """The implementations of this class defines a serialization layer for a protocol.""" class Protocol(Component): """ This class implements a specifications for a protocol. It includes a serializer to encode/decode a message. """ __slots__ = ("_message_class",) def __init__( self, configuration: ProtocolConfig, message_class: Type[Message], **kwargs: Any ) -> None: """ Initialize the protocol manager. :param configuration: the protocol configurations. :param message_class: the message class. :param kwargs: the keyword arguments. """ super().__init__(configuration, **kwargs) self._message_class = message_class @property def serializer(self) -> Type[Serializer]: """Get the serializer.""" return self._message_class.serializer @classmethod def from_dir(cls, directory: str, **kwargs: Any) -> "Protocol": """ Load the protocol from a directory. :param directory: the directory to the skill package. :param kwargs: the keyword arguments. :return: the protocol object. """ configuration = cast( ProtocolConfig, load_component_configuration(ComponentType.PROTOCOL, Path(directory)), ) configuration.directory = Path(directory) return Protocol.from_config(configuration, **kwargs) @classmethod def from_config(cls, configuration: ProtocolConfig, **kwargs: Any) -> "Protocol": """ Load the protocol from configuration. :param configuration: the protocol configuration. :param kwargs: the keyword arguments. :return: the protocol object. """ if configuration.directory is None: # pragma: nocover raise ValueError("Configuration must be associated with a directory.") load_aea_package(configuration) class_module = importlib.import_module( configuration.prefix_import_path + ".message" ) classes = inspect.getmembers(class_module, inspect.isclass) name_camel_case = "".join( word.capitalize() for word in configuration.name.split("_") ) message_classes = list( filter(lambda x: re.match(f"{name_camel_case}Message", x[0]), classes) ) if len(message_classes) != 1: # pragma: nocover raise AEAComponentLoadException("Not exactly one message class detected.") message_class = message_classes[0][1] class_module = importlib.import_module( configuration.prefix_import_path + ".serialization" ) classes = inspect.getmembers(class_module, inspect.isclass) serializer_classes = list( filter( lambda x: re.match(f"{name_camel_case}Serializer", x[0]), classes, ) ) if len(serializer_classes) != 1: # pragma: nocover raise AEAComponentLoadException( "Not exactly one serializer class detected." ) serialize_class = serializer_classes[0][1] message_class.serializer = serialize_class return Protocol(configuration, message_class, **kwargs) @property def protocol_id(self) -> PublicId: """Get protocol id.""" return cast(ProtocolConfig, self._configuration).public_id @property def protocol_specification_id(self) -> PublicId: """Get protocol specification id.""" return cast(ProtocolConfig, self._configuration).protocol_specification_id def __repr__(self) -> str: """Get str representation of the protocol.""" return f"Protocol({self.protocol_id})" ================================================ FILE: aea/protocols/dialogue/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the dialogue modules.""" ================================================ FILE: aea/protocols/dialogue/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DialogueLabel: The dialogue label class acts as an identifier for dialogues. - Dialogue: The dialogue class maintains state of a dialogue and manages it. - Dialogues: The dialogues class keeps track of all dialogues. """ import inspect import secrets import sys from collections import defaultdict, namedtuple from enum import Enum from inspect import signature from typing import ( Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, Type, cast, ) from aea.common import Address from aea.exceptions import AEAEnforceError, enforce from aea.helpers.base import cached_property from aea.helpers.storage.generic_storage import SyncCollection from aea.protocols.base import Message from aea.skills.base import SkillComponent if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 7): DialogueMessage = namedtuple( # pragma: no cover "DialogueMessage", ["performative", "contents", "is_incoming", "target"], rename=False, module="aea.protocols.dialogues.base", ) DialogueMessage.__new__.__defaults__ = (dict(), None, None) # pragma: no cover else: DialogueMessage = namedtuple( # pylint: disable=unexpected-keyword-arg "DialogueMessage", ["performative", "contents", "is_incoming", "target"], rename=False, defaults=[dict(), None, None], module="aea.protocols.dialogues.base", ) class InvalidDialogueMessage(Exception): """Exception for adding invalid message to a dialogue.""" class DialogueLabel: """The dialogue label class acts as an identifier for dialogues.""" __slots__ = ( "_dialogue_reference", "_dialogue_opponent_addr", "_dialogue_starter_addr", ) NONCE_BYTES_NB = 32 def __init__( self, dialogue_reference: Tuple[str, str], dialogue_opponent_addr: Address, dialogue_starter_addr: Address, ) -> None: """ Initialize a dialogue label. :param dialogue_reference: the reference of the dialogue. :param dialogue_opponent_addr: the addr of the agent with which the dialogue is kept. :param dialogue_starter_addr: the addr of the agent which started the dialogue. """ self._dialogue_reference = dialogue_reference self._dialogue_opponent_addr = dialogue_opponent_addr self._dialogue_starter_addr = dialogue_starter_addr @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue reference.""" return self._dialogue_reference @property def dialogue_starter_reference(self) -> str: """Get the dialogue starter reference.""" return self._dialogue_reference[0] @property def dialogue_responder_reference(self) -> str: """Get the dialogue responder reference.""" return self._dialogue_reference[1] @property def dialogue_opponent_addr(self) -> str: """Get the address of the dialogue opponent.""" return self._dialogue_opponent_addr @property def dialogue_starter_addr(self) -> str: """Get the address of the dialogue starter.""" return self._dialogue_starter_addr def __eq__(self, other: Any) -> bool: """Check for equality between two DialogueLabel objects.""" return ( isinstance(other, DialogueLabel) and self.dialogue_reference == other.dialogue_reference and self.dialogue_opponent_addr == other.dialogue_opponent_addr and self.dialogue_starter_addr == other.dialogue_starter_addr ) def __hash__(self) -> int: """Turn object into hash.""" return hash( ( self.dialogue_reference, self.dialogue_opponent_addr, self.dialogue_starter_addr, ) ) @property def json(self) -> Dict: """Return the JSON representation.""" return { "dialogue_starter_reference": self.dialogue_starter_reference, "dialogue_responder_reference": self.dialogue_responder_reference, "dialogue_opponent_addr": self.dialogue_opponent_addr, "dialogue_starter_addr": self.dialogue_starter_addr, } @classmethod def from_json(cls, obj: Dict[str, str]) -> "DialogueLabel": """Get dialogue label from json.""" dialogue_label = DialogueLabel( ( cast(str, obj.get("dialogue_starter_reference")), cast(str, obj.get("dialogue_responder_reference")), ), cast(str, obj.get("dialogue_opponent_addr")), cast(str, obj.get("dialogue_starter_addr")), ) return dialogue_label def get_incomplete_version(self) -> "DialogueLabel": """Get the incomplete version of the label.""" dialogue_label = DialogueLabel( (self.dialogue_starter_reference, Dialogue.UNASSIGNED_DIALOGUE_REFERENCE), self.dialogue_opponent_addr, self.dialogue_starter_addr, ) return dialogue_label def __str__(self) -> str: """Get the string representation.""" return "{}_{}_{}_{}".format( self.dialogue_starter_reference, self.dialogue_responder_reference, self.dialogue_opponent_addr, self.dialogue_starter_addr, ) @classmethod def from_str(cls, obj: str) -> "DialogueLabel": """Get the dialogue label from string representation.""" ( dialogue_starter_reference, dialogue_responder_reference, dialogue_opponent_addr, dialogue_starter_addr, ) = obj.split("_") dialogue_label = DialogueLabel( (dialogue_starter_reference, dialogue_responder_reference), dialogue_opponent_addr, dialogue_starter_addr, ) return dialogue_label class _DialogueMeta(type): """ Metaclass for Dialogue. Creates class level Rules instance to share among instances """ def __new__(cls, name: str, bases: Tuple[Type], dct: Dict) -> "_DialogueMeta": """Construct a new type.""" # set class level `_rules` dialogue_cls = cast(Type["Dialogue"], super().__new__(cls, name, bases, dct)) dialogue_cls._rules = dialogue_cls.Rules( dialogue_cls.INITIAL_PERFORMATIVES, dialogue_cls.TERMINAL_PERFORMATIVES, dialogue_cls.VALID_REPLIES, ) return dialogue_cls class Dialogue(metaclass=_DialogueMeta): """The dialogue class maintains state of a dialogue and manages it.""" STARTING_MESSAGE_ID = 1 STARTING_TARGET = 0 UNASSIGNED_DIALOGUE_REFERENCE = "" INITIAL_PERFORMATIVES = frozenset() # type: FrozenSet[Message.Performative] TERMINAL_PERFORMATIVES = frozenset() # type: FrozenSet[Message.Performative] VALID_REPLIES = ( dict() ) # type: Dict[Message.Performative, FrozenSet[Message.Performative]] __slots__ = ( "_self_address", "_dialogue_label", "_role", "_message_class", "_outgoing_messages", "_incoming_messages", "_terminal_state_callbacks", "_last_message_id", "_ordered_message_ids", ) class Rules: """This class defines the rules for the dialogue.""" def __init__( self, initial_performatives: FrozenSet[Message.Performative], terminal_performatives: FrozenSet[Message.Performative], valid_replies: Dict[Message.Performative, FrozenSet[Message.Performative]], ) -> None: """ Initialize a dialogue. :param initial_performatives: the set of all initial performatives. :param terminal_performatives: the set of all terminal performatives. :param valid_replies: the reply structure of speech-acts. """ self._initial_performatives = initial_performatives self._terminal_performatives = terminal_performatives self._valid_replies = valid_replies @property def initial_performatives(self) -> FrozenSet[Message.Performative]: """ Get the performatives one of which the terminal message in the dialogue must have. :return: the valid performatives of an terminal message """ return self._initial_performatives @property def terminal_performatives(self) -> FrozenSet[Message.Performative]: """ Get the performatives one of which the terminal message in the dialogue must have. :return: the valid performatives of an terminal message """ return self._terminal_performatives @property def valid_replies( self, ) -> Dict[Message.Performative, FrozenSet[Message.Performative]]: """ Get all the valid performatives which are a valid replies to performatives. :return: the full valid reply structure. """ return self._valid_replies def get_valid_replies( self, performative: Message.Performative ) -> FrozenSet[Message.Performative]: """ Given a `performative`, return the list of performatives which are its valid replies in a dialogue. :param performative: the performative in a message :return: list of valid performative replies """ enforce( performative in self.valid_replies, "this performative '{}' is not supported".format(performative), ) return self.valid_replies[performative] class Role(Enum): """This class defines the agent's role in a dialogue.""" def __str__(self) -> str: """Get the string representation.""" return str(self.value) class EndState(Enum): """This class defines the end states of a dialogue.""" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _rules: Optional[Rules] = None def __init__( self, dialogue_label: DialogueLabel, message_class: Type[Message], self_address: Address, role: Role, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param message_class: the message class used :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for """ self._self_address = self_address self._dialogue_label = dialogue_label self._role = role self._outgoing_messages = [] # type: List[Message] self._incoming_messages = [] # type: List[Message] enforce( issubclass(message_class, Message), "Message class provided not a subclass of `Message`.", ) self._message_class = message_class self._terminal_state_callbacks: Set[Callable[["Dialogue"], None]] = set() self._last_message_id: Optional[int] = None self._ordered_message_ids: List[int] = [] def add_terminal_state_callback(self, fn: Callable[["Dialogue"], None]) -> None: """ Add callback to be called on dialogue reach terminal state. :param fn: callable to be called with one argument: Dialogue """ self._terminal_state_callbacks.add(fn) def __eq__(self, other: Any) -> bool: """Compare two dialogues.""" return ( type(self) == type(other) # pylint: disable=unidiomatic-typecheck and self.dialogue_label == other.dialogue_label and self.message_class == other.message_class and self._incoming_messages == other._incoming_messages and self._outgoing_messages == other._outgoing_messages and self._ordered_message_ids == other._ordered_message_ids and self.role == other.role and self.self_address == other.self_address ) def json(self) -> dict: """Get json representation of the dialogue.""" data = { "dialogue_label": self._dialogue_label.json, "self_address": self.self_address, "role": self._role.value, "incoming_messages": [i.json() for i in self._incoming_messages], "outgoing_messages": [i.json() for i in self._outgoing_messages], "last_message_id": self._last_message_id, "ordered_message_ids": self._ordered_message_ids, } return data @classmethod def from_json(cls, message_class: Type[Message], data: dict) -> "Dialogue": """ Create a dialogue instance with all messages from json data. :param message_class: type of message used with this dialogue :param data: dict with data exported with Dialogue.to_json() method :return: Dialogue instance """ try: obj = cls( dialogue_label=DialogueLabel.from_json(data["dialogue_label"]), message_class=message_class, self_address=Address(data["self_address"]), role=cls.Role(data["role"]), ) obj._incoming_messages = [ # pylint: disable=protected-access message_class.from_json(i) for i in data["incoming_messages"] ] obj._outgoing_messages = [ # pylint: disable=protected-access message_class.from_json(i) for i in data["outgoing_messages"] ] last_message_id = int(data["last_message_id"]) obj._last_message_id = last_message_id # pylint: disable=protected-access obj._ordered_message_ids = [ # pylint: disable=protected-access int(el) for el in data["ordered_message_ids"] ] return obj except KeyError: # pragma: nocover raise ValueError(f"Dialogue representation is invalid: {data}") @property def dialogue_label(self) -> DialogueLabel: """ Get the dialogue label. :return: The dialogue label """ return self._dialogue_label @property def incomplete_dialogue_label(self) -> DialogueLabel: """ Get the dialogue label. :return: The incomplete dialogue label """ return self.dialogue_label.get_incomplete_version() @property def dialogue_labels(self) -> Set[DialogueLabel]: """ Get the dialogue labels (incomplete and complete, if it exists). :return: the dialogue labels """ return {self.dialogue_label, self.incomplete_dialogue_label} @property def self_address(self) -> Address: """ Get the address of the entity for whom this dialogues is maintained. :return: the address of this entity """ if self._self_address is None: # pragma: nocover raise ValueError("self_address is not set.") return self._self_address @property def role(self) -> "Role": """ Get the agent's role in the dialogue. :return: the agent's role """ if self._role is None: # pragma: nocover raise ValueError("Role is not set.") return self._role @property def rules(self) -> "Rules": """ Get the dialogue rules. :return: the rules """ if self._rules is None: # pragma: nocover raise ValueError("Rules is not set.") return self._rules @property def message_class(self) -> Type[Message]: """ Get the message class. :return: the message class """ return self._message_class @property def is_self_initiated(self) -> bool: """ Check whether the agent initiated the dialogue. :return: True if the agent initiated the dialogue, False otherwise """ return ( self.dialogue_label.dialogue_opponent_addr is not self.dialogue_label.dialogue_starter_addr ) @property def last_incoming_message(self) -> Optional[Message]: """ Get the last incoming message. :return: the last incoming message if it exists, None otherwise """ return self._incoming_messages[-1] if len(self._incoming_messages) > 0 else None @property def last_outgoing_message(self) -> Optional[Message]: """ Get the last outgoing message. :return: the last outgoing message if it exists, None otherwise """ return self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None @property def last_message(self) -> Optional[Message]: """ Get the last message. :return: the last message if it exists, None otherwise """ if self._last_message_id is None: return None if ( self.last_incoming_message and self.last_incoming_message.message_id == self._last_message_id ): return self.last_incoming_message return self.last_outgoing_message @property def is_empty(self) -> bool: """ Check whether the dialogue is empty. :return: True if empty, False otherwise """ return len(self._outgoing_messages) == 0 and len(self._incoming_messages) == 0 def _counterparty_from_message(self, message: Message) -> Address: """ Determine the counterparty of the agent in the dialogue from a message. :param message: the message :return: The address of the counterparty """ counterparty = ( message.to if self._is_message_by_self(message) else message.sender ) return counterparty def _is_message_by_self(self, message: Message) -> bool: """ Check whether the message is by this agent or not. :param message: the message :return: True if message is by this agent, False otherwise """ return message.sender == self.self_address def _is_message_by_other(self, message: Message) -> bool: """ Check whether the message is by the counterparty agent in this dialogue or not. :param message: the message :return: True if message is by the counterparty agent in this dialogue, False otherwise """ return not self._is_message_by_self(message) def _has_message_id(self, message_id: int) -> bool: """ Check whether a message with the supplied message id exists in this dialogue. :param message_id: the message id :return: True if message with that id exists in this dialogue, False otherwise """ return self.get_message_by_id(message_id) is not None def _update(self, message: Message) -> None: """ Extend the list of incoming/outgoing messages with 'message', if 'message' belongs to dialogue and is valid. :param message: a message to be added :raises: InvalidDialogueMessage: if message does not belong to this dialogue, or if message is invalid """ if not message.has_sender: message.sender = self.self_address # pragma: nocover if not self._is_belonging_to_dialogue(message): raise InvalidDialogueMessage( "The message {} does not belong to this dialogue." "The dialogue reference of the message is {}, while the dialogue reference of the dialogue is {}".format( message.message_id, message.dialogue_reference, self.dialogue_label.dialogue_reference, ) ) is_valid_result, validation_message = self._validate_next_message(message) if not is_valid_result: raise InvalidDialogueMessage( "Message {} is invalid with respect to this dialogue. Error: {}".format( message.message_id, validation_message, ) ) if self._is_message_by_self(message): self._outgoing_messages.append(message) else: self._incoming_messages.append(message) self._last_message_id = message.message_id self._ordered_message_ids.append(message.message_id) if message.performative in self.rules.terminal_performatives: for fn in self._terminal_state_callbacks: fn(self) def _is_belonging_to_dialogue(self, message: Message) -> bool: """ Check if the message is belonging to the dialogue. :param message: the message :return: True if message is part of the dialogue, False otherwise """ opponent = self._counterparty_from_message(message) if self.is_self_initiated: self_initiated_dialogue_label = DialogueLabel( ( message.dialogue_reference[0], Dialogue.UNASSIGNED_DIALOGUE_REFERENCE, ), opponent, self.self_address, ) result = self_initiated_dialogue_label in self.dialogue_labels else: other_initiated_dialogue_label = DialogueLabel( message.dialogue_reference, opponent, opponent, ) result = other_initiated_dialogue_label in self.dialogue_labels return result def reply( self, performative: Message.Performative, target_message: Optional[Message] = None, target: Optional[int] = None, **kwargs: Any, ) -> Message: """ Reply to the 'target_message' in this dialogue with a message with 'performative', and contents from kwargs. Note if no target_message is provided, the last message in the dialogue will be replied to. :param target_message: the message to reply to. :param target: the id of the message to reply to. :param performative: the performative of the reply message. :param kwargs: the content of the reply message. :return: the reply message if it was successfully added as a reply, None otherwise. """ last_message = self.last_message if last_message is None: raise ValueError("Cannot reply in an empty dialogue!") if target_message is None and target is not None: target_message = self.get_message_by_id(target) elif target_message is None and target is None: target_message = last_message target = last_message.message_id elif target_message is not None and target is None: target = target_message.message_id elif target_message is not None and target is not None: if target != target_message.message_id: raise AEAEnforceError( "The provided target and target_message do not match." ) if target_message is None: raise ValueError("No target message found!") enforce( self._has_message_id(target), # type: ignore "The target message does not exist in this dialogue.", ) reply = self._message_class( dialogue_reference=self.dialogue_label.dialogue_reference, message_id=self.get_outgoing_next_message_id(), target=target, performative=performative, **kwargs, ) reply.sender = self.self_address reply.to = self.dialogue_label.dialogue_opponent_addr self._update(reply) return reply def _validate_next_message(self, message: Message) -> Tuple[bool, str]: """ Check whether 'message' is a valid next message in this dialogue. The evaluation of a message validity involves performing several categories of checks. Each category of checks resides in a separate method. Currently, basic rules are general fundamental structural constraints, additional rules are applied for the time being, and more specific rules to each dialogue are captured in the is_valid method. :param message: the message to be validated :return: Boolean result, and associated message. """ is_basic_validated, msg_basic_validation = self._basic_validation(message) if not is_basic_validated: return False, msg_basic_validation result_is_valid, msg_is_valid = self._custom_validation(message) if not result_is_valid: return False, msg_is_valid return True, "Message is valid with respect to this dialogue." def _basic_validation(self, message: Message) -> Tuple[bool, str]: """ Check whether 'message' is a valid next message in the dialogue, according to basic rules. This method redirects the checks to two other methods based on whether the message is the first in the dialogue or not. :param message: the message to be validated :return: Boolean result, and associated message. """ if self.is_empty: # initial message return self._basic_validation_initial_message(message) return self._basic_validation_non_initial_message(message) def _basic_validation_initial_message(self, message: Message) -> Tuple[bool, str]: """ Check whether an initial 'message' is a valid next message in the dialogue, according to basic rules. These rules are designed to be fundamental to all dialogues, and enforce the following: - message ids are consistent - targets are consistent - message targets are according to the reply structure of performatives :param message: the message to be validated :return: Boolean result, and associated message. """ dialogue_reference = message.dialogue_reference message_id = message.message_id performative = message.performative if dialogue_reference[0] != self.dialogue_label.dialogue_reference[0]: return ( False, "Invalid dialogue_reference[0]. Expected {}. Found {}.".format( self.dialogue_label.dialogue_reference[0], dialogue_reference[0] ), ) if message_id != Dialogue.STARTING_MESSAGE_ID: return ( False, "Invalid message_id. Expected {}. Found {}.".format( Dialogue.STARTING_MESSAGE_ID, message_id ), ) err = self._validate_message_target(message) if err: return False, err if performative not in self.rules.initial_performatives: return ( False, "Invalid initial performative. Expected one of {}. Found {}.".format( self.rules.initial_performatives, performative ), ) return True, "The initial message passes basic validation." def _basic_validation_non_initial_message( self, message: Message ) -> Tuple[bool, str]: """ Check whether a non-initial 'message' is a valid next message in the dialogue, according to basic rules. These rules are designed to be fundamental to all dialogues, and enforce the following: - message ids are consistent - targets are consistent - message targets are according to the reply structure of performatives :param message: the message to be validated :return: Boolean result, and associated message. """ dialogue_reference = message.dialogue_reference if dialogue_reference[0] != self.dialogue_label.dialogue_reference[0]: return ( False, "Invalid dialogue_reference[0]. Expected {}. Found {}.".format( self.dialogue_label.dialogue_reference[0], dialogue_reference[0] ), ) err = self._validate_message_id(message) if err: return False, err err = self._validate_message_target(message) if err: return False, err return True, "The non-initial message passes basic validation." def _validate_message_target(self, message: Message) -> Optional[str]: """Check message target corresponds to messages in the dialogue, if not return error string.""" target = message.target performative = message.performative if message.message_id == self.STARTING_MESSAGE_ID: # for initial message! if target == self.STARTING_TARGET: # no need to check in details return None return "Invalid target. Expected 0. Found {}.".format(target) if ( message.message_id != self.STARTING_MESSAGE_ID and target == self.STARTING_TARGET ): return "Invalid target. Expected a non-zero integer. Found {}.".format( target ) # quick target check. latest_ids: List[int] = [] if self.last_incoming_message: latest_ids.append(abs(self.last_incoming_message.message_id)) if self.last_outgoing_message: latest_ids.append(abs(self.last_outgoing_message.message_id)) if abs(target) > max(latest_ids): return "Invalid target. Expected a value less than or equal to abs({}). Found abs({}).".format( max(latest_ids), abs(target) ) # detailed target check target_message = self.get_message_by_id(target) if not target_message: return "Invalid target {}. target_message can not be found.".format( target ) # pragma: nocover target_performative = target_message.performative if performative not in self.rules.get_valid_replies(target_performative): return "Invalid performative. Expected one of {}. Found {}.".format( self.rules.get_valid_replies(target_performative), performative ) return None def _validate_message_id(self, message: Message) -> Optional[str]: """Check message id corresponds to message id sequences, if not return error string.""" is_outgoing = message.to != self.self_address # This assumes that messages sent by the opponent are sent in the right order. if is_outgoing: next_message_id = self.get_outgoing_next_message_id() else: next_message_id = self.get_incoming_next_message_id() # we know what is the next message id for incoming and outgoing! if message.message_id != next_message_id: return "Invalid message_id. Expected {}. Found {}.".format( next_message_id, message.message_id ) return None def get_message_by_id(self, message_id: int) -> Optional[Message]: """Get message by id, if not presents return None.""" if self.is_empty: return None if message_id == 0: raise ValueError("message_id == 0 is invalid!") # pragma: nocover if bool(message_id > 0) == self.is_self_initiated: messages_list = self._outgoing_messages else: messages_list = self._incoming_messages if len(messages_list) == 0: return None if abs(message_id) > abs(messages_list[-1].message_id): return None return messages_list[abs(message_id) - 1] def get_outgoing_next_message_id(self) -> int: """Get next outgoing message id.""" next_message_id = Dialogue.STARTING_MESSAGE_ID if self.last_outgoing_message: next_message_id = abs(self.last_outgoing_message.message_id) + 1 if not self.is_self_initiated: next_message_id = 0 - next_message_id return next_message_id def get_incoming_next_message_id(self) -> int: """Get next incoming message id.""" next_message_id = Dialogue.STARTING_MESSAGE_ID if self.last_incoming_message: next_message_id = abs(self.last_incoming_message.message_id) + 1 if self.is_self_initiated: next_message_id = 0 - next_message_id return next_message_id def _update_dialogue_label(self, final_dialogue_label: DialogueLabel) -> None: """ Update the dialogue label of the dialogue. :param final_dialogue_label: the final dialogue label """ enforce( self.dialogue_label.dialogue_reference[1] == self.UNASSIGNED_DIALOGUE_REFERENCE and final_dialogue_label.dialogue_reference[1] != self.UNASSIGNED_DIALOGUE_REFERENCE, "Dialogue label cannot be updated.", ) self._dialogue_label = final_dialogue_label def _custom_validation( # pylint: disable=unused-argument self, message: Message ) -> Tuple[bool, str]: """ Check whether 'message' is a valid next message in the dialogue. These rules capture specific constraints designed for dialogues which are instance of a concrete sub-class of this class. :param message: the message to be validated :return: True if valid, False otherwise. """ return True, "The message passes custom validation." def __str__(self) -> str: """ Get the string representation. :return: The string representation of the dialogue """ representation = f"Dialogue Label:\n{self.dialogue_label}\nMessages:\n" for msg_id in self._ordered_message_ids: msg = self.get_message_by_id(msg_id) if msg is None: # pragma: nocover raise ValueError("Dialogue inconsistent! Missing message.") representation += f"message_id={msg.message_id}, target={msg.target}, performative={msg.performative}\n" return representation class DialogueStats: """Class to handle statistics on default dialogues.""" def __init__(self, end_states: FrozenSet[Dialogue.EndState]) -> None: """ Initialize a StatsManager. :param end_states: the list of dialogue endstates """ self._self_initiated = { e: 0 for e in end_states } # type: Dict[Dialogue.EndState, int] self._other_initiated = { e: 0 for e in end_states } # type: Dict[Dialogue.EndState, int] @property def self_initiated(self) -> Dict[Dialogue.EndState, int]: """Get the stats dictionary on self initiated dialogues.""" return self._self_initiated @property def other_initiated(self) -> Dict[Dialogue.EndState, int]: """Get the stats dictionary on other initiated dialogues.""" return self._other_initiated def add_dialogue_endstate( self, end_state: Dialogue.EndState, is_self_initiated: bool ) -> None: """ Add dialogue endstate stats. :param end_state: the end state of the dialogue :param is_self_initiated: whether the dialogue is initiated by the agent or the opponent """ if is_self_initiated: enforce(end_state in self._self_initiated, "End state not present!") self._self_initiated[end_state] += 1 else: enforce(end_state in self._other_initiated, "End state not present!") self._other_initiated[end_state] += 1 def find_caller_object(object_type: Type) -> Any: """Find caller object of certain type in the call stack.""" caller_object = None for frame_info in inspect.stack(): frame_self = frame_info.frame.f_locals.get("self", None) if not frame_self: continue if not isinstance(frame_self, object_type): continue caller_object = frame_self return caller_object class BasicDialoguesStorage: """Dialogues state storage.""" def __init__(self, dialogues: "Dialogues") -> None: """Init dialogues storage.""" self._dialogues_by_dialogue_label = {} # type: Dict[DialogueLabel, Dialogue] self._dialogue_by_address = defaultdict( list ) # type: Dict[Address, List[Dialogue]] self._incomplete_to_complete_dialogue_labels = ( {} ) # type: Dict[DialogueLabel, DialogueLabel] self._dialogues = dialogues self._terminal_state_dialogues_labels: Set[DialogueLabel] = set() @property def dialogues_in_terminal_state(self) -> List["Dialogue"]: """Get all dialogues in terminal state.""" return list( filter( None, [ self._dialogues_by_dialogue_label.get(i) for i in self._terminal_state_dialogues_labels ], ) ) @property def dialogues_in_active_state(self) -> List["Dialogue"]: """Get all dialogues in active state.""" active_dialogues = ( set(self._dialogues_by_dialogue_label.keys()) - self._terminal_state_dialogues_labels ) return list( filter( None, [self._dialogues_by_dialogue_label.get(i) for i in active_dialogues], ) ) @property def is_terminal_dialogues_kept(self) -> bool: """Return True if dialogues should stay after terminal state.""" return self._dialogues.is_keep_dialogues_in_terminal_state def dialogue_terminal_state_callback(self, dialogue: "Dialogue") -> None: """Method to be called on dialogue terminal state reached.""" if self.is_terminal_dialogues_kept: self._terminal_state_dialogues_labels.add(dialogue.dialogue_label) else: self.remove(dialogue.dialogue_label) def setup(self) -> None: """Set up dialogue storage.""" def teardown(self) -> None: """Tear down dialogue storage.""" def add(self, dialogue: Dialogue) -> None: """ Add dialogue to storage. :param dialogue: dialogue to add. """ dialogue.add_terminal_state_callback(self.dialogue_terminal_state_callback) self._dialogues_by_dialogue_label[dialogue.dialogue_label] = dialogue self._dialogue_by_address[ dialogue.dialogue_label.dialogue_opponent_addr ].append(dialogue) def _add_terminal_state_dialogue(self, dialogue: Dialogue) -> None: """ Add terminal state dialogue to storage. :param dialogue: dialogue to add. """ self.add(dialogue) self._terminal_state_dialogues_labels.add(dialogue.dialogue_label) def remove(self, dialogue_label: DialogueLabel) -> None: """ Remove dialogue from storage by it's label. :param dialogue_label: label of the dialogue to remove """ dialogue = self._dialogues_by_dialogue_label.pop(dialogue_label, None) self._incomplete_to_complete_dialogue_labels.pop(dialogue_label, None) if dialogue_label in self._terminal_state_dialogues_labels: self._terminal_state_dialogues_labels.remove(dialogue_label) if dialogue: self._dialogue_by_address[dialogue_label.dialogue_opponent_addr].remove( dialogue ) def get(self, dialogue_label: DialogueLabel) -> Optional[Dialogue]: """ Get dialogue stored by it's label. :param dialogue_label: label of the dialogue :return: dialogue if presents or None """ return self._dialogues_by_dialogue_label.get(dialogue_label, None) def get_dialogues_with_counterparty(self, counterparty: Address) -> List[Dialogue]: """ Get the dialogues by address. :param counterparty: the counterparty :return: The dialogues with the counterparty. """ return self._dialogue_by_address.get(counterparty, []) def is_in_incomplete(self, dialogue_label: DialogueLabel) -> bool: """Check dialogue label presents in list of incomplete.""" return dialogue_label in self._incomplete_to_complete_dialogue_labels def set_incomplete_dialogue( self, incomplete_dialogue_label: DialogueLabel, complete_dialogue_label: DialogueLabel, ) -> None: """Set incomplete dialogue label.""" self._incomplete_to_complete_dialogue_labels[ incomplete_dialogue_label ] = complete_dialogue_label def is_dialogue_present(self, dialogue_label: DialogueLabel) -> bool: """Check dialogue with label specified presents in storage.""" return dialogue_label in self._dialogues_by_dialogue_label def get_latest_label(self, dialogue_label: DialogueLabel) -> DialogueLabel: """Get latest label for dialogue.""" return self._incomplete_to_complete_dialogue_labels.get( dialogue_label, dialogue_label ) class PersistDialoguesStorage(BasicDialoguesStorage): """ Persist dialogues storage. Uses generic storage to load/save dialogues data on setup/teardown. """ INCOMPLETE_DIALOGUES_OBJECT_NAME = "incomplete_dialogues" TERMINAL_STATE_DIALOGUES_COLLECTTION_SUFFIX = "_terminal" def __init__(self, dialogues: "Dialogues") -> None: """Init dialogues storage.""" super().__init__(dialogues) self._skill_component: Optional[SkillComponent] = self.get_skill_component() @staticmethod def get_skill_component() -> Optional[SkillComponent]: """Get skill component dialogues storage constructed for.""" caller_object = find_caller_object(SkillComponent) if not caller_object: # pragma: nocover return None return caller_object def _get_collection_name(self) -> Optional[str]: """Generate collection name based on the dialogues class name and skill component.""" if not self._skill_component: # pragma: nocover return None return "_".join( [ self._skill_component.skill_id.author, self._skill_component.skill_id.name, self._skill_component.name, self._skill_component.__class__.__name__, self._dialogues.__class__.__name__, ] ) def _get_collection_instance(self, col_name: str) -> Optional[SyncCollection]: """Get sync collection if generic storage available.""" if ( not self._skill_component or not self._skill_component.context.storage ): # pragma: nocover return None return self._skill_component.context.storage.get_sync_collection(col_name) @cached_property def _terminal_dialogues_collection(self) -> Optional[SyncCollection]: col_name = self._get_collection_name() if not col_name: return None col_name = f"{col_name}{self.TERMINAL_STATE_DIALOGUES_COLLECTTION_SUFFIX}" return self._get_collection_instance(col_name) @cached_property def _active_dialogues_collection(self) -> Optional[SyncCollection]: col_name = self._get_collection_name() if not col_name: return None return self._get_collection_instance(col_name) def _dump(self) -> None: """Dump dialogues storage to the generic storage.""" if ( not self._active_dialogues_collection or not self._terminal_dialogues_collection ): return # pragma: nocover self._dump_incomplete_dialogues_labels(self._active_dialogues_collection) self._dump_dialogues( self.dialogues_in_active_state, self._active_dialogues_collection ) self._dump_dialogues( self.dialogues_in_terminal_state, self._terminal_dialogues_collection ) def _dump_incomplete_dialogues_labels(self, collection: SyncCollection) -> None: """Dump incomplete labels.""" collection.put( self.INCOMPLETE_DIALOGUES_OBJECT_NAME, self._incomplete_dialogues_labels_to_json(), ) def _load_incomplete_dialogues_labels(self, collection: SyncCollection) -> None: """Load and set incomplete dialogue labels.""" incomplete_dialogues_data = collection.get( self.INCOMPLETE_DIALOGUES_OBJECT_NAME ) if incomplete_dialogues_data is not None: incomplete_dialogues_data = cast(List, incomplete_dialogues_data) self._set_incomplete_dialogues_labels_from_json(incomplete_dialogues_data) def _load_dialogues(self, collection: SyncCollection) -> Iterable[Dialogue]: """Load dialogues from collection.""" if not collection: # pragma: nocover return for label, dialogue_data in collection.list(): if label == self.INCOMPLETE_DIALOGUES_OBJECT_NAME: continue dialogue_data = cast(Dict, dialogue_data) yield self._dialogue_from_json(dialogue_data) def _dialogue_from_json(self, dialogue_data: dict) -> "Dialogue": return self._dialogues.dialogue_class.from_json( self._dialogues.message_class, dialogue_data ) @staticmethod def _dump_dialogues( dialogues: Iterable[Dialogue], collection: SyncCollection ) -> None: """Dump dialogues to collection.""" for dialogue in dialogues: collection.put(str(dialogue.dialogue_label), dialogue.json()) def _load(self) -> None: """Dump dialogues and incomplete dialogues labels from the generic storage.""" if ( not self._active_dialogues_collection or not self._terminal_dialogues_collection ): return # pragma: nocover self._load_incomplete_dialogues_labels(self._active_dialogues_collection) self._load_active_dialogues() self._load_terminated_dialogues() def _load_active_dialogues(self) -> None: """Load active dialogues from storage.""" for dialogue in self._load_dialogues(self._active_dialogues_collection): self.add(dialogue) def _load_terminated_dialogues(self) -> None: """Load terminated dialogues from storage.""" for dialogue in self._load_dialogues(self._terminal_dialogues_collection): self._add_terminal_state_dialogue(dialogue) def _incomplete_dialogues_labels_to_json(self) -> List: """Dump incomplete_to_complete_dialogue_labels to json friendly dict.""" return [ [k.json, v.json] for k, v in self._incomplete_to_complete_dialogue_labels.items() ] def _set_incomplete_dialogues_labels_from_json(self, data: List) -> None: """Set incomplete_to_complete_dialogue_labels from json friendly dict.""" self._incomplete_to_complete_dialogue_labels = { DialogueLabel.from_json(k): DialogueLabel.from_json(v) for k, v in data } def setup(self) -> None: """Set up dialogue storage.""" if not self._skill_component: # pragma: nocover return self._load() def teardown(self) -> None: """Tear down dialogue storage.""" if not self._skill_component: # pragma: nocover return self._dump() def remove(self, dialogue_label: DialogueLabel) -> None: """Remove dialogue from memory and persistent storage.""" if dialogue_label in self._terminal_state_dialogues_labels: collection = self._terminal_dialogues_collection else: collection = self._active_dialogues_collection super().remove(dialogue_label) if collection: collection.remove(str(dialogue_label)) class PersistDialoguesStorageWithOffloading(PersistDialoguesStorage): """Dialogue Storage with dialogues offloading.""" def dialogue_terminal_state_callback(self, dialogue: "Dialogue") -> None: """Call on dialogue reaches terminal state.""" if ( not self.is_terminal_dialogues_kept or not self._terminal_dialogues_collection ): # pragma: nocover super().dialogue_terminal_state_callback(dialogue) return # do offloading # push to storage self._terminal_dialogues_collection.put( str(dialogue.dialogue_label), dialogue.json() ) # remove from memory self.remove(dialogue.dialogue_label) def get(self, dialogue_label: DialogueLabel) -> Optional[Dialogue]: """Try to get dialogue by label from memory or persists storage.""" dialogue = super().get(dialogue_label) if dialogue: return dialogue dialogue = self._get_dialogue_from_collection( dialogue_label, self._terminal_dialogues_collection ) if dialogue: # get dialogue from terminal state collection and cache it self._add_terminal_state_dialogue(dialogue) return dialogue return None def _get_dialogue_from_collection( self, dialogue_label: "DialogueLabel", collection: SyncCollection ) -> Optional[Dialogue]: """ Get dialogue by label from collection. :param dialogue_label: label for lookup :param collection: collection with dialogues :return: dialogue if exists """ if not collection: return None dialogue_data = collection.get(str(dialogue_label)) if not dialogue_data: return None dialogue_data = cast(Dict, dialogue_data) return self._dialogue_from_json(dialogue_data) def _load_terminated_dialogues(self) -> None: """Skip terminated dialogues loading, cause it's offloaded.""" def _get_dialogues_by_address_from_collection( self, address: Address, collection: SyncCollection ) -> List["Dialogue"]: """ Get all dialogues with opponent address from specified collection. :param address: address for lookup. :param: collection: collection to get dialogues from. :return: list of dialogues """ if not collection: return [] return [ self._dialogue_from_json(cast(Dict, i[1])) for i in collection.find("dialogue_label.dialogue_opponent_addr", address) ] def get_dialogues_with_counterparty(self, counterparty: Address) -> List[Dialogue]: """ Get the dialogues by address. :param counterparty: the counterparty :return: The dialogues with the counterparty. """ dialogues = ( self._get_dialogues_by_address_from_collection( counterparty, self._active_dialogues_collection ) + self._get_dialogues_by_address_from_collection( counterparty, self._terminal_dialogues_collection ) + super().get_dialogues_with_counterparty(counterparty) ) return self._unique_dialogues_by_label(dialogues) @staticmethod def _unique_dialogues_by_label(dialogues: List[Dialogue]) -> List[Dialogue]: """Filter list of dialogues by unique dialogue label.""" return list( {dialogue.dialogue_label: dialogue for dialogue in dialogues}.values() ) @property def dialogues_in_terminal_state(self) -> List["Dialogue"]: """Get all dialogues in terminal state.""" dialogues = super().dialogues_in_terminal_state + list( self._load_dialogues(self._terminal_dialogues_collection) ) return self._unique_dialogues_by_label(dialogues) class Dialogues: """The dialogues class keeps track of all dialogues for an agent.""" _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, end_states: FrozenSet[Dialogue.EndState], message_class: Type[Message], dialogue_class: Type[Dialogue], role_from_first_message: Callable[[Message, Address], Dialogue.Role], keep_terminal_state_dialogues: Optional[bool] = None, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param end_states: the list of dialogue endstates :param message_class: the message class used :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message :param keep_terminal_state_dialogues: specify do dialogues in terminal state should stay or not """ self._dialogues_storage = PersistDialoguesStorageWithOffloading(self) self._self_address = self_address self._dialogue_stats = DialogueStats(end_states) if keep_terminal_state_dialogues is not None: self._keep_terminal_state_dialogues = keep_terminal_state_dialogues enforce( issubclass(message_class, Message), "message_class is not a subclass of Message.", ) self._message_class = message_class enforce( issubclass(dialogue_class, Dialogue), "dialogue_class is not a subclass of Dialogue.", ) self._dialogue_class = dialogue_class # Note the following might be too restrictive; if the supplied role_from_first_message function # does not have the type hinting for its parameter or its return value, the second and third checks # below would fail. sig = signature(role_from_first_message) parameter_length = len(sig.parameters.keys()) enforce( parameter_length == 2, "Invalid number of parameters for role_from_first_message. Expected 2. Found {}.".format( parameter_length ), ) parameter_1_type = list(sig.parameters.values())[0].annotation enforce( parameter_1_type == Message, "Invalid type for the first parameter of role_from_first_message. Expected 'Message'. Found {}.".format( parameter_1_type ), ) parameter_2_type = list(sig.parameters.values())[1].annotation enforce( parameter_2_type == Address, "Invalid type for the second parameter of role_from_first_message. Expected 'Address'. Found {}.".format( parameter_2_type ), ) return_type = sig.return_annotation enforce( return_type == Dialogue.Role, "Invalid return type for role_from_first_message. Expected 'Dialogue.Role'. Found {}.".format( return_type ), ) self._role_from_first_message = role_from_first_message @property def is_keep_dialogues_in_terminal_state(self) -> bool: """Is required to keep dialogues in terminal state.""" return self._keep_terminal_state_dialogues @property def self_address(self) -> Address: """Get the address of the agent for whom dialogues are maintained.""" enforce(self._self_address != "", "self_address is not set.") return self._self_address @property def dialogue_stats(self) -> DialogueStats: """ Get the dialogue statistics. :return: dialogue stats object """ return self._dialogue_stats @property def message_class(self) -> Type[Message]: """ Get the message class. :return: the message class """ return self._message_class @property def dialogue_class(self) -> Type[Dialogue]: """ Get the dialogue class. :return: the dialogue class """ return self._dialogue_class def get_dialogues_with_counterparty(self, counterparty: Address) -> List[Dialogue]: """ Get the dialogues by address. :param counterparty: the counterparty :return: The dialogues with the counterparty. """ return self._dialogues_storage.get_dialogues_with_counterparty(counterparty) def _is_message_by_self(self, message: Message) -> bool: """ Check whether the message is by this agent or not. :param message: the message :return: True if message is by this agent, False otherwise """ return message.sender == self.self_address def _is_message_by_other(self, message: Message) -> bool: """ Check whether the message is by the counterparty agent in this dialogue or not. :param message: the message :return: True if message is by the counterparty agent in this dialogue, False otherwise """ return not self._is_message_by_self(message) def _counterparty_from_message(self, message: Message) -> Address: """ Determine the counterparty of the agent in the dialogue from a message. :param message: the message :return: The address of the counterparty """ counterparty = ( message.to if self._is_message_by_self(message) else message.sender ) return counterparty @classmethod def new_self_initiated_dialogue_reference(cls) -> Tuple[str, str]: """ Return a dialogue label for a new self initiated dialogue. :return: the next nonce """ return cls._generate_dialogue_nonce(), Dialogue.UNASSIGNED_DIALOGUE_REFERENCE def create( self, counterparty: Address, performative: Message.Performative, **kwargs: Any, ) -> Tuple[Message, Dialogue]: """ Create a dialogue with 'counterparty', with an initial message whose performative is 'performative' and contents are from 'kwargs'. :param counterparty: the counterparty of the dialogue. :param performative: the performative of the initial message. :param kwargs: the content of the initial message. :return: the initial message and the dialogue. """ initial_message = self._message_class( dialogue_reference=self.new_self_initiated_dialogue_reference(), message_id=Dialogue.STARTING_MESSAGE_ID, target=Dialogue.STARTING_TARGET, performative=performative, **kwargs, ) initial_message.sender = self.self_address initial_message.to = counterparty dialogue = self._create_dialogue(counterparty, initial_message) return initial_message, dialogue def create_with_message( self, counterparty: Address, initial_message: Message ) -> Dialogue: """ Create a dialogue with 'counterparty', with an initial message provided. :param counterparty: the counterparty of the dialogue. :param initial_message: the initial_message. :return: the initial message and the dialogue. """ enforce( not initial_message.has_sender, "The message's 'sender' field is already set {}".format(initial_message), ) enforce( not initial_message.has_to, "The message's 'to' field is already set {}".format(initial_message), ) initial_message.sender = self.self_address initial_message.to = counterparty dialogue = self._create_dialogue(counterparty, initial_message) return dialogue def _create_dialogue( self, counterparty: Address, initial_message: Message ) -> Dialogue: """ Create a dialogue from an initial message provided. :param counterparty: the counterparty of the dialogue. :param initial_message: the initial_message. :return: the dialogue. """ dialogue = self._create_self_initiated( dialogue_opponent_addr=counterparty, dialogue_reference=initial_message.dialogue_reference, role=self._role_from_first_message(initial_message, self.self_address), ) try: dialogue._update(initial_message) # pylint: disable=protected-access except InvalidDialogueMessage as e: self._dialogues_storage.remove(dialogue.dialogue_label) raise ValueError( f"Cannot create a dialogue with the specified performative and contents. {e}" ) from e return dialogue def update(self, message: Message) -> Optional[Dialogue]: """ Update the state of dialogues with a new incoming message. If the message is for a new dialogue, a new dialogue is created with 'message' as its first message, and returned. If the message is addressed to an existing dialogue, the dialogue is retrieved, extended with this message and returned. If there are any errors, e.g. the message dialogue reference does not exists or the message is invalid w.r.t. the dialogue, return None. :param message: a new incoming message :return: the new or existing dialogue the message is intended for, or None in case of any errors. """ enforce( message.has_sender and self._is_message_by_other(message), "Invalid 'update' usage. Update must only be used with a message by another agent.", ) enforce( message.has_to, "The message's 'to' field is not set {}".format(message) ) enforce( message.to == self.self_address, f"Message to and dialogue self address do not match. Got 'to={message.to}' expected 'to={self.self_address}'.", ) dialogue_reference = message.dialogue_reference is_invalid_label = ( dialogue_reference[0] == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE and dialogue_reference[1] == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE ) is_new_dialogue = ( dialogue_reference[0] != Dialogue.UNASSIGNED_DIALOGUE_REFERENCE and dialogue_reference[1] == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE and message.message_id == Dialogue.STARTING_MESSAGE_ID ) is_incomplete_label_and_non_initial_msg = ( dialogue_reference[0] != Dialogue.UNASSIGNED_DIALOGUE_REFERENCE and dialogue_reference[1] == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE and message.message_id not in (Dialogue.STARTING_MESSAGE_ID, Dialogue.STARTING_TARGET) ) if is_invalid_label: dialogue = None # type: Optional[Dialogue] elif is_new_dialogue: # initial message for new dialogue dialogue = self._create_opponent_initiated( dialogue_opponent_addr=message.sender, dialogue_reference=dialogue_reference, role=self._role_from_first_message(message, self.self_address), ) elif is_incomplete_label_and_non_initial_msg: # we can allow a dialogue to have incomplete reference # as multiple messages can be sent before one is received with complete reference dialogue = self.get_dialogue(message) else: # non-initial message for existing dialogue self._complete_dialogue_reference(message) dialogue = self.get_dialogue(message) if dialogue is not None: try: dialogue._update(message) # pylint: disable=protected-access result = dialogue # type: Optional[Dialogue] except InvalidDialogueMessage: # invalid message for the dialogue found result = None if ( is_new_dialogue ): # remove the newly created dialogue if the initial message is invalid self._dialogues_storage.remove(dialogue.dialogue_label) else: # couldn't find the dialogue referenced by the message result = None return result def _complete_dialogue_reference(self, message: Message) -> None: """ Update a self initiated dialogue label with a complete dialogue reference from counterparty's first message. :param message: A message in the dialogue (the first by the counterparty with a complete reference) """ complete_dialogue_reference = message.dialogue_reference enforce( Dialogue.UNASSIGNED_DIALOGUE_REFERENCE not in (complete_dialogue_reference[0], complete_dialogue_reference[1]), "Only complete dialogue references allowed.", ) incomplete_dialogue_reference = ( complete_dialogue_reference[0], Dialogue.UNASSIGNED_DIALOGUE_REFERENCE, ) incomplete_dialogue_label = DialogueLabel( incomplete_dialogue_reference, message.sender, self.self_address, ) if self._dialogues_storage.is_dialogue_present( incomplete_dialogue_label ) and not self._dialogues_storage.is_in_incomplete(incomplete_dialogue_label): dialogue = self._dialogues_storage.get(incomplete_dialogue_label) if not dialogue: # pragma: nocover raise ValueError("no dialogue found") self._dialogues_storage.remove(incomplete_dialogue_label) final_dialogue_label = DialogueLabel( complete_dialogue_reference, incomplete_dialogue_label.dialogue_opponent_addr, incomplete_dialogue_label.dialogue_starter_addr, ) dialogue._update_dialogue_label( # pylint: disable=protected-access final_dialogue_label ) self._dialogues_storage.add(dialogue) self._dialogues_storage.set_incomplete_dialogue( incomplete_dialogue_label, final_dialogue_label ) def get_dialogue(self, message: Message) -> Optional[Dialogue]: """ Retrieve the dialogue 'message' belongs to. :param message: a message :return: the dialogue, or None in case such a dialogue does not exist """ self_initiated_dialogue_label = DialogueLabel( message.dialogue_reference, self._counterparty_from_message(message), self.self_address, ) other_initiated_dialogue_label = DialogueLabel( message.dialogue_reference, self._counterparty_from_message(message), self._counterparty_from_message(message), ) self_initiated_dialogue_label = self._get_latest_label( self_initiated_dialogue_label ) other_initiated_dialogue_label = self._get_latest_label( other_initiated_dialogue_label ) self_initiated_dialogue = self.get_dialogue_from_label( self_initiated_dialogue_label ) other_initiated_dialogue = self.get_dialogue_from_label( other_initiated_dialogue_label ) result = self_initiated_dialogue or other_initiated_dialogue return result def _get_latest_label(self, dialogue_label: DialogueLabel) -> DialogueLabel: """ Retrieve the latest dialogue label if present otherwise return same label. :param dialogue_label: the dialogue label :return: the dialogue label """ return self._dialogues_storage.get_latest_label(dialogue_label) def get_dialogue_from_label( self, dialogue_label: DialogueLabel ) -> Optional[Dialogue]: """ Retrieve a dialogue based on its label. :param dialogue_label: the dialogue label :return: the dialogue if present """ return self._dialogues_storage.get(dialogue_label) def _create_self_initiated( self, dialogue_opponent_addr: Address, dialogue_reference: Tuple[str, str], role: Dialogue.Role, ) -> Dialogue: """ Create a self initiated dialogue. :param dialogue_opponent_addr: the address of the agent with which the dialogue is kept. :param dialogue_reference: the reference of the dialogue :param role: the agent's role :return: the created dialogue. """ enforce( dialogue_reference[0] != Dialogue.UNASSIGNED_DIALOGUE_REFERENCE and dialogue_reference[1] == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE, "Cannot initiate dialogue with preassigned dialogue_responder_reference!", ) incomplete_dialogue_label = DialogueLabel( dialogue_reference, dialogue_opponent_addr, self.self_address ) dialogue = self._create(incomplete_dialogue_label, role) return dialogue def _create_opponent_initiated( self, dialogue_opponent_addr: Address, dialogue_reference: Tuple[str, str], role: Dialogue.Role, ) -> Dialogue: """ Create an opponent initiated dialogue. :param dialogue_opponent_addr: the address of the agent with which the dialogue is kept. :param dialogue_reference: the reference of the dialogue. :param role: the agent's role :return: the created dialogue """ enforce( dialogue_reference[0] != Dialogue.UNASSIGNED_DIALOGUE_REFERENCE and dialogue_reference[1] == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE, "Cannot initiate dialogue with preassigned dialogue_responder_reference!", ) incomplete_dialogue_label = DialogueLabel( dialogue_reference, dialogue_opponent_addr, dialogue_opponent_addr ) new_dialogue_reference = ( dialogue_reference[0], self._generate_dialogue_nonce(), ) complete_dialogue_label = DialogueLabel( new_dialogue_reference, dialogue_opponent_addr, dialogue_opponent_addr ) dialogue = self._create( incomplete_dialogue_label, role, complete_dialogue_label ) return dialogue def _create( self, incomplete_dialogue_label: DialogueLabel, role: Dialogue.Role, complete_dialogue_label: Optional[DialogueLabel] = None, ) -> Dialogue: """ Create a dialogue from label and role. :param incomplete_dialogue_label: the dialogue label (incomplete) :param role: the agent's role :param complete_dialogue_label: the dialogue label (complete) :return: the created dialogue """ enforce( not self._dialogues_storage.is_in_incomplete(incomplete_dialogue_label), "Incomplete dialogue label already present.", ) if complete_dialogue_label is None: dialogue_label = incomplete_dialogue_label else: self._dialogues_storage.set_incomplete_dialogue( incomplete_dialogue_label, complete_dialogue_label ) dialogue_label = complete_dialogue_label enforce( not self._dialogues_storage.is_dialogue_present(dialogue_label), "Dialogue label already present in dialogues.", ) dialogue = self._dialogue_class( dialogue_label=dialogue_label, message_class=self._message_class, self_address=self.self_address, role=role, ) self._dialogues_storage.add(dialogue) return dialogue @staticmethod def _generate_dialogue_nonce() -> str: """ Generate the nonce and return it. :return: the next nonce """ return secrets.token_hex(DialogueLabel.NONCE_BYTES_NB) def setup(self) -> None: """Set up.""" self._dialogues_storage.setup() super_obj = super() if hasattr(super_obj, "setup"): # pragma: nocover super_obj.setup() # type: ignore # pylint: disable=no-member def teardown(self) -> None: """Tear down.""" self._dialogues_storage.teardown() super_obj = super() if hasattr(super_obj, "teardown"): # pragma: nocover super_obj.teardown() # type: ignore # pylint: disable=no-member ================================================ FILE: aea/protocols/generator/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the protocol generator modules.""" ================================================ FILE: aea/protocols/generator/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the protocol generator.""" import itertools import os import re import shutil # pylint: skip-file from pathlib import Path from typing import Optional, Tuple # pylint: skip-file from aea.__version__ import __version__ as __aea_version__ from aea.configurations.base import ProtocolSpecificationParseError from aea.configurations.constants import ( PROTOCOL_LANGUAGE_PYTHON, SUPPORTED_PROTOCOL_LANGUAGES, ) from aea.configurations.data_types import PublicId from aea.protocols.generator.common import ( CUSTOM_TYPES_DOT_PY_FILE_NAME, DIALOGUE_DOT_PY_FILE_NAME, INIT_FILE_NAME, MESSAGE_DOT_PY_FILE_NAME, MESSAGE_IMPORT, PATH_TO_PACKAGES, PROTOCOL_YAML_FILE_NAME, PYTHON_TYPE_TO_PROTO_TYPE, SERIALIZATION_DOT_PY_FILE_NAME, SERIALIZER_IMPORT, _camel_case_to_snake_case, _create_protocol_file, _get_sub_types_of_compositional_types, _includes_custom_type, _is_compositional_type, _python_pt_or_ct_type_to_proto_type, _to_camel_case, _union_sub_type_to_protobuf_variable_name, apply_protolint, check_prerequisites, compile_protobuf_using_protoc, get_protoc_version, load_protocol_specification, try_run_black_formatting, try_run_isort_formatting, ) from aea.protocols.generator.extract_specification import extract from aea.protocols.generator.validate import validate PYLINT_DISABLE_SERIALIZATION_PY = [ "too-many-statements", "too-many-locals", "no-member", "too-few-public-methods", "redefined-builtin", ] PYLINT_DISABLE_MESSAGE_PY = [ "too-many-statements", "too-many-locals", "no-member", "too-few-public-methods", "too-many-branches", "not-an-iterable", "unidiomatic-typecheck", "unsubscriptable-object", ] def _type_check(variable_name: str, variable_type: str) -> str: """ Return the type check Python instruction. If variable_type == int: type(variable_name) == int else: isinstance(variable_name, variable_type) :param variable_name: the variable name. :param variable_type: the variable type. :return: the Python instruction to check the type, in string form. """ if variable_type != "int": return f"isinstance({variable_name}, {variable_type})" else: return f"type({variable_name}) is {variable_type}" copy_right_str = "# -*- coding: utf-8 -*-\n" def _copyright_header_str(author: str) -> str: """ Produce the copyright header text for a protocol. :param author: the author of the protocol. :return: The copyright header text. """ return copy_right_str class ProtocolGenerator: """This class generates a protocol_verification package from a ProtocolTemplate object.""" def __init__( self, path_to_protocol_specification: str, output_path: str = ".", dotted_path_to_protocol_package: Optional[str] = None, ) -> None: """ Instantiate a protocol generator. :param path_to_protocol_specification: path to protocol specification file :param output_path: the path to the location in which the protocol module is to be generated. :param dotted_path_to_protocol_package: the path to the protocol package :raises FileNotFoundError if any prerequisite application is not installed :raises yaml.YAMLError if yaml parser encounters an error condition :raises ProtocolSpecificationParseError if specification fails generator's validation """ # Check the prerequisite applications are installed try: check_prerequisites() except FileNotFoundError: raise self.protoc_version = get_protoc_version() # Load protocol specification yaml file self.protocol_specification = load_protocol_specification( path_to_protocol_specification ) # Validate the specification result_bool, result_msg = validate(self.protocol_specification) if not result_bool: raise ProtocolSpecificationParseError(result_msg) # Extract specification fields self.spec = extract(self.protocol_specification) # Helper fields self.path_to_protocol_specification = path_to_protocol_specification self.protocol_specification_in_camel_case = _to_camel_case( self.protocol_specification.name ) self.path_to_generated_protocol_package = os.path.join( output_path, self.protocol_specification.name ) self.dotted_path_to_protocol_package = ( dotted_path_to_protocol_package + self.protocol_specification.name if dotted_path_to_protocol_package is not None else "{}.{}.protocols.{}".format( PATH_TO_PACKAGES, self.protocol_specification.author, self.protocol_specification.name, ) ) self.indent = "" def _change_indent(self, number: int, mode: str = None) -> None: """ Update the value of 'indent' global variable. This function controls the indentation of the code produced throughout the generator. There are two modes: - Setting the indent to a desired 'number' level. In this case, 'mode' has to be set to "s". - Updating the incrementing/decrementing the indentation level by 'number' amounts. In this case 'mode' is None. :param number: the number of indentation levels to set/increment/decrement :param mode: the mode of indentation change """ if mode and mode == "s": if number >= 0: self.indent = number * " " else: raise ValueError("Error: setting indent to be a negative number.") else: if number >= 0: for _ in itertools.repeat(None, number): self.indent += " " else: if abs(number) <= len(self.indent) / 4: self.indent = self.indent[abs(number) * 4 :] else: raise ValueError( "Not enough spaces in the 'indent' variable to remove." ) def _import_from_typing_module(self) -> str: """ Manage import statement for the typing package. :return: import statement for the typing package """ ordered_packages = [ "Dict", "FrozenSet", "Optional", "Set", "Tuple", "Union", "cast", ] import_str = "from typing import Any, " for package in ordered_packages: if self.spec.typing_imports[package]: import_str += "{}, ".format(package) import_str = import_str[:-2] return import_str def _import_from_custom_types_module(self) -> str: """ Manage import statement from custom_types module. :return: import statement for the custom_types module """ import_str = "" if len(self.spec.all_custom_types) == 0: pass else: for custom_class in self.spec.all_custom_types: import_str += "from {}.custom_types import {} as Custom{}\n".format( self.dotted_path_to_protocol_package, custom_class, custom_class, ) import_str = import_str[:-1] return import_str def _performatives_str(self) -> str: """ Generate the performatives instance property string, a set containing all valid performatives of this protocol. :return: the performatives set string """ performatives_str = "{" for performative in self.spec.all_performatives: performatives_str += '"{}", '.format(performative) performatives_str = performatives_str[:-2] performatives_str += "}" return performatives_str def _performatives_enum_str(self) -> str: """ Generate the performatives Enum class. :return: the performatives Enum string """ enum_str = self.indent + "class Performative(Message.Performative):\n" self._change_indent(1) enum_str += self.indent + '"""Performatives for the {} protocol."""\n\n'.format( self.protocol_specification.name ) for performative in self.spec.all_performatives: enum_str += self.indent + '{} = "{}"\n'.format( performative.upper(), performative ) enum_str += "\n" enum_str += self.indent + "def __str__(self) -> str:\n" self._change_indent(1) enum_str += self.indent + '"""Get the string representation."""\n' enum_str += self.indent + "return str(self.value)\n" self._change_indent(-1) enum_str += "\n" self._change_indent(-1) return enum_str def _to_custom_custom(self, content_type: str) -> str: """ Evaluate whether a content type is a custom type or has a custom type as a sub-type. :param content_type: the content type. :return: Boolean result """ new_content_type = content_type if _includes_custom_type(content_type): for custom_type in self.spec.all_custom_types: new_content_type = re.sub( rf"(^|[ \[\,])({custom_type})($|[, \]])", rf"\g<1>{self.spec.custom_custom_types[custom_type]}\g<3>", new_content_type, ) return new_content_type def _check_content_type_str(self, content_name: str, content_type: str) -> str: """ Produce the checks of elements of compositional types. :param content_name: the name of the content to be checked :param content_type: the type of the content to be checked :return: the string containing the checks. """ check_str = "" if content_type.startswith("Optional["): optional = True check_str += self.indent + 'if self.is_set("{}"):\n'.format(content_name) self._change_indent(1) check_str += self.indent + "expected_nb_of_contents += 1\n" content_type = _get_sub_types_of_compositional_types(content_type)[0] check_str += self.indent + "{} = cast({}, self.{})\n".format( content_name, self._to_custom_custom(content_type), content_name ) content_variable = content_name else: optional = False content_variable = "self." + content_name if content_type.startswith("Union["): element_types = _get_sub_types_of_compositional_types(content_type) unique_standard_types_set = set() for typing_content_type in element_types: if typing_content_type.startswith("FrozenSet"): unique_standard_types_set.add("frozenset") elif typing_content_type.startswith("Tuple"): unique_standard_types_set.add("tuple") elif typing_content_type.startswith("Dict"): unique_standard_types_set.add("dict") else: unique_standard_types_set.add(typing_content_type) unique_standard_types_list = sorted(unique_standard_types_set) check_str += self.indent check_str += "enforce(" for unique_type in unique_standard_types_list: check_str += "{} or ".format( _type_check(content_variable, self._to_custom_custom(unique_type)) ) check_str = check_str[:-4] check_str += ", \"Invalid type for content '{}'. Expected either of '{}'. Found '{{}}'.\".format(type({})))\n".format( content_name, [ unique_standard_type for unique_standard_type in unique_standard_types_list ], content_variable, ) if "frozenset" in unique_standard_types_list: check_str += self.indent + "if isinstance({}, frozenset):\n".format( content_variable ) self._change_indent(1) check_str += self.indent + "enforce(\n" self._change_indent(1) frozen_set_element_types_set = set() for element_type in element_types: if element_type.startswith("FrozenSet"): frozen_set_element_types_set.add( _get_sub_types_of_compositional_types(element_type)[0] ) frozen_set_element_types = sorted(frozen_set_element_types_set) for frozen_set_element_type in frozen_set_element_types: check_str += self.indent + "all({} for element in {}) or\n".format( _type_check( "element", self._to_custom_custom(frozen_set_element_type) ), content_variable, ) check_str = check_str[:-4] check_str += "\n" self._change_indent(-1) if len(frozen_set_element_types) == 1: check_str += ( self.indent + ", \"Invalid type for elements of content '{}'. Expected ".format( content_name ) ) for frozen_set_element_type in frozen_set_element_types: check_str += "'{}'".format( self._to_custom_custom(frozen_set_element_type) ) check_str += '.")\n' else: check_str += ( self.indent + ", \"Invalid type for frozenset elements in content '{}'. Expected either ".format( content_name ) ) for frozen_set_element_type in frozen_set_element_types: check_str += "'{}' or ".format( self._to_custom_custom(frozen_set_element_type) ) check_str = check_str[:-4] check_str += '.")\n' self._change_indent(-1) if "tuple" in unique_standard_types_list: check_str += self.indent + "if isinstance({}, tuple):\n".format( content_variable ) self._change_indent(1) check_str += self.indent + "enforce(\n" self._change_indent(1) tuple_element_types_set = set() for element_type in element_types: if element_type.startswith("Tuple"): tuple_element_types_set.add( _get_sub_types_of_compositional_types(element_type)[0] ) tuple_element_types = sorted(tuple_element_types_set) for tuple_element_type in tuple_element_types: check_str += self.indent + "all({} for element in {}) or \n".format( _type_check( "element", self._to_custom_custom(tuple_element_type) ), content_variable, ) check_str = check_str[:-4] check_str += "\n" self._change_indent(-1) if len(tuple_element_types) == 1: check_str += ( self.indent + ", \"Invalid type for tuple elements in content '{}'. Expected ".format( content_name ) ) for tuple_element_type in tuple_element_types: check_str += "'{}'".format( self._to_custom_custom(tuple_element_type) ) check_str += '.")\n' else: check_str += ( self.indent + ", \"Invalid type for tuple elements in content '{}'. Expected either ".format( content_name ) ) for tuple_element_type in tuple_element_types: check_str += "'{}' or ".format( self._to_custom_custom(tuple_element_type) ) check_str = check_str[:-4] check_str += '.")\n' self._change_indent(-1) if "dict" in unique_standard_types_list: check_str += self.indent + "if isinstance({}, dict):\n".format( content_variable ) self._change_indent(1) check_str += ( self.indent + "for key_of_{}, value_of_{} in {}.items():\n".format( content_name, content_name, content_variable ) ) self._change_indent(1) check_str += self.indent + "enforce(\n" self._change_indent(1) dict_key_value_types = {} for element_type in element_types: if element_type.startswith("Dict"): dict_key_value_types[ _get_sub_types_of_compositional_types(element_type)[0] ] = _get_sub_types_of_compositional_types(element_type)[1] for element1_type in sorted(dict_key_value_types.keys()): check_str += self.indent + "({} and {}) or\n".format( _type_check( "key_of_" + content_name, self._to_custom_custom(element1_type), ), _type_check( "value_of_" + content_name, self._to_custom_custom(dict_key_value_types[element1_type]), ), ) check_str = check_str[:-4] check_str += "\n" self._change_indent(-1) if len(dict_key_value_types) == 1: check_str += ( self.indent + ", \"Invalid type for dictionary key, value in content '{}'. Expected ".format( content_name ) ) for key in sorted(dict_key_value_types.keys()): check_str += "'{}', '{}'".format(key, dict_key_value_types[key]) check_str += '.")\n' else: check_str += ( self.indent + ", \"Invalid type for dictionary key, value in content '{}'. Expected ".format( content_name ) ) for key in sorted(dict_key_value_types.keys()): check_str += "'{}','{}' or ".format( key, dict_key_value_types[key] ) check_str = check_str[:-4] check_str += '.")\n' self._change_indent(-2) elif content_type.startswith("FrozenSet["): # check the type check_str += ( self.indent + "enforce(isinstance({}, frozenset), \"Invalid type for content '{}'. Expected 'frozenset'. Found '{{}}'.\".format(type({})))\n".format( content_variable, content_name, content_variable ) ) element_type = _get_sub_types_of_compositional_types(content_type)[0] check_str += self.indent + "enforce(all(\n" self._change_indent(1) check_str += self.indent + "{} for element in {}\n".format( _type_check("element", self._to_custom_custom(element_type)), content_variable, ) self._change_indent(-1) check_str += ( self.indent + "), \"Invalid type for frozenset elements in content '{}'. Expected '{}'.\")\n".format( content_name, element_type ) ) elif content_type.startswith("Tuple["): # check the type check_str += ( self.indent + "enforce(isinstance({}, tuple), \"Invalid type for content '{}'. Expected 'tuple'. Found '{{}}'.\".format(type({})))\n".format( content_variable, content_name, content_variable ) ) element_type = _get_sub_types_of_compositional_types(content_type)[0] check_str += self.indent + "enforce(all(\n" self._change_indent(1) check_str += self.indent + "{} for element in {}\n".format( _type_check("element", self._to_custom_custom(element_type)), content_variable, ) self._change_indent(-1) check_str += ( self.indent + "), \"Invalid type for tuple elements in content '{}'. Expected '{}'.\")\n".format( content_name, element_type ) ) elif content_type.startswith("Dict["): # check the type check_str += ( self.indent + "enforce(isinstance({}, dict), \"Invalid type for content '{}'. Expected 'dict'. Found '{{}}'.\".format(type({})))\n".format( content_variable, content_name, content_variable ) ) element_type_1 = _get_sub_types_of_compositional_types(content_type)[0] element_type_2 = _get_sub_types_of_compositional_types(content_type)[1] # check the keys type then check the values type check_str += ( self.indent + "for key_of_{}, value_of_{} in {}.items():\n".format( content_name, content_name, content_variable ) ) self._change_indent(1) check_str += self.indent + "enforce(\n" self._change_indent(1) check_str += self.indent + "{}\n".format( _type_check( "key_of_" + content_name, self._to_custom_custom(element_type_1) ) ) self._change_indent(-1) check_str += ( self.indent + ", \"Invalid type for dictionary keys in content '{}'. Expected '{}'. Found '{{}}'.\".format(type(key_of_{})))\n".format( content_name, element_type_1, content_name ) ) check_str += self.indent + "enforce(\n" self._change_indent(1) check_str += self.indent + "{}\n".format( _type_check( "value_of_" + content_name, self._to_custom_custom(element_type_2) ) ) self._change_indent(-1) check_str += ( self.indent + ", \"Invalid type for dictionary values in content '{}'. Expected '{}'. Found '{{}}'.\".format(type(value_of_{})))\n".format( content_name, element_type_2, content_name ) ) self._change_indent(-1) else: check_str += ( self.indent + "enforce({}, \"Invalid type for content '{}'. Expected '{}'. Found '{{}}'.\".format(type({})))\n".format( _type_check(content_variable, self._to_custom_custom(content_type)), content_name, content_type, content_variable, ) ) if optional: self._change_indent(-1) return check_str def _message_class_str(self) -> str: """ Produce the content of the Message class. :return: the message.py file content """ self._change_indent(0, "s") # Header cls_str = _copyright_header_str(self.protocol_specification.author) + "\n" # Module docstring cls_str += ( self.indent + '"""This module contains {}\'s message definition."""\n\n'.format( self.protocol_specification.name ) ) cls_str += f"# pylint: disable={','.join(PYLINT_DISABLE_MESSAGE_PY)}\n" # Imports cls_str += self.indent + "import logging\n" cls_str += self._import_from_typing_module() + "\n\n" cls_str += self.indent + "from aea.configurations.base import PublicId\n" cls_str += self.indent + "from aea.exceptions import AEAEnforceError, enforce\n" cls_str += MESSAGE_IMPORT + "\n" if self._import_from_custom_types_module() != "": cls_str += "\n" + self._import_from_custom_types_module() + "\n" else: cls_str += self._import_from_custom_types_module() cls_str += ( self.indent + '\n_default_logger = logging.getLogger("aea.packages.{}.protocols.{}.message")\n'.format( self.protocol_specification.author, self.protocol_specification.name ) ) cls_str += self.indent + "\nDEFAULT_BODY_SIZE = 4\n" # Class Header cls_str += self.indent + "\n\nclass {}Message(Message):\n".format( self.protocol_specification_in_camel_case ) self._change_indent(1) cls_str += self.indent + '"""{}"""\n\n'.format( self.protocol_specification.description ) # Class attributes cls_str += self.indent + 'protocol_id = PublicId.from_str("{}/{}:{}")\n'.format( self.protocol_specification.author, self.protocol_specification.name, self.protocol_specification.version, ) cls_str += ( self.indent + 'protocol_specification_id = PublicId.from_str("{}/{}:{}")\n'.format( self.protocol_specification.protocol_specification_id.author, self.protocol_specification.protocol_specification_id.name, self.protocol_specification.protocol_specification_id.version, ) ) for custom_type in self.spec.all_custom_types: cls_str += "\n" cls_str += self.indent + "{} = Custom{}\n".format(custom_type, custom_type) # Performatives Enum cls_str += "\n" + self._performatives_enum_str() cls_str += self.indent + "_performatives = {}\n".format( self._performatives_str() ) # slots cls_str += self.indent + "__slots__: Tuple[str, ...] = tuple()\n" cls_str += self.indent + "class _SlotsCls():\n" self._change_indent(1) cls_str += self.indent + "__slots__ = (\n" self._change_indent(1) # default fields default_slots = ["performative", "dialogue_reference", "message_id", "target"] slots = list(self.spec.all_unique_contents.keys()) + default_slots for field_name in sorted(slots): cls_str += self.indent + f'"{field_name}",' self._change_indent(-1) cls_str += self.indent + ")\n" self._change_indent(-1) # __init__ cls_str += self.indent + "def __init__(\n" self._change_indent(1) cls_str += self.indent + "self,\n" cls_str += self.indent + "performative: Performative,\n" cls_str += self.indent + 'dialogue_reference: Tuple[str, str] = ("", ""),\n' cls_str += self.indent + "message_id: int = 1,\n" cls_str += self.indent + "target: int = 0,\n" cls_str += self.indent + "**kwargs: Any,\n" self._change_indent(-1) cls_str += self.indent + "):\n" self._change_indent(1) cls_str += self.indent + '"""\n' cls_str += self.indent + "Initialise an instance of {}Message.\n\n".format( self.protocol_specification_in_camel_case ) cls_str += self.indent + ":param message_id: the message id.\n" cls_str += self.indent + ":param dialogue_reference: the dialogue reference.\n" cls_str += self.indent + ":param target: the message target.\n" cls_str += self.indent + ":param performative: the message performative.\n" cls_str += self.indent + ":param **kwargs: extra options.\n" cls_str += self.indent + '"""\n' cls_str += self.indent + "super().__init__(\n" self._change_indent(1) cls_str += self.indent + "dialogue_reference=dialogue_reference,\n" cls_str += self.indent + "message_id=message_id,\n" cls_str += self.indent + "target=target,\n" cls_str += ( self.indent + "performative={}Message.Performative(performative),\n".format( self.protocol_specification_in_camel_case ) ) cls_str += self.indent + "**kwargs,\n" self._change_indent(-1) cls_str += self.indent + ")\n" self._change_indent(-1) # Instance properties cls_str += self.indent + "@property\n" cls_str += self.indent + "def valid_performatives(self) -> Set[str]:\n" self._change_indent(1) cls_str += self.indent + '"""Get valid performatives."""\n' cls_str += self.indent + "return self._performatives\n\n" self._change_indent(-1) cls_str += self.indent + "@property\n" cls_str += self.indent + "def dialogue_reference(self) -> Tuple[str, str]:\n" self._change_indent(1) cls_str += self.indent + '"""Get the dialogue_reference of the message."""\n' cls_str += ( self.indent + 'enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.")\n' ) cls_str += ( self.indent + 'return cast(Tuple[str, str], self.get("dialogue_reference"))\n\n' ) self._change_indent(-1) cls_str += self.indent + "@property\n" cls_str += self.indent + "def message_id(self) -> int:\n" self._change_indent(1) cls_str += self.indent + '"""Get the message_id of the message."""\n' cls_str += ( self.indent + 'enforce(self.is_set("message_id"), "message_id is not set.")\n' ) cls_str += self.indent + 'return cast(int, self.get("message_id"))\n\n' self._change_indent(-1) cls_str += self.indent + "@property\n" cls_str += ( self.indent + "def performative(self) -> Performative: # type: ignore # noqa: F821\n" ) self._change_indent(1) cls_str += self.indent + '"""Get the performative of the message."""\n' cls_str += ( self.indent + 'enforce(self.is_set("performative"), "performative is not set.")\n' ) cls_str += ( self.indent + 'return cast({}Message.Performative, self.get("performative"))\n\n'.format( self.protocol_specification_in_camel_case ) ) self._change_indent(-1) cls_str += self.indent + "@property\n" cls_str += self.indent + "def target(self) -> int:\n" self._change_indent(1) cls_str += self.indent + '"""Get the target of the message."""\n' cls_str += ( self.indent + 'enforce(self.is_set("target"), "target is not set.")\n' ) cls_str += self.indent + 'return cast(int, self.get("target"))\n\n' self._change_indent(-1) for content_name in sorted(self.spec.all_unique_contents.keys()): content_type = self.spec.all_unique_contents[content_name] cls_str += self.indent + "@property\n" cls_str += self.indent + "def {}(self) -> {}:\n".format( content_name, self._to_custom_custom(content_type) ) self._change_indent(1) cls_str += ( self.indent + '"""Get the \'{}\' content from the message."""\n'.format( content_name ) ) if not content_type.startswith("Optional"): cls_str += ( self.indent + 'enforce(self.is_set("{}"), "\'{}\' content is not set.")\n'.format( content_name, content_name ) ) cls_str += self.indent + 'return cast({}, self.get("{}"))\n\n'.format( self._to_custom_custom(content_type), content_name ) self._change_indent(-1) # check_consistency method cls_str += self.indent + "def _is_consistent(self) -> bool:\n" self._change_indent(1) cls_str += ( self.indent + '"""Check that the message follows the {} protocol."""\n'.format( self.protocol_specification.name ) ) cls_str += self.indent + "try:\n" self._change_indent(1) cls_str += ( self.indent + "enforce(isinstance(self.dialogue_reference, tuple), \"Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.\"" ".format(type(self.dialogue_reference)))\n" ) cls_str += ( self.indent + "enforce(isinstance(self.dialogue_reference[0], str), \"Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.\"" ".format(type(self.dialogue_reference[0])))\n" ) cls_str += ( self.indent + "enforce(isinstance(self.dialogue_reference[1], str), \"Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.\"" ".format(type(self.dialogue_reference[1])))\n" ) cls_str += ( self.indent + "enforce(" + _type_check("self.message_id", "int") + ", \"Invalid type for 'message_id'. Expected 'int'. Found '{}'.\"" ".format(type(self.message_id)))\n" ) cls_str += ( self.indent + "enforce(" + _type_check("self.target", "int") + ", \"Invalid type for 'target'. Expected 'int'. Found '{}'.\"" ".format(type(self.target)))\n\n" ) cls_str += self.indent + "# Light Protocol Rule 2\n" cls_str += self.indent + "# Check correct performative\n" cls_str += ( self.indent + "enforce(isinstance(self.performative, {}Message.Performative)".format( self.protocol_specification_in_camel_case ) ) cls_str += ( ", \"Invalid 'performative'. Expected either of '{}'. Found '{}'.\".format(" ) cls_str += "self.valid_performatives, self.performative" cls_str += "))\n\n" cls_str += self.indent + "# Check correct contents\n" cls_str += ( self.indent + "actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE\n" ) cls_str += self.indent + "expected_nb_of_contents = 0\n" counter = 1 for performative, contents in self.spec.speech_acts.items(): if counter == 1: cls_str += self.indent + "if " else: cls_str += self.indent + "elif " cls_str += "self.performative == {}Message.Performative.{}:\n".format( self.protocol_specification_in_camel_case, performative.upper(), ) self._change_indent(1) nb_of_non_optional_contents = 0 for content_type in contents.values(): if not content_type.startswith("Optional"): nb_of_non_optional_contents += 1 cls_str += self.indent + "expected_nb_of_contents = {}\n".format( nb_of_non_optional_contents ) for content_name, content_type in contents.items(): cls_str += self._check_content_type_str(content_name, content_type) counter += 1 self._change_indent(-1) cls_str += "\n" cls_str += self.indent + "# Check correct content count\n" cls_str += ( self.indent + "enforce(expected_nb_of_contents == actual_nb_of_contents, " '"Incorrect number of contents. Expected {}. Found {}"' ".format(expected_nb_of_contents, actual_nb_of_contents))\n\n" ) cls_str += self.indent + "# Light Protocol Rule 3\n" cls_str += self.indent + "if self.message_id == 1:\n" self._change_indent(1) cls_str += ( self.indent + "enforce(self.target == 0, \"Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.\".format(self.target))\n" ) self._change_indent(-2) cls_str += ( self.indent + "except (AEAEnforceError, ValueError, KeyError) as e:\n" ) self._change_indent(1) cls_str += self.indent + "_default_logger.error(str(e))\n" cls_str += self.indent + "return False\n\n" self._change_indent(-1) cls_str += self.indent + "return True\n" return cls_str def _valid_replies_str(self) -> str: """ Generate the `valid replies` dictionary. :return: the `valid replies` dictionary string """ valid_replies_str = ( self.indent + "VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = {\n" ) self._change_indent(1) for performative in sorted(self.spec.reply.keys()): valid_replies_str += ( self.indent + "{}Message.Performative.{}: frozenset(".format( self.protocol_specification_in_camel_case, performative.upper() ) ) if len(self.spec.reply[performative]) > 0: valid_replies_str += "\n" self._change_indent(1) valid_replies_str += self.indent + "{" for reply in self.spec.reply[performative]: valid_replies_str += "{}Message.Performative.{}, ".format( self.protocol_specification_in_camel_case, reply.upper() ) valid_replies_str = valid_replies_str[:-2] valid_replies_str += "}\n" self._change_indent(-1) valid_replies_str += self.indent + "),\n" self._change_indent(-1) valid_replies_str += self.indent + "}" return valid_replies_str def _end_state_enum_str(self) -> str: """ Generate the end state Enum class. :return: the end state Enum string """ enum_str = self.indent + "class EndState(Dialogue.EndState):\n" self._change_indent(1) enum_str += ( self.indent + '"""This class defines the end states of a {} dialogue."""\n\n'.format( self.protocol_specification.name ) ) tag = 0 for end_state in self.spec.end_states: enum_str += self.indent + "{} = {}\n".format(end_state.upper(), tag) tag += 1 self._change_indent(-1) return enum_str def _agent_role_enum_str(self) -> str: """ Generate the agent role Enum class. :return: the agent role Enum string """ enum_str = self.indent + "class Role(Dialogue.Role):\n" self._change_indent(1) enum_str += ( self.indent + '"""This class defines the agent\'s role in a {} dialogue."""\n\n'.format( self.protocol_specification.name ) ) for role in self.spec.roles: enum_str += self.indent + '{} = "{}"\n'.format(role.upper(), role) self._change_indent(-1) return enum_str def _dialogue_class_str(self) -> str: """ Produce the content of the Message class. :return: the message.py file content """ self._change_indent(0, "s") # Header cls_str = _copyright_header_str(self.protocol_specification.author) + "\n" # Module docstring cls_str += self.indent + '"""\n' cls_str += ( self.indent + "This module contains the classes required for {} dialogue management.\n\n".format( self.protocol_specification.name ) ) cls_str += ( self.indent + "- {}Dialogue: The dialogue class maintains state of a dialogue and manages it.\n".format( self.protocol_specification_in_camel_case ) ) cls_str += ( self.indent + "- {}Dialogues: The dialogues class keeps track of all dialogues.\n".format( self.protocol_specification_in_camel_case ) ) cls_str += self.indent + '"""\n\n' # Imports cls_str += self.indent + "from abc import ABC\n" cls_str += ( self.indent + "from typing import Callable, Dict, FrozenSet, Type, cast\n\n" ) cls_str += self.indent + "from aea.common import Address\n" cls_str += self.indent + "from aea.protocols.base import Message\n" cls_str += ( self.indent + "from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues\n\n" ) cls_str += self.indent + "from {}.message import {}Message\n".format( self.dotted_path_to_protocol_package, self.protocol_specification_in_camel_case, ) # Class Header cls_str += "\nclass {}Dialogue(Dialogue):\n".format( self.protocol_specification_in_camel_case ) self._change_indent(1) cls_str += ( self.indent + '"""The {} dialogue class maintains state of a dialogue and manages it."""\n'.format( self.protocol_specification.name ) ) # Class Constants initial_performatives_str = ", ".join( [ "{}Message.Performative.{}".format( self.protocol_specification_in_camel_case, initial_performative ) for initial_performative in self.spec.initial_performatives ] ) terminal_performatives_str = ", ".join( [ "{}Message.Performative.{}".format( self.protocol_specification_in_camel_case, terminal_performative ) for terminal_performative in self.spec.terminal_performatives ] ) cls_str += ( self.indent + "INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset({" + initial_performatives_str + "})\n" + self.indent + "TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset({" + terminal_performatives_str + "})\n" + self._valid_replies_str() ) # Enums cls_str += "\n" + self._agent_role_enum_str() cls_str += "\n" + self._end_state_enum_str() cls_str += "\n" # initializer cls_str += self.indent + "def __init__(\n" self._change_indent(1) cls_str += self.indent + "self,\n" cls_str += self.indent + "dialogue_label: DialogueLabel,\n" cls_str += self.indent + "self_address: Address,\n" cls_str += self.indent + "role: Dialogue.Role,\n" cls_str += self.indent + "message_class: Type[{}Message] = {}Message,\n".format( self.protocol_specification_in_camel_case, self.protocol_specification_in_camel_case, ) self._change_indent(-1) cls_str += self.indent + ") -> None:\n" self._change_indent(1) cls_str += self.indent + '"""\n' cls_str += self.indent + "Initialize a dialogue.\n\n" cls_str += ( self.indent + ":param dialogue_label: the identifier of the dialogue\n" ) cls_str += ( self.indent + ":param self_address: the address of the entity for whom this dialogue is maintained\n" ) cls_str += ( self.indent + ":param role: the role of the agent this dialogue is maintained for\n" ) cls_str += self.indent + ":param message_class: the message class used\n" cls_str += self.indent + '"""\n' cls_str += self.indent + "Dialogue.__init__(\n" cls_str += self.indent + "self,\n" cls_str += self.indent + "dialogue_label=dialogue_label,\n" cls_str += self.indent + "message_class=message_class,\n" cls_str += self.indent + "self_address=self_address,\n" cls_str += self.indent + "role=role,\n" cls_str += self.indent + ")\n" self._change_indent(-2) # dialogues class cls_str += self.indent + "class {}Dialogues(Dialogues, ABC):\n".format( self.protocol_specification_in_camel_case ) self._change_indent(1) cls_str += ( self.indent + '"""This class keeps track of all {} dialogues."""\n\n'.format( self.protocol_specification.name ) ) end_states_str = ", ".join( [ "{}Dialogue.EndState.{}".format( self.protocol_specification_in_camel_case, end_state.upper() ) for end_state in self.spec.end_states ] ) cls_str += self.indent + "END_STATES = frozenset(\n" cls_str += self.indent + "{" + end_states_str + "}" cls_str += self.indent + ")\n\n" cls_str += ( self.indent + f"_keep_terminal_state_dialogues = {repr(self.spec.keep_terminal_state_dialogues)}\n\n" ) cls_str += self.indent + "def __init__(\n" self._change_indent(1) cls_str += self.indent + "self,\n" cls_str += self.indent + "self_address: Address,\n" cls_str += ( self.indent + "role_from_first_message: Callable[[Message, Address], Dialogue.Role],\n" ) cls_str += ( self.indent + "dialogue_class: Type[{}Dialogue] = {}Dialogue,\n".format( self.protocol_specification_in_camel_case, self.protocol_specification_in_camel_case, ) ) self._change_indent(-1) cls_str += self.indent + ") -> None:\n" self._change_indent(1) cls_str += self.indent + '"""\n' cls_str += self.indent + "Initialize dialogues.\n\n" cls_str += ( self.indent + ":param self_address: the address of the entity for whom dialogues are maintained\n" ) cls_str += self.indent + ":param dialogue_class: the dialogue class used\n" cls_str += ( self.indent + ":param role_from_first_message: the callable determining role from first message\n" ) cls_str += self.indent + '"""\n' cls_str += self.indent + "Dialogues.__init__(\n" self._change_indent(1) cls_str += self.indent + "self,\n" cls_str += self.indent + "self_address=self_address,\n" cls_str += ( self.indent + "end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES),\n" ) cls_str += self.indent + "message_class={}Message,\n".format( self.protocol_specification_in_camel_case ) cls_str += self.indent + "dialogue_class=dialogue_class,\n" cls_str += self.indent + "role_from_first_message=role_from_first_message,\n" self._change_indent(-1) cls_str += self.indent + ")\n" self._change_indent(-2) cls_str += self.indent + "\n" return cls_str def _custom_types_module_str(self) -> str: """ Produce the contents of the custom_types module, containing classes corresponding to every custom type in the protocol specification. :return: the custom_types.py file content """ self._change_indent(0, "s") # Header cls_str = _copyright_header_str(self.protocol_specification.author) + "\n" # Module docstring cls_str += '"""This module contains class representations corresponding to every custom type in the protocol specification."""\n' # class code per custom type for custom_type in self.spec.all_custom_types: cls_str += self.indent + "\n\nclass {}:\n".format(custom_type) self._change_indent(1) cls_str += ( self.indent + '"""This class represents an instance of {}."""\n\n'.format( custom_type ) ) cls_str += self.indent + "def __init__(self):\n" self._change_indent(1) cls_str += self.indent + '"""Initialise an instance of {}."""\n'.format( custom_type ) cls_str += self.indent + "raise NotImplementedError\n\n" self._change_indent(-1) cls_str += self.indent + "@staticmethod\n" cls_str += ( self.indent + 'def encode({}_protobuf_object, {}_object: "{}") -> None:\n'.format( _camel_case_to_snake_case(custom_type), _camel_case_to_snake_case(custom_type), custom_type, ) ) self._change_indent(1) cls_str += self.indent + '"""\n' cls_str += ( self.indent + "Encode an instance of this class into the protocol buffer object.\n\n" ) cls_str += ( self.indent + "The protocol buffer object in the {}_protobuf_object argument is matched with the instance of this class in the '{}_object' argument.\n\n".format( _camel_case_to_snake_case(custom_type), _camel_case_to_snake_case(custom_type), ) ) cls_str += ( self.indent + ":param {}_protobuf_object: the protocol buffer object whose type corresponds with this class.\n".format( _camel_case_to_snake_case(custom_type) ) ) cls_str += ( self.indent + ":param {}_object: an instance of this class to be encoded in the protocol buffer object.\n".format( _camel_case_to_snake_case(custom_type) ) ) cls_str += self.indent + '"""\n' cls_str += self.indent + "raise NotImplementedError\n\n" self._change_indent(-1) cls_str += self.indent + "@classmethod\n" cls_str += ( self.indent + 'def decode(cls, {}_protobuf_object) -> "{}":\n'.format( _camel_case_to_snake_case(custom_type), custom_type, ) ) self._change_indent(1) cls_str += self.indent + '"""\n' cls_str += ( self.indent + "Decode a protocol buffer object that corresponds with this class into an instance of this class.\n\n" ) cls_str += ( self.indent + "A new instance of this class is created that matches the protocol buffer object in the '{}_protobuf_object' argument.\n\n".format( _camel_case_to_snake_case(custom_type) ) ) cls_str += ( self.indent + ":param {}_protobuf_object: the protocol buffer object whose type corresponds with this class.\n".format( _camel_case_to_snake_case(custom_type) ) ) cls_str += ( self.indent + ":return: A new instance of this class that matches the protocol buffer object in the '{}_protobuf_object' argument.\n".format( _camel_case_to_snake_case(custom_type) ) ) cls_str += self.indent + '"""\n' cls_str += self.indent + "raise NotImplementedError\n\n" self._change_indent(-1) cls_str += self.indent + "def __eq__(self, other):\n" self._change_indent(1) cls_str += self.indent + "raise NotImplementedError\n" self._change_indent(-2) return cls_str def _to_python_type(self, content_type: str) -> str: """ Return python type. :param content_type: str :return: str """ if not _is_compositional_type(content_type): return content_type m = re.search(r"^(pt:)?([a-zA-Z0-9_]+)(\[.*)?", content_type) if not m: return content_type type_ = m.groups()[1].lower() if type_ == "frozenset": return "(set, frozenset)" if type_ == "tuple": return "(list, tuple)" return type_ def _encoding_message_content_from_python_to_protobuf( self, content_name: str, content_type: str, performative_name: Optional[str] = None, ) -> str: """ Produce the encoding of message contents for the serialisation class. :param content_name: the name of the content to be encoded :param content_type: the type of the content to be encoded :param performative_name: optional performative name of the content to be encoded :return: the encoding string """ performative_name = performative_name or content_name encoding_str = "" if content_type in PYTHON_TYPE_TO_PROTO_TYPE.keys(): encoding_str += self.indent + "{} = msg.{}\n".format( performative_name, content_name ) encoding_str += self.indent + "performative.{} = {}\n".format( performative_name, performative_name ) elif content_type.startswith("FrozenSet") or content_type.startswith("Tuple"): encoding_str += self.indent + "{} = msg.{}\n".format( content_name, content_name ) encoding_str += self.indent + "performative.{}.extend({})\n".format( performative_name, content_name ) elif content_type.startswith("Dict"): encoding_str += self.indent + "{} = msg.{}\n".format( content_name, content_name ) encoding_str += self.indent + "performative.{}.update({})\n".format( performative_name, content_name ) elif content_type.startswith("Union"): sub_types = _get_sub_types_of_compositional_types(content_type) encoding_str += self.indent + f'if msg.is_set("{content_name}"):\n' self._change_indent(1) elif_add = "" for sub_type in sub_types: sub_type_name_in_protobuf = _union_sub_type_to_protobuf_variable_name( content_name, sub_type ) extra = "" if _is_compositional_type(sub_type): subt = _get_sub_types_of_compositional_types(sub_type) if "dict" in sub_type.lower(): extra = f" and all(map(lambda x: isinstance(x[0], {subt[0]}) and isinstance(x[1], {subt[1]}), msg.{content_name}.items()))" else: extra = f" and all(map(lambda x: isinstance(x, {subt[0]}), msg.{content_name}))" encoding_str += ( self.indent + f"{elif_add}if isinstance(msg.{content_name}, {self._to_python_type(sub_type)}){extra}:\n" ) self._change_indent(1) encoding_str += self.indent + "performative.{}_is_set = True\n".format( sub_type_name_in_protobuf ) encoding_str += self._encoding_message_content_from_python_to_protobuf( content_name, sub_type, performative_name=sub_type_name_in_protobuf ) self._change_indent(-1) elif_add = "el" encoding_str += self.indent + f"elif msg.{content_name} is None:\n" self._change_indent(1) encoding_str += self.indent + "pass\n" self._change_indent(-1) encoding_str += self.indent + "else:\n" self._change_indent(1) encoding_str += ( self.indent + f"raise ValueError(f'Bad value set to `{content_name}` {{msg.{content_name} }}')\n" ) self._change_indent(-1) self._change_indent(-1) elif content_type.startswith("Optional"): sub_type = _get_sub_types_of_compositional_types(content_type)[0] if not sub_type.startswith("Union"): encoding_str += self.indent + 'if msg.is_set("{}"):\n'.format( content_name ) self._change_indent(1) encoding_str += self.indent + "performative.{}_is_set = True\n".format( content_name ) encoding_str += self._encoding_message_content_from_python_to_protobuf( content_name, sub_type ) if not sub_type.startswith("Union"): self._change_indent(-1) else: encoding_str += self.indent + "{} = msg.{}\n".format( performative_name, content_name ) encoding_str += self.indent + "{}.encode(performative.{}, {})\n".format( content_type, performative_name, performative_name ) return encoding_str def _decoding_message_content_from_protobuf_to_python( self, performative: str, content_name: str, content_type: str, variable_name_in_protobuf: Optional[str] = "", ) -> str: """ Produce the decoding of message contents for the serialisation class. :param performative: the performative to which the content belongs :param content_name: the name of the content to be decoded :param content_type: the type of the content to be decoded :param variable_name_in_protobuf: the name of the variable in the protobuf schema :return: the decoding string """ decoding_str = "" variable_name = ( content_name if variable_name_in_protobuf == "" else variable_name_in_protobuf ) if content_type in PYTHON_TYPE_TO_PROTO_TYPE.keys(): decoding_str += self.indent + "{} = {}_pb.{}.{}\n".format( content_name, self.protocol_specification.name, performative, variable_name, ) decoding_str += self.indent + 'performative_content["{}"] = {}\n'.format( content_name, content_name ) elif content_type.startswith("FrozenSet"): decoding_str += self.indent + "{} = {}_pb.{}.{}\n".format( content_name, self.protocol_specification.name, performative, variable_name, ) decoding_str += self.indent + "{}_frozenset = frozenset({})\n".format( content_name, content_name ) decoding_str += ( self.indent + 'performative_content["{}"] = {}_frozenset\n'.format( content_name, content_name ) ) elif content_type.startswith("Tuple"): decoding_str += self.indent + "{} = {}_pb.{}.{}\n".format( content_name, self.protocol_specification.name, performative, variable_name, ) decoding_str += self.indent + "{}_tuple = tuple({})\n".format( content_name, content_name ) decoding_str += ( self.indent + 'performative_content["{}"] = {}_tuple\n'.format( content_name, content_name ) ) elif content_type.startswith("Dict"): decoding_str += self.indent + "{} = {}_pb.{}.{}\n".format( content_name, self.protocol_specification.name, performative, variable_name, ) decoding_str += self.indent + "{}_dict = dict({})\n".format( content_name, content_name ) decoding_str += ( self.indent + 'performative_content["{}"] = {}_dict\n'.format( content_name, content_name ) ) elif content_type.startswith("Union"): sub_types = _get_sub_types_of_compositional_types(content_type) for sub_type in sub_types: sub_type_name_in_protobuf = _union_sub_type_to_protobuf_variable_name( content_name, sub_type ) decoding_str += self.indent + "if {}_pb.{}.{}_is_set:\n".format( self.protocol_specification.name, performative, sub_type_name_in_protobuf, ) self._change_indent(1) decoding_str += self._decoding_message_content_from_protobuf_to_python( performative=performative, content_name=content_name, content_type=sub_type, variable_name_in_protobuf=sub_type_name_in_protobuf, ) self._change_indent(-1) elif content_type.startswith("Optional"): sub_type = _get_sub_types_of_compositional_types(content_type)[0] if not sub_type.startswith("Union"): decoding_str += self.indent + "if {}_pb.{}.{}_is_set:\n".format( self.protocol_specification.name, performative, content_name ) self._change_indent(1) decoding_str += self._decoding_message_content_from_protobuf_to_python( performative, content_name, sub_type ) if not sub_type.startswith("Union"): self._change_indent(-1) else: decoding_str += self.indent + "pb2_{} = {}_pb.{}.{}\n".format( variable_name, self.protocol_specification.name, performative, variable_name, ) decoding_str += self.indent + "{} = {}.decode(pb2_{})\n".format( content_name, content_type, variable_name, ) decoding_str += self.indent + 'performative_content["{}"] = {}\n'.format( content_name, content_name ) return decoding_str def _serialization_class_str(self) -> str: """ Produce the content of the Serialization class. :return: the serialization.py file content """ self._change_indent(0, "s") # Header cls_str = _copyright_header_str(self.protocol_specification.author) + "\n" # Module docstring cls_str += ( self.indent + '"""Serialization module for {} protocol."""\n\n'.format( self.protocol_specification.name ) ) cls_str += f"# pylint: disable={','.join(PYLINT_DISABLE_SERIALIZATION_PY)}\n" # Imports cls_str += self.indent + "from typing import Any, Dict, cast\n\n" cls_str += ( self.indent + "from aea.mail.base_pb2 import DialogueMessage, Message as ProtobufMessage\n" ) cls_str += MESSAGE_IMPORT + "\n" cls_str += SERIALIZER_IMPORT + "\n\n" cls_str += self.indent + "from {} import (\n {}_pb2,\n)\n".format( self.dotted_path_to_protocol_package, self.protocol_specification.name, ) for custom_type in self.spec.all_custom_types: cls_str += ( self.indent + "from {}.custom_types import (\n {},\n)\n".format( self.dotted_path_to_protocol_package, custom_type, ) ) cls_str += self.indent + "from {}.message import (\n {}Message,\n)\n".format( self.dotted_path_to_protocol_package, self.protocol_specification_in_camel_case, ) # Class Header cls_str += self.indent + "\n\nclass {}Serializer(Serializer):\n".format( self.protocol_specification_in_camel_case, ) self._change_indent(1) cls_str += ( self.indent + '"""Serialization for the \'{}\' protocol."""\n\n'.format( self.protocol_specification.name, ) ) # encoder cls_str += self.indent + "@staticmethod\n" cls_str += self.indent + "def encode(msg: Message) -> bytes:\n" self._change_indent(1) cls_str += self.indent + '"""\n' cls_str += self.indent + "Encode a '{}' message into bytes.\n\n".format( self.protocol_specification_in_camel_case, ) cls_str += self.indent + ":param msg: the message object.\n" cls_str += self.indent + ":return: the bytes.\n" cls_str += self.indent + '"""\n' cls_str += self.indent + "msg = cast({}Message, msg)\n".format( self.protocol_specification_in_camel_case ) cls_str += self.indent + "message_pb = ProtobufMessage()\n" cls_str += self.indent + "dialogue_message_pb = DialogueMessage()\n" cls_str += self.indent + "{}_msg = {}_pb2.{}Message()\n\n".format( self.protocol_specification.name, self.protocol_specification.name, self.protocol_specification_in_camel_case, ) cls_str += self.indent + "dialogue_message_pb.message_id = msg.message_id\n" cls_str += self.indent + "dialogue_reference = msg.dialogue_reference\n" cls_str += ( self.indent + "dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0]\n" ) cls_str += ( self.indent + "dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1]\n" ) cls_str += self.indent + "dialogue_message_pb.target = msg.target\n\n" cls_str += self.indent + "performative_id = msg.performative\n" counter = 1 for performative, contents in self.spec.speech_acts.items(): if counter == 1: cls_str += self.indent + "if " else: cls_str += self.indent + "elif " cls_str += "performative_id == {}Message.Performative.{}:\n".format( self.protocol_specification_in_camel_case, performative.upper() ) self._change_indent(1) cls_str += ( self.indent + "performative = {}_pb2.{}Message.{}_Performative() # type: ignore\n".format( self.protocol_specification.name, self.protocol_specification_in_camel_case, performative.title(), ) ) for content_name, content_type in contents.items(): cls_str += self._encoding_message_content_from_python_to_protobuf( content_name, content_type ) cls_str += self.indent + "{}_msg.{}.CopyFrom(performative)\n".format( self.protocol_specification.name, performative ) counter += 1 self._change_indent(-1) cls_str += self.indent + "else:\n" self._change_indent(1) cls_str += ( self.indent + 'raise ValueError("Performative not valid: {}".format(performative_id))\n\n' ) self._change_indent(-1) cls_str += ( self.indent + "dialogue_message_pb.content = {}_msg.SerializeToString()\n\n".format( self.protocol_specification.name, ) ) cls_str += ( self.indent + "message_pb.dialogue_message.CopyFrom(dialogue_message_pb)\n" ) cls_str += self.indent + "message_bytes = message_pb.SerializeToString()\n" cls_str += self.indent + "return message_bytes\n" self._change_indent(-1) # decoder cls_str += self.indent + "@staticmethod\n" cls_str += self.indent + "def decode(obj: bytes) -> Message:\n" self._change_indent(1) cls_str += self.indent + '"""\n' cls_str += self.indent + "Decode bytes into a '{}' message.\n\n".format( self.protocol_specification_in_camel_case, ) cls_str += self.indent + ":param obj: the bytes object.\n" cls_str += self.indent + ":return: the '{}' message.\n".format( self.protocol_specification_in_camel_case ) cls_str += self.indent + '"""\n' cls_str += self.indent + "message_pb = ProtobufMessage()\n" cls_str += self.indent + "{}_pb = {}_pb2.{}Message()\n".format( self.protocol_specification.name, self.protocol_specification.name, self.protocol_specification_in_camel_case, ) cls_str += self.indent + "message_pb.ParseFromString(obj)\n" cls_str += self.indent + "message_id = message_pb.dialogue_message.message_id\n" cls_str += ( self.indent + "dialogue_reference = (message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference)\n" ) cls_str += self.indent + "target = message_pb.dialogue_message.target\n\n" cls_str += ( self.indent + "{}_pb.ParseFromString(message_pb.dialogue_message.content)\n".format( self.protocol_specification.name ) ) cls_str += ( self.indent + 'performative = {}_pb.WhichOneof("performative")\n'.format( self.protocol_specification.name ) ) cls_str += ( self.indent + "performative_id = {}Message.Performative(str(performative))\n".format( self.protocol_specification_in_camel_case ) ) cls_str += self.indent + "performative_content = {} # type: Dict[str, Any]\n" counter = 1 for performative, contents in self.spec.speech_acts.items(): if counter == 1: cls_str += self.indent + "if " else: cls_str += self.indent + "elif " cls_str += "performative_id == {}Message.Performative.{}:\n".format( self.protocol_specification_in_camel_case, performative.upper() ) self._change_indent(1) if len(contents.keys()) == 0: cls_str += self.indent + "pass\n" else: for content_name, content_type in contents.items(): cls_str += self._decoding_message_content_from_protobuf_to_python( performative, content_name, content_type ) counter += 1 self._change_indent(-1) cls_str += self.indent + "else:\n" self._change_indent(1) cls_str += ( self.indent + 'raise ValueError("Performative not valid: {}.".format(performative_id))\n\n' ) self._change_indent(-1) cls_str += self.indent + "return {}Message(\n".format( self.protocol_specification_in_camel_case, ) self._change_indent(1) cls_str += self.indent + "message_id=message_id,\n" cls_str += self.indent + "dialogue_reference=dialogue_reference,\n" cls_str += self.indent + "target=target,\n" cls_str += self.indent + "performative=performative,\n" cls_str += self.indent + "**performative_content\n" self._change_indent(-1) cls_str += self.indent + ")\n" self._change_indent(-2) return cls_str def _content_to_proto_field_str( self, content_name: str, content_type: str, tag_no: int, ) -> Tuple[str, int]: """ Convert a message content to its representation in a protocol buffer schema. :param content_name: the name of the content :param content_type: the type of the content :param tag_no: the tag number :return: the content in protocol buffer schema and the next tag number to be used """ entry = "" if content_type.startswith("FrozenSet") or content_type.startswith( "Tuple" ): # it is a element_type = _get_sub_types_of_compositional_types(content_type)[0] proto_type = _python_pt_or_ct_type_to_proto_type(element_type) entry = self.indent + "repeated {} {} = {};\n".format( proto_type, content_name, tag_no ) tag_no += 1 elif content_type.startswith("Dict"): # it is a key_type = _get_sub_types_of_compositional_types(content_type)[0] value_type = _get_sub_types_of_compositional_types(content_type)[1] proto_key_type = _python_pt_or_ct_type_to_proto_type(key_type) proto_value_type = _python_pt_or_ct_type_to_proto_type(value_type) entry = self.indent + "map<{}, {}> {} = {};\n".format( proto_key_type, proto_value_type, content_name, tag_no ) tag_no += 1 elif content_type.startswith("Union"): # it is an sub_types = _get_sub_types_of_compositional_types(content_type) for sub_type in sub_types: sub_type_name = _union_sub_type_to_protobuf_variable_name( content_name, sub_type ) content_to_proto_field_str, tag_no = self._content_to_proto_field_str( sub_type_name, f"Optional[{sub_type}]", tag_no ) entry += content_to_proto_field_str elif content_type.startswith("Optional"): # it is an sub_type = _get_sub_types_of_compositional_types(content_type)[0] content_to_proto_field_str, tag_no = self._content_to_proto_field_str( content_name, sub_type, tag_no ) entry = content_to_proto_field_str entry += self.indent + "bool {}_is_set = {};\n".format(content_name, tag_no) tag_no += 1 else: # it is a or proto_type = _python_pt_or_ct_type_to_proto_type(content_type) entry = self.indent + "{} {} = {};\n".format( proto_type, content_name, tag_no ) tag_no += 1 return entry, tag_no def _protocol_buffer_schema_str(self) -> str: """ Produce the content of the Protocol Buffers schema. :return: the protocol buffers schema content """ self._change_indent(0, "s") # heading proto_buff_schema_str = self.indent + 'syntax = "proto3";\n\n' proto_buff_schema_str += self.indent + "package {};\n\n".format( public_id_to_package_name( self.protocol_specification.protocol_specification_id ) ) proto_buff_schema_str += self.indent + "message {}Message{{\n\n".format( self.protocol_specification_in_camel_case ) self._change_indent(1) # custom types if ( (len(self.spec.all_custom_types) != 0) and (self.protocol_specification.protobuf_snippets is not None) and (self.protocol_specification.protobuf_snippets != "") ): proto_buff_schema_str += self.indent + "// Custom Types\n" for custom_type in self.spec.all_custom_types: proto_buff_schema_str += self.indent + "message {}{{\n".format( custom_type ) self._change_indent(1) # formatting and adding the custom type protobuf entry specification_custom_type = "ct:" + custom_type proto_part = self.protocol_specification.protobuf_snippets[ specification_custom_type ] number_of_new_lines = proto_part.count("\n") if number_of_new_lines != 0: formatted_proto_part = proto_part.replace( "\n", "\n" + self.indent, number_of_new_lines - 1 ) else: formatted_proto_part = proto_part proto_buff_schema_str += self.indent + formatted_proto_part self._change_indent(-1) proto_buff_schema_str += self.indent + "}\n\n" proto_buff_schema_str += "\n" # performatives proto_buff_schema_str += self.indent + "// Performatives and contents\n" for performative, contents in self.spec.speech_acts.items(): proto_buff_schema_str += self.indent + "message {}_Performative{{".format( performative.title() ) self._change_indent(1) tag_no = 1 if len(contents) == 0: proto_buff_schema_str += "}\n\n" self._change_indent(-1) else: proto_buff_schema_str += "\n" for content_name, content_type in contents.items(): ( content_to_proto_field_str, tag_no, ) = self._content_to_proto_field_str( content_name, content_type, tag_no ) proto_buff_schema_str += content_to_proto_field_str self._change_indent(-1) proto_buff_schema_str += self.indent + "}\n\n" proto_buff_schema_str += "\n" proto_buff_schema_str += self.indent + "oneof performative{\n" self._change_indent(1) tag_no = 5 for performative in self.spec.all_performatives: proto_buff_schema_str += self.indent + "{}_Performative {} = {};\n".format( performative.title(), performative, tag_no ) tag_no += 1 self._change_indent(-1) proto_buff_schema_str += self.indent + "}\n" self._change_indent(-1) proto_buff_schema_str += self.indent + "}\n" return proto_buff_schema_str def _protocol_yaml_str(self) -> str: """ Produce the content of the protocol.yaml file. :return: the protocol.yaml content """ protocol_yaml_str = "name: {}\n".format(self.protocol_specification.name) protocol_yaml_str += "author: {}\n".format(self.protocol_specification.author) protocol_yaml_str += "version: {}\n".format(self.protocol_specification.version) protocol_yaml_str += "protocol_specification_id: {}\n".format( str(self.protocol_specification.protocol_specification_id) ) protocol_yaml_str += "type: {}\n".format( self.protocol_specification.component_type ) protocol_yaml_str += "description: {}\n".format( self.protocol_specification.description ) protocol_yaml_str += "license: {}\n".format(self.protocol_specification.license) protocol_yaml_str += "aea_version: '{}'\n".format( self.protocol_specification.aea_version ) protocol_yaml_str += "fingerprint: {}\n" protocol_yaml_str += "fingerprint_ignore_patterns: []\n" protocol_yaml_str += "dependencies:\n" protocol_yaml_str += " protobuf: {}\n" return protocol_yaml_str def _init_str(self) -> str: """ Produce the content of the __init__.py file. :return: the __init__.py content """ init_str = _copyright_header_str(self.protocol_specification.author) init_str += "\n" init_str += '"""\nThis module contains the support resources for the {} protocol.\n\nIt was created with protocol buffer compiler version `{}` and aea version `{}`.\n"""\n\n'.format( self.protocol_specification.name, self.protoc_version, __aea_version__ ) init_str += "from {}.message import {}Message\n".format( self.dotted_path_to_protocol_package, self.protocol_specification_in_camel_case, ) init_str += "from {}.serialization import {}Serializer\n".format( self.dotted_path_to_protocol_package, self.protocol_specification_in_camel_case, ) init_str += "{}Message.serializer = {}Serializer\n".format( self.protocol_specification_in_camel_case, self.protocol_specification_in_camel_case, ) return init_str def generate_protobuf_only_mode( self, language: str = PROTOCOL_LANGUAGE_PYTHON, run_protolint: bool = True, ) -> Optional[str]: """ Run the generator in "protobuf only" mode: a) validate the protocol specification. b) create the protocol buffer schema file. c) create the protocol buffer implementation file via 'protoc'. :param language: the target language in which to generate the package. :param run_protolint: whether to run protolint or not. :return: None """ if language not in SUPPORTED_PROTOCOL_LANGUAGES: raise ValueError( f"Unsupported language. Expected one of {SUPPORTED_PROTOCOL_LANGUAGES}. Found {language}." ) protobuf_output = None # type: Optional[str] # Create the output folder output_folder = Path(self.path_to_generated_protocol_package) if not output_folder.exists(): os.mkdir(output_folder) # Generate protocol buffer schema file _create_protocol_file( self.path_to_generated_protocol_package, "{}.proto".format(self.protocol_specification.name), self._protocol_buffer_schema_str(), ) # Try to compile protobuf schema file is_compiled, msg = compile_protobuf_using_protoc( self.path_to_generated_protocol_package, self.protocol_specification.name, language, ) if not is_compiled: # Remove the generated folder and files shutil.rmtree(output_folder) raise SyntaxError( "Error when trying to compile the protocol buffer schema file:\n" + msg ) # Run protolint if run_protolint: is_correctly_formatted, protolint_output = apply_protolint( self.path_to_generated_protocol_package, self.protocol_specification.name, ) if not is_correctly_formatted and protolint_output != "": protobuf_output = "Protolint warnings:\n" + protolint_output # Run black and isort formatting for python if language == PROTOCOL_LANGUAGE_PYTHON: try_run_black_formatting(self.path_to_generated_protocol_package) try_run_isort_formatting(self.path_to_generated_protocol_package) return protobuf_output def generate_full_mode(self, language: str) -> Optional[str]: """ Run the generator in "full" mode: Runs the generator in protobuf only mode: a) validate the protocol specification. b) create the protocol buffer schema file. c) create the protocol buffer implementation file via 'protoc'. Additionally: d) generates python modules. e) applies black formatting f) applies isort formatting :param language: the language for which to create protobuf files :return: optional warning message """ if language != PROTOCOL_LANGUAGE_PYTHON: raise ValueError( f"Unsupported language. Expected 'python' because currently the framework supports full generation of protocols only in Python. Found {language}." ) # Run protobuf only mode full_mode_output = self.generate_protobuf_only_mode( language=PROTOCOL_LANGUAGE_PYTHON ) # Generate Python protocol package _create_protocol_file( self.path_to_generated_protocol_package, INIT_FILE_NAME, self._init_str() ) _create_protocol_file( self.path_to_generated_protocol_package, PROTOCOL_YAML_FILE_NAME, self._protocol_yaml_str(), ) _create_protocol_file( self.path_to_generated_protocol_package, MESSAGE_DOT_PY_FILE_NAME, self._message_class_str(), ) if ( self.protocol_specification.dialogue_config is not None and self.protocol_specification.dialogue_config != {} ): _create_protocol_file( self.path_to_generated_protocol_package, DIALOGUE_DOT_PY_FILE_NAME, self._dialogue_class_str(), ) if len(self.spec.all_custom_types) > 0: _create_protocol_file( self.path_to_generated_protocol_package, CUSTOM_TYPES_DOT_PY_FILE_NAME, self._custom_types_module_str(), ) _create_protocol_file( self.path_to_generated_protocol_package, SERIALIZATION_DOT_PY_FILE_NAME, self._serialization_class_str(), ) # Run black formatting try_run_black_formatting(self.path_to_generated_protocol_package) # Run isort formatting try_run_isort_formatting(self.path_to_generated_protocol_package) # Warn if specification has custom types if len(self.spec.all_custom_types) > 0: incomplete_generation_warning_msg = "The generated protocol is incomplete, because the protocol specification contains the following custom types: {}. Update the generated '{}' file with the appropriate implementations of these custom types.".format( self.spec.all_custom_types, CUSTOM_TYPES_DOT_PY_FILE_NAME ) if full_mode_output is not None: full_mode_output += incomplete_generation_warning_msg else: full_mode_output = incomplete_generation_warning_msg return full_mode_output def generate( self, protobuf_only: bool = False, language: str = PROTOCOL_LANGUAGE_PYTHON ) -> Optional[str]: """ Run the generator either in "full" or "protobuf only" mode. :param protobuf_only: mode of running the generator. :param language: the target language in which to generate the protocol package. :return: optional warning message. """ if protobuf_only: output = self.generate_protobuf_only_mode(language) # type: Optional[str] # Warn about the protobuf only mode protobuf_mode_warning_msg = ( "The generated protocol is incomplete. It only includes the protocol buffer definitions. " + "You must implement and add other definitions (e.g. messages, serialisation, dialogue, etc) to this package." ) if output is not None: output += protobuf_mode_warning_msg else: output = protobuf_mode_warning_msg else: output = self.generate_full_mode(language) return output def public_id_to_package_name(public_id: PublicId) -> str: """Make package name string from public_id provided.""" return f'aea.{public_id.author}.{public_id.name}.v{public_id.version.replace(".", "_")}' ================================================ FILE: aea/protocols/generator/common.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains utility code for generator modules.""" import inspect import os import re import shutil import subprocess # nosec import sys import tempfile from pathlib import Path from typing import Tuple from aea.configurations.base import ProtocolSpecification from aea.configurations.constants import ( DEFAULT_PROTOCOL_CONFIG_FILE, PACKAGES, PROTOCOL_LANGUAGE_JS, PROTOCOL_LANGUAGE_PYTHON, ) from aea.configurations.loader import ConfigLoader from aea.helpers.io import open_file SPECIFICATION_PRIMITIVE_TYPES = ["pt:bytes", "pt:int", "pt:float", "pt:bool", "pt:str"] SPECIFICATION_COMPOSITIONAL_TYPES = [ "pt:set", "pt:list", "pt:dict", "pt:union", "pt:optional", ] PYTHON_COMPOSITIONAL_TYPES = [ "FrozenSet", "Tuple", "Dict", "Union", "Optional", ] MESSAGE_IMPORT = "from aea.protocols.base import Message" SERIALIZER_IMPORT = "from aea.protocols.base import Serializer" PATH_TO_PACKAGES = PACKAGES INIT_FILE_NAME = "__init__.py" PROTOCOL_YAML_FILE_NAME = DEFAULT_PROTOCOL_CONFIG_FILE MESSAGE_DOT_PY_FILE_NAME = "message.py" DIALOGUE_DOT_PY_FILE_NAME = "dialogues.py" CUSTOM_TYPES_DOT_PY_FILE_NAME = "custom_types.py" SERIALIZATION_DOT_PY_FILE_NAME = "serialization.py" PYTHON_TYPE_TO_PROTO_TYPE = { "bytes": "bytes", "int": "int64", "float": "float", "bool": "bool", "str": "string", } CURRENT_DIR = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore ISORT_CONFIGURATION_FILE = os.path.join(CURRENT_DIR, "isort.cfg") ISORT_CLI_ARGS = [ "--settings-path", ISORT_CONFIGURATION_FILE, "--quiet", ] PROTOLINT_CONFIGURATION_FILE_NAME = "protolint.yaml" PROTOLINT_CONFIGURATION = """lint: rules: remove: - MESSAGE_NAMES_UPPER_CAMEL_CASE - ENUM_FIELD_NAMES_ZERO_VALUE_END_WITH - PACKAGE_NAME_LOWER_CASE - REPEATED_FIELD_NAMES_PLURALIZED - FIELD_NAMES_LOWER_SNAKE_CASE - ENUM_FIELD_NAMES_PREFIX""" PROTOLINT_INDENTATION_ERROR_STR = "incorrect indentation style" PROTOLINT_ERROR_WHITELIST = [PROTOLINT_INDENTATION_ERROR_STR] def _to_camel_case(text: str) -> str: """ Convert a text in snake_case format into the CamelCase format. :param text: the text to be converted. :return: The text in CamelCase format. """ return "".join(word.title() for word in text.split("_")) def _camel_case_to_snake_case(text: str) -> str: """ Convert a text in CamelCase format into the snake_case format. :param text: the text to be converted. :return: The text in CamelCase format. """ return re.sub(r"(? int: """ Give the index of the matching close bracket for the opening bracket at 'index_of_open_bracket' in the input 'text'. :param text: the text containing the brackets. :param index_of_open_bracket: the index of the opening bracket. :return: the index of the matching closing bracket (if any). :raises SyntaxError if there are no matching closing bracket. """ if text[index_of_open_bracket] != "[": raise SyntaxError( "Index {} in 'text' is not an open bracket '['. It is {}".format( index_of_open_bracket, text[index_of_open_bracket], ) ) open_bracket_stack = [] for index in range(index_of_open_bracket, len(text)): if text[index] == "[": open_bracket_stack.append(text[index]) elif text[index] == "]": open_bracket_stack.pop() if not open_bracket_stack: return index raise SyntaxError( "No matching closing bracket ']' for the opening bracket '[' at {} " + str(index_of_open_bracket) ) def _has_matched_brackets(text: str) -> bool: """ Evaluate whether every opening bracket '[' in the 'text' has a matching closing bracket ']'. :param text: the text. :return: Boolean result, and associated message. """ open_bracket_stack = [] for index, _ in enumerate(text): if text[index] == "[": open_bracket_stack.append(index) elif text[index] == "]": if len(open_bracket_stack) == 0: return False open_bracket_stack.pop() return len(open_bracket_stack) == 0 def _get_sub_types_of_compositional_types(compositional_type: str) -> Tuple[str, ...]: """ Extract the sub-types of compositional types. This method handles both specification types (e.g. pt:set[], pt:dict[]) as well as python types (e.g. FrozenSet[], Union[]). :param compositional_type: the compositional type string whose sub-types are to be extracted. :return: tuple containing all extracted sub-types. """ sub_types_list = [] for valid_compositional_type in ( SPECIFICATION_COMPOSITIONAL_TYPES + PYTHON_COMPOSITIONAL_TYPES ): if compositional_type.startswith(valid_compositional_type): inside_string = compositional_type[ compositional_type.index("[") + 1 : compositional_type.rindex("]") ].strip() while inside_string != "": do_not_add = False if inside_string.find(",") == -1: # No comma; this is the last sub-type provisional_sub_type = inside_string.strip() if ( provisional_sub_type == "..." ): # The sub-string is ... used for Tuple, e.g. Tuple[int, ...] do_not_add = True else: sub_type = provisional_sub_type inside_string = "" else: # There is a comma; this MAY not be the last sub-type sub_string_until_comma = inside_string[ : inside_string.index(",") ].strip() if ( sub_string_until_comma.find("[") == -1 ): # No open brackets; this is a primitive type and NOT the last sub-type sub_type = sub_string_until_comma inside_string = inside_string[ inside_string.index(",") + 1 : ].strip() else: # There is an open bracket'['; this is a compositional type try: closing_bracket_index = _match_brackets( inside_string, inside_string.index("[") ) except SyntaxError: raise SyntaxError( "Bad formatting. No matching close bracket ']' for the open bracket at {}".format( inside_string[ : inside_string.index("[") + 1 ].strip() ) ) sub_type = inside_string[: closing_bracket_index + 1].strip() the_rest_of_inside_string = inside_string[ closing_bracket_index + 1 : ].strip() if ( the_rest_of_inside_string.find(",") == -1 ): # No comma; this is the last sub-type inside_string = the_rest_of_inside_string.strip() else: # There is a comma; this is not the last sub-type inside_string = the_rest_of_inside_string[ the_rest_of_inside_string.index(",") + 1 : ].strip() if not do_not_add: sub_types_list.append(sub_type) return tuple(sub_types_list) raise SyntaxError( "{} is not a valid compositional type.".format(compositional_type) ) def _union_sub_type_to_protobuf_variable_name( content_name: str, content_type: str ) -> str: """ Given a content of type union, create a variable name for its sub-type for protobuf. :param content_name: the name of the content :param content_type: the sub-type of a union type :return: The variable name """ if content_type.startswith("FrozenSet"): sub_type = _get_sub_types_of_compositional_types(content_type)[0] expanded_type_str = "set_of_{}".format(sub_type) elif content_type.startswith("Tuple"): sub_type = _get_sub_types_of_compositional_types(content_type)[0] expanded_type_str = "list_of_{}".format(sub_type) elif content_type.startswith("Dict"): sub_type_1 = _get_sub_types_of_compositional_types(content_type)[0] sub_type_2 = _get_sub_types_of_compositional_types(content_type)[1] expanded_type_str = "dict_of_{}_{}".format(sub_type_1, sub_type_2) else: expanded_type_str = content_type protobuf_variable_name = "{}_type_{}".format(content_name, expanded_type_str) return protobuf_variable_name def _python_pt_or_ct_type_to_proto_type(content_type: str) -> str: """ Convert a PT or CT from python to their protobuf equivalent. :param content_type: the python type :return: The protobuf equivalent """ if content_type in PYTHON_TYPE_TO_PROTO_TYPE.keys(): proto_type = PYTHON_TYPE_TO_PROTO_TYPE[content_type] else: proto_type = content_type return proto_type def _includes_custom_type(content_type: str) -> bool: """ Evaluate whether a content type is a custom type or has a custom type as a sub-type. :param content_type: the content type :return: Boolean result """ if content_type.startswith("Optional"): sub_type = _get_sub_types_of_compositional_types(content_type)[0] result = _includes_custom_type(sub_type) elif content_type.startswith("Union"): sub_types = _get_sub_types_of_compositional_types(content_type) result = False for sub_type in sub_types: if _includes_custom_type(sub_type): result = True break elif ( content_type.startswith("FrozenSet") or content_type.startswith("Tuple") or content_type.startswith("Dict") or content_type in PYTHON_TYPE_TO_PROTO_TYPE.keys() ): result = False else: result = True return result def is_installed(programme: str) -> bool: """ Check whether a programme is installed on the system. :param programme: the name of the programme. :return: True if installed, False otherwise """ res = shutil.which(programme) return res is not None def base_protolint_command() -> str: """ Return the base protolint command. :return: The base protolint command """ if sys.platform.startswith("win"): protolint_base_cmd = "protolint" # pragma: nocover else: protolint_base_cmd = "PATH=${PATH}:${GOPATH}/bin/:~/go/bin protolint" return protolint_base_cmd def check_prerequisites() -> None: """Check whether a programme is installed on the system.""" # check black code formatter is installed if not is_installed("black"): raise FileNotFoundError( "Cannot find black code formatter! To install, please follow this link: https://black.readthedocs.io/en/stable/installation_and_usage.html" ) # check isort code formatter is installed if not is_installed("isort"): raise FileNotFoundError( "Cannot find isort code formatter! To install, please follow this link: https://pycqa.github.io/isort/#installing-isort" ) # check protolint code formatter is installed if subprocess.call(f"{base_protolint_command()} version", shell=True) != 0: # nosec raise FileNotFoundError( "Cannot find protolint protocol buffer schema file linter! To install, please follow this link: https://github.com/yoheimuta/protolint." ) # check protocol buffer compiler is installed if not is_installed("protoc"): raise FileNotFoundError( "Cannot find protocol buffer compiler! To install, please follow this link: https://developers.google.com/protocol-buffers/" ) def get_protoc_version() -> str: """Get the protoc version used.""" result = subprocess.run( # nosec ["protoc", "--version"], stdout=subprocess.PIPE, check=True ) result_str = result.stdout.decode("utf-8").strip("\n").strip("\r") return result_str def load_protocol_specification(specification_path: str) -> ProtocolSpecification: """ Load a protocol specification. :param specification_path: path to the protocol specification yaml file. :return: A ProtocolSpecification object """ config_loader = ConfigLoader( "protocol-specification_schema.json", ProtocolSpecification ) protocol_spec = config_loader.load_protocol_specification( open_file(specification_path) ) return protocol_spec def _create_protocol_file( path_to_protocol_package: str, file_name: str, file_content: str ) -> None: """ Create a file in the generated protocol package. :param path_to_protocol_package: path to the file :param file_name: the name of the file :param file_content: the content of the file """ pathname = os.path.join(path_to_protocol_package, file_name) with open_file(pathname, "w") as file: file.write(file_content) def try_run_black_formatting(path_to_protocol_package: str) -> None: """ Run Black code formatting via subprocess. :param path_to_protocol_package: a path where formatting should be applied. """ subprocess.run( # nosec [sys.executable, "-m", "black", path_to_protocol_package, "--quiet"], check=True, ) def try_run_isort_formatting(path_to_protocol_package: str) -> None: """ Run Isort code formatting via subprocess. :param path_to_protocol_package: a path where formatting should be applied. """ subprocess.run( # nosec [sys.executable, "-m", "isort", *ISORT_CLI_ARGS, path_to_protocol_package], check=True, ) def try_run_protoc( path_to_generated_protocol_package: str, name: str, language: str = PROTOCOL_LANGUAGE_PYTHON, ) -> None: """ Run 'protoc' protocol buffer compiler via subprocess. :param path_to_generated_protocol_package: path to the protocol buffer schema file. :param name: name of the protocol buffer schema file. :param language: the target language in which to compile the protobuf schema file """ # for closure-styled imports for JS, comment the first line and uncomment the second js_commonjs_import_option = ( "import_style=commonjs,binary:" if language == PROTOCOL_LANGUAGE_JS else "" ) language_part_of_the_command = f"--{language}_out={js_commonjs_import_option}{path_to_generated_protocol_package}" subprocess.run( # nosec [ "protoc", f"-I={path_to_generated_protocol_package}", language_part_of_the_command, f"{path_to_generated_protocol_package}/{name}.proto", ], stderr=subprocess.PIPE, encoding="utf-8", check=True, env=os.environ.copy(), ) def try_run_protolint(path_to_generated_protocol_package: str, name: str) -> None: """ Run 'protolint' linter via subprocess. :param path_to_generated_protocol_package: path to the protocol buffer schema file. :param name: name of the protocol buffer schema file. """ # path to proto file path_to_proto_file = os.path.join( path_to_generated_protocol_package, f"{name}.proto", ) # Dump protolint configuration into a temporary file temp_dir = tempfile.mkdtemp() path_to_configuration_in_tmp_file = Path( temp_dir, PROTOLINT_CONFIGURATION_FILE_NAME ) with open_file(path_to_configuration_in_tmp_file, "w") as file: file.write(PROTOLINT_CONFIGURATION) # Protolint command cmd = f'{base_protolint_command()} lint -config_path={path_to_configuration_in_tmp_file} -fix "{path_to_proto_file}"' # Execute protolint command subprocess.run( # nosec cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding="utf-8", check=True, env=os.environ.copy(), shell=True, ) # Delete temporary configuration file shutil.rmtree(temp_dir) # pragma: no cover def check_protobuf_using_protoc( path_to_generated_protocol_package: str, name: str ) -> Tuple[bool, str]: """ Check whether a protocol buffer schema file is valid. Validation is via trying to compile the schema file. If successfully compiled it is valid, otherwise invalid. If valid, return True and a 'protobuf file is valid' message, otherwise return False and the error thrown by the compiler. :param path_to_generated_protocol_package: path to the protocol buffer schema file. :param name: name of the protocol buffer schema file. :return: Boolean result and an accompanying message """ try: try_run_protoc(path_to_generated_protocol_package, name) os.remove(os.path.join(path_to_generated_protocol_package, name + "_pb2.py")) return True, "protobuf file is valid" except subprocess.CalledProcessError as e: pattern = name + ".proto:[0-9]+:[0-9]+: " error_message = re.sub(pattern, "", e.stderr[:-1]) return False, error_message def compile_protobuf_using_protoc( path_to_generated_protocol_package: str, name: str, language: str ) -> Tuple[bool, str]: """ Compile a protocol buffer schema file using protoc. If successfully compiled, return True and a success message, otherwise return False and the error thrown by the compiler. :param path_to_generated_protocol_package: path to the protocol buffer schema file. :param name: name of the protocol buffer schema file. :param language: the target language in which to compile the protobuf schema file :return: Boolean result and an accompanying message """ try: try_run_protoc(path_to_generated_protocol_package, name, language) return True, "protobuf schema successfully compiled" except subprocess.CalledProcessError as e: pattern = name + ".proto:[0-9]+:[0-9]+: " error_message = re.sub(pattern, "", e.stderr[:-1]) return False, error_message def apply_protolint(path_to_proto_file: str, name: str) -> Tuple[bool, str]: """ Apply protolint linter to a protocol buffer schema file. If no output, return True and a success message, otherwise return False and the output shown by the linter (minus the indentation suggestions which are automatically fixed by protolint). :param path_to_proto_file: path to the protocol buffer schema file. :param name: name of the protocol buffer schema file. :return: Boolean result and an accompanying message """ try: try_run_protolint(path_to_proto_file, name) return True, "protolint has no output" except subprocess.CalledProcessError as e: lines_to_show = [] for line in e.stderr.split("\n"): to_show = True for whitelist_error_str in PROTOLINT_ERROR_WHITELIST: if whitelist_error_str in line: to_show = False break if to_show: lines_to_show.append(line) error_message = "\n".join(lines_to_show) return False, error_message def _is_compositional_type(content_type: str) -> bool: """Checks if content_type is compositional. :param content_type: the type string. :return: bool. """ for valid_compositional_type in ( SPECIFICATION_COMPOSITIONAL_TYPES + PYTHON_COMPOSITIONAL_TYPES ): if content_type.startswith(valid_compositional_type): return True return False ================================================ FILE: aea/protocols/generator/extract_specification.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module extracts a valid protocol specification into pythonic objects.""" import re from typing import Dict, List, cast from aea.configurations.base import ( ProtocolSpecification, ProtocolSpecificationParseError, ) from aea.protocols.generator.common import ( SPECIFICATION_PRIMITIVE_TYPES, _get_sub_types_of_compositional_types, _is_compositional_type, ) def _ct_specification_type_to_python_type(specification_type: str) -> str: """ Convert a custom specification type into its python equivalent. :param specification_type: a protocol specification data type :return: The equivalent data type in Python """ python_type = specification_type[3:] return python_type def _pt_specification_type_to_python_type(specification_type: str) -> str: """ Convert a primitive specification type into its python equivalent. :param specification_type: a protocol specification data type :return: The equivalent data type in Python """ python_type = specification_type[3:] return python_type def _pct_specification_type_to_python_type(specification_type: str) -> str: """ Convert a primitive collection specification type into its python equivalent. :param specification_type: a protocol specification data type :return: The equivalent data type in Python """ element_type = _get_sub_types_of_compositional_types(specification_type)[0] element_type_in_python = _specification_type_to_python_type(element_type) if specification_type.startswith("pt:set"): python_type = "FrozenSet[{}]".format(element_type_in_python) else: python_type = "Tuple[{}, ...]".format(element_type_in_python) return python_type def _pmt_specification_type_to_python_type(specification_type: str) -> str: """ Convert a primitive mapping specification type into its python equivalent. :param specification_type: a protocol specification data type :return: The equivalent data type in Python """ element_types = _get_sub_types_of_compositional_types(specification_type) element1_type_in_python = _specification_type_to_python_type(element_types[0]) element2_type_in_python = _specification_type_to_python_type(element_types[1]) python_type = "Dict[{}, {}]".format( element1_type_in_python, element2_type_in_python ) return python_type def _mt_specification_type_to_python_type(specification_type: str) -> str: """ Convert a 'pt:union' specification type into its python equivalent. :param specification_type: a protocol specification data type :return: The equivalent data type in Python """ sub_types = _get_sub_types_of_compositional_types(specification_type) python_type = "Union[" for sub_type in sub_types: python_type += "{}, ".format(_specification_type_to_python_type(sub_type)) python_type = python_type[:-2] python_type += "]" return python_type def _optional_specification_type_to_python_type(specification_type: str) -> str: """ Convert a 'pt:optional' specification type into its python equivalent. :param specification_type: a protocol specification data type :return: The equivalent data type in Python """ element_type = _get_sub_types_of_compositional_types(specification_type)[0] element_type_in_python = _specification_type_to_python_type(element_type) python_type = "Optional[{}]".format(element_type_in_python) return python_type def _specification_type_to_python_type(specification_type: str) -> str: """ Convert a data type in protocol specification into its Python equivalent. :param specification_type: a protocol specification data type :return: The equivalent data type in Python """ if specification_type.startswith("pt:optional"): python_type = _optional_specification_type_to_python_type(specification_type) elif specification_type.startswith("pt:union"): python_type = _mt_specification_type_to_python_type(specification_type) elif specification_type.startswith("ct:"): python_type = _ct_specification_type_to_python_type(specification_type) elif specification_type in SPECIFICATION_PRIMITIVE_TYPES: python_type = _pt_specification_type_to_python_type(specification_type) elif specification_type.startswith("pt:set"): python_type = _pct_specification_type_to_python_type(specification_type) elif specification_type.startswith("pt:list"): python_type = _pct_specification_type_to_python_type(specification_type) elif specification_type.startswith("pt:dict"): python_type = _pmt_specification_type_to_python_type(specification_type) else: raise ProtocolSpecificationParseError( "Unsupported type: '{}'".format(specification_type) ) return python_type class PythonicProtocolSpecification: # pylint: disable=too-few-public-methods """This class represents a protocol specification in python.""" def __init__(self) -> None: """Instantiate a Pythonic protocol specification.""" self.speech_acts = {} # type: Dict[str, Dict[str, str]] self.all_performatives = [] # type: List[str] self.all_unique_contents = {} # type: Dict[str, str] self.all_custom_types = [] # type: List[str] self.custom_custom_types = {} # type: Dict[str, str] # dialogue config self.initial_performatives = [] # type: List[str] self.reply = {} # type: Dict[str, List[str]] self.terminal_performatives = [] # type: List[str] self.roles = [] # type: List[str] self.end_states = [] # type: List[str] self.keep_terminal_state_dialogues = False # type: bool self.typing_imports = { "Set": True, "Tuple": True, "cast": True, "FrozenSet": False, "Dict": False, "Union": False, "Optional": False, } def extract( protocol_specification: ProtocolSpecification, ) -> PythonicProtocolSpecification: """ Converts a protocol specification into a Pythonic protocol specification. :param protocol_specification: a protocol specification :return: a Pythonic protocol specification """ spec = PythonicProtocolSpecification() all_performatives_set = set() all_custom_types_set = set() for ( performative, speech_act_content_config, ) in protocol_specification.speech_acts.read_all(): all_performatives_set.add(performative) spec.speech_acts[performative] = {} for content_name, content_type in speech_act_content_config.args.items(): # determine necessary imports from typing if len(re.findall("pt:set\\[", content_type)) >= 1: spec.typing_imports["FrozenSet"] = True if len(re.findall("pt:dict\\[", content_type)) >= 1: spec.typing_imports["Dict"] = True if len(re.findall("pt:union\\[", content_type)) >= 1: spec.typing_imports["Union"] = True if len(re.findall("pt:optional\\[", content_type)) >= 1: spec.typing_imports["Optional"] = True # specification type --> python type pythonic_content_type = _specification_type_to_python_type(content_type) spec.all_unique_contents[content_name] = pythonic_content_type spec.speech_acts[performative][content_name] = pythonic_content_type if content_type.startswith("ct:"): all_custom_types_set.add(pythonic_content_type) for sub_type in ( list(_get_sub_types_of_compositional_types(content_type)) if _is_compositional_type(content_type) else [] ): if sub_type.startswith("ct:"): pythonic_content_type = _specification_type_to_python_type(sub_type) all_custom_types_set.add(pythonic_content_type) # sort the sets spec.all_performatives = sorted(all_performatives_set) spec.all_custom_types = sorted(all_custom_types_set) # "XXX" custom type --> "CustomXXX" spec.custom_custom_types = { pure_custom_type: "Custom" + pure_custom_type for pure_custom_type in spec.all_custom_types } # Dialogue attributes if ( protocol_specification.dialogue_config != {} and protocol_specification.dialogue_config is not None ): spec.initial_performatives = [ initial_performative.upper() for initial_performative in cast( List[str], protocol_specification.dialogue_config["initiation"] ) ] spec.reply = cast( Dict[str, List[str]], protocol_specification.dialogue_config["reply"], ) spec.terminal_performatives = [ terminal_performative.upper() for terminal_performative in cast( List[str], protocol_specification.dialogue_config["termination"], ) ] roles_set = cast( Dict[str, None], protocol_specification.dialogue_config["roles"] ) spec.roles = sorted(roles_set) spec.end_states = cast( List[str], protocol_specification.dialogue_config["end_states"] ) spec.keep_terminal_state_dialogues = cast( bool, protocol_specification.dialogue_config.get( "keep_terminal_state_dialogues", False ), ) return spec ================================================ FILE: aea/protocols/generator/isort.cfg ================================================ # isort configurations for the protocol generator [isort] # for black compatibility multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 use_parentheses=True ensure_newline_before_comments = True line_length=88 # custom configurations order_by_type=False case_sensitive=True lines_after_imports=2 skip_glob = **/*_pb2.py known_first_party=aea known_packages=packages known_local_folder=tests sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,PACKAGES,LOCALFOLDER ================================================ FILE: aea/protocols/generator/validate.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module validates a protocol specification.""" import re from typing import Any, Dict, List, Optional, Set, Tuple, cast from aea.configurations.base import ProtocolSpecification from aea.protocols.generator.common import ( SPECIFICATION_COMPOSITIONAL_TYPES, SPECIFICATION_PRIMITIVE_TYPES, _get_sub_types_of_compositional_types, _has_matched_brackets, _is_compositional_type, ) # The following names are reserved for standard message fields and cannot be # used as user defined names for performative or contents RESERVED_NAMES = {"_body", "message_id", "dialogue_reference", "target", "performative"} # Regular expression patterns for various fields in protocol specifications PERFORMATIVE_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" CONTENT_NAME_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" CT_NAME_RE = ( "[A-Z][a-zA-Z0-9]*" # or maybe "ct:(?:[A-Z][a-z]+)+" or # "^ct:([A-Z]+[a-z]*)+$" ) CT_CONTENT_TYPE_REGEX_PATTERN = f"^ct:{CT_NAME_RE}$" ROLE_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" END_STATE_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" DIALOGUE_SECTION_REQUIRED_FIELDS = [ "initiation", "reply", "termination", "roles", "end_states", "keep_terminal_state_dialogues", ] def _is_reserved_name(content_name: str) -> bool: """ Evaluate whether a content name is a reserved name or not. :param content_name: a content name :return: Boolean result """ return content_name in RESERVED_NAMES def _is_valid_regex(regex_pattern: str, text: str) -> bool: """ Evaluate whether a 'text' matches a regular expression pattern. :param regex_pattern: the regular expression pattern :param text: the text on which to match regular expression :return: Boolean result """ match = re.match(regex_pattern, text) return match is not None def _has_brackets(content_type: str) -> bool: """ Evaluate whether a compositional content type in a protocol specification is valid has corresponding brackets. :param content_type: an 'set' content type. :return: Boolean result """ for compositional_type in SPECIFICATION_COMPOSITIONAL_TYPES: if content_type.startswith(compositional_type): content_type = content_type[len(compositional_type) :] if len(content_type) < 2: return False return content_type[0] == "[" and content_type[len(content_type) - 1] == "]" raise SyntaxError("Content type must be a compositional type!") def _is_valid_ct(content_type: str) -> bool: """ Evaluate whether the format of a 'ct' content type in a protocol specification is valid. :param content_type: a 'ct' content type. :return: Boolean result """ content_type = content_type.strip() return _is_valid_regex(CT_CONTENT_TYPE_REGEX_PATTERN, content_type) def _is_valid_pt(content_type: str) -> bool: """ Evaluate whether the format of a 'pt' content type in a protocol specification is valid. :param content_type: a 'pt' content type. :return: Boolean result """ content_type = content_type.strip() return content_type in SPECIFICATION_PRIMITIVE_TYPES def _is_valid_set(content_type: str) -> bool: """ Evaluate whether the format of a 'set' content type in a protocol specification is valid. :param content_type: a 'set' content type. :return: Boolean result """ content_type = content_type.strip() if not content_type.startswith("pt:set"): return False if not _has_matched_brackets(content_type): return False if not _has_brackets(content_type): return False sub_types = _get_sub_types_of_compositional_types(content_type) if len(sub_types) != 1: return False sub_type = sub_types[0] return _is_valid_pt(sub_type) def _is_valid_list(content_type: str) -> bool: """ Evaluate whether the format of a 'list' content type in a protocol specification is valid. :param content_type: a 'list' content type. :return: Boolean result """ content_type = content_type.strip() if not content_type.startswith("pt:list"): return False if not _has_matched_brackets(content_type): return False if not _has_brackets(content_type): return False sub_types = _get_sub_types_of_compositional_types(content_type) if len(sub_types) != 1: return False sub_type = sub_types[0] return _is_valid_pt(sub_type) def _is_valid_dict(content_type: str) -> bool: """ Evaluate whether the format of a 'dict' content type in a protocol specification is valid. :param content_type: a 'dict' content type. :return: Boolean result """ content_type = content_type.strip() if not content_type.startswith("pt:dict"): return False if not _has_matched_brackets(content_type): return False if not _has_brackets(content_type): return False sub_types = _get_sub_types_of_compositional_types(content_type) if len(sub_types) != 2: return False sub_type_1 = sub_types[0] sub_type_2 = sub_types[1] return _is_valid_pt(sub_type_1) and _is_valid_pt(sub_type_2) def _is_valid_union(content_type: str) -> bool: """ Evaluate whether the format of a 'union' content type in a protocol specification is valid. :param content_type: an 'union' content type. :return: Boolean result """ content_type = content_type.strip() if not content_type.startswith("pt:union"): return False if not _has_matched_brackets(content_type): return False if not _has_brackets(content_type): return False sub_types = _get_sub_types_of_compositional_types(content_type) # check there are at least two subtypes in the union if len(sub_types) < 2: return False # check there are no duplicate subtypes in the union sub_types_set = set(sub_types) if len(sub_types) != len(sub_types_set): return False for sub_type in sub_types: if not ( _is_valid_ct(sub_type) or _is_valid_pt(sub_type) or _is_valid_dict(sub_type) or _is_valid_list(sub_type) or _is_valid_set(sub_type) ): return False return True def _is_valid_optional(content_type: str) -> bool: """ Evaluate whether the format of an 'optional' content type in a protocol specification is valid. :param content_type: an 'optional' content type. :return: Boolean result """ content_type = content_type.strip() if not content_type.startswith("pt:optional"): return False if not _has_matched_brackets(content_type): return False if not _has_brackets(content_type): return False sub_types = _get_sub_types_of_compositional_types(content_type) if len(sub_types) != 1: return False sub_type = sub_types[0] return ( _is_valid_ct(sub_type) or _is_valid_pt(sub_type) or _is_valid_set(sub_type) or _is_valid_list(sub_type) or _is_valid_dict(sub_type) or _is_valid_union(sub_type) ) def _is_valid_content_type_format(content_type: str) -> bool: """ Evaluate whether the format of a content type in a protocol specification is valid. :param content_type: a content type. :return: Boolean result """ return ( _is_valid_ct(content_type) or _is_valid_pt(content_type) or _is_valid_set(content_type) or _is_valid_list(content_type) or _is_valid_dict(content_type) or _is_valid_union(content_type) or _is_valid_optional(content_type) ) def _validate_performatives(performative: str) -> Tuple[bool, str]: """ Evaluate whether a performative in a protocol specification is valid. :param performative: a performative. :return: Boolean result, and associated message. """ # check performative is not a reserved name if _is_reserved_name(performative): return ( False, "Invalid name for performative '{}'. This name is reserved.".format( performative, ), ) # check performative's format if not _is_valid_regex(PERFORMATIVE_REGEX_PATTERN, performative): return ( False, "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( performative, PERFORMATIVE_REGEX_PATTERN ), ) return True, "Performative '{}' is valid.".format(performative) def _validate_content_name(content_name: str, performative: str) -> Tuple[bool, str]: """ Evaluate whether the name of a content in a protocol specification is valid. :param content_name: a content name. :param performative: the performative the content belongs to. :return: Boolean result, and associated message. """ # check content name's format if not _is_valid_regex(CONTENT_NAME_REGEX_PATTERN, content_name): return ( False, "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( content_name, performative, CONTENT_NAME_REGEX_PATTERN ), ) # check content name is not a reserved name if _is_reserved_name(content_name): return ( False, "Invalid name for content '{}' of performative '{}'. This name is reserved.".format( content_name, performative, ), ) return ( True, "Content name '{}' of performative '{}' is valid.".format( content_name, performative ), ) def _validate_content_type( content_type: str, content_name: str, performative: str ) -> Tuple[bool, str]: """ Evaluate whether the type of a content in a protocol specification is valid. :param content_type: a content type. :param content_name: a content name. :param performative: the performative the content belongs to. :return: Boolean result, and associated message. """ if not _is_valid_content_type_format(content_type): return ( False, "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ), ) return ( True, "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ), ) def _validate_speech_acts_section( protocol_specification: ProtocolSpecification, ) -> Tuple[bool, str, Optional[Set[str]], Optional[Set[str]]]: """ Evaluate whether speech-acts of a protocol specification is valid. :param protocol_specification: a protocol specification. :return: Boolean result, associated message, set of all performatives (auxiliary), set of all custom types (auxiliary). """ custom_types_set = set() performatives_set = set() content_names_types: Dict[str, Tuple[str, str]] = {} # check that speech-acts definition is not empty if len(protocol_specification.speech_acts.read_all()) == 0: return ( False, "Speech-acts cannot be empty!", None, None, ) for ( performative, speech_act_content_config, ) in protocol_specification.speech_acts.read_all(): # Validate performative name ( result_performative_validation, msg_performative_validation, ) = _validate_performatives(performative) if not result_performative_validation: return ( result_performative_validation, msg_performative_validation, None, None, ) performatives_set.add(performative) for content_name, content_type in speech_act_content_config.args.items(): # Validate content name ( result_content_name_validation, msg_content_name_validation, ) = _validate_content_name(content_name, performative) if not result_content_name_validation: return ( result_content_name_validation, msg_content_name_validation, None, None, ) # check type of content_type if not isinstance(content_type, str): return ( False, "Invalid type for '{}'. Expected str. Found {}.".format( content_name, type(content_type) ), None, None, ) # Validate content type ( result_content_type_validation, msg_content_type_validation, ) = _validate_content_type(content_type, content_name, performative) if not result_content_type_validation: return ( result_content_type_validation, msg_content_type_validation, None, None, ) # check content name isn't repeated with a different type if content_name in content_names_types: last_performative = content_names_types[content_name][0] last_content_type = content_names_types[content_name][1] if last_content_type != content_type: return ( False, "Content '{}' with type '{}' under performative '{}' is already defined under performative '{}' with a different type ('{}').".format( content_name, content_type, performative, last_performative, last_content_type, ), None, None, ) content_names_types[content_name] = (performative, content_type) for sub_type in ( list(_get_sub_types_of_compositional_types(content_type)) if _is_compositional_type(content_type) else [] ) + [content_type]: if _is_valid_ct(sub_type): custom_types_set.add(sub_type.strip()) return True, "Speech-acts are valid.", performatives_set, custom_types_set def _validate_protocol_buffer_schema_code_snippets( protocol_specification: ProtocolSpecification, custom_types_set: Set[str] ) -> Tuple[bool, str]: """ Evaluate whether the protobuf code snippet section of a protocol specification is valid. :param protocol_specification: a protocol specification. :param custom_types_set: set of all custom types in the protocol. :return: Boolean result, and associated message. """ if ( protocol_specification.protobuf_snippets is not None and protocol_specification.protobuf_snippets != "" ): # check all custom types are actually used in speech-acts definition for custom_type in protocol_specification.protobuf_snippets.keys(): if custom_type not in custom_types_set: return ( False, "Extra protobuf code snippet provided. Type '{}' is not used anywhere in your protocol definition.".format( custom_type, ), ) custom_types_set.remove(custom_type) # check that no custom type already used in speech-acts definition is missing if len(custom_types_set) != 0: return ( False, "No protobuf code snippet is provided for the following custom types: {}".format( custom_types_set, ), ) return True, "Protobuf code snippet section is valid." def _validate_field_existence(dialogue_config: List[str]) -> Tuple[bool, str]: """ Evaluate whether the dialogue section of a protocol specification contains the required fields. :param dialogue_config: the dialogue section of a protocol specification. :return: Boolean result, and associated message. """ # check required fields exist for required_field in DIALOGUE_SECTION_REQUIRED_FIELDS: if required_field not in dialogue_config: return ( False, "Missing required field '{}' in the dialogue section of the protocol specification.".format( required_field ), ) return True, "Dialogue section has all the required fields." def _validate_initiation( initiation: List[str], performatives_set: Set[str] ) -> Tuple[bool, str]: """ Evaluate whether the initiation field in a protocol specification is valid. :param initiation: List of initial messages of a dialogue. :param performatives_set: set of all performatives in the dialogue. :return: Boolean result, and associated message. """ # check type if not isinstance(initiation, list): return ( False, "Invalid type for initiation. Expected list. Found '{}'.".format( type(initiation) ), ) # check initiation is not empty/None if len(initiation) == 0 or initiation is None: return ( False, "At least one initial performative for this dialogue must be specified.", ) # check performatives are previously defined for performative in initiation: if performative not in performatives_set: return ( False, "Performative '{}' specified in \"initiation\" is not defined in the protocol's speech-acts.".format( performative, ), ) return True, "Initial messages are valid." def _validate_reply( reply_definition: Dict[str, List[str]], performatives_set: Set[str] ) -> Tuple[bool, str, Optional[Set[str]]]: """ Evaluate whether the reply definition in a protocol specification is valid. :param reply_definition: Reply structure of a dialogue. :param performatives_set: set of all performatives in the dialogue. :return: Boolean result, and associated message. """ # check type if not isinstance(reply_definition, dict): return ( False, "Invalid type for the reply definition. Expected dict. Found '{}'.".format( type(reply_definition) ), None, ) performatives_set_2 = performatives_set.copy() terminal_performatives_from_reply = set() for performative, replies in reply_definition.items(): # check only previously defined performatives are included in the reply definition if performative not in performatives_set_2: return ( False, "Performative '{}' specified in \"reply\" is not defined in the protocol's speech-acts.".format( performative, ), None, ) # check the type of replies if not isinstance(replies, list): return ( False, "Invalid type for replies of performative {}. Expected list. Found '{}'.".format( performative, type(replies) ), None, ) # check all replies are performatives which are previously defined in the speech-acts definition for reply in replies: if reply not in performatives_set: return ( False, "Performative '{}' in the list of replies for '{}' is not defined in speech-acts.".format( reply, performative ), None, ) performatives_set_2.remove(performative) if len(replies) == 0: terminal_performatives_from_reply.add(performative) # check all previously defined performatives are included in the reply definition if len(performatives_set_2) != 0: return ( False, "No reply is provided for the following performatives: {}".format( performatives_set_2, ), None, ) return True, "Reply structure is valid.", terminal_performatives_from_reply def _validate_termination( termination: List[str], performatives_set: Set[str], terminal_performatives_from_reply: Set[str], ) -> Tuple[bool, str]: """ Evaluate whether termination field in a protocol specification is valid. :param termination: List of terminal messages of a dialogue. :param performatives_set: set of all performatives in the dialogue. :param terminal_performatives_from_reply: terminal performatives extracted from reply structure. :return: Boolean result, and associated message. """ # check type if not isinstance(termination, list): return ( False, "Invalid type for termination. Expected list. Found '{}'.".format( type(termination) ), ) # check termination is not empty/None if len(termination) == 0 or termination is None: return ( False, "At least one terminal performative for this dialogue must be specified.", ) # check terminal performatives are previously defined for performative in termination: if performative not in performatives_set: return ( False, "Performative '{}' specified in \"termination\" is not defined in the protocol's speech-acts.".format( performative, ), ) # check that there are no repetitive performatives in termination number_of_duplicates = len(termination) - len(set(termination)) if number_of_duplicates > 0: return ( False, 'There are {} duplicate performatives in "termination".'.format( number_of_duplicates, ), ) # check terminal performatives have no replies for performative in termination: if performative not in terminal_performatives_from_reply: return ( False, 'The terminal performative \'{}\' specified in "termination" is assigned replies in "reply".'.format( performative, ), ) # check performatives with no replies are specified as terminal performatives for performative in terminal_performatives_from_reply: if performative not in termination: return ( False, "The performative '{}' has no replies but is not listed as a terminal performative in \"termination\".".format( performative, ), ) return True, "Terminal messages are valid." def _validate_roles(roles: Dict[str, Any]) -> Tuple[bool, str]: """ Evaluate whether roles field in a protocol specification is valid. :param roles: Set of roles of a dialogue. :return: Boolean result, and associated message. """ # check type if not isinstance(roles, dict): return ( False, "Invalid type for roles. Expected dict. Found '{}'.".format(type(roles)), ) # check number of roles if not 1 <= len(roles) <= 2: return ( False, "There must be either 1 or 2 roles defined in this dialogue. Found {}".format( len(roles) ), ) # check each role's format for role in roles: if not _is_valid_regex(ROLE_REGEX_PATTERN, role): return ( False, "Invalid name for role '{}'. Role names must match the following regular expression: {} ".format( role, ROLE_REGEX_PATTERN ), ) return True, "Dialogue roles are valid." def _validate_end_states(end_states: List[str]) -> Tuple[bool, str]: """ Evaluate whether end_states field in a protocol specification is valid. :param end_states: List of end states of a dialogue. :return: Boolean result, and associated message. """ # check type if not isinstance(end_states, list): return ( False, "Invalid type for roles. Expected list. Found '{}'.".format( type(end_states) ), ) # check each end_state's format for end_state in end_states: if not _is_valid_regex(END_STATE_REGEX_PATTERN, end_state): return ( False, "Invalid name for end_state '{}'. End_state names must match the following regular expression: {} ".format( end_state, END_STATE_REGEX_PATTERN ), ) return True, "Dialogue end_states are valid." def _validate_keep_terminal(keep_terminal_state_dialogues: bool) -> Tuple[bool, str]: """ Evaluate whether keep_terminal_state_dialogues field in a protocol specification is valid. :param keep_terminal_state_dialogues: the value of keep_terminal_state_dialogues. :return: Boolean result, and associated message. """ # check the type of keep_terminal_state_dialogues's value if ( type(keep_terminal_state_dialogues) # pylint: disable=unidiomatic-typecheck != bool ): return ( False, "Invalid type for keep_terminal_state_dialogues. Expected bool. Found {}.".format( type(keep_terminal_state_dialogues) ), ) return True, "Dialogue keep_terminal_state_dialogues is valid." def _validate_dialogue_section( protocol_specification: ProtocolSpecification, performatives_set: Set[str] ) -> Tuple[bool, str]: """ Evaluate whether the dialogue section of a protocol specification is valid. :param protocol_specification: a protocol specification. :param performatives_set: set of all performatives in the dialogue. :return: Boolean result, and associated message. """ if ( protocol_specification.dialogue_config != {} and protocol_specification.dialogue_config is not None ): # validate required fields exist ( result_field_existence_validation, msg_field_existence_validation, ) = _validate_field_existence( cast(List[str], protocol_specification.dialogue_config), ) if not result_field_existence_validation: return result_field_existence_validation, msg_field_existence_validation # Validate initiation result_initiation_validation, msg_initiation_validation = _validate_initiation( cast(List[str], protocol_specification.dialogue_config["initiation"]), performatives_set, ) if not result_initiation_validation: return result_initiation_validation, msg_initiation_validation # Validate reply ( result_reply_validation, msg_reply_validation, terminal_performatives_from_reply, ) = _validate_reply( cast(Dict[str, List[str]], protocol_specification.dialogue_config["reply"]), performatives_set, ) if not result_reply_validation: return result_reply_validation, msg_reply_validation # Validate termination terminal_performatives_from_reply = cast( Set[str], terminal_performatives_from_reply ) ( result_termination_validation, msg_termination_validation, ) = _validate_termination( cast(List[str], protocol_specification.dialogue_config["termination"]), performatives_set, terminal_performatives_from_reply, ) if not result_termination_validation: return result_termination_validation, msg_termination_validation # Validate roles result_roles_validation, msg_roles_validation = _validate_roles( cast(Dict[str, Any], protocol_specification.dialogue_config["roles"]) ) if not result_roles_validation: return result_roles_validation, msg_roles_validation # Validate end_state result_end_states_validation, msg_end_states_validation = _validate_end_states( cast(List[str], protocol_specification.dialogue_config["end_states"]) ) if not result_end_states_validation: return result_end_states_validation, msg_end_states_validation # Validate keep_terminal_state_dialogues ( result_keep_terminal_validation, msg_keep_terminal_validation, ) = _validate_keep_terminal( cast( bool, protocol_specification.dialogue_config["keep_terminal_state_dialogues"], ) ) if not result_keep_terminal_validation: return result_keep_terminal_validation, msg_keep_terminal_validation return True, "Dialogue section of the protocol specification is valid." def validate(protocol_specification: ProtocolSpecification) -> Tuple[bool, str]: """ Evaluate whether a protocol specification is valid. :param protocol_specification: a protocol specification. :return: Boolean result, and associated message. """ # Validate speech-acts section ( result_speech_acts_validation, msg_speech_acts_validation, performatives_set, custom_types_set, ) = _validate_speech_acts_section(protocol_specification) if not result_speech_acts_validation: return result_speech_acts_validation, msg_speech_acts_validation # Validate protocol buffer schema code snippets result_protobuf_validation, msg_protobuf_validation = _validate_protocol_buffer_schema_code_snippets(protocol_specification, custom_types_set) # type: ignore if not result_protobuf_validation: return result_protobuf_validation, msg_protobuf_validation # Validate dialogue section result_dialogue_validation, msg_dialogue_validation = _validate_dialogue_section(protocol_specification, performatives_set) # type: ignore if not result_dialogue_validation: return result_dialogue_validation, msg_dialogue_validation return True, "Protocol specification is valid." ================================================ FILE: aea/protocols/scaffold/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the support resources for the scaffold protocol.""" from aea.protocols.scaffold.message import MyScaffoldMessage from aea.protocols.scaffold.serialization import MyScaffoldSerializer MyScaffoldMessage.serializer = MyScaffoldSerializer ================================================ FILE: aea/protocols/scaffold/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the scaffold message definition.""" from enum import Enum from typing import Any from aea.configurations.base import PublicId from aea.exceptions import enforce from aea.protocols.base import Message from aea.protocols.scaffold.serialization import MyScaffoldSerializer class MyScaffoldMessage(Message): """The scaffold message class.""" protocol_id = PublicId.from_str("fetchai/scaffold:0.1.0") serializer = MyScaffoldSerializer class Performative(Enum): """Scaffold Message types.""" def __str__(self) -> str: """Get string representation.""" return str(self.value) # pragma: no cover def __init__(self, performative: Performative, **kwargs: Any) -> None: """ Initialize. :param performative: the type of message. :param kwargs: the keyword arguments. """ super().__init__(performative=performative, **kwargs) enforce( # pragma: no cover self._is_consistent(), "MyScaffoldMessage initialization inconsistent." ) def _is_consistent(self) -> bool: """Check that the data is consistent.""" try: raise NotImplementedError except (AssertionError, ValueError): return False # pragma: no cover return True # pragma: no cover ================================================ FILE: aea/protocols/scaffold/protocol.yaml ================================================ name: scaffold author: fetchai version: 0.1.0 type: protocol description: The scaffold protocol scaffolds a protocol to be implemented by the developer. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/scaffold:0.1.0 fingerprint: __init__.py: QmV67bUSsM7YErJJ3WgQb12zumYurryUn5MAJEeo4hJayu message.py: QmYHZFdU9TVJNuXVmUX8Hw6nZ2uWU9SnpjeVBR1Qg8tQrf serialization.py: QmW4HLfpGe4RXeeYZqVsoSoNJghJXVkDqq8TD4p7iqgXmG fingerprint_ignore_patterns: [] dependencies: {} ================================================ FILE: aea/protocols/scaffold/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization for the scaffold protocol.""" from aea.protocols.base import Message, Serializer class MyScaffoldSerializer(Serializer): # pragma: no cover """Serialization for the scaffold protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Decode the message. :param msg: the message object :return: the bytes # noqa: DAR202 """ raise NotImplementedError # pragma: no cover @staticmethod def decode(obj: bytes) -> Message: """ Decode the message. :param obj: the bytes object :return: the message # noqa: DAR202 """ raise NotImplementedError # pragma: no cover ================================================ FILE: aea/py.typed ================================================ # Marker file for PEP 561. The aea package uses inline types. ================================================ FILE: aea/registries/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the registries used by the framework.""" ================================================ FILE: aea/registries/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains registries.""" import copy from abc import ABC, abstractmethod from operator import itemgetter from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar, cast from aea.components.base import Component from aea.configurations.base import ComponentId, ComponentType, PublicId from aea.exceptions import AEASetupError, AEATeardownError, parse_exception from aea.helpers.logging import WithLogger, get_logger from aea.skills.base import Behaviour, Handler, Model Item = TypeVar("Item") ItemId = TypeVar("ItemId") SkillComponentType = TypeVar("SkillComponentType", Handler, Behaviour, Model) class Registry(Generic[ItemId, Item], WithLogger, ABC): """This class implements an abstract registry.""" def __init__(self, agent_name: str = "standalone") -> None: """ Initialize the registry. :param agent_name: the name of the agent """ logger = get_logger(__name__, agent_name) super().__init__(logger) @abstractmethod def register( self, item_id: ItemId, item: Item, is_dynamically_added: bool = False ) -> None: """ Register an item. :param item_id: the public id of the item. :param item: the item. :param is_dynamically_added: whether or not the item is dynamically added. :return: None :raises: ValueError if an item is already registered with that item id. """ @abstractmethod def unregister(self, item_id: ItemId) -> Optional[Item]: """ Unregister an item. :param item_id: the public id of the item. :return: the item :raises: ValueError if no item registered with that item id. """ @abstractmethod def fetch(self, item_id: ItemId) -> Optional[Item]: """ Fetch an item. :param item_id: the public id of the item. :return: the Item """ @abstractmethod def fetch_all(self) -> List[Item]: """ Fetch all the items. :return: the list of items. """ @abstractmethod def ids(self) -> Set[ItemId]: """ Return the set of all the used item ids. :return: the set of item ids. """ @abstractmethod def setup(self) -> None: """ Set up registry. :return: None """ @abstractmethod def teardown(self) -> None: """ Teardown the registry. :return: None """ class PublicIdRegistry(Generic[Item], Registry[PublicId, Item]): """ This class implement a registry whose keys are public ids. In particular, it is able to handle the case when the public id points to the 'latest' version of a package. """ __slots__ = ("_public_id_to_item",) def __init__(self) -> None: """Initialize the registry.""" super().__init__() self._public_id_to_item: Dict[PublicId, Item] = {} def register( # pylint: disable=arguments-differ,unused-argument,arguments-renamed self, public_id: PublicId, item: Item, is_dynamically_added: bool = False ) -> None: """Register an item.""" if public_id.package_version.is_latest: raise ValueError( f"Cannot register item with public id 'latest': {public_id}" ) if public_id in self._public_id_to_item: raise ValueError(f"Item already registered with item id '{public_id}'") self._public_id_to_item[public_id] = item def unregister( # pylint: disable=arguments-differ,arguments-renamed self, public_id: PublicId ) -> Item: """Unregister an item.""" if public_id not in self._public_id_to_item: raise ValueError(f"No item registered with item id '{public_id}'") item = self._public_id_to_item.pop(public_id) return item def fetch( # pylint: disable=arguments-differ,arguments-renamed self, public_id: PublicId ) -> Optional[Item]: """ Fetch an item associated with a public id. :param public_id: the public id. :return: an item, or None if the key is not present. """ if public_id.package_version.is_latest: filtered_records: List[Tuple[PublicId, Item]] = list( filter( lambda x: public_id.same_prefix(x[0]), self._public_id_to_item.items(), ) ) if len(filtered_records) == 0: return None return max(filtered_records, key=itemgetter(0))[1] return self._public_id_to_item.get(public_id, None) def fetch_all(self) -> List[Item]: """Fetch all the items.""" return list(self._public_id_to_item.values()) def ids(self) -> Set[PublicId]: """Get all the item ids.""" return set(self._public_id_to_item.keys()) def setup(self) -> None: """Set up the items.""" def teardown(self) -> None: """Tear down the items.""" class AgentComponentRegistry(Registry[ComponentId, Component]): """This class implements a simple dictionary-based registry for agent components.""" __slots__ = ("_components_by_type", "_registered_keys") def __init__(self, **kwargs: Any) -> None: """ Instantiate the registry. :param kwargs: kwargs """ super().__init__(**kwargs) self._components_by_type: Dict[ComponentType, Dict[PublicId, Component]] = {} self._registered_keys: Set[ComponentId] = set() def register( # pylint: disable=arguments-differ,unused-argument,arguments-renamed self, component_id: ComponentId, component: Component, is_dynamically_added: bool = False, ) -> None: """ Register a component. :param component_id: the id of the component. :param component: the component object. :param is_dynamically_added: whether or not the item is dynamically added. """ if component_id in self._registered_keys: raise ValueError( "Component already registered with item id '{}'".format(component_id) ) if component.component_id != component_id: raise ValueError( "Component id '{}' is different to the id '{}' specified.".format( component.component_id, component_id ) ) self._register(component_id, component) def _register(self, component_id: ComponentId, component: Component) -> None: """ Do the actual registration. :param component_id: the component id :param component: the component to register """ self._components_by_type.setdefault(component_id.component_type, {})[ component_id.public_id ] = component self._registered_keys.add(component_id) def _unregister(self, component_id: ComponentId) -> Optional[Component]: """ Do the actual unregistration. :param component_id: the component id :return: the item """ item = self._components_by_type.get(component_id.component_type, {}).pop( component_id.public_id, None ) self._registered_keys.discard(component_id) if item is not None: self.logger.debug( "Component '{}' has been removed.".format(item.component_id) ) return item def unregister( # pylint: disable=arguments-differ,arguments-renamed self, component_id: ComponentId ) -> Optional[Component]: """ Unregister a component. :param component_id: the ComponentId :return: the item """ if component_id not in self._registered_keys: raise ValueError( "No item registered with item id '{}'".format(component_id) ) return self._unregister(component_id) def fetch( # pylint: disable=arguments-differ,arguments-renamed self, component_id: ComponentId ) -> Optional[Component]: """ Fetch the component by id. :param component_id: the contract id :return: the component or None if the component is not registered """ return self._components_by_type.get(component_id.component_type, {}).get( component_id.public_id, None ) def fetch_all(self) -> List[Component]: """ Fetch all the components. :return: the list of registered components. """ return [ component for components_by_public_id in self._components_by_type.values() for component in components_by_public_id.values() ] def fetch_by_type(self, component_type: ComponentType) -> List[Component]: """ Fetch all the components by a given type.. :param component_type: a component type :return: the list of registered components of a given type. """ return list(self._components_by_type.get(component_type, {}).values()) def ids(self) -> Set[ComponentId]: """Get the item ids.""" return self._registered_keys def setup(self) -> None: """Set up the registry.""" def teardown(self) -> None: """Teardown the registry.""" class ComponentRegistry( Registry[Tuple[PublicId, str], SkillComponentType], Generic[SkillComponentType] ): """This class implements a generic registry for skill components.""" __slots__ = ("_items", "_dynamically_added") def __init__(self, **kwargs: Any) -> None: """ Instantiate the registry. :param kwargs: kwargs """ super().__init__(**kwargs) self._items: PublicIdRegistry[ Dict[str, SkillComponentType] ] = PublicIdRegistry() self._dynamically_added: Dict[PublicId, Set[str]] = {} def register( self, item_id: Tuple[PublicId, str], item: SkillComponentType, is_dynamically_added: bool = False, ) -> None: """ Register a item. :param item_id: a pair (skill id, item name). :param item: the item to register. :param is_dynamically_added: whether or not the item is dynamically added. :raises: ValueError if an item is already registered with that item id. """ skill_id = item_id[0] item_name = item_id[1] skill_items = self._items.fetch(skill_id) if skill_items is not None and item_name in skill_items.keys(): raise ValueError( f"Item already registered with skill id '{skill_id}' and name '{item_name}'" ) if skill_items is not None: self._items.unregister(skill_id) else: skill_items = {} skill_items[item_name] = item self._items.register(skill_id, skill_items) if is_dynamically_added: self._dynamically_added.setdefault(skill_id, set()).add(item_name) def unregister(self, item_id: Tuple[PublicId, str]) -> Optional[SkillComponentType]: """ Unregister a item. :param item_id: a pair (skill id, item name). :return: skill component :raises: ValueError if no item registered with that item id. """ return self._unregister_from_main_index(item_id) def _unregister_from_main_index( self, item_id: Tuple[PublicId, str] ) -> SkillComponentType: """ Unregister a item. :param item_id: a pair (skill id, item name). :return: None :raises: ValueError if no item registered with that item id. """ skill_id = item_id[0] item_name = item_id[1] name_to_item = self._items.fetch(skill_id) if name_to_item is None or item_name not in name_to_item: raise ValueError( "No item registered with component id '{}'".format(item_id) ) self.logger.debug("Unregistering item with id {}".format(item_id)) item = name_to_item.pop(item_name) if len(name_to_item) == 0: self._items.unregister(skill_id) else: self._items.unregister(skill_id) self._items.register(skill_id, name_to_item) items = self._dynamically_added.get(skill_id, None) if items is not None: items.remove(item_name) if len(items) == 0: self._dynamically_added.pop(skill_id, None) return item def fetch(self, item_id: Tuple[PublicId, str]) -> Optional[SkillComponentType]: """ Fetch an item. :param item_id: the public id of the item. :return: the Item """ skill_id = item_id[0] item_name = item_id[1] name_to_item = self._items.fetch(skill_id) if name_to_item is None: return None return name_to_item.get(item_name, None) def fetch_by_skill(self, skill_id: PublicId) -> List[SkillComponentType]: """Fetch all the items of a given skill.""" temp: Optional[Dict[str, SkillComponentType]] = self._items.fetch(skill_id) name_to_item: Dict[str, SkillComponentType] = {} if temp is None else temp return list(name_to_item.values()) def fetch_all(self) -> List[SkillComponentType]: """Fetch all the items.""" return [item for items in self._items.fetch_all() for item in items.values()] def unregister_by_skill(self, skill_id: PublicId) -> None: """Unregister all the components by skill.""" if skill_id not in self._items.ids(): raise ValueError( "No component of skill {} present in the registry.".format(skill_id) ) self._items.unregister(skill_id) self._dynamically_added.pop(skill_id, None) def ids(self) -> Set[Tuple[PublicId, str]]: """Get the item ids.""" result: Set[Tuple[PublicId, str]] = set() for skill_id in self._items.ids(): name_to_item = cast( Dict[str, SkillComponentType], self._items.fetch(skill_id) ) for name, _ in name_to_item.items(): result.add((skill_id, name)) return result def setup(self) -> None: """Set up the items in the registry.""" for item in self.fetch_all(): if item.context.is_active: self.logger.debug( "Calling setup() of component {} of skill {}".format( item.name, item.skill_id ) ) try: item.setup() except Exception as e: # pragma: nocover # pylint: disable=broad-except e_str = parse_exception(e) e_str = f"An error occurred while setting up item {item.skill_id}/{type(item).__name__}:\n{e_str}" raise AEASetupError(e_str) else: self.logger.debug( "Ignoring setup() of component {} of skill {}, because the skill is not active.".format( item.name, item.skill_id ) ) def teardown(self) -> None: """Teardown the registry.""" for name_to_items in self._items.fetch_all(): for _, item in name_to_items.items(): self.logger.debug( "Calling teardown() of component {} of skill {}".format( item.name, item.skill_id ) ) try: item.teardown() except Exception as e: # pragma: nocover # pylint: disable=broad-except e_str = parse_exception(e) e_str = f"An error occurred while tearing down item {item.skill_id}/{type(item).__name__}:\n{str(e_str)}" e = AEATeardownError(e_str) self.logger.error(str(e)) _dynamically_added = copy.deepcopy(self._dynamically_added) for skill_id, items_names in _dynamically_added.items(): for item_name in items_names: self.unregister((skill_id, item_name)) class HandlerRegistry(ComponentRegistry[Handler]): """This class implements the handlers registry.""" __slots__ = ("_items_by_protocol_and_skill",) def __init__(self, **kwargs: Any) -> None: """ Instantiate the registry. :param kwargs: kwargs """ super().__init__(**kwargs) # nested public id registries; one for protocol ids, one for skill ids self._items_by_protocol_and_skill = PublicIdRegistry[ PublicIdRegistry[Handler] ]() def register( self, item_id: Tuple[PublicId, str], item: Handler, is_dynamically_added: bool = False, ) -> None: """ Register a handler. :param item_id: the item id. :param item: the handler. :param is_dynamically_added: whether or not the item is dynamically added. :raises ValueError: if the protocol is None, or an item with pair (skill_id, protocol_id_ already exists. """ skill_id = item_id[0] protocol_id = item.SUPPORTED_PROTOCOL if protocol_id is None: raise ValueError( "Please specify a supported protocol for handler class '{}'".format( item.__class__.__name__ ) ) protocol_handlers_by_skill = self._items_by_protocol_and_skill.fetch( protocol_id ) if ( protocol_handlers_by_skill is not None and skill_id in protocol_handlers_by_skill.ids() ): raise ValueError( "A handler already registered with pair of protocol id {} and skill id {}".format( protocol_id, skill_id ) ) if protocol_handlers_by_skill is None: # registry from skill ids to handlers. new_registry: PublicIdRegistry = PublicIdRegistry() self._items_by_protocol_and_skill.register(protocol_id, new_registry) registry = cast(Registry, self._items_by_protocol_and_skill.fetch(protocol_id)) registry.register(skill_id, item) super().register(item_id, item, is_dynamically_added=is_dynamically_added) def unregister(self, item_id: Tuple[PublicId, str]) -> Handler: """ Unregister a item. :param item_id: a pair (skill id, item name). :return: the unregistered handler :raises: ValueError if no item is registered with that item id. """ skill_id = item_id[0] handler = super()._unregister_from_main_index(item_id) # remove from index by protocol and skill protocol_id = cast(PublicId, handler.SUPPORTED_PROTOCOL) protocol_handlers_by_skill = cast( PublicIdRegistry, self._items_by_protocol_and_skill.fetch(protocol_id) ) protocol_handlers_by_skill.unregister(skill_id) if len(protocol_handlers_by_skill.ids()) == 0: self._items_by_protocol_and_skill.unregister(protocol_id) return handler def unregister_by_skill(self, skill_id: PublicId) -> None: """Unregister all the components by skill.""" # unregister from the main index. if skill_id not in self._items.ids(): raise ValueError( "No component of skill {} present in the registry.".format(skill_id) ) self._dynamically_added.pop(skill_id, None) handlers = cast(Dict[str, Handler], self._items.fetch(skill_id)).values() self._items.unregister(skill_id) # unregister from the protocol-skill index for handler in handlers: protocol_id = cast(PublicId, handler.SUPPORTED_PROTOCOL) if protocol_id in self._items_by_protocol_and_skill.ids(): skill_id_to_handler = cast( PublicIdRegistry, self._items_by_protocol_and_skill.fetch(protocol_id), ) skill_id_to_handler.unregister(skill_id) def fetch_by_protocol(self, protocol_id: PublicId) -> List[Handler]: """ Fetch the handler by the pair protocol id and skill id. :param protocol_id: the protocol id :return: the handlers registered for the protocol_id and skill_id """ if protocol_id not in self._items_by_protocol_and_skill.ids(): return [] protocol_handlers_by_skill = cast( PublicIdRegistry, self._items_by_protocol_and_skill.fetch(protocol_id) ) handlers = [ cast(Handler, protocol_handlers_by_skill.fetch(skill_id)) for skill_id in protocol_handlers_by_skill.ids() ] return handlers def fetch_by_protocol_and_skill( self, protocol_id: PublicId, skill_id: PublicId ) -> Optional[Handler]: """ Fetch the handler by the pair protocol id and skill id. :param protocol_id: the protocol id :param skill_id: the skill id. :return: the handlers registered for the protocol_id and skill_id """ if protocol_id not in self._items_by_protocol_and_skill.ids(): return None protocols_by_skill_id = cast( PublicIdRegistry, self._items_by_protocol_and_skill.fetch(protocol_id) ) if skill_id not in protocols_by_skill_id.ids(): return None return protocols_by_skill_id.fetch(skill_id) ================================================ FILE: aea/registries/filter.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains registries.""" from typing import List, Optional from aea.configurations.base import PublicId from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.helpers.logging import WithLogger, get_logger from aea.protocols.base import Message from aea.registries.resources import Resources from aea.skills.base import Behaviour, Handler class Filter(WithLogger): """This class implements the filter of an AEA.""" def __init__( self, resources: Resources, decision_maker_out_queue: AsyncFriendlyQueue ) -> None: """ Instantiate the filter. :param resources: the resources :param decision_maker_out_queue: the decision maker queue """ logger = get_logger(__name__, resources.agent_name) WithLogger.__init__(self, logger=logger) self._resources = resources self._decision_maker_out_queue = decision_maker_out_queue @property def resources(self) -> Resources: """Get resources.""" return self._resources @property def decision_maker_out_queue(self) -> AsyncFriendlyQueue: """Get decision maker (out) queue.""" return self._decision_maker_out_queue def get_active_handlers( self, protocol_id: PublicId, skill_id: Optional[PublicId] = None ) -> List[Handler]: """ Get active handlers based on protocol id and optional skill id. :param protocol_id: the protocol id :param skill_id: the skill id :return: the list of handlers currently active """ if skill_id is not None: handler = self.resources.get_handler(protocol_id, skill_id) active_handlers = ( [] if handler is None or not handler.context.is_active else [handler] ) else: handlers = self.resources.get_handlers(protocol_id) active_handlers = list( filter(lambda handler: handler.context.is_active, handlers) ) return active_handlers def get_active_behaviours(self) -> List[Behaviour]: """ Get the active behaviours. :return: the list of behaviours currently active """ behaviours = self.resources.get_all_behaviours() active_behaviour = list( filter(lambda b: b.context.is_active and not b.is_done(), behaviours) ) return active_behaviour def handle_new_handlers_and_behaviours(self) -> None: """Handle the messages from the decision maker.""" self._handle_new_behaviours() self._handle_new_handlers() async def get_internal_message(self) -> Optional[Message]: """Get a message from decision_maker_out_queue.""" return await self.decision_maker_out_queue.async_get() def handle_internal_message(self, internal_message: Optional[Message]) -> None: """Handle internal message.""" if internal_message is None: self.logger.warning("Got 'None' while processing internal messages.") return self._handle_internal_message(internal_message) def _handle_new_behaviours(self) -> None: """Register new behaviours added to skills.""" for skill in self.resources.get_all_skills(): while not skill.skill_context.new_behaviours.empty(): new_behaviour = skill.skill_context.new_behaviours.get() try: self.resources.behaviour_registry.register( (skill.skill_context.skill_id, new_behaviour.name), new_behaviour, is_dynamically_added=True, ) except ValueError as e: self.logger.warning( "Error when trying to add a new behaviour: {}".format(str(e)) ) def _handle_new_handlers(self) -> None: """Register new handlers added to skills.""" for skill in self.resources.get_all_skills(): while not skill.skill_context.new_handlers.empty(): new_handler = skill.skill_context.new_handlers.get() try: self.resources.handler_registry.register( (skill.skill_context.skill_id, new_handler.name), new_handler, is_dynamically_added=True, ) except ValueError as e: self.logger.warning( "Error when trying to add a new handler: {}".format(str(e)) ) def _handle_internal_message(self, message: Message) -> None: """Handle message from the Decision Maker.""" try: skill_id = PublicId.from_str(message.to) except ValueError: self.logger.warning( "Invalid public id as destination={}".format(message.to) ) return handler = self.resources.handler_registry.fetch_by_protocol_and_skill( message.protocol_id, skill_id, ) if handler is not None: self.logger.debug( "Calling handler {} of skill {}".format(type(handler), skill_id) ) handler.handle(message) else: self.logger.warning( "No internal handler fetched for skill_id={}".format(skill_id) ) ================================================ FILE: aea/registries/resources.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the resources class.""" from contextlib import suppress from typing import Dict, List, Optional, cast from aea.components.base import Component from aea.configurations.base import ComponentId, ComponentType, PublicId from aea.connections.base import Connection from aea.contracts.base import Contract from aea.protocols.base import Protocol from aea.registries.base import ( AgentComponentRegistry, ComponentRegistry, HandlerRegistry, Registry, ) from aea.skills.base import Behaviour, Handler, Model, Skill class Resources: """This class implements the object that holds the resources of an AEA.""" __slots__ = ( "_agent_name", "_component_registry", "_specification_to_protocol_id", "_handler_registry", "_behaviour_registry", "_model_registry", "_registries", ) def __init__(self, agent_name: str = "standalone") -> None: """ Instantiate the resources. :param agent_name: the name of the agent """ self._agent_name = agent_name self._component_registry = AgentComponentRegistry(agent_name=agent_name) self._specification_to_protocol_id: Dict[PublicId, PublicId] = {} self._handler_registry = HandlerRegistry(agent_name=agent_name) self._behaviour_registry = ComponentRegistry[Behaviour](agent_name=agent_name) self._model_registry = ComponentRegistry[Model](agent_name=agent_name) self._registries = [ self._component_registry, self._handler_registry, self._behaviour_registry, self._model_registry, ] # type: List[Registry] @property def agent_name(self) -> str: """Get the agent name.""" return self._agent_name @property def component_registry(self) -> AgentComponentRegistry: """Get the agent component registry.""" return self._component_registry @property def behaviour_registry(self) -> ComponentRegistry[Behaviour]: """Get the behaviour registry.""" return self._behaviour_registry @property def handler_registry(self) -> HandlerRegistry: """Get the handler registry.""" return self._handler_registry @property def model_registry(self) -> ComponentRegistry[Model]: """Get the model registry.""" return self._model_registry def add_component(self, component: Component) -> None: """Add a component to resources.""" if component.component_type == ComponentType.PROTOCOL: self.add_protocol(cast(Protocol, component)) elif component.component_type == ComponentType.SKILL: self.add_skill(cast(Skill, component)) elif component.component_type == ComponentType.CONTRACT: self.add_contract(cast(Contract, component)) elif component.component_type == ComponentType.CONNECTION: self.add_connection(cast(Connection, component)) else: raise ValueError( "Component type {} not supported.".format( component.component_type.value ) ) def add_protocol(self, protocol: Protocol) -> None: """ Add a protocol to the set of resources. :param protocol: a protocol """ self._component_registry.register(protocol.component_id, protocol) self._specification_to_protocol_id[ protocol.protocol_specification_id ] = protocol.public_id def get_protocol(self, protocol_id: PublicId) -> Optional[Protocol]: """ Get protocol for given protocol id. :param protocol_id: the protocol id :return: a matching protocol, if present, else None """ protocol = self._component_registry.fetch( ComponentId(ComponentType.PROTOCOL, protocol_id) ) return cast(Protocol, protocol) def get_protocol_by_specification_id( self, protocol_specification_id: PublicId ) -> Optional[Protocol]: """ Get protocol for given protocol_specification_id. :param protocol_specification_id: the protocol id :return: a matching protocol, if present, else None """ protocol_id = self._specification_to_protocol_id.get( protocol_specification_id, None ) if protocol_id is None: return None return self.get_protocol(protocol_id) def get_all_protocols(self) -> List[Protocol]: """ Get the list of all the protocols. :return: the list of protocols. """ protocols = self._component_registry.fetch_by_type(ComponentType.PROTOCOL) return cast(List[Protocol], protocols) def remove_protocol(self, protocol_id: PublicId) -> None: """ Remove a protocol from the set of resources. :param protocol_id: the protocol id for the protocol to be removed. """ protocol = cast( Optional[Protocol], self._component_registry.unregister( ComponentId(ComponentType.PROTOCOL, protocol_id) ), ) if protocol is not None: self._specification_to_protocol_id.pop(protocol.protocol_specification_id) def add_contract(self, contract: Contract) -> None: """ Add a contract to the set of resources. :param contract: a contract """ self._component_registry.register(contract.component_id, contract) def get_contract(self, contract_id: PublicId) -> Optional[Contract]: """ Get contract for given contract id. :param contract_id: the contract id :return: a matching contract, if present, else None """ contract = self._component_registry.fetch( ComponentId(ComponentType.CONTRACT, contract_id) ) return cast(Contract, contract) def get_all_contracts(self) -> List[Contract]: """ Get the list of all the contracts. :return: the list of contracts. """ contracts = self._component_registry.fetch_by_type(ComponentType.CONTRACT) return cast(List[Contract], contracts) def remove_contract(self, contract_id: PublicId) -> None: """ Remove a contract from the set of resources. :param contract_id: the contract id for the contract to be removed. """ self._component_registry.unregister( ComponentId(ComponentType.CONTRACT, contract_id) ) def add_connection(self, connection: Connection) -> None: """ Add a connection to the set of resources. :param connection: a connection """ self._component_registry.register(connection.component_id, connection) def get_connection(self, connection_id: PublicId) -> Optional[Connection]: """ Get connection for given connection id. :param connection_id: the connection id :return: a matching connection, if present, else None """ connection = self._component_registry.fetch( ComponentId(ComponentType.CONNECTION, connection_id) ) return cast(Connection, connection) def get_all_connections(self) -> List[Connection]: """ Get the list of all the connections. :return: the list of connections. """ connections = self._component_registry.fetch_by_type(ComponentType.CONNECTION) return cast(List[Connection], connections) def remove_connection(self, connection_id: PublicId) -> None: """ Remove a connection from the set of resources. :param connection_id: the connection id for the connection to be removed. """ self._component_registry.unregister( ComponentId(ComponentType.CONNECTION, connection_id) ) def add_skill(self, skill: Skill) -> None: """ Add a skill to the set of resources. :param skill: a skill """ self._component_registry.register(skill.component_id, skill) if skill.handlers is not None: for handler in skill.handlers.values(): self._handler_registry.register( (skill.public_id, handler.name), handler ) if skill.behaviours is not None: for behaviour in skill.behaviours.values(): self._behaviour_registry.register( (skill.public_id, behaviour.name), behaviour ) if skill.models is not None: for model in skill.models.values(): self._model_registry.register((skill.public_id, model.name), model) def get_skill(self, skill_id: PublicId) -> Optional[Skill]: """ Get the skill for a given skill id. :param skill_id: the skill id :return: a matching skill, if present, else None """ skill = self._component_registry.fetch( ComponentId(ComponentType.SKILL, skill_id) ) return cast(Skill, skill) def get_all_skills(self) -> List[Skill]: """ Get the list of all the skills. :return: the list of skills. """ skills = self._component_registry.fetch_by_type(ComponentType.SKILL) return cast(List[Skill], skills) def remove_skill(self, skill_id: PublicId) -> None: """ Remove a skill from the set of resources. :param skill_id: the skill id for the skill to be removed. """ self._component_registry.unregister(ComponentId(ComponentType.SKILL, skill_id)) with suppress(ValueError): self._handler_registry.unregister_by_skill(skill_id) with suppress(ValueError): self._behaviour_registry.unregister_by_skill(skill_id) with suppress(ValueError): self._model_registry.unregister_by_skill(skill_id) def get_handler( self, protocol_id: PublicId, skill_id: PublicId ) -> Optional[Handler]: """ Get a specific handler. :param protocol_id: the protocol id the handler is handling :param skill_id: the skill id of the handler's skill :return: the handler """ handler = self._handler_registry.fetch_by_protocol_and_skill( protocol_id, skill_id ) return handler def get_handlers(self, protocol_id: PublicId) -> List[Handler]: """ Get all handlers for a given protocol. :param protocol_id: the protocol id the handler is handling :return: the list of handlers matching the protocol """ handlers = self._handler_registry.fetch_by_protocol(protocol_id) return handlers def get_all_handlers(self) -> List[Handler]: """ Get all handlers from all skills. :return: the list of handlers """ handlers = self._handler_registry.fetch_all() return handlers def get_behaviour( self, skill_id: PublicId, behaviour_name: str ) -> Optional[Behaviour]: """ Get a specific behaviours for a given skill. :param skill_id: the skill id :param behaviour_name: the behaviour name :return: the behaviour, if it is present, else None """ behaviour = self._behaviour_registry.fetch((skill_id, behaviour_name)) return behaviour def get_behaviours(self, skill_id: PublicId) -> List[Behaviour]: """ Get all behaviours for a given skill. :param skill_id: the skill id :return: the list of behaviours of the skill """ behaviours = self._behaviour_registry.fetch_by_skill( skill_id ) # type: List[Behaviour] return behaviours def get_all_behaviours(self) -> List[Behaviour]: """ Get all behaviours from all skills. :return: the list of all behaviours """ behaviours = self._behaviour_registry.fetch_all() return behaviours def setup(self) -> None: """ Set up the resources. Calls setup on all resources. """ for r in self._registries: r.setup() def teardown(self) -> None: """ Teardown the resources. Calls teardown on all resources. """ for r in self._registries: r.teardown() ================================================ FILE: aea/runner.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of AEA multiple instances runner.""" import logging from asyncio.events import AbstractEventLoop from typing import Dict, Sequence, Type from aea.aea import AEA from aea.helpers.multiple_executor import ( AbstractExecutorTask, AbstractMultipleExecutor, AbstractMultipleRunner, AsyncExecutor, ExecutorExceptionPolicies, TaskAwaitable, ThreadExecutor, ) from aea.runtime import AsyncRuntime _default_logger = logging.getLogger(__name__) class AEAInstanceTask(AbstractExecutorTask): """Task to run agent instance.""" def __init__(self, agent: AEA) -> None: """ Init aea instance task. :param agent: AEA instance to run within task. """ self._agent = agent super().__init__() @property def id(self) -> str: """Return agent name.""" return self._agent.name def start(self) -> None: # type: ignore """Start task.""" try: self._agent.start() except BaseException: _default_logger.exception("Exceptions raised in runner task.") raise def stop(self) -> None: """Stop task.""" self._agent.runtime.stop() def create_async_task(self, loop: AbstractEventLoop) -> TaskAwaitable: """ Return asyncio Task for task run in asyncio loop. :param loop: abstract event loop :return: task to run runtime """ self._agent.runtime.set_loop(loop) if not isinstance(self._agent.runtime, AsyncRuntime): # pragma: nocover raise ValueError( "Agent runtime is not async compatible. Please use runtime_mode=async" ) return loop.create_task(self._agent.runtime.start_and_wait_completed()) # type: ignore class AEARunner(AbstractMultipleRunner): """Run multiple AEA instances.""" SUPPORTED_MODES: Dict[str, Type[AbstractMultipleExecutor]] = { "threaded": ThreadExecutor, "async": AsyncExecutor, } def __init__( self, agents: Sequence[AEA], mode: str, fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies.propagate, ) -> None: """ Init AEARunner. :param agents: sequence of AEA instances to run. :param mode: executor name to use. :param fail_policy: one of ExecutorExceptionPolicies to be used with Executor """ self._agents = agents super().__init__(mode=mode, fail_policy=fail_policy) def _make_tasks(self) -> Sequence[AbstractExecutorTask]: """Make tasks to run with executor.""" return [AEAInstanceTask(agent) for agent in self._agents] ================================================ FILE: aea/runtime.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of runtime for economic agent (AEA).""" import asyncio from asyncio.events import AbstractEventLoop from concurrent.futures._base import CancelledError from contextlib import suppress from enum import Enum from typing import Dict, Optional, Type, cast from aea.abstract_agent import AbstractAgent from aea.agent_loop import ( AgentLoopStates, AsyncAgentLoop, AsyncState, BaseAgentLoop, SyncAgentLoop, ) from aea.connections.base import ConnectionStates from aea.decision_maker.base import DecisionMaker, DecisionMakerHandler from aea.exceptions import _StopRuntime from aea.helpers.async_utils import Runnable from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.helpers.logging import WithLogger, get_logger from aea.helpers.storage.generic_storage import Storage from aea.multiplexer import AsyncMultiplexer from aea.skills.tasks import ProcessTaskManager, TaskManager, ThreadedTaskManager class RuntimeStates(Enum): """Runtime states.""" starting = "starting" running = "running" stopping = "stopping" stopped = "stopped" error = "error" class BaseRuntime(Runnable, WithLogger): """Abstract runtime class to create implementations.""" RUN_LOOPS: Dict[str, Type[BaseAgentLoop]] = { "async": AsyncAgentLoop, "sync": SyncAgentLoop, } DEFAULT_RUN_LOOP: str = "async" TASKMANAGERS = {"threaded": ThreadedTaskManager, "multiprocess": ProcessTaskManager} DEFAULT_TASKMANAGER = "threaded" def __init__( self, agent: AbstractAgent, multiplexer_options: Dict, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, threaded: bool = False, task_manager_mode: Optional[str] = None, ) -> None: """ Init runtime. :param agent: Agent to run. :param multiplexer_options: options for the multiplexer. :param loop_mode: agent main loop mode. :param loop: optional event loop. if not provided a new one will be created. :param threaded: if True, run in threaded mode, else async :param task_manager_mode: mode of the task manager. """ Runnable.__init__(self, threaded=threaded, loop=loop if not threaded else None) logger = get_logger(__name__, agent.name) WithLogger.__init__(self, logger=logger) self._agent: AbstractAgent = agent self._state: AsyncState = AsyncState(RuntimeStates.stopped, RuntimeStates) self._state.add_callback(self._log_runtime_state) self._multiplexer: AsyncMultiplexer = self._get_multiplexer_instance( multiplexer_options ) self._task_manager_mode = task_manager_mode or self.DEFAULT_TASKMANAGER self._task_manager = self._get_taskmanager_instance() self._decision_maker: Optional[DecisionMaker] = None self._storage: Optional[Storage] = self._get_storage(agent) self._loop_mode = loop_mode or self.DEFAULT_RUN_LOOP self._agent_loop: BaseAgentLoop = self._get_agent_loop_instance(self._loop_mode) def _log_runtime_state(self, state: RuntimeStates) -> None: """Log a runtime state changed.""" self.logger.debug(f"[{self._agent.name}]: Runtime state changed to {state}.") def _get_taskmanager_instance(self) -> TaskManager: """Get taskmanager instance.""" if self._task_manager_mode not in self.TASKMANAGERS: raise ValueError( # pragma: nocover f"Task manager mode `{self._task_manager_mode} is not supported. valid are: `{list(self.TASKMANAGERS.keys())}`" ) cls = self.TASKMANAGERS[self._task_manager_mode] return cls() def _get_multiplexer_instance( self, multiplexer_options: Dict, threaded: bool = False ) -> AsyncMultiplexer: """Create multiplexer instance.""" loop: Optional[AbstractEventLoop] = None if not threaded: loop = self.loop return AsyncMultiplexer( loop=loop, threaded=threaded, agent_name=self._agent.name, connections=multiplexer_options["connections"], exception_policy=multiplexer_options.get( "connection_exception_policy", ExceptionPolicyEnum.propagate ), default_routing=multiplexer_options.get("default_routing"), default_connection=multiplexer_options.get("default_connection"), protocols=multiplexer_options.get("protocols", []), ) @staticmethod def _get_storage(agent: AbstractAgent) -> Optional[Storage]: """Get storage instance if storage_uri provided.""" if agent.storage_uri: # threaded has to be always True, cause synchronous operations are supported return Storage(agent.storage_uri, threaded=True) return None # pragma: nocover def _get_agent_loop_instance(self, loop_mode: str) -> BaseAgentLoop: """ Construct agent loop instance. :param: loop_mode: str. :return: AgentLoop instance """ loop_cls = self._get_agent_loop_class(loop_mode) return loop_cls(self._agent) def _get_agent_loop_class(self, loop_mode: str) -> Type[BaseAgentLoop]: """ Get agent loop class based on loop mode. :param: loop_mode: str. :return: AgentLoop class """ if loop_mode not in self.RUN_LOOPS: # pragma: nocover raise ValueError( f"Loop `{loop_mode} is not supported. valid are: `{list(self.RUN_LOOPS.keys())}`" ) return self.RUN_LOOPS[loop_mode] @property def storage(self) -> Optional[Storage]: """Get optional storage.""" return self._storage @property def loop_mode(self) -> str: # pragma: nocover """Get current loop mode.""" return self._loop_mode @property def task_manager(self) -> TaskManager: """Get the task manager.""" return self._task_manager @property def loop(self) -> Optional[AbstractEventLoop]: """Get event loop.""" return self._loop @property def agent_loop(self) -> BaseAgentLoop: """Get the agent loop.""" return self._agent_loop @property def multiplexer(self) -> AsyncMultiplexer: """Get multiplexer.""" return self._multiplexer @property def is_running(self) -> bool: """Get running state of the runtime.""" return self._state.get() == RuntimeStates.running @property def is_stopped(self) -> bool: # pragma: nocover """Get stopped state of the runtime.""" return self._state.get() in [RuntimeStates.stopped] @property def state(self) -> RuntimeStates: # pragma: nocover """ Get runtime state. :return: RuntimeStates """ return cast(RuntimeStates, self._state.get()) @property def decision_maker(self) -> DecisionMaker: """Return decision maker if set.""" if self._decision_maker is None: # pragma: nocover raise ValueError("call `set_decision_maker` first!") return self._decision_maker def _set_task(self) -> None: """Set task.""" if self._loop is None: raise ValueError("Loop not set!") # pragma: nocover self._task = self._loop.create_task(self._run_wrapper()) def set_decision_maker(self, decision_maker_handler: DecisionMakerHandler) -> None: """Set decision maker with handler provided.""" self._decision_maker = DecisionMaker( decision_maker_handler=decision_maker_handler ) def _teardown(self) -> None: """Tear down runtime.""" self.logger.debug("[{}]: Runtime teardown...".format(self._agent.name)) if self._decision_maker is not None: # pragma: nocover self.decision_maker.stop() self.task_manager.stop() self.logger.debug("[{}]: Calling teardown method...".format(self._agent.name)) self._agent.teardown() self.logger.debug("[{}]: Runtime teardown completed".format(self._agent.name)) def set_loop(self, loop: AbstractEventLoop) -> None: """ Set event loop to be used. :param loop: event loop to use. """ self._loop = loop asyncio.set_event_loop(self._loop) class AsyncRuntime(BaseRuntime): """Asynchronous runtime: uses asyncio loop for multiplexer and async agent main loop.""" AGENT_LOOP_STARTED_TIMEOUT: float = 5 def __init__( self, agent: AbstractAgent, multiplexer_options: Dict, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, threaded: bool = False, task_manager_mode: Optional[str] = None, ) -> None: """ Init runtime. :param agent: Agent to run. :param multiplexer_options: options for the multiplexer. :param loop_mode: agent main loop mode. :param loop: optional event loop. if not provided a new one will be created. :param threaded: if True, run in threaded mode, else async :param task_manager_mode: mode of the task manager. """ super().__init__( agent=agent, multiplexer_options=multiplexer_options, loop_mode=loop_mode, loop=loop, threaded=threaded, task_manager_mode=task_manager_mode, ) self._task: Optional[asyncio.Task] = None def set_loop(self, loop: AbstractEventLoop) -> None: """ Set event loop to be used. :param loop: event loop to use. """ BaseRuntime.set_loop(self, loop) async def run(self) -> None: """ Start runtime task. Starts multiplexer and agent loop. """ terminal_state = RuntimeStates.error try: await self.run_runtime() except _StopRuntime as e: self._state.set(RuntimeStates.stopping) terminal_state = RuntimeStates.stopped if e.reraise: raise e.reraise except (asyncio.CancelledError, CancelledError, KeyboardInterrupt): self._state.set(RuntimeStates.stopping) terminal_state = RuntimeStates.stopped finally: await self.stop_runtime() self._state.set(terminal_state) async def stop_runtime(self) -> None: """ Stop runtime coroutine. Stop main loop. Tear down the agent.. Disconnect multiplexer. """ self.agent_loop.stop() with suppress(_StopRuntime): await self.agent_loop.wait_completed() self._teardown() if self._storage is not None: self._storage.stop() await self._storage.wait_completed() self.multiplexer.stop() await self.multiplexer.wait_completed() self.logger.debug("Runtime loop stopped!") async def run_runtime(self) -> None: """Run runtime which means start agent loop, multiplexer and storage.""" self._state.set(RuntimeStates.starting) await asyncio.gather( self._start_multiplexer(), self._start_agent_loop(), self._start_storage() ) async def _start_storage(self) -> None: """Start storage component asynchronously.""" if self._storage is not None: self._storage.start() await self._storage.wait_completed() async def _start_multiplexer(self) -> None: """Call multiplexer connect asynchronous way.""" if not self._loop: # pragma: nocover raise ValueError("no loop is set for runtime.") self.multiplexer.set_loop(self._loop) self.multiplexer.start() await self.multiplexer.wait_completed() async def _start_agent_loop(self) -> None: """Start agent main loop asynchronous way.""" self.logger.debug("[{}] Runtime started".format(self._agent.name)) await self.multiplexer.connection_status.wait(ConnectionStates.connected) self.logger.debug("[{}] Multiplexer connected.".format(self._agent.name)) if self.storage: await self.storage.wait_connected() self.logger.debug("[{}] Storage connected.".format(self._agent.name)) self.task_manager.start() if self._decision_maker is not None: # pragma: nocover self.decision_maker.start() self.logger.debug("[{}] Calling setup method...".format(self._agent.name)) self._agent.setup() self.logger.debug("[{}] Run main loop...".format(self._agent.name)) self.agent_loop.start() await asyncio.wait_for( self.agent_loop.wait_state(AgentLoopStates.started), timeout=self.AGENT_LOOP_STARTED_TIMEOUT, ) self._state.set(RuntimeStates.running) try: await self.agent_loop.wait_completed() except asyncio.CancelledError: self.agent_loop.stop() await self.agent_loop.wait_completed() raise class ThreadedRuntime(AsyncRuntime): """Run agent and multiplexer in different threads with own asyncio loops.""" def _get_multiplexer_instance( self, multiplexer_options: Dict, threaded: bool = True ) -> AsyncMultiplexer: """Create multiplexer instance.""" return super()._get_multiplexer_instance( multiplexer_options=multiplexer_options, threaded=threaded ) ================================================ FILE: aea/skills/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the skills supported by the framework.""" ================================================ FILE: aea/skills/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the base classes for the skills.""" import datetime import inspect import logging import queue import re import types from abc import ABC, abstractmethod from copy import copy from logging import Logger from pathlib import Path from queue import Queue from types import SimpleNamespace from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union, cast from aea.common import Address from aea.components.base import Component, load_aea_package from aea.configurations.base import ( ComponentType, PublicId, SkillComponentConfiguration, SkillConfig, ) from aea.configurations.loader import load_component_configuration from aea.context.base import AgentContext from aea.exceptions import ( AEAActException, AEAComponentLoadException, AEAHandleException, AEAInstantiationException, _StopRuntime, enforce, parse_exception, ) from aea.helpers.base import _get_aea_logger_name_prefix, load_module from aea.helpers.logging import AgentLoggerAdapter from aea.helpers.storage.generic_storage import Storage from aea.mail.base import Envelope, EnvelopeContext from aea.multiplexer import MultiplexerStatus, OutBox from aea.protocols.base import Message from aea.skills.tasks import TaskManager _default_logger = logging.getLogger(__name__) class SkillContext: """This class implements the context of a skill.""" def __init__( self, agent_context: Optional[AgentContext] = None, skill: Optional["Skill"] = None, ) -> None: """ Initialize a skill context. :param agent_context: the agent context. :param skill: the skill. """ self._agent_context = agent_context # type: Optional[AgentContext] self._in_queue = Queue() # type: Queue self._skill = skill # type: Optional[Skill] self._is_active = True # type: bool self._new_behaviours_queue = queue.Queue() # type: Queue self._new_handlers_queue = queue.Queue() # type: Queue self._logger: Optional[Logger] = None @property def logger(self) -> Logger: """Get the logger.""" if self._logger is None: return _default_logger return self._logger @logger.setter def logger(self, logger_: Logger) -> None: """Set the logger.""" self._logger = logger_ def _get_agent_context(self) -> AgentContext: """Get the agent context.""" if self._agent_context is None: # pragma: nocover raise ValueError("Agent context not set yet.") return self._agent_context def set_agent_context(self, agent_context: AgentContext) -> None: """Set the agent context.""" self._agent_context = agent_context @property def shared_state(self) -> Dict[str, Any]: """Get the shared state dictionary.""" return self._get_agent_context().shared_state @property def agent_name(self) -> str: """Get agent name.""" return self._get_agent_context().agent_name @property def skill_id(self) -> PublicId: """Get the skill id of the skill context.""" if self._skill is None: raise ValueError("Skill not set yet.") # pragma: nocover return self._skill.configuration.public_id @property def is_active(self) -> bool: """Get the status of the skill (active/not active).""" return self._is_active @is_active.setter def is_active(self, value: bool) -> None: """Set the status of the skill (active/not active).""" self._is_active = value self.logger.debug( "New status of skill {}: is_active={}".format( self.skill_id, self._is_active ) ) @property def new_behaviours(self) -> "Queue[Behaviour]": """ Queue for the new behaviours. This queue can be used to send messages to the framework to request the registration of a behaviour. :return: the queue of new behaviours. """ return self._new_behaviours_queue @property def new_handlers(self) -> "Queue[Handler]": """ Queue for the new handlers. This queue can be used to send messages to the framework to request the registration of a handler. :return: the queue of new handlers. """ return self._new_handlers_queue @property def agent_addresses(self) -> Dict[str, str]: """Get addresses.""" return self._get_agent_context().addresses @property def agent_address(self) -> str: """Get address.""" return self._get_agent_context().address @property def public_key(self) -> str: """Get public key.""" return self._get_agent_context().public_key @property def public_keys(self) -> Dict[str, str]: """Get public keys.""" return self._get_agent_context().public_keys @property def connection_status(self) -> MultiplexerStatus: """Get connection status.""" return self._get_agent_context().connection_status @property def outbox(self) -> OutBox: """Get outbox.""" return self._get_agent_context().outbox @property def storage(self) -> Optional[Storage]: """Get optional storage for agent.""" return self._get_agent_context().storage @property def message_in_queue(self) -> Queue: """Get message in queue.""" return self._in_queue @property def decision_maker_message_queue(self) -> Queue: """Get message queue of decision maker.""" return self._get_agent_context().decision_maker_message_queue @property def decision_maker_handler_context(self) -> SimpleNamespace: """Get decision maker handler context.""" return cast( SimpleNamespace, self._get_agent_context().decision_maker_handler_context ) @property def task_manager(self) -> TaskManager: """Get behaviours of the skill.""" if self._skill is None: raise ValueError("Skill not initialized.") return self._get_agent_context().task_manager @property def default_ledger_id(self) -> str: """Get the default ledger id.""" return self._get_agent_context().default_ledger_id @property def currency_denominations(self) -> Dict[str, str]: """Get a dictionary mapping ledger ids to currency denominations.""" return self._get_agent_context().currency_denominations @property def search_service_address(self) -> Address: """Get the address of the search service.""" return self._get_agent_context().search_service_address @property def decision_maker_address(self) -> Address: """Get the address of the decision maker.""" return self._get_agent_context().decision_maker_address @property def handlers(self) -> SimpleNamespace: """Get handlers of the skill.""" if self._skill is None: raise ValueError("Skill not initialized.") return SimpleNamespace(**self._skill.handlers) @property def behaviours(self) -> SimpleNamespace: """Get behaviours of the skill.""" if self._skill is None: raise ValueError("Skill not initialized.") return SimpleNamespace(**self._skill.behaviours) @property def namespace(self) -> SimpleNamespace: """Get the agent context namespace.""" return self._get_agent_context().namespace def __getattr__(self, item: Any) -> Any: """Get attribute.""" return super().__getattribute__(item) # pragma: no cover def send_to_skill( self, message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None, ) -> None: """ Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. :param message_or_envelope: envelope to send to another skill. :param context: the optional envelope context """ if self._agent_context is None: # pragma: nocover raise ValueError("agent context was not set!") self._agent_context.send_to_skill(message_or_envelope, context) class SkillComponent(ABC): """This class defines an abstract interface for skill component classes.""" def __init__( self, name: str, skill_context: SkillContext, configuration: Optional[SkillComponentConfiguration] = None, **kwargs: Any, ) -> None: """ Initialize a skill component. :param name: the name of the component. :param configuration: the configuration for the component. :param skill_context: the skill context. :param kwargs: the keyword arguments. """ if name is None: raise ValueError("SkillComponent name is not provided.") if skill_context is None: raise ValueError("SkillConext is not provided") if configuration is None: class_name = type(self).__name__ configuration = SkillComponentConfiguration(class_name=class_name, **kwargs) self._configuration = configuration self._name = name self._context = skill_context if len(kwargs) != 0: self.context.logger.warning( "The kwargs={} passed to {} have not been set!".format(kwargs, name) ) @property def name(self) -> str: """Get the name of the skill component.""" return self._name @property def context(self) -> SkillContext: """Get the context of the skill component.""" return self._context @property def skill_id(self) -> PublicId: """Get the skill id of the skill component.""" return self.context.skill_id @property def configuration(self) -> SkillComponentConfiguration: """Get the skill component configuration.""" if self._configuration is None: raise ValueError("Configuration not set.") # pragma: nocover return self._configuration @property def config(self) -> Dict[Any, Any]: """Get the config of the skill component.""" return self.configuration.args @abstractmethod def setup(self) -> None: """Implement the setup.""" super_obj = super() if hasattr(super_obj, "setup"): super_obj.setup() # type: ignore # pylint: disable=no-member @abstractmethod def teardown(self) -> None: """Implement the teardown.""" super_obj = super() if hasattr(super_obj, "teardown"): super_obj.teardown() # type: ignore # pylint: disable=no-member @classmethod @abstractmethod def parse_module( cls, path: str, configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext, ) -> dict: """Parse the component module.""" class AbstractBehaviour(SkillComponent, ABC): """ Abstract behaviour for periodical calls. tick_interval: float, interval to call behaviour's act. start_at: optional datetime, when to start periodical calls. """ _tick_interval: float = 0.001 _start_at: Optional[datetime.datetime] = None @property def tick_interval(self) -> float: """Get the tick_interval in seconds.""" return self._tick_interval @property def start_at(self) -> Optional[datetime.datetime]: """Get the start time of the behaviour.""" return self._start_at class Behaviour(AbstractBehaviour, ABC): """ This class implements an abstract behaviour. In a subclass of Behaviour, the flag 'is_programmatically_defined' can be used by the developer to signal to the framework that the class is meant to be used programmatically; hence, in case the class is not declared in the configuration file but it is present in a skill module, the framework will just ignore this class instead of printing a warning message. """ is_programmatically_defined: bool = False @abstractmethod def act(self) -> None: """ Implement the behaviour. :return: None """ def is_done(self) -> bool: """Return True if the behaviour is terminated, False otherwise.""" return False def act_wrapper(self) -> None: """Wrap the call of the action. This method must be called only by the framework.""" try: self.act() except _StopRuntime: raise except Exception as e: # pylint: disable=broad-except e_str = parse_exception(e) raise AEAActException( f"An error occurred during act of behaviour {self.context.skill_id}/{type(self).__name__}:\n{e_str}" ) @classmethod def parse_module( # pylint: disable=arguments-differ,arguments-renamed cls, path: str, behaviour_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext, ) -> Dict[str, "Behaviour"]: """ Parse the behaviours module. :param path: path to the Python module containing the Behaviour classes. :param behaviour_configs: a list of behaviour configurations. :param skill_context: the skill context :return: a list of Behaviour. """ return _parse_module(path, behaviour_configs, skill_context, Behaviour) class Handler(SkillComponent, ABC): """ This class implements an abstract behaviour. In a subclass of Handler, the flag 'is_programmatically_defined' can be used by the developer to signal to the framework that the component is meant to be used programmatically; hence, in case the class is not declared in the configuration file but it is present in a skill module, the framework will just ignore this class instead of printing a warning message. SUPPORTED_PROTOCOL is read by the framework when the handlers are loaded to register them as 'listeners' to the protocol identified by the specified public id. Whenever a message of protocol 'SUPPORTED_PROTOCOL' is sent to the agent, the framework will call the 'handle' method. """ SUPPORTED_PROTOCOL: Optional[PublicId] = None is_programmatically_defined: bool = False @abstractmethod def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ def handle_wrapper(self, message: Message) -> None: """Wrap the call of the handler. This method must be called only by the framework.""" try: self.handle(message) except _StopRuntime: raise except Exception as e: # pylint: disable=broad-except e_str = parse_exception(e) raise AEAHandleException( f"An error occurred during handle of handler {self.context.skill_id}/{type(self).__name__}:\n{e_str}" ) @classmethod def parse_module( # pylint: disable=arguments-differ,arguments-renamed cls, path: str, handler_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext, ) -> Dict[str, "Handler"]: """ Parse the handler module. :param path: path to the Python module containing the Handler class. :param handler_configs: the list of handler configurations. :param skill_context: the skill context :return: an handler, or None if the parsing fails. """ return _parse_module(path, handler_configs, skill_context, Handler) class Model(SkillComponent, ABC): """This class implements an abstract model.""" def __init__( self, name: str, skill_context: SkillContext, configuration: Optional[SkillComponentConfiguration] = None, keep_terminal_state_dialogues: Optional[bool] = None, **kwargs: Any, ) -> None: """ Initialize a model. :param name: the name of the component. :param configuration: the configuration for the component. :param skill_context: the skill context. :param keep_terminal_state_dialogues: specify do dialogues in terminal state should stay or not :param kwargs: the keyword arguments. """ super().__init__(name, skill_context, configuration=configuration, **kwargs) # used by dialogues if mixed with the Model if keep_terminal_state_dialogues is not None: self._keep_terminal_state_dialogues = keep_terminal_state_dialogues def setup(self) -> None: """Set the class up.""" super_obj = super() if hasattr(super_obj, "setup"): super_obj.setup() # type: ignore # pylint: disable=no-member def teardown(self) -> None: """Tear the class down.""" super_obj = super() if hasattr(super_obj, "teardown"): super_obj.teardown() # type: ignore # pylint: disable=no-member @classmethod def parse_module( # pylint: disable=arguments-differ,arguments-renamed cls, path: str, model_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext, ) -> Dict[str, "Model"]: """ Parse the model module. :param path: path to the Python skill module. :param model_configs: a list of model configurations. :param skill_context: the skill context :return: a list of Model. """ return _parse_module(path, model_configs, skill_context, Model) class Skill(Component): """This class implements a skill.""" __slots__ = ("_skill_context", "_handlers", "_behaviours", "_models") def __init__( self, configuration: SkillConfig, skill_context: Optional[SkillContext] = None, handlers: Optional[Dict[str, Handler]] = None, behaviours: Optional[Dict[str, Behaviour]] = None, models: Optional[Dict[str, Model]] = None, **kwargs: Any, ): """ Initialize a skill. :param configuration: the skill configuration. :param skill_context: the skill context. :param handlers: dictionary of handlers. :param behaviours: dictionary of behaviours. :param models: dictionary of models. :param kwargs: the keyword arguments. """ if kwargs is not None: pass super().__init__(configuration) self._skill_context = ( skill_context if skill_context is not None else SkillContext() ) self._handlers = ( {} if handlers is None else handlers ) # type: Dict[str, Handler] self._behaviours = ( {} if behaviours is None else behaviours ) # type: Dict[str, Behaviour] self._models = {} if models is None else models # type: Dict[str, Model] self._skill_context._skill = self self._set_models_on_context() def _set_models_on_context(self) -> None: """Set the models on the skill context.""" if self._models != {}: for model_id, model_instance in self._models.items(): if getattr(self._skill_context, model_id, None) is None: setattr(self._skill_context, model_id, model_instance) @property def skill_context(self) -> SkillContext: """Get the skill context.""" if self._skill_context is None: raise ValueError("Skill context not set.") # pragma: nocover return self._skill_context @property def handlers(self) -> Dict[str, Handler]: """Get the handlers.""" return self._handlers @property def behaviours(self) -> Dict[str, Behaviour]: """Get the handlers.""" return self._behaviours @property def models(self) -> Dict[str, Model]: """Get the handlers.""" return self._models @classmethod def from_dir( cls, directory: str, agent_context: AgentContext, **kwargs: Any ) -> "Skill": """ Load the skill from a directory. :param directory: the directory to the skill package. :param agent_context: the skill context. :param kwargs: the keyword arguments. :return: the skill object. """ configuration = cast( SkillConfig, load_component_configuration(ComponentType.SKILL, Path(directory)), ) configuration.directory = Path(directory) return Skill.from_config(configuration, agent_context, **kwargs) @property def logger(self) -> Logger: """ Get the logger. In the case of a skill, return the logger provided by the skill context. :return: the logger """ return self.skill_context.logger @logger.setter def logger(self, *args: str) -> None: """Set the logger.""" raise ValueError("Cannot set logger to a skill component.") @classmethod def from_config( cls, configuration: SkillConfig, agent_context: AgentContext, **kwargs: Any ) -> "Skill": """ Load the skill from configuration. :param configuration: a skill configuration. Must be associated with a directory. :param agent_context: the agent context. :param kwargs: the keyword arguments. :return: the skill. """ if configuration.directory is None: # pragma: nocover raise ValueError("Configuration must be associated with a directory.") # we put the initialization here because some skill components # might need some info from the skill # (e.g. see https://github.com/fetchai/agents-aea/issues/1095) skill_context = SkillContext() skill_context.set_agent_context(agent_context) logger_name = f"aea.packages.{configuration.author}.skills.{configuration.name}" logger_name = _get_aea_logger_name_prefix(logger_name, agent_context.agent_name) _logger = AgentLoggerAdapter( logging.getLogger(logger_name), agent_context.agent_name ) skill_context.logger = cast(Logger, _logger) skill_component_loader = _SkillComponentLoader( configuration, skill_context, **kwargs ) skill = skill_component_loader.load_skill() return skill def _parse_module( path: str, component_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext, component_class: Type, ) -> Dict[str, Any]: """ Parse a module to find skill component classes, and instantiate them. This is a private framework function, used in SkillComponentClass.parse_module. :param path: path to the Python module. :param component_configs: the component configurations. :param skill_context: the skill context. :param component_class: the class of the skill components to be loaded. :return: A mapping from skill component name to the skill component instance. """ components: Dict[str, Any] = {} component_type_name = component_class.__name__.lower() component_type_name_plural = component_type_name + "s" if component_configs == {}: return components component_names = set(config.class_name for _, config in component_configs.items()) component_module = load_module(component_type_name_plural, Path(path)) classes = inspect.getmembers(component_module, inspect.isclass) component_classes = list( filter( lambda x: any(re.match(component, x[0]) for component in component_names) and issubclass(x[1], component_class) and not str.startswith(x[1].__module__, "aea.") and not str.startswith( x[1].__module__, f"packages.{skill_context.skill_id.author}.skills.{skill_context.skill_id.name}", ), classes, ) ) name_to_class = dict(component_classes) _print_warning_message_for_non_declared_skill_components( skill_context, set(name_to_class.keys()), { component_config.class_name for component_config in component_configs.values() }, component_type_name_plural, path, ) for component_id, component_config in component_configs.items(): component_class_name = cast(str, component_config.class_name) skill_context.logger.debug( f"Processing {component_type_name} {component_class_name}" ) if not component_id.isidentifier(): raise AEAComponentLoadException( # pragma: nocover f"'{component_id}' is not a valid identifier." ) component_class = name_to_class.get(component_class_name, None) if component_class is None: skill_context.logger.warning( f"{component_type_name.capitalize()} '{component_class_name}' cannot be found." ) else: try: component = component_class( name=component_id, configuration=component_config, skill_context=skill_context, **dict(component_config.args), ) except Exception as e: # pylint: disable=broad-except # pragma: nocover e_str = parse_exception(e) raise AEAInstantiationException( f"An error occurred during instantiation of component {skill_context.skill_id}/{component_config.class_name}:\n{e_str}" ) components[component_id] = component return components def _print_warning_message_for_non_declared_skill_components( skill_context: SkillContext, classes: Set[str], config_components: Set[str], item_type: str, module_path: str, ) -> None: """Print a warning message if a skill component is not declared in the config files.""" for class_name in classes.difference(config_components): skill_context.logger.warning( "Class {} of type {} found in skill module {} but not declared in the configuration file.".format( class_name, item_type, module_path ) ) _SKILL_COMPONENT_TYPES = Type[Union[Handler, Behaviour, Model]] _ComponentsHelperIndex = Dict[_SKILL_COMPONENT_TYPES, Dict[str, SkillComponent]] """ Helper index to store component instances. """ class _SkillComponentLoadingItem: # pylint: disable=too-few-public-methods """Class to represent a triple (component name, component configuration, component class).""" def __init__( self, name: str, config: SkillComponentConfiguration, class_: Type[SkillComponent], type_: _SKILL_COMPONENT_TYPES, ): """Initialize the item.""" self.name = name self.config = config self.class_ = class_ self.type_ = type_ class _SkillComponentLoader: """This class implements the loading policy for skill components.""" def __init__( self, configuration: SkillConfig, skill_context: SkillContext, **kwargs: Any ): """Initialize the helper class.""" enforce( configuration.directory is not None, "Configuration not associated to directory.", ) self.configuration = configuration self.skill_directory = cast(Path, configuration.directory) self.skill_context = skill_context self.kwargs = kwargs self.skill = Skill(self.configuration, self.skill_context, **self.kwargs) self.skill_dotted_path = f"packages.{self.configuration.public_id.author}.skills.{self.configuration.public_id.name}" def load_skill(self) -> Skill: """Load the skill.""" load_aea_package(self.configuration) python_modules: Set[Path] = self._get_python_modules() declared_component_classes: Dict[ _SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration] ] = self._get_declared_skill_component_configurations() component_classes_by_path: Dict[ Path, Set[Tuple[str, Type[SkillComponent]]] ] = self._load_component_classes(python_modules) component_loading_items = self._match_class_and_configurations( component_classes_by_path, declared_component_classes ) components = self._get_component_instances(component_loading_items) self._update_skill(components) return self.skill def _update_skill(self, components: _ComponentsHelperIndex) -> None: self.skill.handlers.update( cast(Dict[str, Handler], components.get(Handler, {})) ) self.skill.behaviours.update( cast(Dict[str, Behaviour], components.get(Behaviour, {})) ) self.skill.models.update(cast(Dict[str, Model], components.get(Model, {}))) self.skill._set_models_on_context() # pylint: disable=protected-access def _get_python_modules(self) -> Set[Path]: """ Get all the Python modules of the skill package. We ignore '__pycache__' Python modules as they are not relevant. :return: a set of paths pointing to all the Python modules in the skill. """ ignore_regex = "__pycache__*" all_python_modules = self.skill_directory.rglob("*.py") module_paths: Set[Path] = set( map( lambda p: Path(p).relative_to(self.skill_directory), filter( lambda x: not re.match(ignore_regex, x.name), all_python_modules ), ) ) return module_paths @classmethod def _compute_module_dotted_path(cls, module_path: Path) -> str: """Compute the dotted path for a skill module.""" suffix = ".".join(module_path.with_name(module_path.stem).parts) return suffix def _filter_classes( self, classes: List[Tuple[str, Type]] ) -> List[Tuple[str, Type[SkillComponent]]]: """ Filter classes of skill components. The following filters are applied: - the class must be a subclass of "SkillComponent"; - its __module__ attribute must not start with 'aea.' (we exclude classes provided by the framework) - its __module__ attribute starts with the expected dotted path of this skill. In particular, it should not be imported from another skill. :param classes: a list of pairs (class name, class object) :return: a list of the same kind, but filtered with only skill component classes. """ filtered_classes = filter( lambda name_and_class: issubclass(name_and_class[1], SkillComponent) # the following condition filters out classes imported from 'aea' and not str.startswith(name_and_class[1].__module__, "aea.") # the following condition filters out classes imported # from other skills and not str.startswith( name_and_class[1].__module__, self.skill_dotted_path + "." ), classes, ) classes = list(filtered_classes) return cast(List[Tuple[str, Type[SkillComponent]]], classes) def _load_component_classes( self, module_paths: Set[Path] ) -> Dict[Path, Set[Tuple[str, Type[SkillComponent]]]]: """ Load component classes from Python modules. :param module_paths: a set of paths to Python modules. :return: a mapping from path to skill component classes in that module (containing potential duplicates). Skill components in one path are """ module_to_classes: Dict[Path, Set[Tuple[str, Type[SkillComponent]]]] = {} for module_path in module_paths: self.skill_context.logger.debug(f"Trying to load module {module_path}") module_dotted_path: str = self._compute_module_dotted_path(module_path) component_module: types.ModuleType = load_module( module_dotted_path, self.skill_directory / module_path ) classes: List[Tuple[str, Type]] = inspect.getmembers( component_module, inspect.isclass ) filtered_classes: List[ Tuple[str, Type[SkillComponent]] ] = self._filter_classes(classes) module_to_classes[module_path] = set(filtered_classes) return module_to_classes def _get_declared_skill_component_configurations( self, ) -> Dict[_SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration]]: """ Get all the declared skill component configurations. :return: dictionary of declared skill component configurations """ handlers_by_id = dict(self.configuration.handlers.read_all()) behaviours_by_id = dict(self.configuration.behaviours.read_all()) models_by_id = dict(self.configuration.models.read_all()) result: Dict[ _SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration] ] = {} for component_type, components_by_id in [ (Handler, handlers_by_id), (Behaviour, behaviours_by_id), (Model, models_by_id), ]: for component_id, component_config in components_by_id.items(): result.setdefault(component_type, {})[component_id] = component_config # type: ignore return result def _get_component_instances( self, component_loading_items: List[_SkillComponentLoadingItem], ) -> _ComponentsHelperIndex: """ Instantiate classes declared in configuration files. :param component_loading_items: a list of loading items. :return: the instances of the skill components. """ result: _ComponentsHelperIndex = {} for item in component_loading_items: instance = item.class_( name=item.name, configuration=item.config, skill_context=self.skill_context, **item.config.args, ) result.setdefault(item.type_, {})[item.name] = instance return result @classmethod def _get_skill_component_type( cls, skill_component_type: Type[SkillComponent], ) -> Type[Union[Handler, Behaviour, Model]]: """Get the concrete skill component type.""" parent_skill_component_types = list( filter( lambda class_: class_ in (Handler, Behaviour, Model), skill_component_type.__mro__, ) ) enforce( len(parent_skill_component_types) == 1, f"Class {skill_component_type.__name__} in module {skill_component_type.__module__} is not allowed to inherit from more than one skill component type. Found: {parent_skill_component_types}.", ) return cast( Type[Union[Handler, Behaviour, Model]], parent_skill_component_types[0] ) def _match_class_and_configurations( self, component_classes_by_path: Dict[Path, Set[Tuple[str, Type[SkillComponent]]]], declared_component_classes: Dict[ _SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration] ], ) -> List[_SkillComponentLoadingItem]: """ Match skill component classes to their configurations. Given a class of a skill component, we can disambiguate it in three ways: - by its name - by its type (one of 'Handler', 'Behaviour', 'Model') - whether the user has set the 'file_path' field. If one of the skill component cannot be disambiguated, we raise error. In this function, the above criteria are applied in that order. :param component_classes_by_path: the component classes by path :param declared_component_classes: the declared component classes :return: list of skill component loading items """ result: List[_SkillComponentLoadingItem] = [] class_index: Dict[ str, Dict[_SKILL_COMPONENT_TYPES, Set[Type[SkillComponent]]] ] = {} used_classes: Set[Type[SkillComponent]] = set() not_resolved_configurations: Dict[ Tuple[_SKILL_COMPONENT_TYPES, str], SkillComponentConfiguration ] = {} # populate indexes for _path, component_classes in component_classes_by_path.items(): for (component_classname, _component_class) in component_classes: type_ = self._get_skill_component_type(_component_class) class_index.setdefault(component_classname, {}).setdefault( type_, set() ).add(_component_class) for component_type, by_id in declared_component_classes.items(): for component_id, component_config in by_id.items(): path = component_config.file_path class_name = component_config.class_name if path is not None: classes_in_path = component_classes_by_path[path] component_class_or_none: Optional[Type[SkillComponent]] = next( ( actual_class for actual_class_name, actual_class in classes_in_path if actual_class_name == class_name ), None, ) enforce( component_class_or_none is not None, self._get_error_message_prefix() + f"Cannot find class '{class_name}' for component '{component_id}' of type '{self._type_to_str(component_type)}' of skill '{self.configuration.public_id}' in module {path}", ) component_class = cast( Type[SkillComponent], component_class_or_none ) actual_component_type = self._get_skill_component_type( component_class ) enforce( actual_component_type == component_type, self._get_error_message_prefix() + f"Found class '{class_name}' for component '{component_id}' of type '{self._type_to_str(component_type)}' of skill '{self.configuration.public_id}' in module {path}, but the expected type was {self._type_to_str(component_type)}, found {self._type_to_str(actual_component_type)} ", ) used_classes.add(component_class) result.append( _SkillComponentLoadingItem( component_id, component_config, component_class, component_type, ) ) else: # process the configuration at the end of the loop not_resolved_configurations[ (component_type, component_id) ] = component_config for (component_type, component_id), component_config in copy( not_resolved_configurations ).items(): class_name = component_config.class_name classes_by_type = class_index.get(class_name, {}) enforce( class_name in class_index and component_type in classes_by_type, self._get_error_message_prefix() + f"Cannot find class '{class_name}' for skill component '{component_id}' of type '{self._type_to_str(component_type)}'", ) classes = classes_by_type[component_type] not_used_classes = classes.difference(used_classes) enforce( not_used_classes != 0, f"Cannot find class of skill '{self.configuration.public_id}' for component configuration '{component_id}' of type '{self._type_to_str(component_type)}'.", ) enforce( len(not_used_classes) == 1, self._get_error_message_ambiguous_classes( class_name, not_used_classes, component_type, component_id ), ) not_used_class = list(not_used_classes)[0] result.append( _SkillComponentLoadingItem( component_id, component_config, not_used_class, component_type ) ) used_classes.add(not_used_class) self._print_warning_message_for_unused_classes( component_classes_by_path, used_classes ) return result def _print_warning_message_for_unused_classes( self, component_classes_by_path: Dict[Path, Set[Tuple[str, Type[SkillComponent]]]], used_classes: Set[Type[SkillComponent]], ) -> None: """ Print warning message for every unused class. :param component_classes_by_path: the component classes by path. :param used_classes: the classes used. """ for path, set_of_class_name_pairs in component_classes_by_path.items(): # take only classes, not class names set_of_classes = {pair[1] for pair in set_of_class_name_pairs} set_of_unused_classes = set( filter(lambda x: x not in used_classes, set_of_classes) ) # filter out classes that are from other packages set_of_unused_classes = set( filter( lambda x: not str.startswith(x.__module__, "packages."), set_of_unused_classes, ) ) if len(set_of_unused_classes) == 0: # all classes in the module are used! continue # for each unused class, print a warning message. However, # if it is a Handler or a Behaviour, print the message # only if 'is_programmatically_defined' is not True for unused_class in set_of_unused_classes: component_type_class = self._get_skill_component_type(unused_class) if ( issubclass(unused_class, (Handler, Behaviour)) and cast( Union[Handler, Behaviour], unused_class ).is_programmatically_defined ): continue _print_warning_message_for_non_declared_skill_components( self.skill_context, {unused_class.__name__}, set(), self._type_to_str(component_type_class), str(path), ) @classmethod def _type_to_str(cls, component_type: _SKILL_COMPONENT_TYPES) -> str: """Get the string of a component type.""" return component_type.__name__.lower() def _get_error_message_prefix(self) -> str: """Get error message prefix.""" return f"Error while loading skill '{self.configuration.public_id}': " def _get_error_message_ambiguous_classes( self, class_name: str, not_used_classes: Set, component_type: _SKILL_COMPONENT_TYPES, component_id: str, ) -> str: return f"{self._get_error_message_prefix()}found many classes with name '{class_name}' for component '{component_id}' of type '{self._type_to_str(component_type)}' in the following modules: {', '.join([c.__module__ for c in not_used_classes])}" ================================================ FILE: aea/skills/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the classes for specific behaviours.""" import datetime from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List, Optional, Set from aea.exceptions import enforce from aea.skills.base import Behaviour class SimpleBehaviour(Behaviour, ABC): """This class implements a simple behaviour.""" def __init__(self, act: Optional[Callable[[], None]] = None, **kwargs: Any) -> None: """ Initialize a simple behaviour. :param act: the act callable. :param kwargs: the keyword arguments to be passed to the parent class. """ super().__init__(**kwargs) if act is not None: self.act = act # type: ignore def setup(self) -> None: """Set the behaviour up.""" def act(self) -> None: """Do the action.""" raise NotImplementedError # pragma: no cover def teardown(self) -> None: """Tear the behaviour down.""" class CompositeBehaviour(Behaviour, ABC): """This class implements a composite behaviour.""" class CyclicBehaviour(SimpleBehaviour, ABC): """This behaviour is executed until the agent is stopped.""" def __init__(self, **kwargs: Any) -> None: """Initialize the cyclic behaviour.""" super().__init__(**kwargs) self._number_of_executions = 0 @property def number_of_executions(self) -> int: """Get the number of executions.""" return self._number_of_executions def act_wrapper(self) -> None: """Wrap the call of the action. This method must be called only by the framework.""" if not self.is_done(): super().act_wrapper() self._number_of_executions += 1 def is_done(self) -> bool: """ Return True if the behaviour is terminated, False otherwise. The user should implement it properly to determine the stopping condition. :return: bool indicating status """ return False class OneShotBehaviour(SimpleBehaviour, ABC): """This behaviour is executed only once.""" def __init__(self, **kwargs: Any) -> None: """Initialize the cyclic behaviour.""" super().__init__(**kwargs) self._already_executed = False # type def is_done(self) -> bool: """Return True if the behaviour is terminated, False otherwise.""" return self._already_executed def act_wrapper(self) -> None: """Wrap the call of the action. This method must be called only by the framework.""" if not self._already_executed: super().act_wrapper() self._already_executed = True class TickerBehaviour(SimpleBehaviour, ABC): """This behaviour is executed periodically with an interval.""" def __init__( self, tick_interval: float = 1.0, start_at: Optional[datetime.datetime] = None, **kwargs: Any, ) -> None: """ Initialize the ticker behaviour. :param tick_interval: interval of the behaviour in seconds. :param start_at: whether to start the behaviour with an offset. :param kwargs: the keyword arguments. """ super().__init__(**kwargs) self._tick_interval = tick_interval self._start_at = ( start_at if start_at is not None else datetime.datetime.now() ) # type: datetime.datetime # note, we set _last_act_time to be in the past so the ticker starts immediately self._last_act_time = datetime.datetime.now() - datetime.timedelta( seconds=tick_interval ) @property def tick_interval(self) -> float: """Get the tick_interval in seconds.""" return self._tick_interval @property def start_at(self) -> datetime.datetime: """Get the start time.""" return self._start_at @property def last_act_time(self) -> datetime.datetime: """Get the last time the act method has been called.""" return self._start_at def act_wrapper(self) -> None: """Wrap the call of the action. This method must be called only by the framework.""" if not self.is_done() and self.is_time_to_act(): self._last_act_time = datetime.datetime.now() super().act_wrapper() def is_time_to_act(self) -> bool: """ Check whether it is time to act, according to the tick_interval constraint and the 'start at' constraint. :return: True if it is time to act, false otherwise. """ now = datetime.datetime.now() return ( now > self._start_at and (now - self._last_act_time).total_seconds() > self.tick_interval ) class SequenceBehaviour(CompositeBehaviour, ABC): """This behaviour executes sub-behaviour serially.""" def __init__(self, behaviour_sequence: List[Behaviour], **kwargs: Any) -> None: """ Initialize the sequence behaviour. :param behaviour_sequence: the sequence of behaviour. :param kwargs: the keyword arguments """ super().__init__(**kwargs) self._behaviour_sequence = behaviour_sequence enforce(len(self._behaviour_sequence) > 0, "at least one behaviour.") self._index = 0 @property def current_behaviour(self) -> Optional[Behaviour]: """ Get the current behaviour. If None, the sequence behaviour can be considered done. :return: current behaviour or None """ return ( None if self._index >= len(self._behaviour_sequence) else self._behaviour_sequence[self._index] ) def _increase_index_if_possible(self) -> None: if self._index < len(self._behaviour_sequence): self._index += 1 def act(self) -> None: """Implement the behaviour.""" while ( not self.is_done() and self.current_behaviour is not None and self.current_behaviour.is_done() ): self._increase_index_if_possible() if ( not self.is_done() and self.current_behaviour is not None and not self.current_behaviour.is_done() ): self.current_behaviour.act_wrapper() def is_done(self) -> bool: """Return True if the behaviour is terminated, False otherwise.""" return self._index >= len(self._behaviour_sequence) class State(SimpleBehaviour, ABC): """ A state of a FSMBehaviour. A State behaviour is a simple behaviour with a special property 'event' that is opportunely set by the implementer. The event is read by the framework when the behaviour is done in order to pick the transition to trigger. """ def __init__(self, **kwargs: Any) -> None: """Initialize a state of the state machine.""" super().__init__(**kwargs) self._event = None # type: Optional[str] @property def event(self) -> Optional[str]: """Get the event to be triggered at the end of the behaviour.""" return self._event @abstractmethod def is_done(self) -> bool: """Return True if the behaviour is terminated, False otherwise.""" raise NotImplementedError # pragma: no cover def reset(self) -> None: """Reset initial conditions.""" class FSMBehaviour(CompositeBehaviour, ABC): """This class implements a finite-state machine behaviour.""" def __init__(self, **kwargs: Any) -> None: """Initialize the finite-state machine behaviour.""" super().__init__(**kwargs) self._name_to_state = {} # type: Dict[str, State] self._initial_state = None # type: Optional[str] self._final_states = set() # type: Set[str] self.current = None # type: Optional[str] # mapping from state to mappings event-next_state self.transitions = {} # type: Dict[str, Dict[Optional[str], str]] @property def is_started(self) -> bool: """Check if the behaviour is started.""" return self._initial_state is not None def register_state(self, name: str, state: State, initial: bool = False) -> None: """ Register a state. :param name: the name of the state. :param state: the behaviour in that state. :param initial: whether the state is an initial state. :raises ValueError: if a state with the provided name already exists. """ if name in self._name_to_state: raise ValueError("State name already existing.") self._name_to_state[name] = state if initial: self._initial_state = name self.current = self._initial_state def register_final_state(self, name: str, state: State) -> None: """ Register a final state. :param name: the name of the state. :param state: the state. :raises ValueError: if a state with the provided name already exists. """ if name in self._name_to_state: raise ValueError("State name already existing.") self._name_to_state[name] = state self._final_states.add(name) def unregister_state(self, name: str) -> None: """ Unregister a state. :param name: the state name to unregister. :raises ValueError: if the state is not registered. """ if name not in self._name_to_state: raise ValueError("State name not registered.") # remove state from mapping self._name_to_state.pop(name) # if it is an initial state, reset it to None. if name == self._initial_state: self._initial_state = None # if it is a final state, remove from the final state set. if name in self._final_states: self._final_states.remove(name) @property def states(self) -> Set[str]: """Get all the state names.""" return set(self._name_to_state.keys()) @property def initial_state(self) -> Optional[str]: """Get the initial state name.""" return self._initial_state @initial_state.setter def initial_state(self, name: str) -> None: """Set the initial state.""" if name not in self._name_to_state: raise ValueError("Name is not registered as state.") self._initial_state = name self.current = self._initial_state @property def final_states(self) -> Set[str]: """Get the final state names.""" return self._final_states def get_state(self, name: str) -> Optional[State]: """Get a state from its name.""" return self._name_to_state.get(name, None) def act(self) -> None: """Implement the behaviour.""" if self.current is None: return current_state = self.get_state(self.current) if current_state is None: return current_state.act_wrapper() if current_state.is_done(): if current_state in self._final_states: # we reached a final state - return. self.current = None return event = current_state.event next_state = self.transitions.get(self.current, {}).get(event, None) self.current = next_state def is_done(self) -> bool: """Return True if the behaviour is terminated, False otherwise.""" return self.current is None def register_transition( self, source: str, destination: str, event: Optional[str] = None ) -> None: """ Register a transition. No sanity check is done. :param source: the source state name. :param destination: the destination state name. :param event: the event. :raises ValueError: if a transition from source with event is already present. """ if source in self.transitions and event in self.transitions.get(source, {}): raise ValueError("Transition already registered.") self.transitions.setdefault(source, {})[event] = destination def unregister_transition( self, source: str, destination: str, event: Optional[str] = None ) -> None: """ Unregister a transition. :param source: the source state name. :param destination: the destination state name. :param event: the event. :raises ValueError: if a transition from source with event is not present. """ if ( source not in self.transitions.keys() or event not in self.transitions[source].keys() or self.transitions[source][event] != destination ): raise ValueError("Transaction not registered.") self.transitions.get(source, {}).pop(event, None) if len(self.transitions[source]) == 0: self.transitions.pop(source, None) ================================================ FILE: aea/skills/scaffold/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/scaffold:0.1.0") ================================================ FILE: aea/skills/scaffold/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a behaviour.""" from aea.skills.base import Behaviour class MyScaffoldBehaviour(Behaviour): """This class scaffolds a behaviour.""" def setup(self) -> None: """Implement the setup.""" raise NotImplementedError def act(self) -> None: """Implement the act.""" raise NotImplementedError def teardown(self) -> None: """Implement the task teardown.""" raise NotImplementedError ================================================ FILE: aea/skills/scaffold/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a handler.""" from typing import Optional from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler class MyScaffoldHandler(Handler): """This class scaffolds a handler.""" SUPPORTED_PROTOCOL = None # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" raise NotImplementedError def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ raise NotImplementedError def teardown(self) -> None: """Implement the handler teardown.""" raise NotImplementedError ================================================ FILE: aea/skills/scaffold/my_model.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a model.""" from aea.skills.base import Model class MyModel(Model): """This class scaffolds a model.""" ================================================ FILE: aea/skills/scaffold/skill.yaml ================================================ name: scaffold author: fetchai version: 0.1.0 type: skill description: The scaffold skill is a scaffold for your own skill implementation. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmcZ7oz5k3TiyMAxHsGCXP5UGQA9FCqcXuBFYqEmwP7Kym behaviours.py: QmTHQWGuBgc8YjVVLzqnFwVUZNmSsYUqMCRB2K8MJULkR5 handlers.py: QmSjtqfb5KB9afc3WDgC9V8C8vw1u8DpupKU6WYHTURUej my_model.py: QmbHTbagGPb3EFfBs4wLNbA7BiY7r2qtxYiq4WpVSYXbFf fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: [] skills: [] behaviours: scaffold: args: foo: bar class_name: MyScaffoldBehaviour handlers: scaffold: args: foo: bar class_name: MyScaffoldHandler models: scaffold: args: foo: bar class_name: MyModel dependencies: {} is_abstract: false ================================================ FILE: aea/skills/tasks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the classes for tasks.""" import logging import signal import threading from abc import abstractmethod from multiprocessing.pool import AsyncResult, Pool, ThreadPool from typing import Any, Callable, Dict, List, Optional, Sequence, Type, cast from aea.components.utils import _enlist_component_packages, _populate_packages from aea.helpers.logging import WithLogger THREAD_POOL_MODE = "multithread" PROCESS_POOL_MODE = "multiprocess" DEFAULT_WORKERS_AMOUNT = 2 class Task(WithLogger): """This class implements an abstract task.""" def __init__(self, **kwargs: Any) -> None: """Initialize a task.""" super().__init__(**kwargs) self._is_executed = False # this is where we store the result. self._result = None self.config = kwargs def __call__(self, *args: Any, **kwargs: Any) -> Any: """ Execute the task. :param args: positional arguments forwarded to the 'execute' method. :param kwargs: keyword arguments forwarded to the 'execute' method. :return: the task instance :raises ValueError: if the task has already been executed. """ if self._is_executed: raise ValueError("Task already executed.") self.setup() try: self._result = self.execute(*args, **kwargs) except Exception as e: # pylint: disable=broad-except self.logger.debug( "Got exception of type {} with message '{}' while executing task.".format( type(e), str(e) ) ) finally: self._is_executed = True self.teardown() return self._result @property def is_executed(self) -> bool: """Check if the task has already been executed.""" return self._is_executed @property def result(self) -> Any: """ Get the result. :return: the result from the execute method. :raises ValueError: if the task has not been executed yet. """ if not self._is_executed: raise ValueError("Task not executed yet.") return self._result def setup(self) -> None: """Implement the task setup.""" @abstractmethod def execute(self, *args: Any, **kwargs: Any) -> Any: """ Run the task logic. :param args: the positional arguments :param kwargs: the keyword arguments :return: any """ def teardown(self) -> None: """Implement the task teardown.""" # for api compatability. to remove in the next release def init_worker() -> None: # pragma: nocover """ Initialize a worker. Disable the SIGINT handler of process pool is using. Related to a well-known bug: https://bugs.python.org/issue8296 """ if Pool.__class__.__name__ == "Pool": # pragma: nocover # Process worker signal.signal(signal.SIGINT, signal.SIG_IGN) def _init_worker(mode: str, packages: Dict[str, List[Dict[str, str]]]) -> None: """ Initialize a worker. Disable the SIGINT handler of process pool is using. Related to a well-known bug: https://bugs.python.org/issue8296 :param mode: str. mode task manager runs in :param packages: dict with list of packages to load if needed """ if mode == PROCESS_POOL_MODE: # pragma: nocover signal.signal(signal.SIGINT, signal.SIG_IGN) _populate_packages(packages) class TaskManager(WithLogger): """A Task manager.""" POOL_MODES: Dict[str, Type[Pool]] = { THREAD_POOL_MODE: ThreadPool, PROCESS_POOL_MODE: Pool, } def __init__( self, nb_workers: int = DEFAULT_WORKERS_AMOUNT, is_lazy_pool_start: bool = True, logger: Optional[logging.Logger] = None, pool_mode: str = THREAD_POOL_MODE, ) -> None: """ Initialize the task manager. :param nb_workers: the number of worker processes. :param is_lazy_pool_start: option to postpone pool creation till the first enqueue_task called. :param logger: the logger. :param pool_mode: str. multithread or multiprocess """ WithLogger.__init__(self, logger) self._nb_workers = nb_workers self._is_lazy_pool_start = is_lazy_pool_start self._pool = None # type: Optional[Pool] self._stopped = True self._lock = threading.Lock() self._task_enqueued_counter = 0 self._results_by_task_id = {} # type: Dict[int, Any] self._pool_mode = pool_mode @property def is_started(self) -> bool: """ Get started status of TaskManager. :return: bool """ return not self._stopped @property def nb_workers(self) -> int: """ Get the number of workers. :return: int """ return self._nb_workers def enqueue_task( self, func: Callable, args: Sequence = (), kwargs: Optional[Dict[str, Any]] = None, ) -> int: """ Enqueue a task with the executor. :param func: the callable instance to be enqueued :param args: the positional arguments to be passed to the function. :param kwargs: the keyword arguments to be passed to the function. :return: the task id to get the the result. :raises ValueError: if the task manager is not running. """ with self._lock: if self._stopped: raise ValueError("Task manager not running.") if not self._pool and self._is_lazy_pool_start: self._start_pool() self._pool = cast(Pool, self._pool) task_id = self._task_enqueued_counter self._task_enqueued_counter += 1 async_result = self._pool.apply_async( func, args=args, kwds=kwargs if kwargs is not None else {} ) self._results_by_task_id[task_id] = async_result if self._logger: # pragma: nocover self._logger.info(f"Task <{func}{args}> set. Task id is {task_id}") return task_id def get_task_result(self, task_id: int) -> AsyncResult: """ Get the result from a task. :param task_id: the task id :return: async result for task_id """ task_result = self._results_by_task_id.get( task_id, None ) # type: Optional[AsyncResult] if task_result is None: raise ValueError("Task id {} not present.".format(task_id)) return task_result def start(self) -> None: """Start the task manager.""" with self._lock: if self._stopped is False: self.logger.debug("Task manager already running.") else: self.logger.debug("Start the task manager.") self._stopped = False if not self._is_lazy_pool_start: self._start_pool() def stop(self) -> None: """Stop the task manager.""" with self._lock: if self._stopped is True: self.logger.debug("Task manager already stopped.") else: self.logger.debug("Stop the task manager.") self._stopped = True self._stop_pool() def _start_pool(self) -> None: """ Start internal task pool. Only one pool will be created. """ if self._pool: self.logger.debug("Pool was already started!") return pool_cls = self.POOL_MODES.get(self._pool_mode) if not pool_cls: # pragma: nocover raise ValueError(f"Mode: `{self._pool_mode}` is not supported") init_args = ( self._pool_mode, _enlist_component_packages(), ) self._pool = pool_cls( self._nb_workers, initializer=_init_worker, initargs=init_args ) def _stop_pool(self) -> None: """Stop internal task pool.""" if not self._pool: self.logger.debug("Pool is not started!.") return self._pool = cast(Pool, self._pool) self._pool.terminate() self._pool.join() self._pool = None class ThreadedTaskManager(TaskManager): """A threaded task manager.""" def __init__( self, nb_workers: int = DEFAULT_WORKERS_AMOUNT, is_lazy_pool_start: bool = True, logger: Optional[logging.Logger] = None, ) -> None: """ Initialize the task manager. :param nb_workers: the number of worker processes. :param is_lazy_pool_start: option to postpone pool creation till the first enqueue_task called. :param logger: the logger. """ super().__init__( nb_workers=nb_workers, is_lazy_pool_start=is_lazy_pool_start, logger=logger, pool_mode=THREAD_POOL_MODE, ) class ProcessTaskManager(TaskManager): """A multiprocess task manager.""" def __init__( self, nb_workers: int = DEFAULT_WORKERS_AMOUNT, is_lazy_pool_start: bool = True, logger: Optional[logging.Logger] = None, ) -> None: """ Initialize the task manager. :param nb_workers: the number of worker processes. :param is_lazy_pool_start: option to postpone pool creation till the first enqueue_task called. :param logger: the logger. """ super().__init__( nb_workers=nb_workers, is_lazy_pool_start=is_lazy_pool_start, logger=logger, pool_mode=PROCESS_POOL_MODE, ) ================================================ FILE: aea/test_tools/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the AEA test framework.""" ================================================ FILE: aea/test_tools/click_testing.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains an adaptation of click.testing.CliRunner. In particular, it fixes an issue in CliRunner.invoke, in the 'finally' clause. More precisely, before reading from the testing outstream, it checks whether it has been already closed. Links: https://github.com/pallets/click/issues/824 """ import shlex import sys from typing import Optional from click.testing import CliRunner as ClickCliRunner from click.testing import Result class CliRunner(ClickCliRunner): """Patch of click.testing.CliRunner.""" def invoke( # type: ignore self, cli, args=None, input=None, # pylint: disable=redefined-builtin env=None, catch_exceptions=True, color=False, **extra, ) -> Result: """Call a cli command with click.testing.CliRunner.invoke.""" exc_info = None exception: Optional[BaseException] = None exit_code = 0 with self.isolation(input=input, env=env, color=color) as outstreams: if isinstance(args, str): args = shlex.split(args) try: prog_name = extra.pop("prog_name") except KeyError: prog_name = self.get_default_prog_name(cli) try: cli.main(args=args or (), prog_name=prog_name, **extra) except SystemExit as e: exc_info = sys.exc_info() exit_code = e.code if exit_code is None: # pragma: nocover exit_code = 0 if exit_code != 0: # pragma: nocover exception = e if not isinstance(exit_code, int): sys.stdout.write(str(exit_code)) sys.stdout.write("\n") exit_code = 1 except Exception as e: # pylint: disable=broad-except if not catch_exceptions: raise exception = e exit_code = 1 exc_info = sys.exc_info() finally: sys.stdout.flush() stdout = outstreams[0].getvalue() if not outstreams[0].closed else b"" # type: ignore if self.mix_stderr: # when it mixed, stderr always empty cause all output goes to stdout stderr: Optional[bytes] = None else: stderr = ( outstreams[1].getvalue() if not outstreams[1].closed else b"" # type: ignore ) return Result( runner=self, stdout_bytes=stdout, stderr_bytes=stderr, # type: ignore exit_code=exit_code, exception=exception, exc_info=exc_info, # type: ignore return_value=None, ) ================================================ FILE: aea/test_tools/constants.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This is a module with constants for test tools.""" DEFAULT_AUTHOR = "default_author" ================================================ FILE: aea/test_tools/exceptions.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Module with AEA testing exceptions.""" class AEATestingException(Exception): """An exception to be raised on incorrect testing tools usage.""" ================================================ FILE: aea/test_tools/generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains generic tools for AEA end-to-end testing.""" from collections import OrderedDict from pathlib import Path from typing import Any, Dict, List, cast from aea.configurations.base import ( CRUDCollection, ComponentConfiguration, PackageConfiguration, PackageType, PublicId, SkillConfig, dependencies_from_json, ) from aea.configurations.manager import handle_dotted_path from aea.exceptions import enforce from aea.helpers.file_io import write_envelope from aea.helpers.io import open_file from aea.helpers.yaml_utils import yaml_dump, yaml_dump_all from aea.mail.base import Envelope from aea.test_tools.constants import DEFAULT_AUTHOR def write_envelope_to_file(envelope: Envelope, file_path: str) -> None: """ Write an envelope to a file. :param envelope: Envelope. :param file_path: the file path """ with open(Path(file_path), "ab+") as f: write_envelope(envelope, f) def read_envelope_from_file(file_path: str) -> Envelope: """ Read an envelope from a file. :param file_path: the file path. :return: envelope """ lines = [] with open(Path(file_path), "rb+") as f: lines.extend(f.readlines()) enforce(len(lines) == 2, "Did not find two lines.") line = lines[0] + lines[1] to_b, sender_b, protocol_specification_id_b, message, end = line.strip().split( b",", maxsplit=4 ) to = to_b.decode("utf-8") sender = sender_b.decode("utf-8") protocol_specification_id = PublicId.from_str( protocol_specification_id_b.decode("utf-8") ) enforce(end in [b"", b"\n"], "Envelope improperly formatted.") return Envelope( to=to, sender=sender, protocol_specification_id=protocol_specification_id, message=message, ) def _nested_set( configuration_obj: PackageConfiguration, keys: List, value: Any ) -> None: """ Nested set a value to a dict. Force sets the values, overwriting any present values, but maintaining schema validation. :param configuration_obj: configuration object :param keys: list of keys. :param value: a value to set. """ def get_nested_ordered_dict_from_dict(input_dict: Dict) -> Dict: _dic = {} for _key, _value in input_dict.items(): if isinstance(_value, dict): _dic[_key] = OrderedDict(get_nested_ordered_dict_from_dict(_value)) else: _dic[_key] = _value return _dic def get_nested_ordered_dict_from_keys_and_value( keys: List[str], value: Any ) -> Dict: _dic = ( OrderedDict(get_nested_ordered_dict_from_dict(value)) if isinstance(value, dict) else value ) for key in keys[::-1]: _dic = OrderedDict({key: _dic}) return _dic root_key = keys[0] if ( isinstance(configuration_obj, SkillConfig) and root_key in SkillConfig.FIELDS_WITH_NESTED_FIELDS ): root_attr = getattr(configuration_obj, root_key) length = len(keys) if length < 3 or keys[2] not in SkillConfig.NESTED_FIELDS_ALLOWED_TO_UPDATE: raise ValueError(f"Invalid keys={keys}.") # pragma: nocover skill_component_id = keys[1] skill_component_config = root_attr.read(skill_component_id) if length == 3 and isinstance(value, dict): # root.skill_component_id.args # set all args skill_component_config.args = get_nested_ordered_dict_from_dict(value) elif len(keys) >= 4: # root.skill_component_id.args.[keys] # update some args dic = get_nested_ordered_dict_from_keys_and_value(keys[3:], value) skill_component_config.args.update(dic) else: raise ValueError( # pragma: nocover f"Invalid keys={keys} and values={value}." ) root_attr.update(skill_component_id, skill_component_config) else: root_attr = getattr(configuration_obj, root_key) if isinstance(root_attr, CRUDCollection): if isinstance(value, dict) and len(keys) == 1: # root. for _key, _value in value.items(): dic = get_nested_ordered_dict_from_keys_and_value([_key], _value) root_attr.update(_key, dic[_key]) elif len(keys) >= 2: # root.[keys] dic = get_nested_ordered_dict_from_keys_and_value(keys[1:], value) root_attr.update(keys[1], dic[keys[1]]) else: raise ValueError( # pragma: nocover f"Invalid keys={keys} and values={value}." ) elif root_key == "dependencies": enforce( isinstance(configuration_obj, ComponentConfiguration), "Cannot only set dependencies to ComponentConfiguration instances.", ) configuration_obj = cast(ComponentConfiguration, configuration_obj) new_pypi_dependencies = dependencies_from_json(value) configuration_obj.pypi_dependencies = new_pypi_dependencies else: dic = get_nested_ordered_dict_from_keys_and_value(keys, value) setattr(configuration_obj, root_key, dic[root_key]) def nested_set_config( dotted_path: str, value: Any, author: str = DEFAULT_AUTHOR ) -> None: """ Set an AEA config with nested values. Run from agent's directory. Allowed dotted_path: 'agent.an_attribute_name' 'protocols.my_protocol.an_attribute_name' 'connections.my_connection.an_attribute_name' 'contracts.my_contract.an_attribute_name' 'skills.my_skill.an_attribute_name' 'vendor.author.[protocols|connections|skills].package_name.attribute_name :param dotted_path: dotted path to a setting. :param value: a value to assign. Must be of yaml serializable type. :param author: the author name, used to parse the dotted path. """ settings_keys, config_file_path, config_loader, _ = handle_dotted_path( dotted_path, author ) with open_file(config_file_path) as fp: config = config_loader.load(fp) _nested_set(config, settings_keys, value) if config.package_type == PackageType.AGENT: json_data = config.ordered_json component_configurations = json_data.pop("component_configurations") with open_file(config_file_path, "w") as fp: yaml_dump_all([json_data] + component_configurations, fp) else: with open_file(config_file_path, "w") as fp: yaml_dump(config.ordered_json, fp) ================================================ FILE: aea/test_tools/test_cases.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains test case classes based on pytest for AEA end-to-end testing.""" import copy import logging import os import random import shutil import string import subprocess # nosec import sys import tempfile import time from abc import ABC from contextlib import suppress from filecmp import dircmp from io import TextIOWrapper from pathlib import Path from threading import Thread from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Union import yaml from aea.cli import cli from aea.configurations.base import ( AgentConfig, PackageType, _get_default_configuration_file_name_from_type, ) from aea.configurations.constants import ( DEFAULT_AEA_CONFIG_FILE, DEFAULT_INPUT_FILE_NAME, DEFAULT_LEDGER, DEFAULT_OUTPUT_FILE_NAME, DEFAULT_PRIVATE_KEY_FILE, DEFAULT_REGISTRY_NAME, LAUNCH_SUCCEED_MESSAGE, ) from aea.configurations.loader import ConfigLoader, ConfigLoaders from aea.exceptions import enforce from aea.helpers.base import cd, send_control_c, win_popen_kwargs from aea.helpers.io import open_file from aea.mail.base import Envelope from aea.test_tools.click_testing import CliRunner, Result from aea.test_tools.constants import DEFAULT_AUTHOR from aea.test_tools.exceptions import AEATestingException from aea.test_tools.generic import ( nested_set_config, read_envelope_from_file, write_envelope_to_file, ) _default_logger = logging.getLogger(__name__) CLI_LOG_OPTION = ["-v", "OFF"] DEFAULT_PROCESS_TIMEOUT = 120 DEFAULT_LAUNCH_TIMEOUT = 10 class BaseAEATestCase(ABC): # pylint: disable=too-many-public-methods """Base class for AEA test cases.""" runner: CliRunner # CLI runner last_cli_runner_result: Optional[Result] = None author: str = DEFAULT_AUTHOR # author subprocesses: List[subprocess.Popen] = [] # list of launched subprocesses threads: List[Thread] = [] # list of started threads packages_dir_path: Path = Path(DEFAULT_REGISTRY_NAME) package_registry_src: Path = Path(".") use_packages_dir: bool = True package_registry_src_rel: Path = Path(os.getcwd(), packages_dir_path) old_cwd: Path # current working directory path t: Path # temporary directory path current_agent_context: str = "" # the name of the current agent agents: Set[str] = set() # the set of created agents stdout: Dict[int, str] # dict of process.pid: string stdout stderr: Dict[int, str] # dict of process.pid: string stderr _is_teardown_class_called: bool = False capture_log: bool = False cli_log_options: List[str] = [] method_list: List[str] = [] @classmethod def set_agent_context(cls, agent_name: str) -> None: """Set the current agent context.""" cls.current_agent_context = agent_name @classmethod def unset_agent_context(cls) -> None: """Unset the current agent context.""" cls.current_agent_context = "" @classmethod def set_config( cls, dotted_path: str, value: Any, type_: Optional[str] = None ) -> Result: """ Set a config. Run from agent's directory. :param dotted_path: str dotted path to config param. :param value: a new value to set. :param type_: the type :return: Result """ if type_ is None: type_ = type(value).__name__ return cls.run_cli_command( "config", "set", dotted_path, str(value), "--type", type_, cwd=cls._get_cwd(), ) @classmethod def nested_set_config(cls, dotted_path: str, value: Any) -> None: """Force set config.""" with cd(cls._get_cwd()): nested_set_config(dotted_path, value) @classmethod def disable_aea_logging(cls) -> None: """ Disable AEA logging of specific agent. Run from agent's directory. """ config_update_dict = { "agent.logging_config.disable_existing_loggers": "False", "agent.logging_config.version": "1", } for path, value in config_update_dict.items(): cls.run_cli_command("config", "set", path, value, cwd=cls._get_cwd()) @classmethod def run_cli_command(cls, *args: str, cwd: str = ".", **kwargs: str) -> Result: """ Run AEA CLI command. :param args: CLI args :param cwd: the working directory from where to run the command. :param kwargs: other keyword arguments to click.CliRunner.invoke. :raises AEATestingException: if command fails. :return: Result """ with cd(cwd): result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, *args], standalone_mode=False, catch_exceptions=False, **kwargs, ) cls.last_cli_runner_result = result if result.exit_code != 0: # pragma: nocover raise AEATestingException( "Failed to execute AEA CLI command with args {}.\n" "Exit code: {}\nException: {}".format( args, result.exit_code, result.exception ) ) return result @classmethod def _run_python_subprocess(cls, *args: str, cwd: str = ".") -> subprocess.Popen: """ Run python with args as subprocess. :param args: CLI args :param cwd: the current working directory :return: subprocess object. """ kwargs = dict( stdout=subprocess.PIPE, stdin=subprocess.PIPE, env=os.environ.copy(), cwd=cwd, ) kwargs.update(win_popen_kwargs()) process = subprocess.Popen( # type: ignore # nosec # mypy fails on **kwargs # pylint: disable=consider-using-with [sys.executable, *args], **kwargs, ) cls.subprocesses.append(process) return process @classmethod def start_subprocess(cls, *args: str, cwd: str = ".") -> subprocess.Popen: """ Run python with args as subprocess. :param args: CLI args :param cwd: the current working directory :return: subprocess object. """ process = cls._run_python_subprocess(*args, cwd=cwd) cls._start_output_read_thread(process) cls._start_error_read_thread(process) return process @classmethod def start_thread(cls, target: Callable, **kwargs: subprocess.Popen) -> Thread: """ Start python Thread. :param target: target method. :param kwargs: thread keyword arguments :return: thread """ if "process" in kwargs: thread = Thread(target=target, args=(kwargs["process"],)) else: thread = Thread(target=target) thread.start() cls.threads.append(thread) return thread @classmethod def create_agents( cls, *agents_names: str, is_local: bool = True, is_empty: bool = False ) -> None: """ Create agents in current working directory. :param agents_names: str agent names. :param is_local: a flag for local folder add True by default. :param is_empty: optional boolean flag for skip adding default dependencies. """ cli_args = ["create", "--local", "--empty"] if not is_local: # pragma: nocover cli_args.remove("--local") if not is_empty: # pragma: nocover cli_args.remove("--empty") for name in set(agents_names): cls.run_cli_command(*cli_args, name) cls.agents.add(name) @classmethod def fetch_agent( cls, public_id: str, agent_name: str, is_local: bool = True ) -> None: """ Create agents in current working directory. :param public_id: str public id :param agent_name: str agent name. :param is_local: a flag for local folder add True by default. """ cli_args = ["fetch", "--local"] if not is_local: # pragma: nocover cli_args.remove("--local") cls.run_cli_command(*cli_args, public_id, "--alias", agent_name) cls.agents.add(agent_name) @classmethod def difference_to_fetched_agent(cls, public_id: str, agent_name: str) -> List[str]: """ Compare agent against the one fetched from public id. :param public_id: str public id :param agent_name: str agent name. :return: list of files differing in the projects """ # for pydocstyle def is_allowed_diff_in_agent_config( path_to_fetched_aea: str, path_to_manually_created_aea: str ) -> Tuple[ bool, Union[Dict[str, str], List[Any]], Union[Dict[str, str], List[Any]] ]: with open_file( os.path.join(path_to_fetched_aea, "aea-config.yaml"), "r" ) as file: content1 = list(yaml.safe_load_all(file)) # load all contents with open_file( os.path.join(path_to_manually_created_aea, "aea-config.yaml"), "r" ) as file: content2 = list(yaml.safe_load_all(file)) content1_agentconfig = content1[0] content2_agentconfig = content2[0] content1_agentconfig_copy = copy.deepcopy(content1_agentconfig) # check only agent part for key, value in content1_agentconfig_copy.items(): if content2_agentconfig[key] == value: content1_agentconfig.pop(key) content2_agentconfig.pop(key) allowed_diff_keys = [ "aea_version", "author", "description", "version", "connection_private_key_paths", "private_key_paths", "dependencies", "required_ledgers", ] result = all( (key in allowed_diff_keys for key in content1_agentconfig.keys()) ) result = result and all( (key in allowed_diff_keys for key in content2_agentconfig.keys()) ) if not result: return result, content1_agentconfig, content2_agentconfig # else, additionally check the other YAML pages # (i.e. the component configuration overrides) content1_component_overrides = content1[1:] content2_component_overrides = content2[1:] if len(content1_component_overrides) != len(content2_component_overrides): return False, content1_component_overrides, content2_component_overrides diff_1, diff_2 = [], [] for index, (override_1, override_2) in enumerate( zip(content1_component_overrides, content2_component_overrides) ): if override_1 != override_2: result = False diff_1.append((index, override_1)) diff_2.append((index, override_2)) return result, diff_1, diff_2 path_to_manually_created_aea = os.path.join(cls.t, agent_name) new_cwd = os.path.join(cls.t, "fetch_dir") os.mkdir(new_cwd) fetched_agent_name = agent_name path_to_fetched_aea = os.path.join(new_cwd, fetched_agent_name) registry_tmp_dir = os.path.join(new_cwd, cls.packages_dir_path) shutil.copytree(str(cls.package_registry_src_rel), str(registry_tmp_dir)) with cd(new_cwd): cls.run_cli_command( "fetch", "--local", public_id, "--alias", fetched_agent_name ) comp = dircmp(path_to_manually_created_aea, path_to_fetched_aea) file_diff = comp.diff_files result, diff1, diff2 = is_allowed_diff_in_agent_config( path_to_fetched_aea, path_to_manually_created_aea ) if result: if "aea-config.yaml" in file_diff: # pragma: nocover file_diff.remove("aea-config.yaml") # won't match! else: file_diff.append( "Difference in aea-config.yaml: " + str(diff1) + " vs. " + str(diff2) ) with suppress(OSError, IOError): shutil.rmtree(new_cwd) return file_diff @classmethod def delete_agents(cls, *agents_names: str) -> None: """ Delete agents in current working directory. :param agents_names: str agent names. """ for name in set(agents_names): cls.run_cli_command("delete", name) cls.agents.remove(name) @classmethod def run_agent(cls, *args: str) -> subprocess.Popen: """ Run agent as subprocess. Run from agent's directory. :param args: CLI args :return: subprocess object. """ return cls._start_cli_process("run", *args) @classmethod def run_interaction(cls) -> subprocess.Popen: """ Run interaction as subprocess. Run from agent's directory. :return: subprocess object. """ return cls._start_cli_process("interact") @classmethod def _start_cli_process(cls, *args: str) -> subprocess.Popen: """ Start cli subprocess with args specified. :param args: CLI args :return: subprocess object. """ process = cls._run_python_subprocess( "-m", "aea.cli", *cls.cli_log_options, *args, cwd=cls._get_cwd() ) cls._start_output_read_thread(process) cls._start_error_read_thread(process) return process @classmethod def terminate_agents( cls, *subprocesses: subprocess.Popen, timeout: int = 20, ) -> None: """ Terminate agent subprocesses. Run from agent's directory. :param subprocesses: the subprocesses running the agents :param timeout: the timeout for interruption """ if not subprocesses: subprocesses = tuple(cls.subprocesses) for process in subprocesses: process.poll() if process.returncode is None: # stop only pending processes send_control_c(process) for process in subprocesses: process.wait(timeout=timeout) @classmethod def is_successfully_terminated(cls, *subprocesses: subprocess.Popen) -> bool: """Check if all subprocesses terminated successfully.""" if not subprocesses: subprocesses = tuple(cls.subprocesses) all_terminated = all((process.returncode == 0 for process in subprocesses)) return all_terminated @classmethod def initialize_aea(cls, author: str) -> None: """Initialize AEA locally with author name.""" cls.run_cli_command("init", "--author", author, cwd=cls._get_cwd()) @classmethod def add_item(cls, item_type: str, public_id: str, local: bool = True) -> Result: """ Add an item to the agent. Run from agent's directory. :param item_type: str item type. :param public_id: public id of the item. :param local: a flag for local folder add True by default. :return: Result """ cli_args = ["add", "--local", item_type, public_id] if not local: # pragma: nocover cli_args.remove("--local") return cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) @classmethod def remove_item(cls, item_type: str, public_id: str) -> Result: """ Remove an item from the agent. Run from agent's directory. :param item_type: str item type. :param public_id: public id of the item. :return: Result """ cli_args = ["remove", item_type, public_id] return cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) @classmethod def scaffold_item( cls, item_type: str, name: str, skip_consistency_check: bool = False ) -> Result: """ Scaffold an item for the agent. Run from agent's directory. :param item_type: str item type. :param name: name of the item. :param skip_consistency_check: if True, skip consistency check. :return: Result """ flags = ["-s"] if skip_consistency_check else [] if item_type == "protocol": return cls.run_cli_command( *flags, "scaffold", item_type, "-y", name, cwd=cls._get_cwd() ) return cls.run_cli_command( *flags, "scaffold", item_type, name, cwd=cls._get_cwd() ) @classmethod def fingerprint_item(cls, item_type: str, public_id: str) -> Result: """ Fingerprint an item for the agent. Run from agent's directory. :param item_type: str item type. :param public_id: public id of the item. :return: Result """ return cls.run_cli_command( "fingerprint", item_type, public_id, cwd=cls._get_cwd() ) @classmethod def eject_item(cls, item_type: str, public_id: str) -> Result: """ Eject an item in the agent in quiet mode (i.e. no interaction). Run from agent's directory. :param item_type: str item type. :param public_id: public id of the item. :return: None """ cli_args = ["eject", "--quiet", item_type, public_id] return cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) @classmethod def run_install(cls) -> Result: """ Execute AEA CLI install command. Run from agent's directory. :return: Result """ return cls.run_cli_command("install", cwd=cls._get_cwd()) @classmethod def generate_private_key( cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_file: Optional[str] = None, password: Optional[str] = None, ) -> Result: """ Generate AEA private key with CLI command. Run from agent's directory. :param ledger_api_id: ledger API ID. :param private_key_file: the private key file. :param password: the password. :return: Result """ cli_args = ["generate-key", ledger_api_id] if private_key_file is not None: # pragma: nocover cli_args.append(private_key_file) cli_args += _get_password_option_args(password) return cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) @classmethod def add_private_key( cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_filepath: str = DEFAULT_PRIVATE_KEY_FILE, connection: bool = False, password: Optional[str] = None, ) -> Result: """ Add private key with CLI command. Run from agent's directory. :param ledger_api_id: ledger API ID. :param private_key_filepath: private key filepath. :param connection: whether or not the private key filepath is for a connection. :param password: the password to encrypt private keys. :return: Result """ password_option = _get_password_option_args(password) if connection: return cls.run_cli_command( "add-key", ledger_api_id, private_key_filepath, "--connection", *password_option, cwd=cls._get_cwd(), ) return cls.run_cli_command( "add-key", ledger_api_id, private_key_filepath, *password_option, cwd=cls._get_cwd(), ) @classmethod def remove_private_key( cls, ledger_api_id: str = DEFAULT_LEDGER, connection: bool = False, ) -> Result: """ Remove private key with CLI command. Run from agent's directory. :param ledger_api_id: ledger API ID. :param connection: whether or not the private key filepath is for a connection. :return: Result """ args = ["remove-key", ledger_api_id] + (["--connection"] if connection else []) return cls.run_cli_command(*args, cwd=cls._get_cwd()) @classmethod def replace_private_key_in_file( cls, private_key: str, private_key_filepath: str = DEFAULT_PRIVATE_KEY_FILE ) -> None: """ Replace the private key in the provided file with the provided key. :param private_key: the private key :param private_key_filepath: the filepath to the private key file :raises: exception if file does not exist """ with cd(cls._get_cwd()): # pragma: nocover with open_file(private_key_filepath, "wt") as f: f.write(private_key) @classmethod def generate_wealth( cls, ledger_api_id: str = DEFAULT_LEDGER, password: Optional[str] = None ) -> Result: """ Generate wealth with CLI command. Run from agent's directory. :param ledger_api_id: ledger API ID. :param password: the password. :return: Result """ password_option = _get_password_option_args(password) return cls.run_cli_command( "generate-wealth", ledger_api_id, *password_option, "--sync", cwd=cls._get_cwd(), ) @classmethod def get_wealth( cls, ledger_api_id: str = DEFAULT_LEDGER, password: Optional[str] = None ) -> str: """ Get wealth with CLI command. Run from agent's directory. :param ledger_api_id: ledger API ID. :param password: the password to encrypt/decrypt private keys. :return: command line output """ password_option = _get_password_option_args(password) cls.run_cli_command( "get-wealth", ledger_api_id, *password_option, cwd=cls._get_cwd() ) if cls.last_cli_runner_result is None: raise ValueError("Runner result not set!") # pragma: nocover return str(cls.last_cli_runner_result.stdout_bytes, "utf-8") @classmethod def get_address( cls, ledger_api_id: str = DEFAULT_LEDGER, password: Optional[str] = None ) -> str: """ Get address with CLI command. Run from agent's directory. :param ledger_api_id: ledger API ID. :param password: the password to encrypt/decrypt private keys. :return: command line output """ password_option = _get_password_option_args(password) cls.run_cli_command( "get-address", ledger_api_id, *password_option, cwd=cls._get_cwd() ) if cls.last_cli_runner_result is None: raise ValueError("Runner result not set!") # pragma: nocover return str(cls.last_cli_runner_result.stdout_bytes, "utf-8").strip() @classmethod def replace_file_content(cls, src: Path, dest: Path) -> None: # pragma: nocover """ Replace the content of the source file to the destination file. :param src: the source file. :param dest: the destination file. """ enforce( src.is_file() and dest.is_file(), "Source or destination is not a file." ) dest.write_text(src.read_text()) @classmethod def change_directory(cls, path: Path) -> None: """ Change current working directory. :param path: path to the new working directory. """ os.chdir(Path(path)) @classmethod def _terminate_subprocesses(cls) -> None: """Terminate all launched subprocesses.""" for process in cls.subprocesses: if not process.returncode == 0: poll = process.poll() if poll is None: process.terminate() process.wait(2) cls.subprocesses = [] @classmethod def _join_threads(cls) -> None: """Join all started threads.""" for thread in cls.threads: thread.join() cls.threads = [] @classmethod def _read_out( cls, process: subprocess.Popen ) -> None: # pragma: nocover # runs in thread! if process.stdout is None: raise Exception("Stdout of the process is None") for line in TextIOWrapper(process.stdout, encoding="utf-8"): cls._log_capture("stdout", process.pid, line) cls.stdout[process.pid] += line @classmethod def _read_err( cls, process: subprocess.Popen ) -> None: # pragma: nocover # runs in thread! if process.stderr is not None: for line in TextIOWrapper(process.stderr, encoding="utf-8"): cls._log_capture("stderr", process.pid, line) cls.stderr[process.pid] += line @classmethod def _log_capture(cls, name: str, pid: int, line: str) -> None: # pragma: nocover if not cls.capture_log: return sys.stdout.write(f"[{pid}]{name}>{line}") sys.stdout.flush() @classmethod def _start_output_read_thread(cls, process: subprocess.Popen) -> None: """ Start an output reading thread. :param process: target process passed to a thread args. """ cls.stdout[process.pid] = "" cls.start_thread(target=cls._read_out, process=process) @classmethod def _start_error_read_thread(cls, process: subprocess.Popen) -> None: """ Start an error reading thread. :param process: target process passed to a thread args. """ cls.stderr[process.pid] = "" cls.start_thread(target=cls._read_err, process=process) @classmethod def _get_cwd(cls) -> str: """Get the current working directory.""" return str(cls.t / cls.current_agent_context) @classmethod def send_envelope_to_agent(cls, envelope: Envelope, agent: str) -> None: """Send an envelope to an agent, using the stub connection.""" # check added cause sometimes fails on win with permission error dir_path = Path(cls.t / agent) enforce(dir_path.exists(), "Dir path does not exist.") enforce(dir_path.is_dir(), "Dir path is not a directory.") write_envelope_to_file(envelope, str(cls.t / agent / DEFAULT_INPUT_FILE_NAME)) @classmethod def read_envelope_from_agent(cls, agent: str) -> Envelope: """Read an envelope from an agent, using the stub connection.""" return read_envelope_from_file(str(cls.t / agent / DEFAULT_OUTPUT_FILE_NAME)) @classmethod def missing_from_output( cls, process: subprocess.Popen, strings: Sequence[str], timeout: int = DEFAULT_PROCESS_TIMEOUT, period: int = 1, is_terminating: bool = True, ) -> List[str]: """ Check if strings are present in process output. Read process stdout in thread and terminate when all strings are present or timeout expired. :param process: agent subprocess. :param strings: tuple of strings expected to appear in output. :param timeout: int amount of seconds before stopping check. :param period: int period of checking. :param is_terminating: whether or not the agents are terminated :return: list of missed strings. """ missing_strings = list(strings) end_time = time.time() + timeout while missing_strings: if time.time() > end_time: break missing_strings = [ line for line in missing_strings if line not in cls.stdout[process.pid] ] time.sleep(period) if is_terminating: cls.terminate_agents(process) if missing_strings != []: _default_logger.info( "Non-empty missing strings, stderr:\n{}".format(cls.stderr[process.pid]) ) _default_logger.info("=====================") _default_logger.info( "Non-empty missing strings, stdout:\n{}".format(cls.stdout[process.pid]) ) _default_logger.info("=====================") return missing_strings @classmethod def is_running( cls, process: subprocess.Popen, timeout: int = DEFAULT_LAUNCH_TIMEOUT ) -> bool: """ Check if the AEA is launched and running (ready to process messages). :param process: agent subprocess. :param timeout: the timeout to wait for launch to complete :return: bool indicating status """ missing_strings = cls.missing_from_output( process, (LAUNCH_SUCCEED_MESSAGE,), timeout, is_terminating=False ) return missing_strings == [] @classmethod def invoke(cls, *args: str) -> Result: """Call the cli command.""" with cd(cls._get_cwd()): result = cls.runner.invoke( cli, args, standalone_mode=False, catch_exceptions=False ) return result @classmethod def load_agent_config(cls, agent_name: str) -> AgentConfig: """Load agent configuration.""" if agent_name not in cls.agents: raise AEATestingException( f"Cannot find agent '{agent_name}' in the current test case." ) loader = ConfigLoaders.from_package_type(PackageType.AGENT) config_file_name = _get_default_configuration_file_name_from_type( PackageType.AGENT ) configuration_file_path = Path(cls.t, agent_name, config_file_name) with open_file(configuration_file_path) as file_input: agent_config = loader.load(file_input) return agent_config @classmethod def setup_class(cls) -> None: """Set up the test class.""" cls.method_list = [ func for func in dir(cls) if callable(getattr(cls, func)) and not func.startswith("__") and func.startswith("test_") ] cls.runner = CliRunner() cls.old_cwd = Path(os.getcwd()) cls.subprocesses = [] cls.threads = [] cls.t = Path(tempfile.mkdtemp()) cls.change_directory(cls.t) cls.package_registry_src = cls.old_cwd / cls.package_registry_src_rel if cls.use_packages_dir: registry_tmp_dir = cls.t / cls.packages_dir_path shutil.copytree(str(cls.package_registry_src), str(registry_tmp_dir)) cls.initialize_aea(cls.author) cls.stdout = {} cls.stderr = {} @classmethod def teardown_class(cls) -> None: """Teardown the test.""" cls.change_directory(cls.old_cwd) cls.terminate_agents(*cls.subprocesses) cls._terminate_subprocesses() cls._join_threads() cls.unset_agent_context() cls.last_cli_runner_result = None cls.packages_dir_path = Path(DEFAULT_REGISTRY_NAME) cls.use_packages_dir = True cls.agents = set() cls.current_agent_context = "" cls.stdout = {} cls.stderr = {} with suppress(OSError, IOError): shutil.rmtree(cls.t) cls._is_teardown_class_called = True def _get_password_option_args(password: Optional[str]) -> List[str]: """ Get password option arguments. :param password: the password (optional). :return: empty list if password is None, else ['--password', password]. """ return [] if password is None else ["--password", password] class AEATestCaseEmpty(BaseAEATestCase): """ Test case for a default AEA project. This test case will create a default AEA project. """ agent_name = "" IS_LOCAL = True IS_EMPTY = False @classmethod def setup_class(cls) -> None: """Set up the test class.""" super(AEATestCaseEmpty, cls).setup_class() cls.agent_name = "agent_" + "".join( random.choices(string.ascii_lowercase, k=5) # nosec ) cls.create_agents(cls.agent_name, is_local=cls.IS_LOCAL, is_empty=cls.IS_EMPTY) cls.set_agent_context(cls.agent_name) @classmethod def teardown_class(cls) -> None: """Teardown the test class.""" super(AEATestCaseEmpty, cls).teardown_class() cls.agent_name = "" class AEATestCaseEmptyFlaky(AEATestCaseEmpty): """ Test case for a default AEA project. This test case will create a default AEA project. Use for flaky tests with the flaky decorator. """ run_count: int = 0 @classmethod def setup_class(cls) -> None: """Set up the test class.""" super(AEATestCaseEmptyFlaky, cls).setup_class() if len(cls.method_list) > 1: # pragma: nocover raise ValueError(f"{cls.__name__} can only contain one test method!") cls.run_count += 1 @classmethod def teardown_class(cls) -> None: """Teardown the test class.""" super(AEATestCaseEmptyFlaky, cls).teardown_class() class AEATestCaseMany(BaseAEATestCase): """Test case for many AEA projects.""" @classmethod def setup_class(cls) -> None: """Set up the test class.""" super(AEATestCaseMany, cls).setup_class() @classmethod def teardown_class(cls) -> None: """Teardown the test class.""" super(AEATestCaseMany, cls).teardown_class() class AEATestCaseManyFlaky(AEATestCaseMany): """ Test case for many AEA projects which are flaky. Use for flaky tests with the flaky decorator. """ run_count: int = 0 @classmethod def setup_class(cls) -> None: """Set up the test class.""" super(AEATestCaseManyFlaky, cls).setup_class() if len(cls.method_list) > 1: # pragma: nocover raise ValueError(f"{cls.__name__} can only contain one test method!") cls.run_count += 1 @classmethod def teardown_class(cls) -> None: """Teardown the test class.""" super(AEATestCaseManyFlaky, cls).teardown_class() class AEATestCase(BaseAEATestCase): """ Test case from an existing AEA project. Subclass this class and set `path_to_aea` properly. By default, it is assumed the project is inside the current working directory. """ agent_name = "" path_to_aea: Path = Path(".") packages_dir_path: Path = Path("..", DEFAULT_REGISTRY_NAME) agent_configuration: Optional[AgentConfig] = None t: Path # temporary directory path @classmethod def setup_class(cls) -> None: """Set up the test class.""" # make paths absolute cls.path_to_aea = cls.path_to_aea.absolute() # load agent configuration with Path(cls.path_to_aea, DEFAULT_AEA_CONFIG_FILE).open( mode="r", encoding="utf-8" ) as fp: loader = ConfigLoader.from_configuration_type(PackageType.AGENT) agent_configuration = loader.load(fp) cls.agent_configuration = agent_configuration cls.agent_name = agent_configuration.agent_name # this will create a temporary directory and move into it cls.use_packages_dir = False super(AEATestCase, cls).setup_class() # copy the content of the agent into the temporary directory shutil.copytree(str(cls.path_to_aea), str(cls.t / cls.agent_name)) cls.set_agent_context(cls.agent_name) @classmethod def teardown_class(cls) -> None: """Teardown the test class.""" cls.agent_name = "" cls.path_to_aea = Path(".") cls.agent_configuration = None super(AEATestCase, cls).teardown_class() ================================================ FILE: aea/test_tools/test_contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains test case classes based on pytest for AEA contract testing.""" import time from abc import ABC, abstractmethod from pathlib import Path from typing import Any, Dict, List, Optional, cast from aea.common import JSONLike from aea.configurations.loader import ( ComponentType, ContractConfig, load_component_configuration, ) from aea.contracts.base import Contract, contract_registry from aea.crypto.base import Crypto, FaucetApi, LedgerApi from aea.crypto.registries import ( crypto_registry, faucet_apis_registry, ledger_apis_registry, ) class BaseContractTestCase(ABC): """A class to test a contract.""" path_to_contract: Path = Path(".") ledger_identifier: str = "" fund_from_faucet: bool = False _deployment_gas: int = 5000000 _contract: Contract ledger_api: LedgerApi deployer_crypto: Crypto item_owner_crypto: Crypto faucet_api: FaucetApi deployment_tx_receipt: JSONLike contract_address: str @property def contract(self) -> Contract: """Get the contract.""" try: value = self._contract except AttributeError: raise ValueError("Ensure the contract is set during setup.") return value @classmethod def setup(cls, **kwargs: Any) -> None: """Set up the contract test case.""" if cls.ledger_identifier == "": raise ValueError("ledger_identifier not set!") # pragma: nocover _ledger_config: Dict[str, str] = kwargs.pop("ledger_config", {}) _deployer_private_key_path: Optional[str] = kwargs.pop( "deployer_private_key_path", None ) _item_owner_private_key_path: Optional[str] = kwargs.pop( "item_owner_private_key_path", None ) cls.ledger_api = ledger_apis_registry.make( cls.ledger_identifier, **_ledger_config ) cls.deployer_crypto = crypto_registry.make( cls.ledger_identifier, private_key_path=_deployer_private_key_path ) cls.item_owner_crypto = crypto_registry.make( cls.ledger_identifier, private_key_path=_item_owner_private_key_path ) cls.faucet_api = faucet_apis_registry.make(cls.ledger_identifier) # Fund from faucet if cls.fund_from_faucet: # Refill deployer account from faucet cls.refill_from_faucet( cls.ledger_api, cls.faucet_api, cls.deployer_crypto.address ) # Refill item owner account from faucet cls.refill_from_faucet( cls.ledger_api, cls.faucet_api, cls.item_owner_crypto.address ) # register contract configuration = cast( ContractConfig, load_component_configuration(ComponentType.CONTRACT, cls.path_to_contract), ) configuration._directory = ( # pylint: disable=protected-access cls.path_to_contract ) if str(configuration.public_id) not in contract_registry.specs: # load contract into sys modules Contract.from_config(configuration) # pragma: nocover cls._contract = contract_registry.make(str(configuration.public_id)) # deploy contract cls.deployment_tx_receipt = cls._deploy_contract( cls._contract, cls.ledger_api, cls.deployer_crypto, gas=cls._deployment_gas, ) cls.contract_address = cls.finish_contract_deployment() @classmethod @abstractmethod def finish_contract_deployment(cls) -> str: """ Finish deploying contract. :return: contract address """ @staticmethod def refill_from_faucet( ledger_api: LedgerApi, faucet_api: FaucetApi, address: str ) -> None: """Refill from faucet.""" start_balance = ledger_api.get_balance(address) faucet_api.get_wealth(address) balance = ledger_api.get_balance(address) if balance == start_balance: raise ValueError("Balance not increased!") # pragma: nocover @staticmethod def sign_send_confirm_receipt_multisig_transaction( tx: JSONLike, ledger_api: LedgerApi, cryptos: List[Crypto], sleep_time: float = 2.0, ) -> JSONLike: """ Sign, send and confirm settlement of a transaction with multiple signatures. :param tx: the transaction :param ledger_api: the ledger api :param cryptos: Cryptos to sign transaction with :param sleep_time: the time to sleep between transaction submission and receipt request :return: The transaction receipt """ for crypto in cryptos: tx = crypto.sign_transaction(tx) tx_digest = ledger_api.send_signed_transaction(tx) if tx_digest is None: raise ValueError("Transaction digest not found!") # pragma: nocover tx_receipt = ledger_api.get_transaction_receipt(tx_digest) not_settled = True elapsed_time = 0 while not_settled and elapsed_time < 20: elapsed_time += 1 time.sleep(sleep_time) tx_receipt = ledger_api.get_transaction_receipt(tx_digest) if tx_receipt is None: continue not_settled = not ledger_api.is_transaction_settled(tx_receipt) if tx_receipt is None: raise ValueError("Transaction receipt not found!") # pragma: nocover if not_settled: raise ValueError( # pragma: nocover f"Transaction receipt not valid!\n{tx_receipt['raw_log']}" ) return tx_receipt @classmethod # noqa def sign_send_confirm_receipt_transaction( cls, tx: JSONLike, ledger_api: LedgerApi, crypto: Crypto, sleep_time: float = 2.0, ) -> JSONLike: """ Sign, send and confirm settlement of a transaction with multiple signatures. :param tx: the transaction :param ledger_api: the ledger api :param crypto: Crypto to sign transaction with :param sleep_time: the time to sleep between transaction submission and receipt request :return: The transaction receipt """ # BACKWARDS COMPATIBILITY: This method supports only 1 signer and is kept for backwards compatibility. # new method sign_send_confirm_receipt_multisig_transaction should be used always instead of this one. return cls.sign_send_confirm_receipt_multisig_transaction( tx, ledger_api, [crypto], sleep_time ) @classmethod def _deploy_contract( cls, contract: Contract, ledger_api: LedgerApi, deployer_crypto: Crypto, gas: int, ) -> JSONLike: """ Deploy contract on network. :param contract: the contract :param ledger_api: the ledger api :param deployer_crypto: the contract deployer crypto :param gas: the gas amount :return: the transaction receipt for initial transaction deployment """ tx = contract.get_deploy_transaction( ledger_api=ledger_api, deployer_address=deployer_crypto.address, gas=gas, ) if tx is None: raise ValueError("Deploy transaction not found!") # pragma: nocover tx_receipt = cls.sign_send_confirm_receipt_multisig_transaction( tx, ledger_api, [deployer_crypto] ) return tx_receipt ================================================ FILE: aea/test_tools/test_skill.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains test case classes based on pytest for AEA skill testing.""" import asyncio import os from pathlib import Path from queue import Queue from types import SimpleNamespace from typing import Any, Dict, Optional, Tuple, Type, cast from aea.configurations.loader import ConfigLoaders, PackageType, SkillConfig from aea.context.base import AgentContext from aea.crypto.ledger_apis import DEFAULT_CURRENCY_DENOMINATIONS from aea.exceptions import AEAEnforceError from aea.helpers.io import open_file from aea.identity.base import Identity from aea.mail.base import Address from aea.multiplexer import AsyncMultiplexer, Multiplexer, OutBox from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueMessage, Dialogues from aea.skills.base import Skill from aea.skills.tasks import TaskManager COUNTERPARTY_AGENT_ADDRESS = "counterparty" COUNTERPARTY_SKILL_ADDRESS = "some_author/some_skill:0.1.0" class BaseSkillTestCase: """A class to test a skill.""" path_to_skill: Path = Path(".") is_agent_to_agent_messages: bool = True _skill: Skill _multiplexer: AsyncMultiplexer _outbox: OutBox @property def skill(self) -> Skill: """Get the skill.""" try: value = self._skill except AttributeError: raise ValueError("Ensure skill is set during setup_class.") return value def get_quantity_in_outbox(self) -> int: """Get the quantity of envelopes in the outbox.""" return self._multiplexer.out_queue.qsize() def get_message_from_outbox(self) -> Optional[Message]: """Get message from outbox.""" if self._outbox.empty(): return None envelope = self._multiplexer.out_queue.get_nowait() return envelope.message def drop_messages_from_outbox(self, number: int = 1) -> None: """Dismiss the first 'number' number of message from outbox.""" while (not self._outbox.empty()) and number != 0: self._multiplexer.out_queue.get_nowait() number -= 1 def get_quantity_in_decision_maker_inbox(self) -> int: """Get the quantity of messages in the decision maker inbox.""" return self._skill.skill_context.decision_maker_message_queue.qsize() def get_message_from_decision_maker_inbox(self) -> Optional[Message]: """Get message from decision maker inbox.""" if self._skill.skill_context.decision_maker_message_queue.empty(): return None return self._skill.skill_context.decision_maker_message_queue.get_nowait() def drop_messages_from_decision_maker_inbox(self, number: int = 1) -> None: """Dismiss the first 'number' number of message from decision maker inbox.""" while ( not self._skill.skill_context.decision_maker_message_queue.empty() ) and number != 0: self._skill.skill_context.decision_maker_message_queue.get_nowait() number -= 1 def assert_quantity_in_outbox(self, expected_quantity: int) -> None: """Assert the quantity of messages in the outbox.""" quantity = self.get_quantity_in_outbox() assert ( # nosec quantity == expected_quantity ), f"Invalid number of messages in outbox. Expected {expected_quantity}. Found {quantity}." def assert_quantity_in_decision_making_queue(self, expected_quantity: int) -> None: """Assert the quantity of messages in the decision maker queue.""" quantity = self.get_quantity_in_decision_maker_inbox() assert ( # nosec quantity == expected_quantity ), f"Invalid number of messages in decision maker queue. Expected {expected_quantity}. Found {quantity}." @staticmethod def message_has_attributes( actual_message: Message, message_type: Type[Message], **kwargs: Any, ) -> Tuple[bool, str]: """ Evaluates whether a message's attributes match the expected attributes provided. :param actual_message: the actual message :param message_type: the expected message type :param kwargs: other expected message attributes :return: boolean result of the evaluation and accompanied message """ if ( type(actual_message) # pylint: disable=unidiomatic-typecheck != message_type ): return ( False, "The message types do not match. Actual type: {}. Expected type: {}".format( type(actual_message), message_type ), ) for attribute_name, expected_value in kwargs.items(): actual_value = getattr(actual_message, attribute_name) if actual_value != expected_value: return ( False, f"The '{attribute_name}' fields do not match. Actual '{attribute_name}': {actual_value}. Expected '{attribute_name}': {expected_value}", ) return True, "The message has the provided expected attributes." def build_incoming_message( self, message_type: Type[Message], performative: Message.Performative, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Optional[Address] = None, is_agent_to_agent_messages: Optional[bool] = None, **kwargs: Any, ) -> Message: """ Quickly create an incoming message with the provided attributes. For any attribute not provided, the corresponding default value in message is used. :param message_type: the type of the message :param dialogue_reference: the dialogue_reference :param message_id: the message_id :param target: the target :param performative: the performative :param to: the 'to' address :param sender: the 'sender' address :param is_agent_to_agent_messages: whether the dialogue is between agents or components :param kwargs: other attributes :return: the created incoming message """ if is_agent_to_agent_messages is None: is_agent_to_agent_messages = self.is_agent_to_agent_messages if sender is None: sender = ( COUNTERPARTY_AGENT_ADDRESS if is_agent_to_agent_messages else COUNTERPARTY_SKILL_ADDRESS ) message_attributes = {} # type: Dict[str, Any] default_dialogue_reference = Dialogues.new_self_initiated_dialogue_reference() dialogue_reference = ( default_dialogue_reference if dialogue_reference is None else dialogue_reference ) message_attributes["dialogue_reference"] = dialogue_reference if message_id is not None: message_attributes["message_id"] = message_id if target is not None: message_attributes["target"] = target message_attributes["performative"] = performative message_attributes.update(kwargs) incoming_message = message_type(**message_attributes) incoming_message.sender = sender default_to = ( self.skill.skill_context.agent_address if is_agent_to_agent_messages else str(self.skill.public_id) ) incoming_message.to = default_to if to is None else to return incoming_message def build_incoming_message_for_skill_dialogue( self, dialogue: Dialogue, performative: Message.Performative, message_type: Optional[Type[Message]] = None, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Optional[Address] = None, **kwargs: Any, ) -> Message: """ Quickly create an incoming message with the provided attributes for a dialogue. For any attribute not provided, a value based on the dialogue is used. These values are shown in parentheses in the list of parameters below. NOTE: This method must be used with care. The dialogue provided is part of the skill which is being tested. Because for any unspecified attribute, a "correct" value is used, the test will be, by design, insured to pass on these values. :param dialogue: the dialogue to which the incoming message is intended :param performative: the performative of the message :param message_type: (the message_class of the provided dialogue) the type of the message :param dialogue_reference: (the dialogue_reference of the provided dialogue) the dialogue reference of the message :param message_id: (the id of the last message in the provided dialogue + 1) the id of the message :param target: (the id of the last message in the provided dialogue) the target of the message :param to: (the agent address associated with this skill) the receiver of the message :param sender: (the counterparty in the provided dialogue) the sender of the message :param kwargs: other attributes :return: the created incoming message """ if dialogue is None: raise AEAEnforceError("dialogue cannot be None.") if dialogue.last_message is None: raise AEAEnforceError("dialogue cannot be empty.") message_type = ( message_type if message_type is not None else dialogue.message_class ) dialogue_reference = ( dialogue_reference if dialogue_reference is not None else dialogue.dialogue_label.dialogue_reference ) message_id = ( message_id if message_id is not None else dialogue.get_incoming_next_message_id() ) target = target if target is not None else dialogue.last_message.message_id to = to if to is not None else dialogue.self_address sender = ( sender if sender is not None else dialogue.dialogue_label.dialogue_opponent_addr ) incoming_message = self.build_incoming_message( message_type=message_type, performative=performative, dialogue_reference=dialogue_reference, message_id=message_id, target=target, to=to, sender=sender, **kwargs, ) return incoming_message @staticmethod def _provide_unspecified_fields( message: DialogueMessage, last_is_incoming: Optional[bool] ) -> Tuple[bool, Optional[int]]: """ Specifies values (an interpretation) for the unspecified fields of a DialogueMessage. For an unspecified is_incoming, the opposite of the last_is_incoming value is used. For an unspecified target, the message_id of the previous message (message_id - 1) is used. :param message: the DialogueMessage :param last_is_incoming: the is_incoming value of the previous DialogueMessage :return: the is_incoming and target values """ default_is_incoming = not last_is_incoming is_incoming = default_is_incoming if message[2] is None else message[2] default_target = None target = default_target if message[3] is None else message[3] return is_incoming, target @staticmethod def _non_initial_incoming_message_dialogue_reference( dialogue: Dialogue, ) -> Tuple[str, str]: """ Specifies the dialogue reference of a non-initial incoming message for a dialogue. It uses a complete version of the reference in the dialogue if it is incomplete, otherwise it uses the reference in the dialogue. :param dialogue: the dialogue to which the incoming message is intended :return: its dialogue reference """ dialogue_reference = ( dialogue.dialogue_label.dialogue_reference[0], Dialogues._generate_dialogue_nonce() # pylint: disable=protected-access if dialogue.dialogue_label.dialogue_reference[1] == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE else dialogue.dialogue_label.dialogue_reference[1], ) return dialogue_reference def _extract_message_fields( self, message: DialogueMessage, index: int, last_is_incoming: bool, ) -> Tuple[Message.Performative, Dict, int, bool, Optional[int]]: """ Extracts message attributes from a dialogue message. :param message: the dialogue message :param index: the index of this dialogue message in the sequence of messages :param last_is_incoming: the is_incoming of the last message in the sequence :return: the performative, contents, message_id, is_incoming, target of the message """ performative = message[0] contents = message[1] message_id = index + 1 is_incoming, target = self._provide_unspecified_fields( message, last_is_incoming=last_is_incoming ) return performative, contents, message_id, is_incoming, target def prepare_skill_dialogue( self, dialogues: Dialogues, messages: Tuple[DialogueMessage, ...], counterparty: Optional[Address] = None, is_agent_to_agent_messages: Optional[bool] = None, ) -> Dialogue: """ Quickly create a dialogue. The 'messages' argument is a tuple of DialogueMessages. For every DialogueMessage (performative, contents, is_incoming, target): - if 'is_incoming' is not provided: for the first message it is assumed False (outgoing), for any other message, it is the opposite of the one preceding it. - if 'target' is not provided: for the first message it is assumed 0, for any other message, it is the index of the message before it in the tuple of messages + 1. :param dialogues: a dialogues class :param counterparty: the message_id :param messages: the dialogue_reference :param is_agent_to_agent_messages: whether the dialogue is between agents or components :return: the created incoming message """ if is_agent_to_agent_messages is None: is_agent_to_agent_messages = self.is_agent_to_agent_messages if counterparty is None: counterparty = ( # pragma: nocover COUNTERPARTY_AGENT_ADDRESS if is_agent_to_agent_messages else COUNTERPARTY_SKILL_ADDRESS ) if len(messages) == 0: raise AEAEnforceError("the list of messages must be positive.") ( performative, contents, message_id, is_incoming, target, ) = self._extract_message_fields(messages[0], index=0, last_is_incoming=True) if is_incoming: # first message from the opponent dialogue_reference = dialogues.new_self_initiated_dialogue_reference() message = self.build_incoming_message( message_type=dialogues.message_class, dialogue_reference=dialogue_reference, message_id=Dialogue.STARTING_MESSAGE_ID, target=target or Dialogue.STARTING_TARGET, performative=performative, to=dialogues.self_address, sender=counterparty, is_agent_to_agent_messages=is_agent_to_agent_messages, **contents, ) dialogue = cast(Dialogue, dialogues.update(message)) if dialogue is None: raise AEAEnforceError( "Cannot update the dialogue with message number {}".format( message_id ) ) else: # first message from self _, dialogue = dialogues.create( counterparty=counterparty, performative=performative, **contents ) for idx, dialogue_message in enumerate(messages[1:]): ( performative, contents, message_id, is_incoming, target, ) = self._extract_message_fields(dialogue_message, idx + 1, is_incoming) if target is None: target = cast(Message, dialogue.last_message).message_id if is_incoming: # messages from the opponent dialogue_reference = ( self._non_initial_incoming_message_dialogue_reference(dialogue) ) message_id = dialogue.get_incoming_next_message_id() message = self.build_incoming_message( message_type=dialogues.message_class, dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=performative, to=dialogues.self_address, sender=counterparty, is_agent_to_agent_messages=is_agent_to_agent_messages, **contents, ) dialogue = cast(Dialogue, dialogues.update(message)) if dialogue is None: raise AEAEnforceError( "Cannot update the dialogue with message number {}".format( message_id ) ) else: # messages from self dialogue.reply(performative=performative, target=target, **contents) return dialogue @classmethod def setup(cls, **kwargs: Any) -> None: """Set up the skill test case.""" identity = Identity( "test_agent_name", "test_agent_address", "test_agent_public_key" ) cls._multiplexer = AsyncMultiplexer() cls._multiplexer._out_queue = ( # pylint: disable=protected-access asyncio.Queue() ) cls._outbox = OutBox(cast(Multiplexer, cls._multiplexer)) _shared_state = cast(Optional[Dict[str, Any]], kwargs.pop("shared_state", None)) _skill_config_overrides = cast( Optional[Dict[str, Any]], kwargs.pop("config_overrides", None) ) _dm_context_kwargs = cast( Dict[str, Any], kwargs.pop("dm_context_kwargs", dict()) ) agent_context = AgentContext( identity=identity, connection_status=cls._multiplexer.connection_status, outbox=cls._outbox, decision_maker_message_queue=Queue(), decision_maker_handler_context=SimpleNamespace(**_dm_context_kwargs), task_manager=TaskManager(), default_ledger_id=identity.default_address_key, currency_denominations=DEFAULT_CURRENCY_DENOMINATIONS, default_connection=None, default_routing={}, search_service_address="dummy_author/dummy_search_skill:0.1.0", decision_maker_address="dummy_decision_maker_address", data_dir=os.getcwd(), ) # Pre-populate the 'shared_state' prior to loading the skill if _shared_state is not None: for key, value in _shared_state.items(): agent_context.shared_state[key] = value skill_configuration_file_path: Path = Path(cls.path_to_skill, "skill.yaml") loader = ConfigLoaders.from_package_type(PackageType.SKILL) with open_file(skill_configuration_file_path) as fp: skill_config: SkillConfig = loader.load(fp) # Override skill's config prior to loading if _skill_config_overrides is not None: skill_config.update(_skill_config_overrides) skill_config.directory = cls.path_to_skill cls._skill = Skill.from_config(skill_config, agent_context) ================================================ FILE: benchmark/Dockerfile ================================================ FROM python:3.8-alpine ENV PYTHONPATH "$PYTHONPATH:/usr/lib/python3.8/site-packages" RUN apk add --no-cache make git bash wget RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev RUN apk add --update --no-cache py3-numpy py3-scipy py3-pillow RUN apk add --update --no-cache gfortran freetype-dev libpng-dev openblas-dev g++ py3-numpy-dev RUN apk add --no-cache go rust cargo RUN pip install --upgrade pip pipenv RUN pip install aea[all] --upgrade --force-reinstall RUN wget https://raw.githubusercontent.com/fetchai/agents-aea/main/Pipfile RUN pipenv install -d --deploy --skip-lock --system RUN pip install --no-deps aea-ledger-fetchai RUN pip install --no-deps aea-ledger-cosmos RUN pip install --no-deps aea-ledger-ethereum ================================================ FILE: benchmark/README.md ================================================ # Benchmarks ## Running a benchmark locally First, set permissions: ``` bash chmod +x /benchmark/checks/run_benchmark.sh ``` Then, run: ``` bash ./benchmark/checks/run_benchmark.sh ``` or to save to file: ``` bash ./benchmark/checks/run_benchmark.sh | tee benchmark.txt ``` The benchmark will use the locally installed AEA version! ## Deploying a benchmark run and serving results First remove any old configuration maps and create a new one: ``` bash kubectl delete configmap run-benchmark kubectl create configmap run-benchmark --from-file=run_from_branch.sh ``` To remove old nodes (auto-restarts new node): ``` bash kubectl delete pod NODE_NAME ``` To completely remove: ``` bash kubectl delete deployment benchmark ``` To deploy: ``` bash kubectl apply -f benchmark-deployment.yaml ``` List pods: ``` bash kubectl get pod -o wide ``` To access NGINX (wait for status: ``): ``` bash kubectl port-forward NODE_NAME 8000:80 ``` then ``` bash curl localhost:8000 | tee results.txt ``` ================================================ FILE: benchmark/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Performance testing framework.""" ================================================ FILE: benchmark/benchmark-deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: benchmark spec: selector: matchLabels: app: benchmark replicas: 1 template: metadata: labels: app: benchmark namespace: aea-research spec: tolerations: - key: dedicated operator: Equal value: agent effect: NoSchedule nodeSelector: # type: agent-test kubernetes.io/os: linux initContainers: - name: python image: python:3.8-buster command: ['sh', '-c', 'bash /app/run_from_branch.sh | tee /data/index.html'] volumeMounts: - name: benchmark-data mountPath: /data - name: benchmark-script mountPath: /app containers: - name: benchmark image: nginx:1.18.0 volumeMounts: - name: benchmark-data mountPath: /usr/share/nginx/html - name: benchmark-script mountPath: /app volumes: - name: benchmark-data persistentVolumeClaim: claimName: benchmark-vol - name: benchmark-script persistentVolumeClaim: claimName: benchmark-scripts-vol --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: benchmark-vol spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: benchmark-scripts-vol spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi ================================================ FILE: benchmark/cases/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Simple cases for performance tests.""" ================================================ FILE: benchmark/cases/cpu_burn.py ================================================ #!/usr/bin/ev python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Example performance test using benchmark framework. Just test CPU usage with empty while loop.""" import time from benchmark.framework.benchmark import BenchmarkControl from benchmark.framework.cli import TestCli def cpu_burn( benchmark: BenchmarkControl, run_time: int = 10, sleep: float = 0.0001 ) -> None: """ Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop """ benchmark.start() start_time = time.time() while True: time.sleep(sleep) if time.time() - start_time >= run_time: break if __name__ == "__main__": TestCli(cpu_burn).run() ================================================ FILE: benchmark/cases/helpers/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Helpers to make cases easier.""" ================================================ FILE: benchmark/cases/helpers/dummy_handler.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Dummy handler to use in test skills.""" from random import randint from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage class DummyHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Noop setup.""" def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message, actually noop.""" randint(1, 100) + randint( # nosec # pylint: disable=expression-not-assigned 1, 100 ) ================================================ FILE: benchmark/cases/react_multi_agents_fake_connection.py ================================================ #!/usr/bin/ev python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Example performance test using benchmark framework. Test react speed on amount of incoming messages using normal agent operating. Messages are generated by fake connection. """ import time from aea.configurations.base import SkillConfig from aea.skills.base import Skill from benchmark.cases.helpers.dummy_handler import DummyHandler from benchmark.framework.aea_test_wrapper import AEATestWrapper from benchmark.framework.benchmark import BenchmarkControl from benchmark.framework.cli import TestCli def _make_custom_config(name: str = "dummy_agent", skills_num: int = 1) -> dict: """ Construct config for test wrapper. :param name: agent's name :param skills_num: number of skills to add to agent :return: dict to be used in AEATestWrapper(**result) """ # noqa def _make_skill(id_: int) -> Skill: return AEATestWrapper.make_skill( config=SkillConfig(name=f"sc{id_}", author="fetchai"), handlers={"dummy_handler": DummyHandler}, ) return { "name": name, "components": [_make_skill(i) for i in range(skills_num)], } def react_speed_in_loop( benchmark: BenchmarkControl, agents_num: int = 1, skills_num: int = 1, inbox_num: int = 5000, agent_loop_timeout: float = 0.01, ) -> None: """ Test inbox message processing in a loop. Messages are generated by fake connection. :param benchmark: benchmark special parameter to communicate with executor :param agents_num: number of agents to start :param skills_num: number of skills to add to each agent :param inbox_num: number of inbox messages for every agent :param agent_loop_timeout: idle sleep time for agent's loop """ wrappers = [] envelope = AEATestWrapper.dummy_envelope() for i in range(agents_num): aea_test_wrapper = AEATestWrapper( **_make_custom_config(f"agent{i}", skills_num) ) aea_test_wrapper.set_loop_timeout(agent_loop_timeout) aea_test_wrapper.set_fake_connection(inbox_num, envelope) wrappers.append(aea_test_wrapper) benchmark.start() for aea_test_wrapper in wrappers: aea_test_wrapper.start_loop() try: # wait all messages are pushed to inboxes while sum(i.is_messages_in_fake_connection() for i in wrappers): time.sleep(0.01) # wait all messages are consumed from inboxes while sum(not i.is_inbox_empty() for i in wrappers): time.sleep(0.01) finally: for aea_test_wrapper in wrappers: aea_test_wrapper.stop_loop() if __name__ == "__main__": TestCli(react_speed_in_loop).run() ================================================ FILE: benchmark/cases/react_speed_in_loop.py ================================================ #!/usr/bin/ev python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Example performance test using benchmark framework. Test react speed on amount of incoming messages using normal agent operating.""" import time from benchmark.cases.helpers.dummy_handler import DummyHandler from benchmark.framework.aea_test_wrapper import AEATestWrapper from benchmark.framework.benchmark import BenchmarkControl from benchmark.framework.cli import TestCli def react_speed_in_loop(benchmark: BenchmarkControl, inbox_amount: int = 1000) -> None: """ Test inbox message processing in a loop. :param benchmark: benchmark special parameter to communicate with executor :param inbox_amount: number of inbox messages for every agent """ aea_test_wrapper = AEATestWrapper( name="dummy agent", components=[ AEATestWrapper.make_skill(handlers={"dummy_handler": DummyHandler}) ], ) for _ in range(inbox_amount): aea_test_wrapper.put_inbox(aea_test_wrapper.dummy_envelope()) aea_test_wrapper.set_loop_timeout(0.0) benchmark.start() aea_test_wrapper.start_loop() while not aea_test_wrapper.is_inbox_empty(): time.sleep(0.1) aea_test_wrapper.stop_loop() if __name__ == "__main__": TestCli(react_speed_in_loop).run() ================================================ FILE: benchmark/cases/react_speed_multi_agents.py ================================================ #!/usr/bin/ev python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Example performance test using benchmark framework. Test react speed on amount of incoming messages using normal agent operating.""" import time from aea.configurations.base import SkillConfig from aea.skills.base import Skill from benchmark.cases.helpers.dummy_handler import DummyHandler from benchmark.framework.aea_test_wrapper import AEATestWrapper from benchmark.framework.benchmark import BenchmarkControl from benchmark.framework.cli import TestCli def _make_custom_config(name: str = "dummy_agent", skills_num: int = 1) -> dict: """ Construct config for test wrapper. :param name: agent's name :param skills_num: number of skills to add to agent :return: dict to be used in AEATestWrapper(**result) """ # noqa def _make_skill(id_: int) -> Skill: return AEATestWrapper.make_skill( config=SkillConfig(name=f"sc{id_}", author="fetchai"), handlers={"dummy_handler": DummyHandler}, ) return { "name": name, "components": [_make_skill(i) for i in range(skills_num)], } def react_speed_in_loop( benchmark: BenchmarkControl, agents_num: int = 2, skills_num: int = 1, inbox_num: int = 1000, agent_loop_timeout: float = 0.01, ) -> None: """ Test inbox message processing in a loop. :param benchmark: benchmark special parameter to communicate with executor :param agents_num: number of agents to start :param skills_num: number of skills to add to each agent :param inbox_num: number of inbox messages for every agent :param agent_loop_timeout: idle sleep time for agent's loop """ aea_test_wrappers = [] for i in range(agents_num): aea_test_wrapper = AEATestWrapper( **_make_custom_config(f"agent{i}", skills_num) ) aea_test_wrapper.set_loop_timeout(agent_loop_timeout) aea_test_wrappers.append(aea_test_wrapper) for _ in range(inbox_num): aea_test_wrapper.put_inbox(aea_test_wrapper.dummy_envelope()) benchmark.start() for aea_test_wrapper in aea_test_wrappers: aea_test_wrapper.start_loop() try: while sum((not i.is_inbox_empty() for i in aea_test_wrappers)): time.sleep(0.1) finally: # wait to start, Race condition in case no messages to process while sum(not i.is_running() for i in aea_test_wrappers): pass for aea_test_wrapper in aea_test_wrappers: aea_test_wrapper.stop_loop() if __name__ == "__main__": TestCli(react_speed_in_loop).run() ================================================ FILE: benchmark/checks/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Performance checks..""" ================================================ FILE: benchmark/checks/check_agent_construction_time.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Check amount of time and mem for agent setup.""" import os import shutil import time from pathlib import Path from statistics import mean from tempfile import TemporaryDirectory from typing import Any, List, Tuple, Union import click from click.testing import CliRunner from aea.aea_builder import AEABuilder from aea.cli.core import cli from benchmark.checks.utils import ( get_mem_usage_in_mb, multi_run, number_of_runs_deco, output_format_deco, print_results, ) PACKAGES = Path(__file__).parent / "../../packages" PROJECT_PATH = str(PACKAGES / "fetchai/agents/my_first_aea") def run(agents: int) -> List[Tuple[str, Union[int, float]]]: """Check construction time and memory usage.""" load_times = [] full_times = [] with TemporaryDirectory() as tmp_dir: agent_dir = Path(tmp_dir) / "agent" shutil.copytree(PROJECT_PATH, agent_dir) shutil.copytree(PACKAGES, agent_dir / "vendor") os.chdir(agent_dir) if ( CliRunner() .invoke(cli, ["generate-key", "fetchai"], catch_exceptions=False) .exit_code != 0 ): raise Exception("generate-key failed") if ( CliRunner() .invoke(cli, ["add-key", "fetchai"], catch_exceptions=False) .exit_code != 0 ): raise Exception("add-key failed") agents_list = [] env_mem_usage = get_mem_usage_in_mb() for _ in range(agents): start_time = time.time() builder = AEABuilder.from_aea_project(agent_dir) load_times.append(time.time() - start_time) agents_list.append(builder.build()) full_times.append(time.time() - start_time) mem_usage = get_mem_usage_in_mb() return [ ("avg config load time", mean(load_times)), ("avg full construction", mean(full_times)), ("avg build time", mean(full_times) - mean(load_times)), ("agent mem usage (Mb)", mem_usage - env_mem_usage), ] @click.command() @click.option("--agents", default=25, help="Amount of agents to construct.") @number_of_runs_deco @output_format_deco def main(agents: int, number_of_runs: int, output_format: str) -> Any: """Check agents construction time and memory usage.""" parameters = {"Agents": agents, "Number of runs": number_of_runs} def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (agents,), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_decision_maker.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Memory usage check.""" import time from pathlib import Path from tempfile import TemporaryDirectory from typing import Any, List, Tuple, Union from unittest.mock import patch import click from aea.common import Address from aea.configurations.data_types import PublicId from aea.crypto.registries import make_crypto, make_ledger_api from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMaker from aea.decision_maker.default import DecisionMakerHandler from aea.helpers.transaction.base import RawTransaction, Terms from aea.identity.base import Identity from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from benchmark.checks.utils import ( get_mem_usage_in_mb, multi_run, number_of_runs_deco, output_format_deco, print_results, ) from packages.fetchai.protocols.signing.dialogues import SigningDialogue from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage class SigningDialogues(BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: Address) -> None: # pylint: disable=useless-return """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return SigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) return None def make_desc_maker_wallet( ledger_id: str, key_path: str ) -> Tuple[DecisionMaker, Wallet]: """Construct decision maker and wallet.""" wallet = Wallet({ledger_id: key_path}) agent_name = "test" identity = Identity( agent_name, addresses=wallet.addresses, default_address_key=ledger_id, ) config = {} # type: ignore decision_maker_handler = DecisionMakerHandler( identity=identity, wallet=wallet, config=config ) decision_maker = DecisionMaker(decision_maker_handler) return decision_maker, wallet def sign_txs( decision_maker: DecisionMaker, wallet: Wallet, num_runs: int, ledger_id: str ) -> float: """Sign txs sprcified amount fo runs and return time taken (seconds).""" amount = 10000 fc2 = make_crypto(ledger_id) sender_address = wallet.addresses[ledger_id] ledger_api = make_ledger_api(ledger_id) if ledger_id == "fetchai": with patch( "aea_ledger_fetchai._cosmos._CosmosApi._try_get_account_number_and_sequence", return_value=(987, 0), ), patch( "aea_ledger_fetchai._cosmos._CosmosApi.get_balance", return_value=100000 ): transfer_transaction = ledger_api.get_transfer_transaction( sender_address=sender_address, destination_address=fc2.address, amount=amount, tx_fee=1000, tx_nonce="something", ) elif ledger_id == "cosmos": with patch( "aea_ledger_cosmos.cosmos._CosmosApi._try_get_account_number_and_sequence", return_value=(987, 0), ), patch( "aea_ledger_cosmos.cosmos._CosmosApi.get_balance", return_value=100000 ): transfer_transaction = ledger_api.get_transfer_transaction( sender_address=sender_address, destination_address=fc2.address, amount=amount, tx_fee=1000, tx_nonce="something", ) elif ledger_id == "ethereum": transfer_transaction = {"gasPrice": 30, "nonce": 1, "gas": 20000} else: raise ValueError("Ledger not supported!") signing_dialogues = SigningDialogues(str(PublicId("author", "a_skill", "0.1.0"))) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=ledger_id, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_transaction=RawTransaction(ledger_id, transfer_transaction), # type: ignore ) start_time = time.time() for _ in range(num_runs): signing_msg._sender = None # pylint: disable=protected-access signing_msg._to = None # pylint: disable=protected-access signing_msg._slots.dialogue_reference = ( # type: ignore# pylint: disable=protected-access signing_dialogues.new_self_initiated_dialogue_reference() ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) if signing_dialogue is None: raise ValueError("dialogue failure") decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = decision_maker.message_out_queue.get(timeout=2) if ( signing_msg_response.performative != SigningMessage.Performative.SIGNED_TRANSACTION ): raise ValueError("Sign message error!") return time.time() - start_time def run(ledger_id: str, amount_of_tx: int) -> List[Tuple[str, Union[int, float]]]: """Check memory usage.""" # pylint: disable=import-outside-toplevel,unused-import # import manually due to some lazy imports in decision_maker import aea.decision_maker.default # noqa: F401 with TemporaryDirectory() as tmp_dir: key_file = str(Path(tmp_dir) / "key-file") crypto = make_crypto(ledger_id) crypto.dump(key_file) decision_maker, wallet = make_desc_maker_wallet(ledger_id, key_file) decision_maker.start() mem_usage = get_mem_usage_in_mb() running_time = sign_txs(decision_maker, wallet, amount_of_tx, ledger_id) mem_usage = get_mem_usage_in_mb() - mem_usage decision_maker.stop() rate = running_time / amount_of_tx return [ ("run_time (seconds)", running_time), ("rate (envelopes/second)", rate), ("mem usage (Mb)", mem_usage), ] @click.command() @click.option("--ledger_id", default="fetchai", help="Ledger id") @click.option("--amount_of_tx", default=100, help="Amount of tx to sign") @number_of_runs_deco @output_format_deco def main( ledger_id: str, amount_of_tx: int, number_of_runs: int, output_format: str ) -> Any: """Run test.""" parameters = { "Ledger id": ledger_id, "Amount of txs": amount_of_tx, "Number of runs": number_of_runs, } def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, ( ledger_id, amount_of_tx, ), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_dialogues_memory_usage.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Memory usage of dialogues across the time.""" import os import sys import time import uuid from typing import Any, List, Tuple, Union, cast import click from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue from benchmark.checks.utils import get_mem_usage_in_mb # noqa: I100 from benchmark.checks.utils import ( multi_run, number_of_runs_deco, output_format_deco, print_results, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage ROOT_PATH = os.path.join(os.path.abspath(__file__), "..", "..") sys.path.append(ROOT_PATH) class DialogueHandler: """Generate messages and process with dialogues.""" def __init__(self) -> None: """Set dialogues.""" # pylint: disable=unused-argument def role(m: Message, addr: Address) -> Dialogue.Role: return HttpDialogue.Role.CLIENT self.addr = self.random_string self.dialogues = HttpDialogues(self.addr, role_from_first_message=role) @property def random_string(self) -> str: """Get random string on every access.""" return uuid.uuid4().hex def process_message(self) -> None: """Process a message with dialogues.""" message = self.create() dialogue = self.update(message) self.reply(dialogue, message) def update(self, message: HttpMessage) -> HttpDialogue: """Update dialogues with message.""" return cast(HttpDialogue, self.dialogues.update(message)) @staticmethod def reply(dialogue: HttpDialogue, message: HttpMessage) -> Message: """Construct and send a response for message received.""" return dialogue.reply( target_message=message, performative=HttpMessage.Performative.RESPONSE, version=message.version, headers="", status_code=200, status_text="Success", body=message.body, ) def create(self) -> HttpMessage: """Make initial http request.""" message = HttpMessage( dialogue_reference=HttpDialogues.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="get", url="some url", headers="", version="", body=b"", ) message.sender = self.random_string message.to = self.addr return message def run(messages_amount: int) -> List[Tuple[str, Union[float, int]]]: """Test messages generation and memory consumption with dialogues.""" handler = DialogueHandler() mem_usage_on_start = get_mem_usage_in_mb() start_time = time.time() for _ in range(messages_amount): handler.process_message() mem_usage = get_mem_usage_in_mb() return [ ("Mem usage(Mb)", mem_usage - mem_usage_on_start), ("Time (seconds)", time.time() - start_time), ] @click.command() @click.option("--messages", default=1000, help="Run time in seconds.") @number_of_runs_deco @output_format_deco def main(messages: str, number_of_runs: int, output_format: str) -> Any: """Run test.""" parameters = {"Messages": messages, "Number of runs": number_of_runs} def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (int(messages),), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_mem_usage.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Memory usage check.""" import time from threading import Thread from typing import Any, List, Tuple, Union import click from aea.protocols.base import Message from aea.registries.resources import Resources from aea.skills.base import Handler from benchmark.checks.utils import SyncedGeneratorConnection # noqa: I100 from benchmark.checks.utils import ( get_mem_usage_in_mb, make_agent, make_envelope, make_skill, multi_run, number_of_runs_deco, output_format_deco, print_results, wait_for_condition, ) from packages.fetchai.protocols.default.message import DefaultMessage class TestHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Noop setup.""" def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message.""" self.context.outbox.put(make_envelope(message.to, message.sender)) def run(duration: int, runtime_mode: str) -> List[Tuple[str, Union[int, float]]]: """Check memory usage.""" # pylint: disable=import-outside-toplevel,unused-import # import manually due to some lazy imports in decision_maker import aea.decision_maker.default # noqa: F401 connection = SyncedGeneratorConnection.make() resources = Resources() resources.add_connection(connection) agent = make_agent(runtime_mode=runtime_mode, resources=resources) agent.resources.add_skill(make_skill(agent, handlers={"test": TestHandler})) t = Thread(target=agent.start, daemon=True) t.start() wait_for_condition(lambda: agent.is_running, timeout=5) connection.enable() time.sleep(duration) connection.disable() mem_usage = get_mem_usage_in_mb() agent.stop() t.join(5) rate = connection.count_in / duration return [ ("envelopes received", connection.count_in), ("envelopes sent", connection.count_out), ("rate (envelopes/second)", rate), ("mem usage (Mb)", mem_usage), ] @click.command() @click.option("--duration", default=3, help="Run time in seconds.") @click.option( "--runtime_mode", default="async", help="Runtime mode: async or threaded." ) @number_of_runs_deco @output_format_deco def main( duration: int, runtime_mode: str, number_of_runs: int, output_format: str ) -> Any: """Run test.""" parameters = { "Duration(seconds)": duration, "Runtime mode": runtime_mode, "Number of runs": number_of_runs, } def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (duration, runtime_mode), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_messages_memory_usage.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Memory usage of huge amount of messages.""" import os import sys import time from typing import Any, List, Tuple, Union import click from aea.protocols.base import Message from benchmark.checks.utils import get_mem_usage_in_mb # noqa: I100 from benchmark.checks.utils import ( multi_run, number_of_runs_deco, output_format_deco, print_results, ) from packages.fetchai.protocols.default.message import DefaultMessage ROOT_PATH = os.path.join(os.path.abspath(__file__), "..", "..") sys.path.append(ROOT_PATH) def make_message() -> Message: """Create a message.""" return DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"", ) def run(messages_amount: int) -> List[Tuple[str, Union[int, float]]]: """Test messages generation and memory consumption.""" messages: List[Any] = [ 0 for i in range(messages_amount) ] # generate dummy list to count list structure memory mem_usage_on_start = get_mem_usage_in_mb() start_time = time.time() for i in range(messages_amount): messages[i] = make_message() mem_usage = get_mem_usage_in_mb() return [ ("Mem usage(Mb)", mem_usage - mem_usage_on_start), ("Time (seconds)", time.time() - start_time), ] @click.command() @click.option("--messages", default=10**6, help="Amount of messages.") @number_of_runs_deco @output_format_deco def main(messages: int, number_of_runs: int, output_format: str) -> Any: """Run test.""" parameters = {"Messages": messages, "Number of runs": number_of_runs} def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (int(messages),), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_multiagent.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Envelopes generation speed for Behaviour act test.""" import itertools import os import struct import sys import time from typing import Any, List, Tuple, Union, cast import click from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.protocols.base import Message from aea.registries.resources import Resources from aea.runner import AEARunner from aea.skills.base import Handler from benchmark.checks.utils import get_mem_usage_in_mb # noqa: I100 from benchmark.checks.utils import ( make_agent, make_envelope, make_skill, multi_run, number_of_runs_deco, output_format_deco, print_results, wait_for_condition, ) from packages.fetchai.connections.local.connection import ( # noqa: E402 # pylint: disable=C0413 LocalNode, OEFLocalConnection, ) from packages.fetchai.protocols.default.message import DefaultMessage ROOT_PATH = os.path.join(os.path.abspath(__file__), "..", "..") sys.path.append(ROOT_PATH) class TestHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Noop setup.""" self.count: int = 0 # pylint: disable=attribute-defined-outside-init self.rtt_total_time: float = ( # pylint: disable=attribute-defined-outside-init 0.0 ) self.rtt_count: int = 0 # pylint: disable=attribute-defined-outside-init self.latency_total_time: float = ( # pylint: disable=attribute-defined-outside-init 0.0 ) self.latency_count: int = 0 # pylint: disable=attribute-defined-outside-init def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message.""" self.count += 1 if message.dialogue_reference[0] != "": rtt_ts, latency_ts = struct.unpack("dd", message.content) # type: ignore if message.dialogue_reference[0] == self.context.agent_address: self.rtt_total_time += time.time() - rtt_ts self.rtt_count += 1 self.latency_total_time += time.time() - latency_ts self.latency_count += 1 if message.dialogue_reference[0] in ["", self.context.agent_address]: # create new response_msg = DefaultMessage( dialogue_reference=(self.context.agent_address, ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=struct.pack("dd", time.time(), time.time()), ) else: # update ttfb copy rtt response_msg = DefaultMessage( dialogue_reference=message.dialogue_reference, message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=struct.pack("dd", rtt_ts, time.time()), # type: ignore ) self.context.outbox.put(make_envelope(message.to, message.sender, response_msg)) def run( duration: int, runtime_mode: str, runner_mode: str, start_messages: int, num_of_agents: int, ) -> List[Tuple[str, Union[int, float]]]: """Test multiagent message exchange.""" # pylint: disable=import-outside-toplevel,unused-import # import manually due to some lazy imports in decision_maker import aea.decision_maker.default # noqa: F401 local_node = LocalNode() local_node.start() agents = [] skills = [] for i in range(num_of_agents): resources = Resources() agent_name = f"agent{i}" public_key = f"public_key{i}" identity = Identity(agent_name, address=agent_name, public_key=public_key) connection = OEFLocalConnection( local_node, configuration=ConnectionConfig( connection_id=OEFLocalConnection.connection_id, ), identity=identity, data_dir="tmp", ) resources.add_connection(connection) agent = make_agent( agent_name=agent_name, runtime_mode=runtime_mode, resources=resources, identity=identity, ) skill = make_skill(agent, handlers={"test": TestHandler}) agent.resources.add_skill(skill) agents.append(agent) skills.append(skill) runner = AEARunner(agents, runner_mode) runner.start(threaded=True) for agent in agents: wait_for_condition( ( # pylint: disable=unnecessary-direct-lambda-call lambda agnt: lambda: agnt.is_running )(agent), timeout=5, ) wait_for_condition(lambda: runner.is_running, timeout=5) time.sleep(1) for agent1, agent2 in itertools.permutations(agents, 2): env = make_envelope(agent1.identity.address, agent2.identity.address) for _ in range(int(start_messages)): agent1.outbox.put(env) time.sleep(duration) mem_usage = get_mem_usage_in_mb() local_node.stop() runner.stop(timeout=5) total_messages = sum( cast(TestHandler, skill.handlers["test"]).count for skill in skills ) rate = total_messages / duration rtt_total_time = sum( cast(TestHandler, skill.handlers["test"]).rtt_total_time for skill in skills ) rtt_count = sum( cast(TestHandler, skill.handlers["test"]).rtt_count for skill in skills ) if rtt_count == 0: rtt_count = -1 latency_total_time = sum( cast(TestHandler, skill.handlers["test"]).latency_total_time for skill in skills ) latency_count = sum( cast(TestHandler, skill.handlers["test"]).latency_count for skill in skills ) if latency_count == 0: latency_count = -1 return [ ("Total Messages handled", total_messages), ("Messages rate(envelopes/second)", rate), ("Mem usage(Mb)", mem_usage), ("RTT (ms)", rtt_total_time / rtt_count), ("Latency (ms)", latency_total_time / latency_count), ] @click.command() @click.option("--duration", default=1, help="Run time in seconds.") @click.option( "--runtime_mode", default="async", help="Runtime mode: async or threaded." ) @click.option("--runner_mode", default="async", help="Runtime mode: async or threaded.") @click.option( "--start_messages", default=100, help="Amount of messages to prepopulate." ) @click.option("--num_of_agents", default=2, help="Amount of agents to run.") @number_of_runs_deco @output_format_deco def main( duration: int, runtime_mode: str, runner_mode: str, start_messages: int, num_of_agents: int, number_of_runs: int, output_format: str, ) -> Any: """Run test.""" parameters = { "Duration(seconds)": duration, "Runtime mode": runtime_mode, "Runner mode": runner_mode, "Start messages": start_messages, "Number of agents": num_of_agents, "Number of runs": number_of_runs, } def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (duration, runtime_mode, runner_mode, start_messages, num_of_agents), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_multiagent_http_dialogues.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Memory usage across the time.""" import itertools import os import struct import sys import time from typing import Any, List, Tuple, Union, cast import click from aea.aea import AEA from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.protocols.base import Message, Protocol from aea.protocols.dialogue.base import Dialogue from aea.registries.resources import Resources from aea.runner import AEARunner from aea.skills.base import Handler from benchmark.checks.utils import get_mem_usage_in_mb # noqa: I100 from benchmark.checks.utils import PACKAGES_DIR from benchmark.checks.utils import make_agent as base_make_agent from benchmark.checks.utils import ( make_skill, multi_run, number_of_runs_deco, output_format_deco, print_results, wait_for_condition, ) from packages.fetchai.connections.local.connection import ( # noqa: E402 # pylint: disable=C0413 LocalNode, OEFLocalConnection, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage ROOT_PATH = os.path.join(os.path.abspath(__file__), "..", "..") sys.path.append(ROOT_PATH) class HttpPingPongHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id def setup(self) -> None: """Noop setup.""" # pylint: disable=attribute-defined-outside-init, unused-argument self.count: int = 0 self.rtt_total_time: float = 0.0 self.rtt_count: int = 0 self.latency_total_time: float = 0.0 self.latency_count: int = 0 def role(m: Message, addr: Address) -> Dialogue.Role: return HttpDialogue.Role.CLIENT self.dialogues = HttpDialogues( self.context.agent_address, role_from_first_message=role ) def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message.""" self.count += 1 message = cast(HttpMessage, message) dialogue = self.dialogues.update(message) if not dialogue: raise Exception("something goes wrong") rtt_ts, latency_ts = struct.unpack("dd", message.body) # type: ignore if message.performative == HttpMessage.Performative.REQUEST: self.latency_total_time += time.time() - latency_ts self.latency_count += 1 self.make_response(cast(HttpDialogue, dialogue), message) elif message.performative == HttpMessage.Performative.RESPONSE: self.rtt_total_time += time.time() - rtt_ts self.rtt_count += 1 # got response, make another request to the same agent self.make_request(message.sender) def make_response(self, dialogue: HttpDialogue, message: HttpMessage) -> None: """Construct and send a response for message received.""" response_message = dialogue.reply( target_message=message, performative=HttpMessage.Performative.RESPONSE, version=message.version, headers="", status_code=200, status_text="Success", body=message.body, ) self.context.outbox.put_message(response_message) def make_request(self, recipient_addr: str) -> None: """Make initial http request.""" message, _ = self.dialogues.create( recipient_addr, performative=HttpMessage.Performative.REQUEST, method="get", url="some url", headers="", version="", body=struct.pack("dd", time.time(), time.time()), ) self.context.outbox.put_message(message) def make_agent(*args: Any, **kwargs: Any) -> AEA: """Make agent with http protocol support.""" aea = base_make_agent(*args, **kwargs) aea.resources.add_protocol( Protocol.from_dir(str(PACKAGES_DIR / "fetchai" / "protocols" / "http")) ) return aea def run( duration: int, runtime_mode: str, runner_mode: str, start_messages: int, num_of_agents: int, ) -> List[Tuple[str, Union[int, float]]]: """Test multiagent message exchange.""" # pylint: disable=import-outside-toplevel,unused-import # import manually due to some lazy imports in decision_maker import aea.decision_maker.default # noqa: F401 local_node = LocalNode() local_node.start() agents = [] skills = {} handler_name = "httpingpong" for i in range(num_of_agents): agent_name = f"agent{i}" public_key = f"public_key{i}" identity = Identity(agent_name, address=agent_name, public_key=public_key) resources = Resources() connection = OEFLocalConnection( local_node, configuration=ConnectionConfig( connection_id=OEFLocalConnection.connection_id, ), identity=identity, data_dir="tmp", ) resources.add_connection(connection) agent = make_agent( agent_name=agent_name, runtime_mode=runtime_mode, resources=resources, identity=identity, ) skill = make_skill(agent, handlers={handler_name: HttpPingPongHandler}) agent.resources.add_skill(skill) agents.append(agent) skills[agent_name] = skill runner = AEARunner(agents, runner_mode) runner.start(threaded=True) for agent in agents: wait_for_condition( ( # pylint: disable=unnecessary-direct-lambda-call lambda agnt: lambda: agnt.is_running )(agent), timeout=5, ) wait_for_condition(lambda: runner.is_running, timeout=5) time.sleep(1) for agent1, agent2 in itertools.permutations(agents, 2): for _ in range(int(start_messages)): cast( HttpPingPongHandler, skills[agent1.identity.address].handlers[handler_name], ).make_request(agent2.identity.address) time.sleep(duration) mem_usage = get_mem_usage_in_mb() local_node.stop() runner.stop(timeout=5) total_messages = sum( cast(HttpPingPongHandler, skill.handlers[handler_name]).count for skill in skills.values() ) rate = total_messages / duration rtt_total_time = sum( cast(HttpPingPongHandler, skill.handlers[handler_name]).rtt_total_time for skill in skills.values() ) rtt_count = sum( cast(HttpPingPongHandler, skill.handlers[handler_name]).rtt_count for skill in skills.values() ) if rtt_count == 0: rtt_count = -1 latency_total_time = sum( cast(HttpPingPongHandler, skill.handlers[handler_name]).latency_total_time for skill in skills.values() ) latency_count = sum( cast(HttpPingPongHandler, skill.handlers[handler_name]).latency_count for skill in skills.values() ) if latency_count == 0: latency_count = -1 return [ ("Total Messages handled", total_messages), ("Messages rate(envelopes/second)", rate), ("Mem usage(Mb)", mem_usage), ("RTT (ms)", rtt_total_time / rtt_count), ("Latency (ms)", latency_total_time / latency_count), ] @click.command() @click.option("--duration", default=1, help="Run time in seconds.") @click.option( "--runtime_mode", default="async", help="Runtime mode: async or threaded." ) @click.option("--runner_mode", default="async", help="Runtime mode: async or threaded.") @click.option( "--start_messages", default=100, help="Amount of messages to prepopulate." ) @click.option("--num_of_agents", default=2, help="Amount of agents to run.") @number_of_runs_deco @output_format_deco def main( duration: int, runtime_mode: str, runner_mode: str, start_messages: int, num_of_agents: int, number_of_runs: int, output_format: str, ) -> Any: """Run test.""" parameters = { "Duration(seconds)": duration, "Runtime mode": runtime_mode, "Runner mode": runner_mode, "Start messages": start_messages, "Number of agents": num_of_agents, "Number of runs": number_of_runs, } def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (duration, runtime_mode, runner_mode, start_messages, num_of_agents), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_proactive.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Envelopes generation speed for Behaviour act test.""" import time from threading import Thread from typing import Any, List, Tuple, Union, cast import click from aea.registries.resources import Resources from aea.skills.base import Behaviour from benchmark.checks.utils import SyncedGeneratorConnection # noqa: I100 from benchmark.checks.utils import ( make_agent, make_envelope, make_skill, multi_run, number_of_runs_deco, output_format_deco, print_results, wait_for_condition, ) from packages.fetchai.protocols.default.message import DefaultMessage class TestBehaviour(Behaviour): """Dummy handler to handle messages.""" _tick_interval = 1 SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Set up behaviour.""" self.count = 0 # pylint: disable=attribute-defined-outside-init def teardown(self) -> None: """Tear up behaviour.""" def act(self) -> None: """Perform action on periodic basis.""" s = time.time() while time.time() - s < self.tick_interval: self.context.outbox.put(make_envelope("1", "2")) self.count += 1 def run(duration: int, runtime_mode: str) -> List[Tuple[str, Union[int, float]]]: """Test act message generate performance.""" # pylint: disable=import-outside-toplevel,unused-import # import manually due to some lazy imports in decision_maker import aea.decision_maker.default # noqa: F401 resources = Resources() connection = SyncedGeneratorConnection.make() resources.add_connection(connection) agent = make_agent(runtime_mode=runtime_mode, resources=resources) skill = make_skill(agent, behaviours={"test": TestBehaviour}) agent.resources.add_skill(skill) t = Thread(target=agent.start, daemon=True) t.start() wait_for_condition(lambda: agent.is_running, timeout=5) time.sleep(duration) agent.stop() t.join(5) rate = connection.count_in / duration return [ ("envelopes sent", cast(TestBehaviour, skill.behaviours["test"]).count), ("envelopes received", connection.count_in), ("rate(envelopes/second)", rate), ] @click.command() @click.option("--duration", default=3, help="Run time in seconds.") @click.option( "--runtime_mode", default="async", help="Runtime mode: async or threaded." ) @number_of_runs_deco @output_format_deco def main( duration: int, runtime_mode: str, number_of_runs: int, output_format: str ) -> Any: """Run test.""" parameters = { "Duration(seconds)": duration, "Runtime mode": runtime_mode, "Number of runs": number_of_runs, } def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (duration, runtime_mode), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/check_reactive.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Latency and throughput check.""" import time from statistics import mean from threading import Thread from typing import Any, List, Optional, Tuple, Union import click from aea.mail.base import Envelope from aea.protocols.base import Message from aea.registries.resources import Resources from aea.skills.base import Handler from benchmark.checks.utils import GeneratorConnection # noqa: I100 from benchmark.checks.utils import ( SyncedGeneratorConnection, make_agent, make_envelope, make_skill, multi_run, number_of_runs_deco, output_format_deco, print_results, wait_for_condition, ) from packages.fetchai.protocols.default.message import DefaultMessage class TestConnectionMixIn: """Test connection with messages timing.""" def __init__(self, *args: Any, **kwargs: Any): """Init connection.""" super().__init__(*args, **kwargs) # type: ignore self.sends: List[float] = [] self.recvs: List[float] = [] async def send(self, envelope: Envelope) -> None: """Handle incoming envelope.""" self.recvs.append(time.time()) return await super().send(envelope) # type: ignore # pylint: disable=no-member async def receive(self, *args: Any, **kwargs: Any) -> Optional[Envelope]: """Generate outgoing envelope.""" envelope = await super().receive(*args, **kwargs) # type: ignore # pylint: disable=no-member self.sends.append(time.time()) return envelope CONNECTION_MODES = {"sync": SyncedGeneratorConnection, "nonsync": GeneratorConnection} class TestHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Noop setup.""" def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message.""" self.context.outbox.put(make_envelope(message.to, message.sender)) def run( duration: int, runtime_mode: str, connection_mode: str ) -> List[Tuple[str, Union[int, float]]]: """Test memory usage.""" # pylint: disable=import-outside-toplevel,unused-import # import manually due to some lazy imports in decision_maker import aea.decision_maker.default # noqa: F401 resources = Resources() if connection_mode not in CONNECTION_MODES: raise ValueError( f"bad connection mode {connection_mode}. valid is one of {list(CONNECTION_MODES.keys())}" ) base_cls = CONNECTION_MODES[connection_mode] conn_cls = type("conn_cls", (TestConnectionMixIn, base_cls), {}) connection = conn_cls.make() # type: ignore # pylint: disable=no-member resources.add_connection(connection) agent = make_agent(runtime_mode=runtime_mode, resources=resources) agent.resources.add_skill(make_skill(agent, handlers={"test": TestHandler})) t = Thread(target=agent.start, daemon=True) t.start() wait_for_condition(lambda: agent.is_running, timeout=5) connection.enable() time.sleep(duration) connection.disable() time.sleep(0.2) # possible race condition in stop? agent.stop() t.join(5) latency = mean( map( lambda x: x[1] - x[0], zip( connection.sends, connection.recvs, ), ) ) total_amount = len(connection.recvs) rate = total_amount / duration return [ ("envelopes received", len(connection.recvs)), ("envelopes sent", len(connection.sends)), ("latency(ms)", 10**6 * latency), ("rate(envelopes/second)", rate), ] @click.command() @click.option("--duration", default=1, help="Run time in seconds.") @click.option( "--runtime_mode", default="async", help="Runtime mode: async or threaded." ) @click.option( "--connection_mode", default="sync", help="Connection mode: sync or nonsync." ) @number_of_runs_deco @output_format_deco def main( duration: int, runtime_mode: str, connection_mode: str, number_of_runs: int, output_format: str, ) -> Any: """Run test.""" parameters = { "Duration(seconds)": duration, "Runtime mode": runtime_mode, "Connection mode": connection_mode, "Number of runs": number_of_runs, } def result_fn() -> List[Tuple[str, Any, Any, Any]]: return multi_run( int(number_of_runs), run, (duration, runtime_mode, connection_mode), ) return print_results(output_format, parameters, result_fn) if __name__ == "__main__": main() # pylint: disable=no-value-for-parameter ================================================ FILE: benchmark/checks/data/2020.09.05_17-49.txt ================================================ Collecting pipenv Downloading pipenv-2020.8.13-py2.py3-none-any.whl (3.9 MB) Requirement already satisfied: setuptools>=36.2.1 in /usr/local/lib/python3.8/site-packages (from pipenv) (50.0.1) Collecting certifi Downloading certifi-2020.6.20-py2.py3-none-any.whl (156 kB) Requirement already satisfied: pip>=18.0 in /usr/local/lib/python3.8/site-packages (from pipenv) (20.2.2) Collecting virtualenv-clone>=0.2.5 Downloading virtualenv_clone-0.5.4-py2.py3-none-any.whl (6.6 kB) Collecting virtualenv Downloading virtualenv-20.0.31-py2.py3-none-any.whl (4.9 MB) Collecting filelock<4,>=3.0.0 Downloading filelock-3.0.12-py3-none-any.whl (7.6 kB) Collecting appdirs<2,>=1.4.3 Downloading appdirs-1.4.4-py2.py3-none-any.whl (9.6 kB) Collecting six<2,>=1.9.0 Downloading six-1.15.0-py2.py3-none-any.whl (10 kB) Collecting distlib<1,>=0.3.1 Downloading distlib-0.3.1-py2.py3-none-any.whl (335 kB) Installing collected packages: certifi, virtualenv-clone, filelock, appdirs, six, distlib, virtualenv, pipenv Successfully installed appdirs-1.4.4 certifi-2020.6.20 distlib-0.3.1 filelock-3.0.12 pipenv-2020.8.13 six-1.15.0 virtualenv-20.0.31 virtualenv-clone-0.5.4 Installing dependencies from Pipfile.lock (6321dc)… To activate this project's virtualenv, run pipenv shell. Alternatively, run a command inside the virtualenv with pipenv run. Collecting aea[all]==0.6.0 Downloading aea-0.6.0-py2.py3-none-any.whl (416 kB) Collecting ecdsa==0.15 Downloading ecdsa-0.15-py2.py3-none-any.whl (100 kB) Collecting pymultihash==0.8.2 Downloading pymultihash-0.8.2-py3-none-any.whl (13 kB) Requirement already satisfied, skipping upgrade: ipfshttpclient==0.6.1 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (0.6.1) Requirement already satisfied, skipping upgrade: pyyaml>=4.2b1 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (5.3.1) Requirement already satisfied, skipping upgrade: base58>=1.0.3 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (2.0.1) Requirement already satisfied, skipping upgrade: requests==2.22.0 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (2.22.0) Collecting semver>=2.9.1 Downloading semver-2.10.2-py2.py3-none-any.whl (12 kB) Requirement already satisfied, skipping upgrade: protobuf in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (3.13.0) Requirement already satisfied, skipping upgrade: jsonschema>=3.0.0 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (3.2.0) Collecting eth-account==0.5.2 Downloading eth_account-0.5.2-py3-none-any.whl (94 kB) Collecting web3==5.12.0 Downloading web3-5.12.0-py3-none-any.whl (467 kB) Requirement already satisfied, skipping upgrade: packaging>=20.3 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (20.4) Collecting bech32==1.2.0 Downloading bech32-1.2.0-py3-none-any.whl (4.6 kB) Collecting connexion[swagger-ui]>=2.4.0; extra == "all" Downloading connexion-2.7.0-py2.py3-none-any.whl (77 kB) Requirement already satisfied, skipping upgrade: docker; extra == "all" in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (4.2.0) Collecting python-dotenv; extra == "all" Downloading python_dotenv-0.14.0-py2.py3-none-any.whl (17 kB) Requirement already satisfied, skipping upgrade: click; extra == "all" in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from aea[all]==0.6.0) (7.1.2) Collecting flask; extra == "all" Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB) Requirement already satisfied, skipping upgrade: six>=1.9.0 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from ecdsa==0.15->aea[all]==0.6.0) (1.15.0) Requirement already satisfied, skipping upgrade: multiaddr>=0.0.7 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from ipfshttpclient==0.6.1->aea[all]==0.6.0) (0.0.9) Requirement already satisfied, skipping upgrade: chardet<3.1.0,>=3.0.2 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from requests==2.22.0->aea[all]==0.6.0) (3.0.4) Requirement already satisfied, skipping upgrade: idna<2.9,>=2.5 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from requests==2.22.0->aea[all]==0.6.0) (2.8) Requirement already satisfied, skipping upgrade: certifi>=2017.4.17 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from requests==2.22.0->aea[all]==0.6.0) (2020.6.20) Requirement already satisfied, skipping upgrade: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from requests==2.22.0->aea[all]==0.6.0) (1.25.10) Requirement already satisfied, skipping upgrade: setuptools in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from protobuf->aea[all]==0.6.0) (49.6.0) Requirement already satisfied, skipping upgrade: pyrsistent>=0.14.0 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from jsonschema>=3.0.0->aea[all]==0.6.0) (0.16.0) Requirement already satisfied, skipping upgrade: attrs>=17.4.0 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from jsonschema>=3.0.0->aea[all]==0.6.0) (20.2.0) Collecting bitarray<1.3.0,>=1.2.1 Downloading bitarray-1.2.2.tar.gz (48 kB) Collecting hexbytes<1,>=0.1.0 Downloading hexbytes-0.2.1-py3-none-any.whl (6.0 kB) Collecting eth-rlp<1,>=0.1.2 Downloading eth_rlp-0.2.0-py3-none-any.whl (5.0 kB) Collecting eth-keys!=0.3.2,<0.4.0,>=0.2.1 Downloading eth_keys-0.3.3-py3-none-any.whl (20 kB) Collecting rlp<2,>=1.0.0 Downloading rlp-1.2.0-py2.py3-none-any.whl (19 kB) Collecting eth-utils<2,>=1.3.0 Downloading eth_utils-1.9.5-py3-none-any.whl (23 kB) Collecting eth-keyfile<0.6.0,>=0.5.0 Downloading eth_keyfile-0.5.1-py3-none-any.whl (8.3 kB) Collecting eth-abi<3,>=2.0.0b7 Downloading eth_abi-2.1.1-py3-none-any.whl (27 kB) Collecting lru-dict<2.0.0,>=1.1.6 Downloading lru-dict-1.1.6.tar.gz (9.4 kB) Collecting websockets<9.0.0,>=8.1.0 Downloading websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl (78 kB) Collecting eth-hash[pycryptodome]<1.0.0,>=0.2.0 Downloading eth_hash-0.2.0-py3-none-any.whl (7.2 kB) Collecting eth-typing<3.0.0,>=2.0.0 Downloading eth_typing-2.2.2-py3-none-any.whl (6.2 kB) Requirement already satisfied, skipping upgrade: pyparsing>=2.0.2 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from packaging>=20.3->aea[all]==0.6.0) (2.4.7) Requirement already satisfied, skipping upgrade: openapi-spec-validator>=0.2.4 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from connexion[swagger-ui]>=2.4.0; extra == "all"->aea[all]==0.6.0) (0.2.8) Collecting clickclick>=1.2 Downloading clickclick-1.2.2-py2.py3-none-any.whl (9.8 kB) Collecting inflection>=0.3.1 Downloading inflection-0.5.1-py2.py3-none-any.whl (9.5 kB) Collecting swagger-ui-bundle>=0.0.2; extra == "swagger-ui" Downloading swagger_ui_bundle-0.0.8-py3-none-any.whl (3.8 MB) Requirement already satisfied, skipping upgrade: websocket-client>=0.32.0 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from docker; extra == "all"->aea[all]==0.6.0) (0.57.0) Requirement already satisfied, skipping upgrade: Jinja2>=2.10.1 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from flask; extra == "all"->aea[all]==0.6.0) (2.11.2) Collecting itsdangerous>=0.24 Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB) Requirement already satisfied, skipping upgrade: Werkzeug>=0.15 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from flask; extra == "all"->aea[all]==0.6.0) (1.0.1) Requirement already satisfied, skipping upgrade: varint in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from multiaddr>=0.0.7->ipfshttpclient==0.6.1->aea[all]==0.6.0) (1.0.2) Requirement already satisfied, skipping upgrade: netaddr in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from multiaddr>=0.0.7->ipfshttpclient==0.6.1->aea[all]==0.6.0) (0.8.0) Collecting cytoolz<1.0.0,>=0.10.1; implementation_name == "cpython" Downloading cytoolz-0.10.1.tar.gz (475 kB) Requirement already satisfied, skipping upgrade: pycryptodome<4.0.0,>=3.4.7 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from eth-keyfile<0.6.0,>=0.5.0->eth-account==0.5.2->aea[all]==0.6.0) (3.9.8) Collecting parsimonious<0.9.0,>=0.8.0 Downloading parsimonious-0.8.1.tar.gz (45 kB) Requirement already satisfied, skipping upgrade: MarkupSafe>=0.23 in /root/.local/share/virtualenvs/bench-DjFm5PZm8q-aRtk-7Go/lib/python3.8/site-packages (from Jinja2>=2.10.1->flask; extra == "all"->aea[all]==0.6.0) (1.1.1) Collecting toolz>=0.8.0 Downloading toolz-0.10.0.tar.gz (49 kB) Building wheels for collected packages: bitarray, lru-dict, cytoolz, parsimonious, toolz Building wheel for bitarray (setup.py): started Building wheel for bitarray (setup.py): finished with status 'done' Created wheel for bitarray: filename=bitarray-1.2.2-cp38-cp38-linux_x86_64.whl size=167712 sha256=bbb8f7bb90b9d8b62215424239d766b0a7e2beef7ef2d61395224993e1eb130a Stored in directory: /root/.cache/pip/wheels/41/36/95/5b4eca059535a8400e8f4ca38f4883ea1801bb221fbd8170df Building wheel for lru-dict (setup.py): started Building wheel for lru-dict (setup.py): finished with status 'done' Created wheel for lru-dict: filename=lru_dict-1.1.6-cp38-cp38-linux_x86_64.whl size=30640 sha256=109c28c39bb8c3272185a6ae17a46364f4757d2433e150b5dd6f4cdef1361c8a Stored in directory: /root/.cache/pip/wheels/c6/91/94/23d462effc91bc844998b2023dc2dfd037a9d0a60034ddab16 Building wheel for cytoolz (setup.py): started Building wheel for cytoolz (setup.py): finished with status 'done' Created wheel for cytoolz: filename=cytoolz-0.10.1-cp38-cp38-linux_x86_64.whl size=1927948 sha256=e89af3c669a3cfe42667911024e435e47f72cfe976f8858c3d9f7235f052cbb0 Stored in directory: /root/.cache/pip/wheels/7f/71/e7/690b241fea90859f7ed6defe9c56e37ab9b7dcd80a4067dc54 Building wheel for parsimonious (setup.py): started Building wheel for parsimonious (setup.py): finished with status 'done' Created wheel for parsimonious: filename=parsimonious-0.8.1-py3-none-any.whl size=42710 sha256=44be43c73c058b615f3d9c6a2ea08cd1556a50b83d6473f75d29e3f842ebe68f Stored in directory: /root/.cache/pip/wheels/d8/af/19/fb896f509a437aca2dcf62583e84d7fb2cd5b628c1564a609c Building wheel for toolz (setup.py): started Building wheel for toolz (setup.py): finished with status 'done' Created wheel for toolz: filename=toolz-0.10.0-py3-none-any.whl size=55576 sha256=3da2914c4fbbc90e5236295bb458c37ab82c22cd3d633ab82882a204986b12d6 Stored in directory: /root/.cache/pip/wheels/a5/2b/b5/05758d5828d65f2adef8fbb5d5484e4adb946ae1827a973a01 Successfully built bitarray lru-dict cytoolz parsimonious toolz Installing collected packages: ecdsa, pymultihash, semver, bitarray, hexbytes, eth-hash, eth-typing, toolz, cytoolz, eth-utils, rlp, eth-rlp, eth-keys, eth-keyfile, parsimonious, eth-abi, eth-account, lru-dict, websockets, web3, bech32, itsdangerous, flask, clickclick, inflection, swagger-ui-bundle, connexion, python-dotenv, aea Successfully installed aea-0.6.0 bech32-1.2.0 bitarray-1.2.2 clickclick-1.2.2 connexion-2.7.0 cytoolz-0.10.1 ecdsa-0.15 eth-abi-2.1.1 eth-account-0.5.2 eth-hash-0.2.0 eth-keyfile-0.5.1 eth-keys-0.3.3 eth-rlp-0.2.0 eth-typing-2.2.2 eth-utils-1.9.5 flask-1.1.2 hexbytes-0.2.1 inflection-0.5.1 itsdangerous-1.1.0 lru-dict-1.1.6 parsimonious-0.8.1 pymultihash-0.8.2 python-dotenv-0.14.0 rlp-1.2.0 semver-2.10.2 swagger-ui-bundle-0.0.8 toolz-0.10.0 web3-5.12.0 websockets-8.1 Performance report for 05.09.2020_17:49 ----------------------------- Reactive: number of runs: 100, duration: 10 ---------------------------------------------------- runtime mode value mean stdev ---------------------------------------------------- threaded latency 800.629597 14.672578 threaded rate 1106.762 18.863731 async latency 526.03548 5.211927 async rate 1630.32 16.211843 Proactive: number of runs: 100, duration: 10 ---------------------------------------------------- runtime mode value mean stdev ---------------------------------------------------- threaded rate 4587.775 820.52555 async rate 6158.846 516.349488 MultiAgent: number of runs: 3, duration: 3, messages: 100 ------------------------------------------------------------------ runtime mode num_agents value mean stdev ------------------------------------------------------------------ threaded 2 rate 1244.333333 19.341952 threaded 2 mem 54.832031 0.222382 threaded 2 RTT 0.334617 0.001791 threaded 2 latency 0.166745 0.000905 threaded 4 rate 1389.444444 108.157464 threaded 4 mem 56.289062 0.299306 threaded 4 RTT 1.994238 0.015903 threaded 4 latency 1.00406 0.011688 threaded 8 rate 2499.777778 258.057774 threaded 8 mem 61.90625 0.547377 threaded 8 RTT 4.667974 0.410991 threaded 8 latency 2.988324 0.210826 threaded 16 rate 14042.333333 862.192812 threaded 16 mem 77.326823 0.394364 threaded 16 RTT 23.403793 0.618454 threaded 16 latency 13.766599 0.152124 async 2 rate 1200.222222 14.369464 async 2 mem 55.052083 0.245079 async 2 RTT 0.343028 0.003275 async 2 latency 0.170238 0.002034 async 4 rate 1168.222222 44.762749 async 4 mem 56.222656 0.343128 async 4 RTT 2.056596 0.091798 async 4 latency 1.051261 0.033161 async 8 rate 2012.555556 73.014712 async 8 mem 61.355469 0.332651 async 8 RTT 4.312067 0.1969 async 8 latency 3.101899 0.217263 async 16 rate 6193.666667 578.218048 async 16 mem 76.97526 1.526111 async 16 RTT 12.952551 2.512823 async 16 latency 9.581866 1.340397 Done! ================================================ FILE: benchmark/checks/data/2020.10.27_mem_usage_report.txt ================================================ Performance report for 27.10.2020_17:06 ----------------------------- Multi agents with http dialogues: number of runs: 10, num_agents: 10, messages: 100 ------------------------------------------------------------------ runtime mode duration value mean stdev ------------------------------------------------------------------ async 2 mem 87.979687 0.829828 async 5 mem 91.964844 1.508087 async 10 mem 97.392578 1.318029 async 20 mem 107.630859 2.540685 async 30 mem 118.990234 1.807382 async 50 mem 142.086328 3.1786 Message generation and allocation: number of runs: 10 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 6.171484 0.045347 10000 time 0.633887 0.048279 50000 mem 30.24375 0.045969 50000 time 3.303643 0.115329 100000 mem 60.433203 0.083708 100000 time 6.773516 0.14151 Dialogues message processing: number of runs: 10 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 31.895703 0.024777 10000 time 4.464438 0.218168 20000 mem 63.472656 0.04187 20000 time 8.889359 0.291928 50000 mem 162.076953 0.019317 50000 time 22.409405 0.52609 ================================================ FILE: benchmark/checks/data/2020.10.30 optimized messages.txt ================================================ Start Test Performance report for 30.10.2020_15:37 ----------------------------- Multi agents with http dialogues: number of runs: 10, num_agents: 10, messages: 100 ------------------------------------------------------------------ runtime mode duration value mean stdev ------------------------------------------------------------------ async 2 mem 79.781641 0.882952 async 5 mem 80.928906 3.816324 async 10 mem 86.929688 0.739895 async 20 mem 94.683203 0.871148 async 30 mem 100.952344 2.460479 async 50 mem 114.601562 5.314621 Message generation and allocation: number of runs: 10 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 2.438281 0.05618 10000 time 0.772864 0.106754 50000 mem 12.507422 0.001235 50000 time 3.949191 0.182593 100000 mem 24.722656 0.0 100000 time 7.435021 0.232879 Dialogues message processing: number of runs: 10 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 22.803906 0.01424 10000 time 4.745211 0.179063 20000 mem 45.456641 0.077262 20000 time 9.931134 0.405339 50000 mem 116.507422 0.061141 50000 time 24.472803 0.325537 pod "benchmark-checks" deleted ================================================ FILE: benchmark/checks/data/2020.12.10_optimized_messages.txt ================================================ Performance report for 10.12.2020_09:32 ----------------------------- Multi agents with http dialogues: number of runs: 100, num_agents: 10, messages: 100 ------------------------------------------------------------------ runtime mode duration value mean stdev ------------------------------------------------------------------ async 2 mem 71.147305 0.636357 async 5 mem 71.937031 0.630627 async 10 mem 73.255586 0.891906 async 20 mem 75.449219 0.901333 async 30 mem 77.292383 0.808147 async 50 mem 81.357891 1.194153 Message generation and allocation: number of runs: 100 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 1.514961 0.02531 10000 time 0.567152 0.070787 50000 mem 7.804883 0.000856 50000 time 2.942939 0.233939 100000 mem 15.384297 0.003956 100000 time 5.779464 0.321653 Dialogues message processing: number of runs: 100 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 7.255781 0.062602 10000 time 4.002207 0.275296 ================================================ FILE: benchmark/checks/data/2021.03.09_test_run.txt ================================================ Performance report for 09.03.2021_21:45 ----------------------------- Multi agents with http dialogues: number of runs: 100, num_agents: 10, messages: 100 ------------------------------------------------------------------ runtime mode duration value mean stdev ------------------------------------------------------------------ async 2 mem 62.248555 0.530499 async 5 mem 63.226367 0.662026 async 10 mem 65.892383 0.527979 async 20 mem 68.232656 0.493123 async 30 mem 71.403828 0.592728 async 50 mem 77.623242 0.464335 Message generation and allocation: number of runs: 100 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 0.45918 0.068106 10000 time 0.482406 0.01351 50000 mem 6.80457 0.120214 50000 time 2.366248 0.026497 100000 mem 14.542109 0.100293 100000 time 4.770077 0.056299 Dialogues message processing: number of runs: 100 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 6.991953 0.049681 10000 time 3.377206 0.039903 20000 mem 14.286914 0.039206 20000 time 6.732406 0.060848 50000 mem 37.929336 0.094448 50000 time 16.87671 0.155116 ================================================ FILE: benchmark/checks/data/2021.04.01_v1_benchmark.txt ================================================ Performance report for 01.04.2021 ----------------------------- Multi agents with http dialogues: number of runs: 100, num_agents: 10, messages: 100 ------------------------------------------------------------------ runtime mode duration value mean stdev ------------------------------------------------------------------ async 2 mem 63.065273 0.41207 async 5 mem 64.22625 0.379626 async 10 mem 66.182461 0.40166 async 20 mem 69.05918 0.282377 async 30 mem 71.520469 0.438192 async 50 mem 76.893984 0.882001 Message generation and allocation: number of runs: 100 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 0.304688 0.051566 10000 time 0.509305 0.031975 50000 mem 6.645781 0.065213 50000 time 2.743519 0.10603 100000 mem 14.294063 0.024781 100000 time 5.541937 0.326452 Dialogues message processing: number of runs: 100 ------------------------------------------------------------------ message value mean stdev ------------------------------------------------------------------ 10000 mem 7.238008 0.065209 10000 time 3.555162 0.141159 20000 mem 14.319102 0.025112 20000 time 6.942097 0.232013 50000 mem 38.223203 0.139953 50000 time 17.392467 0.503368 Reactive: number of runs: 100, duration: 10 ---------------------------------------------------- runtime mode value mean stdev ---------------------------------------------------- threaded latency 1350.913681 39.467721 threaded rate 654.473 17.384876 async latency 1094.870201 46.66503 async rate 788.42 28.667664 Proactive: number of runs: 100, duration: 10 ---------------------------------------------------- runtime mode value mean stdev ---------------------------------------------------- threaded rate 3136.821 50.660694 async rate 0.791 0.028762 MultiAgent: number of runs: 100, duration: 10, messages: 100 ------------------------------------------------------------------ runtime mode num_agents value mean stdev ------------------------------------------------------------------ threaded 2 rate 675.654 13.657716 threaded 2 mem 48.126836 0.176016 threaded 2 RTT 0.596222 0.011607 threaded 2 latency 0.297669 0.005628 threaded 4 rate 679.966 17.678088 threaded 4 mem 49.095859 0.218971 threaded 4 RTT 3.662645 0.078505 threaded 4 latency 1.802625 0.03475 threaded 8 rate 1449.882 281.714217 threaded 8 mem 52.813281 0.467479 threaded 8 RTT 19.373796 3.780258 threaded 8 latency 10.401749 1.717344 threaded 16 rate 4735.086 1410.656106 threaded 16 mem 61.390781 1.540248 threaded 16 RTT 60.171576 14.865577 threaded 16 latency 35.057816 9.472234 threaded 32 rate 14810.817 6722.336041 threaded 32 mem 87.794141 8.46622 threaded 32 RTT 189.72728 79.581276 threaded 32 latency 122.309386 51.589368 threaded 64 rate 27860.344 18340.58782 threaded 64 mem 163.312305 44.267701 threaded 64 RTT 419.02488 271.182984 threaded 64 latency 282.563078 179.758986 threaded 128 rate 49397.043 18398.274532 threaded 128 mem 294.158906 103.471503 threaded 128 RTT 855.809125 483.501904 threaded 128 latency 568.189828 301.868828 async 2 rate 670.926 13.463461 async 2 mem 48.149766 0.174008 async 2 RTT 0.597284 0.012575 async 2 latency 0.298328 0.006285 async 4 rate 609.235 11.144201 async 4 mem 49.121719 0.154711 async 4 RTT 3.841597 0.070842 async 4 latency 1.858407 0.038073 async 8 rate 548.831 25.953609 async 8 mem 51.70832 0.193377 async 8 RTT 2.237455 4.020524 async 8 latency 6.375603 0.453957 async 16 rate 2249.182 823.35554 async 16 mem 55.84707 0.714784 async 16 RTT 28.762427 25.072808 async 16 latency 28.067046 12.508153 async 32 rate 3961.37 3479.249814 async 32 mem 70.209453 2.544705 async 32 RTT 38.147726 78.039112 async 32 latency 42.875331 68.118355 async 64 rate 7647.276 2359.938405 async 64 mem 132.389844 13.37811 async 64 RTT 167.018874 138.893537 async 64 latency 65.395231 79.366779 ================================================ FILE: benchmark/checks/run_benchmark.sh ================================================ #!/bin/bash DATE=`LC_ALL=C date +"%d.%m.%Y_%H:%M"` echo "Performance report for $DATE" echo -e "-----------------------------\n" DURATION=10 NUM_RUNS=100 MESSAGES=100 chmod +x benchmark/checks/check_reactive.py echo "Reactive: number of runs: $NUM_RUNS, duration: $DURATION" echo "----------------------------------------------------" echo "runtime mode value mean stdev" echo "----------------------------------------------------" for mode in threaded async; do data=`./benchmark/checks/check_reactive.py --duration=$DURATION --number_of_runs=$NUM_RUNS --runtime_mode=$mode` latency=`echo "$data"|grep latency|awk '{print $4 " " $6}'` rate=`echo "$data"|grep rate|awk '{print $4 " " $6}'` echo -e "$mode latency ${latency}" echo -e "$mode rate ${rate}" done # # ~ 10 * 2 * 100 sec = 33.3 min chmod +x benchmark/checks/check_proactive.py echo -e "\nProactive: number of runs: $NUM_RUNS, duration: $DURATION" echo "----------------------------------------------------" echo "runtime mode value mean stdev" echo "----------------------------------------------------" for mode in threaded async; do data=`./benchmark/checks/check_proactive.py --duration=$DURATION --number_of_runs=$NUM_RUNS --runtime_mode=$mode` latency=`echo "$data"|grep latency|awk '{print $4 " " $6}'` rate=`echo "$data"|grep rate|awk '{print $4 " " $6}'` echo -e "$mode rate ${rate}" done # # ~ 10 * 2 * 100 sec = 33.3 min chmod +x benchmark/checks/check_multiagent.py echo -e "\nMultiAgent: number of runs: $NUM_RUNS, duration: $DURATION, messages: $MESSAGES" echo "------------------------------------------------------------------" echo "runtime mode num_agents value mean stdev" echo "------------------------------------------------------------------" for mode in threaded async; do for agent in 2 4 8 16 32 64 128; do data=`./benchmark/checks/check_multiagent.py --num_of_agents=$agent --duration=$DURATION --number_of_runs=$NUM_RUNS --runtime_mode=$mode --start_messages=$MESSAGES --runner_mode=threaded` rate=`echo "$data"|grep rate|awk '{print $5 " " $7}'` mem=`echo "$data"|grep Mem|awk '{print $5 " " $7}'` rtt_latency=`echo "$data"|grep 'RTT'|awk '{print $5 " " $7}'` latency=`echo "$data"|grep 'Latency'|awk '{print $5 " " $7}'` echo -e "$mode $agent rate ${rate}" echo -e "$mode $agent mem ${mem}" echo -e "$mode $agent RTT ${rtt_latency}" echo -e "$mode $agent latency ${latency}" done done # ~ 10 * 2 * 4 * 100 sec = 133.3 min ================================================ FILE: benchmark/checks/run_benchmark_messages_mem.sh ================================================ #!/bin/bash DATE=`LC_ALL=C date +"%d.%m.%Y_%H:%M"` echo "Performance report for $DATE" echo -e "-----------------------------\n" AGENTS=10 NUM_RUNS=100 MESSAGES=100 RUNNER_MODE=threaded # check memory usage in terms of duration chmod +x benchmark/checks/check_multiagent_http_dialogues.py echo -e "\n\nMulti agents with http dialogues: number of runs: $NUM_RUNS, num_agents: $AGENTS, messages: $MESSAGES" echo "------------------------------------------------------------------" echo "runtime mode duration value mean stdev" echo "------------------------------------------------------------------" for runtime_mode in async; do for duration in 2 5 10 20 30 50; do cmd="./benchmark/checks/check_multiagent_http_dialogues.py --num_of_agents=$AGENTS --duration=$duration --number_of_runs=$NUM_RUNS --runtime_mode=$runtime_mode --start_messages=$MESSAGES --runner_mode=$RUNNER_MODE" data=`$cmd 2>>/dev/null` rate=`echo "$data"|grep rate|awk '{print $5 " " $7}'` mem=`echo "$data"|grep Mem|awk '{print $5 " " $7}'` rtt_latency=`echo "$data"|grep 'RTT'|awk '{print $5 " " $7}'` latency=`echo "$data"|grep 'Latency'|awk '{print $5 " " $7}'` #echo -e "$runtime_mode $duration rate ${rate}" echo -e "$runtime_mode\t\t $duration\t\t mem\t ${mem}" #echo -e "$runtime_mode $duration RTT ${rtt_latency}" #echo -e "$runtime_mode $duration latency ${latency}" done done # check memory usage in terms of duration chmod +x benchmark/checks/check_messages_memory_usage.py echo -e "\n\nMessage generation and allocation: number of runs: $NUM_RUNS" echo "------------------------------------------------------------------" echo "message value mean stdev" echo "------------------------------------------------------------------" for messages in 10000 50000 100000; do cmd="./benchmark/checks/check_messages_memory_usage.py --messages=$messages --number_of_runs=$NUM_RUNS" data=`$cmd 2>>/dev/null` time=`echo "$data"|grep Time|awk '{print $5 " " $7}'` mem=`echo "$data"|grep Mem|awk '{print $5 " " $7}'` echo -e "$messages\t\t mem\t ${mem}" echo -e "$messages\t\t time ${time}" done # check memory usage in terms of duration chmod +x benchmark/checks/check_dialogues_memory_usage.py echo -e "\n\nDialogues message processing: number of runs: $NUM_RUNS" echo "------------------------------------------------------------------" echo "message value mean stdev" echo "------------------------------------------------------------------" for messages in 10000 20000 50000; do cmd="./benchmark/checks/check_dialogues_memory_usage.py --messages=$messages --number_of_runs=$NUM_RUNS" data=`$cmd 2>>/dev/null` time=`echo "$data"|grep Time|awk '{print $5 " " $7}'` mem=`echo "$data"|grep Mem|awk '{print $5 " " $7}'` echo -e "$messages\t\t mem\t ${mem}" echo -e "$messages\t\t time ${time}" done ================================================ FILE: benchmark/checks/utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Performance checks utils.""" import asyncio import inspect import multiprocessing import os import time from multiprocessing import Pool from pathlib import Path from statistics import mean, stdev, variance from typing import Any, Callable, Dict, List, Optional, Tuple, Type from unittest.mock import MagicMock import click import psutil # type: ignore from aea import AEA_DIR from aea.aea import AEA from aea.configurations.base import ConnectionConfig, PublicId, SkillConfig from aea.configurations.constants import ( DEFAULT_LEDGER, DEFAULT_PROTOCOL, PACKAGES, PROTOCOLS, SKILLS, _FETCHAI_IDENTIFIER, ) from aea.connections.base import Connection, ConnectionStates from aea.crypto.wallet import Wallet from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Protocol from aea.registries.resources import Resources from aea.skills.base import Behaviour, Handler, Skill, SkillContext from packages.fetchai.protocols.default.message import DefaultMessage ERROR_SKILL_NAME = "error" ROOT_DIR = os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe()))) # type: ignore PACKAGES_DIR = Path(AEA_DIR, "..", PACKAGES) output_format_deco = click.option( "--output_format", default="text", help="Output format" ) number_of_runs_deco = click.option( "--number_of_runs", default=10, help="How many times run test." ) def wait_for_condition( condition_checker: Callable, timeout: int = 2, error_msg: str = "Timeout" ) -> None: """Wait for condition occurs in selected timeout.""" start_time = time.time() while not condition_checker(): time.sleep(0.0001) if time.time() > start_time + timeout: raise TimeoutError(error_msg) def make_agent( agent_name: str = "my_agent", runtime_mode: str = "threaded", resources: Optional[Resources] = None, identity: Optional[Identity] = None, ) -> AEA: """Make AEA instance.""" wallet = Wallet({DEFAULT_LEDGER: None}) identity = identity or Identity( agent_name, address=agent_name, public_key=f"public_key_for_{agent_name}" ) resources = resources or Resources() datadir = os.getcwd() agent_context = MagicMock() agent_context.agent_name = agent_name agent_context.agent_address = agent_name resources.add_skill( Skill.from_dir( str(PACKAGES_DIR / _FETCHAI_IDENTIFIER / SKILLS / ERROR_SKILL_NAME), agent_context=agent_context, ) ) resources.add_protocol( Protocol.from_dir( str( PACKAGES_DIR / _FETCHAI_IDENTIFIER / PROTOCOLS / PublicId.from_str(DEFAULT_PROTOCOL).name ) ) ) return AEA(identity, wallet, resources, datadir, runtime_mode=runtime_mode) def make_envelope( sender: str, to: str, message: Optional[DefaultMessage] = None ) -> Envelope: """Make an envelope.""" if message is None: message = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"content", ) message.sender = sender message.to = to return Envelope( to=to, sender=sender, message=message, ) class GeneratorConnection(Connection): """Generates messages and count.""" connection_id = PublicId("fetchai", "generator", "0.1.0") ENABLED_WAIT_SLEEP = 0.00001 def __init__(self, *args: Any, **kwargs: Any): """Init connection.""" super().__init__(*args, **kwargs) self._enabled = False self.count_in = 0 self.count_out = 0 def enable(self) -> None: """Enable message generation.""" self._enabled = True def disable(self) -> None: """Disable message generation.""" self._enabled = False async def connect(self) -> None: """Connect connection.""" self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """Disconnect connection.""" self._state.set(ConnectionStates.disconnected) async def send(self, envelope: "Envelope") -> None: """Handle incoming envelope.""" self.count_in += 1 async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """Generate an envelope.""" while not self._enabled: await asyncio.sleep(self.ENABLED_WAIT_SLEEP) envelope = make_envelope(self.address, "echo_skill") self.count_out += 1 return envelope @classmethod def make( cls, ) -> "GeneratorConnection": """Construct connection instance.""" configuration = ConnectionConfig( connection_id=cls.connection_id, ) test_connection = cls( configuration=configuration, identity=Identity("name", "address", "public_key"), data_dir=".tmp", ) return test_connection class SyncedGeneratorConnection(GeneratorConnection): """Synchronized message generator.""" def __init__(self, *args: Any, **kwargs: Any) -> None: """Init connection.""" super().__init__(*args, **kwargs) self._condition: Optional[asyncio.Event] = None @property def condition(self) -> asyncio.Event: """Get condition.""" if self._condition is None: raise ValueError("Event not set.") return self._condition async def connect(self) -> None: """Connect connection.""" await super().connect() self._condition = asyncio.Event() self.condition.set() async def send(self, envelope: "Envelope") -> None: """Handle incoming envelope.""" await super().send(envelope) self.condition.set() async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """Generate an envelope.""" await self.condition.wait() self.condition.clear() return await super().receive(*args, **kwargs) def make_skill( agent: AEA, handlers: Optional[Dict[str, Type[Handler]]] = None, behaviours: Optional[Dict[str, Type[Behaviour]]] = None, ) -> Skill: """Construct skill instance for agent from behaviours.""" handlers = handlers or {} behaviours = behaviours or {} config = SkillConfig(name="test_skill", author="fetchai") skill_context = SkillContext(agent.context) skill = Skill(configuration=config, skill_context=skill_context) for name, handler_cls in handlers.items(): handler = handler_cls(name=name, skill_context=skill_context) skill.handlers.update({handler.name: handler}) for name, behaviour_cls in behaviours.items(): behaviour = behaviour_cls(name=name, skill_context=skill_context) skill.behaviours.update({behaviour.name: behaviour}) return skill def get_mem_usage_in_mb() -> float: """Get memory usage of the current process in megabytes.""" return 1.0 * psutil.Process(os.getpid()).memory_info().rss / 1024**2 def multi_run( num_runs: int, fn: Callable, args: Tuple ) -> List[Tuple[str, Any, Any, Any]]: """ Perform multiple test runs. :param num_runs: host many times to run :param fn: callable that returns list of tuples with result :param args: args to pass to callable :return: list of tuples of results """ multiprocessing.set_start_method("spawn") results = [] for _ in range(num_runs): with Pool(1) as p: results.append(p.apply(fn, tuple(args))) del p mean_values = map(mean, zip(*(map(lambda x: x[1], i) for i in results))) stdev_values = map(stdev, zip(*(map(lambda x: x[1], i) for i in results))) variance_values = map(variance, zip(*(map(lambda x: x[1], i) for i in results))) return list( zip(map(lambda x: x[0], results[0]), mean_values, stdev_values, variance_values) ) def print_results( output_format: str, parameters: Dict, result_fn: Callable[..., List[Tuple[str, Any, Any, Any]]], ) -> Any: """Print result for multi_run response.""" if output_format != "text": raise ValueError(f"Bad output format {output_format}") click.echo("Start test with options:") for name, value in parameters.items(): click.echo(f"* {name}: {value}") click.echo("\nResults:") for msg, *values_set in result_fn(): mean_, stdev_, variance_ = map(lambda x: round(x, 6), values_set) click.echo(f" * {msg}: mean: {mean_} stdev: {stdev_} variance: {variance_} ") click.echo("Test finished.") ================================================ FILE: benchmark/framework/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Performance testing framework.""" ================================================ FILE: benchmark/framework/aea_test_wrapper.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains AEA/AEABuilder wrapper to make performance tests easy.""" import uuid from threading import Thread from typing import Dict, List, Optional, Tuple, Type, Union from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.components.base import Component from aea.configurations.base import SkillConfig from aea.configurations.constants import _FETCHAI_IDENTIFIER from aea.mail.base import Envelope from aea.protocols.base import Message from aea.skills.base import Handler, Skill, SkillContext from benchmark.framework.fake_connection import FakeConnection from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.default.serialization import DefaultSerializer class AEATestWrapper: """A testing wrapper to run and control an agent.""" def __init__(self, name: str = "my_aea", components: List[Component] = None): """ Make an agency with optional name and skills. :param name: name of the agent :param components: dict of components to add to agent """ self.components = components or [] self.name = name self._fake_connection: Optional[FakeConnection] = None self.aea = self.make_aea(self.name, self.components) self._thread = None # type: Optional[Thread] def make_aea( self, name: Optional[str] = None, components: List[Component] = None ) -> AEA: """ Create AEA from name and already loaded components. :param name: name of the agent :param components: list of components to add to agent :return: AEA """ components = components or [] builder = AEABuilder() builder.set_name(name or self.name) builder.add_private_key(_FETCHAI_IDENTIFIER, private_key_path=None) for component in components: builder.add_component_instance(component) aea = builder.build() return aea @classmethod def make_skill( cls, config: SkillConfig = None, context: SkillContext = None, handlers: Optional[Dict[str, Type[Handler]]] = None, ) -> Skill: """ Make a skill from optional config, context, handlers dict. :param config: SkillConfig :param context: SkillContext :param handlers: dict of handler types to add to skill :return: Skill """ handlers = handlers or {} context = context or SkillContext() config = config or SkillConfig( name="skill_{}".format(uuid.uuid4().hex[0:5]), author="fetchai" ) handlers_instances = { name: handler_cls(name=name, skill_context=context) for name, handler_cls in handlers.items() } skill = Skill( configuration=config, skill_context=context, handlers=handlers_instances ) return skill @classmethod def dummy_default_message( cls, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, performative: DefaultMessage.Performative = DefaultMessage.Performative.BYTES, content: Union[str, bytes] = "hello world!", ) -> Message: """ Construct simple message, all arguments are optional. :param dialogue_reference: the dialogue reference. :param message_id: the message id. :param target: the message target. :param performative: the message performative. :param content: string or bytes payload. :return: Message """ if isinstance(content, str): content = content.encode("utf-8") return DefaultMessage( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=performative, content=content, ) @classmethod def dummy_envelope( cls, to: str = "test", sender: str = "test", message: Message = None, ) -> Envelope: """ Create envelope, if message is not passed use .dummy_message method. :param to: the address of the receiver. :param sender: the address of the sender. :param message: the protocol-specific message. :return: Envelope """ message = message or cls.dummy_default_message() return Envelope( to=to, sender=sender, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(message), ) def set_loop_timeout(self, period: float) -> None: """ Set agent's loop timeout. :param period: idle sleep timeout for agent's loop """ self.aea._period = period # pylint: disable=protected-access def setup(self) -> None: """Set up agent: start multiplexer etc.""" self.aea.setup() def stop(self) -> None: """Stop the agent.""" self.aea.stop() def put_inbox(self, envelope: Envelope) -> None: """ Add an envelope to agent's inbox. :param envelope: envelope to process by agent """ self.aea.runtime.multiplexer.in_queue.put(envelope) def is_inbox_empty(self) -> bool: """ Check there is no messages in inbox. :return: boolean indicating whether inbox is empty """ return self.aea.runtime.multiplexer.in_queue.empty() def __enter__(self) -> None: """Context manager enter.""" self.start_loop() def __exit__( # type: ignore # pylint: disable=useless-return self, exc_type=None, exc=None, traceback=None ) -> None: """Context manager exit, stop agent.""" self.stop_loop() return None def start_loop(self) -> None: """Start agents loop in dedicated thread.""" self._thread = Thread(target=self.aea.start) self._thread.start() def stop_loop(self) -> None: """Stop agents loop in dedicated thread, close thread.""" if self._thread is None: raise ValueError("Thread not set, call start_loop first.") self.aea.stop() self._thread.join() def is_running(self) -> bool: """ Check is agent loop is set as running. :return: bool """ return not self.aea.is_running def set_fake_connection( self, inbox_num: int, envelope: Optional[Envelope] = None ) -> None: """ Add fake connection for testing. :param inbox_num: number of messages to generate by connection. :param envelope: envelope to generate. dummy one created by default. """ if self._fake_connection: raise Exception("Fake connection is already set!") envelope = envelope or self.dummy_envelope() self._fake_connection = FakeConnection( envelope, inbox_num, connection_id="fake_connection" ) self.aea.runtime.multiplexer.add_connection(self._fake_connection) def is_messages_in_fake_connection(self) -> bool: """ Check fake connection has messages left. :return: bool """ if not self._fake_connection: raise Exception("Fake connection is not set!") return self._fake_connection.num != 0 # type: ignore # cause fake connection is used. ================================================ FILE: benchmark/framework/benchmark.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Helper for benchmark run control.""" from multiprocessing import Queue class BenchmarkControl: """Class to sync executor and function in test.""" START_MSG = "start" def __init__(self) -> None: """Init.""" self._queue: Queue = Queue(2) def start(self) -> None: """Notify executor to start measure resources.""" self._queue.put(self.START_MSG) def wait_msg(self) -> str: """ Wait a message from function being tested. :return: message from tested function. """ return self._queue.get() ================================================ FILE: benchmark/framework/cli.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Cli implementation for performance tests suits.""" import ast import inspect from typing import Any, Callable, Dict, List, Optional, Tuple, Type import click from click.core import Argument, Command, Context, Option, Parameter from .executor import Executor from .func_details import BenchmarkFuncDetails from .report_printer import PerformanceReport, ReportPrinter class DefaultArgumentsMultiple(Argument): """Multiple arguments with default value.""" def __init__(self, *args: Any, **kwargs: Any) -> None: """Create MultipleArguments instance.""" kwargs["nargs"] = -1 default = kwargs.pop("default", tuple()) super().__init__(*args, **kwargs) self.default = default def full_process_value(self, ctx: Context, value: Any) -> Any: """ Given a value and context this runs the logic to convert the value as necessary. :param ctx: command context :param value: value for option parsed from command line :return: value for option """ if not value: value = self.default else: value = [self._parse_arg_str(i) for i in value] return super().process_value(ctx, value) @staticmethod def _parse_arg_str(args: str) -> Tuple[Any]: """ Parse arguments string to tuple. :param args: arguments string separated by comma :return: tuple of parsed arguments """ parsed = ast.literal_eval(args) if not isinstance(parsed, tuple): parsed = (parsed,) return parsed class TestCli: """Performance test client.""" def __init__( self, func: Callable, executor_class: Type[Executor] = Executor, report_printer_class: Type[ReportPrinter] = ReportPrinter, ) -> None: """ Make performance client. :param func: function to be tested. :param executor_class: executor to be used for testing :param report_printer_class: report printer to print results func - should be function with first parameter multithreading.Queue func - should have docstring, and default values for every extra argument. Example of usage: def test_fn(benchmark: BenchmarkControl, list_size: int = 1000000): # test list iteration # prepare some data: big_list = list(range(list_size)) # ready for test benchmark.start() for i in range(big_list): i ** 2 # actually do nothing TestCli(test_fn).run() """ self.func_details = BenchmarkFuncDetails(func) self.func_details.check() self.func = func self.executor_class = executor_class self.report_printer_class = report_printer_class self._report_printer = None # type: Optional[ReportPrinter] @property def report_printer(self) -> ReportPrinter: """Get report printer.""" if self._report_printer is None: raise ValueError("report printer not set!") return self._report_printer def _make_command(self) -> Command: """ Make cli.core.Command. :return: a cli command """ return Command( None, # type: ignore params=self._make_command_params(), callback=self._command_callback, help=self._make_help(), ) def _make_command_params(self) -> Optional[List[Parameter]]: """ Make parameters and arguments for cli.Command. :return: list of options and arguments for cli Command """ return list(self._executor_params().values()) + self._call_params() def _make_help(self) -> str: """ Make help for command. :return: str. """ doc_str = inspect.cleandoc( f""" {self.func_details.doc} ARGS is function arguments in format: `{','.join(self.func_details.argument_names)}` default ARGS is `{self.func_details.default_argument_values_as_string}` """ ) return doc_str @staticmethod def _executor_params() -> Dict[str, Parameter]: """ Get parameters used by Executor. :return: dict of executor's parameters for cli Command """ parameters = { "timeout": Option( ["--timeout"], default=10.0, show_default=True, help="Executor timeout in seconds", type=float, ), "period": Option( ["--period"], default=0.1, show_default=True, help="Period for measurement", type=float, ), } return ( parameters # type: ignore # for some reason mypy does not follow superclass ) def _call_params(self) -> List[Parameter]: """ Make command option and parameters for test cases. :return: function args set, number of executions option, plot option """ argument = DefaultArgumentsMultiple( ["args"], default=[self.func_details.default_argument_values] ) num_executions = Option( ["--num-executions", "-N"], default=1, show_default=True, help="Number of runs for each case", type=int, ) plot = Option( ["--plot", "-P"], default=None, show_default=True, help="X axis parameter idx", type=int, ) return [argument, num_executions, plot] def run(self) -> None: """Run performance test.""" command = self._make_command() command() def _command_callback(self, **params: Any) -> None: """ Run test on command. :param params: dictionary of options and arguments of cli Command """ arguments_list = params.pop("args") executor_params = { k: v for k, v in params.items() if k in self._executor_params() } executor = self.executor_class(**executor_params) num_executions = params["num_executions"] self._report_printer = self.report_printer_class( self.func_details, executor_params ) self.report_printer.print_context_information() reports = [] for arguments in arguments_list: report = self._execute_num_times(arguments, executor, num_executions) self.report_printer.print_report(report) reports.append(report) self._draw_plot(params, reports) def _draw_plot( self, params: Dict[str, Parameter], reports: List[PerformanceReport] ) -> None: """ Draw a plot with case resources if param enabled by command option. Block by plot window shown! :param params: dict of command options passed :param reports: list of performance reports to draw charts for """ xparam_idx = params.get("plot") if xparam_idx is None: return import matplotlib.pyplot as plt # type: ignore # pylint: disable=import-outside-toplevel reports_sorted_by_arg = sorted(reports, key=lambda x: x.arguments[xparam_idx]) # type: ignore xaxis = [i.arguments[xparam_idx] for i in reports_sorted_by_arg] # type: ignore _, ax = plt.subplots(3) # time self._draw_resource(ax[0], xaxis, reports_sorted_by_arg, [0], "Time") # cpu self._draw_resource(ax[1], xaxis, reports_sorted_by_arg, [1, 2, 3], "cpu") # mem self._draw_resource(ax[2], xaxis, reports_sorted_by_arg, [4, 5, 6], "mem") plt.show() @staticmethod def _draw_resource( plt: "matplotpib.axes.Axes", # type: ignore # noqa: F821 xaxis: List[float], reports: List[PerformanceReport], resources_range: List[int], title: str, ) -> None: """ Draw a plot for specific resource. :param plt: a subplot to draw on :param xaxis: list of values for x axis :param reports: performance reports to get values from :param resources_range: list of resource ids in performance.resource list to draw values for :param title: title for chart. """ for r in resources_range: res = reports[0].resources[r] label = res.name plt.plot(xaxis, [i.resources[r].value for i in reports], label=label) plt.set_ylabel(res.unit) plt.set_title(title) plt.legend() def _execute_num_times( self, arguments: Tuple[Any], executor: Executor, num_executions: int ) -> PerformanceReport: """ Execute case several times and provide a performance report. :param arguments: list of arguments for function tested. :param executor: executor to run tests. :param num_executions: how may times repeat test for arguments set. :return: performance report with mean values for every resource counted for multiple runs """ exec_reports = [ executor.run(self.func, arguments) for _ in range(num_executions) ] return PerformanceReport(exec_reports) def print_help(self) -> None: """Print help for command. can be invoked with --help option.""" command = self._make_command() with click.Context(command) as ctx: # type: ignore click.echo(command.get_help(ctx)) ================================================ FILE: benchmark/framework/executor.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Executor to run and measure resources consumed by python code.""" import datetime import inspect import multiprocessing import time from collections import namedtuple from contextlib import contextmanager from multiprocessing import Process from operator import attrgetter from statistics import mean from typing import Callable, Generator, List, Tuple import memory_profiler # type: ignore import psutil # type: ignore from benchmark.framework.benchmark import BenchmarkControl # noqa: I100 class TimeItResult: # pylint: disable=too-few-public-methods """Class to store execution time for timeit_context.""" def __init__(self) -> None: """Init with time passed = -1.""" self.time_passed = -1.0 @contextmanager def timeit_context() -> Generator: """ Context manager to measure execution time of code in context. :yield: TimeItResult example: with timeit_context() as result: do_long_code() print("Long code takes ", result.time_passed) """ result = TimeItResult() started_time = time.time() try: yield result finally: result.time_passed = time.time() - started_time ResourceStats = namedtuple("ResourceStats", "time,cpu,mem") class ExecReport: """Process execution report.""" def __init__( self, args: tuple, time_passed: float, stats: List[ResourceStats], is_killed: bool, period: float, ): """Make an instance. :param args: tuple of arguments passed to function tested. :param time_passed: time test function was executed. :param stats: list of ResourceStats: cpu, mem. :param is_killed: was process terminated by timeout. :param period: what is measurement period length. """ self.args = args self.report_created = datetime.datetime.now() self.time_passed = time_passed self.stats = stats self.is_killed = is_killed self.period = period @property def cpu(self) -> List[float]: """ Return list of cpu usage records. :return: list of cpu usage values """ return list(map(attrgetter("cpu"), self.stats)) @property def mem(self) -> List[float]: """ Return list of memory usage records. :return: list of memory usage values """ return list(map(attrgetter("mem"), self.stats)) def __str__(self) -> str: """ Render report to string. :return: string representation of report. """ return inspect.cleandoc( f""" == Report created {self.report_created} == Arguments are `{self.args}` Time passed {self.time_passed} Terminated by timeout: {self.is_killed} Cpu(%) mean: {mean(self.cpu)} Cpu(%) min: {min(self.cpu)} Cpu(%) max: {max(self.cpu)} Mem(kb) mean: {mean(self.mem)} Mem(kb) min: {min(self.mem)} Mem(kb) max: {max(self.mem)} """ ) class Executor: """Process execution and resources measurement.""" def __init__(self, period: float = 0.1, timeout: float = 30): """ Set executor with parameters. :param period: period to take resource measurement. :param timeout: time limit to perform test, test process will be killed after timeout. """ self.period = period self.timeout = timeout def run(self, func: Callable, args: tuple) -> ExecReport: """ Run function to be tested for performance. :param func: function or callable to be tested for performance. :param args: tuple of argument to pass to function tested. :return: execution report for single test run """ process = self._prepare(func, args) time_usage, stats, killed = self._measure(process) return self._report(args, time_usage, stats, killed) @staticmethod def _prepare(func: Callable, args: tuple) -> Process: """ Start process and wait process ready to be measured. :param func: function or callable to be tested for performance. :param args: tuple of argument to pass to function tested. :return: process with tested code """ control: BenchmarkControl = BenchmarkControl() process = Process(target=func, args=(control, *args)) process.start() msg = control.wait_msg() if msg != control.START_MSG: raise ValueError("Msg does not match control start message.") return process def _measure( self, process: multiprocessing.Process ) -> Tuple[float, List[ResourceStats], bool]: """ Measure resources consumed by the process. :param process: process to measure resource consumption :return: time used, list of resource stats, was killed """ started_time = time.time() is_killed = False proc_info = psutil.Process(process.pid) stats = [] with timeit_context() as timeit: while process.is_alive(): if time.time() - started_time > self.timeout: is_killed = True break stats.append(self._get_stats_record(proc_info)) time.sleep(self.period) if is_killed: process.terminate() process.join() time_usage = timeit.time_passed return time_usage, stats, is_killed @staticmethod def _get_stats_record(proc_info: psutil.Process) -> ResourceStats: """ Read resources usage and create record. :param proc_info: process information to get cpu usage and memory usage from. :return: one time resource stats record """ return ResourceStats( time.time(), proc_info.cpu_percent(), memory_profiler.memory_usage(proc_info.pid, max_usage=True), ) def _report( self, args: tuple, time_passed: float, stats: List[ResourceStats], is_killed: bool, ) -> ExecReport: """ Create execution report. :param args: tuple of argument to pass to function tested. :param time_passed: time test function was executed. :param stats: list of ResourceStats: cpu, mem. :param is_killed: was process terminated by timeout. :return: test case one execution report """ return ExecReport(args, time_passed, stats, is_killed, self.period) ================================================ FILE: benchmark/framework/fake_connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Fake connection to generate test messages.""" import asyncio from typing import Any, Optional from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope class FakeConnection(Connection): """Simple fake connection to populate inbox.""" def __init__(self, envelope: Envelope, num: int, *args: Any, **kwargs: Any): """ Set fake connection with number of envelops to be generated. :param envelope: any envelope :param num: amount of envelopes to generate :param args: positional arguments :param kwargs: keyword arguments """ Connection.__init__(self, *args, **kwargs) self.num = num self.envelope = envelope self.state = ConnectionStates.connected async def connect(self) -> None: """Do nothing. always connected.""" async def disconnect(self) -> None: """Disconnect. just set a flag.""" self.state = ConnectionStates.disconnected async def send(self, envelope: Envelope) -> None: """ Do not send custom envelops. Only generates. :param envelope: envelope to send. :return: None """ return None async def receive(self, *args: Any, **kwargs: Any) -> Optional[Envelope]: """ Return envelope set `num` times. :param args: positional arguments :param kwargs: keyword arguments :return: incoming envelope """ if self.num <= 0: await asyncio.sleep(0.1) # sleep to avoid multiplexer loop without idle. return None self.num -= 1 return self.envelope ================================================ FILE: benchmark/framework/func_details.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Helper module for details about function being tested.""" import inspect from inspect import Parameter from typing import Any, Callable, List class BaseFuncDetails: """Class to introspect some callable details: name, docstring, arguments.""" def __init__(self, func: Callable): """ Create an instance. :param func: Function or another callable to get details. """ self.func = func @property def doc(self) -> str: """ Return docstring for function. :return: str. docstring for function """ return self.func.__doc__ or "" @property def name(self) -> str: """ Return function definition name. :return: str """ return self.func.__name__ or "" @property def _arguments(self) -> List[Parameter]: """ Get list of arguments defined in function. :return: list of function parameters """ sig = inspect.signature(self.func) return list(sig.parameters.values()) @property def argument_names(self) -> List[str]: """ Get list of argument names in function. :return: list of function argument names """ return [i.name for i in self._arguments] @property def default_argument_values(self) -> List[Any]: """ Get list of argument default values. :return: list of default values for function arguments """ default_args = [] for arg in self._arguments: default_args.append(arg.default) return default_args @property def default_argument_values_as_string(self) -> str: """ Get list of argument default values as a string. :return: str """ return ",".join(map(repr, self.default_argument_values)) class BenchmarkFuncDetails(BaseFuncDetails): """ Special benchmarked function details. With check of function definition. :param CONTROL_ARG_NAME: Name of the special argument name, placed first. """ CONTROL_ARG_NAME: str = "benchmark" def check(self) -> None: """ Check for docstring and arguments have default values set. Raises exception if function definition does not contain docstring or default values. """ if not self.doc: raise ValueError("Function docstring is missing") if super()._arguments[0].name != self.CONTROL_ARG_NAME: raise ValueError( f"first function argument must be named `{self.CONTROL_ARG_NAME}`!" ) for arg in self._arguments: if arg.default == inspect._empty: # type: ignore # pylint: disable=protected-access raise ValueError( "function should have default values for every param except first one" ) @property def _arguments(self) -> List[Parameter]: """ Skip first argument, cause it special. :return: list of function arguments except the first one named `benchmark` """ return super()._arguments[1:] ================================================ FILE: benchmark/framework/report_printer.py ================================================ # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Performance report printer for performance tool.""" import inspect from collections import namedtuple from datetime import datetime from statistics import mean, stdev from typing import Any, Callable, List, Optional, Tuple, cast from .executor import ExecReport from .func_details import BaseFuncDetails class ContextPrinter: """Printer for test execution context: function, arguments, execution parameters.""" def __init__(self, func_details: BaseFuncDetails, executor_params: dict) -> None: """ Make performance report printer instance. :param func_details: details about function being tested :param executor_params: executor parameters: timeout, interval """ self.func_details = func_details self.executor_params = executor_params def print_context_information(self) -> None: """Print details about tested function and execution parameters.""" self._print_executor_details() self._print_func_details() print() def _print_executor_details(self) -> None: """Print details about timeout and period timer of executor.""" topic = "Test execution" print(f"{topic} timeout: {self.executor_params['timeout']}") print(f"{topic} measure period: {self.executor_params['period']}") def _print_func_details(self) -> None: """Print details about function to be tested.""" topic = "Tested function" print(f"{topic} name: {self.func_details.name}") print(f"{topic} description: {self.func_details.doc}") print(f"{topic} argument names: {self.func_details.argument_names}") print( f"{topic} argument default values: {self.func_details.default_argument_values}" ) ResourceRecord = namedtuple("ResourceRecord", "name,unit,value,std_dev") class PerformanceReport: """Class represents performance report over multiple exec reports.""" def __init__(self, exec_reports: List[ExecReport]): """ Init performance report with exec reports. :param exec_reports: tested function execution reports with measurements """ self.exec_reports = exec_reports @property def arguments(self) -> Tuple[Any, ...]: """ Return list of arguments for tested function. :return: tuple of arguments """ return self.exec_reports[-1].args @property def report_time(self) -> datetime: """ Return time report was created. :return: datetime """ return self.exec_reports[-1].report_created @property def number_of_runs(self) -> int: """ Return number of executions for this case. :return: int """ return len(self.exec_reports) @property def number_of_terminates(self) -> int: """ Return amount how many times execution was terminated by timeout. :return: int """ return sum((i.is_killed for i in self.exec_reports), 0) @property def resources(self) -> List[ResourceRecord]: """ Return resources values used during execution. :return: List of ResourceRecord """ resources: List[ResourceRecord] = [] resources.append( self._make_resource("Time passed", "seconds", "time_passed", None) ) for name, unit in [("cpu", "%"), ("mem", "kb")]: for func in [min, max, mean]: resources.append( self._make_resource( f"{name} {func.__name__}", unit, name, cast(Callable, func) ) ) return resources def _make_resource( self, name: str, unit: str, attr_name: str, aggr_function: Optional[Callable], # noqa ) -> ResourceRecord: """ Make ResourceRecord. :param name: str. name of the resource (time, cpu, mem,...) :param unit: str. measure unit (seconds, kb, %) :param attr_name: name of the attribute of execreport to count resource. :param aggr_function: function to process value of execreport. :return: ResourceRecord """ return ResourceRecord( name, unit, *self._count_resource(attr_name, aggr_function) ) def _count_resource( self, attr_name: str, aggr_function: Optional[Callable] = None ) -> Tuple[float, float]: """ Calculate resources from exec reports. :param attr_name: name of the attribute of execreport to count resource. :param aggr_function: function to process value of execreport. :return: (mean_value, standart_deviation) """ if not aggr_function: aggr_function = ( lambda x: x # pylint: disable=unnecessary-lambda-assignment ) # noqa: E731 values = [aggr_function(getattr(i, attr_name)) for i in self.exec_reports] mean_value = mean(values) std_dev = stdev(values) if len(values) > 1 else 0 return (mean_value, std_dev) class ReportPrinter(ContextPrinter): """Class to handle output of performance test.""" @staticmethod def _print_header(report: PerformanceReport) -> None: """ Print header for performance report. Prints arguments, number of runs and number of terminates. :param report: performance report to print header for """ text = inspect.cleandoc( f""" == Report created {report.report_time} == Arguments are `{report.arguments}` Number of runs: {report.number_of_runs} Number of time terminated: {report.number_of_terminates} """ ) print(text) @staticmethod def _print_resources(report: PerformanceReport) -> None: """ Print resources details for performance report. :param report: performance report to print header for """ for resource in report.resources: print( f"{resource.name} ({resource.unit}): {resource.value} ± {resource.std_dev}" ) def print_report(self, report: PerformanceReport) -> None: """ Print full performance report for case. :param report: performance report to print header for """ self._print_header(report) self._print_resources(report) ================================================ FILE: benchmark/run_from_branch.sh ================================================ #!/bin/bash REPO=https://github.com/fetchai/agents-aea.git BRANCH=main TMP_DIR=$(mktemp -d -t bench-XXXXXXXXXX) git clone --branch $BRANCH $REPO $TMP_DIR CURDIR=`pwd` cd $TMP_DIR echo "Installing the dependencies." pip install pipenv # this is to install benchmark dependencies pipenv --rm pipenv install --dev --skip-lock # this is to install the AEA in the Pipenv virtual env pipenv run pip install --upgrade .[all] -y pipenv run pip uninstall typing -y pipenv run pip install --no-deps aea-ledger-fetchai -y pipenv run pip install --no-deps aea-ledger-cosmos -y pipenv run pip install --no-deps aea-ledger-ethereum -y chmod +x benchmark/checks/run_benchmark.sh echo "Start the experiments." # we need to add the current directory to PYTHONPATH so we can import from local dirs PYTHONPATH=${PYTHONPATH}:$TMP_DIR pipenv run ./benchmark/checks/run_benchmark.sh rm -fr $TMPDIR cd $CURDIR echo "Done!" ================================================ FILE: benchmark/run_mem_check_in_cloud.sh ================================================ #!/bin/bash CURR_BRANCH=`git rev-parse --abbrev-ref HEAD` prepare_cmd="git clone https://github.com/fetchai/agents-aea.git -b $CURR_BRANCH && cd agents-aea/ && pip install -e .[all] && pipenv install -d --deploy --skip-lock --system" run_check_cmd="echo Start Test && cd /agents-aea/ && PYTHONPATH=`pwd` bash ./benchmark/checks/run_benchmark_messages_mem.sh" cmd="($prepare_cmd) &>/dev/null && $run_check_cmd" kubectl delete po benchmark-checks &>/dev/null kubectl run benchmark-checks --image=gcr.io/fetch-ai-sandbox/aea-benchmark:0.0.0 --overrides='{"spec": {"dnsPolicy": "Default", "hostNetwork": true}}' --restart=Never --attach --rm -- bash -c "$cmd" ================================================ FILE: codecov.yml ================================================ coverage: range: 70..100 round: down precision: 2 ================================================ FILE: deploy-image/.aea/cli_config.yaml ================================================ auth_token: author: registry_path: "packages" ================================================ FILE: deploy-image/Dockerfile ================================================ FROM python:3.8-alpine USER root RUN apk add --no-cache make git bash # cryptography: https://cryptography.io/en/latest/installation/#alpine RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev # https://stackoverflow.com/a/57485724 RUN apk add --update --no-cache py3-numpy py3-scipy py3-pillow ENV PYTHONPATH "$PYTHONPATH:/usr/lib/python3.7/site-packages" # golang RUN apk add --no-cache go # aea installation RUN pip install --upgrade pip RUN pip install --upgrade --force-reinstall aea[all]==1.1.1 # directories and aea cli config COPY /.aea /home/.aea WORKDIR /home/agents COPY ./packages /home/agents/packages # aea build script COPY /build.sh /build.sh RUN ["chmod", "+x", "/build.sh"] RUN [ "/build.sh" ] # optionally, specify any ports to expose here # EXPOSE 9000 # aea entrypoint script COPY /entrypoint.sh /entrypoint.sh RUN ["chmod", "+x", "/entrypoint.sh"] CMD [ "/entrypoint.sh" ] ================================================ FILE: deploy-image/README.md ================================================ # Docker Deployment image This guide explains how to prepare a Docker image containing your AEA for deployment. ## Creating your own image The example uses the `fetchai/my_first_aea` project. You will likely want to modify it to one of your own agents or an agent from the AEA registry. ### Fetch the example directory Install subversion, then download the example directory to your local working directory ``` bash svn export https://github.com/fetchai/agents-aea/trunk/deploy-image ``` ### Modify scripts First, review the `build.sh` script to make sure you are fetching the correct agent and do all the necessary setup. Here you can modify the agent you want to use. Note, when fetching from local, make sure your local packages are in the (currently empty) `packages` folder. Second, review the `entrypoint.sh` script to make sure you supply the agent with the right amount of private keys. In the example one key-pair each for the agent and connection is used, you might need more than that depending on your agent (see `required_ledgers` of your agent). Importantly, do not add any private keys during the build step! Third, create a local `.env` file with the relevant environment variables: ``` bash AGENT_PRIV_KEY=hex_key_here CONNECTION_PRIV_KEY=hex_key_here ``` Finally, if required, modify the `Dockerfile` to expose any ports needed by the AEA. (The default example does not require this.) ### Build the image ``` bash docker build -t my_first_aea -f Dockerfile . ``` ## Run ``` bash docker run --env-file .env -t my_first_aea ``` To stop, use `docker ps` to find the container id and then `docker stop CONTAINER_ID` to stop the container. ## Advanced usage and comments - The above approach implies that key files remain in the container. To avoid this, a static volume can be mounted with the key files in it (). ================================================ FILE: deploy-image/build.sh ================================================ #!/bin/bash set -e # setup the agent aea fetch fetchai/my_first_aea:latest cd my_first_aea/ aea install aea build ================================================ FILE: deploy-image/entrypoint.sh ================================================ #!/bin/bash set -e aea --version cd my_first_aea # create private key files echo ${AGENT_PRIV_KEY} > fetchai_private_key.txt echo ${CONNECTION_PRIV_KEY} > connection_fetchai_private_key.txt # add keys aea add-key fetchai fetchai_private_key.txt aea add-key fetchai connection_fetchai_private_key.txt --connection # issue certs aea issue-certificates # run aea run ================================================ FILE: deploy-image/packages/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Package registry for the AEA framework.""" ================================================ FILE: develop-image/Dockerfile ================================================ FROM ubuntu:20.04 RUN apt-get update && \ apt-get install -y dialog && \ apt-get install -y apt-utils && \ apt-get upgrade -y && \ apt-get install -y sudo # This adds the 'default' user to sudoers with full privileges: RUN HOME=/home/default && \ mkdir -p ${HOME} && \ GROUP_ID=1000 && \ USER_ID=1000 && \ groupadd -r default -f -g "$GROUP_ID" && \ useradd -u "$USER_ID" -r -g default -d "$HOME" -s /sbin/nologin \ -c "Default Application User" default && \ chown -R "$USER_ID:$GROUP_ID" ${HOME} && \ usermod -a -G sudo default && \ echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \ build-essential \ software-properties-common \ vim \ make \ git \ less \ curl \ wget \ zlib1g-dev \ libssl-dev \ libffi-dev \ python3-venv \ python3-pip \ python3-dev # matplotlib build dependencies RUN apt-get install -y \ libxft-dev \ libfreetype6 \ libfreetype6-dev # needed by Pipenv ENV DEBIAN_FRONTEND noninteractive ENV LC_ALL C.UTF-8 ENV LANG C.UTF-8 RUN apt-get install -y tox RUN python3 -m pip install -U pipenv==2021.5.29 ENV PATH="/usr/local/bin:${PATH}" USER default RUN sudo mkdir /build WORKDIR /build COPY . /build RUN sudo chown -R default /build RUN sudo make clean RUN pipenv --python python3.8 RUN pipenv run pip3 install --upgrade pip RUN pipenv install --dev --skip-lock RUN pipenv run pip3 install .[all] CMD ["/bin/bash"] ================================================ FILE: develop-image/README.md ================================================ # Docker Development image All the commands must be executed from the parent directory, if not stated otherwise. ## Build ``` bash ./develop-image/scripts/docker-build-img.sh -t fetchai/aea-deploy:latest -- ``` To pass immediate parameters to the `docker build` command: ``` bash ./develop-image/scripts/docker-build-img.sh arg1 arg2 -- ``` E.g.: ``` bash ./develop-image/scripts/docker-build-img.sh --squash --cpus 4 --compress -- ``` ## Run ``` bash ./develop-image/scripts/docker-run.sh -- /bin/bash ``` As before, to pass parameters to the `docker run` command: ``` bash ./develop-image/scripts/docker-run.sh -p 8080:80 -- /bin/bash ``` ## Publish First, be sure you tagged the image with the `latest` tag: ``` bash docker tag fetchai/aea-develop: fetchai/aea-develop:latest ``` Then, publish the images. First, the `fetchai/aea-develop:` ``` bash ./develop-image/scripts/docker-publish-img.sh ``` And then, the `fetchai/aea-develop:latest` image: - In `docker-env.sh`, uncomment `DOCKER_IMAGE_TAG=fetchai/aea-develop:latest` - Run the publish command again: ``` bash ./develop-image/scripts/docker-publish-img.sh ``` ### Publish to k8s Switch the context: ``` shell kubectx sandbox ``` List pods in cluster: ``` shell kubectl get pods ``` Optionally, create new namespace: ``` shell kubectl create namespace aea-research ``` Ensure right namespace is used: ``` shell kubens aea-research ``` Choose namespace in cluster: ``` shell kubens aea-research ``` To enter selected namespace: ``` shell kubens ``` From the `develop-image` folder run: ``` shell skaffold run -p sandbox ``` SSH into a new image: ``` shell kubectl run --generator=run-pod/v1 -it debian --image=debian -- bash ``` ## Dedicated node pool for benchmarking agents ### Setup and tear down To create the node pool ``` shell gcloud container node-pools create agent-test-pool --cluster sandbox --project fetch-ai-sandbox --node-taints dedicated=agent:NoSchedule --machine-type=n1-standard-4 --num-nodes=1 --enable-autoscaling --node-labels=type=agent-test --max-nodes=1 --min-nodes=0 ``` To remove the node pool ``` shell gcloud container node-pools delete agent-test-pool --cluster sandbox --project fetch-ai-sandbox ``` ### Usage List pods ``` shell kubectl get pod -o wide ``` ``` shell kubectl exec -it NAME -- bash ``` ================================================ FILE: develop-image/docker-env.sh ================================================ #!/bin/bash # Swap the following lines if you want to work with 'latest' DOCKER_IMAGE_TAG=fetchai/aea-develop:1.1.1 # DOCKER_IMAGE_TAG=aea-develop:latest DOCKER_BUILD_CONTEXT_DIR=.. DOCKERFILE=./Dockerfile ================================================ FILE: develop-image/k8s/deployment.yaml ================================================ --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: aea-develop-image namespace: aea-research labels: app: aea-develop-image spec: replicas: 1 template: metadata: labels: app: aea-develop-image spec: tolerations: - key: dedicated operator: Equal value: agent effect: NoSchedule nodeSelector: type: agent-test containers: - image: gcr.io/fetch-ai-sandbox/aea-develop-image name: aea-develop-image resources: requests: memory: "64Mi" cpu: "150m" limits: memory: "128Mi" cpu: "500m" restartPolicy: Always ... --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: aea-deploy-image namespace: aea-research labels: app: aea-deploy-image spec: replicas: 1 template: metadata: labels: app: aea-deploy-image spec: tolerations: - key: dedicated operator: Equal value: agent effect: NoSchedule nodeSelector: type: agent-test containers: - image: gcr.io/fetch-ai-sandbox/aea-deploy-image name: aea-deploy-image resources: requests: memory: "64Mi" cpu: "150m" limits: memory: "128Mi" cpu: "500m" restartPolicy: Always ... ================================================ FILE: develop-image/scripts/docker-build-img.sh ================================================ #!/bin/bash -e # Usage: # ./docker-build-img.sh -- # Where: # * resulting docker build commandline will be: # docker build $IMMEDIATE_PARAMS -t $DOCKER_IMAGE_TAG $TAIL_PARAMS $DOCKER_BUILD_CONTEXT_DIR # * DOCKER_IMAGE_TAG and DOCKER_BUILD_CONTEXT_DIR variables are defined in the `docker-env.sh` and/or `docker-env-common.sh` # # Examples: # * the following example provides the `--cpus 4 --compress` parameters to `docker build` command as IMMEDIATE_PARAMS, **ommiting** the TAIL_PARAMS: # # ./docker-build-img.sh --cpus 4 ---compress -- # # the the resulting docker process commandline will be: # # docker build --cpus 4 --compress -t $DOCKER_IMAGE_TAG $TAIL_PARAMS $DOCKER_BUILD_CONTEXT_DIR # # * the following example provides the `--squash` parameters to `docker build` command as IMMEDIATE_PARAMS, and `../../` parameter as TAIL_PARAMS (what corresponds to the context directory, what also means that DOCKER_BUILD_CONTEXT_DIR variable needs to be unset or set to empty string in the `docker-env.sh`): # # ./docker-build-img.sh --squash -- ../../ # # the the resulting docker process commandline will be: # # docker build --squash -t $DOCKER_IMAGE_TAG ../../ $DOCKER_BUILD_CONTEXT_DIR # # the `DOCKER_BUILD_CONTEXT_DIR` shall be set to empty string in `docker-env.sh` file. # NOTE: For more details, please see description for the `split_params()` shell function in the `docker-common.sh` script. SCRIPTS_DIR=${0%/*} . "$SCRIPTS_DIR"/docker-env-common.sh docker_build_callback() { local IMMEDIATE_PARAMS="$1" local TAIL_PARAMS="$2" if [ -n "${DOCKERFILE}" ]; then TAIL_PARAMS="-f $DOCKERFILE $TAIL_PARAMS" fi local COMMAND="docker build $IMMEDIATE_PARAMS -t $DOCKER_IMAGE_TAG $TAIL_PARAMS $DOCKER_BUILD_CONTEXT_DIR" echo $COMMAND $COMMAND } split_params docker_build_callback "$@" ================================================ FILE: develop-image/scripts/docker-publish-img.sh ================================================ #!/bin/bash -e # NOTE: First docker needs to be authorized to push image to container registry. # Normally this is done using 'docker login ', where the # 'registry_url' value is set in the $DOCKER_CONTAINER_REGISTRY environment # variable which is defined in the 'docker-env-common.sh' script file. # If you are using the Google cloud docker registry, please run the # 'gcloud auth configure-docker' instead. SCRIPTS_DIR=${0%/*} . "$SCRIPTS_DIR"/docker-env-common.sh docker tag "$DOCKER_IMAGE_TAG" "$REGISTRY_DOCKER_IMAGE_TAG" docker push "$REGISTRY_DOCKER_IMAGE_TAG" ================================================ FILE: develop-image/skaffold.yaml ================================================ apiVersion: skaffold/v1beta13 kind: Config build: artifacts: local: {} profiles: - name: sandbox build: artifacts: - image: gcr.io/fetch-ai-sandbox/aea-develop-image context: .. docker: dockerfile: develop-image/Dockerfile - image: gcr.io/fetch-ai-sandbox/aea-deploy-image context: .. docker: dockerfile: deploy-image/Dockerfile deploy: kubectl: manifests: - "k8s/deployment.yaml" activation: - kubeContext: sandbox ================================================ FILE: docs/12-factor.md ================================================ # Relationship with the Twelve-Factor App Methodology The Twelve-Factor App is a set of best practices to build modern web applications, or *software-as-a-service*. In this section, we will see how the AEA framework facilitates the achievement of those in the development, release and deployment phases of an AEA project. Note that an AEA instance, as a software agent, can be seen as a more general case of a web app, as it not only shows reactive behaviour, but it is also *proactive*, depending on the goals assigned to it. ## Codebase !!! info "Factor 1" One codebase tracked in revision control, many deploys Support: Excellent The framework does not impose any particular requirement or convention on the type of version control software to be used to store an AEA project. ## Dependencies !!! info "Factor 2" Explicitly declare and isolate dependencies Support: Good The framework allows an AEA project to explicitly declare the AEA package dependencies, and the PyPI dependencies needed to proper working. However, it does not provide built-in support for checking platform-specific dependencies, e.g. specific Python version, or needed system-wide available libraries. Nevertheless, this can be indirectly achieved by means of build scripts called on `aea build`, which can do the checks manually according to the specific requirements of the project. ## Configuration !!! info "Factor 3" Store configuration in the environment Support: Good An AEA project can specify an environment configuration file `.env`, stored in the project root, that the framework will use to update environment variables before the execution of the AEA instance. The CLI tool command `aea run` accepts the option `--env PATH` to change the default configuration file. However, the framework does not automatically switch between, nor allows to add, different types of configuration files, one for each deployment step (e.g. development, staging, production), without using the `--env` option. ## Backing Services !!! info "Factor 4" Treat backing services as attached resources Support: Good A persistent storage of an AEA can be seen as an attached resource in the 12-factor terminology. The default storage is SQLite, but the interface `AbstractStorageBacked` allows to implement specific wrappers to other backing services, without changing the AEA project code. The support for integrating different storage back-end implementations in an AEA project by using a plug-in mechanism is currently missing. Moreover, new adapters to backing services can be implemented as custom connections, which can connect to attached resources. This does not usually require a change in the skill code, especially in the case when a custom protocol can abstract the details of the interaction with the specific resource. ## Build, Release, Run !!! info "Factor 5" Strictly separate build and run stages Support: Excellent The phases of build, release and run of an AEA project are neatly separated, both for programmatic usage and through the usage of the CLI tool, as each of them corresponds to different subcommands. ## Processes !!! info "Factor 6" Execute the app as one or more stateless processes Support: Excellent Whether the process is stateless depends on the specific AEA. No strict enforcement is applied by the framework. Moreover, dialogue histories can be stored with persistent storage, if enabled by the developer. ## Port Binding !!! info "Factor 7" Export services via port binding Support: Excellent An AEA project may not need to expose services via HTTP. This property depends on the specific choices of the project developer, and the framework does not impose any restriction. One of the provided package, the "HTTP server" connection, relies on `aiohttp`, which makes the connection completely self-contained—therefore, it satisfies the requirement. Another relevant example is the ACN node, which exposes its service to the Libp2p AEA connection ## Concurrency !!! info "Factor 8" Scale out via the process model Support: Not Supported The framework does not easily allow to scale up an AEA instance with multiple processes, as it is bound to a process. However, note that its attached services can live in a different process, which could give better scalability. ## Disposability !!! info "Factor 9" Maximize robustness with fast startup and graceful shutdown Support: Good Disposability of an AEA instance depends, in general, on the AEA itself; whether the connections can be quickly connected and disconnected, whether skills can be easily torn down or not, whether other resources can be detached successfully like the persistent storage, just to name a few examples. There has been put some effort into reducing startup time, and to ensure that a graceful shut-down can happen when the process receives a SIGTERM under normal circumstances, but robustness cannot be ensured for individual components, as it depends on their implementation. Additionally, the framework does provide some features to control some aspects of AEA disposability, e.g. the possibility to change execution timeout for behaviours or handlers, implementation of an effective exception propagation from a component code to the main agent loop. ## Dev/Prod Parity !!! info "Factor 10" Keep development, staging, and production as similar as possible Support: Good This aspect mostly depends on the specific AEA project, and the framework does not impose particular restrictions on best deployment practices (e.g. continuous integration, same backing services between development and production stages). ## Logs !!! info "Factor 11" Treat logs as event streams Support: Excellent Thanks to the seamless integration with the Python standard library `logging`, the developer or the deployer has great control on the routing and filtering of log records. The behaviour can be changed by providing a proper configuration in the AEA project configuration file, according to the standard library specification. The framework facilitates this by creating ad-hoc logger names that can be used for finer-grained routing or filtering; for example, each AEA instance uses its own logging namespace to send logging events. Integration with other log handlers is delegated to extensions of the standard library, hence not necessarily coupled with the AEA framework. ## Admin Processes !!! info "Factor 12" Run admin/management tasks as one-off processes Support: Good The CLI tool provides commands to manage private keys and ledger related operations, and it is possible to extend it with a plugin to manage databases of AEA's persistent storage for maintenance operations. Moreover, the Python programming language makes it easy to run one-off scripts or running a console (also known as REPL) to do management tasks. It follows that it is also easy to ensure dependency isolation and same configurations of the running AEA instance. ================================================ FILE: docs/Pipfile ================================================ [[source]] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] [packages] mkdocs = "*" mkdocs-material = "*" mkdocs-material-extensions = "*" mkdocs-mermaid-plugin = {git = "https://github.com/pugong/mkdocs-mermaid-plugin.git"} [requires] python_version = "3.7" ================================================ FILE: docs/acn-internals.md ================================================ # ACN Internals The aim of this document is to describe at a high-level the main implementation of the Agent Communication Network (ACN). In particular: - the `libp2p_node` Golang library; - the `p2p_libp2p` AEA connection written in Python, that implements the **direct connection** with an ACN peer; - the `p2p_libp2p_client` AEA connection written in Python, which implements the **delegate connection** with an ACN peer. It is assumed the reader already knows what is the ACN and its purposes; if not, we suggest reading this page. This documentation page is structured as follows: - Firstly, the ACN protocol is described: all the messages and data structures involved, as well as some example of interaction protocol with these messages; - Then, it is explained how a peer can join an existing ACN network, and the message exchange involved; - It follows the description of the journey of an envelope in the ACN network: from the agent connection to its contact peer, between ACN peers, and then from the contact peer of the destination agent to the target agent; - The following section describes the functionalities of the AEA connections that allow to communicate through the ACN: `fetchai/p2p_libp2p` and `fetchia/p2p_libp2p_delegate`; - The documentation ends with a section of known issues and limitations of the current implementation. ## Messages and Data Structures At the foundation of the ACN there is the _ACN protocol_. The protocol messages and the reply structure are generated from this protocol specification, using the protocol generator. Therefore, it uses Protocol Buffers as a serialization format, and the definition of the data structures involved is defined in this `.proto` file. To know more about the protocol generator, refer to the relevant section of the documentation: Protocol Generator. ### Agent Record An _agent record_ is a data structure containing information about an agent and its Proof-of-Representation (PoR) to be used by a peer for other peers. This data structure is used as a payload in other ACN messages (see below). The `AgentRecord` data structure contains the following fields: - `service_id`: a string describing the service identifier. - `ledger_id`: a string. It is the identifier of the ledger this agent record is associated to. Currently, the allowed values are: - `fetchai`, the identifier for the Fetch.AI ledger; - `ethereum`, the identifier for the Ethereum ledger; - `cosmos`, the identifier for the Cosmos ledger; - `address`: a string. It is the public key of a public-private key pair. It is used as an identifier for routing purposes. - `public_key`: a string. The representative's public key. Used in case of (PoR). - `peer_public_key`: a string. The public key of the peer. - `signature`: a string. The signature for PoR. - `not_before`: a string. Specify the lower bound for certificate validity. If it is a string, it must follow the format: `YYYY-MM-DD`. It will be interpreted as time zone UTC-0 - `not_after`: a string. Specify the upper bound for certificate validity. If it is a string, it must follow the format: `YYYY-MM-DD`. It will be interpreted as time zone UTC-0. ### ACN Message Entities in the ACN (i.e. either agents or peers) exchange _ACN messages_. An ACN message contains a `payload` field, which is the actual content of the message. There are different types of payloads: - `Status` - `Register` - `LookupRequest` - `LookupResponse` - `AeaEnvelope` ### Status The `Status` payload is used as a response message to inform the sender about the handling of certain requests. The payload contains: - the `status_code`, a positive integer among the ones in the Protobuf file. - a list of error messages (string). A status code `0`, identified as `SUCCESS`, means that the request has been processed successfully. Status codes greater than `0` can be: - Generic errors: errors that occur under generic circumstances. - `ERROR_UNSUPPORTED_VERSION`, with integer value `1`: the receiver of the message does not support the protocol version of the sender; - `ERROR_UNEXPECTED_PAYLOAD`, with integer value `2`: the payload could not be deserialized on the receiver side; - `ERROR_GENERIC`, with integer value `3`: an internal error; - `ERROR_SERIALIZATION`, with integer value `4`: a serialization error occurred on the receiving end; - Register errors: errors that occur during agent registration operations in the ACN. - `ERROR_WRONG_AGENT_ADDRESS`, with integer value `10`: the PoR by a peer from another peer does not match the destination address of the envelope to be routed by the receiving peer. - `ERROR_WRONG_PUBLIC_KEY`, with integer value `11`: the representative peer public key does not match the one in the agent record; - `ERROR_INVALID_PROOF`, with integer value `12`: the signature is invalid; - `ERROR_UNSUPPORTED_LEDGER`, with integer value `13`: the ledger of the PoR is not supported by the peer; - Lookup and delivery errors: errors that occur during lookup to the DHT and envelope delivery operations in the ACN. - `ERROR_UNKNOWN_AGENT_ADDRESS`, with integer value `20`: the requested agent address has not been found in the local DHT of the peer; - `ERROR_AGENT_NOT_READY`, with integer value `21`: the agent is not ready for envelope delivery. ### Register The `Register` payload is used to request a peer to register an agent among his known ones. The payload contains the field `record`, which is an instance of `AgentRecord`. ### LookupRequest The `LookupRequest` payload is sent between peer to look-up addresses in the Distributed Hash Table (DHT). It contains the agent address (a string) that the sender needs to correctly route an envelope. ### LookupResponse The `LookupResponse` payload is the response sent by a peer that received a `LookupRequest`. It contains the `AgentRecord` associated to the requested address. ### AeaEnvelope The `AeaEnvelope` payload contains the envelope sent by an agent and to be delivered to another agent. It contains: - `envelope`: the envelope to be forwarded, in byte representation; - an `AgentRecord` (see above). ## ACN Protocol Interactions The ACN protocol specifies three different possible interactions: - the _registration_ interaction - the _look-up_ interaction - the _routing_ interaction ### "Registration" Interaction The registration interaction is used by delegate agents or relayed peers to register themselves to another peer. ``` mermaid sequenceDiagram participant Agent/RelayedPeer participant Peer Agent/RelayedPeer->>Peer: Register(AgentRecord) alt success note over Peer: check PoR Peer->>Agent/RelayedPeer: Status(SUCCESS) else wrong agent address Peer->>Agent/RelayedPeer: Status(ERROR_WRONG_AGENT_ADDRESS) else wrong public key Peer->>Agent/RelayedPeer: Status(ERROR_WRONG_PUBLIC_KEY) else invalid proof of representation Peer->>Agent/RelayedPeer: Status(ERROR_INVALID_PROOF) else unsupported ledger Peer->>Agent/RelayedPeer: Status(ERROR_UNSUPPORTED_LEDGER) end ``` ### "Look-up" Interaction The look-up interaction is used by a peer to request information to another peer about an agent address. ``` mermaid sequenceDiagram participant Peer1 participant Peer2 Peer1->>Peer2: LookupRequest(address) alt success Peer2->>Peer1: LookupResponse(AgentRecord) else unknown agent address Peer2->>Peer1: Status(ERROR_UNKNOWN_AGENT_ADDRESS) end ``` ### "Routing" Interaction The routing interaction is used by agents and peers to route the envelope through the ACN. ``` mermaid sequenceDiagram participant Peer1 participant Peer2 Peer1->>Peer2: AeaEnvelope(envelope, AgentRecord) alt success note over Peer2: check PoR Peer2->>Peer1: Status(SUCCESS) else error on decoding of Envelope payload Peer2->>Peer1: Status(ERROR_SERIALIZATION) else PoR errors note over Peer1,Peer2: see above end ``` ## Joining the ACN network When an ACN peer wants to join the network, it has to start from a list of _bootstrap peers_, i.e. a list of ACN peers to connect with (at least one). Each node handles four different types of libp2p streams: - the _notification stream_, identified by the URI `/aea-notif/`: this stream is used by new peers to notify their existence to - the _address stream_, identified by the URI `/aea-address/`: used to send look-up requests and look-up responses; - the _envelope stream_, identified by the URI `/aea/`: used to forward and to receive ACN envelopes; - the _register relay stream_, identified by the URI `/aea-register/`: this is to receive messages from clients that want to register their agents addresses; this peer, and then it can register their addresses. To begin with, the node process initializes the transport connections with the bootstrap peers, the local copy of the Kademlia Distributed Hash Table (DHT), the persistent storage for agent records, and performs other non-functional operations like setting up the Prometheus monitoring system. Optionally, can also start listening for relay connections and delegate connections. Then, it sets up the notification stream and notifies the bootstrap peers (if any). ``` mermaid sequenceDiagram participant Peer1 participant Peer2 participant Peer3 note over Peer1: notify
bootstrap peers Peer1->>Peer2: notify Peer2->>Peer2: wait until notifying peer
added to DHT activate Peer2 Peer1->>Peer3: notify Peer3->>Peer3: wait until notifying peer
added to DHT activate Peer3 note over Peer2,Peer3: Peer1 registered to DHT deactivate Peer2 deactivate Peer3 loop for each local/relay/delegate address Peer1->>Peer1: compute CID from address Peer1->>Peer2: register address Peer1->>Peer3: register address end note over Peer1: set up:
- address stream
- envelope stream
- register relay stream ``` ### Relay Connections If the ACN node is configured to run the relay service, it sets up the register relay stream, waiting for registration requests. The following diagram shows an example of the message exchanged during a registration request: ``` mermaid sequenceDiagram participant Agent participant Peer Agent->>Peer: Register alt decoding error of ACN message Peer->>Agent: Status(ERROR_SERIALIZATION) else wrong payload Peer->>Agent: Status(ERROR_UNEXPECTED_PAYLOAD) else PoR check fails alt wrong agent address Peer->>Agent: Status(ERROR_WRONG_AGENT_ADDRESS) else unsupported ledger Peer->>Agent: Status(ERROR_UNSUPPORTED_LEDGER) else agent address and public key don't match Peer->>Agent: Status(ERROR_WRONG_AGENT_ADDRESS) else invalid proof Peer->>Agent: Status(ERROR_INVALID_PROOF) end else PoR check succeeds Peer->>Agent: Status(SUCCESS) note over Peer: announce agent address
to other peers end ``` ### Delegate Connections If the ACN node is configured to run the delegate service, it starts listening from a TCP socket at a configurable URI. To see a diagram of the message exchanged during a registration request read this section. ## ACN Transport In the following sections, we describe the main three steps of the routing of an envelope through the ACN: - _ACN entrance_: when an envelope sent by an agent enters the peer-to-peer network via the peer the agent is connected to i.e. agent-to-peer communication; - _ACN routing_: when an envelope gets routed through the peer-to-peer network, i.e. peer-to-peer communication; - _ACN exit_: when an envelope gets delivered to the receiving agent through its representative peer, i.e. peer-to-agent communication. ### ACN Envelope Entrance: Agent -> Peer In this section, we will describe the interaction protocols between agents and peers for the messages sent by the agent to the ACN network; in particular, the communication from the contact peer of an agent to the agent. The following diagram explains the exchange of messages on entering an envelope in the ACN. In the case of _direct connection_, `Agent` is a Python process, whereas `Peer` is in a separate (Golang) process. The logic of the Python Agent client is implemented in the AEA connection `fetchai/p2p_libp2p` The communication between `Agent` and `Peer` is done through an OS pipe for Inter-Process Communication (IPC) between the AEA's process and the libp2p node process; then, the message gets enqueued to an output queue by an input coroutine. Finally, the envelope ends up in an output queue, which is processed by an output coroutine and routed to the next peer. In the case of _delegate connection_, the message exchange is very similar; however, instead of using pipes, the communication is done through the network, i.e. TCP, with a peer which has the delegate service enabled. The logic of the `Agent` client connected with a delegate connection is implemented in the AEA connection `fetchai/p2p_libp2p_client` ``` mermaid sequenceDiagram participant Agent participant Peer loop until Status(success) received Agent->>Peer: AeaEnvelope Agent->>Agent: wait note left of Agent: Wait until
Status(success) alt successful case Peer->>Agent: Status(success) note over Agent: break loop else ack-timeout OR conn-error note left of Agent: continue: Try to
resend/reconnect else version not supported Peer->>Agent: Status(ERROR_UNSUPPORTED_VERSION) else error on decoding of ACN message Peer->>Agent: Status(ERROR_SERIALIZATION) else error on decoding of Envelope payload Peer->>Agent: Status(ERROR_SERIALIZATION) else the payload cannot be handled Peer->>Agent: Status(ERROR_UNEXPECTED_PAYLOAD) end end note over Peer: route envelope
to next peer ``` ### ACN Envelope Routing In this section, we describe the interaction between peers when it comes to envelope routing. Assume an envelope arrives from an agent to peer `Peer1`, i.e. `Peer1` is the first hop of the routing. Let `Agent` be the local agent directly connected to `Peer1`, `Peer2` a direct peer of peer `Peer1`. When the envelope is leaving `Peer1`, we may have different scenario: 1. In case of direct connection, and the field `sender` of the envelope is not the local agent address: the message is considered invalid, and it is dropped. ``` mermaid sequenceDiagram participant Agent participant Peer1 participant Peer2 Agent->>Peer1: AeaEnvelope alt envelope sender not registered locally note over Peer1: stop, log error end ``` 2. the `target` of the envelope is the local agent connected to the peer: the envelope is routed to the local agent. ``` mermaid sequenceDiagram participant Agent participant Peer1 participant Peer2 Agent->>Peer1: AeaEnvelope alt target == peer1.my_agent note over Peer1: envelope destinated
to local agent,
not routing loop agent not ready note over Peer1: sleep for 100ms end Peer1->>Agent: AeaEnvelope Agent->>Peer1: Status(Success) end ``` 3. the `target` is a delegate client. Send the envelope via TCP. ``` mermaid sequenceDiagram participant Delegate participant Peer1 participant Peer2 Delegate->>Peer1: AeaEnvelope alt destination is a delegate note over Peer1: send envelope
to delegate via TCP Peer1->>Delegate: AeaEnvelope Delegate->>Peer1: Status(Success) end ``` 4. Otherwise, look up the local DHT. If an entry is found, use it; otherwise, send a look-up request to connected peers. ``` mermaid sequenceDiagram participant Agent participant Peer1 participant Peer2 Agent->>Peer1: AeaEnvelope alt address found in DHT note over Peer1: destination is a
relay client else lookup address in DHT note over Peer1: send lookup request
to all peers Peer1->>Peer2: LookupRequest alt generic error Peer2->>Peer1: Status(GENERIC_ERROR) else look-up response Peer2->>Peer1: LookupResponse note over Peer1: Check PoR else not found Peer2->>Peer1:Status(UNKNOWN_AGENT_ADDRESS) end end note over Peer1,Peer2: Now Peer1 knows the contact peer
is PeerX ``` In particular, when a peer receives a LookupRequest message, it does the following: ``` mermaid sequenceDiagram participant Peer1 participant Peer2 Peer1->>Peer2: LookupRequest alt error Peer2->>Peer1: Status(Error) else local agent/relay/delegate note over Peer2: requested address is
a local agent
OR
requested address is
in my relay clients
OR
requested address is
in my delegate clients Peer2->>Peer1: LookupResponse note over Peer1: Check PoR else not found locally note over Peer2: send lookup request
to other peers... alt found Peer2->>Peer1: LookupResponse note over Peer1: Check PoR else not found Peer2->>Peer1:Status(UNKNOWN_AGENT_ADDRESS) end end ``` Let `Peer3` the contact peer of the recipient of the envelope. The following diagram shows how the contact peer of the envelope recipient handles the incoming envelope: ``` mermaid sequenceDiagram participant Peer1 participant Peer3 Peer1->>Peer3: AeaEnvelope alt decoding error of ACN message Peer3->>Peer1: Status(ERROR_SERIALIZATION) else unexpected payload Peer3->>Peer1: Status(ERROR_UNEXPECTED_PAYLOAD) else decoding error of envelope payload Peer3->>Peer1: Status(ERROR_SERIALIZATION) else PoR check fails alt wrong agent address Peer3->>Peer1: Status(ERROR_WRONG_AGENT_ADDRESS) else unsupported ledger Peer3->>Peer1: Status(ERROR_UNSUPPORTED_LEDGER) else agent address and public key don't match Peer3->>Peer1: Status(ERROR_WRONG_AGENT_ADDRESS) else invalid proof Peer3->>Peer1: Status(ERROR_INVALID_PROOF) end else PoR check succeeds alt target is delegate, not ready Peer3->>Peer1: Status(ERROR_AGENT_NOT_READY) else exists delegate, ready note over Peer3: forward envelope via
delegate connection Peer3->>Peer1: Status(SUCCESS) else target is local agent, not ready Peer3->>Peer1: Status(ERROR_AGENT_NOT_READY) else target is local agent, ready note over Peer3: forward envelope via
direct connection Peer3->>Peer1: Status(SUCCESS) else agent does not exist Peer3->>Peer1: Status(ERROR_UNKNOWN_AGENT_ADDRESS) end end ``` ### ACN Envelope Exit: Peer -> Agent The following diagram explains the exchange of messages on exiting an envelope in the ACN. That is, the communication from the contact peer of an agent to the agent. The same message exchange is done both in the case of direct connection and delegate connection, similarly for what has been described for the envelope entrance (see above). ``` mermaid sequenceDiagram participant Agent participant Peer Peer->>Agent: AeaEnvelope alt successful case Agent->>Peer: Status(success) else ack-timeout OR conn-error note left of Peer: do nothing else error on decoding of ACN message Agent->>Peer: Status(GENERIC_ERROR) else error on decoding of Envelope payload Agent->>Peer: Status(GENERIC_ERROR) else wrong payload Agent->>Peer: Status(GENERIC_ERROR) end ``` ## Connect your AEA to the ACN To connect the AEA to the ACN network, there are two AEA connections available: - the `fetchai/p2p_libp2p`, that implements a direct connection, and - the `fetchai/p2p_libp2p_delegate` connection, that implements the delegate connection. For more information on the AEA connection package type, refer to this guide. ### The `fetchai/p2p_libp2p` Connection The source code of the `fetchai/p2p_libp2p` connection can be downloaded from the AEA Registry, or from the main AEA framework repository. The package provides the connection class `P2PLibp2pConnection`, which implements the `Connection` interface and therefore can be used by the Multiplexer as any other connection. - The `connect` method of this connection spawns a new instance of the `libp2p_node` program (i.e. an ACN peer node) and connects to it through OS pipes. Then, it sets up the _message receiving loop_, which enqueues messages in the input queue to be read by `read` method calls, and the _message sending loop_, which dequeues messages from the output queue and forwards them to the Libp2p node. The loops are run concurrently in the Multiplexer thread, using the Python asynchronous programming library `asyncio`. ``` mermaid sequenceDiagram participant Libp2p Connection participant sending loop participant receiving loop participant Libp2p Node Libp2p Connection->>Libp2p Node: spawn process activate Libp2p Node Libp2p Connection->>sending loop: start recv loop sending loop->>sending loop: wait messages from output queue activate sending loop Libp2p Connection->>receiving loop: start send loop receiving loop->>receiving loop: wait messages from input queue activate receiving loop deactivate Libp2p Node deactivate sending loop deactivate receiving loop ``` - The `send` method enqueues a message in the output queue. The message is then dequeued by the sending loop, and then sent to the Libp2p node. ``` mermaid sequenceDiagram participant Libp2p Connection participant sending loop participant Libp2p Node activate sending loop Libp2p Connection->>Libp2p Connection: enqueue message to output queue sending loop->>sending loop: dequeue message from output queue deactivate sending loop sending loop->>Libp2p Node: AeaEnvelope sending loop->>sending loop: wait for status activate sending loop alt success note over Libp2p Node: route envelope Libp2p Node->>sending loop: Status(SUCCESS) deactivate sending loop note over sending loop: OK else timed out note over sending loop: raise with error else acn message decoding error Libp2p Node->>sending loop: Status(ERROR_SERIALIZATION) else unexpected payload Libp2p Node->>sending loop: Status(ERROR_UNEXPECTED_PAYLOAD) else envelope decoding error Libp2p Node->>sending loop: Status(ERROR_SERIALIZATION) end ``` - The `receive` method dequeues a message from the input queue. The queue is populated by the receiving loop, which receives messages from the Libp2p node. ``` mermaid sequenceDiagram participant Libp2p Connection participant receiving loop participant Libp2p Node activate receiving loop Libp2p Node->>receiving loop: AeaEnvelope deactivate receiving loop Libp2p Node->>Libp2p Node: wait for status activate Libp2p Node alt success note over receiving loop: enqueue envelope
to input queue receiving loop->>Libp2p Node: Status(SUCCESS) deactivate Libp2p Node note over receiving loop: OK else timed out note over Libp2p Node: ignore else acn message decoding error receiving loop->>Libp2p Node: Status(ERROR_SERIALIZATION) else unexpected payload receiving loop->>Libp2p Node: Status(ERROR_UNEXPECTED_PAYLOAD) else envelope decoding error receiving loop->>Libp2p Node: Status(ERROR_SERIALIZATION) end Libp2p Connection->>receiving loop: read message from output queue note over Libp2p Connection: return message
to Multiplexer ``` - the `disconnect` method stops both the receiving loop and the sending loop, and stops the Libp2p node. ### The `fetchai/p2p_libp2p_delegate` Connection The source code of the `fetchai/p2p_libp2p_delegate` connection can be downloaded from the main AEA framework repository. or from the main AEA framework repository. The package provides the connection class `P2PLibp2pClientConnection`, which implements the `Connection` interface and therefore can be used by the Multiplexer as any other connection. - The `connect` method of this connection will set up a TCP connection to the URI of the delegate peer. Then, it will send a `Register` request to register the agent among the peer's client connections. On registration success, it sets up the _message receiving loop_, which enqueues messages in the input queue to be read by read method calls, and the _message sending loop_, which dequeues messages from the output queue and forwards them to the Libp2p node. The loops are run concurrently in the Multiplexer thread, using the Python asynchronous programming library `asyncio`. ``` mermaid sequenceDiagram participant Libp2p Client Connection participant Libp2p Node activate Libp2p Node Libp2p Node->>Libp2p Node: listening for TCP connections Libp2p Client Connection->>Libp2p Node: Register (via TCP) deactivate Libp2p Node alt decoding error of ACN message Libp2p Node->>Libp2p Client Connection: Status(ERROR_SERIALIZATION) else wrong payload Libp2p Node->>Libp2p Client Connection: Status(ERROR_UNEXPECTED_PAYLOAD) else PoR check fails alt wrong agent address Libp2p Node->>Libp2p Client Connection: Status(ERROR_WRONG_AGENT_ADDRESS) else unsupported ledger Libp2p Node->>Libp2p Client Connection: Status(ERROR_UNSUPPORTED_LEDGER) else agent address and public key don't match Libp2p Node->>Libp2p Client Connection: Status(ERROR_WRONG_AGENT_ADDRESS) else invalid proof Libp2p Node->>Libp2p Client Connection: Status(ERROR_INVALID_PROOF) end else PoR check succeeds Libp2p Node->>Libp2p Client Connection: Status(SUCCESS) note over Libp2p Node: announce agent
address to
other peers Libp2p Node->>Libp2p Node: wait data from socket activate Libp2p Node deactivate Libp2p Node end ``` - The `send` method and the `receive` methods behave similarly to the `send` and `receive` methods of the `p2p_libp2p connection`, in terms of message exchange; however, the communication is done via TCP rather than pipes. - The `disconnect` method interrupts the connection with the delegate peer, without explicitly unregistering. ## Known Issues and Limitations In this section, we provide a list of known issues and limitations of the current implementation of the ACN, considering both the ACN nodes (written in Golang) and the AEA connections, for the Python AEA framework, to interact with them. ### Delegate Client on Client Disconnection/Reconnection In case of disconnection/reconnection, delegate client record will be removed. This can cause two problems: either the delegate client is not found, or connection is closed during the send operation. Possible solutions: - Create more complicated structure for clients storage; - Keep the delegate client record for longer; - Clean up the record by timeout, per client queues. Code references: - record removed: https://github.com/fetchai/agents-aea/blob/1db1720081969bcec1be5a2000ca176475d2b487/libs/go/libp2p_node/dht/dhtpeer/dhtpeer.go#L864 - send code: https://github.com/fetchai/agents-aea/blob/1db1720081969bcec1be5a2000ca176475d2b487/libs/go/libp2p_node/dht/dhtpeer/dhtpeer.go#L955 ### Golang Node <> Python Client `libp2p` Connection In case of connection between the Golang side (i.e. ACN node) and the Python side (i.e. the `libp2p` AEA connection) is broken, there is no reconnection attempt. The Golang side connect to the Python server opened, but if the connection is broken Golang can try to reconnect; however, the Python side does not know about this and will restart the node completely. Possible solutions: the problem requires updates on both sides and assume possible timeouts on broken connection. If connection is broken, the Python side awaits for reconnection from Golang side, and restart node completely after timeout. ### What a Peer Should Do if it Receives an Acknowledgement with an Error? If an ACN response is the `Status` with error code different from `SUCCESS`, the forwarding to other peers is not repeated. A possible solution is to resend the message; however, not clear why it should help in case of healthy connection, how many times the sender should retry, and how it would help. Discussion on GitHub: https://github.com/fetchai/agents-aea/pull/2509#discussion_r642628983 ### No Possibility of Switching Peers In case of a peer becoming unavailable, a delegate client or relay client currently has no means to automatically switch the peer. In particular, the DHT should be updated when a client switches peers. ================================================ FILE: docs/acn.md ================================================ # Agent Communication Network The agent communication network (ACN) provides a system for agents to find each other and communicate, solely based on their wallet addresses. It addresses the message delivery problem. ## Message Delivery Problem Agents need to contact each others. Given the wallet address of a target agent, how can the originator agent deliver a message to it whilst guaranteeing certain properties? The properties we would like to have, are: - Reliability: with guarantees on message reception - Authentication: to prevent impersonation - Confidentiality: to prevent exposing sensitive information within the message - Availability: some guarantees about the liveness of the service (tampering detection) The problem statement and the agent framework context impose a number of design constraints: - Distributed environment: no assumption are placed about the location of the agent, they can be anywhere in the publicly reachable internet - Decentralized environment: no trusted central authority - Support for resource-constrained devices The ACN solves the above problem whilst providing the above guarantees and satisfying the constraints. ## Peers The ACN is maintained by peers. Peers are not to be equated with agents. They are processes (usually distributed and decentralized) that together maintain the service. To use the service, agents need to associate themselves with peers. Thanks to digital signatures, the association between a given peer and agent can be verified by any participant in the system. ## Distributed Hash Table At its core, the ACN implements a distributed hash table (DHT). A DHT is similar to a regular hash table in that it stores key-value pairs. However, storage is distributed across the participating machines (peers) with an efficient lookup operation. This is enabled by: - Consistent hashing: decide responsibility for assignment of the DHT key-value storage - Structured overlays: organize the participating peers in a well-defined topology for efficient routing DHT For the ACN, we use the DHT to store and maintain association between an agent address and the (network) location of its peer. ## N-Tier Architecture To satisfy different resource constraints and flexible deployment the ACN is implemented as a multi-tier architecture. As such, it provides an extension of the client-server model. The agent framework exploits this by implementing different tiers as different `Connections`: DHT !!! note The `p2p_libp2p_mailbox` connection is not available yet. ## Trust and Security An agent can choose which connection to use depending on the resource and trust requirements: - `p2p_libp2p` connection: the agent maintains a peer of the ACN. The agent has full control over the peer and does not need to trust any other entity. - `p2p_libp2p_client` connection: the agent maintains a client connection to a server which is operated by a peer of the ACN. The agent does need to trust the entity operating the peer. All communication protocols use public cryptography to ensure security (authentication, confidentiality, and availability) using TLS handshakes with pre-shared public keys. DHT ================================================ FILE: docs/aea-vs-mvc.md ================================================ # AEA and Web Frameworks The AEA framework borrows several concepts from popular web frameworks like Django and Ruby on Rails. ## MVC Both aforementioned web frameworks use the MVC (model-view-controller) architecture. - Models: contain business logic and data representations - View: contain the HTML templates - Controller: deals with the request-response handling ## Comparison with the AEA Framework The AEA framework is based on asynchronous messaging and other agent-oriented development assumptions. Hence, there is not a direct one-to-one relationship between MVC based architectures and the AEA framework. Nevertheless, there are some parallels which can help a developer familiar with MVC make quick progress in the AEA framework, in particular the development of `Skills`: - `Handler`: receives messages for the protocol it is registered against and is supposed to handle these messages. Handlers are the reactive parts of a skill and can be thought of as similar to the `Controller` in MVC. They can also send new messages. - `Behaviour`: a behaviour encapsulates proactive components of the agent. Since web apps do not have any goals or intentions, they do not proactively pursue an objective. Therefore, there is no equivalent concept in MVC. Behaviours also can, but do not have to, send messages. - `Task`: they are meant to deal with long-running executions and can be thought of as the equivalent of background tasks in traditional web apps. - `Model`: they implement business logic and data representation, and as such, they are similar to the `Model` in MVC. AEA Skill Components The `View` concept is probably best compared to the `Message` of a given `Protocol` in the AEA framework. Whilst views represent information to the client, messages represent information sent to other agents, other agent components and services. ## Next Steps We recommend you continue with the next step in the 'Getting Started' series: - Build a skill for an AEA ================================================ FILE: docs/aeas.md ================================================ # Autonomous Economic Agents (AEAs) ## What is an AEA? !!! info "Definition" An Autonomous Economic Agent (AEA) is an intelligent agent that acts on its owner's behalf, with limited or no interference, and whose goal is to generate economic value for its owner. Let's break down the term **Autonomous Economic Agent (AEA)**: * **Agent**: An AEA is first and foremost an _agent_, representing an individual, organisation or object (a.k.a. its "owner") in the digital world. An AEA looks after its owner's interests and has their preferences in mind when acting on their behalf. * **Autonomous**: AEAs operate independently of constant input from their owners and act autonomously to achieve their goals. * **Economic**: AEAs have a narrow and specific focus: creating economic value for their owner. Some of the other characteristics AEAs typically have: * **Proactive**: AEAs are proactive; they take the initiative and perform actions that take them closer to their goals. * **Reactive**: AEAs are also reactive; they are aware of the environment they are in, perceive changes in the environment, and react to these changes in accordance to their goals. * **Self-interested**: An AEA primarily looks after its own interests (which is aligned with those of its owner) and not necessarily the interests of other agents or the larger system. ### What is NOT an AEA? * **Any agent**: AEAs are NOT meant to address _any_ needs their owners might have. They have a clear and well-defined focus, which is generating economic value for their owner and this is manifested in a variety of different ways in their design. * **Digital twins**: An AEA is NOT it's owner's twin in the digital world; i.e. mirroring their preferences, values, and priorities. An AEA can be given whatever preference, value, and priority its owner wants them to have. * **APIs or Sensors**: These do NOT have any agency, nor proactiveness. They just "sit there" and respond to requests or changes in the environment. * **Smart contracts**: Similar to APIs and sensors, smart contracts do NOT display any proactiveness; they are purely reactive to external requests (in their case, contract calls and transactions). * An agent with **Artificial General Intelligence (AGI)**: AEAs have a well-defined, narrow, and goal directed focus that involves some economic gain. !!! info "Agents and AEAs" In the rest of the documentation, unless specified, we use the terms **AEA** and **Agent** interchangeably to mean AEA as defined above description. ================================================ FILE: docs/agent-oriented-development.md ================================================ # Agent-Oriented Development In this section, we discuss some of the most fundamental characteristics of an agent-oriented approach to solution development, which might be different from existing paradigms and methodologies that you may be used to. We hope that with this, we can guide you towards the right mindset when designing your own agent-based solutions to real world problems. ## Decentralisation Multi-Agent Systems (**MAS**) are inherently decentralized. The vision is of an environment in which every agent is able to directly connect with everyone else and interact with them without having to rely on a third party acting as an intermediary or match-maker. This is in direct contrast to centralized systems in which a single entity is the central point of authority, through which all interactions happen. The conventional client-server model is an example of a centralized architecture where clients interact with one another regarding specific services (e.g. communication, commerce) only through a server. This is not to say that facilitators and middlemen have no place in a multi-agent system; rather it is the '_commanding reliance on middlemen_' that MAS rejects. **Division of responsibilities:** In a decentralized system, every agent is equally privileged, and (in principle) should be able to interact with any other agent. The idea is very much aligned with the peer-to-peer paradigm, in which it is the voluntary participation and contribution of the peers that create the infrastructure. Therefore, in a decentralized system, there is no central 'enforcer'. This means all the work that would typically fall under the responsibilities of a central entity must be performed by individual parties in a decentralized system. Blockchain-based cryptocurrencies are a good example of this. A notable characteristic of cryptocurrencies is the absence of central trusted entities (e.g. banks). But this in turn means that most security precautions related to the handling of digital assets and the execution of transactions are the responsibility of individuals. **Decentralisation vs distribution:** It is important to emphasise that by decentralisation we do not mean distribution; although multi-agent systems typically do also tend to be distributed. A distributed system is one whose components are physically located in different places and connected over a network. A fully centralized system, owned and operated by a single entity, may in fact be highly distributed. Google or Microsoft's cloud infrastructure are examples of this, where their components are distributed across the globe yet designed to work together harmoniously and function in unison. Decentralisation on the other hand refers to a system whose components may be owned, operated, and managed by different stakeholders, each with their own personal objectives, interests, and preferences which may not necessarily be aligned with one another or the system itself. Therefore, distribution refers to the physical placement of a system's components, whereas decentralisation refers to **a.** the diversity of ownership and control over a system's constituents, and **b.** the absence of central authorities between them. **Example:** To better illustrate the distinction between centralized and decentralized systems, consider another example: search and discoverability in a commerce environment. In a centralized system (say Amazon), there is a single search service -- provided, owned and run by the commerce company itself -- which takes care of all search-related functionality for every product within their domain. So to be discoverable in this system, all sellers must register their products with this particular service. However, in a decentralized system, there may not necessarily be a single search service provider. There may be multiple such services, run by different, perhaps competing entities. Each seller has the freedom to register with (i.e. make themselves known to) one or a handful of services. On the buyers side, the more services they contact and query, the higher their chances of finding the product they are looking for. ## Conflicting Environment As discussed above, the notion of decentralisation extends as far as ownership and control. Therefore, the different components that make up a decentralized system may each be owned by a different entity, designed according to very different principles and standards, with heterogeneous software and hardware, and each with internal objectives that may be fundamentally inconsistent, worse yet contradictory, with those of others. As such, a distinctive characteristic of a multi-agent environment, is that it is inhabited by more than one agent (as the name suggests), where each agent may be owned potentially by a different stakeholder (individual, company, government). Since by design, each agent represents and looks after the interests of its owner(s), and because different stakeholders may have unaligned, conflicting, or contradictory interests, it is very common to have multi-agent systems in which the agents' objectives, values and preferences are unaligned, conflicting, or contradictory. **In practice:** There are practical implications that follow from the above when it comes to designing an agent. For example, it is not rational for an agent to automatically rely on the information it receives from other agents. The information could be: - Incomplete: what is unrevealed may have been deemed private for strategic reasons. - Uncertain: it may be the result of an inaccurate prediction. - Incorrect: it could be an outright lie, due to the adversarial nature of the environment. Therefore, one can argue that there is a degree of uncertainty attached to almost all information an agent receives or infers in a multi-agent system. It wouldn't then be illogical for an agent to take a sceptical approach: treating everything as uncertain, unless proved otherwise. ## Asynchronization The conflicting nature of multi-agent systems, consisting of self-interested autonomous agents, points to _asynchronization_ as the preferred method of designing and managing processes and interactions. **Synchronisation vs asynchronization:** In general, asynchronization refers to the decoupling of events that do interact with one another but do not occur at predetermined intervals, not necessarily relying on each other's existence to function. This is in contrast with _synchronous_ systems in which processes are aware of one another, where one's execution depends in some way on the other. **Asynchronization in MAS:** In the context of multi-agent systems, the decentralized and potentially conflicting nature of the environment creates uncertainty over the behaviour of the whole system, in particular of other agents. For example, suppose an agent `i` sends a message requesting some resources from an agent `j`. Since MAS often tends to be distributed, there is the usual uncertainties with communication over a network: `j` may never receive `i`'s request, or may receive it after a long delay. Furthermore, `j` could receive the request in time and respond immediately, but as mentioned in the last section, its answer might be incomplete (gives only some of the requested resources), uncertain (promises to give the resources, but cannot be fully trusted), or incorrect (sends a wrong resource). In addition, since agents are self-interested, `j` may _decide_ to reply much later, to the point that the resource is no longer useful to agent `i`, or `j` may simply decide not to respond at all. There might be a myriad of reasons why it may choose to do that; it could be because `j` assigns a low priority to answering `i` over its other tasks. But that's beside the point. The takeaway is that agents' autonomy strongly influences what can be expected of them, and of an environment inhabited by them. As such, developing for a system whose constituents are autonomous, e.g. agents in a multi-agent system, is fundamentally different from one whose constituents aren't, e.g. objects in an object-oriented system. **Objects vs agents:** In object-oriented systems, objects are entities that encapsulate state and perform actions, i.e. call methods, on this state. In object-oriented languages, like C++ and Java, it is common practice to declare methods as public, so they can be invoked by other objects in the system whenever they wish. This implies that an object does not control its own behaviour. If an object’s method is public, the object has no control over whether that method is executed. We cannot take for granted that an agent `j` will execute an action (the equivalent of a method in object-oriented systems) just because another agent `i` wants it to; this action may not be in the best interests of agent `j`. So we do not think of agents as invoking methods on one another, rather as _requesting_ actions. If `i` requests `j` to perform an action, then `j` may or may not perform the action. It may choose to do it later or do it in exchange for something. The locus of control is therefore different in object-oriented and agent-oriented systems. In the former, the decision lies with the object invoking the method, whereas in the latter, the decision lies with the agent receiving the request. This distinction could be summarised by the following slogan (from An Introduction to MultiAgent Systems by Michael Wooldridge): >objects do it for free; agents do it because they want to. All of this makes asynchronization the preferred method for designing agent processes and interactions. An agent's interactions should be independent of each other, as much as possible, and of the agent's decision-making processes and actions. This means the success or failure of, or delay in any single interaction does not block the agent's other tasks. ## Time Closely related with the discussion of asynchronicity, is the idea that in multi-agent systems, time is not a universally agreed notion. Agents may not necessarily share the same clock and this fact must be taken into account when designing agent-based systems. For example, you cannot necessarily expect agents to synchronise their behaviour according to time (e.g. perform a certain task at a time `X`). Another related issue, is that unlike some agent-based simulation (ABS) systems where there is a global tick rate for all agents, in AEA-based systems tick rates may be different for different agents. This is due to the fundamental difference that ABS systems control some aspects of all of their agents' executions while in AEA-based systems, agents are truly decoupled from one another - most likely distributed and running on different machines and networks - and there is absolutely no central unit that moderates any aspect of their behaviour. ## Complex, Incomplete, Inconsistent and Uncertain The fourth characteristic(s) relate to the environment in which agents are expected to operate in, and these have been mentioned a number of times in the previous sections. The environment agents are suited for typically tend to be complex, to the point that it is usually impossible for any single agent to perceive the whole of the environment on its own. This means that at any point in time, any agent has a limited knowledge about the state of the environment. In other words, the agents;' information tend to be incomplete due to the complexity and sophistication of the world in which they reside. Consider an agent which represents a driver-less vehicle. The complexity of the problem of driving on the road makes it impossible for a single vehicle to have an accurate and up-to-date knowledge of the overall state of the world . This means that an agent's model of the world is at best uncertain. For instance, the vehicle, through its sensor may detect green light at a junction, and by being aware of what it means, it may infer that it is safe to cross a junction. However, that simply may not be true as another car in the opposite direction may still cross the junction violating their red light. Therefore, there is uncertainty associated with the knowledge "it is safe to cross the road because the light is green", and the agent must recognise that. Furthermore, the often conflicting nature of the environment means information obtained from multiple sources (agents) may be inconsistent. Again, this must be taken into consideration when designing an agent which is expected to operate successfully in a potentially conflicting environment. ## Further Reading - Wooldridge, M. (2009). _An Introduction to MultiAgent Systems_. Wiley, Second edition. - Shoham, Y. and Leyton-Brown, K. (2008). _Multiagent Systems: Algorithmic, Game-Theoretic, and Logical Foundations_. Cambridge University Press ================================================ FILE: docs/agent-vs-aea.md ================================================ # AEAs vs Agents AEAs are more than just agents. AEA vs Agent vs Multiplexer In this guide, we show some of the differences in terms of code. The Build an AEA programmatically guide shows how to programmatically build an AEA. We can build an agent of the `Agent` class programmatically as well. First, import the python and application specific libraries. (Get the `packages` directory from the AEA repository `svn export https://github.com/fetchai/agents-aea.git/trunk/packages`.) ``` python import os import time from threading import Thread from typing import List from aea.agent import Agent from aea.configurations.base import ConnectionConfig from aea.connections.base import Connection from aea.helpers.file_io import write_with_lock from aea.identity.base import Identity from aea.mail.base import Envelope from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.protocols.default.message import DefaultMessage ``` Unlike an `AEA`, an `Agent` does not require a `Wallet`, `LedgerApis` or `Resources` module. However, we need to implement 4 abstract methods: - `setup()` - `act()` - `handle_envelope()` - `teardown()` When we run an agent, `start()` calls `setup()` and then the main agent loop. The main agent loop calls `act()`, `react()` and `update()` on each tick. When the agent is stopped via `stop()` then `teardown()` is called. Such a lightweight agent can be used to implement simple logic. ## Code an `Agent` We define our `Agent` which simply receives envelopes, prints the sender address and `protocol_id` and returns it unopened. ``` python INPUT_FILE = "input_file" OUTPUT_FILE = "output_file" class MyAgent(Agent): """A simple agent.""" def __init__(self, identity: Identity, connections: List[Connection]): """Initialise the agent.""" super().__init__(identity, connections) def setup(self): """Setup the agent.""" def act(self): """Act implementation.""" print("Act called for tick {}".format(self.tick)) def handle_envelope(self, envelope: Envelope) -> None: """ Handle envelope. :param envelope: the envelope received :return: None """ print("React called for tick {}".format(self.tick)) if ( envelope is not None and envelope.protocol_specification_id == DefaultMessage.protocol_specification_id ): sender = envelope.sender receiver = envelope.to envelope.to = sender envelope.sender = receiver envelope.message = DefaultMessage.serializer.decode(envelope.message_bytes) envelope.message.sender = receiver envelope.message.to = sender print( "Received envelope from {} with protocol_specification_id={}".format( sender, envelope.protocol_specification_id ) ) self.outbox.put(envelope) def teardown(self): """Teardown the agent.""" ``` ## Instantiate an `Agent` ``` python # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # Create an addresses identity: identity = Identity( name="my_agent", address="some_address", public_key="public_key" ) # Set up the stub connection configuration = ConnectionConfig( input_file_path=INPUT_FILE, output_file_path=OUTPUT_FILE, connection_id=StubConnection.connection_id, ) stub_connection = StubConnection( configuration=configuration, data_dir=".", identity=identity ) # Create our Agent my_agent = MyAgent(identity, [stub_connection]) ``` ## Start the Agent We run the agent from a different thread so that we can still use the main thread to pass it messages. ``` python # Set the agent running in a different thread try: t = Thread(target=my_agent.start) t.start() # Wait for everything to start up time.sleep(3) ``` ## Send and Receive an Envelope We use the input and output text files to send an envelope to our agent and receive a response ``` python # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = b"my_agent,other_agent,fetchai/default:1.0.0,\x12\r\x08\x01*\t*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) # Wait for the envelope to get processed time.sleep(2) # Read the output envelope generated by the agent with open(OUTPUT_FILE, "rb") as f: print("output message: " + f.readline().decode("utf-8")) ``` ## Shutdown Finally, stop our agent and wait for it to finish ``` python finally: # Shut down the agent my_agent.stop() t.join() ``` ## Your Turn Now it is your turn to develop a simple agent with the `Agent` class. ## Entire Code Listing If you just want to copy and paste the entire script in you can find it here: ??? note "Click here to see full listing" ``` python import os import time from threading import Thread from typing import List from aea.agent import Agent from aea.configurations.base import ConnectionConfig from aea.connections.base import Connection from aea.helpers.file_io import write_with_lock from aea.identity.base import Identity from aea.mail.base import Envelope from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.protocols.default.message import DefaultMessage INPUT_FILE = "input_file" OUTPUT_FILE = "output_file" class MyAgent(Agent): """A simple agent.""" def __init__(self, identity: Identity, connections: List[Connection]): """Initialise the agent.""" super().__init__(identity, connections) def setup(self): """Setup the agent.""" def act(self): """Act implementation.""" print("Act called for tick {}".format(self.tick)) def handle_envelope(self, envelope: Envelope) -> None: """ Handle envelope. :param envelope: the envelope received :return: None """ print("React called for tick {}".format(self.tick)) if ( envelope is not None and envelope.protocol_specification_id == DefaultMessage.protocol_specification_id ): sender = envelope.sender receiver = envelope.to envelope.to = sender envelope.sender = receiver envelope.message = DefaultMessage.serializer.decode(envelope.message_bytes) envelope.message.sender = receiver envelope.message.to = sender print( "Received envelope from {} with protocol_specification_id={}".format( sender, envelope.protocol_specification_id ) ) self.outbox.put(envelope) def teardown(self): """Teardown the agent.""" def run(): """Run demo.""" # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # Create an addresses identity: identity = Identity( name="my_agent", address="some_address", public_key="public_key" ) # Set up the stub connection configuration = ConnectionConfig( input_file_path=INPUT_FILE, output_file_path=OUTPUT_FILE, connection_id=StubConnection.connection_id, ) stub_connection = StubConnection( configuration=configuration, data_dir=".", identity=identity ) # Create our Agent my_agent = MyAgent(identity, [stub_connection]) # Set the agent running in a different thread try: t = Thread(target=my_agent.start) t.start() # Wait for everything to start up time.sleep(3) # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = b"my_agent,other_agent,fetchai/default:1.0.0,\x12\r\x08\x01*\t*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) # Wait for the envelope to get processed time.sleep(2) # Read the output envelope generated by the agent with open(OUTPUT_FILE, "rb") as f: print("output message: " + f.readline().decode("utf-8")) finally: # Shut down the agent my_agent.stop() t.join() if __name__ == "__main__": run() ``` ================================================ FILE: docs/aggregation-demo.md ================================================ # Aggregation Skill This demo shows how AEAs can aggregate values over the peer-to-peer network. ## Discussion This demonstration shows how to set up a simple aggregation network in which several AEAs take an average of values fetched from different sources for the same real-world quantity. For this particular example, we take an average of Bitcoin prices from four public APIs. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ## Demo ### Create the AEAs Repeat the following process four times in four different terminals (for each {`i=0`, `i=1`, `i=2`, `i=3`}): Fetch the aggregator AEA: ``` bash agent_name="agg$i" aea fetch fetchai/simple_aggregator:0.5.5 --alias $agent_name cd $agent_name aea install aea build ``` ??? note "Alternatively, create from scratch:" Create the AEA: ``` bash agent_name="agg$i" aea create agent_name cd agent_name aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/http_server:0.23.6 aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/prometheus:0.9.6 aea add skill fetchai/advanced_data_request:0.7.6 aea add skill fetchai/simple_aggregation:0.3.6 aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea install aea build ``` Set the desired decimal precision for the quantity: ``` bash aea config set --type int vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.decimals 0 ``` Disable the http server since it is not used in this demo: ``` bash aea config set --type bool vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.use_http_server false ``` Set the cert requests for the peer-to-peer connection: ``` bash aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "fetchai", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt"}]' ``` Match the agent index `i` to the `COIN_URL` and `JSON_PATH` below: - `agg0`: `COIN_URL="https://api.coinbase.com/v2/prices/BTC-USD/buy" && JSON_PATH="data.amount"` - `agg1`: `COIN_URL="https://api.coinpaprika.com/v1/tickers/btc-bitcoin" && JSON_PATH="quotes.USD.price"` - `agg2`: `COIN_URL="https://api.cryptowat.ch/markets/kraken/btcusd/price" && JSON_PATH="result.price"` - `agg3`: `COIN_URL="https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd" && JSON_PATH="bitcoin.usd"` Set the following configuration for the `advanced_data_request` skill: ``` bash aea config set vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url $COIN_URL aea config set vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs '[{"name": "price", "json_path": '"\"$JSON_PATH\""'}]' ``` Set the name of the quantity to aggregate and choose an aggregation function for the AEAs (the currently implemented options are `mean`, `median`, and `mode`): ``` bash aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.quantity_name price aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.aggregation_function mean ``` Specify a name for your aggregation service: ``` bash SERVICE_ID=my_btc_aggregation_service aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.service_id $SERVICE_ID aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.search_query.search_value $SERVICE_ID ``` Additionally, create private keys for use with the ledger and the peer-to-peer connection: ``` bash aea generate-key fetchai aea add-key fetchai aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the keys for use by the connections that request them: ``` bash aea issue-certificates ``` ### Configure the Peer-to-Peer Network Set the multiaddress of the first AEA as an initial peer to help the remaining AEAs find each other on the network. Also, if these AEAs are all running on the same machine, set different ports for their connections to ensure there are no conflicts (from the `agg1`, `agg2`, and `agg3` directories): ``` bash MULTIADDR=$(cd ../agg0 && aea get-multiaddress fetchai --connection) aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:'$((11000+i))'", "entry_peers": ["/dns4/127.0.0.1/tcp/9000/p2p/'"$MULTIADDR\""'], "local_uri": "127.0.0.1:'$((9000+i))'", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:'$((9000+i))'" }' aea config set vendor.fetchai.connections.prometheus.config.port $((20000+i)) aea config set vendor.fetchai.connections.http_server.config.port $((8000+i)) ``` ### Oracle Integration (optional) To publish the aggregated value to an oracle smart contract, add the ledger connection and simple oracle skill to one of the aggregators: ``` bash aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/simple_oracle:0.16.5 ``` Configure the simple oracle skill for the `fetchai` ledger: ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id fetchai aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function update_oracle_value ``` Generate some wealth to use for transactions on the testnet ledger: ``` bash aea generate-wealth fetchai ``` Set the name of the oracle value to match the value collected by the aggregators: ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.oracle_value_name price_mean ``` ### Run the AEAs Run each of the aggregator AEAs in separate terminals: ``` bash aea run ``` After a few moments, you should see the AEAs finding peers, making observations, sending them to peers, and taking the average of their observations: ``` bash info: [agg_i] found agents... ... info: [agg_i] Fetching data from... ... info: [agg_i] Observation: {'price': {'value':... ... info: [agg_i] sending observation to peer... ... info: [agg_i] received observation from sender... ... info: [agg_i] Observations:... ... info: [agg_i] Aggregation (mean):... ``` ================================================ FILE: docs/api/abstract_agent.md ================================================ # aea.abstract`_`agent This module contains the interface definition of the abstract agent. ## AbstractAgent Objects ```python class AbstractAgent(ABC) ``` This class provides an abstract base interface for an agent. #### name ```python @property @abstractmethod def name() -> str ``` Get agent's name. #### storage`_`uri ```python @property @abstractmethod def storage_uri() -> Optional[str] ``` Return storage uri. #### start ```python @abstractmethod def start() -> None ``` Start the agent. **Returns**: None #### stop ```python @abstractmethod def stop() -> None ``` Stop the agent. **Returns**: None #### setup ```python @abstractmethod def setup() -> None ``` Set up the agent. **Returns**: None #### act ```python @abstractmethod def act() -> None ``` Perform actions on period. **Returns**: None #### handle`_`envelope ```python @abstractmethod def handle_envelope(envelope: Envelope) -> None ``` Handle an envelope. **Arguments**: - `envelope`: the envelope to handle. **Returns**: None #### get`_`periodic`_`tasks ```python @abstractmethod def get_periodic_tasks( ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]] ``` Get all periodic tasks for agent. **Returns**: dict of callable with period specified #### get`_`message`_`handlers ```python @abstractmethod def get_message_handlers() -> List[Tuple[Callable[[Any], None], Callable]] ``` Get handlers with message getters. **Returns**: List of tuples of callables: handler and coroutine to get a message #### exception`_`handler ```python @abstractmethod def exception_handler(exception: Exception, function: Callable) -> Optional[bool] ``` Handle exception raised during agent main loop execution. **Arguments**: - `exception`: exception raised - `function`: a callable exception raised in. **Returns**: skip exception if True, otherwise re-raise it #### teardown ```python @abstractmethod def teardown() -> None ``` Tear down the agent. **Returns**: None ================================================ FILE: docs/api/aea.md ================================================ # aea.aea This module contains the implementation of an autonomous economic agent (AEA). ## AEA Objects ```python class AEA(Agent) ``` This class implements an autonomous economic agent. #### `__`init`__` ```python def __init__( identity: Identity, wallet: Wallet, resources: Resources, data_dir: str, loop: Optional[AbstractEventLoop] = None, period: float = 0.05, execution_timeout: float = 0, max_reactions: int = 20, error_handler_class: Optional[Type[AbstractErrorHandler]] = None, error_handler_config: Optional[Dict[str, Any]] = None, decision_maker_handler_class: Optional[ Type[DecisionMakerHandler]] = None, decision_maker_handler_config: Optional[Dict[str, Any]] = None, skill_exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum. propagate, connection_exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum. propagate, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, default_ledger: Optional[str] = None, currency_denominations: Optional[Dict[str, str]] = None, default_connection: Optional[PublicId] = None, default_routing: Optional[Dict[PublicId, PublicId]] = None, connection_ids: Optional[Collection[PublicId]] = None, search_service_address: str = DEFAULT_SEARCH_SERVICE_ADDRESS, storage_uri: Optional[str] = None, task_manager_mode: Optional[str] = None, **kwargs: Any) -> None ``` Instantiate the agent. **Arguments**: - `identity`: the identity of the agent - `wallet`: the wallet of the agent. - `resources`: the resources (protocols and skills) of the agent. - `data_dir`: directory where to put local files. - `loop`: the event loop to run the connections. - `period`: period to call agent's act - `execution_timeout`: amount of time to limit single act/handle to execute. - `max_reactions`: the processing rate of envelopes per tick (i.e. single loop). - `error_handler_class`: the class implementing the error handler - `error_handler_config`: the configuration of the error handler - `decision_maker_handler_class`: the class implementing the decision maker handler to be used. - `decision_maker_handler_config`: the configuration of the decision maker handler - `skill_exception_policy`: the skill exception policy enum - `connection_exception_policy`: the connection exception policy enum - `loop_mode`: loop_mode to choose agent run loop. - `runtime_mode`: runtime mode (async, threaded) to run AEA in. - `default_ledger`: default ledger id - `currency_denominations`: mapping from ledger id to currency denomination - `default_connection`: public id to the default connection - `default_routing`: dictionary for default routing. - `connection_ids`: active connection ids. Default: consider all the ones in the resources. - `search_service_address`: the address of the search service used. - `storage_uri`: optional uri to set generic storage - `task_manager_mode`: task manager mode (threaded) to run tasks with. - `kwargs`: keyword arguments to be attached in the agent context namespace. #### get`_`build`_`dir ```python @classmethod def get_build_dir(cls) -> str ``` Get agent build directory. #### context ```python @property def context() -> AgentContext ``` Get (agent) context. #### resources ```python @property def resources() -> Resources ``` Get resources. #### resources ```python @resources.setter def resources(resources: "Resources") -> None ``` Set resources. #### filter ```python @property def filter() -> Filter ``` Get the filter. #### active`_`behaviours ```python @property def active_behaviours() -> List[Behaviour] ``` Get all active behaviours to use in act. #### setup ```python def setup() -> None ``` Set up the agent. Calls setup() on the resources. #### act ```python def act() -> None ``` Perform actions. Adds new handlers and behaviours for use/execution by the runtime. #### handle`_`envelope ```python def handle_envelope(envelope: Envelope) -> None ``` Handle an envelope. Performs the following: - fetching the protocol referenced by the envelope, and - handling if the protocol is unsupported, using the error handler, or - handling if there is a decoding error, using the error handler, or - handling if no active handler is available for the specified protocol, using the error handler, or - handling the message recovered from the envelope with all active handlers for the specified protocol. **Arguments**: - `envelope`: the envelope to handle. **Returns**: None #### get`_`periodic`_`tasks ```python def get_periodic_tasks( ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]] ``` Get all periodic tasks for agent. **Returns**: dict of callable with period specified #### get`_`message`_`handlers ```python def get_message_handlers() -> List[Tuple[Callable[[Any], None], Callable]] ``` Get handlers with message getters. **Returns**: List of tuples of callables: handler and coroutine to get a message #### exception`_`handler ```python def exception_handler(exception: Exception, function: Callable) -> bool ``` Handle exception raised during agent main loop execution. **Arguments**: - `exception`: exception raised - `function`: a callable exception raised in. **Returns**: bool, propagate exception if True otherwise skip it. #### teardown ```python def teardown() -> None ``` Tear down the agent. Performs the following: - tears down the resources. #### get`_`task`_`result ```python def get_task_result(task_id: int) -> AsyncResult ``` Get the result from a task. **Arguments**: - `task_id`: the id of the task **Returns**: async result for task_id #### enqueue`_`task ```python def enqueue_task(func: Callable, args: Sequence = (), kwargs: Optional[Dict[str, Any]] = None) -> int ``` Enqueue a task with the task manager. **Arguments**: - `func`: the callable instance to be enqueued - `args`: the positional arguments to be passed to the function. - `kwargs`: the keyword arguments to be passed to the function. **Returns**: the task id to get the the result. ================================================ FILE: docs/api/aea_builder.md ================================================ # aea.aea`_`builder This module contains utilities for building an AEA. ## `_`DependenciesManager Objects ```python class _DependenciesManager() ``` Class to manage dependencies of agent packages. #### `__`init`__` ```python def __init__() -> None ``` Initialize the dependency graph. #### all`_`dependencies ```python @property def all_dependencies() -> Set[ComponentId] ``` Get all dependencies. #### dependencies`_`highest`_`version ```python @property def dependencies_highest_version() -> Set[ComponentId] ``` Get the dependencies with highest version. #### get`_`components`_`by`_`type ```python def get_components_by_type( component_type: ComponentType ) -> Dict[ComponentId, ComponentConfiguration] ``` Get the components by type. #### protocols ```python @property def protocols() -> Dict[ComponentId, ProtocolConfig] ``` Get the protocols. #### connections ```python @property def connections() -> Dict[ComponentId, ConnectionConfig] ``` Get the connections. #### skills ```python @property def skills() -> Dict[ComponentId, SkillConfig] ``` Get the skills. #### contracts ```python @property def contracts() -> Dict[ComponentId, ContractConfig] ``` Get the contracts. #### add`_`component ```python def add_component(configuration: ComponentConfiguration) -> None ``` Add a component to the dependency manager. **Arguments**: - `configuration`: the component configuration to add. #### remove`_`component ```python def remove_component(component_id: ComponentId) -> None ``` Remove a component. **Arguments**: - `component_id`: the component id **Raises**: - `ValueError`: if some component depends on this package. #### pypi`_`dependencies ```python @property def pypi_dependencies() -> Dependencies ``` Get all the PyPI dependencies. We currently consider only dependency that have the default PyPI index url and that specify only the version field. **Returns**: the merged PyPI dependencies #### install`_`dependencies ```python def install_dependencies() -> None ``` Install extra dependencies for components. ## AEABuilder Objects ```python class AEABuilder(WithLogger) ``` This class helps to build an AEA. It follows the fluent interface. Every method of the builder returns the instance of the builder itself. Note: the method 'build()' is guaranteed of being re-entrant with respect to the 'add_component(path)' method. That is, you can invoke the building method many times against the same builder instance, and the returned agent instance will not share the components with other agents, e.g.: builder = AEABuilder() builder.add_component(...) ... # first call my_aea_1 = builder.build() # following agents will have different components. my_aea_2 = builder.build() # all good However, if you manually loaded some of the components and added them with the method 'add_component_instance()', then calling build more than one time is prevented: builder = AEABuilder() builder.add_component_instance(...) ... # other initialization code # first call my_aea_1 = builder.build() # second call to `build()` would raise a Value Error. # call reset builder.reset() # re-add the component and private keys builder.add_component_instance(...) ... # add private keys # second call my_aea_2 = builder.builder() #### `__`init`__` ```python def __init__(with_default_packages: bool = True, registry_dir: str = DEFAULT_REGISTRY_NAME, build_dir_root: Optional[str] = None) -> None ``` Initialize the builder. **Arguments**: - `with_default_packages`: add the default packages. - `registry_dir`: the registry directory. - `build_dir_root`: the root of the build directory. #### reset ```python def reset(is_full_reset: bool = False) -> None ``` Reset the builder. A full reset causes a reset of all data on the builder. A partial reset only resets: - name, - private keys, and - component instances **Arguments**: - `is_full_reset`: whether it is a full reset or not. #### set`_`period ```python def set_period(period: Optional[float]) -> "AEABuilder" ``` Set agent act period. **Arguments**: - `period`: period in seconds **Returns**: self #### set`_`execution`_`timeout ```python def set_execution_timeout(execution_timeout: Optional[float]) -> "AEABuilder" ``` Set agent execution timeout in seconds. **Arguments**: - `execution_timeout`: execution_timeout in seconds **Returns**: self #### set`_`max`_`reactions ```python def set_max_reactions(max_reactions: Optional[int]) -> "AEABuilder" ``` Set agent max reaction in one react. **Arguments**: - `max_reactions`: int **Returns**: self #### set`_`decision`_`maker`_`handler`_`details ```python def set_decision_maker_handler_details(decision_maker_handler_dotted_path: str, file_path: str, config: Dict[str, Any]) -> "AEABuilder" ``` Set error handler details. **Arguments**: - `decision_maker_handler_dotted_path`: the dotted path to the decision maker handler - `file_path`: the file path to the file which contains the decision maker handler - `config`: the configuration passed to the decision maker handler on instantiation **Returns**: self #### set`_`error`_`handler`_`details ```python def set_error_handler_details(error_handler_dotted_path: str, file_path: str, config: Dict[str, Any]) -> "AEABuilder" ``` Set error handler details. **Arguments**: - `error_handler_dotted_path`: the dotted path to the error handler - `file_path`: the file path to the file which contains the error handler - `config`: the configuration passed to the error handler on instantiation **Returns**: self #### set`_`skill`_`exception`_`policy ```python def set_skill_exception_policy( skill_exception_policy: Optional[ExceptionPolicyEnum]) -> "AEABuilder" ``` Set skill exception policy. **Arguments**: - `skill_exception_policy`: the policy **Returns**: self #### set`_`connection`_`exception`_`policy ```python def set_connection_exception_policy( connection_exception_policy: Optional[ExceptionPolicyEnum] ) -> "AEABuilder" ``` Set connection exception policy. **Arguments**: - `connection_exception_policy`: the policy **Returns**: self #### set`_`default`_`routing ```python def set_default_routing( default_routing: Dict[PublicId, PublicId]) -> "AEABuilder" ``` Set default routing. This is a map from public ids (protocols) to public ids (connections). **Arguments**: - `default_routing`: the default routing mapping **Returns**: self #### set`_`loop`_`mode ```python def set_loop_mode(loop_mode: Optional[str]) -> "AEABuilder" ``` Set the loop mode. **Arguments**: - `loop_mode`: the agent loop mode **Returns**: self #### set`_`runtime`_`mode ```python def set_runtime_mode(runtime_mode: Optional[str]) -> "AEABuilder" ``` Set the runtime mode. **Arguments**: - `runtime_mode`: the agent runtime mode **Returns**: self #### set`_`task`_`manager`_`mode ```python def set_task_manager_mode(task_manager_mode: Optional[str]) -> "AEABuilder" ``` Set the task_manager_mode. **Arguments**: - `task_manager_mode`: the agent task_manager_mode **Returns**: self #### set`_`storage`_`uri ```python def set_storage_uri(storage_uri: Optional[str]) -> "AEABuilder" ``` Set the storage uri. **Arguments**: - `storage_uri`: storage uri **Returns**: self #### set`_`data`_`dir ```python def set_data_dir(data_dir: Optional[str]) -> "AEABuilder" ``` Set the data directory. **Arguments**: - `data_dir`: path to directory where to store data. **Returns**: self #### set`_`logging`_`config ```python def set_logging_config(logging_config: Dict) -> "AEABuilder" ``` Set the logging configurations. The dictionary must satisfy the following schema: https://docs.python.org/3/library/logging.config.html#logging-config-dictschema **Arguments**: - `logging_config`: the logging configurations. **Returns**: self #### set`_`search`_`service`_`address ```python def set_search_service_address(search_service_address: str) -> "AEABuilder" ``` Set the search service address. **Arguments**: - `search_service_address`: the search service address **Returns**: self #### set`_`name ```python def set_name(name: str) -> "AEABuilder" ``` Set the name of the agent. **Arguments**: - `name`: the name of the agent. **Returns**: the AEABuilder #### set`_`default`_`connection ```python def set_default_connection( public_id: Optional[PublicId] = None) -> "AEABuilder" ``` Set the default connection. **Arguments**: - `public_id`: the public id of the default connection package. **Returns**: the AEABuilder #### add`_`private`_`key ```python def add_private_key(identifier: str, private_key_path: Optional[PathLike] = None, is_connection: bool = False) -> "AEABuilder" ``` Add a private key path. **Arguments**: - `identifier`: the identifier for that private key path. - `private_key_path`: an (optional) path to the private key file. If None, the key will be created at build time. - `is_connection`: if the pair is for the connection cryptos **Returns**: the AEABuilder #### remove`_`private`_`key ```python def remove_private_key(identifier: str, is_connection: bool = False) -> "AEABuilder" ``` Remove a private key path by identifier, if present. **Arguments**: - `identifier`: the identifier of the private key. - `is_connection`: if the pair is for the connection cryptos **Returns**: the AEABuilder #### private`_`key`_`paths ```python @property def private_key_paths() -> Dict[str, Optional[str]] ``` Get the private key paths. #### connection`_`private`_`key`_`paths ```python @property def connection_private_key_paths() -> Dict[str, Optional[str]] ``` Get the connection private key paths. #### set`_`default`_`ledger ```python def set_default_ledger(identifier: Optional[str]) -> "AEABuilder" ``` Set a default ledger API to use. **Arguments**: - `identifier`: the identifier of the ledger api **Returns**: the AEABuilder #### set`_`required`_`ledgers ```python def set_required_ledgers( required_ledgers: Optional[List[str]]) -> "AEABuilder" ``` Set the required ledger identifiers. These are the ledgers for which the AEA requires a key pair. **Arguments**: - `required_ledgers`: the required ledgers. **Returns**: the AEABuilder. #### set`_`build`_`entrypoint ```python def set_build_entrypoint(build_entrypoint: Optional[str]) -> "AEABuilder" ``` Set build entrypoint. **Arguments**: - `build_entrypoint`: path to the builder script. **Returns**: the AEABuilder #### set`_`currency`_`denominations ```python def set_currency_denominations( currency_denominations: Dict[str, str]) -> "AEABuilder" ``` Set the mapping from ledger ids to currency denominations. **Arguments**: - `currency_denominations`: the mapping **Returns**: the AEABuilder #### add`_`component ```python def add_component(component_type: ComponentType, directory: PathLike, skip_consistency_check: bool = False) -> "AEABuilder" ``` Add a component, given its type and the directory. **Arguments**: - `component_type`: the component type. - `directory`: the directory path. - `skip_consistency_check`: if True, the consistency check are skipped. **Raises**: - `AEAException`: if a component is already registered with the same component id. # noqa: DAR402 | or if there's a missing dependency. # noqa: DAR402 **Returns**: the AEABuilder #### add`_`component`_`instance ```python def add_component_instance(component: Component) -> "AEABuilder" ``` Add already initialized component object to resources or connections. Please, pay attention, all dependencies have to be already loaded. Notice also that this will make the call to 'build()' non re-entrant. You will have to `reset()` the builder before calling `build()` again. **Arguments**: - `component`: Component instance already initialized. **Returns**: self #### set`_`context`_`namespace ```python def set_context_namespace(context_namespace: Dict[str, Any]) -> "AEABuilder" ``` Set the context namespace. #### set`_`agent`_`pypi`_`dependencies ```python def set_agent_pypi_dependencies(dependencies: Dependencies) -> "AEABuilder" ``` Set agent PyPI dependencies. **Arguments**: - `dependencies`: PyPI dependencies for the agent. **Returns**: the AEABuilder. #### remove`_`component ```python def remove_component(component_id: ComponentId) -> "AEABuilder" ``` Remove a component. **Arguments**: - `component_id`: the public id of the component. **Returns**: the AEABuilder #### add`_`protocol ```python def add_protocol(directory: PathLike) -> "AEABuilder" ``` Add a protocol to the agent. **Arguments**: - `directory`: the path to the protocol directory **Returns**: the AEABuilder #### remove`_`protocol ```python def remove_protocol(public_id: PublicId) -> "AEABuilder" ``` Remove protocol. **Arguments**: - `public_id`: the public id of the protocol **Returns**: the AEABuilder #### add`_`connection ```python def add_connection(directory: PathLike) -> "AEABuilder" ``` Add a connection to the agent. **Arguments**: - `directory`: the path to the connection directory **Returns**: the AEABuilder #### remove`_`connection ```python def remove_connection(public_id: PublicId) -> "AEABuilder" ``` Remove a connection. **Arguments**: - `public_id`: the public id of the connection **Returns**: the AEABuilder #### add`_`skill ```python def add_skill(directory: PathLike) -> "AEABuilder" ``` Add a skill to the agent. **Arguments**: - `directory`: the path to the skill directory **Returns**: the AEABuilder #### remove`_`skill ```python def remove_skill(public_id: PublicId) -> "AEABuilder" ``` Remove protocol. **Arguments**: - `public_id`: the public id of the skill **Returns**: the AEABuilder #### add`_`contract ```python def add_contract(directory: PathLike) -> "AEABuilder" ``` Add a contract to the agent. **Arguments**: - `directory`: the path to the contract directory **Returns**: the AEABuilder #### remove`_`contract ```python def remove_contract(public_id: PublicId) -> "AEABuilder" ``` Remove protocol. **Arguments**: - `public_id`: the public id of the contract **Returns**: the AEABuilder #### call`_`all`_`build`_`entrypoints ```python def call_all_build_entrypoints() -> None ``` Call all the build entrypoints. #### get`_`build`_`root`_`directory ```python def get_build_root_directory() -> str ``` Get build directory root. #### run`_`build`_`for`_`component`_`configuration ```python @classmethod def run_build_for_component_configuration( cls, config: ComponentConfiguration, logger: Optional[logging.Logger] = None) -> None ``` Run a build entrypoint script for component configuration. #### install`_`pypi`_`dependencies ```python def install_pypi_dependencies() -> None ``` Install components extra dependencies. #### build ```python def build(connection_ids: Optional[Collection[PublicId]] = None, password: Optional[str] = None) -> AEA ``` Build the AEA. This method is re-entrant only if the components have been added through the method 'add_component'. If some of them have been loaded with 'add_component_instance', it can be called only once, and further calls are only possible after a call to 'reset' and re-loading of the components added via 'add_component_instance' and the private keys. **Arguments**: - `connection_ids`: select only these connections to run the AEA. - `password`: the password to encrypt/decrypt the private key. **Returns**: the AEA object. #### get`_`default`_`ledger ```python def get_default_ledger() -> str ``` Return default ledger. **Returns**: the default ledger identifier. #### get`_`required`_`ledgers ```python def get_required_ledgers() -> List[str] ``` Get the required ledger identifiers. These are the ledgers for which the AEA requires a key pair. **Returns**: the list of required ledgers. #### try`_`to`_`load`_`agent`_`configuration`_`file ```python @classmethod def try_to_load_agent_configuration_file( cls, aea_project_path: Union[str, Path]) -> AgentConfig ``` Try to load the agent configuration file.. #### set`_`from`_`configuration ```python def set_from_configuration(agent_configuration: AgentConfig, aea_project_path: Path, skip_consistency_check: bool = False) -> None ``` Set builder variables from AgentConfig. **Arguments**: - `agent_configuration`: AgentConfig to get values from. - `aea_project_path`: PathLike root directory of the agent project. - `skip_consistency_check`: if True, the consistency check are skipped. #### from`_`aea`_`project ```python @classmethod def from_aea_project(cls, aea_project_path: PathLike, skip_consistency_check: bool = False, password: Optional[str] = None) -> "AEABuilder" ``` Construct the builder from an AEA project. - load agent configuration file - set name and default configurations - load private keys - load ledger API configurations - set default ledger - load every component **Arguments**: - `aea_project_path`: path to the AEA project. - `skip_consistency_check`: if True, the consistency check are skipped. - `password`: the password to encrypt/decrypt private keys. **Returns**: an AEABuilder. #### get`_`configuration`_`file`_`path ```python @staticmethod def get_configuration_file_path(aea_project_path: Union[Path, str]) -> Path ``` Return path to aea-config file for the given aea project path. #### make`_`component`_`logger ```python def make_component_logger(configuration: ComponentConfiguration, agent_name: str) -> Optional[logging.Logger] ``` Make the logger for a component. **Arguments**: - `configuration`: the component configuration - `agent_name`: the agent name **Returns**: the logger. ================================================ FILE: docs/api/agent.md ================================================ # aea.agent This module contains the implementation of a generic agent. ## Agent Objects ```python class Agent(AbstractAgent, WithLogger) ``` This class provides an abstract base class for a generic agent. #### `__`init`__` ```python def __init__(identity: Identity, connections: List[Connection], loop: Optional[AbstractEventLoop] = None, period: float = 1.0, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, storage_uri: Optional[str] = None, logger: Logger = _default_logger, task_manager_mode: Optional[str] = None) -> None ``` Instantiate the agent. **Arguments**: - `identity`: the identity of the agent. - `connections`: the list of connections of the agent. - `loop`: the event loop to run the connections. - `period`: period to call agent's act - `loop_mode`: loop_mode to choose agent run loop. - `runtime_mode`: runtime mode to up agent. - `storage_uri`: optional uri to set generic storage - `task_manager_mode`: task manager mode. - `logger`: the logger. - `task_manager_mode`: mode of the task manager. #### storage`_`uri ```python @property def storage_uri() -> Optional[str] ``` Return storage uri. #### is`_`running ```python @property def is_running() -> bool ``` Get running state of the runtime and agent. #### is`_`stopped ```python @property def is_stopped() -> bool ``` Get running state of the runtime and agent. #### identity ```python @property def identity() -> Identity ``` Get the identity. #### inbox ```python @property def inbox() -> InBox ``` Get the inbox. The inbox contains Envelopes from the Multiplexer. The agent can pick these messages for processing. **Returns**: InBox instance #### outbox ```python @property def outbox() -> OutBox ``` Get the outbox. The outbox contains Envelopes for the Multiplexer. Envelopes placed in the Outbox are processed by the Multiplexer. **Returns**: OutBox instance #### name ```python @property def name() -> str ``` Get the agent name. #### tick ```python @property def tick() -> int ``` Get the tick or agent loop count. Each agent loop (one call to each one of act(), react(), update()) increments the tick. **Returns**: tick count #### state ```python @property def state() -> RuntimeStates ``` Get state of the agent's runtime. **Returns**: RuntimeStates #### period ```python @property def period() -> float ``` Get a period to call act. #### runtime ```python @property def runtime() -> BaseRuntime ``` Get the runtime. #### setup ```python def setup() -> None ``` Set up the agent. #### start ```python def start() -> None ``` Start the agent. Performs the following: - calls start() on runtime. - waits for runtime to complete running (blocking) #### handle`_`envelope ```python def handle_envelope(envelope: Envelope) -> None ``` Handle an envelope. **Arguments**: - `envelope`: the envelope to handle. #### act ```python def act() -> None ``` Perform actions on period. #### stop ```python def stop() -> None ``` Stop the agent. Performs the following: - calls stop() on runtime - waits for runtime to stop (blocking) #### teardown ```python def teardown() -> None ``` Tear down the agent. #### get`_`periodic`_`tasks ```python def get_periodic_tasks( ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]] ``` Get all periodic tasks for agent. **Returns**: dict of callable with period specified #### get`_`message`_`handlers ```python def get_message_handlers() -> List[Tuple[Callable[[Any], None], Callable]] ``` Get handlers with message getters. **Returns**: List of tuples of callables: handler and coroutine to get a message #### exception`_`handler ```python def exception_handler(exception: Exception, function: Callable) -> bool ``` Handle exception raised during agent main loop execution. **Arguments**: - `exception`: exception raised - `function`: a callable exception raised in. **Returns**: bool, propagate exception if True otherwise skip it. ================================================ FILE: docs/api/agent_loop.md ================================================ # aea.agent`_`loop This module contains the implementation of an agent loop using asyncio. ## AgentLoopException Objects ```python class AgentLoopException(AEAException) ``` Exception for agent loop runtime errors. ## AgentLoopStates Objects ```python class AgentLoopStates(Enum) ``` Internal agent loop states. ## BaseAgentLoop Objects ```python class BaseAgentLoop(Runnable, WithLogger, ABC) ``` Base abstract agent loop class. #### `__`init`__` ```python def __init__(agent: AbstractAgent, loop: Optional[AbstractEventLoop] = None, threaded: bool = False) -> None ``` Init loop. **Arguments**: - `agent`: Agent or AEA to run. - `loop`: optional asyncio event loop. if not specified a new loop will be created. - `threaded`: if True, run in threaded mode, else async #### agent ```python @property def agent() -> AbstractAgent ``` Get agent. #### state ```python @property def state() -> AgentLoopStates ``` Get current main loop state. #### wait`_`state ```python async def wait_state( state_or_states: Union[Any, Sequence[Any]]) -> Tuple[Any, Any] ``` Wait state to be set. **Arguments**: - `state_or_states`: state or list of states. **Returns**: tuple of previous state and new state. #### is`_`running ```python @property def is_running() -> bool ``` Get running state of the loop. #### set`_`loop ```python def set_loop(loop: AbstractEventLoop) -> None ``` Set event loop and all event loop related objects. #### run ```python async def run() -> None ``` Run agent loop. #### send`_`to`_`skill ```python @abstractmethod def send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None ``` Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. **Arguments**: - `message_or_envelope`: envelope to send to another skill. - `context`: envelope context #### skill2skill`_`queue ```python @property @abstractmethod def skill2skill_queue() -> Queue ``` Get skill to skill message queue. ## AsyncAgentLoop Objects ```python class AsyncAgentLoop(BaseAgentLoop) ``` Asyncio based agent loop suitable only for AEA. #### `__`init`__` ```python def __init__(agent: AbstractAgent, loop: AbstractEventLoop = None, threaded: bool = False) -> None ``` Init agent loop. **Arguments**: - `agent`: AEA instance - `loop`: asyncio loop to use. optional - `threaded`: is a new thread to be started for the agent loop #### skill2skill`_`queue ```python @property def skill2skill_queue() -> Queue ``` Get skill to skill message queue. #### send`_`to`_`skill ```python def send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None ``` Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. **Arguments**: - `message_or_envelope`: envelope to send to another skill. - `context`: envelope context ================================================ FILE: docs/api/common.md ================================================ # aea.common This module contains the common types and interfaces used in the aea framework. ================================================ FILE: docs/api/components/base.md ================================================ # aea.components.base This module contains definitions of agent components. ## Component Objects ```python class Component(ABC, WithLogger) ``` Abstract class for an agent component. #### `__`init`__` ```python def __init__(configuration: Optional[ComponentConfiguration] = None, is_vendor: bool = False, **kwargs: Any) -> None ``` Initialize a package. **Arguments**: - `configuration`: the package configuration. - `is_vendor`: whether the package is vendorized. - `kwargs`: the keyword arguments for the logger. #### component`_`type ```python @property def component_type() -> ComponentType ``` Get the component type. #### is`_`vendor ```python @property def is_vendor() -> bool ``` Get whether the component is vendorized or not. #### prefix`_`import`_`path ```python @property def prefix_import_path() -> str ``` Get the prefix import path for this component. #### component`_`id ```python @property def component_id() -> ComponentId ``` Ge the package id. #### public`_`id ```python @property def public_id() -> PublicId ``` Get the public id. #### configuration ```python @property def configuration() -> ComponentConfiguration ``` Get the component configuration. #### directory ```python @property def directory() -> Path ``` Get the directory. Raise error if it has not been set yet. #### directory ```python @directory.setter def directory(path: Path) -> None ``` Set the directory. Raise error if already set. #### build`_`directory ```python @property def build_directory() -> Optional[str] ``` Get build directory for the component. #### load`_`aea`_`package ```python def load_aea_package(configuration: ComponentConfiguration) -> None ``` Load the AEA package from configuration. It adds all the __init__.py modules into `sys.modules`. **Arguments**: - `configuration`: the configuration object. #### perform`_`load`_`aea`_`package ```python def perform_load_aea_package(dir_: Path, author: str, package_type_plural: str, package_name: str) -> None ``` Load the AEA package from values provided. It adds all the __init__.py modules into `sys.modules`. **Arguments**: - `dir_`: path of the component. - `author`: str - `package_type_plural`: str - `package_name`: str ================================================ FILE: docs/api/components/loader.md ================================================ # aea.components.loader This module contains utilities for loading components. #### component`_`type`_`to`_`class ```python def component_type_to_class(component_type: ComponentType) -> Type[Component] ``` Get the component class from the component type. **Arguments**: - `component_type`: the component type **Returns**: the component class #### load`_`component`_`from`_`config ```python def load_component_from_config(configuration: ComponentConfiguration, *args, **kwargs) -> Component ``` Load a component from a directory. **Arguments**: - `configuration`: the component configuration. - `args`: the positional arguments. - `kwargs`: the keyword arguments. **Returns**: the component instance. ## AEAPackageNotFound Objects ```python class AEAPackageNotFound(Exception) ``` Exception when failed to import package, cause not exists. ================================================ FILE: docs/api/components/utils.md ================================================ # aea.components.utils This module contains the component loading utils. ================================================ FILE: docs/api/configurations/base.md ================================================ ================================================ FILE: docs/api/configurations/constants.md ================================================ # aea.configurations.constants Module to declare constants. ================================================ FILE: docs/api/configurations/data_types.md ================================================ # aea.configurations.data`_`types Base config data types. ## JSONSerializable Objects ```python class JSONSerializable(ABC) ``` Interface for JSON-serializable objects. #### json ```python @property @abstractmethod def json() -> Dict ``` Compute the JSON representation. #### from`_`json ```python @classmethod @abstractmethod def from_json(cls, obj: Dict) -> "JSONSerializable" ``` Build from a JSON object. ## PackageVersion Objects ```python @functools.total_ordering class PackageVersion() ``` A package version. #### `__`init`__` ```python def __init__(version_like: PackageVersionLike) -> None ``` Initialize a package version. **Arguments**: - `version_like`: a string, os a semver.VersionInfo object. #### is`_`latest ```python @property def is_latest() -> bool ``` Check whether the version is 'latest'. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`lt`__` ```python def __lt__(other: Any) -> bool ``` Compare with another object. ## PackageType Objects ```python class PackageType(Enum) ``` Package types. #### to`_`plural ```python def to_plural() -> str ``` Get the plural name. >>> PackageType.AGENT.to_plural() 'agents' >>> PackageType.PROTOCOL.to_plural() 'protocols' >>> PackageType.CONNECTION.to_plural() 'connections' >>> PackageType.SKILL.to_plural() 'skills' >>> PackageType.CONTRACT.to_plural() 'contracts' **Returns**: pluralised package type #### `__`str`__` ```python def __str__() -> str ``` Convert to string. ## ComponentType Objects ```python class ComponentType(Enum) ``` Enum of component types supported. #### to`_`package`_`type ```python def to_package_type() -> PackageType ``` Get package type for component type. #### plurals ```python @staticmethod def plurals() -> Collection[str] ``` Get the collection of type names, plural. >>> ComponentType.plurals() ['protocols', 'connections', 'skills', 'contracts'] **Returns**: list of all pluralised component types #### to`_`plural ```python def to_plural() -> str ``` Get the plural version of the component type. >>> ComponentType.PROTOCOL.to_plural() 'protocols' >>> ComponentType.CONNECTION.to_plural() 'connections' >>> ComponentType.SKILL.to_plural() 'skills' >>> ComponentType.CONTRACT.to_plural() 'contracts' **Returns**: pluralised component type #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. ## PublicId Objects ```python class PublicId(JSONSerializable) ``` This class implement a public identifier. A public identifier is composed of three elements: - author - name - version The concatenation of those three elements gives the public identifier: author/name:version >>> public_id = PublicId("author", "my_package", "0.1.0") >>> assert public_id.author == "author" >>> assert public_id.name == "my_package" >>> assert public_id.version == "0.1.0" >>> another_public_id = PublicId("author", "my_package", "0.1.0") >>> assert hash(public_id) == hash(another_public_id) >>> assert public_id == another_public_id >>> latest_public_id = PublicId("author", "my_package", "latest") >>> latest_public_id >>> latest_public_id.package_version.is_latest True #### `__`init`__` ```python def __init__(author: SimpleIdOrStr, name: SimpleIdOrStr, version: Optional[PackageVersionLike] = None) -> None ``` Initialize the public identifier. #### author ```python @property def author() -> str ``` Get the author. #### name ```python @property def name() -> str ``` Get the name. #### version ```python @property def version() -> str ``` Get the version string. #### package`_`version ```python @property def package_version() -> PackageVersion ``` Get the package version object. #### to`_`any ```python def to_any() -> "PublicId" ``` Return the same public id, but with any version. #### same`_`prefix ```python def same_prefix(other: "PublicId") -> bool ``` Check if the other public id has the same author and name of this. #### to`_`latest ```python def to_latest() -> "PublicId" ``` Return the same public id, but with latest version. #### is`_`valid`_`str ```python @classmethod def is_valid_str(cls, public_id_string: str) -> bool ``` Check if a string is a public id. **Arguments**: - `public_id_string`: the public id in string format. **Returns**: bool indicating validity #### from`_`str ```python @classmethod def from_str(cls, public_id_string: str) -> "PublicId" ``` Initialize the public id from the string. >>> str(PublicId.from_str("author/package_name:0.1.0")) 'author/package_name:0.1.0' A bad formatted input raises value error: >>> PublicId.from_str("bad/formatted:input") Traceback (most recent call last): ... ValueError: Input 'bad/formatted:input' is not well formatted. **Arguments**: - `public_id_string`: the public id in string format. **Raises**: - `ValueError`: if the string in input is not well formatted. **Returns**: the public id object. #### try`_`from`_`str ```python @classmethod def try_from_str(cls, public_id_string: str) -> Optional["PublicId"] ``` Safely try to get public id from string. **Arguments**: - `public_id_string`: the public id in string format. **Returns**: the public id object or None #### from`_`uri`_`path ```python @classmethod def from_uri_path(cls, public_id_uri_path: str) -> "PublicId" ``` Initialize the public id from the string. >>> str(PublicId.from_uri_path("author/package_name/0.1.0")) 'author/package_name:0.1.0' A bad formatted input raises value error: >>> PublicId.from_uri_path("bad/formatted:input") Traceback (most recent call last): ... ValueError: Input 'bad/formatted:input' is not well formatted. **Arguments**: - `public_id_uri_path`: the public id in uri path string format. **Raises**: - `ValueError`: if the string in input is not well formatted. **Returns**: the public id object. #### to`_`uri`_`path ```python @property def to_uri_path() -> str ``` Turn the public id into a uri path string. **Returns**: uri path string #### json ```python @property def json() -> Dict ``` Compute the JSON representation. #### from`_`json ```python @classmethod def from_json(cls, obj: Dict) -> "PublicId" ``` Build from a JSON object. #### `__`hash`__` ```python def __hash__() -> int ``` Get the hash. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`repr`__` ```python def __repr__() -> str ``` Get the representation. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare with another object. #### `__`lt`__` ```python def __lt__(other: Any) -> bool ``` Compare two public ids. >>> public_id_1 = PublicId("author_1", "name_1", "0.1.0") >>> public_id_2 = PublicId("author_1", "name_1", "0.1.1") >>> public_id_3 = PublicId("author_1", "name_2", "0.1.0") >>> public_id_1 > public_id_2 False >>> public_id_1 < public_id_2 True >>> public_id_1 < public_id_3 Traceback (most recent call last): ... ValueError: The public IDs author_1/name_1:0.1.0 and author_1/name_2:0.1.0 cannot be compared. Their author or name attributes are different. **Arguments**: - `other`: the object to compate to **Raises**: - `ValueError`: if the public ids cannot be confirmed **Returns**: whether or not the inequality is satisfied ## PackageId Objects ```python class PackageId() ``` A package identifier. #### `__`init`__` ```python def __init__(package_type: Union[PackageType, str], public_id: PublicId) -> None ``` Initialize the package id. **Arguments**: - `package_type`: the package type. - `public_id`: the public id. #### package`_`type ```python @property def package_type() -> PackageType ``` Get the package type. #### public`_`id ```python @property def public_id() -> PublicId ``` Get the public id. #### author ```python @property def author() -> str ``` Get the author of the package. #### name ```python @property def name() -> str ``` Get the name of the package. #### version ```python @property def version() -> str ``` Get the version of the package. #### package`_`prefix ```python @property def package_prefix() -> Tuple[PackageType, str, str] ``` Get the package identifier without the version. #### from`_`uri`_`path ```python @classmethod def from_uri_path(cls, package_id_uri_path: str) -> "PackageId" ``` Initialize the package id from the string. >>> str(PackageId.from_uri_path("skill/author/package_name/0.1.0")) '(skill, author/package_name:0.1.0)' A bad formatted input raises value error: >>> PackageId.from_uri_path("very/bad/formatted:input") Traceback (most recent call last): ... ValueError: Input 'very/bad/formatted:input' is not well formatted. **Arguments**: - `package_id_uri_path`: the package id in uri path string format. **Raises**: - `ValueError`: if the string in input is not well formatted. **Returns**: the package id object. #### to`_`uri`_`path ```python @property def to_uri_path() -> str ``` Turn the package id into a uri path string. **Returns**: uri path string #### `__`hash`__` ```python def __hash__() -> int ``` Get the hash. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`repr`__` ```python def __repr__() -> str ``` Get the object representation in string. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare with another object. #### `__`lt`__` ```python def __lt__(other: Any) -> bool ``` Compare two public ids. ## ComponentId Objects ```python class ComponentId(PackageId) ``` Class to represent a component identifier. A component id is a package id, but excludes the case when the package is an agent. >>> pacakge_id = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) >>> component_id = ComponentId(ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0")) >>> pacakge_id == component_id True >>> component_id2 = ComponentId(ComponentType.PROTOCOL, PublicId("author", "name", "0.1.1")) >>> pacakge_id == component_id2 False #### `__`init`__` ```python def __init__(component_type: Union[ComponentType, str], public_id: PublicId) -> None ``` Initialize the component id. **Arguments**: - `component_type`: the component type. - `public_id`: the public id. #### component`_`type ```python @property def component_type() -> ComponentType ``` Get the component type. #### component`_`prefix ```python @property def component_prefix() -> PackageIdPrefix ``` Get the component identifier without the version. #### same`_`prefix ```python def same_prefix(other: "ComponentId") -> bool ``` Check if the other component id has the same type, author and name of this. #### prefix`_`import`_`path ```python @property def prefix_import_path() -> str ``` Get the prefix import path for this component. #### json ```python @property def json() -> Dict ``` Get the JSON representation. #### from`_`json ```python @classmethod def from_json(cls, json_data: Dict) -> "ComponentId" ``` Create component id from json data. ## PyPIPackageName Objects ```python class PyPIPackageName(RegexConstrainedString) ``` A PyPI Package name. ## GitRef Objects ```python class GitRef(RegexConstrainedString) ``` A Git reference. It can be a branch name, a commit hash or a tag. ## Dependency Objects ```python class Dependency() ``` This class represents a PyPI dependency. It contains the following information: - version: a version specifier(s) (e.g. '==0.1.0'). - index: the PyPI index where to download the package from (default: https://pypi.org) - git: the URL to the Git repository (e.g. https://github.com/fetchai/agents-aea.git) - ref: either the branch name, the tag, the commit number or a Git reference (default: 'master'.) If the 'git' field is set, the 'version' field will be ignored. These fields will be forwarded to the 'pip' command. #### `__`init`__` ```python def __init__(name: Union[PyPIPackageName, str], version: Union[str, SpecifierSet] = "", index: Optional[str] = None, git: Optional[str] = None, ref: Optional[Union[GitRef, str]] = None) -> None ``` Initialize a PyPI dependency. **Arguments**: - `name`: the package name. - `version`: the specifier set object - `index`: the URL to the PyPI server. - `git`: the URL to a git repository. - `ref`: the Git reference (branch/commit/tag). #### name ```python @property def name() -> str ``` Get the name. #### version ```python @property def version() -> str ``` Get the version. #### index ```python @property def index() -> Optional[str] ``` Get the index. #### git ```python @property def git() -> Optional[str] ``` Get the git. #### ref ```python @property def ref() -> Optional[str] ``` Get the ref. #### from`_`json ```python @classmethod def from_json(cls, obj: Dict[str, Dict[str, str]]) -> "Dependency" ``` Parse a dependency object from a dictionary. #### to`_`json ```python def to_json() -> Dict[str, Dict[str, str]] ``` Transform the object to JSON. #### get`_`pip`_`install`_`args ```python def get_pip_install_args() -> List[str] ``` Get 'pip install' arguments. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare with another object. #### Dependencies A dictionary from package name to dependency data structure (see above). The package name must satisfy the constraints on Python packages names. The main advantage of having a dictionary is that we implicitly filter out dependency duplicates. We cannot have two items with the same package name since the keys of a YAML object form a set. ## CRUDCollection Objects ```python class CRUDCollection(Generic[T]) ``` Interface of a CRUD collection. #### `__`init`__` ```python def __init__() -> None ``` Instantiate a CRUD collection. #### create ```python def create(item_id: str, item: T) -> None ``` Add an item. **Arguments**: - `item_id`: the item id. - `item`: the item to be added. **Raises**: - `ValueError`: if the item with the same id is already in the collection. #### read ```python def read(item_id: str) -> Optional[T] ``` Get an item by its name. **Arguments**: - `item_id`: the item id. **Returns**: the associated item, or None if the item id is not present. #### update ```python def update(item_id: str, item: T) -> None ``` Update an existing item. **Arguments**: - `item_id`: the item id. - `item`: the item to be added. #### delete ```python def delete(item_id: str) -> None ``` Delete an item. #### read`_`all ```python def read_all() -> List[Tuple[str, T]] ``` Read all the items. #### keys ```python def keys() -> Set[str] ``` Get the set of keys. ================================================ FILE: docs/api/configurations/loader.md ================================================ ================================================ FILE: docs/api/configurations/manager.md ================================================ # aea.configurations.manager Implementation of the AgentConfigManager. ## VariableDoesNotExist Objects ```python class VariableDoesNotExist(ValueError) ``` Variable does not exist in a config exception. #### handle`_`dotted`_`path ```python def handle_dotted_path( value: str, author: str, aea_project_path: Union[str, Path] = "." ) -> Tuple[List[str], Path, ConfigLoader, Optional[ComponentId]] ``` Separate the path between path to resource and json path to attribute. Allowed values: 'agent.an_attribute_name' 'protocols.my_protocol.an_attribute_name' 'connections.my_connection.an_attribute_name' 'contracts.my_contract.an_attribute_name' 'skills.my_skill.an_attribute_name' 'vendor.author.[protocols|contracts|connections|skills].package_name.attribute_name We also return the component id to retrieve the configuration of a specific component. Notice that at this point we don't know the version, so we put 'latest' as version, but later we will ignore it because we will filter with only the component prefix (i.e. the triple type, author and name). **Arguments**: - `value`: dotted path. - `author`: the author string. - `aea_project_path`: project path **Returns**: Tuple[list of settings dict keys, filepath, config loader, component id]. #### find`_`component`_`directory`_`from`_`component`_`id ```python def find_component_directory_from_component_id( aea_project_directory: Path, component_id: ComponentId) -> Path ``` Find a component directory from component id. ## AgentConfigManager Objects ```python class AgentConfigManager() ``` AeaConfig manager. #### `__`init`__` ```python def __init__(agent_config: AgentConfig, aea_project_directory: Union[str, Path], env_vars_friendly: bool = False) -> None ``` Init manager. **Arguments**: - `agent_config`: AgentConfig to manage. - `aea_project_directory`: directory where project for agent_config placed. - `env_vars_friendly`: whether or not it is env vars friendly #### load`_`component`_`configuration ```python def load_component_configuration( component_id: ComponentId, skip_consistency_check: bool = True) -> ComponentConfiguration ``` Load component configuration from the project directory. **Arguments**: - `component_id`: Id of the component to load config for. - `skip_consistency_check`: bool. **Returns**: ComponentConfiguration #### agent`_`config`_`file`_`path ```python @property def agent_config_file_path() -> Path ``` Return agent config file path. #### load ```python @classmethod def load(cls, aea_project_path: Union[Path, str], substitude_env_vars: bool = False) -> "AgentConfigManager" ``` Create AgentConfigManager instance from agent project path. #### set`_`variable ```python def set_variable(path: VariablePath, value: JSON_TYPES) -> None ``` Set config variable. **Arguments**: - `path`: str dotted path or List[Union[ComponentId, str]] - `value`: one of the json friendly objects. #### get`_`variable ```python def get_variable(path: VariablePath) -> JSON_TYPES ``` Set config variable. **Arguments**: - `path`: str dotted path or List[Union[ComponentId, str]] **Returns**: json friendly value. #### update`_`config ```python def update_config(overrides: Dict) -> None ``` Apply overrides for agent config. Validates and applies agent config and component overrides. Does not save it on the disc! **Arguments**: - `overrides`: overridden values dictionary **Returns**: None #### validate`_`current`_`config ```python def validate_current_config() -> None ``` Check is current config valid. #### json ```python @property def json() -> Dict ``` Return current agent config json representation. #### dump`_`config ```python def dump_config() -> None ``` Save agent config on the disc. #### verify`_`private`_`keys ```python @classmethod def verify_private_keys( cls, aea_project_path: Union[Path, str], private_key_helper: Callable[[AgentConfig, Path, Optional[str]], None], substitude_env_vars: bool = False, password: Optional[str] = None) -> "AgentConfigManager" ``` Verify private keys. Does not saves the config! Use AgentConfigManager.dump_config() **Arguments**: - `aea_project_path`: path to an AEA project. - `private_key_helper`: private_key_helper is a function that use agent config to check the keys - `substitude_env_vars`: replace env vars with values, does not dump config - `password`: the password to encrypt/decrypt the private key. **Returns**: the agent configuration manager. #### get`_`overridables ```python def get_overridables() -> Tuple[Dict, Dict[ComponentId, Dict]] ``` Get config overridables. ================================================ FILE: docs/api/configurations/pypi.md ================================================ # aea.configurations.pypi This module contains a checker for PyPI version consistency. #### and`_` ```python def and_(s1: SpecifierSet, s2: SpecifierSet) -> SpecifierSet ``` Do the and between two specifier sets. #### is`_`satisfiable ```python def is_satisfiable(specifier_set: SpecifierSet) -> bool ``` Check if the specifier set is satisfiable. Satisfiable means that there exists a version number that satisfies all the constraints. It is worth noticing that it doesn't mean that that version number with that package actually exists. >>> from packaging.specifiers import SpecifierSet The specifier set ">0.9, ==1.0" is satisfiable: the version number "1.0" satisfies the constraints >>> s1 = SpecifierSet(">0.9,==1.0") >>> "1.0" in s1 True >>> is_satisfiable(s1) True The specifier set "==1.0, >1.1" is not satisfiable: >>> s1 = SpecifierSet("==1.0,>1.1") >>> is_satisfiable(s1) False For other details, please refer to PEP440: https://www.python.org/dev/peps/pep-0440 **Arguments**: - `specifier_set`: the specifier set. **Returns**: False if the constraints are surely non-satisfiable, True if we don't know. #### is`_`simple`_`dep ```python def is_simple_dep(dep: Dependency) -> bool ``` Check if it is a simple dependency. Namely, if it has no field specified, or only the 'version' field set. **Arguments**: - `dep`: the dependency **Returns**: whether it is a simple dependency or not #### to`_`set`_`specifier ```python def to_set_specifier(dep: Dependency) -> SpecifierSet ``` Get the set specifier. It assumes to be a simple dependency (see above). #### merge`_`dependencies ```python def merge_dependencies(dep1: Dependencies, dep2: Dependencies) -> Dependencies ``` Merge two groups of dependencies. If some of them are not "simple" (see above), and there is no risk of conflict because there is no other package with the same name, we leave them; otherwise we raise an error. **Arguments**: - `dep1`: the first operand - `dep2`: the second operand. **Returns**: the merged dependencies. #### merge`_`dependencies`_`list ```python def merge_dependencies_list(*deps: Dependencies) -> Dependencies ``` Merge a list of dependencies. **Arguments**: - `deps`: the list of dependencies **Returns**: the merged dependencies. ================================================ FILE: docs/api/configurations/utils.md ================================================ # aea.configurations.utils AEA configuration utils. #### replace`_`component`_`ids ```python @singledispatch def replace_component_ids( _arg: PackageConfiguration, _replacements: Dict[ComponentType, Dict[PublicId, PublicId]]) -> None ``` Update public id references in a package configuration. This depends on the actual configuration being considered. #### `_` ```python @replace_component_ids.register(AgentConfig) # type: ignore def _(arg: AgentConfig, replacements: Dict[ComponentType, Dict[PublicId, PublicId]]) -> None ``` Replace references in agent configuration. It breaks down in: 1) replace public ids in 'protocols', 'connections', 'contracts' and 'skills'; 2) replace public ids in default routing; 3) replace public id of default connection; 4) replace custom component configurations. **Arguments**: - `arg`: the agent configuration. - `replacements`: the replacement mapping. #### `_` ```python @replace_component_ids.register(ProtocolConfig) # type: ignore def _(_arg: ProtocolConfig, _replacements: Dict[ComponentType, Dict[PublicId, PublicId]]) -> None ``` Do nothing - protocols have no references. #### `_` ```python @replace_component_ids.register(ConnectionConfig) # type: ignore def _(arg: ConnectionConfig, replacements: Dict[ComponentType, Dict[PublicId, PublicId]]) -> None ``` Replace references in a connection configuration. #### `_` ```python @replace_component_ids.register(ContractConfig) # type: ignore def _(_arg: ContractConfig, _replacements: Dict[ComponentType, Dict[PublicId, PublicId]]) -> None ``` Do nothing - contracts have no references. #### `_` ```python @replace_component_ids.register(SkillConfig) # type: ignore def _(arg: SkillConfig, replacements: Dict[ComponentType, Dict[PublicId, PublicId]]) -> None ``` Replace references in a skill configuration. #### get`_`latest`_`component`_`id`_`from`_`prefix ```python def get_latest_component_id_from_prefix( agent_config: AgentConfig, component_prefix: PackageIdPrefix) -> Optional[ComponentId] ``` Get component id with the greatest version in an agent configuration given its prefix. **Arguments**: - `agent_config`: the agent configuration. - `component_prefix`: the package prefix. **Returns**: the package id with the greatest version, or None if not found. ================================================ FILE: docs/api/configurations/validation.md ================================================ # aea.configurations.validation Implementation of the configuration validation. #### make`_`jsonschema`_`base`_`uri ```python def make_jsonschema_base_uri(base_uri_path: Path) -> str ``` Make the JSONSchema base URI, cross-platform. **Arguments**: - `base_uri_path`: the path to the base directory. **Returns**: the string in URI form. ## ExtraPropertiesError Objects ```python class ExtraPropertiesError(ValueError) ``` Extra properties exception. #### `__`str`__` ```python def __str__() -> str ``` Get string representation of the object. #### `__`repr`__` ```python def __repr__() -> str ``` Get string representation of the object. ## CustomTypeChecker Objects ```python class CustomTypeChecker(TypeChecker) ``` Custom type checker to handle env variables. #### is`_`type ```python def is_type(instance, type) -> bool ``` Check is instance of type. #### own`_`additional`_`properties ```python def own_additional_properties(validator, aP, instance, schema) -> Iterator ``` Additional properties validator. ## ConfigValidator Objects ```python class ConfigValidator() ``` Configuration validator implementation. #### `__`init`__` ```python def __init__(schema_filename: str, env_vars_friendly: bool = False) -> None ``` Initialize the parser for configuration files. **Arguments**: - `schema_filename`: the path to the JSON-schema file in 'aea/configurations/schemas'. - `env_vars_friendly`: whether or not it is env var friendly. #### split`_`component`_`id`_`and`_`config ```python @staticmethod def split_component_id_and_config( component_index: int, component_configuration_json: Dict) -> ComponentId ``` Split component id and configuration. **Arguments**: - `component_index`: the position of the component configuration in the agent config file.. - `component_configuration_json`: the JSON object to process. **Raises**: - `ValueError`: if the component id cannot be extracted. **Returns**: the component id and the configuration object. #### validate`_`component`_`configuration ```python @classmethod def validate_component_configuration(cls, component_id: ComponentId, configuration: Dict, env_vars_friendly: bool = False) -> None ``` Validate the component configuration of an agent configuration file. This check is to detect inconsistencies in the specified fields. **Arguments**: - `component_id`: the component id. - `configuration`: the configuration dictionary. - `env_vars_friendly`: bool, if set True, will not raise errors over the env variable definitions. **Raises**: - `ValueError`: if the configuration is not valid. #### validate ```python def validate(json_data: Dict) -> None ``` Validate a JSON object against the right JSON schema. **Arguments**: - `json_data`: the JSON data. #### validate`_`agent`_`components`_`configuration ```python def validate_agent_components_configuration( component_configurations: Dict) -> None ``` Validate agent component configurations overrides. **Arguments**: - `component_configurations`: the component configurations to validate. #### required`_`fields ```python @property def required_fields() -> List[str] ``` Get the required fields. **Returns**: list of required fields. #### validate`_`data`_`with`_`pattern ```python def validate_data_with_pattern(data: dict, pattern: dict, excludes: Optional[List[Tuple[str]]] = None, skip_env_vars: bool = False) -> List[str] ``` Validate data dict with pattern dict for attributes present and type match. **Arguments**: - `data`: data dict to validate - `pattern`: dict with pattern to check over - `excludes`: list of tuples of str of paths to be skipped during the check - `skip_env_vars`: is set True will not check data type over env variables. **Returns**: list of str with error descriptions #### filter`_`data ```python def filter_data(base: Any, updates: Any) -> Any ``` Return difference in values or `SAME_MARK` object if values are the same. ================================================ FILE: docs/api/connections/base.md ================================================ # aea.connections.base The base connection package. ## ConnectionStates Objects ```python class ConnectionStates(Enum) ``` Connection states enum. ## Connection Objects ```python class Connection(Component, ABC) ``` Abstract definition of a connection. #### `__`init`__` ```python def __init__(configuration: ConnectionConfig, data_dir: str, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, **kwargs: Any) -> None ``` Initialize the connection. The configuration must be specified if and only if the following parameters are None: connection_id, excluded_protocols or restricted_to_protocols. **Arguments**: - `configuration`: the connection configuration. - `data_dir`: directory where to put local files. - `identity`: the identity object held by the agent. - `crypto_store`: the crypto store for encrypted communication. - `restricted_to_protocols`: the set of protocols ids of the only supported protocols for this connection. - `excluded_protocols`: the set of protocols ids that we want to exclude for this connection. - `kwargs`: keyword arguments passed to component base #### loop ```python @property def loop() -> asyncio.AbstractEventLoop ``` Get the event loop. #### address ```python @property def address() -> "Address" ``` Get the address. #### crypto`_`store ```python @property def crypto_store() -> CryptoStore ``` Get the crypto store. #### has`_`crypto`_`store ```python @property def has_crypto_store() -> bool ``` Check if the connection has the crypto store. #### data`_`dir ```python @property def data_dir() -> str ``` Get the data directory. #### component`_`type ```python @property def component_type() -> ComponentType ``` Get the component type. #### configuration ```python @property def configuration() -> ConnectionConfig ``` Get the connection configuration. #### restricted`_`to`_`protocols ```python @property def restricted_to_protocols() -> Set[PublicId] ``` Get the ids of the protocols this connection is restricted to. #### excluded`_`protocols ```python @property def excluded_protocols() -> Set[PublicId] ``` Get the ids of the excluded protocols for this connection. #### state ```python @property def state() -> ConnectionStates ``` Get the connection status. #### state ```python @state.setter def state(value: ConnectionStates) -> None ``` Set the connection status. #### connect ```python @abstractmethod async def connect() -> None ``` Set up the connection. #### disconnect ```python @abstractmethod async def disconnect() -> None ``` Tear down the connection. #### send ```python @abstractmethod async def send(envelope: "Envelope") -> None ``` Send an envelope. **Arguments**: - `envelope`: the envelope to send. **Returns**: None #### receive ```python @abstractmethod async def receive(*args: Any, **kwargs: Any) -> Optional["Envelope"] ``` Receive an envelope. **Arguments**: - `args`: positional arguments - `kwargs`: keyword arguments **Returns**: the received envelope, or None if an error occurred. #### from`_`dir ```python @classmethod def from_dir(cls, directory: str, identity: Identity, crypto_store: CryptoStore, data_dir: str, **kwargs: Any) -> "Connection" ``` Load the connection from a directory. **Arguments**: - `directory`: the directory to the connection package. - `identity`: the identity object. - `crypto_store`: object to access the connection crypto objects. - `data_dir`: the assets directory. - `kwargs`: keyword arguments passed to connection base **Returns**: the connection object. #### from`_`config ```python @classmethod def from_config(cls, configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, data_dir: str, **kwargs: Any) -> "Connection" ``` Load a connection from a configuration. **Arguments**: - `configuration`: the connection configuration. - `identity`: the identity object. - `crypto_store`: object to access the connection crypto objects. - `data_dir`: the directory of the AEA project data. - `kwargs`: keyword arguments passed to component base **Returns**: an instance of the concrete connection class. #### is`_`connected ```python @property def is_connected() -> bool ``` Return is connected state. #### is`_`connecting ```python @property def is_connecting() -> bool ``` Return is connecting state. #### is`_`disconnected ```python @property def is_disconnected() -> bool ``` Return is disconnected state. ## BaseSyncConnection Objects ```python class BaseSyncConnection(Connection) ``` Base sync connection class to write connections with sync code. #### `__`init`__` ```python def __init__(configuration: ConnectionConfig, data_dir: str, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, **kwargs: Any) -> None ``` Initialize the connection. The configuration must be specified if and only if the following parameters are None: connection_id, excluded_protocols or restricted_to_protocols. **Arguments**: - `configuration`: the connection configuration. - `data_dir`: directory where to put local files. - `identity`: the identity object held by the agent. - `crypto_store`: the crypto store for encrypted communication. - `restricted_to_protocols`: the set of protocols ids of the only supported protocols for this connection. - `excluded_protocols`: the set of protocols ids that we want to exclude for this connection. - `kwargs`: keyword arguments passed to connection base #### put`_`envelope ```python def put_envelope(envelope: Optional["Envelope"]) -> None ``` Put envelope in to the incoming queue. #### connect ```python async def connect() -> None ``` Connect connection. #### disconnect ```python async def disconnect() -> None ``` Disconnect connection. #### send ```python async def send(envelope: "Envelope") -> None ``` Send envelope to connection. #### receive ```python async def receive(*args: Any, **kwargs: Any) -> Optional["Envelope"] ``` Get an envelope from the connection. #### start`_`main ```python def start_main() -> None ``` Start main function of the connection. #### main ```python def main() -> None ``` Run main body of the connection in dedicated thread. #### on`_`connect ```python @abstractmethod def on_connect() -> None ``` Run on connect method called. #### on`_`disconnect ```python @abstractmethod def on_disconnect() -> None ``` Run on disconnect method called. #### on`_`send ```python @abstractmethod def on_send(envelope: "Envelope") -> None ``` Run on send method called. ================================================ FILE: docs/api/context/base.md ================================================ # aea.context.base This module contains the agent context class. ## AgentContext Objects ```python class AgentContext() ``` Provide read access to relevant objects of the agent for the skills. #### `__`init`__` ```python def __init__(identity: Identity, connection_status: MultiplexerStatus, outbox: OutBox, decision_maker_message_queue: Queue, decision_maker_handler_context: SimpleNamespace, task_manager: TaskManager, default_ledger_id: str, currency_denominations: Dict[str, str], default_connection: Optional[PublicId], default_routing: Dict[PublicId, PublicId], search_service_address: Address, decision_maker_address: Address, data_dir: str, storage_callable: Callable[[], Optional[Storage]] = lambda: None, send_to_skill: Optional[Callable] = None, **kwargs: Any) -> None ``` Initialize an agent context. **Arguments**: - `identity`: the identity object - `connection_status`: the connection status of the multiplexer - `outbox`: the outbox - `decision_maker_message_queue`: the (in) queue of the decision maker - `decision_maker_handler_context`: the decision maker's name space - `task_manager`: the task manager - `default_ledger_id`: the default ledger id - `currency_denominations`: mapping from ledger ids to currency denominations - `default_connection`: the default connection - `default_routing`: the default routing - `search_service_address`: the address of the search service - `decision_maker_address`: the address of the decision maker - `data_dir`: directory where to put local files. - `storage_callable`: function that returns optional storage attached to agent. - `send_to_skill`: callable for sending envelopes to skills. - `kwargs`: keyword arguments to be attached in the agent context namespace. #### send`_`to`_`skill ```python def send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None ``` Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. **Arguments**: - `message_or_envelope`: envelope to send to another skill. - `context`: the optional envelope context #### storage ```python @property def storage() -> Optional[Storage] ``` Return storage instance if enabled in AEA. #### data`_`dir ```python @property def data_dir() -> str ``` Return assets directory. #### shared`_`state ```python @property def shared_state() -> Dict[str, Any] ``` Get the shared state dictionary. The shared state is the only object which skills can use to exchange state directly. It is accessible (read and write) from all skills. **Returns**: dictionary of the shared state. #### identity ```python @property def identity() -> Identity ``` Get the identity. #### agent`_`name ```python @property def agent_name() -> str ``` Get agent name. #### addresses ```python @property def addresses() -> Dict[str, Address] ``` Get addresses. #### public`_`keys ```python @property def public_keys() -> Dict[str, str] ``` Get public keys. #### address ```python @property def address() -> Address ``` Get the default address. #### public`_`key ```python @property def public_key() -> str ``` Get the default public key. #### connection`_`status ```python @property def connection_status() -> MultiplexerStatus ``` Get connection status of the multiplexer. #### outbox ```python @property def outbox() -> OutBox ``` Get outbox. #### decision`_`maker`_`message`_`queue ```python @property def decision_maker_message_queue() -> Queue ``` Get decision maker queue. #### decision`_`maker`_`handler`_`context ```python @property def decision_maker_handler_context() -> SimpleNamespace ``` Get the decision maker handler context. #### task`_`manager ```python @property def task_manager() -> TaskManager ``` Get the task manager. #### search`_`service`_`address ```python @property def search_service_address() -> Address ``` Get the address of the search service. #### decision`_`maker`_`address ```python @property def decision_maker_address() -> Address ``` Get the address of the decision maker. #### default`_`ledger`_`id ```python @property def default_ledger_id() -> str ``` Get the default ledger id. #### currency`_`denominations ```python @property def currency_denominations() -> Dict[str, str] ``` Get a dictionary mapping ledger ids to currency denominations. #### default`_`connection ```python @property def default_connection() -> Optional[PublicId] ``` Get the default connection. #### default`_`routing ```python @property def default_routing() -> Dict[PublicId, PublicId] ``` Get the default routing. #### namespace ```python @property def namespace() -> SimpleNamespace ``` Get the agent context namespace. ================================================ FILE: docs/api/contracts/base.md ================================================ # aea.contracts.base The base contract. ## Contract Objects ```python class Contract(Component) ``` Abstract definition of a contract. #### `__`init`__` ```python def __init__(contract_config: ContractConfig, **kwargs: Any) -> None ``` Initialize the contract. **Arguments**: - `contract_config`: the contract configurations. - `kwargs`: the keyword arguments. #### id ```python @property def id() -> PublicId ``` Get the name. #### configuration ```python @property def configuration() -> ContractConfig ``` Get the configuration. #### get`_`instance ```python @classmethod def get_instance(cls, ledger_api: LedgerApi, contract_address: Optional[str] = None) -> Any ``` Get the instance. **Arguments**: - `ledger_api`: the ledger api we are using. - `contract_address`: the contract address. **Returns**: the contract instance #### from`_`dir ```python @classmethod def from_dir(cls, directory: str, **kwargs: Any) -> "Contract" ``` Load the protocol from a directory. **Arguments**: - `directory`: the directory to the skill package. - `kwargs`: the keyword arguments. **Returns**: the contract object. #### from`_`config ```python @classmethod def from_config(cls, configuration: ContractConfig, **kwargs: Any) -> "Contract" ``` Load contract from configuration. **Arguments**: - `configuration`: the contract configuration. - `kwargs`: the keyword arguments. **Returns**: the contract object. #### get`_`deploy`_`transaction ```python @classmethod def get_deploy_transaction(cls, ledger_api: LedgerApi, deployer_address: str, **kwargs: Any) -> Optional[JSONLike] ``` Handler method for the 'GET_DEPLOY_TRANSACTION' requests. Implement this method in the sub class if you want to handle the contract requests manually. **Arguments**: - `ledger_api`: the ledger apis. - `deployer_address`: The address that will deploy the contract. - `kwargs`: keyword arguments. **Returns**: the tx #### get`_`raw`_`transaction ```python @classmethod def get_raw_transaction(cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any) -> Optional[JSONLike] ``` Handler method for the 'GET_RAW_TRANSACTION' requests. Implement this method in the sub class if you want to handle the contract requests manually. **Arguments**: - `ledger_api`: the ledger apis. - `contract_address`: the contract address. - `kwargs`: the keyword arguments. **Returns**: the tx # noqa: DAR202 #### get`_`raw`_`message ```python @classmethod def get_raw_message(cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any) -> Optional[bytes] ``` Handler method for the 'GET_RAW_MESSAGE' requests. Implement this method in the sub class if you want to handle the contract requests manually. **Arguments**: - `ledger_api`: the ledger apis. - `contract_address`: the contract address. - `kwargs`: the keyword arguments. **Returns**: the tx # noqa: DAR202 #### get`_`state ```python @classmethod def get_state(cls, ledger_api: LedgerApi, contract_address: str, **kwargs: Any) -> Optional[JSONLike] ``` Handler method for the 'GET_STATE' requests. Implement this method in the sub class if you want to handle the contract requests manually. **Arguments**: - `ledger_api`: the ledger apis. - `contract_address`: the contract address. - `kwargs`: the keyword arguments. **Returns**: the tx # noqa: DAR202 ================================================ FILE: docs/api/crypto/base.md ================================================ # aea.crypto.base Abstract module wrapping the public and private key cryptography and ledger api. ## Crypto Objects ```python class Crypto(Generic[EntityClass], ABC) ``` Base class for a crypto object. #### `__`init`__` ```python def __init__(private_key_path: Optional[str] = None, password: Optional[str] = None, **kwargs: Any) -> None ``` Initialize the crypto object. The actual behaviour of this constructor is determined by the abstract methods 'generate_private_key()' and 'load_private_key_from_path(). Either way, the entity object will be accessible as a property. **Arguments**: - `private_key_path`: the path to the private key. If None, the key will be generated by 'generate_private_key()'. If not None, the path will be processed by 'load_private_key_from_path()'. - `password`: the password to encrypt/decrypt the private key. - `kwargs`: keyword arguments. #### generate`_`private`_`key ```python @classmethod @abstractmethod def generate_private_key(cls) -> EntityClass ``` Generate a private key. **Returns**: the entity object. Implementation dependent. #### load`_`private`_`key`_`from`_`path ```python @classmethod @abstractmethod def load_private_key_from_path(cls, file_name: str, password: Optional[str] = None) -> EntityClass ``` Load a private key in hex format for raw private key and json format for encrypted private key from a file. **Arguments**: - `file_name`: the path to the hex/json file. - `password`: the password to encrypt/decrypt the private key. **Returns**: the entity object. #### entity ```python @property def entity() -> EntityClass ``` Return an entity object. **Returns**: an entity object #### private`_`key ```python @property @abstractmethod def private_key() -> str ``` Return a private key. **Returns**: a private key string #### public`_`key ```python @property @abstractmethod def public_key() -> str ``` Return a public key. **Returns**: a public key string #### address ```python @property @abstractmethod def address() -> str ``` Return the address. **Returns**: an address string #### sign`_`message ```python @abstractmethod def sign_message(message: bytes, is_deprecated_mode: bool = False) -> str ``` Sign a message in bytes string form. **Arguments**: - `message`: the message to be signed - `is_deprecated_mode`: if the deprecated signing is used **Returns**: signature of the message in string form #### sign`_`transaction ```python @abstractmethod def sign_transaction(transaction: JSONLike) -> JSONLike ``` Sign a transaction in dict form. **Arguments**: - `transaction`: the transaction to be signed **Returns**: signed transaction #### load ```python @classmethod def load(cls, private_key_file: str, password: Optional[str] = None) -> str ``` Load private key from file. **Arguments**: - `private_key_file`: the file where the key is stored. - `password`: the password to encrypt/decrypt the private key. **Returns**: private_key in hex string format #### dump ```python def dump(private_key_file: str, password: Optional[str] = None) -> None ``` Dump private key to file. **Arguments**: - `private_key_file`: the file where the key is stored. - `password`: the password to encrypt/decrypt the private key. #### encrypt ```python @abstractmethod def encrypt(password: str) -> str ``` Encrypt the private key and return in json. **Arguments**: - `password`: the password to decrypt. **Returns**: json string containing encrypted private key. #### decrypt ```python @classmethod @abstractmethod def decrypt(cls, keyfile_json: str, password: str) -> str ``` Decrypt the private key and return in raw form. **Arguments**: - `keyfile_json`: json string containing encrypted private key. - `password`: the password to decrypt. **Returns**: the raw private key. ## Helper Objects ```python class Helper(ABC) ``` Interface for helper class usable as Mixin for LedgerApi or as standalone class. #### is`_`transaction`_`settled ```python @staticmethod @abstractmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool ``` Check whether a transaction is settled or not. **Arguments**: - `tx_receipt`: the receipt associated to the transaction. **Returns**: True if the transaction has been settled, False o/w. #### is`_`transaction`_`valid ```python @staticmethod @abstractmethod def is_transaction_valid(tx: JSONLike, seller: Address, client: Address, tx_nonce: str, amount: int) -> bool ``` Check whether a transaction is valid or not. **Arguments**: - `tx`: the transaction. - `seller`: the address of the seller. - `client`: the address of the client. - `tx_nonce`: the transaction nonce. - `amount`: the amount we expect to get from the transaction. **Returns**: True if the random_message is equals to tx['input'] #### get`_`contract`_`address ```python @staticmethod @abstractmethod def get_contract_address(tx_receipt: JSONLike) -> Optional[str] ``` Get the contract address from a transaction receipt. **Arguments**: - `tx_receipt`: the transaction digest **Returns**: the contract address if successful #### generate`_`tx`_`nonce ```python @staticmethod @abstractmethod def generate_tx_nonce(seller: Address, client: Address) -> str ``` Generate a unique hash to distinguish transactions with the same terms. **Arguments**: - `seller`: the address of the seller. - `client`: the address of the client. **Returns**: return the hash in hex. #### get`_`address`_`from`_`public`_`key ```python @classmethod @abstractmethod def get_address_from_public_key(cls, public_key: str) -> str ``` Get the address from the public key. **Arguments**: - `public_key`: the public key **Returns**: str #### recover`_`message ```python @classmethod @abstractmethod def recover_message(cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[Address, ...] ``` Recover the addresses from the hash. **Arguments**: - `message`: the message we expect - `signature`: the transaction signature - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered addresses #### recover`_`public`_`keys`_`from`_`message ```python @classmethod @abstractmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[str, ...] ``` Get the public key used to produce the `signature` of the `message` **Arguments**: - `message`: raw bytes used to produce signature - `signature`: signature of the message - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered public keys #### get`_`hash ```python @staticmethod @abstractmethod def get_hash(message: bytes) -> str ``` Get the hash of a message. **Arguments**: - `message`: the message to be hashed. **Returns**: the hash of the message. #### is`_`valid`_`address ```python @classmethod @abstractmethod def is_valid_address(cls, address: Address) -> bool ``` Check if the address is valid. **Arguments**: - `address`: the address to validate #### load`_`contract`_`interface ```python @classmethod @abstractmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str] ``` Load contract interface. **Arguments**: - `file_path`: the file path to the interface **Returns**: the interface ## LedgerApi Objects ```python class LedgerApi(Helper, ABC) ``` Interface for ledger APIs. #### api ```python @property @abstractmethod def api() -> Any ``` Get the underlying API object. This can be used for low-level operations with the concrete ledger APIs. If there is no such object, return None. #### get`_`balance ```python @abstractmethod def get_balance(address: Address) -> Optional[int] ``` Get the balance of a given account. This usually takes the form of a web request to be waited synchronously. **Arguments**: - `address`: the address. **Returns**: the balance. #### get`_`state ```python @abstractmethod def get_state(callable_name: str, *args: Any, **kwargs: Any) -> Optional[JSONLike] ``` Call a specified function on the underlying ledger API. This usually takes the form of a web request to be waited synchronously. **Arguments**: - `callable_name`: the name of the API function to be called. - `args`: the positional arguments for the API function. - `kwargs`: the keyword arguments for the API function. **Returns**: the ledger API response. #### get`_`transfer`_`transaction ```python @abstractmethod def get_transfer_transaction(sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, **kwargs: Any) -> Optional[JSONLike] ``` Submit a transfer transaction to the ledger. **Arguments**: - `sender_address`: the sender address of the payer. - `destination_address`: the destination address of the payee. - `amount`: the amount of wealth to be transferred. - `tx_fee`: the transaction fee. - `tx_nonce`: verifies the authenticity of the tx - `kwargs`: the keyword arguments. **Returns**: the transfer transaction #### send`_`signed`_`transaction ```python @abstractmethod def send_signed_transaction(tx_signed: JSONLike) -> Optional[str] ``` Send a signed transaction and wait for confirmation. Use keyword arguments for the specifying the signed transaction payload. **Arguments**: - `tx_signed`: the signed transaction #### get`_`transaction`_`receipt ```python @abstractmethod def get_transaction_receipt(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction receipt for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx receipt, if present #### get`_`transaction ```python @abstractmethod def get_transaction(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx, if present #### get`_`contract`_`instance ```python @abstractmethod def get_contract_instance(contract_interface: Dict[str, str], contract_address: Optional[str] = None) -> Any ``` Get the instance of a contract. **Arguments**: - `contract_interface`: the contract interface. - `contract_address`: the contract address. **Returns**: the contract instance #### get`_`deploy`_`transaction ```python @abstractmethod def get_deploy_transaction(contract_interface: Dict[str, str], deployer_address: Address, **kwargs: Any) -> Optional[JSONLike] ``` Get the transaction to deploy the smart contract. **Arguments**: - `contract_interface`: the contract interface. - `deployer_address`: The address that will deploy the contract. - `kwargs`: the keyword arguments. **Returns**: `tx`: the transaction dictionary. #### update`_`with`_`gas`_`estimate ```python @abstractmethod def update_with_gas_estimate(transaction: JSONLike) -> JSONLike ``` Attempts to update the transaction with a gas estimate **Arguments**: - `transaction`: the transaction **Returns**: the updated transaction ## FaucetApi Objects ```python class FaucetApi(ABC) ``` Interface for testnet faucet APIs. #### get`_`wealth ```python @abstractmethod def get_wealth(address: Address, url: Optional[str] = None) -> None ``` Get wealth from the faucet for the provided address. **Arguments**: - `address`: the address. - `url`: the url **Returns**: None ================================================ FILE: docs/api/crypto/helpers.md ================================================ # aea.crypto.helpers Module wrapping the helpers of public and private key cryptography. #### try`_`validate`_`private`_`key`_`path ```python def try_validate_private_key_path(ledger_id: str, private_key_path: str, password: Optional[str] = None) -> None ``` Try validate a private key path. **Arguments**: - `ledger_id`: one of 'fetchai', 'ethereum' - `private_key_path`: the path to the private key. - `password`: the password to encrypt/decrypt the private key. **Raises**: - `None`: ValueError if the identifier is invalid. #### create`_`private`_`key ```python def create_private_key(ledger_id: str, private_key_file: str, password: Optional[str] = None) -> None ``` Create a private key for the specified ledger identifier. **Arguments**: - `ledger_id`: the ledger identifier. - `private_key_file`: the private key file. - `password`: the password to encrypt/decrypt the private key. **Raises**: - `None`: ValueError if the identifier is invalid. #### try`_`generate`_`testnet`_`wealth ```python def try_generate_testnet_wealth(identifier: str, address: str, url: Optional[str] = None, _sync: bool = True) -> None ``` Try generate wealth on a testnet. **Arguments**: - `identifier`: the identifier of the ledger - `address`: the address to check for - `url`: the url - `_sync`: whether to wait to sync or not; currently unused #### private`_`key`_`verify ```python def private_key_verify(aea_conf: AgentConfig, aea_project_path: Path, password: Optional[str] = None) -> None ``` Check key. **Arguments**: - `aea_conf`: AgentConfig - `aea_project_path`: Path, where project placed. - `password`: the password to encrypt/decrypt the private key. #### make`_`certificate ```python def make_certificate(ledger_id: str, crypto_private_key_path: str, message: bytes, output_path: str, password: Optional[str] = None) -> str ``` Create certificate. **Arguments**: - `ledger_id`: the ledger id - `crypto_private_key_path`: the path to the private key. - `message`: the message to be signed. - `output_path`: the location where to save the certificate. - `password`: the password to encrypt/decrypt the private keys. **Returns**: the signature/certificate #### get`_`wallet`_`from`_`agent`_`config ```python def get_wallet_from_agent_config(agent_config: AgentConfig, password: Optional[str] = None) -> Wallet ``` Get wallet from agent_cofig provided. **Arguments**: - `agent_config`: the agent configuration object - `password`: the password to encrypt/decrypt the private keys. **Returns**: wallet ## DecryptError Objects ```python class DecryptError(ValueError) ``` Error on bytes decryption with password. #### `__`init`__` ```python def __init__(msg: Optional[str] = None) -> None ``` Init exception. ## KeyIsIncorrect Objects ```python class KeyIsIncorrect(ValueError) ``` Error decoding hex string to bytes for private key. #### hex`_`to`_`bytes`_`for`_`key ```python def hex_to_bytes_for_key(data: str) -> bytes ``` Convert hex string to bytes with error handling. ================================================ FILE: docs/api/crypto/ledger_apis.md ================================================ # aea.crypto.ledger`_`apis Module wrapping all the public and private keys cryptography. ## LedgerApis Objects ```python class LedgerApis() ``` Store all the ledger apis we initialise. #### has`_`ledger ```python @staticmethod def has_ledger(identifier: str) -> bool ``` Check if it has the api. #### get`_`api ```python @classmethod def get_api(cls, identifier: str) -> LedgerApi ``` Get the ledger API. #### get`_`balance ```python @classmethod def get_balance(cls, identifier: str, address: str) -> Optional[int] ``` Get the token balance. **Arguments**: - `identifier`: the identifier of the ledger - `address`: the address to check for **Returns**: the token balance #### get`_`transfer`_`transaction ```python @classmethod def get_transfer_transaction(cls, identifier: str, sender_address: str, destination_address: str, amount: int, tx_fee: int, tx_nonce: str, **kwargs: Any) -> Optional[Any] ``` Get a transaction to transfer from self to destination. **Arguments**: - `identifier`: the identifier of the ledger - `sender_address`: the address of the sender - `destination_address`: the address of the receiver - `amount`: the amount - `tx_nonce`: verifies the authenticity of the tx - `tx_fee`: the tx fee - `kwargs`: the keyword arguments. **Returns**: tx #### send`_`signed`_`transaction ```python @classmethod def send_signed_transaction(cls, identifier: str, tx_signed: Any) -> Optional[str] ``` Send a signed transaction and wait for confirmation. **Arguments**: - `identifier`: the identifier of the ledger - `tx_signed`: the signed transaction **Returns**: the tx_digest, if present #### get`_`transaction`_`receipt ```python @classmethod def get_transaction_receipt(cls, identifier: str, tx_digest: str) -> Optional[Any] ``` Get the transaction receipt for a transaction digest. **Arguments**: - `identifier`: the identifier of the ledger - `tx_digest`: the digest associated to the transaction. **Returns**: the tx receipt, if present #### get`_`transaction ```python @classmethod def get_transaction(cls, identifier: str, tx_digest: str) -> Optional[Any] ``` Get the transaction for a transaction digest. **Arguments**: - `identifier`: the identifier of the ledger - `tx_digest`: the digest associated to the transaction. **Returns**: the tx, if present #### get`_`contract`_`address ```python @staticmethod def get_contract_address(identifier: str, tx_receipt: Any) -> Optional[Address] ``` Get the contract address from a transaction receipt. **Arguments**: - `identifier`: the identifier of the ledger - `tx_receipt`: the transaction receipt **Returns**: the contract address if successful #### is`_`transaction`_`settled ```python @staticmethod def is_transaction_settled(identifier: str, tx_receipt: Any) -> bool ``` Check whether the transaction is settled and correct. **Arguments**: - `identifier`: the identifier of the ledger - `tx_receipt`: the transaction digest **Returns**: True if correctly settled, False otherwise #### is`_`transaction`_`valid ```python @staticmethod def is_transaction_valid(identifier: str, tx: Any, seller: Address, client: Address, tx_nonce: str, amount: int) -> bool ``` Check whether the transaction is valid. **Arguments**: - `identifier`: Ledger identifier - `tx`: the transaction - `seller`: the address of the seller. - `client`: the address of the client. - `tx_nonce`: the transaction nonce. - `amount`: the amount we expect to get from the transaction. **Returns**: True if is valid , False otherwise #### generate`_`tx`_`nonce ```python @staticmethod def generate_tx_nonce(identifier: str, seller: Address, client: Address) -> str ``` Generate a random str message. **Arguments**: - `identifier`: ledger identifier. - `seller`: the address of the seller. - `client`: the address of the client. **Returns**: return the hash in hex. #### recover`_`message ```python @staticmethod def recover_message(identifier: str, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[Address, ...] ``` Recover the addresses from the hash. **Arguments**: - `identifier`: ledger identifier. - `message`: the message we expect - `signature`: the transaction signature - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered addresses #### get`_`hash ```python @staticmethod def get_hash(identifier: str, message: bytes) -> str ``` Get the hash of a message. **Arguments**: - `identifier`: ledger identifier. - `message`: the message to be hashed. **Returns**: the hash of the message. #### is`_`valid`_`address ```python @staticmethod def is_valid_address(identifier: str, address: Address) -> bool ``` Check if the address is valid. **Arguments**: - `identifier`: ledger identifier. - `address`: the address to validate. **Returns**: whether it is a valid address or not. ================================================ FILE: docs/api/crypto/plugin.md ================================================ # aea.crypto.plugin Implementation of plug-in mechanism for cryptos. ## Plugin Objects ```python class Plugin() ``` Class that implements an AEA plugin. #### `__`init`__` ```python def __init__(group: str, entry_point: EntryPoint) ``` Initialize the plugin. **Arguments**: - `group`: the group the plugin belongs to. - `entry_point`: the entrypoint. #### name ```python @property def name() -> str ``` Get the plugin identifier. #### group ```python @property def group() -> str ``` Get the group. #### attr ```python @property def attr() -> str ``` Get the class name. #### entry`_`point`_`path ```python @property def entry_point_path() -> str ``` Get the entry point path. #### load`_`all`_`plugins ```python def load_all_plugins(is_raising_exception: bool = True) -> None ``` Load all plugins. ================================================ FILE: docs/api/crypto/registries/base.md ================================================ # aea.crypto.registries.base This module implements the base registry. ## ItemId Objects ```python class ItemId(RegexConstrainedString) ``` The identifier of an item class. #### name ```python @property def name() -> str ``` Get the id name. ## EntryPoint Objects ```python class EntryPoint(Generic[ItemType], RegexConstrainedString) ``` The entry point for a resource. The regular expression matches the strings in the following format: path.to.module:className #### `__`init`__` ```python def __init__(seq: Union["EntryPoint", str]) -> None ``` Initialize the entrypoint. #### import`_`path ```python @property def import_path() -> str ``` Get the import path. #### class`_`name ```python @property def class_name() -> str ``` Get the class name. #### load ```python def load() -> Type[ItemType] ``` Load the item object. **Returns**: the crypto object, loaded following the spec. ## ItemSpec Objects ```python class ItemSpec(Generic[ItemType]) ``` A specification for a particular instance of an object. #### `__`init`__` ```python def __init__(id_: ItemId, entry_point: EntryPoint[ItemType], class_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Dict) -> None ``` Initialize an item specification. **Arguments**: - `id_`: the id associated to this specification - `entry_point`: The Python entry_point of the environment class (e.g. module.name:Class). - `class_kwargs`: keyword arguments to be attached on the class as class variables. - `kwargs`: other custom keyword arguments. #### make ```python def make(**kwargs: Any) -> ItemType ``` Instantiate an instance of the item object with appropriate arguments. **Arguments**: - `kwargs`: the key word arguments **Returns**: an item #### get`_`class ```python def get_class() -> Type[ItemType] ``` Get the class of the item with class variables instantiated. **Returns**: an item class ## Registry Objects ```python class Registry(Generic[ItemType]) ``` Registry for generic classes. #### `__`init`__` ```python def __init__() -> None ``` Initialize the registry. #### supported`_`ids ```python @property def supported_ids() -> Set[str] ``` Get the supported item ids. #### register ```python def register(id_: Union[ItemId, str], entry_point: Union[EntryPoint[ItemType], str], class_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any) -> None ``` Register an item type. **Arguments**: - `id_`: the identifier for the crypto type. - `entry_point`: the entry point to load the crypto object. - `class_kwargs`: keyword arguments to be attached on the class as class variables. - `kwargs`: arguments to provide to the crypto class. #### make ```python def make(id_: Union[ItemId, str], module: Optional[str] = None, **kwargs: Any) -> ItemType ``` Create an instance of the associated type item id. **Arguments**: - `id_`: the id of the item class. Make sure it has been registered earlier before calling this function. - `module`: dotted path to a module. whether a module should be loaded before creating the object. this argument is useful when the item might not be registered beforehand, and loading the specified module will make the registration. E.g. suppose the call to 'register' for a custom object is located in some_package/__init__.py. By providing module="some_package", the call to 'register' in such module gets triggered and the make can then find the identifier. - `kwargs`: keyword arguments to be forwarded to the object. **Returns**: the new item instance. #### make`_`cls ```python def make_cls(id_: Union[ItemId, str], module: Optional[str] = None) -> Type[ItemType] ``` Load a class of the associated type item id. **Arguments**: - `id_`: the id of the item class. Make sure it has been registered earlier before calling this function. - `module`: dotted path to a module. whether a module should be loaded before creating the object. this argument is useful when the item might not be registered beforehand, and loading the specified module will make the registration. E.g. suppose the call to 'register' for a custom object is located in some_package/__init__.py. By providing module="some_package", the call to 'register' in such module gets triggered and the make can then find the identifier. **Returns**: the new item class. #### has`_`spec ```python def has_spec(item_id: ItemId) -> bool ``` Check whether there exist a spec associated with an item id. **Arguments**: - `item_id`: the item identifier. **Returns**: True if it is registered, False otherwise. ================================================ FILE: docs/api/crypto/wallet.md ================================================ # aea.crypto.wallet Module wrapping all the public and private keys cryptography. ## CryptoStore Objects ```python class CryptoStore() ``` Utility class to store and retrieve crypto objects. #### `__`init`__` ```python def __init__(crypto_id_to_path: Optional[Dict[str, Optional[str]]] = None, password: Optional[str] = None) -> None ``` Initialize the crypto store. **Arguments**: - `crypto_id_to_path`: dictionary from crypto id to an (optional) path to the private key. - `password`: the password to encrypt/decrypt the private key. #### public`_`keys ```python @property def public_keys() -> Dict[str, str] ``` Get the public_key dictionary. #### crypto`_`objects ```python @property def crypto_objects() -> Dict[str, Crypto] ``` Get the crypto objects (key pair). #### addresses ```python @property def addresses() -> Dict[str, str] ``` Get the crypto addresses. #### private`_`keys ```python @property def private_keys() -> Dict[str, str] ``` Get the crypto addresses. ## Wallet Objects ```python class Wallet() ``` Container for crypto objects. The cryptos are separated into two categories: - main cryptos: used by the AEA for the economic side (i.e. signing transaction) - connection cryptos: exposed to the connection objects for encrypted communication. #### `__`init`__` ```python def __init__( private_key_paths: Dict[str, Optional[str]], connection_private_key_paths: Optional[Dict[str, Optional[str]]] = None, password: Optional[str] = None) ``` Instantiate a wallet object. **Arguments**: - `private_key_paths`: the private key paths - `connection_private_key_paths`: the private key paths for the connections. - `password`: the password to encrypt/decrypt the private key. #### public`_`keys ```python @property def public_keys() -> Dict[str, str] ``` Get the public_key dictionary. #### crypto`_`objects ```python @property def crypto_objects() -> Dict[str, Crypto] ``` Get the crypto objects (key pair). #### addresses ```python @property def addresses() -> Dict[str, str] ``` Get the crypto addresses. #### private`_`keys ```python @property def private_keys() -> Dict[str, str] ``` Get the crypto addresses. #### main`_`cryptos ```python @property def main_cryptos() -> CryptoStore ``` Get the main crypto store. #### connection`_`cryptos ```python @property def connection_cryptos() -> CryptoStore ``` Get the connection crypto store. #### sign`_`message ```python def sign_message(crypto_id: str, message: bytes, is_deprecated_mode: bool = False) -> Optional[str] ``` Sign a message. **Arguments**: - `crypto_id`: the id of the crypto - `message`: the message to be signed - `is_deprecated_mode`: what signing mode to use **Returns**: the signature of the message #### sign`_`transaction ```python def sign_transaction(crypto_id: str, transaction: Any) -> Optional[JSONLike] ``` Sign a tx. **Arguments**: - `crypto_id`: the id of the crypto - `transaction`: the transaction to be signed **Returns**: the signed tx ================================================ FILE: docs/api/decision_maker/base.md ================================================ # aea.decision`_`maker.base This module contains the decision maker class. ## OwnershipState Objects ```python class OwnershipState(ABC) ``` Represent the ownership state of an agent (can proxy a ledger). #### set ```python @abstractmethod def set(**kwargs: Any) -> None ``` Set values on the ownership state. **Arguments**: - `kwargs`: the relevant keyword arguments #### apply`_`delta ```python @abstractmethod def apply_delta(**kwargs: Any) -> None ``` Apply a state update to the ownership state. This method is used to apply a raw state update without a transaction. **Arguments**: - `kwargs`: the relevant keyword arguments #### is`_`initialized ```python @property @abstractmethod def is_initialized() -> bool ``` Get the initialization status. #### is`_`affordable`_`transaction ```python @abstractmethod def is_affordable_transaction(terms: Terms) -> bool ``` Check if the transaction is affordable (and consistent). **Arguments**: - `terms`: the transaction terms **Returns**: True if the transaction is legal wrt the current state, false otherwise. #### apply`_`transactions ```python @abstractmethod def apply_transactions(list_of_terms: List[Terms]) -> "OwnershipState" ``` Apply a list of transactions to (a copy of) the current state. **Arguments**: - `list_of_terms`: the sequence of transaction terms. **Returns**: the final state. #### `__`copy`__` ```python @abstractmethod def __copy__() -> "OwnershipState" ``` Copy the object. ## Preferences Objects ```python class Preferences(ABC) ``` Class to represent the preferences. #### set ```python @abstractmethod def set(**kwargs: Any) -> None ``` Set values on the preferences. **Arguments**: - `kwargs`: the relevant key word arguments #### is`_`initialized ```python @property @abstractmethod def is_initialized() -> bool ``` Get the initialization status. Returns True if exchange_params_by_currency_id and utility_params_by_good_id are not None. #### marginal`_`utility ```python @abstractmethod def marginal_utility(ownership_state: OwnershipState, **kwargs: Any) -> float ``` Compute the marginal utility. **Arguments**: - `ownership_state`: the ownership state against which to compute the marginal utility. - `kwargs`: optional keyword arguments **Returns**: the marginal utility score #### utility`_`diff`_`from`_`transaction ```python @abstractmethod def utility_diff_from_transaction(ownership_state: OwnershipState, terms: Terms) -> float ``` Simulate a transaction and get the resulting utility difference (taking into account the fee). **Arguments**: - `ownership_state`: the ownership state against which to apply the transaction. - `terms`: the transaction terms. **Returns**: the score. #### `__`copy`__` ```python @abstractmethod def __copy__() -> "Preferences" ``` Copy the object. ## ProtectedQueue Objects ```python class ProtectedQueue(Queue) ``` A wrapper of a queue to protect which object can read from it. #### `__`init`__` ```python def __init__(access_code: str) -> None ``` Initialize the protected queue. **Arguments**: - `access_code`: the access code to read from the queue #### put ```python def put(internal_message: Optional[Message], block: bool = True, timeout: Optional[float] = None) -> None ``` Put an internal message on the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Full exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise the Full exception (timeout is ignored in that case). **Arguments**: - `internal_message`: the internal message to put on the queue - `block`: whether to block or not - `timeout`: timeout on block **Raises**: - `None`: ValueError, if the item is not an internal message #### put`_`nowait ```python def put_nowait(internal_message: Optional[Message]) -> None ``` Put an internal message on the queue. Equivalent to put(item, False). **Arguments**: - `internal_message`: the internal message to put on the queue **Raises**: - `None`: ValueError, if the item is not an internal message #### get ```python def get(block: bool = True, timeout: Optional[float] = None) -> None ``` Inaccessible get method. **Arguments**: - `block`: whether to block or not - `timeout`: timeout on block **Raises**: - `None`: ValueError, access not permitted. #### get`_`nowait ```python def get_nowait() -> None ``` Inaccessible get_nowait method. **Raises**: - `None`: ValueError, access not permitted. #### protected`_`get ```python def protected_get(access_code: str, block: bool = True, timeout: Optional[float] = None) -> Optional[Message] ``` Access protected get method. **Arguments**: - `access_code`: the access code - `block`: If optional args block is true and timeout is None (the default), block if necessary until an item is available. - `timeout`: If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. **Raises**: - `None`: ValueError, if caller is not permitted **Returns**: internal message ## DecisionMakerHandler Objects ```python class DecisionMakerHandler(WithLogger, ABC) ``` This class implements the decision maker. #### `__`init`__` ```python def __init__(identity: Identity, wallet: Wallet, config: Dict[str, Any], **kwargs: Any) -> None ``` Initialize the decision maker handler. **Arguments**: - `identity`: the identity - `wallet`: the wallet - `config`: the user defined configuration of the handler - `kwargs`: the key word arguments #### agent`_`name ```python @property def agent_name() -> str ``` Get the agent name. #### identity ```python @property def identity() -> Identity ``` Get identity of the agent. #### wallet ```python @property def wallet() -> Wallet ``` Get wallet of the agent. #### config ```python @property def config() -> Dict[str, Any] ``` Get user defined configuration #### context ```python @property def context() -> SimpleNamespace ``` Get the context. #### message`_`out`_`queue ```python @property def message_out_queue() -> AsyncFriendlyQueue ``` Get (out) queue. #### handle ```python @abstractmethod def handle(message: Message) -> None ``` Handle an internal message from the skills. **Arguments**: - `message`: the internal message ## DecisionMaker Objects ```python class DecisionMaker(WithLogger) ``` This class implements the decision maker. #### `__`init`__` ```python def __init__(decision_maker_handler: DecisionMakerHandler) -> None ``` Initialize the decision maker. **Arguments**: - `decision_maker_handler`: the decision maker handler #### agent`_`name ```python @property def agent_name() -> str ``` Get the agent name. #### message`_`in`_`queue ```python @property def message_in_queue() -> ProtectedQueue ``` Get (in) queue. #### message`_`out`_`queue ```python @property def message_out_queue() -> AsyncFriendlyQueue ``` Get (out) queue. #### decision`_`maker`_`handler ```python @property def decision_maker_handler() -> DecisionMakerHandler ``` Get the decision maker handler. #### start ```python def start() -> None ``` Start the decision maker. #### stop ```python def stop() -> None ``` Stop the decision maker. #### execute ```python def execute() -> None ``` Execute the decision maker. Performs the following while not stopped: - gets internal messages from the in queue and calls handle() on them #### handle ```python def handle(message: Message) -> None ``` Handle an internal message from the skills. **Arguments**: - `message`: the internal message ================================================ FILE: docs/api/decision_maker/default.md ================================================ # aea.decision`_`maker.default This module contains the decision maker class. ## DecisionMakerHandler Objects ```python class DecisionMakerHandler(BaseDecisionMakerHandler) ``` This class implements the decision maker. ## SigningDialogues Objects ```python class SigningDialogues(BaseSigningDialogues) ``` This class keeps track of all oef_search dialogues. #### `__`init`__` ```python def __init__(self_address: Address, **kwargs: Any) -> None ``` Initialize dialogues. **Arguments**: - `self_address`: the address of the entity for whom dialogues are maintained - `kwargs`: the keyword arguments #### `__`init`__` ```python def __init__(identity: Identity, wallet: Wallet, config: Dict[str, Any]) -> None ``` Initialize the decision maker. **Arguments**: - `identity`: the identity - `wallet`: the wallet - `config`: the user defined configuration of the handler #### handle ```python def handle(message: Message) -> None ``` Handle an internal message from the skills. **Arguments**: - `message`: the internal message ================================================ FILE: docs/api/decision_maker/gop.md ================================================ # aea.decision`_`maker.gop This module contains the decision maker class. ## GoalPursuitReadiness Objects ```python class GoalPursuitReadiness() ``` The goal pursuit readiness. ## Status Objects ```python class Status(Enum) ``` The enum of the readiness status. In particular, it can be one of the following: - Status.READY: when the agent is ready to pursuit its goal - Status.NOT_READY: when the agent is not ready to pursuit its goal #### `__`init`__` ```python def __init__() -> None ``` Instantiate the goal pursuit readiness. #### is`_`ready ```python @property def is_ready() -> bool ``` Get the readiness. #### update ```python def update(new_status: Status) -> None ``` Update the goal pursuit readiness. **Arguments**: - `new_status`: the new status ## OwnershipState Objects ```python class OwnershipState(BaseOwnershipState) ``` Represent the ownership state of an agent (can proxy a ledger). #### `__`init`__` ```python def __init__() -> None ``` Instantiate an ownership state object. #### set ```python def set(amount_by_currency_id: CurrencyHoldings = None, quantities_by_good_id: GoodHoldings = None, **kwargs: Any) -> None ``` Set values on the ownership state. **Arguments**: - `amount_by_currency_id`: the currency endowment of the agent in this state. - `quantities_by_good_id`: the good endowment of the agent in this state. - `kwargs`: the keyword arguments. #### apply`_`delta ```python def apply_delta(delta_amount_by_currency_id: Dict[str, int] = None, delta_quantities_by_good_id: Dict[str, int] = None, **kwargs: Any) -> None ``` Apply a state update to the ownership state. This method is used to apply a raw state update without a transaction. **Arguments**: - `delta_amount_by_currency_id`: the delta in the currency amounts - `delta_quantities_by_good_id`: the delta in the quantities by good - `kwargs`: the keyword arguments #### is`_`initialized ```python @property def is_initialized() -> bool ``` Get the initialization status. #### amount`_`by`_`currency`_`id ```python @property def amount_by_currency_id() -> CurrencyHoldings ``` Get currency holdings in this state. #### quantities`_`by`_`good`_`id ```python @property def quantities_by_good_id() -> GoodHoldings ``` Get good holdings in this state. #### is`_`affordable`_`transaction ```python def is_affordable_transaction(terms: Terms) -> bool ``` Check if the transaction is affordable (and consistent). E.g. check that the agent state has enough money if it is a buyer or enough holdings if it is a seller. Note, the agent is the sender of the transaction message by design. **Arguments**: - `terms`: the transaction terms **Returns**: True if the transaction is legal wrt the current state, false otherwise. #### is`_`affordable ```python def is_affordable(terms: Terms) -> bool ``` Check if the tx is affordable. **Arguments**: - `terms`: the transaction terms **Returns**: whether the transaction is affordable or not #### update ```python def update(terms: Terms) -> None ``` Update the agent state from a transaction. **Arguments**: - `terms`: the transaction terms #### apply`_`transactions ```python def apply_transactions(list_of_terms: List[Terms]) -> "OwnershipState" ``` Apply a list of transactions to (a copy of) the current state. **Arguments**: - `list_of_terms`: the sequence of transaction terms. **Returns**: the final state. #### `__`copy`__` ```python def __copy__() -> "OwnershipState" ``` Copy the object. ## Preferences Objects ```python class Preferences(BasePreferences) ``` Class to represent the preferences. #### `__`init`__` ```python def __init__() -> None ``` Instantiate an agent preference object. #### set ```python def set(exchange_params_by_currency_id: ExchangeParams = None, utility_params_by_good_id: UtilityParams = None, **kwargs: Any) -> None ``` Set values on the preferences. **Arguments**: - `exchange_params_by_currency_id`: the exchange params. - `utility_params_by_good_id`: the utility params for every asset. - `kwargs`: the keyword arguments. #### is`_`initialized ```python @property def is_initialized() -> bool ``` Get the initialization status. **Returns**: True if exchange_params_by_currency_id and utility_params_by_good_id are not None. #### exchange`_`params`_`by`_`currency`_`id ```python @property def exchange_params_by_currency_id() -> ExchangeParams ``` Get exchange parameter for each currency. #### utility`_`params`_`by`_`good`_`id ```python @property def utility_params_by_good_id() -> UtilityParams ``` Get utility parameter for each good. #### logarithmic`_`utility ```python def logarithmic_utility(quantities_by_good_id: GoodHoldings) -> float ``` Compute agent's utility given her utility function params and a good bundle. **Arguments**: - `quantities_by_good_id`: the good holdings (dictionary) with the identifier (key) and quantity (value) for each good **Returns**: utility value #### linear`_`utility ```python def linear_utility(amount_by_currency_id: CurrencyHoldings) -> float ``` Compute agent's utility given her utility function params and a currency bundle. **Arguments**: - `amount_by_currency_id`: the currency holdings (dictionary) with the identifier (key) and quantity (value) for each currency **Returns**: utility value #### utility ```python def utility(quantities_by_good_id: GoodHoldings, amount_by_currency_id: CurrencyHoldings) -> float ``` Compute the utility given the good and currency holdings. **Arguments**: - `quantities_by_good_id`: the good holdings - `amount_by_currency_id`: the currency holdings **Returns**: the utility value. #### marginal`_`utility ```python def marginal_utility( ownership_state: BaseOwnershipState, delta_quantities_by_good_id: Optional[GoodHoldings] = None, delta_amount_by_currency_id: Optional[CurrencyHoldings] = None, **kwargs: Any) -> float ``` Compute the marginal utility. **Arguments**: - `ownership_state`: the ownership state against which to compute the marginal utility. - `delta_quantities_by_good_id`: the change in good holdings - `delta_amount_by_currency_id`: the change in money holdings - `kwargs`: the keyword arguments **Returns**: the marginal utility score #### utility`_`diff`_`from`_`transaction ```python def utility_diff_from_transaction(ownership_state: BaseOwnershipState, terms: Terms) -> float ``` Simulate a transaction and get the resulting utility difference (taking into account the fee). **Arguments**: - `ownership_state`: the ownership state against which to apply the transaction. - `terms`: the transaction terms. **Returns**: the score. #### is`_`utility`_`enhancing ```python def is_utility_enhancing(ownership_state: BaseOwnershipState, terms: Terms) -> bool ``` Check if the tx is utility enhancing. **Arguments**: - `ownership_state`: the ownership state against which to apply the transaction. - `terms`: the transaction terms **Returns**: whether the transaction is utility enhancing or not #### `__`copy`__` ```python def __copy__() -> "Preferences" ``` Copy the object. ## DecisionMakerHandler Objects ```python class DecisionMakerHandler(BaseDecisionMakerHandler) ``` This class implements the decision maker. ## SigningDialogues Objects ```python class SigningDialogues(BaseSigningDialogues) ``` This class keeps track of all oef_search dialogues. #### `__`init`__` ```python def __init__(self_address: Address, **kwargs: Any) -> None ``` Initialize dialogues. **Arguments**: - `self_address`: the address of the entity for whom dialogues are maintained - `kwargs`: the keyword arguments ## StateUpdateDialogues Objects ```python class StateUpdateDialogues(BaseStateUpdateDialogues) ``` This class keeps track of all oef_search dialogues. #### `__`init`__` ```python def __init__(self_address: Address, **kwargs: Any) -> None ``` Initialize dialogues. **Arguments**: - `self_address`: the address of the entity for whom dialogues are maintained - `kwargs`: the keyword arguments #### `__`init`__` ```python def __init__(identity: Identity, wallet: Wallet, config: Dict[str, Any]) -> None ``` Initialize the decision maker. **Arguments**: - `identity`: the identity - `wallet`: the wallet - `config`: the user defined configuration of the handler #### handle ```python def handle(message: Message) -> None ``` Handle an internal message from the skills. **Arguments**: - `message`: the internal message ================================================ FILE: docs/api/error_handler/base.md ================================================ # aea.error`_`handler.base This module contains the abstract error handler class. ## AbstractErrorHandler Objects ```python class AbstractErrorHandler(ABC) ``` Error handler class for handling problematic envelopes. #### `__`init`__` ```python def __init__(**kwargs: Any) ``` Instantiate error handler. #### config ```python @property def config() -> Dict[str, Any] ``` Get handler config. #### send`_`unsupported`_`protocol ```python @abstractmethod def send_unsupported_protocol(envelope: Envelope, logger: Logger) -> None ``` Handle the received envelope in case the protocol is not supported. **Arguments**: - `envelope`: the envelope - `logger`: the logger **Returns**: None #### send`_`decoding`_`error ```python @abstractmethod def send_decoding_error(envelope: Envelope, exception: Exception, logger: Logger) -> None ``` Handle a decoding error. **Arguments**: - `envelope`: the envelope - `exception`: the exception raised during decoding - `logger`: the logger **Returns**: None #### send`_`no`_`active`_`handler ```python @abstractmethod def send_no_active_handler(envelope: Envelope, reason: str, logger: Logger) -> None ``` Handle the received envelope in case the handler is not supported. **Arguments**: - `envelope`: the envelope - `reason`: the reason for the failure - `logger`: the logger **Returns**: None ================================================ FILE: docs/api/error_handler/default.md ================================================ # aea.error`_`handler.default This module contains the default error handler class. ## ErrorHandler Objects ```python class ErrorHandler(AbstractErrorHandler) ``` Error handler class for handling problematic envelopes. #### `__`init`__` ```python def __init__(**kwargs: Any) ``` Instantiate error handler. #### send`_`unsupported`_`protocol ```python def send_unsupported_protocol(envelope: Envelope, logger: Logger) -> None ``` Handle the received envelope in case the protocol is not supported. **Arguments**: - `envelope`: the envelope - `logger`: the logger #### send`_`decoding`_`error ```python def send_decoding_error(envelope: Envelope, exception: Exception, logger: Logger) -> None ``` Handle a decoding error. **Arguments**: - `envelope`: the envelope - `exception`: the exception raised during decoding - `logger`: the logger #### send`_`no`_`active`_`handler ```python def send_no_active_handler(envelope: Envelope, reason: str, logger: Logger) -> None ``` Handle the received envelope in case the handler is not supported. **Arguments**: - `envelope`: the envelope - `reason`: the reason for the failure - `logger`: the logger ================================================ FILE: docs/api/exceptions.md ================================================ # aea.exceptions Exceptions for the AEA package. ## AEAException Objects ```python class AEAException(Exception) ``` User-defined exception for the AEA framework. ## AEAPackageLoadingError Objects ```python class AEAPackageLoadingError(AEAException) ``` Class for exceptions that are raised for loading errors of AEA packages. ## AEASetupError Objects ```python class AEASetupError(AEAException) ``` Class for exceptions that are raised for setup errors of AEA packages. ## AEATeardownError Objects ```python class AEATeardownError(AEAException) ``` Class for exceptions that are raised for teardown errors of AEA packages. ## AEAActException Objects ```python class AEAActException(AEAException) ``` Class for exceptions that are raised for act errors of AEA packages. ## AEAHandleException Objects ```python class AEAHandleException(AEAException) ``` Class for exceptions that are raised for handler errors of AEA packages. ## AEAInstantiationException Objects ```python class AEAInstantiationException(AEAException) ``` Class for exceptions that are raised for instantiation errors of AEA packages. ## AEAPluginError Objects ```python class AEAPluginError(AEAException) ``` Class for exceptions that are raised for wrong plugin setup of the working set. ## AEAEnforceError Objects ```python class AEAEnforceError(AEAException) ``` Class for enforcement errors. ## AEAValidationError Objects ```python class AEAValidationError(AEAException) ``` Class for validation errors of an AEA. ## AEAComponentLoadException Objects ```python class AEAComponentLoadException(AEAException) ``` Class for component loading errors of an AEA. ## AEAWalletNoAddressException Objects ```python class AEAWalletNoAddressException(AEAException) ``` Class for attempts to instantiate a wallet without addresses. ## `_`StopRuntime Objects ```python class _StopRuntime(Exception) ``` Exception to stop runtime. For internal usage only! Used to perform asyncio call from sync callbacks. #### `__`init`__` ```python def __init__(reraise: Optional[Exception] = None) -> None ``` Init _StopRuntime exception. **Arguments**: - `reraise`: exception to reraise. #### enforce ```python def enforce(is_valid_condition: bool, exception_text: str, exception_class: Type[Exception] = AEAEnforceError) -> None ``` Evaluate a condition and raise an exception with the provided text if it is not satisfied. **Arguments**: - `is_valid_condition`: the valid condition - `exception_text`: the exception to be raised - `exception_class`: the class of exception #### parse`_`exception ```python def parse_exception(exception: Exception, limit: int = -1) -> str ``` Parse an exception to get the relevant lines. **Arguments**: - `exception`: the exception to be parsed - `limit`: the limit **Returns**: exception as string ================================================ FILE: docs/api/helpers/acn/agent_record.md ================================================ # aea.helpers.acn.agent`_`record This module contains types and helpers for ACN Proof-of-Representation. ## AgentRecord Objects ```python class AgentRecord() ``` Agent Proof-of-Representation to representative. #### `__`init`__` ```python def __init__(address: str, representative_public_key: str, identifier: SimpleIdOrStr, ledger_id: SimpleIdOrStr, not_before: str, not_after: str, message_format: str, signature: str) -> None ``` Initialize the AgentRecord **Arguments**: - `address`: agent address - `representative_public_key`: representative's public key - `identifier`: certificate identifier. - `ledger_id`: ledger identifier the request is referring to. - `not_before`: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. - `not_after`: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. - `message_format`: message format used for signing - `signature`: proof-of-representation of this AgentRecord #### address ```python @property def address() -> str ``` Get agent address #### public`_`key ```python @property def public_key() -> str ``` Get agent public key #### representative`_`public`_`key ```python @property def representative_public_key() -> str ``` Get agent representative's public key #### signature ```python @property def signature() -> str ``` Get record signature #### message ```python @property def message() -> bytes ``` Get the message. #### identifier ```python @property def identifier() -> SimpleIdOrStr ``` Get the identifier. #### ledger`_`id ```python @property def ledger_id() -> SimpleIdOrStr ``` Get ledger id. #### not`_`before ```python @property def not_before() -> str ``` Get the not_before field. #### not`_`after ```python @property def not_after() -> str ``` Get the not_after field. #### message`_`format ```python @property def message_format() -> str ``` Get the message format. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. #### from`_`cert`_`request ```python @classmethod def from_cert_request(cls, cert_request: CertRequest, address: str, representative_public_key: str, data_dir: Optional[PathLike] = None) -> "AgentRecord" ``` Get agent record from cert request. ================================================ FILE: docs/api/helpers/acn/uri.md ================================================ # aea.helpers.acn.uri This module contains types and helpers for libp2p connections Uris. ## Uri Objects ```python class Uri() ``` Holds a node address in format "host:port". #### `__`init`__` ```python def __init__(uri: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None) -> None ``` Initialise Uri. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. #### `__`repr`__` ```python def __repr__() -> str ``` Get object representation. #### host ```python @property def host() -> str ``` Get host. #### port ```python @property def port() -> int ``` Get port. ================================================ FILE: docs/api/helpers/async_friendly_queue.md ================================================ # aea.helpers.async`_`friendly`_`queue This module contains the implementation of AsyncFriendlyQueue. ## AsyncFriendlyQueue Objects ```python class AsyncFriendlyQueue(queue.Queue) ``` queue.Queue with async_get method. #### `__`init`__` ```python def __init__(*args: Any, **kwargs: Any) -> None ``` Init queue. #### put ```python def put(item: Any, *args: Any, **kwargs: Any) -> None ``` Put an item into the queue. **Arguments**: - `item`: item to put in the queue - `args`: similar to queue.Queue.put - `kwargs`: similar to queue.Queue.put #### get ```python def get(*args: Any, **kwargs: Any) -> Any ``` Get an item into the queue. **Arguments**: - `args`: similar to queue.Queue.get - `kwargs`: similar to queue.Queue.get **Returns**: similar to queue.Queue.get #### async`_`wait ```python async def async_wait() -> None ``` Wait an item appears in the queue. **Returns**: None #### async`_`get ```python async def async_get() -> Any ``` Wait and get an item from the queue. **Returns**: item from queue ================================================ FILE: docs/api/helpers/async_utils.md ================================================ # aea.helpers.async`_`utils This module contains the misc utils for async code. #### ensure`_`list ```python def ensure_list(value: Any) -> List ``` Return [value] or list(value) if value is a sequence. ## AsyncState Objects ```python class AsyncState() ``` Awaitable state. #### `__`init`__` ```python def __init__(initial_state: Any = None, states_enum: Optional[Container[Any]] = None) -> None ``` Init async state. **Arguments**: - `initial_state`: state to set on start. - `states_enum`: container of valid states if not provided state not checked on set. #### set ```python def set(state: Any) -> None ``` Set state. #### add`_`callback ```python def add_callback(callback_fn: Callable[[Any], None]) -> None ``` Add callback to track state changes. **Arguments**: - `callback_fn`: callable object to be called on state changed. #### get ```python def get() -> Any ``` Get state. #### wait ```python async def wait(state_or_states: Union[Any, Sequence[Any]]) -> Tuple[Any, Any] ``` Wait state to be set. **Arguments**: - `state_or_states`: state or list of states. **Returns**: tuple of previous state and new state. #### transit ```python @contextmanager def transit(initial: Any = not_set, success: Any = not_set, fail: Any = not_set) -> Generator ``` Change state context according to success or not. **Arguments**: - `initial`: set state on context enter, not_set by default - `success`: set state on context block done, not_set by default - `fail`: set state on context block raises exception, not_set by default **Returns**: generator ## PeriodicCaller Objects ```python class PeriodicCaller() ``` Schedule a periodic call of callable using event loop. Used for periodic function run using asyncio. #### `__`init`__` ```python def __init__(callback: Callable, period: float, start_at: Optional[datetime.datetime] = None, exception_callback: Optional[Callable[[Callable, Exception], None]] = None, loop: Optional[AbstractEventLoop] = None) -> None ``` Init periodic caller. **Arguments**: - `callback`: function to call periodically - `period`: period in seconds. - `start_at`: optional first call datetime - `exception_callback`: optional handler to call on exception raised. - `loop`: optional asyncio event loop #### start ```python def start() -> None ``` Activate period calls. #### stop ```python def stop() -> None ``` Remove from schedule. ## AnotherThreadTask Objects ```python class AnotherThreadTask() ``` Schedule a task to run on the loop in another thread. Provides better cancel behaviour: on cancel it will wait till cancelled completely. #### `__`init`__` ```python def __init__(coro: Coroutine[Any, Any, Any], loop: AbstractEventLoop) -> None ``` Init the task. **Arguments**: - `coro`: coroutine to schedule - `loop`: an event loop to schedule on. #### result ```python def result(timeout: Optional[float] = None) -> Any ``` Wait for coroutine execution result. **Arguments**: - `timeout`: optional timeout to wait in seconds. **Returns**: result #### cancel ```python def cancel() -> None ``` Cancel coroutine task execution in a target loop. #### done ```python def done() -> bool ``` Check task is done. ## ThreadedAsyncRunner Objects ```python class ThreadedAsyncRunner(Thread) ``` Util to run thread with event loop and execute coroutines inside. #### `__`init`__` ```python def __init__(loop: Optional[AbstractEventLoop] = None) -> None ``` Init threaded runner. **Arguments**: - `loop`: optional event loop. is it's running loop, threaded runner will use it. #### start ```python def start() -> None ``` Start event loop in dedicated thread. #### run ```python def run() -> None ``` Run code inside thread. #### call ```python def call(coro: Coroutine[Any, Any, Any]) -> Any ``` Run a coroutine inside the event loop. **Arguments**: - `coro`: a coroutine to run. **Returns**: task #### stop ```python def stop() -> None ``` Stop event loop in thread. ## Runnable Objects ```python class Runnable(ABC) ``` Abstract Runnable class. Use to run async task in same event loop or in dedicated thread. Provides: start, stop sync methods to start and stop task Use wait_completed to await task was completed. #### `__`init`__` ```python def __init__(loop: asyncio.AbstractEventLoop = None, threaded: bool = False) -> None ``` Init runnable. **Arguments**: - `loop`: asyncio event loop to use. - `threaded`: bool. start in thread if True. #### start ```python def start() -> bool ``` Start runnable. **Returns**: bool started or not. #### is`_`running ```python @property def is_running() -> bool ``` Get running state. #### run ```python @abstractmethod async def run() -> Any ``` Implement run logic respectful to CancelError on termination. #### wait`_`completed ```python def wait_completed(sync: bool = False, timeout: float = None, force_result: bool = False) -> Union[Coroutine, Awaitable] ``` Wait runnable execution completed. **Arguments**: - `sync`: bool. blocking wait - `timeout`: float seconds - `force_result`: check result even it was waited. **Returns**: awaitable if sync is False, otherwise None #### stop ```python def stop(force: bool = False) -> None ``` Stop runnable. #### start`_`and`_`wait`_`completed ```python def start_and_wait_completed(*args: Any, **kwargs: Any) -> Union[Coroutine, Awaitable] ``` Alias for start and wait methods. ================================================ FILE: docs/api/helpers/base.md ================================================ # aea.helpers.base Miscellaneous helpers. #### locate ```python def locate(path: str) -> Any ``` Locate an object by name or dotted save_path, importing as necessary. #### load`_`module ```python def load_module(dotted_path: str, filepath: Path) -> types.ModuleType ``` Load a module. **Arguments**: - `dotted_path`: the dotted save_path of the package/module. - `filepath`: the file to the package/module. **Raises**: - `ValueError`: if the filepath provided is not a module. # noqa: DAR402 - `Exception`: if the execution of the module raises exception. # noqa: DAR402 **Returns**: module type #### load`_`env`_`file ```python def load_env_file(env_file: str) -> None ``` Load the content of the environment file into the process environment. **Arguments**: - `env_file`: save_path to the env file. #### sigint`_`crossplatform ```python def sigint_crossplatform(process: subprocess.Popen) -> None ``` Send a SIGINT, cross-platform. The reason is because the subprocess module doesn't have an API to send a SIGINT-like signal both on Posix and Windows with a single method. However, a subprocess.Popen class has the method 'send_signal' that gives more flexibility in this terms. **Arguments**: - `process`: the process to send the signal to. #### win`_`popen`_`kwargs ```python def win_popen_kwargs() -> dict ``` Return kwargs to start a process in windows with new process group. Help to handle ctrl c properly. Return empty dict if platform is not win32 **Returns**: windows popen kwargs #### send`_`control`_`c ```python def send_control_c(process: subprocess.Popen, kill_group: bool = False) -> None ``` Send ctrl-C cross-platform to terminate a subprocess. **Arguments**: - `process`: the process to send the signal to. - `kill_group`: whether or not to kill group ## RegexConstrainedString Objects ```python class RegexConstrainedString(UserString) ``` A string that is constrained by a regex. The default behaviour is to match anything. Subclass this class and change the 'REGEX' class attribute to implement a different behaviour. #### `__`init`__` ```python def __init__(seq: Union[UserString, str]) -> None ``` Initialize a regex constrained string. ## SimpleId Objects ```python class SimpleId(RegexConstrainedString) ``` A simple identifier. The allowed strings are all the strings that: - have at least length 1 - have at most length 128 - the first character must be between a-z,A-Z or underscore - the other characters must be either the above or digits. Examples of allowed strings: >>> SimpleId("an_identifier") 'an_identifier' Examples of not allowed strings: >>> SimpleId("0an_identifier") Traceback (most recent call last): ... ValueError: Value 0an_identifier does not match the regular expression re.compile('[a-zA-Z_][a-zA-Z0-9_]{0,127}') >>> SimpleId("an identifier") Traceback (most recent call last): ... ValueError: Value an identifier does not match the regular expression re.compile('[a-zA-Z_][a-zA-Z0-9_]{0,127}') >>> SimpleId("") Traceback (most recent call last): ... ValueError: Value does not match the regular expression re.compile('[a-zA-Z_][a-zA-Z0-9_]{0,127}') #### cd ```python @contextlib.contextmanager def cd(path: PathLike) -> Generator ``` Change working directory temporarily. #### get`_`logger`_`method ```python def get_logger_method(fn: Callable, logger_method: Union[str, Callable]) -> Callable ``` Get logger method for function. Get logger in `fn` definition module or creates logger is module.__name__. Or return logger_method if it's callable. **Arguments**: - `fn`: function to get logger for. - `logger_method`: logger name or callable. **Returns**: callable to write log with #### try`_`decorator ```python def try_decorator(error_message: str, default_return: Callable = None, logger_method: Any = "error") -> Callable ``` Run function, log and return default value on exception. Does not support async or coroutines! **Arguments**: - `error_message`: message template with one `{}` for exception - `default_return`: value to return on exception, by default None - `logger_method`: name of the logger method or callable to print logs **Returns**: the callable ## MaxRetriesError Objects ```python class MaxRetriesError(Exception) ``` Exception for retry decorator. #### retry`_`decorator ```python def retry_decorator(number_of_retries: int, error_message: str, delay: float = 0, logger_method: str = "error") -> Callable ``` Run function with several attempts. Does not support async or coroutines! **Arguments**: - `number_of_retries`: amount of attempts - `error_message`: message template with one `{}` for exception - `delay`: number of seconds to sleep between retries. default 0 - `logger_method`: name of the logger method or callable to print logs **Returns**: the callable #### exception`_`log`_`and`_`reraise ```python @contextlib.contextmanager def exception_log_and_reraise(log_method: Callable, message: str) -> Generator ``` Run code in context to log and re raise exception. **Arguments**: - `log_method`: function to print log - `message`: message template to add error text. **Returns**: the generator #### recursive`_`update ```python def recursive_update(to_update: Dict, new_values: Dict, allow_new_values: bool = False) -> None ``` Update a dictionary by replacing conflicts with the new values. It does side-effects to the first dictionary. >>> to_update = dict(a=1, b=2, subdict=dict(subfield1=1)) >>> new_values = dict(b=3, subdict=dict(subfield1=2)) >>> recursive_update(to_update, new_values) >>> to_update {'a': 1, 'b': 3, 'subdict': {'subfield1': 2}} **Arguments**: - `to_update`: the dictionary to update. - `new_values`: the dictionary of new values to replace. - `allow_new_values`: whether or not to allow new values. #### find`_`topological`_`order ```python def find_topological_order(adjacency_list: Dict[T, Set[T]]) -> List[T] ``` Compute the topological order of a graph (using Kahn's algorithm). **Arguments**: - `adjacency_list`: the adjacency list of the graph. **Raises**: - `ValueError`: if the graph contains a cycle. **Returns**: the topological order for the graph (as a sequence of nodes) #### reachable`_`nodes ```python def reachable_nodes(adjacency_list: Dict[T, Set[T]], starting_nodes: Set[T]) -> Dict[T, Set[T]] ``` Find the reachable subgraph induced by a set of starting nodes. **Arguments**: - `adjacency_list`: the adjacency list of the full graph. - `starting_nodes`: the starting nodes of the new graph. **Returns**: the adjacency list of the subgraph. ## cached`_`property Objects ```python class cached_property() ``` Cached property from python3.8 functools. #### `__`init`__` ```python def __init__(func: Callable) -> None ``` Init cached property. #### `__`set`_`name`__` ```python def __set_name__(_: Any, name: Any) -> None ``` Set name. #### `__`get`__` ```python def __get__(instance: Any, _: Optional[Any] = None) -> Any ``` Get instance. #### ensure`_`dir ```python def ensure_dir(dir_path: str) -> None ``` Check if dir_path is a directory or create it. #### dict`_`to`_`path`_`value ```python def dict_to_path_value( data: Mapping, path: Optional[List] = None) -> Iterable[Tuple[List[str], Any]] ``` Convert dict to sequence of terminal path build of keys and value. #### parse`_`datetime`_`from`_`str ```python def parse_datetime_from_str(date_string: str) -> datetime.datetime ``` Parse datetime from string. ## CertRequest Objects ```python class CertRequest() ``` Certificate request for proof of representation. #### `__`init`__` ```python def __init__(public_key: str, identifier: SimpleIdOrStr, ledger_id: SimpleIdOrStr, not_before: str, not_after: str, message_format: str, save_path: str) -> None ``` Initialize the certificate request. **Arguments**: - `public_key`: the public key, or the key id. - `identifier`: certificate identifier. - `ledger_id`: ledger identifier the request is referring to. - `not_before`: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. - `not_after`: specify the lower bound for certificate validity. If it is a string, it must follow the format: 'YYYY-MM-DD'. It will be interpreted as timezone UTC-0. - `message_format`: message format used for signing - `save_path`: the save_path where to save the certificate. #### public`_`key ```python @property def public_key() -> Optional[str] ``` Get the public key. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the ledger id. #### key`_`identifier ```python @property def key_identifier() -> Optional[str] ``` Get the key identifier. #### identifier ```python @property def identifier() -> str ``` Get the identifier. #### not`_`before`_`string ```python @property def not_before_string() -> str ``` Get the not_before field as string. #### not`_`after`_`string ```python @property def not_after_string() -> str ``` Get the not_after field as string. #### not`_`before ```python @property def not_before() -> datetime.datetime ``` Get the not_before field. #### not`_`after ```python @property def not_after() -> datetime.datetime ``` Get the not_after field. #### message`_`format ```python @property def message_format() -> str ``` Get the message format. #### save`_`path ```python @property def save_path() -> Path ``` Get the save path for the certificate. Note: if the path is *not* absolute, then the actual save path might depend on the context. **Returns**: the save path #### get`_`absolute`_`save`_`path ```python def get_absolute_save_path(path_prefix: Optional[PathLike] = None) -> Path ``` Get the absolute save path. If save_path is an absolute path, then the prefix is ignored. Otherwise, the path prefix is prepended. **Arguments**: - `path_prefix`: the (absolute) path to prepend to the save path. **Returns**: the actual save path. #### public`_`key`_`or`_`identifier ```python @property def public_key_or_identifier() -> str ``` Get the public key or identifier. #### get`_`message ```python def get_message(public_key: str) -> bytes ``` Get the message to sign. #### construct`_`message ```python @classmethod def construct_message(cls, public_key: str, identifier: SimpleIdOrStr, not_before_string: str, not_after_string: str, message_format: str) -> bytes ``` Construct message for singning. **Arguments**: - `public_key`: the public key - `identifier`: identifier to be signed - `not_before_string`: signature not valid before - `not_after_string`: signature not valid after - `message_format`: message format used for signing **Returns**: the message #### get`_`signature ```python def get_signature(path_prefix: Optional[PathLike] = None) -> str ``` Get signature from save_path. **Arguments**: - `path_prefix`: the path prefix to be prepended to save_path. Defaults to cwd. **Returns**: the signature. #### json ```python @property def json() -> Dict ``` Compute the JSON representation. #### from`_`json ```python @classmethod def from_json(cls, obj: Dict) -> "CertRequest" ``` Compute the JSON representation. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### compute`_`specifier`_`from`_`version ```python def compute_specifier_from_version(version: Version) -> str ``` Compute the specifier set from a version. version specifier is: >=major.minor.0, #### decorator`_`with`_`optional`_`params ```python def decorator_with_optional_params(decorator: Callable) -> Callable ``` Make a decorator usable either with or without parameters. In other words, if a decorator "mydecorator" is decorated with this decorator, It can be used both as: @mydecorator def myfunction(): ... or as: @mydecorator(arg1, kwarg1="value") def myfunction(): ... **Arguments**: - `decorator`: a decorator callable **Returns**: a decorator callable #### delete`_`directory`_`contents ```python def delete_directory_contents(directory: Path) -> None ``` Delete the content of a directory, without deleting it. #### prepend`_`if`_`not`_`absolute ```python def prepend_if_not_absolute(path: PathLike, prefix: PathLike) -> PathLike ``` Prepend a path with a prefix, but only if not absolute **Arguments**: - `path`: the path to process. - `prefix`: the path prefix. **Returns**: the same path if absolute, else the prepended path. ================================================ FILE: docs/api/helpers/constants.md ================================================ # aea.helpers.constants Module with helpers constants. ================================================ FILE: docs/api/helpers/env_vars.md ================================================ # aea.helpers.env`_`vars Implementation of the environment variables support. #### is`_`env`_`variable ```python def is_env_variable(value: Any) -> bool ``` Check is variable string with env variable pattern. #### replace`_`with`_`env`_`var ```python def replace_with_env_var(value: str, env_variables: dict, default_value: Any = NotSet) -> JSON_TYPES ``` Replace env var with value. #### apply`_`env`_`variables ```python def apply_env_variables(data: Union[Dict, List[Dict]], env_variables: Mapping[str, Any], default_value: Any = NotSet) -> JSON_TYPES ``` Create new resulting dict with env variables applied. #### convert`_`value`_`str`_`to`_`type ```python def convert_value_str_to_type(value: str, type_str: str) -> JSON_TYPES ``` Convert value by type name to native python type. ================================================ FILE: docs/api/helpers/exception_policy.md ================================================ # aea.helpers.exception`_`policy This module contains enum of aea exception policies. ## ExceptionPolicyEnum Objects ```python class ExceptionPolicyEnum(Enum) ``` AEA Exception policies. ================================================ FILE: docs/api/helpers/exec_timeout.md ================================================ # aea.helpers.exec`_`timeout Python code execution time limit tools. ## TimeoutResult Objects ```python class TimeoutResult() ``` Result of ExecTimeout context manager. #### `__`init`__` ```python def __init__() -> None ``` Init. #### set`_`cancelled`_`by`_`timeout ```python def set_cancelled_by_timeout() -> None ``` Set code was terminated cause timeout. #### is`_`cancelled`_`by`_`timeout ```python def is_cancelled_by_timeout() -> bool ``` Return True if code was terminated by ExecTimeout cause timeout. **Returns**: bool ## TimeoutException Objects ```python class TimeoutException(BaseException) ``` TimeoutException raised by ExecTimeout context managers in thread with limited execution time. Used internally, does not propagated outside of context manager ## BaseExecTimeout Objects ```python class BaseExecTimeout(ABC) ``` Base class for implementing context managers to limit python code execution time. exception_class - is exception type to raise in code controlled in case of timeout. #### `__`init`__` ```python def __init__(timeout: float = 0.0) -> None ``` Init. **Arguments**: - `timeout`: number of seconds to execute code before interruption #### `__`enter`__` ```python def __enter__() -> TimeoutResult ``` Enter context manager. **Returns**: TimeoutResult #### `__`exit`__` ```python def __exit__(exc_type: Type[Exception], exc_val: Exception, exc_tb: TracebackType) -> None ``` Exit context manager. **Arguments**: - `exc_type`: the exception type - `exc_val`: the exception - `exc_tb`: the traceback ## ExecTimeoutSigAlarm Objects ```python class ExecTimeoutSigAlarm(BaseExecTimeout) ``` ExecTimeout context manager implementation using signals and SIGALARM. Does not support threads, have to be used only in main thread. ## ExecTimeoutThreadGuard Objects ```python class ExecTimeoutThreadGuard(BaseExecTimeout) ``` ExecTimeout context manager implementation using threads and PyThreadState_SetAsyncExc. Support threads. Requires supervisor thread start/stop to control execution time control. Possible will be not accurate in case of long c functions used inside code controlled. #### `__`init`__` ```python def __init__(timeout: float = 0.0) -> None ``` Init ExecTimeoutThreadGuard variables. **Arguments**: - `timeout`: number of seconds to execute code before interruption #### start ```python @classmethod def start(cls) -> None ``` Start supervisor thread to check timeouts. Supervisor starts once but number of start counted. #### stop ```python @classmethod def stop(cls, force: bool = False) -> None ``` Stop supervisor thread. Actual stop performed on force == True or if number of stops == number of starts **Arguments**: - `force`: force stop regardless number of start. ================================================ FILE: docs/api/helpers/file_io.md ================================================ # aea.helpers.file`_`io Read to and write from file with envelopes. #### lock`_`file ```python @contextmanager def lock_file(file_descriptor: IO[bytes], logger: Logger = _default_logger) -> Generator ``` Lock file in context manager. **Arguments**: - `file_descriptor`: file descriptor of file to lock. - `logger`: the logger. **Returns**: generator #### write`_`envelope ```python def write_envelope(envelope: Envelope, file_pointer: IO[bytes], separator: bytes = SEPARATOR, logger: Logger = _default_logger) -> None ``` Write envelope to file. #### write`_`with`_`lock ```python def write_with_lock(file_pointer: IO[bytes], data: Union[bytes], logger: Logger = _default_logger) -> None ``` Write bytes to file protected with file lock. #### envelope`_`from`_`bytes ```python def envelope_from_bytes( bytes_: bytes, separator: bytes = SEPARATOR, logger: Logger = _default_logger) -> Optional[Envelope] ``` Decode bytes to get the envelope. **Arguments**: - `bytes_`: the encoded envelope - `separator`: the separator used - `logger`: the logger **Returns**: Envelope ================================================ FILE: docs/api/helpers/file_lock.md ================================================ # aea.helpers.file`_`lock Patch of 'fnctl' to make it compatible with Windows. ================================================ FILE: docs/api/helpers/http_requests.md ================================================ # aea.helpers.http`_`requests Wrapper over requests library. #### add`_`default`_`timeout ```python def add_default_timeout(fn: Callable, timeout: float) -> Callable ``` Add default timeout for requests methods. ================================================ FILE: docs/api/helpers/install_dependency.md ================================================ # aea.helpers.install`_`dependency Helper to install python dependencies. #### install`_`dependency ```python def install_dependency(dependency_name: str, dependency: Dependency, logger: Logger, install_timeout: float = 300) -> None ``` Install python dependency to the current python environment. **Arguments**: - `dependency_name`: name of the python package - `dependency`: Dependency specification - `logger`: the logger - `install_timeout`: timeout to wait pip to install #### install`_`dependencies ```python def install_dependencies(dependencies: List[Dependency], logger: Logger, install_timeout: float = 300) -> None ``` Install python dependencies to the current python environment. **Arguments**: - `dependencies`: dict of dependency name and specification - `logger`: the logger - `install_timeout`: timeout to wait pip to install #### call`_`pip ```python def call_pip(pip_args: List[str], timeout: float = 300, retry: bool = False) -> None ``` Run pip install command. **Arguments**: - `pip_args`: list strings of the command - `timeout`: timeout to wait pip to install - `retry`: bool, try one more time if command failed #### run`_`install`_`subprocess ```python def run_install_subprocess(install_command: List[str], install_timeout: float = 300) -> int ``` Try executing install command. **Arguments**: - `install_command`: list strings of the command - `install_timeout`: timeout to wait pip to install **Returns**: the return code of the subprocess ================================================ FILE: docs/api/helpers/io.md ================================================ # aea.helpers.io ================================================ FILE: docs/api/helpers/ipfs/base.md ================================================ # aea.helpers.ipfs.base This module contains helper methods and classes for the 'aea' package. #### chunks ```python def chunks(data: Sized, size: int) -> Generator ``` Yield successivesize chunks from data. ## IPFSHashOnly Objects ```python class IPFSHashOnly() ``` A helper class which allows construction of an IPFS hash without interacting with an IPFS daemon. #### get ```python def get(file_path: str) -> str ``` Get the IPFS hash for a single file. **Arguments**: - `file_path`: the file path **Returns**: the ipfs hash ================================================ FILE: docs/api/helpers/ipfs/utils.md ================================================ # aea.helpers.ipfs.utils This module contains utility methods for ipfs helpers. ================================================ FILE: docs/api/helpers/logging.md ================================================ # aea.helpers.logging Logging helpers. #### get`_`logger ```python def get_logger(module_path: str, agent_name: str) -> Logger ``` Get the logger based on a module path and agent name. ## AgentLoggerAdapter Objects ```python class AgentLoggerAdapter(LoggerAdapter) ``` This class is a logger adapter that prepends the agent name to log messages. #### `__`init`__` ```python def __init__(logger: Logger, agent_name: str) -> None ``` Initialize the logger adapter. **Arguments**: - `logger`: the logger. - `agent_name`: the agent name. #### process ```python def process( msg: Any, kwargs: MutableMapping[str, Any]) -> Tuple[Any, MutableMapping[str, Any]] ``` Prepend the agent name to every log message. ## WithLogger Objects ```python class WithLogger() ``` Interface to endow subclasses with a logger. #### `__`init`__` ```python def __init__(logger: Optional[Logger] = None, default_logger_name: str = "aea") -> None ``` Initialize the logger. **Arguments**: - `logger`: the logger object. - `default_logger_name`: the default logger name, if a logger is not provided. #### logger ```python @property def logger() -> Logger ``` Get the component logger. #### logger ```python @logger.setter def logger(logger: Optional[Logger]) -> None ``` Set the logger. ================================================ FILE: docs/api/helpers/multiaddr/base.md ================================================ # aea.helpers.multiaddr.base This module contains multiaddress class. ## MultiAddr Objects ```python class MultiAddr() ``` Protocol Labs' Multiaddress representation of a network address. #### `__`init`__` ```python def __init__(host: str, port: int, public_key: Optional[str] = None, multihash_id: Optional[str] = None) -> None ``` Initialize a multiaddress. **Arguments**: - `host`: ip host of the address - `port`: port number of the address - `public_key`: hex encoded public key. Must conform to Bitcoin EC encoding standard for Secp256k1 - `multihash_id`: a multihash of the public key #### compute`_`peerid ```python @staticmethod def compute_peerid(public_key: str) -> str ``` Compute the peer id from a public key. In particular, compute the base58 representation of libp2p PeerID from Bitcoin EC encoded Secp256k1 public key. **Arguments**: - `public_key`: the public key. **Returns**: the peer id. #### from`_`string ```python @classmethod def from_string(cls, maddr: str) -> "MultiAddr" ``` Construct a MultiAddr object from its string format **Arguments**: - `maddr`: multiaddress string **Returns**: multiaddress object #### public`_`key ```python @property def public_key() -> str ``` Get the public key. #### peer`_`id ```python @property def peer_id() -> str ``` Get the peer id. #### host ```python @property def host() -> str ``` Get the peer host. #### port ```python @property def port() -> int ``` Get the peer port. #### format ```python def format() -> str ``` Canonical representation of a multiaddress. #### `__`str`__` ```python def __str__() -> str ``` Default string representation of a multiaddress. ================================================ FILE: docs/api/helpers/multiple_executor.md ================================================ # aea.helpers.multiple`_`executor This module contains the helpers to run multiple stoppable tasks in different modes: async, threaded, multiprocess . ## ExecutorExceptionPolicies Objects ```python class ExecutorExceptionPolicies(Enum) ``` Runner exception policy modes. ## AbstractExecutorTask Objects ```python class AbstractExecutorTask(ABC) ``` Abstract task class to create Task classes. #### `__`init`__` ```python def __init__() -> None ``` Init task. #### future ```python @property def future() -> Optional[TaskAwaitable] ``` Return awaitable to get result of task execution. #### future ```python @future.setter def future(future: TaskAwaitable) -> None ``` Set awaitable to get result of task execution. #### start ```python @abstractmethod def start() -> Tuple[Callable, Sequence[Any]] ``` Implement start task function here. #### stop ```python @abstractmethod def stop() -> None ``` Implement stop task function here. #### create`_`async`_`task ```python @abstractmethod def create_async_task(loop: AbstractEventLoop) -> TaskAwaitable ``` Create asyncio task for task run in asyncio loop. **Arguments**: - `loop`: the event loop **Returns**: task to run in asyncio loop. #### id ```python @property def id() -> Any ``` Return task id. #### failed ```python @property def failed() -> bool ``` Return was exception failed or not. If it's running it's not failed. **Returns**: bool ## AbstractMultiprocessExecutorTask Objects ```python class AbstractMultiprocessExecutorTask(AbstractExecutorTask) ``` Task for multiprocess executor. #### start ```python @abstractmethod def start() -> Tuple[Callable, Sequence[Any]] ``` Return function and arguments to call within subprocess. #### create`_`async`_`task ```python def create_async_task(loop: AbstractEventLoop) -> TaskAwaitable ``` Create asyncio task for task run in asyncio loop. Raise error, cause async mode is not supported, cause this task for multiprocess executor only. **Arguments**: - `loop`: the event loop **Raises**: - `ValueError`: async task construction not possible ## AbstractMultipleExecutor Objects ```python class AbstractMultipleExecutor(ABC) ``` Abstract class to create multiple executors classes. #### `__`init`__` ```python def __init__( tasks: Sequence[AbstractExecutorTask], task_fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies. propagate ) -> None ``` Init executor. **Arguments**: - `tasks`: sequence of AbstractExecutorTask instances to run. - `task_fail_policy`: the exception policy of all the tasks #### is`_`running ```python @property def is_running() -> bool ``` Return running state of the executor. #### start ```python def start() -> None ``` Start tasks. #### stop ```python def stop() -> None ``` Stop tasks. #### num`_`failed ```python @property def num_failed() -> int ``` Return number of failed tasks. #### failed`_`tasks ```python @property def failed_tasks() -> Sequence[AbstractExecutorTask] ``` Return sequence failed tasks. #### not`_`failed`_`tasks ```python @property def not_failed_tasks() -> Sequence[AbstractExecutorTask] ``` Return sequence successful tasks. ## ThreadExecutor Objects ```python class ThreadExecutor(AbstractMultipleExecutor) ``` Thread based executor to run multiple agents in threads. ## ProcessExecutor Objects ```python class ProcessExecutor(ThreadExecutor) ``` Subprocess based executor to run multiple agents in threads. ## AsyncExecutor Objects ```python class AsyncExecutor(AbstractMultipleExecutor) ``` Thread based executor to run multiple agents in threads. ## AbstractMultipleRunner Objects ```python class AbstractMultipleRunner() ``` Abstract multiple runner to create classes to launch tasks with selected mode. #### `__`init`__` ```python def __init__( mode: str, fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies. propagate ) -> None ``` Init with selected executor mode. **Arguments**: - `mode`: one of supported executor modes - `fail_policy`: one of ExecutorExceptionPolicies to be used with Executor #### is`_`running ```python @property def is_running() -> bool ``` Return state of the executor. #### start ```python def start(threaded: bool = False) -> None ``` Run agents. **Arguments**: - `threaded`: run in dedicated thread without blocking current thread. #### stop ```python def stop(timeout: Optional[float] = None) -> None ``` Stop agents. **Arguments**: - `timeout`: timeout in seconds to wait thread stopped, only if started in thread mode. #### num`_`failed ```python @property def num_failed() -> int ``` Return number of failed tasks. #### failed ```python @property def failed() -> Sequence[Task] ``` Return sequence failed tasks. #### not`_`failed ```python @property def not_failed() -> Sequence[Task] ``` Return sequence successful tasks. #### try`_`join`_`thread ```python def try_join_thread() -> None ``` Try to join thread if running in thread mode. ================================================ FILE: docs/api/helpers/pipe.md ================================================ # aea.helpers.pipe Portable pipe implementation for Linux, MacOS, and Windows. ## IPCChannelClient Objects ```python class IPCChannelClient(ABC) ``` Multi-platform interprocess communication channel for the client side. #### connect ```python @abstractmethod async def connect(timeout: float = PIPE_CONN_TIMEOUT) -> bool ``` Connect to communication channel **Arguments**: - `timeout`: timeout for other end to connect **Returns**: connection status #### write ```python @abstractmethod async def write(data: bytes) -> None ``` Write `data` bytes to the other end of the channel Will first write the size than the actual data **Arguments**: - `data`: bytes to write #### read ```python @abstractmethod async def read() -> Optional[bytes] ``` Read bytes from the other end of the channel Will first read the size than the actual data **Returns**: read bytes #### close ```python @abstractmethod async def close() -> None ``` Close the communication channel. ## IPCChannel Objects ```python class IPCChannel(IPCChannelClient) ``` Multi-platform interprocess communication channel. #### in`_`path ```python @property @abstractmethod def in_path() -> str ``` Rendezvous point for incoming communication. **Returns**: path #### out`_`path ```python @property @abstractmethod def out_path() -> str ``` Rendezvous point for outgoing communication. **Returns**: path ## PosixNamedPipeProtocol Objects ```python class PosixNamedPipeProtocol() ``` Posix named pipes async wrapper communication protocol. #### `__`init`__` ```python def __init__(in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> None ``` Initialize a new posix named pipe. **Arguments**: - `in_path`: rendezvous point for incoming data - `out_path`: rendezvous point for outgoing data - `logger`: the logger - `loop`: the event loop #### connect ```python async def connect(timeout: float = PIPE_CONN_TIMEOUT) -> bool ``` Connect to the other end of the pipe **Arguments**: - `timeout`: timeout before failing **Returns**: connection success #### write ```python async def write(data: bytes) -> None ``` Write to pipe. **Arguments**: - `data`: bytes to write to pipe #### read ```python async def read() -> Optional[bytes] ``` Read from pipe. **Returns**: read bytes #### close ```python async def close() -> None ``` Disconnect pipe. ## TCPSocketProtocol Objects ```python class TCPSocketProtocol() ``` TCP socket communication protocol. #### `__`init`__` ```python def __init__(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> None ``` Initialize the tcp socket protocol. **Arguments**: - `reader`: established asyncio reader - `writer`: established asyncio writer - `logger`: the logger - `loop`: the event loop #### writer ```python @property def writer() -> StreamWriter ``` Get a writer associated with protocol. #### write ```python async def write(data: bytes) -> None ``` Write to socket. **Arguments**: - `data`: bytes to write #### read ```python async def read() -> Optional[bytes] ``` Read from socket. **Returns**: read bytes #### close ```python async def close() -> None ``` Disconnect socket. ## TCPSocketChannel Objects ```python class TCPSocketChannel(IPCChannel) ``` Interprocess communication channel implementation using tcp sockets. #### `__`init`__` ```python def __init__(logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> None ``` Initialize tcp socket interprocess communication channel. #### connect ```python async def connect(timeout: float = PIPE_CONN_TIMEOUT) -> bool ``` Setup communication channel and wait for other end to connect. **Arguments**: - `timeout`: timeout for the connection to be established **Returns**: connection status #### write ```python async def write(data: bytes) -> None ``` Write to channel. **Arguments**: - `data`: bytes to write #### read ```python async def read() -> Optional[bytes] ``` Read from channel. **Returns**: read bytes #### close ```python async def close() -> None ``` Disconnect from channel and clean it up. #### in`_`path ```python @property def in_path() -> str ``` Rendezvous point for incoming communication. #### out`_`path ```python @property def out_path() -> str ``` Rendezvous point for outgoing communication. ## PosixNamedPipeChannel Objects ```python class PosixNamedPipeChannel(IPCChannel) ``` Interprocess communication channel implementation using Posix named pipes. #### `__`init`__` ```python def __init__(logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> None ``` Initialize posix named pipe interprocess communication channel. #### connect ```python async def connect(timeout: float = PIPE_CONN_TIMEOUT) -> bool ``` Setup communication channel and wait for other end to connect. **Arguments**: - `timeout`: timeout for connection to be established **Returns**: bool, indicating success #### write ```python async def write(data: bytes) -> None ``` Write to the channel. **Arguments**: - `data`: data to write to channel #### read ```python async def read() -> Optional[bytes] ``` Read from the channel. **Returns**: read bytes #### close ```python async def close() -> None ``` Close the channel and clean it up. #### in`_`path ```python @property def in_path() -> str ``` Rendezvous point for incoming communication. #### out`_`path ```python @property def out_path() -> str ``` Rendezvous point for outgoing communication. ## TCPSocketChannelClient Objects ```python class TCPSocketChannelClient(IPCChannelClient) ``` Interprocess communication channel client using tcp sockets. #### `__`init`__` ```python def __init__(in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> None ``` Initialize a tcp socket communication channel client. **Arguments**: - `in_path`: rendezvous point for incoming data - `out_path`: rendezvous point for outgoing data - `logger`: the logger - `loop`: the event loop #### connect ```python async def connect(timeout: float = PIPE_CONN_TIMEOUT) -> bool ``` Connect to the other end of the communication channel. **Arguments**: - `timeout`: timeout for connection to be established **Returns**: connection status #### write ```python async def write(data: bytes) -> None ``` Write data to channel. **Arguments**: - `data`: bytes to write #### read ```python async def read() -> Optional[bytes] ``` Read data from channel. **Returns**: read bytes #### close ```python async def close() -> None ``` Disconnect from communication channel. ## PosixNamedPipeChannelClient Objects ```python class PosixNamedPipeChannelClient(IPCChannelClient) ``` Interprocess communication channel client using Posix named pipes. #### `__`init`__` ```python def __init__(in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> None ``` Initialize a posix named pipe communication channel client. **Arguments**: - `in_path`: rendezvous point for incoming data - `out_path`: rendezvous point for outgoing data - `logger`: the logger - `loop`: the event loop #### connect ```python async def connect(timeout: float = PIPE_CONN_TIMEOUT) -> bool ``` Connect to the other end of the communication channel. **Arguments**: - `timeout`: timeout for connection to be established **Returns**: connection status #### write ```python async def write(data: bytes) -> None ``` Write data to channel. **Arguments**: - `data`: bytes to write #### read ```python async def read() -> Optional[bytes] ``` Read data from channel. **Returns**: read bytes #### close ```python async def close() -> None ``` Disconnect from communication channel. #### make`_`ipc`_`channel ```python def make_ipc_channel(logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> IPCChannel ``` Build a portable bidirectional InterProcess Communication channel **Arguments**: - `logger`: the logger - `loop`: the loop **Returns**: IPCChannel #### make`_`ipc`_`channel`_`client ```python def make_ipc_channel_client( in_path: str, out_path: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None) -> IPCChannelClient ``` Build a portable bidirectional InterProcess Communication client channel **Arguments**: - `in_path`: rendezvous point for incoming communication - `out_path`: rendezvous point for outgoing outgoing - `logger`: the logger - `loop`: the loop **Returns**: IPCChannel ================================================ FILE: docs/api/helpers/preference_representations/base.md ================================================ # aea.helpers.preference`_`representations.base Preference representation helpers. #### logarithmic`_`utility ```python def logarithmic_utility(utility_params_by_good_id: Dict[str, float], quantities_by_good_id: Dict[str, int], quantity_shift: int = 100) -> float ``` Compute agent's utility given her utility function params and a good bundle. **Arguments**: - `utility_params_by_good_id`: utility params by good identifier - `quantities_by_good_id`: quantities by good identifier - `quantity_shift`: a non-negative factor to shift the quantities in the utility function (to ensure the natural logarithm can be used on the entire range of quantities) **Returns**: utility value #### linear`_`utility ```python def linear_utility(exchange_params_by_currency_id: Dict[str, float], balance_by_currency_id: Dict[str, int]) -> float ``` Compute agent's utility given her utility function params and a good bundle. **Arguments**: - `exchange_params_by_currency_id`: exchange params by currency - `balance_by_currency_id`: balance by currency **Returns**: utility value ================================================ FILE: docs/api/helpers/profiling.md ================================================ # aea.helpers.profiling Implementation of background profiling daemon. ## Profiling Objects ```python class Profiling(Runnable) ``` Profiling service. #### `__`init`__` ```python def __init__( period: int = 0, objects_instances_to_count: List[Type] = None, objects_created_to_count: List[Type] = None, output_function: Callable[[str], None] = lambda x: print(x, flush=True) ) -> None ``` Init profiler. **Arguments**: - `period`: delay between profiling output in seconds. - `objects_instances_to_count`: object to count - `objects_created_to_count`: object created to count - `output_function`: function to display output, one str argument. #### set`_`counters ```python def set_counters() -> None ``` Modify obj.__new__ to count objects created created. #### run ```python async def run() -> None ``` Run profiling. #### output`_`profile`_`data ```python def output_profile_data() -> None ``` Render profiling data and call output_function. #### get`_`profile`_`data ```python def get_profile_data() -> Dict ``` Get profiling data dict. #### get`_`objects`_`instances ```python def get_objects_instances() -> Dict ``` Return dict with counted object instances present now. #### get`_`objecst`_`created ```python def get_objecst_created() -> Dict ``` Return dict with counted object instances created. ================================================ FILE: docs/api/helpers/search/generic.md ================================================ # aea.helpers.search.generic This module contains a generic data model. ## GenericDataModel Objects ```python class GenericDataModel(DataModel) ``` Generic data model. #### `__`init`__` ```python def __init__(data_model_name: str, data_model_attributes: Dict[str, Any]) -> None ``` Initialise the dataModel. ================================================ FILE: docs/api/helpers/search/models.md ================================================ ================================================ FILE: docs/api/helpers/serializers.md ================================================ # aea.helpers.serializers This module contains Serializers that can be used for custom types. ## DictProtobufStructSerializer Objects ```python class DictProtobufStructSerializer() ``` Serialize python dictionaries of type DictType = Dict[str, ValueType] recursively conserving their dynamic type, using google.protobuf.Struct ValueType = PrimitiveType | DictType | List[ValueType]] PrimitiveType = bool | int | float | str | bytes #### encode ```python @classmethod def encode(cls, dictionary: Dict[str, Any]) -> bytes ``` Serialize compatible dictionary to bytes. Copies entire dictionary in the process. **Arguments**: - `dictionary`: the dictionary to serialize **Returns**: serialized bytes string #### decode ```python @classmethod def decode(cls, buffer: bytes) -> Dict[str, Any] ``` Deserialize a compatible dictionary ================================================ FILE: docs/api/helpers/storage/backends/base.md ================================================ # aea.helpers.storage.backends.base This module contains storage abstract backend class. ## AbstractStorageBackend Objects ```python class AbstractStorageBackend(ABC) ``` Abstract base class for storage backend. #### `__`init`__` ```python def __init__(uri: str) -> None ``` Init backend. #### connect ```python @abstractmethod async def connect() -> None ``` Connect to backend. #### disconnect ```python @abstractmethod async def disconnect() -> None ``` Disconnect the backend. #### ensure`_`collection ```python @abstractmethod async def ensure_collection(collection_name: str) -> None ``` Create collection if not exits. **Arguments**: - `collection_name`: str. **Returns**: None #### put ```python @abstractmethod async def put(collection_name: str, object_id: str, object_body: JSON_TYPES) -> None ``` Put object into collection. **Arguments**: - `collection_name`: str. - `object_id`: str object id - `object_body`: python dict, json compatible. **Returns**: None #### get ```python @abstractmethod async def get(collection_name: str, object_id: str) -> Optional[JSON_TYPES] ``` Get object from the collection. **Arguments**: - `collection_name`: str. - `object_id`: str object id **Returns**: dict if object exists in collection otherwise None #### remove ```python @abstractmethod async def remove(collection_name: str, object_id: str) -> None ``` Remove object from the collection. **Arguments**: - `collection_name`: str. - `object_id`: str object id **Returns**: None #### find ```python @abstractmethod async def find(collection_name: str, field: str, equals: EQUALS_TYPE) -> List[OBJECT_ID_AND_BODY] ``` Get objects from the collection by filtering by field value. **Arguments**: - `collection_name`: str. - `field`: field name to search: example "parent.field" - `equals`: value field should be equal to **Returns**: list of objects bodies #### list ```python @abstractmethod async def list(collection_name: str) -> List[OBJECT_ID_AND_BODY] ``` List all objects with keys from the collection. **Arguments**: - `collection_name`: str. **Returns**: Tuple of objects keys, bodies. ================================================ FILE: docs/api/helpers/storage/backends/sqlite.md ================================================ # aea.helpers.storage.backends.sqlite This module contains sqlite storage backend implementation. ## SqliteStorageBackend Objects ```python class SqliteStorageBackend(AbstractStorageBackend) ``` Sqlite storage backend. #### `__`init`__` ```python def __init__(uri: str) -> None ``` Init backend. #### connect ```python async def connect() -> None ``` Connect to backend. #### disconnect ```python async def disconnect() -> None ``` Disconnect the backend. #### ensure`_`collection ```python async def ensure_collection(collection_name: str) -> None ``` Create collection if not exits. **Arguments**: - `collection_name`: name of the collection. #### put ```python async def put(collection_name: str, object_id: str, object_body: JSON_TYPES) -> None ``` Put object into collection. **Arguments**: - `collection_name`: str. - `object_id`: str object id - `object_body`: python dict, json compatible. #### get ```python async def get(collection_name: str, object_id: str) -> Optional[JSON_TYPES] ``` Get object from the collection. **Arguments**: - `collection_name`: str. - `object_id`: str object id **Returns**: dict if object exists in collection otherwise None #### remove ```python async def remove(collection_name: str, object_id: str) -> None ``` Remove object from the collection. **Arguments**: - `collection_name`: str. - `object_id`: str object id #### find ```python async def find(collection_name: str, field: str, equals: EQUALS_TYPE) -> List[OBJECT_ID_AND_BODY] ``` Get objects from the collection by filtering by field value. **Arguments**: - `collection_name`: str. - `field`: field name to search: example "parent.field" - `equals`: value field should be equal to **Returns**: list of object ids and body #### list ```python async def list(collection_name: str) -> List[OBJECT_ID_AND_BODY] ``` List all objects with keys from the collection. **Arguments**: - `collection_name`: str. **Returns**: Tuple of objects keys, bodies. ================================================ FILE: docs/api/helpers/storage/generic_storage.md ================================================ # aea.helpers.storage.generic`_`storage This module contains the storage implementation. ## AsyncCollection Objects ```python class AsyncCollection() ``` Async collection. #### `__`init`__` ```python def __init__(storage_backend: AbstractStorageBackend, collection_name: str) -> None ``` Init collection object. **Arguments**: - `storage_backend`: storage backed to use. - `collection_name`: str #### put ```python async def put(object_id: str, object_body: JSON_TYPES) -> None ``` Put object into collection. **Arguments**: - `object_id`: str object id - `object_body`: python dict, json compatible. **Returns**: None #### get ```python async def get(object_id: str) -> Optional[JSON_TYPES] ``` Get object from the collection. **Arguments**: - `object_id`: str object id **Returns**: dict if object exists in collection otherwise None #### remove ```python async def remove(object_id: str) -> None ``` Remove object from the collection. **Arguments**: - `object_id`: str object id **Returns**: None #### find ```python async def find(field: str, equals: EQUALS_TYPE) -> List[OBJECT_ID_AND_BODY] ``` Get objects from the collection by filtering by field value. **Arguments**: - `field`: field name to search: example "parent.field" - `equals`: value field should be equal to **Returns**: None #### list ```python async def list() -> List[OBJECT_ID_AND_BODY] ``` List all objects with keys from the collection. **Returns**: Tuple of objects keys, bodies. ## SyncCollection Objects ```python class SyncCollection() ``` Async collection. #### `__`init`__` ```python def __init__(async_collection_coro: Coroutine, loop: asyncio.AbstractEventLoop) -> None ``` Init collection object. **Arguments**: - `async_collection_coro`: coroutine returns async collection. - `loop`: abstract event loop where storage is running. #### put ```python def put(object_id: str, object_body: JSON_TYPES) -> None ``` Put object into collection. **Arguments**: - `object_id`: str object id - `object_body`: python dict, json compatible. **Returns**: None #### get ```python def get(object_id: str) -> Optional[JSON_TYPES] ``` Get object from the collection. **Arguments**: - `object_id`: str object id **Returns**: dict if object exists in collection otherwise None #### remove ```python def remove(object_id: str) -> None ``` Remove object from the collection. **Arguments**: - `object_id`: str object id **Returns**: None #### find ```python def find(field: str, equals: EQUALS_TYPE) -> List[OBJECT_ID_AND_BODY] ``` Get objects from the collection by filtering by field value. **Arguments**: - `field`: field name to search: example "parent.field" - `equals`: value field should be equal to **Returns**: List of object bodies #### list ```python def list() -> List[OBJECT_ID_AND_BODY] ``` List all objects with keys from the collection. **Returns**: Tuple of objects keys, bodies. ## Storage Objects ```python class Storage(Runnable) ``` Generic storage. #### `__`init`__` ```python def __init__(storage_uri: str, loop: asyncio.AbstractEventLoop = None, threaded: bool = False) -> None ``` Init storage. **Arguments**: - `storage_uri`: configuration string for storage. - `loop`: asyncio event loop to use. - `threaded`: bool. start in thread if True. #### wait`_`connected ```python async def wait_connected() -> None ``` Wait generic storage is connected. #### is`_`connected ```python @property def is_connected() -> bool ``` Get running state of the storage. #### run ```python async def run() -> None ``` Connect storage. #### get`_`collection ```python async def get_collection(collection_name: str) -> AsyncCollection ``` Get async collection. #### get`_`sync`_`collection ```python def get_sync_collection(collection_name: str) -> SyncCollection ``` Get sync collection. #### `__`repr`__` ```python def __repr__() -> str ``` Get string representation of the storage. ================================================ FILE: docs/api/helpers/sym_link.md ================================================ # aea.helpers.sym`_`link Sym link implementation for Linux, MacOS, and Windows. #### make`_`symlink ```python def make_symlink(link_name: str, target: str) -> None ``` Make a symbolic link, cross platform. **Arguments**: - `link_name`: the link name. - `target`: the target. #### cd ```python @contextlib.contextmanager def cd(path: Path) -> Generator ``` Change directory with context manager. #### create`_`symlink ```python def create_symlink(link_path: Path, target_path: Path, root_path: Path) -> int ``` Change directory and call the cross-platform script. The working directory must be the parent of the symbolic link name when executing 'create_symlink_crossplatform.sh'. Hence, we need to translate target_path into the relative path from the symbolic link directory to the target directory. So: 1) from link_path, extract the number of jumps to the parent directory in order to reach the repository root directory, and chain many "../" paths. 2) from target_path, compute the relative path to the root 3) relative_target_path is just the concatenation of the results from step (1) and (2). For instance, given - link_path: './directory_1//symbolic_link - target_path: './directory_2/target_path we want to compute: - link_path: 'symbolic_link' (just the last bit) - relative_target_path: '../../directory_1/target_path' The resulting command on UNIX systems will be: cd directory_1 && ln -s ../../directory_1/target_path symbolic_link **Arguments**: - `link_path`: the source path - `target_path`: the target path - `root_path`: the root path **Returns**: exit code ================================================ FILE: docs/api/helpers/transaction/base.md ================================================ # aea.helpers.transaction.base This module contains terms related classes. ## RawTransaction Objects ```python class RawTransaction() ``` This class represents an instance of RawTransaction. #### `__`init`__` ```python def __init__(ledger_id: str, body: JSONLike) -> None ``` Initialise an instance of RawTransaction. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### body ```python @property def body() -> JSONLike ``` Get the body. #### encode ```python @staticmethod def encode(raw_transaction_protobuf_object: Any, raw_transaction_object: "RawTransaction") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the raw_transaction_protobuf_object argument must be matched with the instance of this class in the 'raw_transaction_object' argument. **Arguments**: - `raw_transaction_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `raw_transaction_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, raw_transaction_protobuf_object: Any) -> "RawTransaction" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'raw_transaction_protobuf_object' argument. **Arguments**: - `raw_transaction_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'raw_transaction_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ## RawMessage Objects ```python class RawMessage() ``` This class represents an instance of RawMessage. #### `__`init`__` ```python def __init__(ledger_id: str, body: bytes, is_deprecated_mode: bool = False) -> None ``` Initialise an instance of RawMessage. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### body ```python @property def body() -> bytes ``` Get the body. #### is`_`deprecated`_`mode ```python @property def is_deprecated_mode() -> bool ``` Get the is_deprecated_mode. #### encode ```python @staticmethod def encode(raw_message_protobuf_object: Any, raw_message_object: "RawMessage") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the raw_message_protobuf_object argument must be matched with the instance of this class in the 'raw_message_object' argument. **Arguments**: - `raw_message_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `raw_message_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, raw_message_protobuf_object: Any) -> "RawMessage" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'raw_message_protobuf_object' argument. **Arguments**: - `raw_message_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'raw_message_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ## SignedTransaction Objects ```python class SignedTransaction() ``` This class represents an instance of SignedTransaction. #### `__`init`__` ```python def __init__(ledger_id: str, body: JSONLike) -> None ``` Initialise an instance of SignedTransaction. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### body ```python @property def body() -> JSONLike ``` Get the body. #### encode ```python @staticmethod def encode(signed_transaction_protobuf_object: Any, signed_transaction_object: "SignedTransaction") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the signed_transaction_protobuf_object argument must be matched with the instance of this class in the 'signed_transaction_object' argument. **Arguments**: - `signed_transaction_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `signed_transaction_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, signed_transaction_protobuf_object: Any) -> "SignedTransaction" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'signed_transaction_protobuf_object' argument. **Arguments**: - `signed_transaction_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'signed_transaction_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ## SignedMessage Objects ```python class SignedMessage() ``` This class represents an instance of RawMessage. #### `__`init`__` ```python def __init__(ledger_id: str, body: str, is_deprecated_mode: bool = False) -> None ``` Initialise an instance of SignedMessage. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### body ```python @property def body() -> str ``` Get the body. #### is`_`deprecated`_`mode ```python @property def is_deprecated_mode() -> bool ``` Get the is_deprecated_mode. #### encode ```python @staticmethod def encode(signed_message_protobuf_object: Any, signed_message_object: "SignedMessage") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the signed_message_protobuf_object argument must be matched with the instance of this class in the 'signed_message_object' argument. **Arguments**: - `signed_message_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `signed_message_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, signed_message_protobuf_object: Any) -> "SignedMessage" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'signed_message_protobuf_object' argument. **Arguments**: - `signed_message_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'signed_message_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ## State Objects ```python class State() ``` This class represents an instance of State. #### `__`init`__` ```python def __init__(ledger_id: str, body: JSONLike) -> None ``` Initialise an instance of State. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### body ```python @property def body() -> JSONLike ``` Get the body. #### encode ```python @staticmethod def encode(state_protobuf_object: Any, state_object: "State") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the state_protobuf_object argument must be matched with the instance of this class in the 'state_object' argument. **Arguments**: - `state_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `state_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, state_protobuf_object: Any) -> "State" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'state_protobuf_object' argument. **Arguments**: - `state_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'state_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ## Terms Objects ```python class Terms() ``` Class to represent the terms of a multi-currency & multi-token ledger transaction. #### `__`init`__` ```python def __init__(ledger_id: str, sender_address: Address, counterparty_address: Address, amount_by_currency_id: Dict[str, int], quantities_by_good_id: Dict[str, int], nonce: str, is_sender_payable_tx_fee: bool = True, fee_by_currency_id: Optional[Dict[str, int]] = None, is_strict: bool = False, **kwargs: Any) -> None ``` Instantiate terms of a transaction. **Arguments**: - `ledger_id`: the ledger on which the terms are to be settled. - `sender_address`: the sender address of the transaction. - `counterparty_address`: the counterparty address of the transaction. - `amount_by_currency_id`: the amount by the currency of the transaction. - `quantities_by_good_id`: a map from good id to the quantity of that good involved in the transaction. - `nonce`: nonce to be included in transaction to discriminate otherwise identical transactions. - `is_sender_payable_tx_fee`: whether the sender or counterparty pays the tx fee. - `fee_by_currency_id`: the fee associated with the transaction. - `is_strict`: whether or not terms must have quantities and amounts of opposite signs. - `kwargs`: keyword arguments #### id ```python @property def id() -> str ``` Get hash of the terms. #### sender`_`hash ```python @property def sender_hash() -> str ``` Get the sender hash. #### counterparty`_`hash ```python @property def counterparty_hash() -> str ``` Get the sender hash. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### sender`_`address ```python @property def sender_address() -> Address ``` Get the sender address. #### counterparty`_`address ```python @property def counterparty_address() -> Address ``` Get the counterparty address. #### counterparty`_`address ```python @counterparty_address.setter def counterparty_address(counterparty_address: Address) -> None ``` Set the counterparty address. #### amount`_`by`_`currency`_`id ```python @property def amount_by_currency_id() -> Dict[str, int] ``` Get the amount by currency id. #### is`_`sender`_`payable`_`tx`_`fee ```python @property def is_sender_payable_tx_fee() -> bool ``` Bool indicating whether the tx fee is paid by sender or counterparty. #### is`_`single`_`currency ```python @property def is_single_currency() -> bool ``` Check whether a single currency is used for payment. #### is`_`empty`_`currency ```python @property def is_empty_currency() -> bool ``` Check whether a single currency is used for payment. #### currency`_`id ```python @property def currency_id() -> str ``` Get the amount the sender must pay. #### sender`_`payable`_`amount ```python @property def sender_payable_amount() -> int ``` Get the amount the sender must pay. #### sender`_`payable`_`amount`_`incl`_`fee ```python @property def sender_payable_amount_incl_fee() -> int ``` Get the amount the sender must pay inclusive fee. #### counterparty`_`payable`_`amount ```python @property def counterparty_payable_amount() -> int ``` Get the amount the counterparty must pay. #### counterparty`_`payable`_`amount`_`incl`_`fee ```python @property def counterparty_payable_amount_incl_fee() -> int ``` Get the amount the counterparty must pay. #### quantities`_`by`_`good`_`id ```python @property def quantities_by_good_id() -> Dict[str, int] ``` Get the quantities by good id. #### good`_`ids ```python @property def good_ids() -> List[str] ``` Get the (ordered) good ids. #### sender`_`supplied`_`quantities ```python @property def sender_supplied_quantities() -> List[int] ``` Get the (ordered) quantities supplied by the sender. #### counterparty`_`supplied`_`quantities ```python @property def counterparty_supplied_quantities() -> List[int] ``` Get the (ordered) quantities supplied by the counterparty. #### nonce ```python @property def nonce() -> str ``` Get the nonce. #### has`_`fee ```python @property def has_fee() -> bool ``` Check if fee is set. #### fee ```python @property def fee() -> int ``` Get the fee. #### sender`_`fee ```python @property def sender_fee() -> int ``` Get the sender fee. #### counterparty`_`fee ```python @property def counterparty_fee() -> int ``` Get the counterparty fee. #### fee`_`by`_`currency`_`id ```python @property def fee_by_currency_id() -> Dict[str, int] ``` Get fee by currency. #### kwargs ```python @property def kwargs() -> JSONLike ``` Get the kwargs. #### is`_`strict ```python @property def is_strict() -> bool ``` Get is_strict. #### get`_`hash ```python @staticmethod def get_hash(ledger_id: str, sender_address: str, counterparty_address: str, good_ids: List[str], sender_supplied_quantities: List[int], counterparty_supplied_quantities: List[int], sender_payable_amount: int, counterparty_payable_amount: int, nonce: str) -> str ``` Generate a hash from transaction information. **Arguments**: - `ledger_id`: the ledger id - `sender_address`: the sender address - `counterparty_address`: the counterparty address - `good_ids`: the list of good ids - `sender_supplied_quantities`: the quantities supplied by the sender (must all be positive) - `counterparty_supplied_quantities`: the quantities supplied by the counterparty (must all be positive) - `sender_payable_amount`: the amount payable by the sender - `counterparty_payable_amount`: the amount payable by the counterparty - `nonce`: the nonce of the transaction **Returns**: the hash #### encode ```python @staticmethod def encode(terms_protobuf_object: Any, terms_object: "Terms") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the terms_protobuf_object argument must be matched with the instance of this class in the 'terms_object' argument. **Arguments**: - `terms_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `terms_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, terms_protobuf_object: Any) -> "Terms" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'terms_protobuf_object' argument. **Arguments**: - `terms_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'terms_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ## TransactionDigest Objects ```python class TransactionDigest() ``` This class represents an instance of TransactionDigest. #### `__`init`__` ```python def __init__(ledger_id: str, body: str) -> None ``` Initialise an instance of TransactionDigest. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### body ```python @property def body() -> str ``` Get the receipt. #### encode ```python @staticmethod def encode(transaction_digest_protobuf_object: Any, transaction_digest_object: "TransactionDigest") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the transaction_digest_protobuf_object argument must be matched with the instance of this class in the 'transaction_digest_object' argument. **Arguments**: - `transaction_digest_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `transaction_digest_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, transaction_digest_protobuf_object: Any) -> "TransactionDigest" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'transaction_digest_protobuf_object' argument. **Arguments**: - `transaction_digest_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'transaction_digest_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ## TransactionReceipt Objects ```python class TransactionReceipt() ``` This class represents an instance of TransactionReceipt. #### `__`init`__` ```python def __init__(ledger_id: str, receipt: JSONLike, transaction: JSONLike) -> None ``` Initialise an instance of TransactionReceipt. #### ledger`_`id ```python @property def ledger_id() -> str ``` Get the id of the ledger on which the terms are to be settled. #### receipt ```python @property def receipt() -> JSONLike ``` Get the receipt. #### transaction ```python @property def transaction() -> JSONLike ``` Get the transaction. #### encode ```python @staticmethod def encode(transaction_receipt_protobuf_object: Any, transaction_receipt_object: "TransactionReceipt") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the transaction_receipt_protobuf_object argument must be matched with the instance of this class in the 'transaction_receipt_object' argument. **Arguments**: - `transaction_receipt_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `transaction_receipt_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, transaction_receipt_protobuf_object: Any) -> "TransactionReceipt" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class must be created that matches the protocol buffer object in the 'transaction_receipt_protobuf_object' argument. **Arguments**: - `transaction_receipt_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'transaction_receipt_protobuf_object' argument. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check equality. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. ================================================ FILE: docs/api/helpers/win32.md ================================================ # aea.helpers.win32 Helpers for Windows. #### enable`_`ctrl`_`c`_`support ```python def enable_ctrl_c_support() -> None ``` Enable ctrl+c support for aea.cli command to be tested on windows platform. ================================================ FILE: docs/api/helpers/yaml_utils.md ================================================ # aea.helpers.yaml`_`utils Helper functions related to YAML loading/dumping. ## `_`AEAYamlLoader Objects ```python class _AEAYamlLoader(yaml.SafeLoader) ``` Custom yaml.SafeLoader for the AEA framework. It extends the default SafeLoader in two ways: - loads YAML configurations while *remembering the order of the fields*; - resolves the environment variables at loading time. This class is for internal usage only; please use the public functions of the module 'yaml_load' and 'yaml_load_all'. #### `__`init`__` ```python def __init__(*args: Any, **kwargs: Any) -> None ``` Initialize the AEAYamlLoader. It adds a YAML Loader constructor to use 'OderedDict' to load the files. **Arguments**: - `args`: the positional arguments. - `kwargs`: the keyword arguments. ## `_`AEAYamlDumper Objects ```python class _AEAYamlDumper(yaml.SafeDumper) ``` Custom yaml.SafeDumper for the AEA framework. It extends the default SafeDumper so to dump YAML configurations while *following the order of the fields*. This class is for internal usage only; please use the public functions of the module 'yaml_dump' and 'yaml_dump_all'. #### `__`init`__` ```python def __init__(*args: Any, **kwargs: Any) -> None ``` Initialize the AEAYamlDumper. It adds a YAML Dumper representer to use 'OderedDict' to dump the files. **Arguments**: - `args`: the positional arguments. - `kwargs`: the keyword arguments. #### yaml`_`load ```python def yaml_load(stream: TextIO) -> Dict[str, Any] ``` Load a yaml from a file pointer in an ordered way. **Arguments**: - `stream`: file pointer to the input file. **Returns**: the dictionary object with the YAML file content. #### yaml`_`load`_`all ```python def yaml_load_all(stream: TextIO) -> List[Dict[str, Any]] ``` Load a multi-paged yaml from a file pointer in an ordered way. **Arguments**: - `stream`: file pointer to the input file. **Returns**: the list of dictionary objects with the (multi-paged) YAML file content. #### yaml`_`dump ```python def yaml_dump(data: Dict, stream: Optional[TextIO] = None) -> None ``` Dump YAML data to a yaml file in an ordered way. **Arguments**: - `data`: the data to write. - `stream`: (optional) the file to write on. #### yaml`_`dump`_`all ```python def yaml_dump_all(data: Sequence[Dict], stream: Optional[TextIO] = None) -> None ``` Dump YAML data to a yaml file in an ordered way. **Arguments**: - `data`: the data to write. - `stream`: (optional) the file to write on. ================================================ FILE: docs/api/identity/base.md ================================================ # aea.identity.base This module contains the identity class. ## Identity Objects ```python class Identity() ``` The identity holds the public elements identifying an agent. It includes: - the agent name - the addresses, a map from address identifier to address (can be a single key-value pair) #### `__`init`__` ```python def __init__(name: SimpleIdOrStr, address: Optional[str] = None, public_key: Optional[str] = None, addresses: Optional[Dict[str, Address]] = None, public_keys: Optional[Dict[str, str]] = None, default_address_key: str = DEFAULT_LEDGER) -> None ``` Instantiate the identity. **Arguments**: - `name`: the name of the agent. - `address`: the default address of the agent. - `public_key`: the public key of the agent. - `addresses`: the addresses of the agent. - `public_keys`: the public keys of the agent. - `default_address_key`: the key for the default address. #### default`_`address`_`key ```python @property def default_address_key() -> str ``` Get the default address key. #### name ```python @property def name() -> str ``` Get the agent name. #### addresses ```python @property def addresses() -> Dict[str, Address] ``` Get the addresses. #### address ```python @property def address() -> Address ``` Get the default address. #### public`_`keys ```python @property def public_keys() -> Dict[str, str] ``` Get the public keys. #### public`_`key ```python @property def public_key() -> str ``` Get the default public key. ================================================ FILE: docs/api/launcher.md ================================================ # aea.launcher This module contains the implementation of multiple AEA configs launcher. #### load`_`agent ```python def load_agent(agent_dir: Union[PathLike, str], password: Optional[str] = None) -> AEA ``` Load AEA from directory. **Arguments**: - `agent_dir`: agent configuration directory - `password`: the password to encrypt/decrypt the private key. **Returns**: AEA instance ## AEADirTask Objects ```python class AEADirTask(AbstractExecutorTask) ``` Task to run agent from agent configuration directory. #### `__`init`__` ```python def __init__(agent_dir: Union[PathLike, str], password: Optional[str] = None) -> None ``` Init aea config dir task. **Arguments**: - `agent_dir`: directory with aea config. - `password`: the password to encrypt/decrypt the private key. #### id ```python @property def id() -> Union[PathLike, str] ``` Return agent_dir. #### start ```python def start() -> None ``` Start task. #### stop ```python def stop() -> None ``` Stop task. #### create`_`async`_`task ```python def create_async_task(loop: AbstractEventLoop) -> TaskAwaitable ``` Return asyncio Task for task run in asyncio loop. ## AEADirMultiprocessTask Objects ```python class AEADirMultiprocessTask(AbstractMultiprocessExecutorTask) ``` Task to run agent from agent configuration directory. Version for multiprocess executor mode. #### `__`init`__` ```python def __init__(agent_dir: Union[PathLike, str], log_level: Optional[str] = None, password: Optional[str] = None) -> None ``` Init aea config dir task. **Arguments**: - `agent_dir`: directory with aea config. - `log_level`: debug level applied for AEA in subprocess - `password`: the password to encrypt/decrypt the private key. #### id ```python @property def id() -> Union[PathLike, str] ``` Return agent_dir. #### failed ```python @property def failed() -> bool ``` Return was exception failed or not. If it's running it's not failed. **Returns**: bool #### start ```python def start() -> Tuple[Callable, Sequence[Any]] ``` Return function and arguments to call within subprocess. #### stop ```python def stop() -> None ``` Stop task. ## AEALauncher Objects ```python class AEALauncher(AbstractMultipleRunner) ``` Run multiple AEA instances. #### `__`init`__` ```python def __init__(agent_dirs: Sequence[Union[PathLike, str]], mode: str, fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies .propagate, log_level: Optional[str] = None, password: Optional[str] = None) -> None ``` Init AEALauncher. **Arguments**: - `agent_dirs`: sequence of AEA config directories. - `mode`: executor name to use. - `fail_policy`: one of ExecutorExceptionPolicies to be used with Executor - `log_level`: debug level applied for AEA in subprocesses - `password`: the password to encrypt/decrypt the private key. ================================================ FILE: docs/api/mail/base.md ================================================ # aea.mail.base Mail module abstract base classes. ## URI Objects ```python class URI() ``` URI following RFC3986. #### `__`init`__` ```python def __init__(uri_raw: str) -> None ``` Initialize the URI. Must follow: https://tools.ietf.org/html/rfc3986.html **Arguments**: - `uri_raw`: the raw form uri #### scheme ```python @property def scheme() -> str ``` Get the scheme. #### netloc ```python @property def netloc() -> str ``` Get the netloc. #### path ```python @property def path() -> str ``` Get the path. #### params ```python @property def params() -> str ``` Get the params. #### query ```python @property def query() -> str ``` Get the query. #### fragment ```python @property def fragment() -> str ``` Get the fragment. #### username ```python @property def username() -> Optional[str] ``` Get the username. #### password ```python @property def password() -> Optional[str] ``` Get the password. #### host ```python @property def host() -> Optional[str] ``` Get the host. #### port ```python @property def port() -> Optional[int] ``` Get the port. #### `__`str`__` ```python def __str__() -> str ``` Get string representation. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare with another object. ## EnvelopeContext Objects ```python class EnvelopeContext() ``` Contains context information of an envelope. #### `__`init`__` ```python def __init__(connection_id: Optional[PublicId] = None, uri: Optional[URI] = None) -> None ``` Initialize the envelope context. **Arguments**: - `connection_id`: the connection id used for routing the outgoing envelope in the multiplexer. - `uri`: the URI sent with the envelope. #### uri ```python @property def uri() -> Optional[URI] ``` Get the URI. #### connection`_`id ```python @property def connection_id() -> Optional[PublicId] ``` Get the connection id to route the envelope. #### connection`_`id ```python @connection_id.setter def connection_id(connection_id: PublicId) -> None ``` Set the 'via' connection id. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare with another object. ## AEAConnectionError Objects ```python class AEAConnectionError(Exception) ``` Exception class for connection errors. ## Empty Objects ```python class Empty(Exception) ``` Exception for when the inbox is empty. ## EnvelopeSerializer Objects ```python class EnvelopeSerializer(ABC) ``` Abstract class to specify the serialization layer for the envelope. #### encode ```python @abstractmethod def encode(envelope: "Envelope") -> bytes ``` Encode the envelope. **Arguments**: - `envelope`: the envelope to encode **Returns**: the encoded envelope #### decode ```python @abstractmethod def decode(envelope_bytes: bytes) -> "Envelope" ``` Decode the envelope. **Arguments**: - `envelope_bytes`: the encoded envelope **Returns**: the envelope ## ProtobufEnvelopeSerializer Objects ```python class ProtobufEnvelopeSerializer(EnvelopeSerializer) ``` Envelope serializer using Protobuf. #### encode ```python def encode(envelope: "Envelope") -> bytes ``` Encode the envelope. **Arguments**: - `envelope`: the envelope to encode **Returns**: the encoded envelope #### decode ```python def decode(envelope_bytes: bytes) -> "Envelope" ``` Decode the envelope. The default serializer doesn't decode the message field. **Arguments**: - `envelope_bytes`: the encoded envelope **Returns**: the envelope ## Envelope Objects ```python class Envelope() ``` The top level message class for agent to agent communication. #### `__`init`__` ```python def __init__(to: Address, sender: Address, message: Union[Message, bytes], context: Optional[EnvelopeContext] = None, protocol_specification_id: Optional[PublicId] = None) -> None ``` Initialize a Message object. **Arguments**: - `to`: the address of the receiver. - `sender`: the address of the sender. - `message`: the protocol-specific message. - `context`: the optional envelope context. - `protocol_specification_id`: the protocol specification id (wire id). #### to ```python @property def to() -> Address ``` Get address of receiver. #### to ```python @to.setter def to(to: Address) -> None ``` Set address of receiver. #### sender ```python @property def sender() -> Address ``` Get address of sender. #### sender ```python @sender.setter def sender(sender: Address) -> None ``` Set address of sender. #### protocol`_`specification`_`id ```python @property def protocol_specification_id() -> PublicId ``` Get protocol_specification_id. #### message ```python @property def message() -> Union[Message, bytes] ``` Get the protocol-specific message. #### message ```python @message.setter def message(message: Union[Message, bytes]) -> None ``` Set the protocol-specific message. #### message`_`bytes ```python @property def message_bytes() -> bytes ``` Get the protocol-specific message. #### context ```python @property def context() -> Optional[EnvelopeContext] ``` Get the envelope context. #### to`_`as`_`public`_`id ```python @property def to_as_public_id() -> Optional[PublicId] ``` Get to as public id. #### is`_`sender`_`public`_`id ```python @property def is_sender_public_id() -> bool ``` Check if sender is a public id. #### is`_`to`_`public`_`id ```python @property def is_to_public_id() -> bool ``` Check if to is a public id. #### is`_`component`_`to`_`component`_`message ```python @property def is_component_to_component_message() -> bool ``` Whether or not the message contained is component to component. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare with another object. #### encode ```python def encode(serializer: Optional[EnvelopeSerializer] = None) -> bytes ``` Encode the envelope. **Arguments**: - `serializer`: the serializer that implements the encoding procedure. **Returns**: the encoded envelope. #### decode ```python @classmethod def decode(cls, envelope_bytes: bytes, serializer: Optional[EnvelopeSerializer] = None) -> "Envelope" ``` Decode the envelope. **Arguments**: - `envelope_bytes`: the bytes to be decoded. - `serializer`: the serializer that implements the decoding procedure. **Returns**: the decoded envelope. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation of an envelope. ================================================ FILE: docs/api/manager/manager.md ================================================ # aea.manager.manager This module contains the implementation of AEA agents manager. ## ProjectNotFoundError Objects ```python class ProjectNotFoundError(ValueError) ``` Project not found exception. ## ProjectCheckError Objects ```python class ProjectCheckError(ValueError) ``` Project check error exception. #### `__`init`__` ```python def __init__(msg: str, source_exception: Exception) ``` Init exception. ## ProjectPackageConsistencyCheckError Objects ```python class ProjectPackageConsistencyCheckError(ValueError) ``` Check consistency of package versions against already added project. #### `__`init`__` ```python def __init__(agent_project_id: PublicId, conflicting_packages: List[Tuple[PackageIdPrefix, str, str, Set[PublicId]]]) ``` Initialize the exception. **Arguments**: - `agent_project_id`: the agent project id whose addition has failed. - `conflicting_packages`: the conflicting packages. ## BaseAgentRunTask Objects ```python class BaseAgentRunTask(ABC) ``` Base abstract class for agent run tasks. #### start ```python @abstractmethod def start() -> None ``` Start task. #### wait ```python @abstractmethod def wait() -> asyncio.Future ``` Return future to wait task completed. #### stop ```python @abstractmethod def stop() -> None ``` Stop task. #### is`_`running ```python @property @abstractmethod def is_running() -> bool ``` Return is task running. ## AgentRunAsyncTask Objects ```python class AgentRunAsyncTask(BaseAgentRunTask) ``` Async task wrapper for agent. #### `__`init`__` ```python def __init__(agent: AEA, loop: asyncio.AbstractEventLoop) -> None ``` Init task with agent alias and loop. #### create`_`run`_`loop ```python def create_run_loop() -> None ``` Create run loop. #### start ```python def start() -> None ``` Start task. #### wait ```python def wait() -> asyncio.Future ``` Return future to wait task completed. #### stop ```python def stop() -> None ``` Stop task. #### run ```python async def run() -> None ``` Run task body. #### is`_`running ```python @property def is_running() -> bool ``` Return is task running. ## AgentRunThreadTask Objects ```python class AgentRunThreadTask(AgentRunAsyncTask) ``` Threaded wrapper to run agent. #### `__`init`__` ```python def __init__(agent: AEA, loop: asyncio.AbstractEventLoop) -> None ``` Init task with agent alias and loop. #### create`_`run`_`loop ```python def create_run_loop() -> None ``` Create run loop. #### start ```python def start() -> None ``` Run task in a dedicated thread. #### stop ```python def stop() -> None ``` Stop the task. ## AgentRunProcessTask Objects ```python class AgentRunProcessTask(BaseAgentRunTask) ``` Subprocess wrapper to run agent. #### `__`init`__` ```python def __init__(agent_alias: AgentAlias, loop: asyncio.AbstractEventLoop) -> None ``` Init task with agent alias and loop. #### start ```python def start() -> None ``` Run task in a dedicated process. #### wait ```python def wait() -> asyncio.Future ``` Return future to wait task completed. #### stop ```python def stop() -> None ``` Stop the task. #### is`_`running ```python @property def is_running() -> bool ``` Is agent running. ## MultiAgentManager Objects ```python class MultiAgentManager() ``` Multi agents manager. #### `__`init`__` ```python def __init__(working_dir: str, mode: str = "async", registry_path: str = DEFAULT_REGISTRY_NAME, auto_add_remove_project: bool = False, password: Optional[str] = None) -> None ``` Initialize manager. **Arguments**: - `working_dir`: directory to store base agents. - `mode`: str. async or threaded - `registry_path`: str. path to the local packages registry - `auto_add_remove_project`: bool. add/remove project on the first agent add/last agent remove - `password`: the password to encrypt/decrypt the private key. #### data`_`dir ```python @property def data_dir() -> str ``` Get the certs directory. #### get`_`data`_`dir`_`of`_`agent ```python def get_data_dir_of_agent(agent_name: str) -> str ``` Get the data directory of a specific agent. #### is`_`running ```python @property def is_running() -> bool ``` Is manager running. #### dict`_`state ```python @property def dict_state() -> Dict[str, Any] ``` Create MultiAgentManager dist state. #### projects ```python @property def projects() -> Dict[PublicId, Project] ``` Get all projects. #### add`_`error`_`callback ```python def add_error_callback( error_callback: Callable[[str, BaseException], None]) -> "MultiAgentManager" ``` Add error callback to call on error raised. #### start`_`manager ```python def start_manager(local: bool = False, remote: bool = False) -> "MultiAgentManager" ``` Start manager. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). **Arguments**: - `local`: whether or not to fetch from local registry. - `remote`: whether or not to fetch from remote registry. **Returns**: the MultiAgentManager instance. #### last`_`start`_`status ```python @property def last_start_status() -> Tuple[bool, Dict[PublicId, List[Dict]], List[Tuple[ PublicId, List[Dict], Exception]], ] ``` Get status of the last agents start loading state. #### stop`_`manager ```python def stop_manager(cleanup: bool = True, save: bool = False) -> "MultiAgentManager" ``` Stop manager. Stops all running agents and stop agent. **Arguments**: - `cleanup`: bool is cleanup on stop. - `save`: bool is save state to file on stop. **Returns**: None #### add`_`project ```python def add_project(public_id: PublicId, local: bool = False, remote: bool = False, restore: bool = False) -> "MultiAgentManager" ``` Fetch agent project and all dependencies to working_dir. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). **Arguments**: - `public_id`: the public if of the agent project. - `local`: whether or not to fetch from local registry. - `remote`: whether or not to fetch from remote registry. - `restore`: bool flag for restoring already fetched agent. **Returns**: self #### remove`_`project ```python def remove_project(public_id: PublicId, keep_files: bool = False) -> "MultiAgentManager" ``` Remove agent project. #### list`_`projects ```python def list_projects() -> List[PublicId] ``` List all agents projects added. **Returns**: list of public ids of projects #### add`_`agent ```python def add_agent(public_id: PublicId, agent_name: Optional[str] = None, agent_overrides: Optional[dict] = None, component_overrides: Optional[List[dict]] = None, local: bool = False, remote: bool = False, restore: bool = False) -> "MultiAgentManager" ``` Create new agent configuration based on project with config overrides applied. Alias is stored in memory only! **Arguments**: - `public_id`: base agent project public id - `agent_name`: unique name for the agent - `agent_overrides`: overrides for agent config. - `component_overrides`: overrides for component section. - `local`: whether or not to fetch from local registry. - `remote`: whether or not to fetch from remote registry. - `restore`: bool flag for restoring already fetched agent. **Returns**: self #### add`_`agent`_`with`_`config ```python def add_agent_with_config( public_id: PublicId, config: List[dict], agent_name: Optional[str] = None) -> "MultiAgentManager" ``` Create new agent configuration based on project with config provided. Alias is stored in memory only! **Arguments**: - `public_id`: base agent project public id - `agent_name`: unique name for the agent - `config`: agent config (used for agent re-creation). **Returns**: manager #### get`_`agent`_`overridables ```python def get_agent_overridables(agent_name: str) -> Tuple[Dict, List[Dict]] ``` Get agent config overridables. **Arguments**: - `agent_name`: str **Returns**: Tuple of agent overridables dict and and list of component overridables dict. #### set`_`agent`_`overrides ```python def set_agent_overrides( agent_name: str, agent_overides: Optional[Dict], components_overrides: Optional[List[Dict]]) -> "MultiAgentManager" ``` Set agent overrides. **Arguments**: - `agent_name`: str - `agent_overides`: optional dict of agent config overrides - `components_overrides`: optional list of dict of components overrides **Returns**: self #### list`_`agents`_`info ```python def list_agents_info() -> List[Dict[str, Any]] ``` List agents detailed info. **Returns**: list of dicts that represents agent info: public_id, name, is_running. #### list`_`agents ```python def list_agents(running_only: bool = False) -> List[str] ``` List all agents. **Arguments**: - `running_only`: returns only running if set to True **Returns**: list of agents names #### remove`_`agent ```python def remove_agent( agent_name: str, skip_project_auto_remove: bool = False) -> "MultiAgentManager" ``` Remove agent alias definition from registry. **Arguments**: - `agent_name`: agent name to remove - `skip_project_auto_remove`: disable auto project remove on last agent removed. **Returns**: None #### start`_`agent ```python def start_agent(agent_name: str) -> "MultiAgentManager" ``` Start selected agent. **Arguments**: - `agent_name`: agent name to start **Returns**: None #### start`_`all`_`agents ```python def start_all_agents() -> "MultiAgentManager" ``` Start all not started agents. **Returns**: None #### stop`_`agent ```python def stop_agent(agent_name: str) -> "MultiAgentManager" ``` Stop running agent. **Arguments**: - `agent_name`: agent name to stop **Returns**: self #### stop`_`all`_`agents ```python def stop_all_agents() -> "MultiAgentManager" ``` Stop all agents running. **Returns**: self #### stop`_`agents ```python def stop_agents(agent_names: List[str]) -> "MultiAgentManager" ``` Stop specified agents. **Arguments**: - `agent_names`: names of agents **Returns**: self #### start`_`agents ```python def start_agents(agent_names: List[str]) -> "MultiAgentManager" ``` Stop specified agents. **Arguments**: - `agent_names`: names of agents **Returns**: self #### get`_`agent`_`alias ```python def get_agent_alias(agent_name: str) -> AgentAlias ``` Return details about agent alias definition. **Arguments**: - `agent_name`: name of agent **Returns**: AgentAlias ================================================ FILE: docs/api/manager/project.md ================================================ # aea.manager.project This module contains the implementation of AEA agents project configuration. ## `_`Base Objects ```python class _Base() ``` Base class to share some methods. #### builder ```python @property def builder() -> AEABuilder ``` Get AEABuilder instance. #### install`_`pypi`_`dependencies ```python def install_pypi_dependencies() -> None ``` Install python dependencies for the project. ## Project Objects ```python class Project(_Base) ``` Agent project representation. #### `__`init`__` ```python def __init__(public_id: PublicId, path: str) -> None ``` Init project with public_id and project's path. #### build ```python def build() -> None ``` Call all build entry points. #### load ```python @classmethod def load(cls, working_dir: str, public_id: PublicId, is_local: bool = False, is_remote: bool = False, is_restore: bool = False, cli_verbosity: str = "INFO", registry_path: str = DEFAULT_REGISTRY_NAME, skip_consistency_check: bool = False, skip_aea_validation: bool = False) -> "Project" ``` Load project with given public_id to working_dir. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). **Arguments**: - `working_dir`: the working directory - `public_id`: the public id - `is_local`: whether to fetch from local - `is_remote`: whether to fetch from remote - `is_restore`: whether to restore or not - `cli_verbosity`: the logging verbosity of the CLI - `registry_path`: the path to the registry locally - `skip_consistency_check`: consistency checks flag - `skip_aea_validation`: aea validation flag **Returns**: project #### remove ```python def remove() -> None ``` Remove project, do cleanup. #### agent`_`config ```python @property def agent_config() -> AgentConfig ``` Get the agent configuration. #### builder ```python @property def builder() -> AEABuilder ``` Get builder instance. #### check ```python def check() -> None ``` Check we can still construct an AEA from the project with builder.build. ## AgentAlias Objects ```python class AgentAlias(_Base) ``` Agent alias representation. #### `__`init`__` ```python def __init__(project: Project, agent_name: str, data_dir: str, password: Optional[str] = None) ``` Init agent alias with project, config, name, agent, builder. #### set`_`agent`_`config`_`from`_`data ```python def set_agent_config_from_data(json_data: List[Dict]) -> None ``` Set agent config instance constructed from json data. **Arguments**: - `json_data`: agent config json data #### builder ```python @property def builder() -> AEABuilder ``` Get builder instance. #### agent`_`config ```python @property def agent_config() -> AgentConfig ``` Get agent config. #### remove`_`from`_`project ```python def remove_from_project() -> None ``` Remove agent alias from project. #### dict ```python @property def dict() -> Dict[str, Any] ``` Convert AgentAlias to dict. #### config`_`json ```python @property def config_json() -> List[Dict] ``` Get agent config json data. #### get`_`aea`_`instance ```python def get_aea_instance() -> AEA ``` Build new aea instance. #### issue`_`certificates ```python def issue_certificates() -> None ``` Issue the certificates for this agent. #### set`_`overrides ```python def set_overrides(agent_overrides: Optional[Dict] = None, component_overrides: Optional[List[Dict]] = None) -> None ``` Set override for this agent alias's config. #### agent`_`config`_`manager ```python @property def agent_config_manager() -> AgentConfigManager ``` Get agent configuration manager instance for the config. #### get`_`overridables ```python def get_overridables() -> Tuple[Dict, List[Dict]] ``` Get all overridables for this agent alias's config. #### get`_`addresses ```python def get_addresses() -> Dict[str, str] ``` Get addresses from private keys. **Returns**: dict with crypto id str as key and address str as value #### get`_`connections`_`addresses ```python def get_connections_addresses() -> Dict[str, str] ``` Get connections addresses from connections private keys. **Returns**: dict with crypto id str as key and address str as value ================================================ FILE: docs/api/manager/utils.md ================================================ # aea.manager.utils Multiagent manager utils. #### get`_`lib`_`path ```python def get_lib_path(env_dir: str) -> str ``` Get librarty path for env dir. #### make`_`venv ```python def make_venv(env_dir: str, set_env: bool = False) -> None ``` Make venv and update variable to use it. **Arguments**: - `env_dir`: str, path for new env dir - `set_env`: bool. use evn within this python process (update, sys.executable and sys.path) #### project`_`install`_`and`_`build ```python def project_install_and_build(project: Project) -> None ``` Install project dependencies and build required components. #### get`_`venv`_`dir`_`for`_`project ```python def get_venv_dir_for_project(project: Project) -> str ``` Get virtual env directory for project specified. #### project`_`check ```python def project_check(project: Project) -> None ``` Perform project loads well. #### run`_`in`_`venv ```python def run_in_venv(env_dir: str, fn: Callable, timeout: float, *args: Any) -> Any ``` Run python function in a dedicated process with virtual env specified. ================================================ FILE: docs/api/multiplexer.md ================================================ # aea.multiplexer Module for the multiplexer class and related classes. ## MultiplexerStatus Objects ```python class MultiplexerStatus(AsyncState) ``` The connection status class. #### `__`init`__` ```python def __init__() -> None ``` Initialize the connection status. #### is`_`connected ```python @property def is_connected() -> bool ``` Return is connected. #### is`_`connecting ```python @property def is_connecting() -> bool ``` Return is connecting. #### is`_`disconnected ```python @property def is_disconnected() -> bool ``` Return is disconnected. #### is`_`disconnecting ```python @property def is_disconnecting() -> bool ``` Return is disconnected. ## AsyncMultiplexer Objects ```python class AsyncMultiplexer(Runnable, WithLogger) ``` This class can handle multiple connections at once. #### `__`init`__` ```python def __init__( connections: Optional[Sequence[Connection]] = None, default_connection_index: int = 0, loop: Optional[AbstractEventLoop] = None, exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum.propagate, threaded: bool = False, agent_name: str = "standalone", default_routing: Optional[Dict[PublicId, PublicId]] = None, default_connection: Optional[PublicId] = None, protocols: Optional[List[Union[Protocol, Message]]] = None) -> None ``` Initialize the connection multiplexer. **Arguments**: - `connections`: a sequence of connections. - `default_connection_index`: the index of the connection to use as default. This information is used for envelopes which don't specify any routing context. If connections is None, this parameter is ignored. - `loop`: the event loop to run the multiplexer. If None, a new event loop is created. - `exception_policy`: the exception policy used for connections. - `threaded`: if True, run in threaded mode, else async - `agent_name`: the name of the agent that owns the multiplexer, for logging purposes. - `default_routing`: default routing map - `default_connection`: default connection - `protocols`: protocols used #### default`_`connection ```python @property def default_connection() -> Optional[Connection] ``` Get the default connection. #### in`_`queue ```python @property def in_queue() -> AsyncFriendlyQueue ``` Get the in queue. #### out`_`queue ```python @property def out_queue() -> asyncio.Queue ``` Get the out queue. #### connections ```python @property def connections() -> Tuple[Connection, ...] ``` Get the connections. #### is`_`connected ```python @property def is_connected() -> bool ``` Check whether the multiplexer is processing envelopes. #### default`_`routing ```python @property def default_routing() -> Dict[PublicId, PublicId] ``` Get the default routing. #### default`_`routing ```python @default_routing.setter def default_routing(default_routing: Dict[PublicId, PublicId]) -> None ``` Set the default routing. #### connection`_`status ```python @property def connection_status() -> MultiplexerStatus ``` Get the connection status. #### run ```python async def run() -> None ``` Run multiplexer connect and receive/send tasks. #### set`_`loop ```python def set_loop(loop: AbstractEventLoop) -> None ``` Set event loop and all event loop related objects. **Arguments**: - `loop`: asyncio event loop. #### add`_`connection ```python def add_connection(connection: Connection, is_default: bool = False) -> None ``` Add a connection to the multiplexer. **Arguments**: - `connection`: the connection to add. - `is_default`: whether the connection added should be the default one. #### connect ```python async def connect() -> None ``` Connect the multiplexer. #### disconnect ```python async def disconnect() -> None ``` Disconnect the multiplexer. #### get ```python def get(block: bool = False, timeout: Optional[float] = None) -> Optional[Envelope] ``` Get an envelope within a timeout. **Arguments**: - `block`: make the call blocking (ignore the timeout). - `timeout`: the timeout to wait until an envelope is received. **Returns**: the envelope, or None if no envelope is available within a timeout. #### async`_`get ```python async def async_get() -> Envelope ``` Get an envelope async way. **Returns**: the envelope #### async`_`wait ```python async def async_wait() -> None ``` Get an envelope async way. **Returns**: the envelope #### put ```python def put(envelope: Envelope) -> None ``` Schedule an envelope for sending it. Notice that the output queue is an asyncio.Queue which uses an event loop running on a different thread than the one used in this function. **Arguments**: - `envelope`: the envelope to be sent. ## Multiplexer Objects ```python class Multiplexer(AsyncMultiplexer) ``` Transit sync multiplexer for compatibility. #### `__`init`__` ```python def __init__(*args: Any, **kwargs: Any) -> None ``` Initialize the connection multiplexer. **Arguments**: - `args`: arguments - `kwargs`: keyword arguments #### set`_`loop ```python def set_loop(loop: AbstractEventLoop) -> None ``` Set event loop and all event loop related objects. **Arguments**: - `loop`: asyncio event loop. #### connect ```python def connect() -> None ``` Connect the multiplexer. Synchronously in thread spawned if new loop created. #### disconnect ```python def disconnect() -> None ``` Disconnect the multiplexer. Also stops a dedicated thread for event loop if spawned on connect. #### put ```python def put(envelope: Envelope) -> None ``` Schedule an envelope for sending it. Notice that the output queue is an asyncio.Queue which uses an event loop running on a different thread than the one used in this function. **Arguments**: - `envelope`: the envelope to be sent. ## InBox Objects ```python class InBox() ``` A queue from where you can only consume envelopes. #### `__`init`__` ```python def __init__(multiplexer: AsyncMultiplexer) -> None ``` Initialize the inbox. **Arguments**: - `multiplexer`: the multiplexer #### empty ```python def empty() -> bool ``` Check for a envelope on the in queue. **Returns**: boolean indicating whether there is an envelope or not #### get ```python def get(block: bool = False, timeout: Optional[float] = None) -> Envelope ``` Check for a envelope on the in queue. **Arguments**: - `block`: make the call blocking (ignore the timeout). - `timeout`: times out the block after timeout seconds. **Raises**: - `Empty`: if the attempt to get an envelope fails. **Returns**: the envelope object. #### get`_`nowait ```python def get_nowait() -> Optional[Envelope] ``` Check for a envelope on the in queue and wait for no time. **Returns**: the envelope object #### async`_`get ```python async def async_get() -> Envelope ``` Check for a envelope on the in queue. **Returns**: the envelope object. #### async`_`wait ```python async def async_wait() -> None ``` Check for a envelope on the in queue. ## OutBox Objects ```python class OutBox() ``` A queue from where you can only enqueue envelopes. #### `__`init`__` ```python def __init__(multiplexer: AsyncMultiplexer) -> None ``` Initialize the outbox. **Arguments**: - `multiplexer`: the multiplexer #### empty ```python def empty() -> bool ``` Check for a envelope on the in queue. **Returns**: boolean indicating whether there is an envelope or not #### put ```python def put(envelope: Envelope) -> None ``` Put an envelope into the queue. **Arguments**: - `envelope`: the envelope. #### put`_`message ```python def put_message(message: Message, context: Optional[EnvelopeContext] = None) -> None ``` Put a message in the outbox. This constructs an envelope with the input arguments. **Arguments**: - `message`: the message - `context`: the envelope context ================================================ FILE: docs/api/plugins/aea_cli_ipfs/core.md ================================================ # plugins.aea-cli-ipfs.aea`_`cli`_`ipfs.core Core components for `ipfs cli command`. #### ipfs ```python @click.group() @click.pass_context def ipfs(click_context: click.Context) -> None ``` IPFS Commands #### process`_`result ```python @ipfs.result_callback() @click.pass_context def process_result(click_context: click.Context, *_: Any) -> None ``` Tear down command group. #### add ```python @ipfs.command() @click.argument( "dir_path", type=click.Path(exists=True, dir_okay=True, file_okay=False, resolve_path=True, readable=True), required=False, ) @click.option("-p", "--publish", is_flag=True) @click.option("--no-pin", is_flag=True) @click.pass_context def add(click_context: click.Context, dir_path: Optional[str], publish: bool = False, no_pin: bool = False) -> None ``` Add directory to ipfs, if not directory specified the current one will be added. #### remove ```python @ipfs.command() @click.argument( "hash_", metavar="hash", type=str, required=True, ) @click.pass_context def remove(click_context: click.Context, hash_: str) -> None ``` Remove a directory from ipfs by it's hash. #### download ```python @ipfs.command() @click.argument( "hash_", metavar="hash", type=str, required=True, ) @click.argument( "target_dir", type=click.Path(dir_okay=True, file_okay=False, resolve_path=True), required=False, ) @click.pass_context def download(click_context: click.Context, hash_: str, target_dir: Optional[str]) -> None ``` Download directory by it's hash, if not target directory specified will use current one. ================================================ FILE: docs/api/plugins/aea_cli_ipfs/ipfs_utils.md ================================================ # plugins.aea-cli-ipfs.aea`_`cli`_`ipfs.ipfs`_`utils Ipfs utils for `ipfs cli command`. ## IPFSDaemon Objects ```python class IPFSDaemon() ``` Set up the IPFS daemon. **Raises**: - `Exception`: if IPFS is not installed. #### `__`init`__` ```python def __init__() -> None ``` Initialise IPFS daemon. #### is`_`started ```python def is_started() -> bool ``` Check daemon was started. #### start ```python def start() -> None ``` Run the ipfs daemon. #### stop ```python def stop() -> None ``` Terminate the ipfs daemon. ## BaseIPFSToolException Objects ```python class BaseIPFSToolException(Exception) ``` Base ipfs tool exception. ## RemoveError Objects ```python class RemoveError(BaseIPFSToolException) ``` Exception on remove. ## PublishError Objects ```python class PublishError(BaseIPFSToolException) ``` Exception on publish. ## NodeError Objects ```python class NodeError(BaseIPFSToolException) ``` Exception for node connection check. ## DownloadError Objects ```python class DownloadError(BaseIPFSToolException) ``` Exception on download failed. ## IPFSTool Objects ```python class IPFSTool() ``` IPFS tool to add, publish, remove, download directories. #### `__`init`__` ```python def __init__(client_options: Optional[Dict] = None) ``` Init tool. **Arguments**: - `client_options`: dict, options for ipfshttpclient instance. #### add ```python def add(dir_path: str, pin: bool = True) -> Tuple[str, str, List] ``` Add directory to ipfs. It wraps into directory. **Arguments**: - `dir_path`: str, path to dir to publish - `pin`: bool, pin object or not **Returns**: dir name published, hash, list of items processed #### remove ```python def remove(hash_id: str) -> Dict ``` Remove dir added by it's hash. **Arguments**: - `hash_id`: str. hash of dir to remove **Returns**: dict with unlinked items. #### download ```python def download(hash_id: str, target_dir: str, fix_path: bool = True) -> None ``` Download dir by it's hash. **Arguments**: - `hash_id`: str. hash of file to download - `target_dir`: str. directory to place downloaded - `fix_path`: bool. default True. on download don't wrap result in to hash_id directory. #### publish ```python def publish(hash_id: str) -> Dict ``` Publish directory by it's hash id. **Arguments**: - `hash_id`: hash of the directory to publish. **Returns**: dict of names it was publish for. #### chec`_`ipfs`_`node`_`running ```python def chec_ipfs_node_running() -> None ``` Check ipfs node running. ================================================ FILE: docs/api/plugins/aea_ledger_cosmos/cosmos.md ================================================ # plugins.aea-ledger-cosmos.aea`_`ledger`_`cosmos.cosmos Cosmos module wrapping the public and private key cryptography and ledger api. ## DataEncrypt Objects ```python class DataEncrypt() ``` Class to encrypt/decrypt data strings with password provided. #### encrypt ```python @classmethod def encrypt(cls, data: bytes, password: str) -> bytes ``` Encrypt data with password. #### bytes`_`encode ```python @staticmethod def bytes_encode(data: bytes) -> str ``` Encode bytes to ascii friendly string. #### bytes`_`decode ```python @staticmethod def bytes_decode(data: str) -> bytes ``` Decode ascii friendly string to bytes. #### decrypt ```python @classmethod def decrypt(cls, encrypted_data: bytes, password: str) -> bytes ``` Decrypt data with password provided. ## CosmosHelper Objects ```python class CosmosHelper(Helper) ``` Helper class usable as Mixin for CosmosApi or as standalone class. #### is`_`transaction`_`settled ```python @staticmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool ``` Check whether a transaction is settled or not. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: True if the transaction has been settled, False o/w. #### get`_`code`_`id ```python @classmethod def get_code_id(cls, tx_receipt: JSONLike) -> Optional[int] ``` Retrieve the `code_id` from a transaction receipt. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: the code id, if present #### get`_`event`_`attributes ```python @staticmethod def get_event_attributes(tx_receipt: JSONLike) -> Dict ``` Retrieve events attributes from tx receipt. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: dict #### get`_`contract`_`address ```python @classmethod def get_contract_address(cls, tx_receipt: JSONLike) -> Optional[str] ``` Retrieve the `contract_address` from a transaction receipt. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: the contract address, if present #### is`_`transaction`_`valid ```python @staticmethod def is_transaction_valid(tx: JSONLike, seller: Address, client: Address, tx_nonce: str, amount: int) -> bool ``` Check whether a transaction is valid or not. **Arguments**: - `tx`: the transaction. - `seller`: the address of the seller. - `client`: the address of the client. - `tx_nonce`: the transaction nonce. - `amount`: the amount we expect to get from the transaction. **Returns**: True if the random_message is equals to tx['input'] #### generate`_`tx`_`nonce ```python @staticmethod def generate_tx_nonce(seller: Address, client: Address) -> str ``` Generate a unique hash to distinguish transactions with the same terms. **Arguments**: - `seller`: the address of the seller. - `client`: the address of the client. **Returns**: return the hash in hex. #### get`_`address`_`from`_`public`_`key ```python @classmethod def get_address_from_public_key(cls, public_key: str) -> str ``` Get the address from the public key. **Arguments**: - `public_key`: the public key **Returns**: str #### recover`_`message ```python @classmethod def recover_message(cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[Address, ...] ``` Recover the addresses from the hash. **Arguments**: - `message`: the message we expect - `signature`: the transaction signature - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered addresses #### recover`_`public`_`keys`_`from`_`message ```python @classmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[str, ...] ``` Get the public key used to produce the `signature` of the `message` **Arguments**: - `message`: raw bytes used to produce signature - `signature`: signature of the message - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered public keys #### get`_`hash ```python @staticmethod def get_hash(message: bytes) -> str ``` Get the hash of a message. **Arguments**: - `message`: the message to be hashed. **Returns**: the hash of the message. #### is`_`valid`_`address ```python @classmethod def is_valid_address(cls, address: Address) -> bool ``` Check if the address is valid. **Arguments**: - `address`: the address to validate **Returns**: whether address is valid or not #### load`_`contract`_`interface ```python @classmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str] ``` Load contract interface. **Arguments**: - `file_path`: the file path to the interface **Returns**: the interface ## CosmosCrypto Objects ```python class CosmosCrypto(Crypto[SigningKey]) ``` Class wrapping the Account Generation from Ethereum ledger. #### `__`init`__` ```python def __init__(private_key_path: Optional[str] = None, password: Optional[str] = None) -> None ``` Instantiate an ethereum crypto object. **Arguments**: - `private_key_path`: the private key path of the agent - `password`: the password to encrypt/decrypt the private key. #### private`_`key ```python @property def private_key() -> str ``` Return a private key. **Returns**: a private key string #### public`_`key ```python @property def public_key() -> str ``` Return a public key in hex format. **Returns**: a public key string in hex format #### address ```python @property def address() -> str ``` Return the address for the key pair. **Returns**: a display_address str #### load`_`private`_`key`_`from`_`path ```python @classmethod def load_private_key_from_path(cls, file_name: str, password: Optional[str] = None) -> SigningKey ``` Load a private key in hex format from a file. **Arguments**: - `file_name`: the path to the hex file. - `password`: the password to encrypt/decrypt the private key. **Returns**: the Entity. #### sign`_`message ```python def sign_message(message: bytes, is_deprecated_mode: bool = False) -> str ``` Sign a message in bytes string form. **Arguments**: - `message`: the message to be signed - `is_deprecated_mode`: if the deprecated signing is used **Returns**: signature of the message in string form #### sign`_`transaction ```python def sign_transaction(transaction: JSONLike) -> JSONLike ``` Sign a transaction in bytes string form. **Arguments**: - `transaction`: the transaction to be signed **Returns**: signed transaction #### generate`_`private`_`key ```python @classmethod def generate_private_key(cls) -> SigningKey ``` Generate a key pair for cosmos network. #### encrypt ```python def encrypt(password: str) -> str ``` Encrypt the private key and return in json. **Arguments**: - `password`: the password to decrypt. **Returns**: json string containing encrypted private key. #### decrypt ```python @classmethod def decrypt(cls, keyfile_json: str, password: str) -> str ``` Decrypt the private key and return in raw form. **Arguments**: - `keyfile_json`: json string containing encrypted private key. - `password`: the password to decrypt. **Returns**: the raw private key. ## `_`CosmosApi Objects ```python class _CosmosApi(LedgerApi) ``` Class to interact with the Cosmos SDK via a HTTP APIs. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize the Cosmos ledger APIs. #### api ```python @property def api() -> Any ``` Get the underlying API object. #### get`_`balance ```python def get_balance(address: Address) -> Optional[int] ``` Get the balance of a given account. #### get`_`state ```python def get_state(callable_name: str, *args: Any, **kwargs: Any) -> Optional[JSONLike] ``` Call a specified function on the ledger API. Based on the cosmos REST API specification, which takes a path (strings separated by '/'). The convention here is to define the root of the path (txs, blocks, etc.) as the callable_name and the rest of the path as args. **Arguments**: - `callable_name`: name of the callable - `args`: positional arguments - `kwargs`: keyword arguments **Returns**: the transaction dictionary #### get`_`deploy`_`transaction ```python def get_deploy_transaction(contract_interface: Dict[str, str], deployer_address: Address, **kwargs: Any) -> Optional[JSONLike] ``` Get the transaction to deploy the smart contract. Dispatches to _get_storage_transaction and _get_init_transaction based on kwargs. **Arguments**: - `contract_interface`: the contract interface. - `deployer_address`: The address that will deploy the contract. - `kwargs`: keyword arguments. **Returns**: the transaction dictionary. #### get`_`handle`_`transaction ```python def get_handle_transaction( sender_address: Address, contract_address: Address, handle_msg: Any, amount: int, tx_fee: int, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None) -> Optional[JSONLike] ``` Create a CosmWasm HandleMsg transaction. **Arguments**: - `sender_address`: the sender address of the message initiator. - `contract_address`: the address of the smart contract. - `handle_msg`: HandleMsg in JSON format. - `amount`: Funds amount sent with transaction. - `tx_fee`: the tx fee accepted. - `denom`: the name of the denomination of the contract funds - `gas`: Maximum amount of gas to be used on executing command. - `memo`: any string comment. - `chain_id`: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet). - `account_number`: Account number - `sequence`: Sequence - `tx_fee_denom`: Denomination of tx_fee, identical with denom param when None **Returns**: the unsigned CosmWasm HandleMsg #### execute`_`contract`_`query ```python def execute_contract_query(contract_address: Address, query_msg: JSONLike) -> Optional[JSONLike] ``` Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing. **Arguments**: - `contract_address`: the address of the smart contract. - `query_msg`: QueryMsg in JSON format. **Returns**: the message receipt #### get`_`transfer`_`transaction ```python def get_transfer_transaction(sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None, **kwargs: Any) -> Optional[JSONLike] ``` Submit a transfer transaction to the ledger. **Arguments**: - `sender_address`: the sender address of the payer. - `destination_address`: the destination address of the payee. - `amount`: the amount of wealth to be transferred. - `tx_fee`: the transaction fee. - `tx_nonce`: verifies the authenticity of the tx - `denom`: the denomination of tx fee and amount - `gas`: the gas used. - `memo`: memo to include in tx. - `chain_id`: the chain ID of the transaction. - `account_number`: Account number - `sequence`: Sequence - `tx_fee_denom`: Denomination of tx_fee, identical with denom param when None - `kwargs`: keyword arguments. **Returns**: the transfer transaction #### get`_`packed`_`exec`_`msg ```python def get_packed_exec_msg(sender_address: Address, contract_address: str, msg: JSONLike, funds: int = 0, denom: Optional[str] = None) -> ProtoAny ``` Create and pack MsgExecuteContract **Arguments**: - `sender_address`: Address of sender - `contract_address`: Address of contract - `msg`: Paramaters to be passed to smart contract - `funds`: Funds to be sent to smart contract - `denom`: the denomination of funds **Returns**: Packed MsgExecuteContract #### get`_`packed`_`send`_`msg ```python def get_packed_send_msg(from_address: Address, to_address: Address, amount: int, denom: Optional[str] = None) -> ProtoAny ``` Generate and pack MsgSend **Arguments**: - `from_address`: Address of sender - `to_address`: Address of recipient - `amount`: amount of coins to be sent - `denom`: the denomination of and amount **Returns**: packer ProtoAny type message #### get`_`multi`_`transaction ```python def get_multi_transaction(from_addresses: List[str], pub_keys: Optional[List[bytes]], msgs: List[ProtoAny], gas: int, tx_fee: int = 0, memo: str = "", chain_id: Optional[str] = None, denom: Optional[str] = None, tx_fee_denom: Optional[str] = None) -> JSONLike ``` Generate transaction with multiple messages **Arguments**: - `from_addresses`: Addresses of signers - `pub_keys`: Public keys of signers - `msgs`: Messages to be included in transaction - `gas`: the gas used. - `tx_fee`: the transaction fee. - `memo`: memo to include in tx. - `chain_id`: the chain ID of the transaction. - `denom`: the denomination of tx fee - `tx_fee_denom`: Denomination of tx_fee, identical with denom param when None **Raises**: - `None`: RuntimeError if number of pubkeys is not equal to number of from_addresses **Returns**: the transaction #### send`_`signed`_`transaction ```python def send_signed_transaction(tx_signed: JSONLike) -> Optional[str] ``` Send a signed transaction and wait for confirmation. **Arguments**: - `tx_signed`: the signed transaction **Returns**: tx_digest, if present #### get`_`transaction`_`receipt ```python def get_transaction_receipt(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction receipt for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx receipt, if present #### get`_`transaction ```python def get_transaction(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx, if present #### get`_`contract`_`instance ```python def get_contract_instance(contract_interface: Dict[str, str], contract_address: Optional[str] = None) -> Any ``` Get the instance of a contract. **Arguments**: - `contract_interface`: the contract interface. - `contract_address`: the contract address. **Returns**: the contract instance #### update`_`with`_`gas`_`estimate ```python def update_with_gas_estimate(transaction: JSONLike) -> JSONLike ``` Attempts to update the transaction with a gas estimate **Arguments**: - `transaction`: the transaction **Raises**: - `None`: NotImplementedError ## CosmosApi Objects ```python class CosmosApi(_CosmosApi, CosmosHelper) ``` Class to interact with the Cosmos SDK via a HTTP APIs. ## CosmosFaucetApi Objects ```python class CosmosFaucetApi(FaucetApi) ``` Cosmos testnet faucet API. #### `__`init`__` ```python def __init__(poll_interval: Optional[float] = None, final_wait_interval: Optional[float] = None) ``` Initialize CosmosFaucetApi. #### get`_`wealth ```python def get_wealth(address: Address, url: Optional[str] = None) -> None ``` Get wealth from the faucet for the provided address. **Arguments**: - `address`: the address. - `url`: the url **Raises**: - `None`: RuntimeError of explicit faucet failures ================================================ FILE: docs/api/plugins/aea_ledger_ethereum/ethereum.md ================================================ # plugins.aea-ledger-ethereum.aea`_`ledger`_`ethereum.ethereum Ethereum module wrapping the public and private key cryptography and ledger api. #### get`_`gas`_`price`_`strategy ```python def get_gas_price_strategy( gas_price_strategy: Optional[str] = None, api_key: Optional[str] = None) -> Callable[[Web3, TxParams], Wei] ``` Get the gas price strategy. ## SignedTransactionTranslator Objects ```python class SignedTransactionTranslator() ``` Translator for SignedTransaction. #### to`_`dict ```python @staticmethod def to_dict( signed_transaction: SignedTransaction) -> Dict[str, Union[str, int]] ``` Write SignedTransaction to dict. #### from`_`dict ```python @staticmethod def from_dict(signed_transaction_dict: JSONLike) -> SignedTransaction ``` Get SignedTransaction from dict. ## AttributeDictTranslator Objects ```python class AttributeDictTranslator() ``` Translator for AttributeDict. #### to`_`dict ```python @classmethod def to_dict(cls, attr_dict: Union[AttributeDict, TxReceipt, TxData]) -> JSONLike ``` Simplify to dict. #### from`_`dict ```python @classmethod def from_dict(cls, di: JSONLike) -> AttributeDict ``` Get back attribute dict. ## EthereumCrypto Objects ```python class EthereumCrypto(Crypto[Account]) ``` Class wrapping the Account Generation from Ethereum ledger. #### `__`init`__` ```python def __init__(private_key_path: Optional[str] = None, password: Optional[str] = None) -> None ``` Instantiate an ethereum crypto object. **Arguments**: - `private_key_path`: the private key path of the agent - `password`: the password to encrypt/decrypt the private key. #### private`_`key ```python @property def private_key() -> str ``` Return a private key. **Returns**: a private key string #### public`_`key ```python @property def public_key() -> str ``` Return a public key in hex format. **Returns**: a public key string in hex format #### address ```python @property def address() -> str ``` Return the address for the key pair. **Returns**: a display_address str #### load`_`private`_`key`_`from`_`path ```python @classmethod def load_private_key_from_path(cls, file_name: str, password: Optional[str] = None) -> Account ``` Load a private key in hex format from a file. **Arguments**: - `file_name`: the path to the hex file. - `password`: the password to encrypt/decrypt the private key. **Returns**: the Entity. #### sign`_`message ```python def sign_message(message: bytes, is_deprecated_mode: bool = False) -> str ``` Sign a message in bytes string form. **Arguments**: - `message`: the message to be signed - `is_deprecated_mode`: if the deprecated signing is used **Returns**: signature of the message in string form #### sign`_`transaction ```python def sign_transaction(transaction: JSONLike) -> JSONLike ``` Sign a transaction in bytes string form. **Arguments**: - `transaction`: the transaction to be signed **Returns**: signed transaction #### generate`_`private`_`key ```python @classmethod def generate_private_key(cls) -> Account ``` Generate a key pair for ethereum network. #### encrypt ```python def encrypt(password: str) -> str ``` Encrypt the private key and return in json. **Arguments**: - `password`: the password to decrypt. **Returns**: json string containing encrypted private key. #### decrypt ```python @classmethod def decrypt(cls, keyfile_json: str, password: str) -> str ``` Decrypt the private key and return in raw form. **Arguments**: - `keyfile_json`: json str containing encrypted private key. - `password`: the password to decrypt. **Returns**: the raw private key. ## EthereumHelper Objects ```python class EthereumHelper(Helper) ``` Helper class usable as Mixin for EthereumApi or as standalone class. #### is`_`transaction`_`settled ```python @staticmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool ``` Check whether a transaction is settled or not. **Arguments**: - `tx_receipt`: the receipt associated to the transaction. **Returns**: True if the transaction has been settled, False o/w. #### get`_`contract`_`address ```python @staticmethod def get_contract_address(tx_receipt: JSONLike) -> Optional[str] ``` Retrieve the `contract_address` from a transaction receipt. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: the contract address, if present #### is`_`transaction`_`valid ```python @staticmethod def is_transaction_valid(tx: dict, seller: Address, client: Address, tx_nonce: str, amount: int) -> bool ``` Check whether a transaction is valid or not. **Arguments**: - `tx`: the transaction. - `seller`: the address of the seller. - `client`: the address of the client. - `tx_nonce`: the transaction nonce. - `amount`: the amount we expect to get from the transaction. **Returns**: True if the random_message is equals to tx['input'] #### generate`_`tx`_`nonce ```python @staticmethod def generate_tx_nonce(seller: Address, client: Address) -> str ``` Generate a unique hash to distinguish transactions with the same terms. **Arguments**: - `seller`: the address of the seller. - `client`: the address of the client. **Returns**: return the hash in hex. #### get`_`address`_`from`_`public`_`key ```python @classmethod def get_address_from_public_key(cls, public_key: str) -> str ``` Get the address from the public key. **Arguments**: - `public_key`: the public key **Returns**: str #### recover`_`message ```python @classmethod def recover_message(cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[Address, ...] ``` Recover the addresses from the hash. **Arguments**: - `message`: the message we expect - `signature`: the transaction signature - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered addresses #### recover`_`public`_`keys`_`from`_`message ```python @classmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[str, ...] ``` Get the public key used to produce the `signature` of the `message` **Arguments**: - `message`: raw bytes used to produce signature - `signature`: signature of the message - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered public keys #### get`_`hash ```python @staticmethod def get_hash(message: bytes) -> str ``` Get the hash of a message. **Arguments**: - `message`: the message to be hashed. **Returns**: the hash of the message. #### load`_`contract`_`interface ```python @classmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str] ``` Load contract interface. **Arguments**: - `file_path`: the file path to the interface **Returns**: the interface ## EthereumApi Objects ```python class EthereumApi(LedgerApi, EthereumHelper) ``` Class to interact with the Ethereum Web3 APIs. #### `__`init`__` ```python def __init__(**kwargs: Any) ``` Initialize the Ethereum ledger APIs. **Arguments**: - `kwargs`: keyword arguments #### api ```python @property def api() -> Web3 ``` Get the underlying API object. #### get`_`balance ```python def get_balance(address: Address) -> Optional[int] ``` Get the balance of a given account. #### get`_`state ```python def get_state(callable_name: str, *args: Any, **kwargs: Any) -> Optional[JSONLike] ``` Call a specified function on the ledger API. #### get`_`transfer`_`transaction ```python def get_transfer_transaction(sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, chain_id: Optional[int] = None, gas_price: Optional[str] = None, gas_price_strategy: Optional[str] = None, **kwargs: Any) -> Optional[JSONLike] ``` Submit a transfer transaction to the ledger. **Arguments**: - `sender_address`: the sender address of the payer. - `destination_address`: the destination address of the payee. - `amount`: the amount of wealth to be transferred (in Wei). - `tx_fee`: the transaction fee (gas) to be used (in Wei). - `tx_nonce`: verifies the authenticity of the tx. - `chain_id`: the Chain ID of the Ethereum transaction. - `gas_price`: the gas price (in Wei) - `gas_price_strategy`: the gas price strategy to be used. - `kwargs`: keyword arguments **Returns**: the transfer transaction #### update`_`with`_`gas`_`estimate ```python def update_with_gas_estimate(transaction: JSONLike) -> JSONLike ``` Attempts to update the transaction with a gas estimate **Arguments**: - `transaction`: the transaction **Returns**: the updated transaction #### send`_`signed`_`transaction ```python def send_signed_transaction(tx_signed: JSONLike) -> Optional[str] ``` Send a signed transaction and wait for confirmation. **Arguments**: - `tx_signed`: the signed transaction **Returns**: tx_digest, if present #### get`_`transaction`_`receipt ```python def get_transaction_receipt(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction receipt for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx receipt, if present #### get`_`transaction ```python def get_transaction(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx, if present #### get`_`contract`_`instance ```python def get_contract_instance(contract_interface: Dict[str, str], contract_address: Optional[str] = None) -> Any ``` Get the instance of a contract. **Arguments**: - `contract_interface`: the contract interface. - `contract_address`: the contract address. **Returns**: the contract instance #### get`_`deploy`_`transaction ```python def get_deploy_transaction(contract_interface: Dict[str, str], deployer_address: Address, value: int = 0, gas: int = 0, gas_price: Optional[str] = None, gas_price_strategy: Optional[str] = None, **kwargs: Any) -> Optional[JSONLike] ``` Get the transaction to deploy the smart contract. **Arguments**: - `contract_interface`: the contract interface. - `deployer_address`: The address that will deploy the contract. - `value`: value to send to contract (in Wei) - `gas`: the gas to be used (in Wei) - `gas_price`: the gas price (in Wei) - `gas_price_strategy`: the gas price strategy to be used. - `kwargs`: keyword arguments **Returns**: the transaction dictionary. #### is`_`valid`_`address ```python @classmethod def is_valid_address(cls, address: Address) -> bool ``` Check if the address is valid. **Arguments**: - `address`: the address to validate **Returns**: whether the address is valid ## EthereumFaucetApi Objects ```python class EthereumFaucetApi(FaucetApi) ``` Ethereum testnet faucet API. #### get`_`wealth ```python def get_wealth(address: Address, url: Optional[str] = None) -> None ``` Get wealth from the faucet for the provided address. **Arguments**: - `address`: the address. - `url`: the url ## LruLockWrapper Objects ```python class LruLockWrapper() ``` Wrapper for LRU with threading.Lock. #### `__`init`__` ```python def __init__(lru: LRU) -> None ``` Init wrapper. #### `__`getitem`__` ```python def __getitem__(*args: Any, **kwargs: Any) -> Any ``` Get item #### `__`setitem`__` ```python def __setitem__(*args: Any, **kwargs: Any) -> Any ``` Set item. #### `__`contains`__` ```python def __contains__(*args: Any, **kwargs: Any) -> Any ``` Contain item. #### `__`delitem`__` ```python def __delitem__(*args: Any, **kwargs: Any) -> Any ``` Del item. #### set`_`wrapper`_`for`_`web3py`_`session`_`cache ```python def set_wrapper_for_web3py_session_cache() -> None ``` Wrap web3py session cache with threading.Lock. ================================================ FILE: docs/api/plugins/aea_ledger_fetchai/_cosmos.md ================================================ # plugins.aea-ledger-fetchai.aea`_`ledger`_`fetchai.`_`cosmos Cosmos module wrapping the public and private key cryptography and ledger api. ## DataEncrypt Objects ```python class DataEncrypt() ``` Class to encrypt/decrypt data strings with password provided. #### encrypt ```python @classmethod def encrypt(cls, data: bytes, password: str) -> bytes ``` Encrypt data with password. #### bytes`_`encode ```python @staticmethod def bytes_encode(data: bytes) -> str ``` Encode bytes to ascii friendly string. #### bytes`_`decode ```python @staticmethod def bytes_decode(data: str) -> bytes ``` Decode ascii friendly string to bytes. #### decrypt ```python @classmethod def decrypt(cls, encrypted_data: bytes, password: str) -> bytes ``` Decrypt data with password provided. ## CosmosHelper Objects ```python class CosmosHelper(Helper) ``` Helper class usable as Mixin for CosmosApi or as standalone class. #### is`_`transaction`_`settled ```python @staticmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool ``` Check whether a transaction is settled or not. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: True if the transaction has been settled, False o/w. #### get`_`code`_`id ```python @classmethod def get_code_id(cls, tx_receipt: JSONLike) -> Optional[int] ``` Retrieve the `code_id` from a transaction receipt. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: the code id, if present #### get`_`event`_`attributes ```python @staticmethod def get_event_attributes(tx_receipt: JSONLike) -> Dict ``` Retrieve events attributes from tx receipt. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: dict #### get`_`contract`_`address ```python @classmethod def get_contract_address(cls, tx_receipt: JSONLike) -> Optional[str] ``` Retrieve the `contract_address` from a transaction receipt. **Arguments**: - `tx_receipt`: the receipt of the transaction. **Returns**: the contract address, if present #### is`_`transaction`_`valid ```python @staticmethod def is_transaction_valid(tx: JSONLike, seller: Address, client: Address, tx_nonce: str, amount: int) -> bool ``` Check whether a transaction is valid or not. **Arguments**: - `tx`: the transaction. - `seller`: the address of the seller. - `client`: the address of the client. - `tx_nonce`: the transaction nonce. - `amount`: the amount we expect to get from the transaction. **Returns**: True if the random_message is equals to tx['input'] #### generate`_`tx`_`nonce ```python @staticmethod def generate_tx_nonce(seller: Address, client: Address) -> str ``` Generate a unique hash to distinguish transactions with the same terms. **Arguments**: - `seller`: the address of the seller. - `client`: the address of the client. **Returns**: return the hash in hex. #### get`_`address`_`from`_`public`_`key ```python @classmethod def get_address_from_public_key(cls, public_key: str) -> str ``` Get the address from the public key. **Arguments**: - `public_key`: the public key **Returns**: str #### recover`_`message ```python @classmethod def recover_message(cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[Address, ...] ``` Recover the addresses from the hash. **Arguments**: - `message`: the message we expect - `signature`: the transaction signature - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered addresses #### recover`_`public`_`keys`_`from`_`message ```python @classmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[str, ...] ``` Get the public key used to produce the `signature` of the `message` **Arguments**: - `message`: raw bytes used to produce signature - `signature`: signature of the message - `is_deprecated_mode`: if the deprecated signing was used **Returns**: the recovered public keys #### get`_`hash ```python @staticmethod def get_hash(message: bytes) -> str ``` Get the hash of a message. **Arguments**: - `message`: the message to be hashed. **Returns**: the hash of the message. #### is`_`valid`_`address ```python @classmethod def is_valid_address(cls, address: Address) -> bool ``` Check if the address is valid. **Arguments**: - `address`: the address to validate **Returns**: whether address is valid or not #### load`_`contract`_`interface ```python @classmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str] ``` Load contract interface. **Arguments**: - `file_path`: the file path to the interface **Returns**: the interface ## CosmosCrypto Objects ```python class CosmosCrypto(Crypto[SigningKey]) ``` Class wrapping the Account Generation from Ethereum ledger. #### `__`init`__` ```python def __init__(private_key_path: Optional[str] = None, password: Optional[str] = None) -> None ``` Instantiate an ethereum crypto object. **Arguments**: - `private_key_path`: the private key path of the agent - `password`: the password to encrypt/decrypt the private key. #### private`_`key ```python @property def private_key() -> str ``` Return a private key. **Returns**: a private key string #### public`_`key ```python @property def public_key() -> str ``` Return a public key in hex format. **Returns**: a public key string in hex format #### address ```python @property def address() -> str ``` Return the address for the key pair. **Returns**: a display_address str #### load`_`private`_`key`_`from`_`path ```python @classmethod def load_private_key_from_path(cls, file_name: str, password: Optional[str] = None) -> SigningKey ``` Load a private key in hex format from a file. **Arguments**: - `file_name`: the path to the hex file. - `password`: the password to encrypt/decrypt the private key. **Returns**: the Entity. #### sign`_`message ```python def sign_message(message: bytes, is_deprecated_mode: bool = False) -> str ``` Sign a message in bytes string form. **Arguments**: - `message`: the message to be signed - `is_deprecated_mode`: if the deprecated signing is used **Returns**: signature of the message in string form #### sign`_`transaction ```python def sign_transaction(transaction: JSONLike) -> JSONLike ``` Sign a transaction in bytes string form. **Arguments**: - `transaction`: the transaction to be signed **Returns**: signed transaction #### generate`_`private`_`key ```python @classmethod def generate_private_key(cls) -> SigningKey ``` Generate a key pair for cosmos network. #### encrypt ```python def encrypt(password: str) -> str ``` Encrypt the private key and return in json. **Arguments**: - `password`: the password to decrypt. **Returns**: json string containing encrypted private key. #### decrypt ```python @classmethod def decrypt(cls, keyfile_json: str, password: str) -> str ``` Decrypt the private key and return in raw form. **Arguments**: - `keyfile_json`: json string containing encrypted private key. - `password`: the password to decrypt. **Returns**: the raw private key. ## `_`CosmosApi Objects ```python class _CosmosApi(LedgerApi) ``` Class to interact with the Cosmos SDK via a HTTP APIs. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize the Cosmos ledger APIs. #### api ```python @property def api() -> Any ``` Get the underlying API object. #### get`_`balance ```python def get_balance(address: Address) -> Optional[int] ``` Get the balance of a given account. #### get`_`state ```python def get_state(callable_name: str, *args: Any, **kwargs: Any) -> Optional[JSONLike] ``` Call a specified function on the ledger API. Based on the cosmos REST API specification, which takes a path (strings separated by '/'). The convention here is to define the root of the path (txs, blocks, etc.) as the callable_name and the rest of the path as args. **Arguments**: - `callable_name`: name of the callable - `args`: positional arguments - `kwargs`: keyword arguments **Returns**: the transaction dictionary #### get`_`deploy`_`transaction ```python def get_deploy_transaction(contract_interface: Dict[str, str], deployer_address: Address, **kwargs: Any) -> Optional[JSONLike] ``` Get the transaction to deploy the smart contract. Dispatches to _get_storage_transaction and _get_init_transaction based on kwargs. **Arguments**: - `contract_interface`: the contract interface. - `deployer_address`: The address that will deploy the contract. - `kwargs`: keyword arguments. **Returns**: the transaction dictionary. #### get`_`handle`_`transaction ```python def get_handle_transaction( sender_address: Address, contract_address: Address, handle_msg: Any, amount: int, tx_fee: int, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None) -> Optional[JSONLike] ``` Create a CosmWasm HandleMsg transaction. **Arguments**: - `sender_address`: the sender address of the message initiator. - `contract_address`: the address of the smart contract. - `handle_msg`: HandleMsg in JSON format. - `amount`: Funds amount sent with transaction. - `tx_fee`: the tx fee accepted. - `denom`: the name of the denomination of the contract funds - `gas`: Maximum amount of gas to be used on executing command. - `memo`: any string comment. - `chain_id`: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet). - `account_number`: Account number - `sequence`: Sequence - `tx_fee_denom`: Denomination of tx_fee, identical with denom param when None **Returns**: the unsigned CosmWasm HandleMsg #### execute`_`contract`_`query ```python def execute_contract_query(contract_address: Address, query_msg: JSONLike) -> Optional[JSONLike] ``` Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing. **Arguments**: - `contract_address`: the address of the smart contract. - `query_msg`: QueryMsg in JSON format. **Returns**: the message receipt #### get`_`transfer`_`transaction ```python def get_transfer_transaction(sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None, **kwargs: Any) -> Optional[JSONLike] ``` Submit a transfer transaction to the ledger. **Arguments**: - `sender_address`: the sender address of the payer. - `destination_address`: the destination address of the payee. - `amount`: the amount of wealth to be transferred. - `tx_fee`: the transaction fee. - `tx_nonce`: verifies the authenticity of the tx - `denom`: the denomination of tx fee and amount - `gas`: the gas used. - `memo`: memo to include in tx. - `chain_id`: the chain ID of the transaction. - `account_number`: Account number - `sequence`: Sequence - `tx_fee_denom`: Denomination of tx_fee, identical with denom param when None - `kwargs`: keyword arguments. **Returns**: the transfer transaction #### get`_`packed`_`exec`_`msg ```python def get_packed_exec_msg(sender_address: Address, contract_address: str, msg: JSONLike, funds: int = 0, denom: Optional[str] = None) -> ProtoAny ``` Create and pack MsgExecuteContract **Arguments**: - `sender_address`: Address of sender - `contract_address`: Address of contract - `msg`: Paramaters to be passed to smart contract - `funds`: Funds to be sent to smart contract - `denom`: the denomination of funds **Returns**: Packed MsgExecuteContract #### get`_`packed`_`send`_`msg ```python def get_packed_send_msg(from_address: Address, to_address: Address, amount: int, denom: Optional[str] = None) -> ProtoAny ``` Generate and pack MsgSend **Arguments**: - `from_address`: Address of sender - `to_address`: Address of recipient - `amount`: amount of coins to be sent - `denom`: the denomination of and amount **Returns**: packer ProtoAny type message #### get`_`multi`_`transaction ```python def get_multi_transaction(from_addresses: List[str], pub_keys: Optional[List[bytes]], msgs: List[ProtoAny], gas: int, tx_fee: int = 0, memo: str = "", chain_id: Optional[str] = None, denom: Optional[str] = None, tx_fee_denom: Optional[str] = None) -> JSONLike ``` Generate transaction with multiple messages **Arguments**: - `from_addresses`: Addresses of signers - `pub_keys`: Public keys of signers - `msgs`: Messages to be included in transaction - `gas`: the gas used. - `tx_fee`: the transaction fee. - `memo`: memo to include in tx. - `chain_id`: the chain ID of the transaction. - `denom`: the denomination of tx fee - `tx_fee_denom`: Denomination of tx_fee, identical with denom param when None **Raises**: - `None`: RuntimeError if number of pubkeys is not equal to number of from_addresses **Returns**: the transaction #### send`_`signed`_`transaction ```python def send_signed_transaction(tx_signed: JSONLike) -> Optional[str] ``` Send a signed transaction and wait for confirmation. **Arguments**: - `tx_signed`: the signed transaction **Returns**: tx_digest, if present #### get`_`transaction`_`receipt ```python def get_transaction_receipt(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction receipt for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx receipt, if present #### get`_`transaction ```python def get_transaction(tx_digest: str) -> Optional[JSONLike] ``` Get the transaction for a transaction digest. **Arguments**: - `tx_digest`: the digest associated to the transaction. **Returns**: the tx, if present #### get`_`contract`_`instance ```python def get_contract_instance(contract_interface: Dict[str, str], contract_address: Optional[str] = None) -> Any ``` Get the instance of a contract. **Arguments**: - `contract_interface`: the contract interface. - `contract_address`: the contract address. **Returns**: the contract instance #### update`_`with`_`gas`_`estimate ```python def update_with_gas_estimate(transaction: JSONLike) -> JSONLike ``` Attempts to update the transaction with a gas estimate **Arguments**: - `transaction`: the transaction **Raises**: - `None`: NotImplementedError ## CosmosApi Objects ```python class CosmosApi(_CosmosApi, CosmosHelper) ``` Class to interact with the Cosmos SDK via a HTTP APIs. ## CosmosFaucetApi Objects ```python class CosmosFaucetApi(FaucetApi) ``` Cosmos testnet faucet API. #### `__`init`__` ```python def __init__(poll_interval: Optional[float] = None, final_wait_interval: Optional[float] = None) ``` Initialize CosmosFaucetApi. #### get`_`wealth ```python def get_wealth(address: Address, url: Optional[str] = None) -> None ``` Get wealth from the faucet for the provided address. **Arguments**: - `address`: the address. - `url`: the url **Raises**: - `None`: RuntimeError of explicit faucet failures ================================================ FILE: docs/api/plugins/aea_ledger_fetchai/fetchai.md ================================================ # plugins.aea-ledger-fetchai.aea`_`ledger`_`fetchai.fetchai Fetchai module wrapping the public and private key cryptography and ledger api. ## FetchAIHelper Objects ```python class FetchAIHelper(CosmosHelper) ``` Helper class usable as Mixin for FetchAIApi or as standalone class. ## FetchAICrypto Objects ```python class FetchAICrypto(CosmosCrypto) ``` Class wrapping the Entity Generation from Fetch.AI ledger. ## FetchAIApi Objects ```python class FetchAIApi(_CosmosApi, FetchAIHelper) ``` Class to interact with the Fetch ledger APIs. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize the Fetch.ai ledger APIs. ## FetchAIFaucetApi Objects ```python class FetchAIFaucetApi(CosmosFaucetApi) ``` Fetchai testnet faucet API. ================================================ FILE: docs/api/protocols/base.md ================================================ # aea.protocols.base This module contains the base message and serialization definition. ## Message Objects ```python class Message() ``` This class implements a message. ## Performative Objects ```python class Performative(Enum) ``` Performatives for the base message. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`init`__` ```python def __init__(_body: Optional[Dict] = None, **kwargs: Any) -> None ``` Initialize a Message object. **Arguments**: - `_body`: the dictionary of values to hold. - `kwargs`: any additional value to add to the body. It will overwrite the body values. #### json ```python def json() -> dict ``` Get json friendly str representation of the message. #### from`_`json ```python @classmethod def from_json(cls, data: dict) -> "Message" ``` Construct message instance from json data. #### valid`_`performatives ```python @property def valid_performatives() -> Set[str] ``` Get valid performatives. #### has`_`sender ```python @property def has_sender() -> bool ``` Check if it has a sender. #### sender ```python @property def sender() -> Address ``` Get the sender of the message in Address form. #### sender ```python @sender.setter def sender(sender: Address) -> None ``` Set the sender of the message. #### has`_`to ```python @property def has_to() -> bool ``` Check if it has a sender. #### to ```python @property def to() -> Address ``` Get address of receiver. #### to ```python @to.setter def to(to: Address) -> None ``` Set address of receiver. #### dialogue`_`reference ```python @property def dialogue_reference() -> Tuple[str, str] ``` Get the dialogue_reference of the message. #### message`_`id ```python @property def message_id() -> int ``` Get the message_id of the message. #### performative ```python @property def performative() -> "Performative" ``` Get the performative of the message. #### target ```python @property def target() -> int ``` Get the target of the message. #### set ```python def set(key: str, value: Any) -> None ``` Set key and value pair. **Arguments**: - `key`: the key. - `value`: the value. #### get ```python def get(key: str) -> Optional[Any] ``` Get value for key. #### is`_`set ```python def is_set(key: str) -> bool ``` Check value is set for key. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare with another object. #### `__`repr`__` ```python def __repr__() -> str ``` Get the representation of the message. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation of the message. Abbreviated to prevent spamming of logs. #### encode ```python def encode() -> bytes ``` Encode the message. #### decode ```python @classmethod def decode(cls, data: bytes) -> "Message" ``` Decode the message. #### has`_`dialogue`_`info ```python @property def has_dialogue_info() -> bool ``` Check whether a message has the dialogue fields populated. More precisely, it checks whether the fields 'message_id', 'target' and 'dialogue_reference' are set. **Returns**: True if the message has the dialogue fields set, False otherwise. ## Encoder Objects ```python class Encoder(ABC) ``` Encoder interface. #### encode ```python @staticmethod @abstractmethod def encode(msg: Message) -> bytes ``` Encode a message. **Arguments**: - `msg`: the message to be encoded. **Returns**: the encoded message. ## Decoder Objects ```python class Decoder(ABC) ``` Decoder interface. #### decode ```python @staticmethod @abstractmethod def decode(obj: bytes) -> Message ``` Decode a message. **Arguments**: - `obj`: the sequence of bytes to be decoded. **Returns**: the decoded message. ## Serializer Objects ```python class Serializer(Encoder, Decoder, ABC) ``` The implementations of this class defines a serialization layer for a protocol. ## Protocol Objects ```python class Protocol(Component) ``` This class implements a specifications for a protocol. It includes a serializer to encode/decode a message. #### `__`init`__` ```python def __init__(configuration: ProtocolConfig, message_class: Type[Message], **kwargs: Any) -> None ``` Initialize the protocol manager. **Arguments**: - `configuration`: the protocol configurations. - `message_class`: the message class. - `kwargs`: the keyword arguments. #### serializer ```python @property def serializer() -> Type[Serializer] ``` Get the serializer. #### from`_`dir ```python @classmethod def from_dir(cls, directory: str, **kwargs: Any) -> "Protocol" ``` Load the protocol from a directory. **Arguments**: - `directory`: the directory to the skill package. - `kwargs`: the keyword arguments. **Returns**: the protocol object. #### from`_`config ```python @classmethod def from_config(cls, configuration: ProtocolConfig, **kwargs: Any) -> "Protocol" ``` Load the protocol from configuration. **Arguments**: - `configuration`: the protocol configuration. - `kwargs`: the keyword arguments. **Returns**: the protocol object. #### protocol`_`id ```python @property def protocol_id() -> PublicId ``` Get protocol id. #### protocol`_`specification`_`id ```python @property def protocol_specification_id() -> PublicId ``` Get protocol specification id. #### `__`repr`__` ```python def __repr__() -> str ``` Get str representation of the protocol. ================================================ FILE: docs/api/protocols/default/custom_types.md ================================================ # packages.fetchai.protocols.default.custom`_`types This module contains class representations corresponding to every custom type in the protocol specification. ## ErrorCode Objects ```python class ErrorCode(Enum) ``` This class represents an instance of ErrorCode. #### encode ```python @staticmethod def encode(error_code_protobuf_object: Any, error_code_object: "ErrorCode") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the error_code_protobuf_object argument is matched with the instance of this class in the 'error_code_object' argument. **Arguments**: - `error_code_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `error_code_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, error_code_protobuf_object: Any) -> "ErrorCode" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'error_code_protobuf_object' argument. **Arguments**: - `error_code_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'error_code_protobuf_object' argument. ================================================ FILE: docs/api/protocols/default/dialogues.md ================================================ # packages.fetchai.protocols.default.dialogues This module contains the classes required for default dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues. ## DefaultDialogue Objects ```python class DefaultDialogue(Dialogue) ``` The default dialogue class maintains state of a dialogue and manages it. ## Role Objects ```python class Role(Dialogue.Role) ``` This class defines the agent's role in a default dialogue. ## EndState Objects ```python class EndState(Dialogue.EndState) ``` This class defines the end states of a default dialogue. #### `__`init`__` ```python def __init__(dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[DefaultMessage] = DefaultMessage) -> None ``` Initialize a dialogue. **Arguments**: - `dialogue_label`: the identifier of the dialogue - `self_address`: the address of the entity for whom this dialogue is maintained - `role`: the role of the agent this dialogue is maintained for - `message_class`: the message class used ## DefaultDialogues Objects ```python class DefaultDialogues(Dialogues, ABC) ``` This class keeps track of all default dialogues. #### `__`init`__` ```python def __init__(self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[DefaultDialogue] = DefaultDialogue) -> None ``` Initialize dialogues. **Arguments**: - `self_address`: the address of the entity for whom dialogues are maintained - `dialogue_class`: the dialogue class used - `role_from_first_message`: the callable determining role from first message ================================================ FILE: docs/api/protocols/default/message.md ================================================ # packages.fetchai.protocols.default.message This module contains default's message definition. ## DefaultMessage Objects ```python class DefaultMessage(Message) ``` A protocol for exchanging any bytes message. ## Performative Objects ```python class Performative(Message.Performative) ``` Performatives for the default protocol. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`init`__` ```python def __init__(performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any) ``` Initialise an instance of DefaultMessage. **Arguments**: - `message_id`: the message id. - `dialogue_reference`: the dialogue reference. - `target`: the message target. - `performative`: the message performative. - `**kwargs`: extra options. #### valid`_`performatives ```python @property def valid_performatives() -> Set[str] ``` Get valid performatives. #### dialogue`_`reference ```python @property def dialogue_reference() -> Tuple[str, str] ``` Get the dialogue_reference of the message. #### message`_`id ```python @property def message_id() -> int ``` Get the message_id of the message. #### performative ```python @property def performative() -> Performative ``` Get the performative of the message. #### target ```python @property def target() -> int ``` Get the target of the message. #### content ```python @property def content() -> bytes ``` Get the 'content' content from the message. #### error`_`code ```python @property def error_code() -> CustomErrorCode ``` Get the 'error_code' content from the message. #### error`_`data ```python @property def error_data() -> Dict[str, bytes] ``` Get the 'error_data' content from the message. #### error`_`msg ```python @property def error_msg() -> str ``` Get the 'error_msg' content from the message. ================================================ FILE: docs/api/protocols/default/serialization.md ================================================ # packages.fetchai.protocols.default.serialization Serialization module for default protocol. ## DefaultSerializer Objects ```python class DefaultSerializer(Serializer) ``` Serialization for the 'default' protocol. #### encode ```python @staticmethod def encode(msg: Message) -> bytes ``` Encode a 'Default' message into bytes. **Arguments**: - `msg`: the message object. **Returns**: the bytes. #### decode ```python @staticmethod def decode(obj: bytes) -> Message ``` Decode bytes into a 'Default' message. **Arguments**: - `obj`: the bytes object. **Returns**: the 'Default' message. ================================================ FILE: docs/api/protocols/dialogue/base.md ================================================ # aea.protocols.dialogue.base This module contains the classes required for dialogue management. - DialogueLabel: The dialogue label class acts as an identifier for dialogues. - Dialogue: The dialogue class maintains state of a dialogue and manages it. - Dialogues: The dialogues class keeps track of all dialogues. ## InvalidDialogueMessage Objects ```python class InvalidDialogueMessage(Exception) ``` Exception for adding invalid message to a dialogue. ## DialogueLabel Objects ```python class DialogueLabel() ``` The dialogue label class acts as an identifier for dialogues. #### `__`init`__` ```python def __init__(dialogue_reference: Tuple[str, str], dialogue_opponent_addr: Address, dialogue_starter_addr: Address) -> None ``` Initialize a dialogue label. **Arguments**: - `dialogue_reference`: the reference of the dialogue. - `dialogue_opponent_addr`: the addr of the agent with which the dialogue is kept. - `dialogue_starter_addr`: the addr of the agent which started the dialogue. #### dialogue`_`reference ```python @property def dialogue_reference() -> Tuple[str, str] ``` Get the dialogue reference. #### dialogue`_`starter`_`reference ```python @property def dialogue_starter_reference() -> str ``` Get the dialogue starter reference. #### dialogue`_`responder`_`reference ```python @property def dialogue_responder_reference() -> str ``` Get the dialogue responder reference. #### dialogue`_`opponent`_`addr ```python @property def dialogue_opponent_addr() -> str ``` Get the address of the dialogue opponent. #### dialogue`_`starter`_`addr ```python @property def dialogue_starter_addr() -> str ``` Get the address of the dialogue starter. #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Check for equality between two DialogueLabel objects. #### `__`hash`__` ```python def __hash__() -> int ``` Turn object into hash. #### json ```python @property def json() -> Dict ``` Return the JSON representation. #### from`_`json ```python @classmethod def from_json(cls, obj: Dict[str, str]) -> "DialogueLabel" ``` Get dialogue label from json. #### get`_`incomplete`_`version ```python def get_incomplete_version() -> "DialogueLabel" ``` Get the incomplete version of the label. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### from`_`str ```python @classmethod def from_str(cls, obj: str) -> "DialogueLabel" ``` Get the dialogue label from string representation. ## `_`DialogueMeta Objects ```python class _DialogueMeta(type) ``` Metaclass for Dialogue. Creates class level Rules instance to share among instances #### `__`new`__` ```python def __new__(cls, name: str, bases: Tuple[Type], dct: Dict) -> "_DialogueMeta" ``` Construct a new type. ## Dialogue Objects ```python class Dialogue(metaclass=_DialogueMeta) ``` The dialogue class maintains state of a dialogue and manages it. ## Rules Objects ```python class Rules() ``` This class defines the rules for the dialogue. #### `__`init`__` ```python def __init__( initial_performatives: FrozenSet[Message.Performative], terminal_performatives: FrozenSet[Message.Performative], valid_replies: Dict[Message.Performative, FrozenSet[Message.Performative]] ) -> None ``` Initialize a dialogue. **Arguments**: - `initial_performatives`: the set of all initial performatives. - `terminal_performatives`: the set of all terminal performatives. - `valid_replies`: the reply structure of speech-acts. #### initial`_`performatives ```python @property def initial_performatives() -> FrozenSet[Message.Performative] ``` Get the performatives one of which the terminal message in the dialogue must have. **Returns**: the valid performatives of an terminal message #### terminal`_`performatives ```python @property def terminal_performatives() -> FrozenSet[Message.Performative] ``` Get the performatives one of which the terminal message in the dialogue must have. **Returns**: the valid performatives of an terminal message #### valid`_`replies ```python @property def valid_replies( ) -> Dict[Message.Performative, FrozenSet[Message.Performative]] ``` Get all the valid performatives which are a valid replies to performatives. **Returns**: the full valid reply structure. #### get`_`valid`_`replies ```python def get_valid_replies( performative: Message.Performative) -> FrozenSet[Message.Performative] ``` Given a `performative`, return the list of performatives which are its valid replies in a dialogue. **Arguments**: - `performative`: the performative in a message **Returns**: list of valid performative replies ## Role Objects ```python class Role(Enum) ``` This class defines the agent's role in a dialogue. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. ## EndState Objects ```python class EndState(Enum) ``` This class defines the end states of a dialogue. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`init`__` ```python def __init__(dialogue_label: DialogueLabel, message_class: Type[Message], self_address: Address, role: Role) -> None ``` Initialize a dialogue. **Arguments**: - `dialogue_label`: the identifier of the dialogue - `message_class`: the message class used - `self_address`: the address of the entity for whom this dialogue is maintained - `role`: the role of the agent this dialogue is maintained for #### add`_`terminal`_`state`_`callback ```python def add_terminal_state_callback(fn: Callable[["Dialogue"], None]) -> None ``` Add callback to be called on dialogue reach terminal state. **Arguments**: - `fn`: callable to be called with one argument: Dialogue #### `__`eq`__` ```python def __eq__(other: Any) -> bool ``` Compare two dialogues. #### json ```python def json() -> dict ``` Get json representation of the dialogue. #### from`_`json ```python @classmethod def from_json(cls, message_class: Type[Message], data: dict) -> "Dialogue" ``` Create a dialogue instance with all messages from json data. **Arguments**: - `message_class`: type of message used with this dialogue - `data`: dict with data exported with Dialogue.to_json() method **Returns**: Dialogue instance #### dialogue`_`label ```python @property def dialogue_label() -> DialogueLabel ``` Get the dialogue label. **Returns**: The dialogue label #### incomplete`_`dialogue`_`label ```python @property def incomplete_dialogue_label() -> DialogueLabel ``` Get the dialogue label. **Returns**: The incomplete dialogue label #### dialogue`_`labels ```python @property def dialogue_labels() -> Set[DialogueLabel] ``` Get the dialogue labels (incomplete and complete, if it exists). **Returns**: the dialogue labels #### self`_`address ```python @property def self_address() -> Address ``` Get the address of the entity for whom this dialogues is maintained. **Returns**: the address of this entity #### role ```python @property def role() -> "Role" ``` Get the agent's role in the dialogue. **Returns**: the agent's role #### rules ```python @property def rules() -> "Rules" ``` Get the dialogue rules. **Returns**: the rules #### message`_`class ```python @property def message_class() -> Type[Message] ``` Get the message class. **Returns**: the message class #### is`_`self`_`initiated ```python @property def is_self_initiated() -> bool ``` Check whether the agent initiated the dialogue. **Returns**: True if the agent initiated the dialogue, False otherwise #### last`_`incoming`_`message ```python @property def last_incoming_message() -> Optional[Message] ``` Get the last incoming message. **Returns**: the last incoming message if it exists, None otherwise #### last`_`outgoing`_`message ```python @property def last_outgoing_message() -> Optional[Message] ``` Get the last outgoing message. **Returns**: the last outgoing message if it exists, None otherwise #### last`_`message ```python @property def last_message() -> Optional[Message] ``` Get the last message. **Returns**: the last message if it exists, None otherwise #### is`_`empty ```python @property def is_empty() -> bool ``` Check whether the dialogue is empty. **Returns**: True if empty, False otherwise #### reply ```python def reply(performative: Message.Performative, target_message: Optional[Message] = None, target: Optional[int] = None, **kwargs: Any) -> Message ``` Reply to the 'target_message' in this dialogue with a message with 'performative', and contents from kwargs. Note if no target_message is provided, the last message in the dialogue will be replied to. **Arguments**: - `target_message`: the message to reply to. - `target`: the id of the message to reply to. - `performative`: the performative of the reply message. - `kwargs`: the content of the reply message. **Returns**: the reply message if it was successfully added as a reply, None otherwise. #### get`_`message`_`by`_`id ```python def get_message_by_id(message_id: int) -> Optional[Message] ``` Get message by id, if not presents return None. #### get`_`outgoing`_`next`_`message`_`id ```python def get_outgoing_next_message_id() -> int ``` Get next outgoing message id. #### get`_`incoming`_`next`_`message`_`id ```python def get_incoming_next_message_id() -> int ``` Get next incoming message id. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. **Returns**: The string representation of the dialogue ## DialogueStats Objects ```python class DialogueStats() ``` Class to handle statistics on default dialogues. #### `__`init`__` ```python def __init__(end_states: FrozenSet[Dialogue.EndState]) -> None ``` Initialize a StatsManager. **Arguments**: - `end_states`: the list of dialogue endstates #### self`_`initiated ```python @property def self_initiated() -> Dict[Dialogue.EndState, int] ``` Get the stats dictionary on self initiated dialogues. #### other`_`initiated ```python @property def other_initiated() -> Dict[Dialogue.EndState, int] ``` Get the stats dictionary on other initiated dialogues. #### add`_`dialogue`_`endstate ```python def add_dialogue_endstate(end_state: Dialogue.EndState, is_self_initiated: bool) -> None ``` Add dialogue endstate stats. **Arguments**: - `end_state`: the end state of the dialogue - `is_self_initiated`: whether the dialogue is initiated by the agent or the opponent #### find`_`caller`_`object ```python def find_caller_object(object_type: Type) -> Any ``` Find caller object of certain type in the call stack. ## BasicDialoguesStorage Objects ```python class BasicDialoguesStorage() ``` Dialogues state storage. #### `__`init`__` ```python def __init__(dialogues: "Dialogues") -> None ``` Init dialogues storage. #### dialogues`_`in`_`terminal`_`state ```python @property def dialogues_in_terminal_state() -> List["Dialogue"] ``` Get all dialogues in terminal state. #### dialogues`_`in`_`active`_`state ```python @property def dialogues_in_active_state() -> List["Dialogue"] ``` Get all dialogues in active state. #### is`_`terminal`_`dialogues`_`kept ```python @property def is_terminal_dialogues_kept() -> bool ``` Return True if dialogues should stay after terminal state. #### dialogue`_`terminal`_`state`_`callback ```python def dialogue_terminal_state_callback(dialogue: "Dialogue") -> None ``` Method to be called on dialogue terminal state reached. #### setup ```python def setup() -> None ``` Set up dialogue storage. #### teardown ```python def teardown() -> None ``` Tear down dialogue storage. #### add ```python def add(dialogue: Dialogue) -> None ``` Add dialogue to storage. **Arguments**: - `dialogue`: dialogue to add. #### remove ```python def remove(dialogue_label: DialogueLabel) -> None ``` Remove dialogue from storage by it's label. **Arguments**: - `dialogue_label`: label of the dialogue to remove #### get ```python def get(dialogue_label: DialogueLabel) -> Optional[Dialogue] ``` Get dialogue stored by it's label. **Arguments**: - `dialogue_label`: label of the dialogue **Returns**: dialogue if presents or None #### get`_`dialogues`_`with`_`counterparty ```python def get_dialogues_with_counterparty(counterparty: Address) -> List[Dialogue] ``` Get the dialogues by address. **Arguments**: - `counterparty`: the counterparty **Returns**: The dialogues with the counterparty. #### is`_`in`_`incomplete ```python def is_in_incomplete(dialogue_label: DialogueLabel) -> bool ``` Check dialogue label presents in list of incomplete. #### set`_`incomplete`_`dialogue ```python def set_incomplete_dialogue(incomplete_dialogue_label: DialogueLabel, complete_dialogue_label: DialogueLabel) -> None ``` Set incomplete dialogue label. #### is`_`dialogue`_`present ```python def is_dialogue_present(dialogue_label: DialogueLabel) -> bool ``` Check dialogue with label specified presents in storage. #### get`_`latest`_`label ```python def get_latest_label(dialogue_label: DialogueLabel) -> DialogueLabel ``` Get latest label for dialogue. ## PersistDialoguesStorage Objects ```python class PersistDialoguesStorage(BasicDialoguesStorage) ``` Persist dialogues storage. Uses generic storage to load/save dialogues data on setup/teardown. #### `__`init`__` ```python def __init__(dialogues: "Dialogues") -> None ``` Init dialogues storage. #### get`_`skill`_`component ```python @staticmethod def get_skill_component() -> Optional[SkillComponent] ``` Get skill component dialogues storage constructed for. #### setup ```python def setup() -> None ``` Set up dialogue storage. #### teardown ```python def teardown() -> None ``` Tear down dialogue storage. #### remove ```python def remove(dialogue_label: DialogueLabel) -> None ``` Remove dialogue from memory and persistent storage. ## PersistDialoguesStorageWithOffloading Objects ```python class PersistDialoguesStorageWithOffloading(PersistDialoguesStorage) ``` Dialogue Storage with dialogues offloading. #### dialogue`_`terminal`_`state`_`callback ```python def dialogue_terminal_state_callback(dialogue: "Dialogue") -> None ``` Call on dialogue reaches terminal state. #### get ```python def get(dialogue_label: DialogueLabel) -> Optional[Dialogue] ``` Try to get dialogue by label from memory or persists storage. #### get`_`dialogues`_`with`_`counterparty ```python def get_dialogues_with_counterparty(counterparty: Address) -> List[Dialogue] ``` Get the dialogues by address. **Arguments**: - `counterparty`: the counterparty **Returns**: The dialogues with the counterparty. #### dialogues`_`in`_`terminal`_`state ```python @property def dialogues_in_terminal_state() -> List["Dialogue"] ``` Get all dialogues in terminal state. ## Dialogues Objects ```python class Dialogues() ``` The dialogues class keeps track of all dialogues for an agent. #### `__`init`__` ```python def __init__(self_address: Address, end_states: FrozenSet[Dialogue.EndState], message_class: Type[Message], dialogue_class: Type[Dialogue], role_from_first_message: Callable[[Message, Address], Dialogue.Role], keep_terminal_state_dialogues: Optional[bool] = None) -> None ``` Initialize dialogues. **Arguments**: - `self_address`: the address of the entity for whom dialogues are maintained - `end_states`: the list of dialogue endstates - `message_class`: the message class used - `dialogue_class`: the dialogue class used - `role_from_first_message`: the callable determining role from first message - `keep_terminal_state_dialogues`: specify do dialogues in terminal state should stay or not #### is`_`keep`_`dialogues`_`in`_`terminal`_`state ```python @property def is_keep_dialogues_in_terminal_state() -> bool ``` Is required to keep dialogues in terminal state. #### self`_`address ```python @property def self_address() -> Address ``` Get the address of the agent for whom dialogues are maintained. #### dialogue`_`stats ```python @property def dialogue_stats() -> DialogueStats ``` Get the dialogue statistics. **Returns**: dialogue stats object #### message`_`class ```python @property def message_class() -> Type[Message] ``` Get the message class. **Returns**: the message class #### dialogue`_`class ```python @property def dialogue_class() -> Type[Dialogue] ``` Get the dialogue class. **Returns**: the dialogue class #### get`_`dialogues`_`with`_`counterparty ```python def get_dialogues_with_counterparty(counterparty: Address) -> List[Dialogue] ``` Get the dialogues by address. **Arguments**: - `counterparty`: the counterparty **Returns**: The dialogues with the counterparty. #### new`_`self`_`initiated`_`dialogue`_`reference ```python @classmethod def new_self_initiated_dialogue_reference(cls) -> Tuple[str, str] ``` Return a dialogue label for a new self initiated dialogue. **Returns**: the next nonce #### create ```python def create(counterparty: Address, performative: Message.Performative, **kwargs: Any) -> Tuple[Message, Dialogue] ``` Create a dialogue with 'counterparty', with an initial message whose performative is 'performative' and contents are from 'kwargs'. **Arguments**: - `counterparty`: the counterparty of the dialogue. - `performative`: the performative of the initial message. - `kwargs`: the content of the initial message. **Returns**: the initial message and the dialogue. #### create`_`with`_`message ```python def create_with_message(counterparty: Address, initial_message: Message) -> Dialogue ``` Create a dialogue with 'counterparty', with an initial message provided. **Arguments**: - `counterparty`: the counterparty of the dialogue. - `initial_message`: the initial_message. **Returns**: the initial message and the dialogue. #### update ```python def update(message: Message) -> Optional[Dialogue] ``` Update the state of dialogues with a new incoming message. If the message is for a new dialogue, a new dialogue is created with 'message' as its first message, and returned. If the message is addressed to an existing dialogue, the dialogue is retrieved, extended with this message and returned. If there are any errors, e.g. the message dialogue reference does not exists or the message is invalid w.r.t. the dialogue, return None. **Arguments**: - `message`: a new incoming message **Returns**: the new or existing dialogue the message is intended for, or None in case of any errors. #### get`_`dialogue ```python def get_dialogue(message: Message) -> Optional[Dialogue] ``` Retrieve the dialogue 'message' belongs to. **Arguments**: - `message`: a message **Returns**: the dialogue, or None in case such a dialogue does not exist #### get`_`dialogue`_`from`_`label ```python def get_dialogue_from_label( dialogue_label: DialogueLabel) -> Optional[Dialogue] ``` Retrieve a dialogue based on its label. **Arguments**: - `dialogue_label`: the dialogue label **Returns**: the dialogue if present #### setup ```python def setup() -> None ``` Set up. #### teardown ```python def teardown() -> None ``` Tear down. ================================================ FILE: docs/api/protocols/generator/base.md ================================================ ================================================ FILE: docs/api/protocols/generator/common.md ================================================ # aea.protocols.generator.common This module contains utility code for generator modules. #### is`_`installed ```python def is_installed(programme: str) -> bool ``` Check whether a programme is installed on the system. **Arguments**: - `programme`: the name of the programme. **Returns**: True if installed, False otherwise #### base`_`protolint`_`command ```python def base_protolint_command() -> str ``` Return the base protolint command. **Returns**: The base protolint command #### check`_`prerequisites ```python def check_prerequisites() -> None ``` Check whether a programme is installed on the system. #### get`_`protoc`_`version ```python def get_protoc_version() -> str ``` Get the protoc version used. #### load`_`protocol`_`specification ```python def load_protocol_specification( specification_path: str) -> ProtocolSpecification ``` Load a protocol specification. **Arguments**: - `specification_path`: path to the protocol specification yaml file. **Returns**: A ProtocolSpecification object #### try`_`run`_`black`_`formatting ```python def try_run_black_formatting(path_to_protocol_package: str) -> None ``` Run Black code formatting via subprocess. **Arguments**: - `path_to_protocol_package`: a path where formatting should be applied. #### try`_`run`_`isort`_`formatting ```python def try_run_isort_formatting(path_to_protocol_package: str) -> None ``` Run Isort code formatting via subprocess. **Arguments**: - `path_to_protocol_package`: a path where formatting should be applied. #### try`_`run`_`protoc ```python def try_run_protoc(path_to_generated_protocol_package: str, name: str, language: str = PROTOCOL_LANGUAGE_PYTHON) -> None ``` Run 'protoc' protocol buffer compiler via subprocess. **Arguments**: - `path_to_generated_protocol_package`: path to the protocol buffer schema file. - `name`: name of the protocol buffer schema file. - `language`: the target language in which to compile the protobuf schema file #### try`_`run`_`protolint ```python def try_run_protolint(path_to_generated_protocol_package: str, name: str) -> None ``` Run 'protolint' linter via subprocess. **Arguments**: - `path_to_generated_protocol_package`: path to the protocol buffer schema file. - `name`: name of the protocol buffer schema file. #### check`_`protobuf`_`using`_`protoc ```python def check_protobuf_using_protoc(path_to_generated_protocol_package: str, name: str) -> Tuple[bool, str] ``` Check whether a protocol buffer schema file is valid. Validation is via trying to compile the schema file. If successfully compiled it is valid, otherwise invalid. If valid, return True and a 'protobuf file is valid' message, otherwise return False and the error thrown by the compiler. **Arguments**: - `path_to_generated_protocol_package`: path to the protocol buffer schema file. - `name`: name of the protocol buffer schema file. **Returns**: Boolean result and an accompanying message #### compile`_`protobuf`_`using`_`protoc ```python def compile_protobuf_using_protoc(path_to_generated_protocol_package: str, name: str, language: str) -> Tuple[bool, str] ``` Compile a protocol buffer schema file using protoc. If successfully compiled, return True and a success message, otherwise return False and the error thrown by the compiler. **Arguments**: - `path_to_generated_protocol_package`: path to the protocol buffer schema file. - `name`: name of the protocol buffer schema file. - `language`: the target language in which to compile the protobuf schema file **Returns**: Boolean result and an accompanying message #### apply`_`protolint ```python def apply_protolint(path_to_proto_file: str, name: str) -> Tuple[bool, str] ``` Apply protolint linter to a protocol buffer schema file. If no output, return True and a success message, otherwise return False and the output shown by the linter (minus the indentation suggestions which are automatically fixed by protolint). **Arguments**: - `path_to_proto_file`: path to the protocol buffer schema file. - `name`: name of the protocol buffer schema file. **Returns**: Boolean result and an accompanying message ================================================ FILE: docs/api/protocols/generator/extract_specification.md ================================================ # aea.protocols.generator.extract`_`specification This module extracts a valid protocol specification into pythonic objects. ## PythonicProtocolSpecification Objects ```python class PythonicProtocolSpecification() ``` This class represents a protocol specification in python. #### `__`init`__` ```python def __init__() -> None ``` Instantiate a Pythonic protocol specification. #### extract ```python def extract( protocol_specification: ProtocolSpecification ) -> PythonicProtocolSpecification ``` Converts a protocol specification into a Pythonic protocol specification. **Arguments**: - `protocol_specification`: a protocol specification **Returns**: a Pythonic protocol specification ================================================ FILE: docs/api/protocols/generator/validate.md ================================================ # aea.protocols.generator.validate This module validates a protocol specification. #### validate ```python def validate( protocol_specification: ProtocolSpecification) -> Tuple[bool, str] ``` Evaluate whether a protocol specification is valid. **Arguments**: - `protocol_specification`: a protocol specification. **Returns**: Boolean result, and associated message. ================================================ FILE: docs/api/protocols/signing/custom_types.md ================================================ # packages.fetchai.protocols.signing.custom`_`types This module contains class representations corresponding to every custom type in the protocol specification. ## ErrorCode Objects ```python class ErrorCode(Enum) ``` This class represents an instance of ErrorCode. #### encode ```python @staticmethod def encode(error_code_protobuf_object: Any, error_code_object: "ErrorCode") -> None ``` Encode an instance of this class into the protocol buffer object. The protocol buffer object in the error_code_protobuf_object argument is matched with the instance of this class in the 'error_code_object' argument. **Arguments**: - `error_code_protobuf_object`: the protocol buffer object whose type corresponds with this class. - `error_code_object`: an instance of this class to be encoded in the protocol buffer object. #### decode ```python @classmethod def decode(cls, error_code_protobuf_object: Any) -> "ErrorCode" ``` Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'error_code_protobuf_object' argument. **Arguments**: - `error_code_protobuf_object`: the protocol buffer object whose type corresponds with this class. **Returns**: A new instance of this class that matches the protocol buffer object in the 'error_code_protobuf_object' argument. ================================================ FILE: docs/api/protocols/signing/dialogues.md ================================================ # packages.fetchai.protocols.signing.dialogues This module contains the classes required for signing dialogue management. - SigningDialogue: The dialogue class maintains state of a dialogue and manages it. - SigningDialogues: The dialogues class keeps track of all dialogues. ## SigningDialogue Objects ```python class SigningDialogue(Dialogue) ``` The signing dialogue class maintains state of a dialogue and manages it. ## Role Objects ```python class Role(Dialogue.Role) ``` This class defines the agent's role in a signing dialogue. ## EndState Objects ```python class EndState(Dialogue.EndState) ``` This class defines the end states of a signing dialogue. #### `__`init`__` ```python def __init__(dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[SigningMessage] = SigningMessage) -> None ``` Initialize a dialogue. **Arguments**: - `dialogue_label`: the identifier of the dialogue - `self_address`: the address of the entity for whom this dialogue is maintained - `role`: the role of the agent this dialogue is maintained for - `message_class`: the message class used ## SigningDialogues Objects ```python class SigningDialogues(Dialogues, ABC) ``` This class keeps track of all signing dialogues. #### `__`init`__` ```python def __init__(self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[SigningDialogue] = SigningDialogue) -> None ``` Initialize dialogues. **Arguments**: - `self_address`: the address of the entity for whom dialogues are maintained - `dialogue_class`: the dialogue class used - `role_from_first_message`: the callable determining role from first message ================================================ FILE: docs/api/protocols/signing/message.md ================================================ # packages.fetchai.protocols.signing.message This module contains signing's message definition. ## SigningMessage Objects ```python class SigningMessage(Message) ``` A protocol for communication between skills and decision maker. ## Performative Objects ```python class Performative(Message.Performative) ``` Performatives for the signing protocol. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`init`__` ```python def __init__(performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any) ``` Initialise an instance of SigningMessage. **Arguments**: - `message_id`: the message id. - `dialogue_reference`: the dialogue reference. - `target`: the message target. - `performative`: the message performative. - `**kwargs`: extra options. #### valid`_`performatives ```python @property def valid_performatives() -> Set[str] ``` Get valid performatives. #### dialogue`_`reference ```python @property def dialogue_reference() -> Tuple[str, str] ``` Get the dialogue_reference of the message. #### message`_`id ```python @property def message_id() -> int ``` Get the message_id of the message. #### performative ```python @property def performative() -> Performative ``` Get the performative of the message. #### target ```python @property def target() -> int ``` Get the target of the message. #### error`_`code ```python @property def error_code() -> CustomErrorCode ``` Get the 'error_code' content from the message. #### raw`_`message ```python @property def raw_message() -> CustomRawMessage ``` Get the 'raw_message' content from the message. #### raw`_`transaction ```python @property def raw_transaction() -> CustomRawTransaction ``` Get the 'raw_transaction' content from the message. #### signed`_`message ```python @property def signed_message() -> CustomSignedMessage ``` Get the 'signed_message' content from the message. #### signed`_`transaction ```python @property def signed_transaction() -> CustomSignedTransaction ``` Get the 'signed_transaction' content from the message. #### terms ```python @property def terms() -> CustomTerms ``` Get the 'terms' content from the message. ================================================ FILE: docs/api/protocols/signing/serialization.md ================================================ # packages.fetchai.protocols.signing.serialization Serialization module for signing protocol. ## SigningSerializer Objects ```python class SigningSerializer(Serializer) ``` Serialization for the 'signing' protocol. #### encode ```python @staticmethod def encode(msg: Message) -> bytes ``` Encode a 'Signing' message into bytes. **Arguments**: - `msg`: the message object. **Returns**: the bytes. #### decode ```python @staticmethod def decode(obj: bytes) -> Message ``` Decode bytes into a 'Signing' message. **Arguments**: - `obj`: the bytes object. **Returns**: the 'Signing' message. ================================================ FILE: docs/api/protocols/state_update/dialogues.md ================================================ # packages.fetchai.protocols.state`_`update.dialogues This module contains the classes required for state_update dialogue management. - StateUpdateDialogue: The dialogue class maintains state of a dialogue and manages it. - StateUpdateDialogues: The dialogues class keeps track of all dialogues. ## StateUpdateDialogue Objects ```python class StateUpdateDialogue(Dialogue) ``` The state_update dialogue class maintains state of a dialogue and manages it. ## Role Objects ```python class Role(Dialogue.Role) ``` This class defines the agent's role in a state_update dialogue. ## EndState Objects ```python class EndState(Dialogue.EndState) ``` This class defines the end states of a state_update dialogue. #### `__`init`__` ```python def __init__( dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[StateUpdateMessage] = StateUpdateMessage) -> None ``` Initialize a dialogue. **Arguments**: - `dialogue_label`: the identifier of the dialogue - `self_address`: the address of the entity for whom this dialogue is maintained - `role`: the role of the agent this dialogue is maintained for - `message_class`: the message class used ## StateUpdateDialogues Objects ```python class StateUpdateDialogues(Dialogues, ABC) ``` This class keeps track of all state_update dialogues. #### `__`init`__` ```python def __init__( self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[StateUpdateDialogue] = StateUpdateDialogue ) -> None ``` Initialize dialogues. **Arguments**: - `self_address`: the address of the entity for whom dialogues are maintained - `dialogue_class`: the dialogue class used - `role_from_first_message`: the callable determining role from first message ================================================ FILE: docs/api/protocols/state_update/message.md ================================================ # packages.fetchai.protocols.state`_`update.message This module contains state_update's message definition. ## StateUpdateMessage Objects ```python class StateUpdateMessage(Message) ``` A protocol for state updates to the decision maker state. ## Performative Objects ```python class Performative(Message.Performative) ``` Performatives for the state_update protocol. #### `__`str`__` ```python def __str__() -> str ``` Get the string representation. #### `__`init`__` ```python def __init__(performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any) ``` Initialise an instance of StateUpdateMessage. **Arguments**: - `message_id`: the message id. - `dialogue_reference`: the dialogue reference. - `target`: the message target. - `performative`: the message performative. - `**kwargs`: extra options. #### valid`_`performatives ```python @property def valid_performatives() -> Set[str] ``` Get valid performatives. #### dialogue`_`reference ```python @property def dialogue_reference() -> Tuple[str, str] ``` Get the dialogue_reference of the message. #### message`_`id ```python @property def message_id() -> int ``` Get the message_id of the message. #### performative ```python @property def performative() -> Performative ``` Get the performative of the message. #### target ```python @property def target() -> int ``` Get the target of the message. #### amount`_`by`_`currency`_`id ```python @property def amount_by_currency_id() -> Dict[str, int] ``` Get the 'amount_by_currency_id' content from the message. #### exchange`_`params`_`by`_`currency`_`id ```python @property def exchange_params_by_currency_id() -> Dict[str, float] ``` Get the 'exchange_params_by_currency_id' content from the message. #### quantities`_`by`_`good`_`id ```python @property def quantities_by_good_id() -> Dict[str, int] ``` Get the 'quantities_by_good_id' content from the message. #### utility`_`params`_`by`_`good`_`id ```python @property def utility_params_by_good_id() -> Dict[str, float] ``` Get the 'utility_params_by_good_id' content from the message. ================================================ FILE: docs/api/protocols/state_update/serialization.md ================================================ # packages.fetchai.protocols.state`_`update.serialization Serialization module for state_update protocol. ## StateUpdateSerializer Objects ```python class StateUpdateSerializer(Serializer) ``` Serialization for the 'state_update' protocol. #### encode ```python @staticmethod def encode(msg: Message) -> bytes ``` Encode a 'StateUpdate' message into bytes. **Arguments**: - `msg`: the message object. **Returns**: the bytes. #### decode ```python @staticmethod def decode(obj: bytes) -> Message ``` Decode bytes into a 'StateUpdate' message. **Arguments**: - `obj`: the bytes object. **Returns**: the 'StateUpdate' message. ================================================ FILE: docs/api/registries/base.md ================================================ # aea.registries.base This module contains registries. ## Registry Objects ```python class Registry(Generic[ItemId, Item], WithLogger, ABC) ``` This class implements an abstract registry. #### `__`init`__` ```python def __init__(agent_name: str = "standalone") -> None ``` Initialize the registry. **Arguments**: - `agent_name`: the name of the agent #### register ```python @abstractmethod def register(item_id: ItemId, item: Item, is_dynamically_added: bool = False) -> None ``` Register an item. **Arguments**: - `item_id`: the public id of the item. - `item`: the item. - `is_dynamically_added`: whether or not the item is dynamically added. **Raises**: - `None`: ValueError if an item is already registered with that item id. **Returns**: None #### unregister ```python @abstractmethod def unregister(item_id: ItemId) -> Optional[Item] ``` Unregister an item. **Arguments**: - `item_id`: the public id of the item. **Raises**: - `None`: ValueError if no item registered with that item id. **Returns**: the item #### fetch ```python @abstractmethod def fetch(item_id: ItemId) -> Optional[Item] ``` Fetch an item. **Arguments**: - `item_id`: the public id of the item. **Returns**: the Item #### fetch`_`all ```python @abstractmethod def fetch_all() -> List[Item] ``` Fetch all the items. **Returns**: the list of items. #### ids ```python @abstractmethod def ids() -> Set[ItemId] ``` Return the set of all the used item ids. **Returns**: the set of item ids. #### setup ```python @abstractmethod def setup() -> None ``` Set up registry. **Returns**: None #### teardown ```python @abstractmethod def teardown() -> None ``` Teardown the registry. **Returns**: None ## PublicIdRegistry Objects ```python class PublicIdRegistry(Generic[Item], Registry[PublicId, Item]) ``` This class implement a registry whose keys are public ids. In particular, it is able to handle the case when the public id points to the 'latest' version of a package. #### `__`init`__` ```python def __init__() -> None ``` Initialize the registry. #### register ```python def register(public_id: PublicId, item: Item, is_dynamically_added: bool = False) -> None ``` Register an item. #### unregister ```python def unregister(public_id: PublicId) -> Item ``` Unregister an item. #### fetch ```python def fetch(public_id: PublicId) -> Optional[Item] ``` Fetch an item associated with a public id. **Arguments**: - `public_id`: the public id. **Returns**: an item, or None if the key is not present. #### fetch`_`all ```python def fetch_all() -> List[Item] ``` Fetch all the items. #### ids ```python def ids() -> Set[PublicId] ``` Get all the item ids. #### setup ```python def setup() -> None ``` Set up the items. #### teardown ```python def teardown() -> None ``` Tear down the items. ## AgentComponentRegistry Objects ```python class AgentComponentRegistry(Registry[ComponentId, Component]) ``` This class implements a simple dictionary-based registry for agent components. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Instantiate the registry. **Arguments**: - `kwargs`: kwargs #### register ```python def register(component_id: ComponentId, component: Component, is_dynamically_added: bool = False) -> None ``` Register a component. **Arguments**: - `component_id`: the id of the component. - `component`: the component object. - `is_dynamically_added`: whether or not the item is dynamically added. #### unregister ```python def unregister(component_id: ComponentId) -> Optional[Component] ``` Unregister a component. **Arguments**: - `component_id`: the ComponentId **Returns**: the item #### fetch ```python def fetch(component_id: ComponentId) -> Optional[Component] ``` Fetch the component by id. **Arguments**: - `component_id`: the contract id **Returns**: the component or None if the component is not registered #### fetch`_`all ```python def fetch_all() -> List[Component] ``` Fetch all the components. **Returns**: the list of registered components. #### fetch`_`by`_`type ```python def fetch_by_type(component_type: ComponentType) -> List[Component] ``` Fetch all the components by a given type.. **Arguments**: - `component_type`: a component type **Returns**: the list of registered components of a given type. #### ids ```python def ids() -> Set[ComponentId] ``` Get the item ids. #### setup ```python def setup() -> None ``` Set up the registry. #### teardown ```python def teardown() -> None ``` Teardown the registry. ## ComponentRegistry Objects ```python class ComponentRegistry(Registry[Tuple[PublicId, str], SkillComponentType], Generic[SkillComponentType]) ``` This class implements a generic registry for skill components. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Instantiate the registry. **Arguments**: - `kwargs`: kwargs #### register ```python def register(item_id: Tuple[PublicId, str], item: SkillComponentType, is_dynamically_added: bool = False) -> None ``` Register a item. **Arguments**: - `item_id`: a pair (skill id, item name). - `item`: the item to register. - `is_dynamically_added`: whether or not the item is dynamically added. **Raises**: - `None`: ValueError if an item is already registered with that item id. #### unregister ```python def unregister(item_id: Tuple[PublicId, str]) -> Optional[SkillComponentType] ``` Unregister a item. **Arguments**: - `item_id`: a pair (skill id, item name). **Raises**: - `None`: ValueError if no item registered with that item id. **Returns**: skill component #### fetch ```python def fetch(item_id: Tuple[PublicId, str]) -> Optional[SkillComponentType] ``` Fetch an item. **Arguments**: - `item_id`: the public id of the item. **Returns**: the Item #### fetch`_`by`_`skill ```python def fetch_by_skill(skill_id: PublicId) -> List[SkillComponentType] ``` Fetch all the items of a given skill. #### fetch`_`all ```python def fetch_all() -> List[SkillComponentType] ``` Fetch all the items. #### unregister`_`by`_`skill ```python def unregister_by_skill(skill_id: PublicId) -> None ``` Unregister all the components by skill. #### ids ```python def ids() -> Set[Tuple[PublicId, str]] ``` Get the item ids. #### setup ```python def setup() -> None ``` Set up the items in the registry. #### teardown ```python def teardown() -> None ``` Teardown the registry. ## HandlerRegistry Objects ```python class HandlerRegistry(ComponentRegistry[Handler]) ``` This class implements the handlers registry. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Instantiate the registry. **Arguments**: - `kwargs`: kwargs #### register ```python def register(item_id: Tuple[PublicId, str], item: Handler, is_dynamically_added: bool = False) -> None ``` Register a handler. **Arguments**: - `item_id`: the item id. - `item`: the handler. - `is_dynamically_added`: whether or not the item is dynamically added. **Raises**: - `ValueError`: if the protocol is None, or an item with pair (skill_id, protocol_id_ already exists. #### unregister ```python def unregister(item_id: Tuple[PublicId, str]) -> Handler ``` Unregister a item. **Arguments**: - `item_id`: a pair (skill id, item name). **Raises**: - `None`: ValueError if no item is registered with that item id. **Returns**: the unregistered handler #### unregister`_`by`_`skill ```python def unregister_by_skill(skill_id: PublicId) -> None ``` Unregister all the components by skill. #### fetch`_`by`_`protocol ```python def fetch_by_protocol(protocol_id: PublicId) -> List[Handler] ``` Fetch the handler by the pair protocol id and skill id. **Arguments**: - `protocol_id`: the protocol id **Returns**: the handlers registered for the protocol_id and skill_id #### fetch`_`by`_`protocol`_`and`_`skill ```python def fetch_by_protocol_and_skill(protocol_id: PublicId, skill_id: PublicId) -> Optional[Handler] ``` Fetch the handler by the pair protocol id and skill id. **Arguments**: - `protocol_id`: the protocol id - `skill_id`: the skill id. **Returns**: the handlers registered for the protocol_id and skill_id ================================================ FILE: docs/api/registries/filter.md ================================================ # aea.registries.filter This module contains registries. ## Filter Objects ```python class Filter(WithLogger) ``` This class implements the filter of an AEA. #### `__`init`__` ```python def __init__(resources: Resources, decision_maker_out_queue: AsyncFriendlyQueue) -> None ``` Instantiate the filter. **Arguments**: - `resources`: the resources - `decision_maker_out_queue`: the decision maker queue #### resources ```python @property def resources() -> Resources ``` Get resources. #### decision`_`maker`_`out`_`queue ```python @property def decision_maker_out_queue() -> AsyncFriendlyQueue ``` Get decision maker (out) queue. #### get`_`active`_`handlers ```python def get_active_handlers(protocol_id: PublicId, skill_id: Optional[PublicId] = None) -> List[Handler] ``` Get active handlers based on protocol id and optional skill id. **Arguments**: - `protocol_id`: the protocol id - `skill_id`: the skill id **Returns**: the list of handlers currently active #### get`_`active`_`behaviours ```python def get_active_behaviours() -> List[Behaviour] ``` Get the active behaviours. **Returns**: the list of behaviours currently active #### handle`_`new`_`handlers`_`and`_`behaviours ```python def handle_new_handlers_and_behaviours() -> None ``` Handle the messages from the decision maker. #### get`_`internal`_`message ```python async def get_internal_message() -> Optional[Message] ``` Get a message from decision_maker_out_queue. #### handle`_`internal`_`message ```python def handle_internal_message(internal_message: Optional[Message]) -> None ``` Handle internal message. ================================================ FILE: docs/api/registries/resources.md ================================================ # aea.registries.resources This module contains the resources class. ## Resources Objects ```python class Resources() ``` This class implements the object that holds the resources of an AEA. #### `__`init`__` ```python def __init__(agent_name: str = "standalone") -> None ``` Instantiate the resources. **Arguments**: - `agent_name`: the name of the agent #### agent`_`name ```python @property def agent_name() -> str ``` Get the agent name. #### component`_`registry ```python @property def component_registry() -> AgentComponentRegistry ``` Get the agent component registry. #### behaviour`_`registry ```python @property def behaviour_registry() -> ComponentRegistry[Behaviour] ``` Get the behaviour registry. #### handler`_`registry ```python @property def handler_registry() -> HandlerRegistry ``` Get the handler registry. #### model`_`registry ```python @property def model_registry() -> ComponentRegistry[Model] ``` Get the model registry. #### add`_`component ```python def add_component(component: Component) -> None ``` Add a component to resources. #### add`_`protocol ```python def add_protocol(protocol: Protocol) -> None ``` Add a protocol to the set of resources. **Arguments**: - `protocol`: a protocol #### get`_`protocol ```python def get_protocol(protocol_id: PublicId) -> Optional[Protocol] ``` Get protocol for given protocol id. **Arguments**: - `protocol_id`: the protocol id **Returns**: a matching protocol, if present, else None #### get`_`protocol`_`by`_`specification`_`id ```python def get_protocol_by_specification_id( protocol_specification_id: PublicId) -> Optional[Protocol] ``` Get protocol for given protocol_specification_id. **Arguments**: - `protocol_specification_id`: the protocol id **Returns**: a matching protocol, if present, else None #### get`_`all`_`protocols ```python def get_all_protocols() -> List[Protocol] ``` Get the list of all the protocols. **Returns**: the list of protocols. #### remove`_`protocol ```python def remove_protocol(protocol_id: PublicId) -> None ``` Remove a protocol from the set of resources. **Arguments**: - `protocol_id`: the protocol id for the protocol to be removed. #### add`_`contract ```python def add_contract(contract: Contract) -> None ``` Add a contract to the set of resources. **Arguments**: - `contract`: a contract #### get`_`contract ```python def get_contract(contract_id: PublicId) -> Optional[Contract] ``` Get contract for given contract id. **Arguments**: - `contract_id`: the contract id **Returns**: a matching contract, if present, else None #### get`_`all`_`contracts ```python def get_all_contracts() -> List[Contract] ``` Get the list of all the contracts. **Returns**: the list of contracts. #### remove`_`contract ```python def remove_contract(contract_id: PublicId) -> None ``` Remove a contract from the set of resources. **Arguments**: - `contract_id`: the contract id for the contract to be removed. #### add`_`connection ```python def add_connection(connection: Connection) -> None ``` Add a connection to the set of resources. **Arguments**: - `connection`: a connection #### get`_`connection ```python def get_connection(connection_id: PublicId) -> Optional[Connection] ``` Get connection for given connection id. **Arguments**: - `connection_id`: the connection id **Returns**: a matching connection, if present, else None #### get`_`all`_`connections ```python def get_all_connections() -> List[Connection] ``` Get the list of all the connections. **Returns**: the list of connections. #### remove`_`connection ```python def remove_connection(connection_id: PublicId) -> None ``` Remove a connection from the set of resources. **Arguments**: - `connection_id`: the connection id for the connection to be removed. #### add`_`skill ```python def add_skill(skill: Skill) -> None ``` Add a skill to the set of resources. **Arguments**: - `skill`: a skill #### get`_`skill ```python def get_skill(skill_id: PublicId) -> Optional[Skill] ``` Get the skill for a given skill id. **Arguments**: - `skill_id`: the skill id **Returns**: a matching skill, if present, else None #### get`_`all`_`skills ```python def get_all_skills() -> List[Skill] ``` Get the list of all the skills. **Returns**: the list of skills. #### remove`_`skill ```python def remove_skill(skill_id: PublicId) -> None ``` Remove a skill from the set of resources. **Arguments**: - `skill_id`: the skill id for the skill to be removed. #### get`_`handler ```python def get_handler(protocol_id: PublicId, skill_id: PublicId) -> Optional[Handler] ``` Get a specific handler. **Arguments**: - `protocol_id`: the protocol id the handler is handling - `skill_id`: the skill id of the handler's skill **Returns**: the handler #### get`_`handlers ```python def get_handlers(protocol_id: PublicId) -> List[Handler] ``` Get all handlers for a given protocol. **Arguments**: - `protocol_id`: the protocol id the handler is handling **Returns**: the list of handlers matching the protocol #### get`_`all`_`handlers ```python def get_all_handlers() -> List[Handler] ``` Get all handlers from all skills. **Returns**: the list of handlers #### get`_`behaviour ```python def get_behaviour(skill_id: PublicId, behaviour_name: str) -> Optional[Behaviour] ``` Get a specific behaviours for a given skill. **Arguments**: - `skill_id`: the skill id - `behaviour_name`: the behaviour name **Returns**: the behaviour, if it is present, else None #### get`_`behaviours ```python def get_behaviours(skill_id: PublicId) -> List[Behaviour] ``` Get all behaviours for a given skill. **Arguments**: - `skill_id`: the skill id **Returns**: the list of behaviours of the skill #### get`_`all`_`behaviours ```python def get_all_behaviours() -> List[Behaviour] ``` Get all behaviours from all skills. **Returns**: the list of all behaviours #### setup ```python def setup() -> None ``` Set up the resources. Calls setup on all resources. #### teardown ```python def teardown() -> None ``` Teardown the resources. Calls teardown on all resources. ================================================ FILE: docs/api/runner.md ================================================ # aea.runner This module contains the implementation of AEA multiple instances runner. ## AEAInstanceTask Objects ```python class AEAInstanceTask(AbstractExecutorTask) ``` Task to run agent instance. #### `__`init`__` ```python def __init__(agent: AEA) -> None ``` Init aea instance task. **Arguments**: - `agent`: AEA instance to run within task. #### id ```python @property def id() -> str ``` Return agent name. #### start ```python def start() -> None ``` Start task. #### stop ```python def stop() -> None ``` Stop task. #### create`_`async`_`task ```python def create_async_task(loop: AbstractEventLoop) -> TaskAwaitable ``` Return asyncio Task for task run in asyncio loop. **Arguments**: - `loop`: abstract event loop **Returns**: task to run runtime ## AEARunner Objects ```python class AEARunner(AbstractMultipleRunner) ``` Run multiple AEA instances. #### `__`init`__` ```python def __init__( agents: Sequence[AEA], mode: str, fail_policy: ExecutorExceptionPolicies = ExecutorExceptionPolicies. propagate ) -> None ``` Init AEARunner. **Arguments**: - `agents`: sequence of AEA instances to run. - `mode`: executor name to use. - `fail_policy`: one of ExecutorExceptionPolicies to be used with Executor ================================================ FILE: docs/api/runtime.md ================================================ # aea.runtime This module contains the implementation of runtime for economic agent (AEA). ## RuntimeStates Objects ```python class RuntimeStates(Enum) ``` Runtime states. ## BaseRuntime Objects ```python class BaseRuntime(Runnable, WithLogger) ``` Abstract runtime class to create implementations. #### `__`init`__` ```python def __init__(agent: AbstractAgent, multiplexer_options: Dict, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, threaded: bool = False, task_manager_mode: Optional[str] = None) -> None ``` Init runtime. **Arguments**: - `agent`: Agent to run. - `multiplexer_options`: options for the multiplexer. - `loop_mode`: agent main loop mode. - `loop`: optional event loop. if not provided a new one will be created. - `threaded`: if True, run in threaded mode, else async - `task_manager_mode`: mode of the task manager. #### storage ```python @property def storage() -> Optional[Storage] ``` Get optional storage. #### loop`_`mode ```python @property def loop_mode() -> str ``` Get current loop mode. #### task`_`manager ```python @property def task_manager() -> TaskManager ``` Get the task manager. #### loop ```python @property def loop() -> Optional[AbstractEventLoop] ``` Get event loop. #### agent`_`loop ```python @property def agent_loop() -> BaseAgentLoop ``` Get the agent loop. #### multiplexer ```python @property def multiplexer() -> AsyncMultiplexer ``` Get multiplexer. #### is`_`running ```python @property def is_running() -> bool ``` Get running state of the runtime. #### is`_`stopped ```python @property def is_stopped() -> bool ``` Get stopped state of the runtime. #### state ```python @property def state() -> RuntimeStates ``` Get runtime state. **Returns**: RuntimeStates #### decision`_`maker ```python @property def decision_maker() -> DecisionMaker ``` Return decision maker if set. #### set`_`decision`_`maker ```python def set_decision_maker(decision_maker_handler: DecisionMakerHandler) -> None ``` Set decision maker with handler provided. #### set`_`loop ```python def set_loop(loop: AbstractEventLoop) -> None ``` Set event loop to be used. **Arguments**: - `loop`: event loop to use. ## AsyncRuntime Objects ```python class AsyncRuntime(BaseRuntime) ``` Asynchronous runtime: uses asyncio loop for multiplexer and async agent main loop. #### `__`init`__` ```python def __init__(agent: AbstractAgent, multiplexer_options: Dict, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, threaded: bool = False, task_manager_mode: Optional[str] = None) -> None ``` Init runtime. **Arguments**: - `agent`: Agent to run. - `multiplexer_options`: options for the multiplexer. - `loop_mode`: agent main loop mode. - `loop`: optional event loop. if not provided a new one will be created. - `threaded`: if True, run in threaded mode, else async - `task_manager_mode`: mode of the task manager. #### set`_`loop ```python def set_loop(loop: AbstractEventLoop) -> None ``` Set event loop to be used. **Arguments**: - `loop`: event loop to use. #### run ```python async def run() -> None ``` Start runtime task. Starts multiplexer and agent loop. #### stop`_`runtime ```python async def stop_runtime() -> None ``` Stop runtime coroutine. Stop main loop. Tear down the agent.. Disconnect multiplexer. #### run`_`runtime ```python async def run_runtime() -> None ``` Run runtime which means start agent loop, multiplexer and storage. ## ThreadedRuntime Objects ```python class ThreadedRuntime(AsyncRuntime) ``` Run agent and multiplexer in different threads with own asyncio loops. ================================================ FILE: docs/api/skills/base.md ================================================ # aea.skills.base This module contains the base classes for the skills. ## SkillContext Objects ```python class SkillContext() ``` This class implements the context of a skill. #### `__`init`__` ```python def __init__(agent_context: Optional[AgentContext] = None, skill: Optional["Skill"] = None) -> None ``` Initialize a skill context. **Arguments**: - `agent_context`: the agent context. - `skill`: the skill. #### logger ```python @property def logger() -> Logger ``` Get the logger. #### logger ```python @logger.setter def logger(logger_: Logger) -> None ``` Set the logger. #### set`_`agent`_`context ```python def set_agent_context(agent_context: AgentContext) -> None ``` Set the agent context. #### shared`_`state ```python @property def shared_state() -> Dict[str, Any] ``` Get the shared state dictionary. #### agent`_`name ```python @property def agent_name() -> str ``` Get agent name. #### skill`_`id ```python @property def skill_id() -> PublicId ``` Get the skill id of the skill context. #### is`_`active ```python @property def is_active() -> bool ``` Get the status of the skill (active/not active). #### is`_`active ```python @is_active.setter def is_active(value: bool) -> None ``` Set the status of the skill (active/not active). #### new`_`behaviours ```python @property def new_behaviours() -> "Queue[Behaviour]" ``` Queue for the new behaviours. This queue can be used to send messages to the framework to request the registration of a behaviour. **Returns**: the queue of new behaviours. #### new`_`handlers ```python @property def new_handlers() -> "Queue[Handler]" ``` Queue for the new handlers. This queue can be used to send messages to the framework to request the registration of a handler. **Returns**: the queue of new handlers. #### agent`_`addresses ```python @property def agent_addresses() -> Dict[str, str] ``` Get addresses. #### agent`_`address ```python @property def agent_address() -> str ``` Get address. #### public`_`key ```python @property def public_key() -> str ``` Get public key. #### public`_`keys ```python @property def public_keys() -> Dict[str, str] ``` Get public keys. #### connection`_`status ```python @property def connection_status() -> MultiplexerStatus ``` Get connection status. #### outbox ```python @property def outbox() -> OutBox ``` Get outbox. #### storage ```python @property def storage() -> Optional[Storage] ``` Get optional storage for agent. #### message`_`in`_`queue ```python @property def message_in_queue() -> Queue ``` Get message in queue. #### decision`_`maker`_`message`_`queue ```python @property def decision_maker_message_queue() -> Queue ``` Get message queue of decision maker. #### decision`_`maker`_`handler`_`context ```python @property def decision_maker_handler_context() -> SimpleNamespace ``` Get decision maker handler context. #### task`_`manager ```python @property def task_manager() -> TaskManager ``` Get behaviours of the skill. #### default`_`ledger`_`id ```python @property def default_ledger_id() -> str ``` Get the default ledger id. #### currency`_`denominations ```python @property def currency_denominations() -> Dict[str, str] ``` Get a dictionary mapping ledger ids to currency denominations. #### search`_`service`_`address ```python @property def search_service_address() -> Address ``` Get the address of the search service. #### decision`_`maker`_`address ```python @property def decision_maker_address() -> Address ``` Get the address of the decision maker. #### handlers ```python @property def handlers() -> SimpleNamespace ``` Get handlers of the skill. #### behaviours ```python @property def behaviours() -> SimpleNamespace ``` Get behaviours of the skill. #### namespace ```python @property def namespace() -> SimpleNamespace ``` Get the agent context namespace. #### `__`getattr`__` ```python def __getattr__(item: Any) -> Any ``` Get attribute. #### send`_`to`_`skill ```python def send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None ``` Send message or envelope to another skill. If message passed it will be wrapped into envelope with optional envelope context. **Arguments**: - `message_or_envelope`: envelope to send to another skill. - `context`: the optional envelope context ## SkillComponent Objects ```python class SkillComponent(ABC) ``` This class defines an abstract interface for skill component classes. #### `__`init`__` ```python def __init__(name: str, skill_context: SkillContext, configuration: Optional[SkillComponentConfiguration] = None, **kwargs: Any) -> None ``` Initialize a skill component. **Arguments**: - `name`: the name of the component. - `configuration`: the configuration for the component. - `skill_context`: the skill context. - `kwargs`: the keyword arguments. #### name ```python @property def name() -> str ``` Get the name of the skill component. #### context ```python @property def context() -> SkillContext ``` Get the context of the skill component. #### skill`_`id ```python @property def skill_id() -> PublicId ``` Get the skill id of the skill component. #### configuration ```python @property def configuration() -> SkillComponentConfiguration ``` Get the skill component configuration. #### config ```python @property def config() -> Dict[Any, Any] ``` Get the config of the skill component. #### setup ```python @abstractmethod def setup() -> None ``` Implement the setup. #### teardown ```python @abstractmethod def teardown() -> None ``` Implement the teardown. #### parse`_`module ```python @classmethod @abstractmethod def parse_module(cls, path: str, configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext) -> dict ``` Parse the component module. ## AbstractBehaviour Objects ```python class AbstractBehaviour(SkillComponent, ABC) ``` Abstract behaviour for periodical calls. tick_interval: float, interval to call behaviour's act. start_at: optional datetime, when to start periodical calls. #### tick`_`interval ```python @property def tick_interval() -> float ``` Get the tick_interval in seconds. #### start`_`at ```python @property def start_at() -> Optional[datetime.datetime] ``` Get the start time of the behaviour. ## Behaviour Objects ```python class Behaviour(AbstractBehaviour, ABC) ``` This class implements an abstract behaviour. In a subclass of Behaviour, the flag 'is_programmatically_defined' can be used by the developer to signal to the framework that the class is meant to be used programmatically; hence, in case the class is not declared in the configuration file but it is present in a skill module, the framework will just ignore this class instead of printing a warning message. #### act ```python @abstractmethod def act() -> None ``` Implement the behaviour. **Returns**: None #### is`_`done ```python def is_done() -> bool ``` Return True if the behaviour is terminated, False otherwise. #### act`_`wrapper ```python def act_wrapper() -> None ``` Wrap the call of the action. This method must be called only by the framework. #### parse`_`module ```python @classmethod def parse_module(cls, path: str, behaviour_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext) -> Dict[str, "Behaviour"] ``` Parse the behaviours module. **Arguments**: - `path`: path to the Python module containing the Behaviour classes. - `behaviour_configs`: a list of behaviour configurations. - `skill_context`: the skill context **Returns**: a list of Behaviour. ## Handler Objects ```python class Handler(SkillComponent, ABC) ``` This class implements an abstract behaviour. In a subclass of Handler, the flag 'is_programmatically_defined' can be used by the developer to signal to the framework that the component is meant to be used programmatically; hence, in case the class is not declared in the configuration file but it is present in a skill module, the framework will just ignore this class instead of printing a warning message. SUPPORTED_PROTOCOL is read by the framework when the handlers are loaded to register them as 'listeners' to the protocol identified by the specified public id. Whenever a message of protocol 'SUPPORTED_PROTOCOL' is sent to the agent, the framework will call the 'handle' method. #### handle ```python @abstractmethod def handle(message: Message) -> None ``` Implement the reaction to a message. **Arguments**: - `message`: the message **Returns**: None #### handle`_`wrapper ```python def handle_wrapper(message: Message) -> None ``` Wrap the call of the handler. This method must be called only by the framework. #### parse`_`module ```python @classmethod def parse_module(cls, path: str, handler_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext) -> Dict[str, "Handler"] ``` Parse the handler module. **Arguments**: - `path`: path to the Python module containing the Handler class. - `handler_configs`: the list of handler configurations. - `skill_context`: the skill context **Returns**: an handler, or None if the parsing fails. ## Model Objects ```python class Model(SkillComponent, ABC) ``` This class implements an abstract model. #### `__`init`__` ```python def __init__(name: str, skill_context: SkillContext, configuration: Optional[SkillComponentConfiguration] = None, keep_terminal_state_dialogues: Optional[bool] = None, **kwargs: Any) -> None ``` Initialize a model. **Arguments**: - `name`: the name of the component. - `configuration`: the configuration for the component. - `skill_context`: the skill context. - `keep_terminal_state_dialogues`: specify do dialogues in terminal state should stay or not - `kwargs`: the keyword arguments. #### setup ```python def setup() -> None ``` Set the class up. #### teardown ```python def teardown() -> None ``` Tear the class down. #### parse`_`module ```python @classmethod def parse_module(cls, path: str, model_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext) -> Dict[str, "Model"] ``` Parse the model module. **Arguments**: - `path`: path to the Python skill module. - `model_configs`: a list of model configurations. - `skill_context`: the skill context **Returns**: a list of Model. ## Skill Objects ```python class Skill(Component) ``` This class implements a skill. #### `__`init`__` ```python def __init__(configuration: SkillConfig, skill_context: Optional[SkillContext] = None, handlers: Optional[Dict[str, Handler]] = None, behaviours: Optional[Dict[str, Behaviour]] = None, models: Optional[Dict[str, Model]] = None, **kwargs: Any) ``` Initialize a skill. **Arguments**: - `configuration`: the skill configuration. - `skill_context`: the skill context. - `handlers`: dictionary of handlers. - `behaviours`: dictionary of behaviours. - `models`: dictionary of models. - `kwargs`: the keyword arguments. #### skill`_`context ```python @property def skill_context() -> SkillContext ``` Get the skill context. #### handlers ```python @property def handlers() -> Dict[str, Handler] ``` Get the handlers. #### behaviours ```python @property def behaviours() -> Dict[str, Behaviour] ``` Get the handlers. #### models ```python @property def models() -> Dict[str, Model] ``` Get the handlers. #### from`_`dir ```python @classmethod def from_dir(cls, directory: str, agent_context: AgentContext, **kwargs: Any) -> "Skill" ``` Load the skill from a directory. **Arguments**: - `directory`: the directory to the skill package. - `agent_context`: the skill context. - `kwargs`: the keyword arguments. **Returns**: the skill object. #### logger ```python @property def logger() -> Logger ``` Get the logger. In the case of a skill, return the logger provided by the skill context. **Returns**: the logger #### logger ```python @logger.setter def logger(*args: str) -> None ``` Set the logger. #### from`_`config ```python @classmethod def from_config(cls, configuration: SkillConfig, agent_context: AgentContext, **kwargs: Any) -> "Skill" ``` Load the skill from configuration. **Arguments**: - `configuration`: a skill configuration. Must be associated with a directory. - `agent_context`: the agent context. - `kwargs`: the keyword arguments. **Returns**: the skill. ## `_`SkillComponentLoadingItem Objects ```python class _SkillComponentLoadingItem() ``` Class to represent a triple (component name, component configuration, component class). #### `__`init`__` ```python def __init__(name: str, config: SkillComponentConfiguration, class_: Type[SkillComponent], type_: _SKILL_COMPONENT_TYPES) ``` Initialize the item. ## `_`SkillComponentLoader Objects ```python class _SkillComponentLoader() ``` This class implements the loading policy for skill components. #### `__`init`__` ```python def __init__(configuration: SkillConfig, skill_context: SkillContext, **kwargs: Any) ``` Initialize the helper class. #### load`_`skill ```python def load_skill() -> Skill ``` Load the skill. ================================================ FILE: docs/api/skills/behaviours.md ================================================ # aea.skills.behaviours This module contains the classes for specific behaviours. ## SimpleBehaviour Objects ```python class SimpleBehaviour(Behaviour, ABC) ``` This class implements a simple behaviour. #### `__`init`__` ```python def __init__(act: Optional[Callable[[], None]] = None, **kwargs: Any) -> None ``` Initialize a simple behaviour. **Arguments**: - `act`: the act callable. - `kwargs`: the keyword arguments to be passed to the parent class. #### setup ```python def setup() -> None ``` Set the behaviour up. #### act ```python def act() -> None ``` Do the action. #### teardown ```python def teardown() -> None ``` Tear the behaviour down. ## CompositeBehaviour Objects ```python class CompositeBehaviour(Behaviour, ABC) ``` This class implements a composite behaviour. ## CyclicBehaviour Objects ```python class CyclicBehaviour(SimpleBehaviour, ABC) ``` This behaviour is executed until the agent is stopped. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize the cyclic behaviour. #### number`_`of`_`executions ```python @property def number_of_executions() -> int ``` Get the number of executions. #### act`_`wrapper ```python def act_wrapper() -> None ``` Wrap the call of the action. This method must be called only by the framework. #### is`_`done ```python def is_done() -> bool ``` Return True if the behaviour is terminated, False otherwise. The user should implement it properly to determine the stopping condition. **Returns**: bool indicating status ## OneShotBehaviour Objects ```python class OneShotBehaviour(SimpleBehaviour, ABC) ``` This behaviour is executed only once. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize the cyclic behaviour. #### is`_`done ```python def is_done() -> bool ``` Return True if the behaviour is terminated, False otherwise. #### act`_`wrapper ```python def act_wrapper() -> None ``` Wrap the call of the action. This method must be called only by the framework. ## TickerBehaviour Objects ```python class TickerBehaviour(SimpleBehaviour, ABC) ``` This behaviour is executed periodically with an interval. #### `__`init`__` ```python def __init__(tick_interval: float = 1.0, start_at: Optional[datetime.datetime] = None, **kwargs: Any) -> None ``` Initialize the ticker behaviour. **Arguments**: - `tick_interval`: interval of the behaviour in seconds. - `start_at`: whether to start the behaviour with an offset. - `kwargs`: the keyword arguments. #### tick`_`interval ```python @property def tick_interval() -> float ``` Get the tick_interval in seconds. #### start`_`at ```python @property def start_at() -> datetime.datetime ``` Get the start time. #### last`_`act`_`time ```python @property def last_act_time() -> datetime.datetime ``` Get the last time the act method has been called. #### act`_`wrapper ```python def act_wrapper() -> None ``` Wrap the call of the action. This method must be called only by the framework. #### is`_`time`_`to`_`act ```python def is_time_to_act() -> bool ``` Check whether it is time to act, according to the tick_interval constraint and the 'start at' constraint. **Returns**: True if it is time to act, false otherwise. ## SequenceBehaviour Objects ```python class SequenceBehaviour(CompositeBehaviour, ABC) ``` This behaviour executes sub-behaviour serially. #### `__`init`__` ```python def __init__(behaviour_sequence: List[Behaviour], **kwargs: Any) -> None ``` Initialize the sequence behaviour. **Arguments**: - `behaviour_sequence`: the sequence of behaviour. - `kwargs`: the keyword arguments #### current`_`behaviour ```python @property def current_behaviour() -> Optional[Behaviour] ``` Get the current behaviour. If None, the sequence behaviour can be considered done. **Returns**: current behaviour or None #### act ```python def act() -> None ``` Implement the behaviour. #### is`_`done ```python def is_done() -> bool ``` Return True if the behaviour is terminated, False otherwise. ## State Objects ```python class State(SimpleBehaviour, ABC) ``` A state of a FSMBehaviour. A State behaviour is a simple behaviour with a special property 'event' that is opportunely set by the implementer. The event is read by the framework when the behaviour is done in order to pick the transition to trigger. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize a state of the state machine. #### event ```python @property def event() -> Optional[str] ``` Get the event to be triggered at the end of the behaviour. #### is`_`done ```python @abstractmethod def is_done() -> bool ``` Return True if the behaviour is terminated, False otherwise. #### reset ```python def reset() -> None ``` Reset initial conditions. ## FSMBehaviour Objects ```python class FSMBehaviour(CompositeBehaviour, ABC) ``` This class implements a finite-state machine behaviour. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize the finite-state machine behaviour. #### is`_`started ```python @property def is_started() -> bool ``` Check if the behaviour is started. #### register`_`state ```python def register_state(name: str, state: State, initial: bool = False) -> None ``` Register a state. **Arguments**: - `name`: the name of the state. - `state`: the behaviour in that state. - `initial`: whether the state is an initial state. **Raises**: - `ValueError`: if a state with the provided name already exists. #### register`_`final`_`state ```python def register_final_state(name: str, state: State) -> None ``` Register a final state. **Arguments**: - `name`: the name of the state. - `state`: the state. **Raises**: - `ValueError`: if a state with the provided name already exists. #### unregister`_`state ```python def unregister_state(name: str) -> None ``` Unregister a state. **Arguments**: - `name`: the state name to unregister. **Raises**: - `ValueError`: if the state is not registered. #### states ```python @property def states() -> Set[str] ``` Get all the state names. #### initial`_`state ```python @property def initial_state() -> Optional[str] ``` Get the initial state name. #### initial`_`state ```python @initial_state.setter def initial_state(name: str) -> None ``` Set the initial state. #### final`_`states ```python @property def final_states() -> Set[str] ``` Get the final state names. #### get`_`state ```python def get_state(name: str) -> Optional[State] ``` Get a state from its name. #### act ```python def act() -> None ``` Implement the behaviour. #### is`_`done ```python def is_done() -> bool ``` Return True if the behaviour is terminated, False otherwise. #### register`_`transition ```python def register_transition(source: str, destination: str, event: Optional[str] = None) -> None ``` Register a transition. No sanity check is done. **Arguments**: - `source`: the source state name. - `destination`: the destination state name. - `event`: the event. **Raises**: - `ValueError`: if a transition from source with event is already present. #### unregister`_`transition ```python def unregister_transition(source: str, destination: str, event: Optional[str] = None) -> None ``` Unregister a transition. **Arguments**: - `source`: the source state name. - `destination`: the destination state name. - `event`: the event. **Raises**: - `ValueError`: if a transition from source with event is not present. ================================================ FILE: docs/api/skills/tasks.md ================================================ # aea.skills.tasks This module contains the classes for tasks. ## Task Objects ```python class Task(WithLogger) ``` This class implements an abstract task. #### `__`init`__` ```python def __init__(**kwargs: Any) -> None ``` Initialize a task. #### `__`call`__` ```python def __call__(*args: Any, **kwargs: Any) -> Any ``` Execute the task. **Arguments**: - `args`: positional arguments forwarded to the 'execute' method. - `kwargs`: keyword arguments forwarded to the 'execute' method. **Raises**: - `ValueError`: if the task has already been executed. **Returns**: the task instance #### is`_`executed ```python @property def is_executed() -> bool ``` Check if the task has already been executed. #### result ```python @property def result() -> Any ``` Get the result. **Raises**: - `ValueError`: if the task has not been executed yet. **Returns**: the result from the execute method. #### setup ```python def setup() -> None ``` Implement the task setup. #### execute ```python @abstractmethod def execute(*args: Any, **kwargs: Any) -> Any ``` Run the task logic. **Arguments**: - `args`: the positional arguments - `kwargs`: the keyword arguments **Returns**: any #### teardown ```python def teardown() -> None ``` Implement the task teardown. #### init`_`worker ```python def init_worker() -> None ``` Initialize a worker. Disable the SIGINT handler of process pool is using. Related to a well-known bug: https://bugs.python.org/issue8296 ## TaskManager Objects ```python class TaskManager(WithLogger) ``` A Task manager. #### `__`init`__` ```python def __init__(nb_workers: int = DEFAULT_WORKERS_AMOUNT, is_lazy_pool_start: bool = True, logger: Optional[logging.Logger] = None, pool_mode: str = THREAD_POOL_MODE) -> None ``` Initialize the task manager. **Arguments**: - `nb_workers`: the number of worker processes. - `is_lazy_pool_start`: option to postpone pool creation till the first enqueue_task called. - `logger`: the logger. - `pool_mode`: str. multithread or multiprocess #### is`_`started ```python @property def is_started() -> bool ``` Get started status of TaskManager. **Returns**: bool #### nb`_`workers ```python @property def nb_workers() -> int ``` Get the number of workers. **Returns**: int #### enqueue`_`task ```python def enqueue_task(func: Callable, args: Sequence = (), kwargs: Optional[Dict[str, Any]] = None) -> int ``` Enqueue a task with the executor. **Arguments**: - `func`: the callable instance to be enqueued - `args`: the positional arguments to be passed to the function. - `kwargs`: the keyword arguments to be passed to the function. **Raises**: - `ValueError`: if the task manager is not running. **Returns**: the task id to get the the result. #### get`_`task`_`result ```python def get_task_result(task_id: int) -> AsyncResult ``` Get the result from a task. **Arguments**: - `task_id`: the task id **Returns**: async result for task_id #### start ```python def start() -> None ``` Start the task manager. #### stop ```python def stop() -> None ``` Stop the task manager. ## ThreadedTaskManager Objects ```python class ThreadedTaskManager(TaskManager) ``` A threaded task manager. #### `__`init`__` ```python def __init__(nb_workers: int = DEFAULT_WORKERS_AMOUNT, is_lazy_pool_start: bool = True, logger: Optional[logging.Logger] = None) -> None ``` Initialize the task manager. **Arguments**: - `nb_workers`: the number of worker processes. - `is_lazy_pool_start`: option to postpone pool creation till the first enqueue_task called. - `logger`: the logger. ## ProcessTaskManager Objects ```python class ProcessTaskManager(TaskManager) ``` A multiprocess task manager. #### `__`init`__` ```python def __init__(nb_workers: int = DEFAULT_WORKERS_AMOUNT, is_lazy_pool_start: bool = True, logger: Optional[logging.Logger] = None) -> None ``` Initialize the task manager. **Arguments**: - `nb_workers`: the number of worker processes. - `is_lazy_pool_start`: option to postpone pool creation till the first enqueue_task called. - `logger`: the logger. ================================================ FILE: docs/api/test_tools/constants.md ================================================ # aea.test`_`tools.constants This is a module with constants for test tools. ================================================ FILE: docs/api/test_tools/exceptions.md ================================================ # aea.test`_`tools.exceptions Module with AEA testing exceptions. ## AEATestingException Objects ```python class AEATestingException(Exception) ``` An exception to be raised on incorrect testing tools usage. ================================================ FILE: docs/api/test_tools/generic.md ================================================ # aea.test`_`tools.generic This module contains generic tools for AEA end-to-end testing. #### write`_`envelope`_`to`_`file ```python def write_envelope_to_file(envelope: Envelope, file_path: str) -> None ``` Write an envelope to a file. **Arguments**: - `envelope`: Envelope. - `file_path`: the file path #### read`_`envelope`_`from`_`file ```python def read_envelope_from_file(file_path: str) -> Envelope ``` Read an envelope from a file. **Arguments**: - `file_path`: the file path. **Returns**: envelope #### nested`_`set`_`config ```python def nested_set_config(dotted_path: str, value: Any, author: str = DEFAULT_AUTHOR) -> None ``` Set an AEA config with nested values. Run from agent's directory. Allowed dotted_path: 'agent.an_attribute_name' 'protocols.my_protocol.an_attribute_name' 'connections.my_connection.an_attribute_name' 'contracts.my_contract.an_attribute_name' 'skills.my_skill.an_attribute_name' 'vendor.author.[protocols|connections|skills].package_name.attribute_name **Arguments**: - `dotted_path`: dotted path to a setting. - `value`: a value to assign. Must be of yaml serializable type. - `author`: the author name, used to parse the dotted path. ================================================ FILE: docs/api/test_tools/test_cases.md ================================================ # aea.test`_`tools.test`_`cases This module contains test case classes based on pytest for AEA end-to-end testing. ## BaseAEATestCase Objects ```python class BaseAEATestCase(ABC) ``` Base class for AEA test cases. #### set`_`agent`_`context ```python @classmethod def set_agent_context(cls, agent_name: str) -> None ``` Set the current agent context. #### unset`_`agent`_`context ```python @classmethod def unset_agent_context(cls) -> None ``` Unset the current agent context. #### set`_`config ```python @classmethod def set_config(cls, dotted_path: str, value: Any, type_: Optional[str] = None) -> Result ``` Set a config. Run from agent's directory. **Arguments**: - `dotted_path`: str dotted path to config param. - `value`: a new value to set. - `type_`: the type **Returns**: Result #### nested`_`set`_`config ```python @classmethod def nested_set_config(cls, dotted_path: str, value: Any) -> None ``` Force set config. #### disable`_`aea`_`logging ```python @classmethod def disable_aea_logging(cls) -> None ``` Disable AEA logging of specific agent. Run from agent's directory. #### run`_`cli`_`command ```python @classmethod def run_cli_command(cls, *args: str, cwd: str = ".", **kwargs: str) -> Result ``` Run AEA CLI command. **Arguments**: - `args`: CLI args - `cwd`: the working directory from where to run the command. - `kwargs`: other keyword arguments to click.CliRunner.invoke. **Raises**: - `AEATestingException`: if command fails. **Returns**: Result #### start`_`subprocess ```python @classmethod def start_subprocess(cls, *args: str, cwd: str = ".") -> subprocess.Popen ``` Run python with args as subprocess. **Arguments**: - `args`: CLI args - `cwd`: the current working directory **Returns**: subprocess object. #### start`_`thread ```python @classmethod def start_thread(cls, target: Callable, **kwargs: subprocess.Popen) -> Thread ``` Start python Thread. **Arguments**: - `target`: target method. - `kwargs`: thread keyword arguments **Returns**: thread #### create`_`agents ```python @classmethod def create_agents(cls, *agents_names: str, is_local: bool = True, is_empty: bool = False) -> None ``` Create agents in current working directory. **Arguments**: - `agents_names`: str agent names. - `is_local`: a flag for local folder add True by default. - `is_empty`: optional boolean flag for skip adding default dependencies. #### fetch`_`agent ```python @classmethod def fetch_agent(cls, public_id: str, agent_name: str, is_local: bool = True) -> None ``` Create agents in current working directory. **Arguments**: - `public_id`: str public id - `agent_name`: str agent name. - `is_local`: a flag for local folder add True by default. #### difference`_`to`_`fetched`_`agent ```python @classmethod def difference_to_fetched_agent(cls, public_id: str, agent_name: str) -> List[str] ``` Compare agent against the one fetched from public id. **Arguments**: - `public_id`: str public id - `agent_name`: str agent name. **Returns**: list of files differing in the projects #### delete`_`agents ```python @classmethod def delete_agents(cls, *agents_names: str) -> None ``` Delete agents in current working directory. **Arguments**: - `agents_names`: str agent names. #### run`_`agent ```python @classmethod def run_agent(cls, *args: str) -> subprocess.Popen ``` Run agent as subprocess. Run from agent's directory. **Arguments**: - `args`: CLI args **Returns**: subprocess object. #### run`_`interaction ```python @classmethod def run_interaction(cls) -> subprocess.Popen ``` Run interaction as subprocess. Run from agent's directory. **Returns**: subprocess object. #### terminate`_`agents ```python @classmethod def terminate_agents(cls, *subprocesses: subprocess.Popen, timeout: int = 20) -> None ``` Terminate agent subprocesses. Run from agent's directory. **Arguments**: - `subprocesses`: the subprocesses running the agents - `timeout`: the timeout for interruption #### is`_`successfully`_`terminated ```python @classmethod def is_successfully_terminated(cls, *subprocesses: subprocess.Popen) -> bool ``` Check if all subprocesses terminated successfully. #### initialize`_`aea ```python @classmethod def initialize_aea(cls, author: str) -> None ``` Initialize AEA locally with author name. #### add`_`item ```python @classmethod def add_item(cls, item_type: str, public_id: str, local: bool = True) -> Result ``` Add an item to the agent. Run from agent's directory. **Arguments**: - `item_type`: str item type. - `public_id`: public id of the item. - `local`: a flag for local folder add True by default. **Returns**: Result #### remove`_`item ```python @classmethod def remove_item(cls, item_type: str, public_id: str) -> Result ``` Remove an item from the agent. Run from agent's directory. **Arguments**: - `item_type`: str item type. - `public_id`: public id of the item. **Returns**: Result #### scaffold`_`item ```python @classmethod def scaffold_item(cls, item_type: str, name: str, skip_consistency_check: bool = False) -> Result ``` Scaffold an item for the agent. Run from agent's directory. **Arguments**: - `item_type`: str item type. - `name`: name of the item. - `skip_consistency_check`: if True, skip consistency check. **Returns**: Result #### fingerprint`_`item ```python @classmethod def fingerprint_item(cls, item_type: str, public_id: str) -> Result ``` Fingerprint an item for the agent. Run from agent's directory. **Arguments**: - `item_type`: str item type. - `public_id`: public id of the item. **Returns**: Result #### eject`_`item ```python @classmethod def eject_item(cls, item_type: str, public_id: str) -> Result ``` Eject an item in the agent in quiet mode (i.e. no interaction). Run from agent's directory. **Arguments**: - `item_type`: str item type. - `public_id`: public id of the item. **Returns**: None #### run`_`install ```python @classmethod def run_install(cls) -> Result ``` Execute AEA CLI install command. Run from agent's directory. **Returns**: Result #### generate`_`private`_`key ```python @classmethod def generate_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_file: Optional[str] = None, password: Optional[str] = None) -> Result ``` Generate AEA private key with CLI command. Run from agent's directory. **Arguments**: - `ledger_api_id`: ledger API ID. - `private_key_file`: the private key file. - `password`: the password. **Returns**: Result #### add`_`private`_`key ```python @classmethod def add_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_filepath: str = DEFAULT_PRIVATE_KEY_FILE, connection: bool = False, password: Optional[str] = None) -> Result ``` Add private key with CLI command. Run from agent's directory. **Arguments**: - `ledger_api_id`: ledger API ID. - `private_key_filepath`: private key filepath. - `connection`: whether or not the private key filepath is for a connection. - `password`: the password to encrypt private keys. **Returns**: Result #### remove`_`private`_`key ```python @classmethod def remove_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER, connection: bool = False) -> Result ``` Remove private key with CLI command. Run from agent's directory. **Arguments**: - `ledger_api_id`: ledger API ID. - `connection`: whether or not the private key filepath is for a connection. **Returns**: Result #### replace`_`private`_`key`_`in`_`file ```python @classmethod def replace_private_key_in_file( cls, private_key: str, private_key_filepath: str = DEFAULT_PRIVATE_KEY_FILE) -> None ``` Replace the private key in the provided file with the provided key. **Arguments**: - `private_key`: the private key - `private_key_filepath`: the filepath to the private key file **Raises**: - `None`: exception if file does not exist #### generate`_`wealth ```python @classmethod def generate_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER, password: Optional[str] = None) -> Result ``` Generate wealth with CLI command. Run from agent's directory. **Arguments**: - `ledger_api_id`: ledger API ID. - `password`: the password. **Returns**: Result #### get`_`wealth ```python @classmethod def get_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER, password: Optional[str] = None) -> str ``` Get wealth with CLI command. Run from agent's directory. **Arguments**: - `ledger_api_id`: ledger API ID. - `password`: the password to encrypt/decrypt private keys. **Returns**: command line output #### get`_`address ```python @classmethod def get_address(cls, ledger_api_id: str = DEFAULT_LEDGER, password: Optional[str] = None) -> str ``` Get address with CLI command. Run from agent's directory. **Arguments**: - `ledger_api_id`: ledger API ID. - `password`: the password to encrypt/decrypt private keys. **Returns**: command line output #### replace`_`file`_`content ```python @classmethod def replace_file_content(cls, src: Path, dest: Path) -> None ``` Replace the content of the source file to the destination file. **Arguments**: - `src`: the source file. - `dest`: the destination file. #### change`_`directory ```python @classmethod def change_directory(cls, path: Path) -> None ``` Change current working directory. **Arguments**: - `path`: path to the new working directory. #### send`_`envelope`_`to`_`agent ```python @classmethod def send_envelope_to_agent(cls, envelope: Envelope, agent: str) -> None ``` Send an envelope to an agent, using the stub connection. #### read`_`envelope`_`from`_`agent ```python @classmethod def read_envelope_from_agent(cls, agent: str) -> Envelope ``` Read an envelope from an agent, using the stub connection. #### missing`_`from`_`output ```python @classmethod def missing_from_output(cls, process: subprocess.Popen, strings: Sequence[str], timeout: int = DEFAULT_PROCESS_TIMEOUT, period: int = 1, is_terminating: bool = True) -> List[str] ``` Check if strings are present in process output. Read process stdout in thread and terminate when all strings are present or timeout expired. **Arguments**: - `process`: agent subprocess. - `strings`: tuple of strings expected to appear in output. - `timeout`: int amount of seconds before stopping check. - `period`: int period of checking. - `is_terminating`: whether or not the agents are terminated **Returns**: list of missed strings. #### is`_`running ```python @classmethod def is_running(cls, process: subprocess.Popen, timeout: int = DEFAULT_LAUNCH_TIMEOUT) -> bool ``` Check if the AEA is launched and running (ready to process messages). **Arguments**: - `process`: agent subprocess. - `timeout`: the timeout to wait for launch to complete **Returns**: bool indicating status #### invoke ```python @classmethod def invoke(cls, *args: str) -> Result ``` Call the cli command. #### load`_`agent`_`config ```python @classmethod def load_agent_config(cls, agent_name: str) -> AgentConfig ``` Load agent configuration. #### setup`_`class ```python @classmethod def setup_class(cls) -> None ``` Set up the test class. #### teardown`_`class ```python @classmethod def teardown_class(cls) -> None ``` Teardown the test. ## AEATestCaseEmpty Objects ```python class AEATestCaseEmpty(BaseAEATestCase) ``` Test case for a default AEA project. This test case will create a default AEA project. #### setup`_`class ```python @classmethod def setup_class(cls) -> None ``` Set up the test class. #### teardown`_`class ```python @classmethod def teardown_class(cls) -> None ``` Teardown the test class. ## AEATestCaseEmptyFlaky Objects ```python class AEATestCaseEmptyFlaky(AEATestCaseEmpty) ``` Test case for a default AEA project. This test case will create a default AEA project. Use for flaky tests with the flaky decorator. #### setup`_`class ```python @classmethod def setup_class(cls) -> None ``` Set up the test class. #### teardown`_`class ```python @classmethod def teardown_class(cls) -> None ``` Teardown the test class. ## AEATestCaseMany Objects ```python class AEATestCaseMany(BaseAEATestCase) ``` Test case for many AEA projects. #### setup`_`class ```python @classmethod def setup_class(cls) -> None ``` Set up the test class. #### teardown`_`class ```python @classmethod def teardown_class(cls) -> None ``` Teardown the test class. ## AEATestCaseManyFlaky Objects ```python class AEATestCaseManyFlaky(AEATestCaseMany) ``` Test case for many AEA projects which are flaky. Use for flaky tests with the flaky decorator. #### setup`_`class ```python @classmethod def setup_class(cls) -> None ``` Set up the test class. #### teardown`_`class ```python @classmethod def teardown_class(cls) -> None ``` Teardown the test class. ## AEATestCase Objects ```python class AEATestCase(BaseAEATestCase) ``` Test case from an existing AEA project. Subclass this class and set `path_to_aea` properly. By default, it is assumed the project is inside the current working directory. #### setup`_`class ```python @classmethod def setup_class(cls) -> None ``` Set up the test class. #### teardown`_`class ```python @classmethod def teardown_class(cls) -> None ``` Teardown the test class. ================================================ FILE: docs/api/test_tools/test_contract.md ================================================ # aea.test`_`tools.test`_`contract This module contains test case classes based on pytest for AEA contract testing. ## BaseContractTestCase Objects ```python class BaseContractTestCase(ABC) ``` A class to test a contract. #### contract ```python @property def contract() -> Contract ``` Get the contract. #### setup ```python @classmethod def setup(cls, **kwargs: Any) -> None ``` Set up the contract test case. #### finish`_`contract`_`deployment ```python @classmethod @abstractmethod def finish_contract_deployment(cls) -> str ``` Finish deploying contract. **Returns**: contract address #### refill`_`from`_`faucet ```python @staticmethod def refill_from_faucet(ledger_api: LedgerApi, faucet_api: FaucetApi, address: str) -> None ``` Refill from faucet. #### sign`_`send`_`confirm`_`receipt`_`multisig`_`transaction ```python @staticmethod def sign_send_confirm_receipt_multisig_transaction( tx: JSONLike, ledger_api: LedgerApi, cryptos: List[Crypto], sleep_time: float = 2.0) -> JSONLike ``` Sign, send and confirm settlement of a transaction with multiple signatures. **Arguments**: - `tx`: the transaction - `ledger_api`: the ledger api - `cryptos`: Cryptos to sign transaction with - `sleep_time`: the time to sleep between transaction submission and receipt request **Returns**: The transaction receipt #### sign`_`send`_`confirm`_`receipt`_`transaction ```python @classmethod def sign_send_confirm_receipt_transaction(cls, tx: JSONLike, ledger_api: LedgerApi, crypto: Crypto, sleep_time: float = 2.0) -> JSONLike ``` Sign, send and confirm settlement of a transaction with multiple signatures. **Arguments**: - `tx`: the transaction - `ledger_api`: the ledger api - `crypto`: Crypto to sign transaction with - `sleep_time`: the time to sleep between transaction submission and receipt request **Returns**: The transaction receipt ================================================ FILE: docs/api/test_tools/test_skill.md ================================================ # aea.test`_`tools.test`_`skill This module contains test case classes based on pytest for AEA skill testing. ## BaseSkillTestCase Objects ```python class BaseSkillTestCase() ``` A class to test a skill. #### skill ```python @property def skill() -> Skill ``` Get the skill. #### get`_`quantity`_`in`_`outbox ```python def get_quantity_in_outbox() -> int ``` Get the quantity of envelopes in the outbox. #### get`_`message`_`from`_`outbox ```python def get_message_from_outbox() -> Optional[Message] ``` Get message from outbox. #### drop`_`messages`_`from`_`outbox ```python def drop_messages_from_outbox(number: int = 1) -> None ``` Dismiss the first 'number' number of message from outbox. #### get`_`quantity`_`in`_`decision`_`maker`_`inbox ```python def get_quantity_in_decision_maker_inbox() -> int ``` Get the quantity of messages in the decision maker inbox. #### get`_`message`_`from`_`decision`_`maker`_`inbox ```python def get_message_from_decision_maker_inbox() -> Optional[Message] ``` Get message from decision maker inbox. #### drop`_`messages`_`from`_`decision`_`maker`_`inbox ```python def drop_messages_from_decision_maker_inbox(number: int = 1) -> None ``` Dismiss the first 'number' number of message from decision maker inbox. #### assert`_`quantity`_`in`_`outbox ```python def assert_quantity_in_outbox(expected_quantity: int) -> None ``` Assert the quantity of messages in the outbox. #### assert`_`quantity`_`in`_`decision`_`making`_`queue ```python def assert_quantity_in_decision_making_queue(expected_quantity: int) -> None ``` Assert the quantity of messages in the decision maker queue. #### message`_`has`_`attributes ```python @staticmethod def message_has_attributes(actual_message: Message, message_type: Type[Message], **kwargs: Any) -> Tuple[bool, str] ``` Evaluates whether a message's attributes match the expected attributes provided. **Arguments**: - `actual_message`: the actual message - `message_type`: the expected message type - `kwargs`: other expected message attributes **Returns**: boolean result of the evaluation and accompanied message #### build`_`incoming`_`message ```python def build_incoming_message(message_type: Type[Message], performative: Message.Performative, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Optional[Address] = None, is_agent_to_agent_messages: Optional[bool] = None, **kwargs: Any) -> Message ``` Quickly create an incoming message with the provided attributes. For any attribute not provided, the corresponding default value in message is used. **Arguments**: - `message_type`: the type of the message - `dialogue_reference`: the dialogue_reference - `message_id`: the message_id - `target`: the target - `performative`: the performative - `to`: the 'to' address - `sender`: the 'sender' address - `is_agent_to_agent_messages`: whether the dialogue is between agents or components - `kwargs`: other attributes **Returns**: the created incoming message #### build`_`incoming`_`message`_`for`_`skill`_`dialogue ```python def build_incoming_message_for_skill_dialogue( dialogue: Dialogue, performative: Message.Performative, message_type: Optional[Type[Message]] = None, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Optional[Address] = None, **kwargs: Any) -> Message ``` Quickly create an incoming message with the provided attributes for a dialogue. For any attribute not provided, a value based on the dialogue is used. These values are shown in parentheses in the list of parameters below. NOTE: This method must be used with care. The dialogue provided is part of the skill which is being tested. Because for any unspecified attribute, a "correct" value is used, the test will be, by design, insured to pass on these values. **Arguments**: - `dialogue`: the dialogue to which the incoming message is intended - `performative`: the performative of the message - `message_type`: (the message_class of the provided dialogue) the type of the message - `dialogue_reference`: (the dialogue_reference of the provided dialogue) the dialogue reference of the message - `message_id`: (the id of the last message in the provided dialogue + 1) the id of the message - `target`: (the id of the last message in the provided dialogue) the target of the message - `to`: (the agent address associated with this skill) the receiver of the message - `sender`: (the counterparty in the provided dialogue) the sender of the message - `kwargs`: other attributes **Returns**: the created incoming message #### prepare`_`skill`_`dialogue ```python def prepare_skill_dialogue( dialogues: Dialogues, messages: Tuple[DialogueMessage, ...], counterparty: Optional[Address] = None, is_agent_to_agent_messages: Optional[bool] = None) -> Dialogue ``` Quickly create a dialogue. The 'messages' argument is a tuple of DialogueMessages. For every DialogueMessage (performative, contents, is_incoming, target): - if 'is_incoming' is not provided: for the first message it is assumed False (outgoing), for any other message, it is the opposite of the one preceding it. - if 'target' is not provided: for the first message it is assumed 0, for any other message, it is the index of the message before it in the tuple of messages + 1. **Arguments**: - `dialogues`: a dialogues class - `counterparty`: the message_id - `messages`: the dialogue_reference - `is_agent_to_agent_messages`: whether the dialogue is between agents or components **Returns**: the created incoming message #### setup ```python @classmethod def setup(cls, **kwargs: Any) -> None ``` Set up the skill test case. ================================================ FILE: docs/application.md ================================================ # Application Areas ## Environments AEAs are most suited for environments which are: - **Decentralized**: there isn't a central authority that controls, manages, or makes decisions. - **Multi-Stakeholder**: the domain, problem, or solutions involve multiple distinct stakeholders. - **Peer-to-Peer**: interactions are (or could be made) direct and peer-to-peer. - **Complex, Incomplete, and Uncertain**: to the point that off-loading tasks to computational entities becomes valuable. ## Applications We identify a number of application areas for AEA-based solutions. This list is by no means comprehensive. In fact, we are most excited about applications which we have not thought of before. **Automation** : AEAs can automate well-defined processes in different domains, such as supply chain, mobility, finance, ... **Micro-transactions** : AEAs make it economically viable to execute trade involving small values. An example is use-cases with many small sellers (e.g. of data) on the supply side. **Wallet** : AEAs can simplify interactions with blockchains. By acting as "smart wallets", they can hide away the majority of the complexities involved in using blockchains for end users. **IoT** : Agents representing objects in the IoT (Internet of Things) space. For example, AEAs paired with hardware devices such as drones, laptops, heat sensors, etc., providing control and receiving data from the device. An example is a thermometer agent. **Web 2.0 <--> Web 3.0 interface** : Agents that interface and bridge the gap between existing (Web 2.0) and new (Web 3.0) economic models. An example is an AEA that communicates with HTTP clients/servers. **Digital data sales** : Agents with access to some data sources that sell the data, access to the data, or access to the usage of the data. An example is an AEA that continuously sells data to another AEA, who in turn uses it to improve their reinforcement learning model. ## Multi-Agent System VS Agent-Based Modelling The AEA framework enables the creation of multi-agent systems as technological solutions to real world problems. Although there are some overlap, the framework is not designed from the outset as an agent-based modelling software, where the goal is scientific behavioural observation rather than practical economic gain. Moreover, there is no restriction to _multi_; single-agent applications are also supported. ================================================ FILE: docs/aries-cloud-agent-demo.md ================================================ # Aries Cloud Agents Demo !!! note This demo is incomplete and will soon be updated. Demonstrating an entire decentralized identity scenario involving AEAs and instances of Aries Cloud Agents (ACAs). ## Discussion This demo corresponds with the one here from Aries cloud agent repository . The aim of this demo is to illustrate how AEAs can connect to ACAs, thus gaining all of their capabilities, such as issuing and requesting verifiable credentials, selective disclosure and zero knowledge proofs. ``` mermaid sequenceDiagram participant faea as Faber_AEA participant faca as Faber_ACA participant aaca as Alice_ACA participant aaea as Alice_AEA activate faea activate faca activate aaca activate aaea Note right of aaea: Shows P2P ID faea->>faca: Request status? faca->>faea: status faea->>faca: Register schema faca->>faea: schema_id faea->>faca: Register credential definition faca->>faea: credential_definition_id faea->>faca: create-invitation faca->>faea: connection inc. invitation faea->>aaea: invitation detail aaea->>aaca: receive-invitation deactivate faea deactivate faca deactivate aaca deactivate aaea ``` There are two AEAs: - **Alice_AEA** - **Faber_AEA** and two ACAs: - **Alice_ACA** - **Faber_ACA** Each AEA is connected to its corresponding ACA: **Alice_AEA** to **Alice_ACA** and **Faber_AEA** to **Faber_ACA**. The following lists the sequence of interactions between the four agents: - **Alice_AEA**: starts - **Alice_AEA**: shows its P2P address in the terminal and waits for an `invitation` detail from **Faber_AEA**. - **Alice_AEA**: registers itself on the SOEF. - **Faber_AEA**: starts - **Faber_AEA**: searches the SOEF and finds **Alice_AEA**. - **Faber_AEA**: tests its connection to **Faber_ACA**. - **Faber_ACA**: responds to **Faber_AEA**. - **Faber_AEA**: registers a DID on the ledger. - **Faber_AEA**: request **Faber_ACA** to register a schema on the ledger. - **Faber_ACA**: responds by sending back the `schema_id`. - **Faber_AEA**: request **Faber_ACA** to register a credential definition on the ledger. - **Faber_ACA**: responds by sending back the `credential_definition_id`. - **Faber_AEA**: requests **Faber_ACA** to create an invitation. - **Faber_ACA**: responds by sending back the `connection` detail, which contains an `invitation` field. - **Faber_AEA**: sends the `invitation` detail to **Alice_AEA**. - **Alice_AEA**: receives `invitation` detail from **Faber_AEA**. - **Alice_AEA**: requests **Alice_ACA** to accept the invitation, by passing it the `invitation` detail it received in the last step. All messages from an AEA to an ACA are http requests (using `http_client` connection). All messages from an AEA to another AEA utilise the P2P communication network accessed via the `p2p_libp2p` connection. All messages initiated from an ACA to an AEA are webhooks (using `webhook` connection). This is the extent of the demo at this point. The rest of the interactions require an instance of the Indy ledger to run. This is what will be implemented next. The rest of the interactions are broadly as follows: - **Alice_ACA**: accepts the invitation. - **Alice_ACA**: sends a matching invitation request to **Faber_ACA**. - **Faber_ACA**: accepts At this point, the two ACAs are connected to each other. - **Faber_AEA**: requests **Faber_ACA** to issue a credential (e.g. university degree) to **Alice_AEA**, which **Faber_ACA** does via **Alice_ACA**. - **Faber_AEA**: requests proof that **Alice_AEA**'s age is above 18. - **Alice_AEA**: presents proof that it's age is above 18, without presenting its credential. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. Install Aries cloud-agents (for more info see here) if you do not have it on your machine: ``` bash pip install aries-cloudagent ``` This demo has been successfully tested with `aca-py` version `0.4.5`. This demo requires an instance of von network running in docker locally (for more info see here) This demo has been successfully tested with the von-network git repository pulled on 07 Aug 2020 (commit number `ad1f84f64d4f4c106a81462f5fbff496c5fbf10e`). ### Terminals Open five terminals. The first terminal is used to run an instance of von-network locally in docker. The other four terminals will be used to run each of the four agents in this demo. ## VON Network In the first terminal move to the `von-network` directory and run an instance of `von-network` locally in docker. This tutorial has information on starting (and stopping) the network locally. ``` bash ./manage build ./manage start --logs ``` Once the ledger is running, you can see the ledger by going to the web server running on port 9000. On localhost, that means going to http://localhost:9000. ## Alice and Faber ACAs To learn about the command for starting an ACA and its various options: ``` bash aca-py start --help ``` ### Faber_ACA In the first terminal: ``` bash aca-py start --admin 127.0.0.1 8021 --admin-insecure-mode --inbound-transport http 0.0.0.0 8020 --outbound-transport http --webhook-url http://127.0.0.1:8022/webhooks ``` Make sure the ports above are unused. Take note of the specific IP addresses and ports you used in the above command. We will refer to them by the following names: - **Faber admin IP**: 127.0.0.1 - **Faber admin port**: 8021 - **Faber webhook port**: 8022 The admin IP and port will be used to send administrative commands to this ACA from an AEA. The webhook port is where the ACA will send notifications to. We will expose this from the AEA so it receives this ACA's notifications. ### Alice_ACA In the second terminal: ``` bash aca-py start --admin 127.0.0.1 8031 --admin-insecure-mode --inbound-transport http 0.0.0.0 8030 --outbound-transp http --webhook-url http://127.0.0.1:8032/webhooks ``` Again, make sure the above ports are unused and take note of the specific IP addresses and ports. In this case: - **Alice admin IP**: 127.0.0.1 - **Alice admin port**: 8031 - **Alice webhook port**: 8032 ## Alice and Faber AEAs Now you can create **Alice_AEA** and **Faber_AEA** in terminals 3 and 4 respectively. ### Alice_AEA In the third terminal, fetch **Alice_AEA** and move into its project folder: ``` bash aea fetch fetchai/aries_alice:0.32.5 cd aries_alice ``` ??? note "Alternatively, create from scratch:" The following steps create **Alice_AEA** from scratch: ``` bash aea create aries_alice cd aries_alice aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/webhook:0.20.6 aea add skill fetchai/aries_alice:0.26.6 ``` #### Configure the `aries_alice` Skill (configuration file: `alice/vendor/fetchai/skills/aries_alice/skill.yaml`) Ensure `admin_host` and `admin_port` values match with the values you noted above for **Alice_ACA**. You can use the framework's handy `config` CLI command to set these values: ``` bash aea config set vendor.fetchai.skills.aries_alice.models.strategy.args.admin_host 127.0.0.1 ``` ``` bash aea config set --type int vendor.fetchai.skills.aries_alice.models.strategy.args.admin_port 8031 ``` #### Configure the `webhook` Connection (configuration file: `alice/vendor/fetchai/connections/webhook/connection.yaml`). First ensure the value of `webhook_port` matches with what you used above for **Alice_ACA**. ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8032 ``` Next, make sure the value of `webhook_url_path` is `/webhooks/topic/{topic}/`. ``` bash aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ ``` #### Configure the `p2p_libp2p` Connection ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11000", "entry_peers": [], "local_uri": "127.0.0.1:7000", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:7000" }' ``` ### Install the Dependencies and Run Alice_AEA Now install all the dependencies: ``` bash aea install aea build ``` Finally, run **Alice_AEA**: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) We will refer to this as **Alice_AEA's P2P address**. ### Faber_AEA In the fourth terminal, fetch **Faber_AEA** and move into its project folder: ``` bash aea fetch fetchai/aries_faber:0.32.5 cd aries_faber ``` ??? note "Alternatively, create from scratch:" The following steps create **Faber_AEA** from scratch: ``` bash aea create aries_faber cd aries_faber aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/webhook:0.20.6 aea add skill fetchai/aries_faber:0.24.5 ``` #### Configure the `aries_faber` Skill (configuration file: `faber/vendor/fetchai/skills/aries_alice/skill.yaml`) Ensure `admin_host` and `admin_port` values match with those you noted above for **Faber_ACA**. ``` bash aea config set vendor.fetchai.skills.aries_faber.models.strategy.args.admin_host 127.0.0.1 ``` ``` bash aea config set --type int vendor.fetchai.skills.aries_faber.models.strategy.args.admin_port 8021 ``` #### Configure the `webhook` Connection (configuration file: `faber/vendor/fetchai/connections/webhook/connection.yaml`). First, ensure the value of `webhook_port` matches with what you used above for **Faber_ACA**. ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8022 ``` Next, make sure the value of `webhook_url_path` is `/webhooks/topic/{topic}/`. ``` bash aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ ``` #### Configure the `p2p_libp2p` Connection ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:7001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:7001" }' ``` where `SOME_ADDRESS` is **Alice_AEA's P2P address** as displayed in the third terminal. ### Install the Dependencies and Run Faber_AEA Now install all the dependencies: ``` bash aea install aea build ``` Finally run **Faber_AEA**: ``` bash aea run ``` You should see **Faber_AEA** running and showing logs of its activities. For example: Aries demo: Faber terminal Looking now at **Alice_AEA** terminal, you should also see more activity by **Alice_AEA** after **Faber_AEA** was started. For example: Aries demo: Alice terminal The last error line in **Alice_AEA**'s terminal is caused due to the absence of an Indy ledger instance. In the next update to this demo, this will be resolved. ## Terminate and Delete the Agents You can terminate each agent by pressing Ctrl+C. To delete the AEAs, go to the projects' parent directory and delete the AEAs: ``` bash aea delete aries_faber aea delete aries_alice ``` ## Further Developments In the next update to this demo, the remaining interactions between AEAs and ACAs must be implemented. This means: - An instance of Indy ledger must be installed and running. See here for more detail. - The commands for running the ACAs need to be adjusted. Additional options relating to a wallet (wallet-name, type, key, storage-type, configuration, credentials) need to be fed to the ACAs as well as the ledger's genesis file so the ACAs can connect to the ledger. - The remaining interactions between the AEAs and ACAs as described here need to be implemented. ================================================ FILE: docs/build-aea-programmatically.md ================================================ # Build an AEA Programmatically These instructions detail the Python code you need for running an AEA outside the `cli` tool, using the code interface. ## Preparation Get the `packages` directory from the AEA repository: ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` Also, install `aea-ledger-fetchai` plug-in: ``` bash pip install aea-ledger-fetchai ``` ## Imports First, import the necessary common Python libraries and classes. ``` python import os import time from threading import Thread ``` Then, import the application specific libraries. ``` python from aea_ledger_fetchai import FetchAICrypto from aea.aea_builder import AEABuilder from aea.configurations.base import SkillConfig from aea.crypto.helpers import PRIVATE_KEY_PATH_SCHEMA, create_private_key from aea.helpers.file_io import write_with_lock from aea.skills.base import Skill ``` Set up a variable pointing to where the `packages` directory is located - this should be our current directory - and where the input and output files are located. ``` python ROOT_DIR = "./" INPUT_FILE = "input_file" OUTPUT_FILE = "output_file" FETCHAI_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(FetchAICrypto.identifier) ``` ## Create a Private Key We need a private key to populate the AEA's wallet. ``` python # Create a private key create_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) ``` ## Clearing the Input and Output Files We will use the stub connection to pass envelopes in and out of the AEA. Ensure that any input and output text files are removed before we start. ``` python # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) ``` ## Initialise the AEA We use the `AEABuilder` to readily build an AEA. By default, the `AEABuilder` adds the `fetchai/default:1.1.7`, `fetchai/state_update:1.1.7` and `fetchai/signing:1.1.7` protocols. ``` python # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added builder = AEABuilder() ``` We set the name, add the private key for the AEA to use and set the ledger configurations for the AEA to use. ``` python builder.set_name("my_aea") builder.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) ``` Next, we add the `fetchai/stub:0.15.0` connection which will read/write messages from file: ``` python # Add the stub connection (assuming it is present in the local directory 'packages') builder.add_connection("./packages/fetchai/connections/stub") ``` Next, we add the echo skill which will bounce our messages back to us. We first need to place the echo skill into a relevant directory (see path), either by downloading the `packages` directory from the AEA repo or by getting the package from the registry. ``` python # Add the echo skill (assuming it is present in the local directory 'packages') builder.add_skill("./packages/fetchai/skills/echo") ``` Also, we can add a component that was instantiated programmatically. : ``` python # create skill and handler manually from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage class DummyHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Noop setup.""" def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message.""" self.context.logger.info("You got a message: {}".format(str(message))) config = SkillConfig(name="test_skill", author="fetchai") skill = Skill(configuration=config) dummy_handler = DummyHandler( name="dummy_handler", skill_context=skill.skill_context ) skill.handlers.update({dummy_handler.name: dummy_handler}) builder.add_component_instance(skill) ``` Finally, we can build our AEA: ``` python # Create our AEA my_aea = builder.build() ``` ## Start the AEA We run the AEA from a different thread so that we can still use the main thread to pass it messages. ``` python # Set the AEA running in a different thread try: t = Thread(target=my_aea.start) t.start() # Wait for everything to start up time.sleep(4) ``` ## Send and Receive an Envelope We use the input and output text files to send an envelope to our AEA and receive a response (from the echo skill) ``` python # Create a message inside an envelope and get the stub connection to pass it on to the echo skill message_text = b"my_aea,other_agent,fetchai/default:1.0.0,\x12\x10\x08\x01\x12\x011*\t*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) print(b"input message: " + message_text) # Wait for the envelope to get processed time.sleep(4) # Read the output envelope generated by the echo skill with open(OUTPUT_FILE, "rb") as f: print(b"output message: " + f.readline()) ``` ## Shutdown Finally, stop our AEA and wait for it to finish ``` python finally: # Shut down the AEA my_aea.stop() t.join() t = None ``` ## Running the AEA If you now run this python script file, you should see this output: ``` text input message: my_aea,other_agent,fetchai/default:1.0.0,\x12\x10\x08\x01\x12\x011*\t*\x07\n\x05hello, output message: other_agent,my_aea,fetchai/default:1.0.0,...\x05hello ``` ## Entire Code Listing If you just want to copy and past the entire script in you can find it here: ??? note "Click here to see full listing:" ``` python import os import time from threading import Thread from aea_ledger_fetchai import FetchAICrypto from aea.aea_builder import AEABuilder from aea.configurations.base import SkillConfig from aea.crypto.helpers import PRIVATE_KEY_PATH_SCHEMA, create_private_key from aea.helpers.file_io import write_with_lock from aea.skills.base import Skill ROOT_DIR = "./" INPUT_FILE = "input_file" OUTPUT_FILE = "output_file" FETCHAI_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(FetchAICrypto.identifier) def run(): """Run demo.""" # Create a private key create_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added builder = AEABuilder() builder.set_name("my_aea") builder.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) # Add the stub connection (assuming it is present in the local directory 'packages') builder.add_connection("./packages/fetchai/connections/stub") # Add the echo skill (assuming it is present in the local directory 'packages') builder.add_skill("./packages/fetchai/skills/echo") # create skill and handler manually from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage class DummyHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Noop setup.""" def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message.""" self.context.logger.info("You got a message: {}".format(str(message))) config = SkillConfig(name="test_skill", author="fetchai") skill = Skill(configuration=config) dummy_handler = DummyHandler( name="dummy_handler", skill_context=skill.skill_context ) skill.handlers.update({dummy_handler.name: dummy_handler}) builder.add_component_instance(skill) # Create our AEA my_aea = builder.build() # Set the AEA running in a different thread try: t = Thread(target=my_aea.start) t.start() # Wait for everything to start up time.sleep(4) # Create a message inside an envelope and get the stub connection to pass it on to the echo skill message_text = b"my_aea,other_agent,fetchai/default:1.0.0,\x12\x10\x08\x01\x12\x011*\t*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) print(b"input message: " + message_text) # Wait for the envelope to get processed time.sleep(4) # Read the output envelope generated by the echo skill with open(OUTPUT_FILE, "rb") as f: print(b"output message: " + f.readline()) finally: # Shut down the AEA my_aea.stop() t.join() t = None if __name__ == "__main__": run() ``` ================================================ FILE: docs/build-aea-step-by-step.md ================================================ # Build an AEA with the CLI Building an AEA step by step (ensure you have followed the Preliminaries and Installation sections from the AEA quick start first): 1. Set up your AEA project with the CLI: `aea create my_aea && cd my_aea` 1. Look at, then add the right connections for your use case: `aea search connections`, then `aea add connection [public_id]` 1. Look for, then add or generate the protocols you require: `aea search protocols`, then `aea add protocol [public_id]` or `aea generate protocol [path_to_specification]` 1. Look for, then add or code the skills you need: `aea search skills`, then `aea add skill [public_id]`. This guide shows you step by step how to develop a skill. 1. Where required, scaffold any of the above resources with the scaffolding tool or generate a protocol with the protocol generator. 1. Now, run your AEA: `aea run --connections [public_id]` See information on the CLI tool here for all the available commands. ================================================ FILE: docs/car-park-skills.md ================================================ # Car park skills The AEA car-park skills demonstrate an interaction between two AEAs. - The `carpark_detection` AEA provides information on the number of car parking spaces available in a given vicinity. - The `carpark_client` AEA is interested in purchasing information on available car parking spaces in the same vicinity. ## Discussion The full Fetch.ai car park AEA demo is documented in its own repo here. This demo allows you to test the AEA functionality of the car park AEA demo without the detection logic. It demonstrates how the AEAs trade car park information. ## Communication This diagram shows the communication between the various entities as data is successfully sold by the car park AEA to the client. ``` mermaid sequenceDiagram participant Search participant Car_Data_Buyer_AEA participant Car_Park_AEA participant Blockchain activate Search activate Car_Data_Buyer_AEA activate Car_Park_AEA activate Blockchain Car_Park_AEA->>Search: register_service Car_Data_Buyer_AEA->>Search: search Search-->>Car_Data_Buyer_AEA: list_of_agents Car_Data_Buyer_AEA->>Car_Park_AEA: call_for_proposal Car_Park_AEA->>Car_Data_Buyer_AEA: propose Car_Data_Buyer_AEA->>Car_Park_AEA: accept Car_Park_AEA->>Car_Data_Buyer_AEA: match_accept Car_Data_Buyer_AEA->>Blockchain: transfer_funds Car_Data_Buyer_AEA->>Car_Park_AEA: send_transaction_hash Car_Park_AEA->>Blockchain: check_transaction_status Car_Park_AEA->>Car_Data_Buyer_AEA: send_data deactivate Search deactivate Car_Data_Buyer_AEA deactivate Car_Park_AEA deactivate Blockchain ``` ## Option 1: AEA Manager Approach Follow this approach when using the AEA Manager Desktop app. Otherwise, skip and follow the CLI approach below. ### Preparation Instructions Install the AEA Manager. ### Demo Instructions The following steps assume you have launched the AEA Manager Desktop app. 1. Add a new AEA called `car_detector` with public id `fetchai/car_detector:0.32.5`. 2. Add another new AEA called `car_data_buyer` with public id `fetchai/car_data_buyer:0.33.5`. 3. Copy the address from the `car_data_buyer` into your clip board. Then go to the Dorado block explorer and request some test tokens via `Get Funds`. 4. Run the `car_detector` AEA. Navigate to its logs and copy the multiaddress displayed. 5. Navigate to the settings of the `car_data_buyer` and under `components > connection >` `fetchai/p2p_libp2p:0.22.0` update as follows (make sure to replace the placeholder with the multiaddress): ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` 6. Run the `car_data_buyer`. In the AEA's logs, you should see the agent trading successfully. ## Option 2: CLI Approach Follow this approach when using the `aea` CLI. ### Preparation Instructions #### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ### Demo Instructions #### Create Car Detector AEA First, fetch the car detector AEA: ``` bash aea fetch fetchai/car_detector:0.32.5 cd car_detector aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the car detector from scratch: ``` bash aea create car_detector cd car_detector aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/carpark_detection:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` #### Create Car Data Buyer AEA Then, fetch the car data client AEA: ``` bash aea fetch fetchai/car_data_buyer:0.33.5 cd car_data_buyer aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the car data client from scratch: ``` bash aea create car_data_buyer cd car_data_buyer aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/carpark_client:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` #### Add Keys for the Car Data Seller AEA First, create the private key for the car data seller AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` #### Add Keys and Generate Wealth for the Car Data Buyer AEA The buyer needs to have some wealth to purchase the service from the seller. First, create the private key for the car data buyer AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Then, create some wealth for your car data buyer based on the network you want to transact with. On the Fetch.ai `Dorado` network: ``` bash aea generate-wealth fetchai ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Run the AEAs Run both AEAs from their respective terminals. First, run the car data seller AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the car data seller. Then, in the car data buyer, run this command (replace `SOME_ADDRESS` with the correct value as described above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` This allows the car data buyer to connect to the same local agent communication network as the car data seller. Then run the buyer AEA: ``` bash aea run ``` You will see that the AEAs negotiate and then transact using the Fetch.ai testnet. #### Cleaning up When you're finished, delete your AEAs: ``` bash cd .. aea delete car_detector aea delete car_data_buyer ``` ================================================ FILE: docs/cli-commands.md ================================================ # CLI Commands | Command | Description | |-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| | `add [package_type] [public_id]` | Add a `package_type` connection, contract, protocol, or skill, with `[public_id]`, to the AEA. `add --local` to add from local `packages` directory. | | `add-key [ledger_id] file [--connection]` | Add a private key from a file for `ledger_id`. | | `build` | Build the agent and its components. | | `config get [path]` | Reads the configuration specified in `path` and prints its target. | | `config set [path] [--type TYPE]` | Sets a new value for the target of the `path`. Optionally cast to type. | | `create [name]` | Create a new AEA project called `name`. | | `delete [name]` | Delete an AEA project. See below for disabling a resource. | | `eject [package_type] [public_id]` | Move a package of `package_type` and `package_id` from vendor to project working directory. | | `fetch [public_id]` | Fetch an AEA project with `public_id`. `fetch --local` to fetch from local `packages` directory. | | `fingerprint [package_type] [public_id]` | Fingerprint connection, contract, protocol, or skill, with `public_id`. | | `freeze` | Get all the dependencies needed for the AEA project and its components. | | `generate protocol [protocol_spec_path]` | Generate a protocol from the specification. | | `generate-key [ledger_id]` | Generate private keys. The AEA uses a private key to derive the associated public key and address. | | `generate-wealth [ledger_id]` | Generate wealth for address on test network. | | `get-address [ledger_id]` | Get the address associated with the private key. | | `get-multiaddress [ledger_id]...` | Get the multiaddress associated with a private key or connection. | | `get-public-key [ledger_id]...` | Get the public key associated with a private key of the agent. | | `get-wealth [ledger_id]` | Get the wealth associated with the private key. | | `init` | Initialize your AEA configurations. (With `--author` to define author.) | | `install [-r ]` | Install the dependencies. (With `--install-deps` to install dependencies.) | | `interact` | Interact with a running AEA via the stub connection. | | `ipfs` | IPFS Commands | | `issue-certificates` | Issue the connection certificates. | | `launch [path_to_agent_project]...` | Launch many agents at the same time. | | `list [package_type]` | List the installed resources. | | `local-registry-sync` | Upgrade the local package registry. | | `login USERNAME [--password password]` | Login to a registry account with credentials. | | `logout` | Logout from registry account. | | `publish` | Publish the AEA to registry. Needs to be executed from an AEA project.`publish --local` to publish to local `packages` directory. | | `push [package_type] [public_id]` | Push connection, protocol, or skill with `public_id` to registry. `push --local` to push to local `packages` directory. | | `register` | Create a new registry account. | | `remove [package_type] [name]` | Remove connection, protocol, or skill, called `name`, from AEA. | | `remove-key [ledger_id] [name]` | Remove a private key registered with id `ledger_id`. | | `reset_password EMAIL` | Reset the password of the registry account. | | `run {using [connections, ...]}` | Run the AEA on the Fetch.ai network with default or specified connections. | | `scaffold [package_type] [name]` | Scaffold a new connection, protocol, or skill called `name`. | | `search [package_type]` | Search for components in the registry. `search --local [package_type] [--query searching_query]` to search in local `packages` directory. | | `transfer [type] [address] [amount]` | Transfer wealth associated with a private key of the agent to another account. | | `upgrade [package_type] [public_id]` | Upgrade the packages of the agent. | | `-v DEBUG run` | Run with debugging. | !!! tip You can also disable a resource without deleting it by removing the entry from the configuration but leaving the package in the `skills` namespace. !!! tip You can skip the consistency checks on the AEA project by using the flag `--skip-consistency-check`. E.g. `aea --skip-consistency-check run` will bypass the fingerprint checks. ================================================ FILE: docs/cli-vs-programmatic-aeas.md ================================================ # CLI vs Programmatic AEAs The AEA framework enables us to create agents either from the CLI tool or programmatically. The following demo demonstrates an interaction between two AEAs. The provider of weather data (managed with the CLI). The buyer of weather data (managed programmatically). ## Discussion The scope of the specific demo is to demonstrate how a CLI based AEA can interact with a programmatically managed AEA. In order to achieve this we are going to use the weather station skills. This demo does not utilize a smart contract or a ledger interaction. ## Get Required Packages Copy the `packages` directory into your local working directory: ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` Also, install `aea-ledger-fetchai` plug-in: ``` bash pip install aea-ledger-fetchai ``` ## Demo Instructions If you want to create the weather station AEA step by step you can follow this guide here ### Create the Weather Station AEA Fetch the weather station AEA with the following command : ``` bash aea fetch fetchai/weather_station:0.32.5 cd weather_station aea install aea build ``` ### Update the AEA Configurations In the terminal change the configuration: ``` bash aea config set vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx False --type bool ``` The `is_ledger_tx` will prevent the AEA to communicate with a ledger. ### Add Keys Add a private key for the weather station. ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Run the Weather Station AEA ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr: ['SOME_ADDRESS']` take note of the address. ### Create the Weather Client AEA Since we want to show the interaction between a programmatically created AEA with a CLI based AEA we are going to write some code for the client. Create a new python file and name it `weather_client.py` and add the following code ??? note "Weather client full code:" ``` python import logging import os import sys from typing import cast from aea_ledger_fetchai import FetchAICrypto from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.configurations.base import ConnectionConfig from aea.crypto.helpers import ( PRIVATE_KEY_PATH_SCHEMA, create_private_key, make_certificate, ) from aea.crypto.wallet import Wallet from aea.helpers.base import CertRequest from aea.identity.base import Identity from aea.protocols.base import Protocol from aea.registries.resources import Resources from aea.skills.base import Skill import packages.fetchai.connections.p2p_libp2p.connection from packages.fetchai.connections.ledger.connection import LedgerConnection from packages.fetchai.connections.p2p_libp2p.connection import P2PLibp2pConnection from packages.fetchai.connections.soef.connection import SOEFConnection from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.weather_client.strategy import Strategy API_KEY = "TwiCIriSl0mLahw17pyqoA" SOEF_ADDR = "s-oef.fetch.ai" SOEF_PORT = 443 ENTRY_PEER_ADDRESS = ( "/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmLBCAqHL8SuFosyDhAKYsLKXBZBWXBsB9oFw2qU4Kckun" ) FETCHAI_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(FetchAICrypto.identifier) FETCHAI_PRIVATE_KEY_FILE_CONNECTION = PRIVATE_KEY_PATH_SCHEMA.format( "fetchai_connection" ) ROOT_DIR = os.getcwd() logger = logging.getLogger("aea") logging.basicConfig(stream=sys.stdout, level=logging.INFO) def run(): """Run demo.""" # Create a private key create_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) create_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION) # Set up the wallet, identity and (empty) resources wallet = Wallet( private_key_paths={FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE}, connection_private_key_paths={ FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_CONNECTION }, ) identity = Identity( "my_aea", address=wallet.addresses.get(FetchAICrypto.identifier), public_key=wallet.public_keys.get(FetchAICrypto.identifier), ) resources = Resources() data_dir = os.getcwd() # specify the default routing for some protocols default_routing = { LedgerApiMessage.protocol_id: LedgerConnection.connection_id, OefSearchMessage.protocol_id: SOEFConnection.connection_id, } default_connection = P2PLibp2pConnection.connection_id state_update_protocol = Protocol.from_dir( os.path.join(os.getcwd(), "packages", "fetchai", "protocols", "state_update") ) resources.add_protocol(state_update_protocol) # Add the default protocol (which is part of the AEA distribution) default_protocol = Protocol.from_dir( os.path.join(os.getcwd(), "packages", "fetchai", "protocols", "default") ) resources.add_protocol(default_protocol) # Add the signing protocol (which is part of the AEA distribution) signing_protocol = Protocol.from_dir( os.path.join(os.getcwd(), "packages", "fetchai", "protocols", "signing") ) resources.add_protocol(signing_protocol) # Add the ledger_api protocol ledger_api_protocol = Protocol.from_dir( os.path.join( os.getcwd(), "packages", "fetchai", "protocols", "ledger_api", ) ) resources.add_protocol(ledger_api_protocol) # Add the oef_search protocol oef_protocol = Protocol.from_dir( os.path.join( os.getcwd(), "packages", "fetchai", "protocols", "oef_search", ) ) resources.add_protocol(oef_protocol) # Add the fipa protocol fipa_protocol = Protocol.from_dir( os.path.join( os.getcwd(), "packages", "fetchai", "protocols", "fipa", ) ) resources.add_protocol(fipa_protocol) # Add the LedgerAPI connection configuration = ConnectionConfig(connection_id=LedgerConnection.connection_id) ledger_api_connection = LedgerConnection( configuration=configuration, data_dir=data_dir, identity=identity ) resources.add_connection(ledger_api_connection) # Add the P2P connection cert_path = ".certs/conn_cert.txt" cert_request = CertRequest( identifier="acn", ledger_id=FetchAICrypto.identifier, not_after="2022-01-01", not_before="2021-01-01", public_key="fetchai", message_format="{public_key}", save_path=cert_path, ) public_key = wallet.connection_cryptos.public_keys.get(FetchAICrypto.identifier) message = cert_request.get_message(public_key) make_certificate( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE, message, cert_path ) configuration = ConnectionConfig( connection_id=P2PLibp2pConnection.connection_id, delegate_uri="127.0.0.1:11001", entry_peers=[ENTRY_PEER_ADDRESS], local_uri="127.0.0.1:9001", log_file="libp2p_node.log", public_uri="127.0.0.1:9001", build_directory=os.getcwd(), build_entrypoint="check_dependencies.py", cert_requests=[cert_request], ) configuration.directory = os.path.dirname( packages.fetchai.connections.p2p_libp2p.connection.__file__ ) AEABuilder.run_build_for_component_configuration(configuration) p2p_connection = P2PLibp2pConnection( configuration=configuration, data_dir=data_dir, identity=identity, crypto_store=wallet.connection_cryptos, ) resources.add_connection(p2p_connection) # Add the SOEF connection configuration = ConnectionConfig( api_key=API_KEY, soef_addr=SOEF_ADDR, soef_port=SOEF_PORT, restricted_to_protocols={OefSearchMessage.protocol_id}, connection_id=SOEFConnection.connection_id, ) soef_connection = SOEFConnection( configuration=configuration, data_dir=data_dir, identity=identity ) resources.add_connection(soef_connection) # create the AEA my_aea = AEA( identity, wallet, resources, data_dir, default_connection=default_connection, default_routing=default_routing, ) # Add the error and weather_client skills error_skill = Skill.from_dir( os.path.join(ROOT_DIR, "packages", "fetchai", "skills", "error"), agent_context=my_aea.context, ) weather_skill = Skill.from_dir( os.path.join(ROOT_DIR, "packages", "fetchai", "skills", "weather_client"), agent_context=my_aea.context, ) strategy = cast(Strategy, weather_skill.models.get("strategy")) strategy._is_ledger_tx = False for skill in [error_skill, weather_skill]: resources.add_skill(skill) # Run the AEA try: logger.info("STARTING AEA NOW!") my_aea.start() except KeyboardInterrupt: logger.info("STOPPING AEA NOW!") my_aea.stop() if __name__ == "__main__": run() ``` Now replace `ENTRY_PEER_ADDRESS` with the peer address (`SOME_ADDRESS`) noted above. For more details on how to create an agent programmatically follow this guide here. ### Run the Weather Client AEA In a new terminal window, navigate to the folder that you created the script and run: ``` bash python weather_client.py ``` You should see both AEAs interacting now. ================================================ FILE: docs/config.md ================================================ # Configurations This document describes the configuration files of the different packages. ## AEA Configuration YAML The following provides a list of the relevant regex used: ``` yaml PACKAGE_REGEX: "[a-zA-Z_][a-zA-Z0-9_]*" AUTHOR_REGEX: "[a-zA-Z_][a-zA-Z0-9_]*" PUBLIC_ID_REGEX: "^[a-zA-Z0-9_]*/[a-zA-Z_][a-zA-Z0-9_]*:(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" LEDGER_ID_REGEX: "^[^\\d\\W]\\w*\\Z" ``` The `aea-config.yaml` defines the AEA project. The compulsory components are listed below: ``` yaml agent_name: my_agent # Name of the AEA project (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the project's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the AEA project (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") description: A demo project # Description of the AEA project license: Apache-2.0 # License of the AEA project aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: {} # Fingerprint of AEA project components. fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: # The list of connection public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX) - fetchai/stub:0.21.3 contracts: [] # The list of contract public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: # The list of protocol public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). - fetchai/default:1.1.7 skills: # The list of skill public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). - fetchai/error:0.18.6 default_connection: fetchai/p2p_libp2p:0.27.5 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). default_ledger: fetchai # The default ledger identifier the AEA project uses (must satisfy LEDGER_ID_REGEX) required_ledgers: [fetchai] # the list of identifiers of ledgers that the AEA project requires key pairs for (each item must satisfy LEDGER_ID_REGEX) default_routing: {} # The default routing scheme applied to envelopes sent by the AEA, it maps from protocol public ids to connection public ids (both keys and values must satisfy PUBLIC_ID_REGEX) connection_private_key_paths: # The private key paths the AEA project uses for its connections (keys must satisfy LEDGER_ID_REGEX, values must be file paths) fetchai: fetchai_private_key.txt private_key_paths: # The private key paths the AEA project uses (keys must satisfy LEDGER_ID_REGEX, values must be file paths) fetchai: fetchai_private_key.txt logging_config: # The logging configurations the AEA project uses disable_existing_loggers: false version: 1 dependencies: {} # The python dependencies the AEA relies on (e.g. plugins). They will be installed when `aea install` is run. ``` The `aea-config.yaml` can be extended with a number of optional fields: ``` yaml period: 0.05 # The period to call agent's act execution_timeout: 0 # The execution time limit on each call to `react` and `act` (0 disables the feature) timeout: 0.05 # The sleep time on each AEA loop spin (only relevant for the `sync` mode) max_reactions: 20 # The maximum number of envelopes processed per call to `react` (only relevant for the `sync` mode) skill_exception_policy: propagate # The exception policy applied to skills (must be one of "propagate", "just_log", or "stop_and_exit") connection_exception_policy: propagate # The exception policy applied to connections (must be one of "propagate", "just_log", or "stop_and_exit") loop_mode: async # The agent loop mode (must be one of "sync" or "async") runtime_mode: threaded # The runtime mode (must be one of "threaded" or "async") and determines how agent loop and multiplexer are run error_handler: None # The error handler to be used. decision_maker_handler: None # The decision maker handler to be used. storage_uri: None # The URI to the storage. data_dir: None # The path to the directory for local files. Defaults to current working directory. ``` The `aea-config.yaml` can further be extended with component configuration overrides. For custom connection configurations: ``` yaml public_id: some_author/some_package:0.1.0 # The public id of the connection (must satisfy PUBLIC_ID_REGEX). type: connection # for connections, this must be "connection". config: ... # a dictionary to overwrite the `config` field (see below) ``` For custom skill configurations: ``` yaml public_id: some_author/some_package:0.1.0 # The public id of the connection (must satisfy PUBLIC_ID_REGEX). type: skill # for skills, this must be "skill". behaviours: # override configurations for behaviours behaviour_1: # override configurations for "behaviour_1" args: # arguments for a specific behaviour (see below) foo: bar handlers: # override configurations for handlers handler_1: # override configurations for "handler_1" args: # arguments for a specific handler (see below) foo: bar models: # override configurations for models model_1: # override configurations for "model_1" args: # arguments for a specific model (see below) foo: bar ``` ## Connection Configuration YAML The `connection.yaml`, which is present in each connection package, has the following required fields: ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: connection # The type of the package; for connections, it must be "connection" description: A scaffold connection # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: QmZvYZ5ECcWwqiNGh8qNTg735wu51HqaLxTSifUxkQ4KGj connection.py: QmagwVgaPgfeXqVTgcpFESA4DYsteSbojz94SLtmnHNAze fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: [] # The list of connection public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: [] # The list of protocol public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). class_name: MyScaffoldConnection # The class name of the class implementing the connection interface. config: # A dictionary containing the kwargs for the connection instantiation. foo: bar excluded_protocols: [] # The list of protocol public ids the package does not permit (each public id must satisfy PUBLIC_ID_REGEX). restricted_to_protocols: [] # The list of protocol public ids the package is limited to (each public id must satisfy PUBLIC_ID_REGEX). dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. is_abstract: false # An optional boolean that if `true` makes the connection ``` ## Contract Configuration YAML The `contract.yaml`, which is present in each contract package, has the following required fields: ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: contract # The type of the package; for contracts, it must be "contract" description: A scaffold contract # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: QmPBwWhEg3wcH1q9612srZYAYdANVdWLDFWKs7TviZmVj6 contract.py: QmXvjkD7ZVEJDJspEz5YApe5bRUxvZHNi8vfyeVHPyQD5G fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. class_name: MyScaffoldContract # The class name of the class implementing the contract interface. contract_interface_paths: {} # The paths to the contract interfaces (one for each ledger identifier). config: # A dictionary containing the kwargs for the contract instantiation. foo: bar dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. ``` ## Protocol Configuration YAML The `protocol.yaml`, which is present in each protocol package, has the following required fields: ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: protocol # The type of the package; for protocols, it must be "protocol" description: A scaffold protocol # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: Qmay9PmfeHqqVa3rdgiJYJnzZzTStboQEfpwXDpcgJMHTJ message.py: QmdvAdYSHNdZyUMrK3ue7quHAuSNwgZZSHqxYXyvh8Nie4 serialization.py: QmVUzwaSMErJgNFYQZkzsDhuuT2Ht4EdbGJ443usHmPxVv fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. ``` ## Skill Configuration YAML The `skill.yaml`, which is present in each protocol package, has the following required fields: ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: skill # The type of the package; for skills, it must be "skill" description: A scaffold skill # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: QmNkZAetyctaZCUf6ACxP5onGWsSxu2hjSNoFmJ3ta6Lta behaviours.py: QmYa1rczhGTtMJBgCd1QR9uZhhkf45orm7TnGTE5Eizjpy handlers.py: QmZYyTENRr6ecnxx1FeBdgjLiBhFLVn9mqarzUtFQmNUFn my_model.py: QmPaZ6G37Juk63mJj88nParaEp71XyURts8AmmX1axs24V fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. contracts: [] # The list of contract public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: [] # The list of protocol public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). skills: [] # The list of skill public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). is_abstract: false # An optional boolean that if `true` makes the skill abstract, i.e. not instantiated by the framework but importable from other skills. Defaults to `false`. behaviours: # The dictionary describing the behaviours immplemented in the package (including their configuration) scaffold: # Name of the behaviour under which it is made available on the skill context. args: # Keyword arguments provided to the skill component on instantiation. foo: bar class_name: MyScaffoldBehaviour # The class name of the class implementing the behaviour interface. handlers: # The dictionary describing the handlers immplemented in the package (including their configuration) scaffold: # Name of the handler under which it is made available on the skill args: # Keyword arguments provided to the skill component on instantiation. foo: bar class_name: MyScaffoldHandler # The class name of the class implementing the handler interface. models: # The dictionary describing the models immplemented in the package (including their configuration) scaffold: # Name of the model under which it is made available on the skill args: # Keyword arguments provided to the skill component on instantiation. foo: bar class_name: MyModel # The class name of the class implementing the model interface. dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. ``` ================================================ FILE: docs/connect-a-frontend.md ================================================ # Front-End Integration This page lays out two options for connecting a front-end to an AEA. The following diagram illustrates these two options. How to connect front-end to your AEA ## Case 1 The first option is to create a `HTTP Server` connection that handles incoming requests from a REST API. In this scenario, the REST API communicates with the AEA and requests are handled by the `HTTP Server` connection package. The REST API should send CRUD requests to the `HTTP Server` connection (`fetchai/http_server:0.23.6`) which translates these into Envelopes to be consumed by the correct skill. ## Case 2 The second option is to create a front-end comprising a stand-alone `Multiplexer` with a `P2P` connection (`fetchai/p2p_libp2p:0.27.5`). In this scenario the Agent Communication Network can be used to send Envelopes from the AEA to the front-end. ================================================ FILE: docs/connection.md ================================================ # Connections A `Connection` provides an interface for the agent to connect with entities in the outside world. Connections wrap SDKs or APIs and provide interfaces to networks, ledgers and other services. As such, a connection is concerned with I/O bound and continuously connected operations. Where necessary, a connection is responsible for translating between the framework specific protocol (an `Envelope` with its contained `Message`) and the external service or third-party protocol (e.g. `HTTP`). Hence, there are two roles for connections: wrapper and transport connection. The transport connection is responsible to delivering AEA envelopes. The messages constructed or received by a connection are eventually processed by one or several skills which deal with handling and generating messages related to a specific business objective. Multiplexer of an AEA An `AEA` can interact with multiple connections at the same time via the `Multiplexer`. Connections are passive in terms of multiplexer interactions (its methods are called by the Multiplexer), but they can run their own asynchronous or threaded tasks. The `Multiplexer` maintains an `InBox` and `OutBox`, which are, respectively, queues for incoming and outgoing envelopes and their contained messages. ## Developing your Connection The easiest way to get started developing your own connection is by using the scaffold command: ``` bash aea scaffold connection my_new_connection ``` This will scaffold a connection package called `my_new_connection` with three files: - `__init__.py` - `connection.py` containing the scaffolded connection class - `connection.yaml` containing the scaffolded configuration file As a developer you have the choice between implementing a sync or asynchronous interface. The scaffolded `connection.py` file contains two classes: the `MyScaffoldAsyncConnection` inherited from the `Connection` base class and the `MyScaffoldSyncConnection` inherited from the `BaseSyncConnection`. Remove the unused class. ### Primary Methods to Develop - Asynchronous Connection Interface The developer needs to implement four public coroutines: - The `connect` coroutine implements the setup logic required to be performed for the connection when it is initially launched. The `connect` coroutine is called by the AEA framework once when the agent is being started. - The `disconnect` coroutine implements the teardown logic required to be performed for the connection when it is eventually stopped. The `disconnect` coroutine is called by the AEA framework once when the agent is being stopped. - The `send` coroutine is called by the AEA framework each time the `Multiplexer` handles an outgoing envelope specified to be handled by this connection. The `send` coroutine must implement the processing of the envelope leaving the agent. - The `receive` coroutine is continuously called by the AEA framework. It either returns `None` or an envelope. The `receive` coroutine must implement the logic of data being received by the agent, and if necessary, its translation into a relevant protocol. The framework provides a demo `stub` connection which implements an I/O reader and writer to send and receive messages between the agent and a local file. To gain inspiration and become familiar with the structure of connection packages, you may find it useful to check out `fetchai/stub:0.21.3`, `fetchai/http_server:0.23.6` or `fetchai/http_client:0.24.6` connections. The latter two connections are for external clients to connect with an agent, and for the agent to connect with external servers, respectively. ### Primary Methods to Develop - Sync Connection Interface The `BaseSyncConnection` uses executors to execute synchronous code from the asynchronous context of the `Multiplexer` in executors/threads, which are limited by the amount of configured workers. The asynchronous methods `connect`, `disconnect` and `send` are converted to callbacks which the developer implements: - `on_connect` - `on_disconnect` - `on_send` All of these methods will be executed in the executor pool. Every method can create a message by putting it into the thread/asynchronous friendly queue that is consumed by the `Multiplexer`. The `receive` coroutine has no direct equivalent. Instead, the developer implements a `main` method which runs synchronously in the background. ## Configuration Every connection must have a configuration file in `connection.yaml`, containing meta-information about the connection as well as all the required configuration details. For more details, have a look here. ### Configuration Options The `connection.yaml` file contains a number of fields that must be edited by the developer of the connection: ``` yaml connections: [] protocols: [] class_name: MyScaffoldConnection config: foo: bar excluded_protocols: [] restricted_to_protocols: [] dependencies: {} is_abstract: false cert_requests: [] ``` - `connections` specifies the list of other connection this connection depends on - `protocols` specifies the list of protocols this connection depends on - `class_name` needs to match the name of the connection class in `connection.py` - `config` can contain arbitrary configuration information which is made available in the constructor of the connection as keyword arguments (`**kwargs`) - `excluded_protocols` lists the protocols which cannot be used in this connection - `restricted_to_protocols` lists the protocols which this connection is restricted to be used by - `dependencies` lists any Python dependencies of the connection package - `is_abstract` specifies whether this connection is only used as an abstract base class - `cert_requests` lists certification requests of the connection (see proof of representation for details) ================================================ FILE: docs/contract.md ================================================ # Contracts `Contracts` wrap smart contracts for Fetch.ai and third-party decentralized ledgers. In particular, they provide wrappers around the API or ABI of a smart contract and its byte code. They implement a translation between framework messages (in the `fetchai/contract_api:1.0.0` protocol) and the implementation specifics of the ABI. Contracts usually implement four types of methods: - a method to create a smart contract deployment transaction, - methods to create transactions to modify state in the deployed smart contract, - methods to create contract calls to execute static methods on the deployed smart contract, and - methods to query the state of the deployed smart contract. Contracts can be added as packages which means they become reusable across AEA projects. The smart contract wrapped in an AEA contract package might be a third-party smart contract or your own smart contract potentially interacting with a third-party contract on-chain. ## Interacting with Contracts from Skills Interacting with contracts in almost all cases requires network access. Therefore, the framework executes contract related logic in a Connection. Message flow for contract and ledger interactions In particular, the `fetchai/ledger:0.21.5` connection can be used to execute contract related logic. The skills communicate with the `fetchai/ledger:0.21.5` connection via the `fetchai/contract_api:1.0.0` protocol. This protocol implements a request-response pattern to serve the four types of methods listed above: - the `get_deploy_transaction` message is used to request a `deploy` transaction for a specific contract. For instance, to request a `deploy` transaction for the deployment of the smart contract wrapped in the `fetchai/erc1155:0.23.3` package, we send the following message to the `fetchai/ledger:0.21.5`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, contract_id="fetchai/erc1155:0.23.3", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( {"deployer_address": self.context.agent_address} ), ) ``` Any additional arguments needed by the contract's constructor method should be added to `kwargs`. This message will be handled by the `fetchai/ledger:0.21.5` connection and then a `raw_transaction` message will be returned with the matching raw transaction. To send this transaction to the ledger for processing, we first sign the message with the decision maker and then send the signed transaction to the `fetchai/ledger:0.21.5` connection using the `fetchai/ledger_api:1.0.0` protocol. For details on how to implement the message handling, see the handlers in the `erc1155_deploy` skill. !!! note "CosmWasm based smart contract deployments" When using CosmWasm based smart contracts two types of deployment transactions exist. The first transaction stores the code on the chain. The second transaction initialises the code. This way, the same contract code can be initialised many times. Both the store and init messages use the ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION performative. The ledger API automatically detects the type of transactions based on the provided keyword arguments. In particular, an init transaction requires the keyword arguments code_id (integer), label (string), amount (integer) and init_msg (JSON). For an example look at the fetchai/erc1155:0.23.3 package. - the `get_raw_transaction` message is used to request any transaction for a specific contract which changes state in the contract. For instance, to request a transaction for the creation of token in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.23.3` package, we send the following message to the `fetchai/ledger:0.21.5`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, contract_id="fetchai/erc1155:0.23.3", contract_address=strategy.contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.context.agent_address, "token_ids": strategy.token_ids, } ), ) ``` This message will be handled by the `fetchai/ledger:0.21.5` connection and then a `raw_transaction` message will be returned with the matching raw transaction. For this to be executed correctly, the `fetchai/erc1155:0.23.3` contract package needs to implement the `get_create_batch_transaction` method with the specified key word arguments (see example in *Deploy your own*, below). Similar to the above, to send this transaction to the ledger for processing, we first sign the message with the decision maker and then send the signed transaction to the `fetchai/ledger:0.21.5` connection using the `fetchai/ledger_api:1.0.0` protocol. - the `get_raw_message` message is used to request any contract method call for a specific contract which does not change state in the contract. For instance, to request a call to get a hash from some input data in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.23.3` package, we send the following message to the `fetchai/ledger:0.21.5`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, contract_id="fetchai/erc1155:0.23.3", contract_address=strategy.contract_address, callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( { "from_address": from_address, "to_address": to_address, "token_id": token_id, "from_supply": from_supply, "to_supply": to_supply, "value": value, "trade_nonce": trade_nonce, } ), ) ``` This message will be handled by the `fetchai/ledger:0.21.5` connection and then a `raw_message` message will be returned with the matching raw message. For this to be executed correctly, the `fetchai/erc1155:0.23.3` contract package needs to implement the `get_hash_single` method with the specified key word arguments. We can then send the raw message to the `fetchai/ledger:0.21.5` connection using the `fetchai/ledger_api:1.0.0` protocol. In this case, signing is not required. - the `get_state` message is used to request any contract method call to query state in the deployed contract. For instance, to request a call to get the balances in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.23.3` package, we send the following message to the `fetchai/ledger:0.21.5`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_STATE, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, contract_id="fetchai/erc1155:0.23.3", contract_address=strategy.contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( {"agent_address": address, "token_id": token_id} ), ) ``` This message will be handled by the `fetchai/ledger:0.21.5` connection and then a `state` message will be returned with the matching state. For this to be executed correctly, the `fetchai/erc1155:0.23.3` contract package needs to implement the `get_balance` method with the specified key word arguments. We can then send the raw message to the `fetchai/ledger:0.21.5` connection using the `fetchai/ledger_api:1.0.0` protocol. In this case, signing is not required. ## Developing your own The easiest way to get started developing your own contract is by using the scaffold command: ``` bash aea scaffold contract my_new_contract ``` This will scaffold a contract package called `my_new_contract` with three files: - `__init__.py` - `contract.py`, containing the scaffolded contract class - `contract.yaml` containing the scaffolded configuration file Once your scaffold is in place, you can create a `build` folder in the package and copy the smart contract interface (e.g. bytes code and ABI) to it. Then, specify the path to the interfaces in the `contract.yaml`. For instance, if you use Ethereum, then you might specify the following: ``` yaml contract_interface_paths: ethereum: build/my_contract.json ``` where `ethereum` is the ledger id and `my_contract.json` is the file containing the byte code and ABI. Finally, you will want to implement the part of the contract interface you need in `contract.py`: ``` python from aea.contracts.base import Contract from aea.crypto.base import LedgerApi class MyContract(Contract): """The MyContract contract class which acts as a bridge between AEA framework and ERC1155 ABI.""" @classmethod def get_create_batch_transaction( cls, ledger_api: LedgerApi, contract_address: str, deployer_address: str, token_ids: List[int], data: Optional[bytes] = b"", gas: int = 300000, ) -> Dict[str, Any]: """ Get the transaction to create a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param token_ids: the list of token ids for creation :param data: the data to include in the transaction :param gas: the gas to be used :return: the transaction object """ # create the transaction dict nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.createBatch( deployer_address, token_ids ).buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = cls._try_estimate_gas(ledger_api, tx) return tx ``` Above, we implement a method to create a transaction, in this case a transaction to create a batch of tokens. The method will be called by the framework, specifically the `fetchai/ledger:0.21.5` connection once it receives a message (see bullet point 2 above). The method first gets the latest transaction nonce of the `deployer_address`, then constructs the contract instance, then uses the instance to build the transaction and finally updates the gas on the transaction. It helps to look at existing contract packages, like `fetchai/erc1155:0.23.3`, and skills using them, like `fetchai/erc1155_client:0.11.0` and `fetchai/erc1155_deploy:0.31.6`, for inspiration and guidance. ================================================ FILE: docs/core-components-1.md ================================================ # Core Components - Part 1 The AEA framework consists of several core components, some required to run an AEA and others optional. The following sections discuss the inner workings of the AEA framework and how it calls the code in custom packages (see inversion of control and a helpful comparison here). Whilst it is in principle possible to use parts of the framework as a library, we do not recommend it. ## The Elements Each AEA Uses ### Envelope Envelope of an AEA `AEA` objects communicate asynchronously via `Envelopes`. An `Envelope` is the core object with which agents communicate. It is a vehicle for `Messages` with five attributes: - `to`: defines the destination address. - `sender`: defines the sender address. - `protocol_id`: defines the id of the `Protocol`. - `message`: is a bytes field which holds the `Message` in serialized form. - `Optional[context]`: an optional field to specify routing information in a URI. `Messages` must adhere to a `Protocol`. ### Protocol `Protocols` define agent-to-agent as well as component-to-component interactions within AEAs. As such, they include: - `Messages` defining the syntax of messages; - `Serialization` defining how a `Message` is encoded for transport; and, optionally - `Dialogues`, which define rules over `Message` sequences. The framework provides one default `Protocol`, called `default` (current version `fetchai/default:1.1.7`). This `Protocol` provides a bare-bones implementation for an AEA `Protocol` which includes a `DefaultMessage` class and associated `DefaultSerializer` and `DefaultDialogue` classes. Additional `Protocols`, for new types of interactions, can be added as packages. For more details on `Protocols` you can read the protocol guide. To learn how you can easily automate protocol definition, head to the guide for the protocol generator. Protocol specific `Messages`, wrapped in `Envelopes`, are sent and received to other agents, agent components and services via `Connections`. ### Connection A `Connection` wraps an SDK or API and provides an interface to networks, ledgers or other services. Where necessary, a `Connection` is responsible for translating between the framework specific `Envelope` with its contained `Message` and the external service or third-party protocol (e.g. `HTTP`). The framework provides one default `Connection`, called `stub` (current version `fetchai/stub:0.21.3`). It implements an I/O reader and writer to send `Messages` to the agent from a local file. Additional `Connections` can be added as packages. For more details on `Connections` read the `Connection` guide . An AEA runs and manages `Connections` via a `Multiplexer`. ### Multiplexer Multiplexer of an AEA The `Multiplexer` is responsible for maintaining (potentially multiple) `Connections`. It maintains an `InBox` and `OutBox`, which are, respectively, queues for incoming and outgoing `Envelopes` from the perspective of `Skills`. ### Skill Skills of an AEA `Skills` are the core focus of the framework's extensibility as they implement business logic to deliver economic value for the AEA. They are self-contained capabilities that AEAs can dynamically take on board, in order to expand their effectiveness in different situations. A `Skill` encapsulates implementations of the three abstract base classes `Handler`, `Behaviour`, `Model`, and is closely related with the abstract base class `Task`: - `Handler`: each `Skill` has zero, one or more `Handler` objects. There is a one-to-one correspondence between `Handlers` and the protocols in an AEA (also known as the _registered protocols_). Handlers implement AEAs' **reactive** behaviour. If an AEA understands a `Protocol` referenced in a received `Envelope` (i.e. the protocol is registered in this AEA), this envelope is sent to the corresponding `Handler` which executes the AEA's reaction to this `Message`. - `Behaviour`: a `skill` can have zero, one or more `Behaviours`, each encapsulating actions which further the AEAs goal and are initiated by internals of the AEA rather than external events. Behaviours implement AEAs' **pro-activeness**. The framework provides a number of abstract base classes implementing different types of simple and composite behaviours (e.g. cyclic, one-shot, finite-state-machine, etc), and these define how often and in what order a behaviour and its sub-behaviours must be executed. - `Model`: zero, one or more `Models` that inherit from the `Model` abstract base class and are accessible via the `SkillContext`. - `Task`: zero, one or more `Tasks` encapsulate background work internal to the AEA. `Task` differs from the other three in that it is not a part of `Skills`, but `Tasks` are declared in or from `Skills` if a packaging approach for AEA creation is used. A `Skill` can read (parts of) an AEA's state (as summarised in the `AgentContext`), and propose actions to the AEA according to its specific logic. As such, more than one `Skill` could exist per `Protocol`, competing with each other in suggesting to the AEA the best course of actions to take. In technical terms, this means `Skills` are horizontally arranged. For instance, an AEA which is trading goods, could subscribe to more than one `Skill`, where each corresponds to a different trading strategy. The framework places no limits on the complexity of `Skills`. They can implement simple (e.g. `if-this-then-that`) logic or be complex (e.g. a deep learning model or reinforcement learning agent). The framework provides one default `Skill`, called `error`. Additional `Skills` can be added as packages. For more details on `Skills` head over to the `Skill` guide . ### Agent Loop The `AgentLoop` performs a series of activities while the `AEA` state is not `stopped`. - it calls the `act()` function of all active registered `Behaviours` at their respective tick rate. - it grabs all Envelopes waiting in the `InBox` queue and calls the `handle()` function for the `Handlers` currently registered against the `Protocol` of the `Envelope`. - it dispatches the internal `Messages` from the decision maker (described below) to the handler in the relevant `Skill`. The `AgentLoop` and `Multiplexer` are decoupled via the `InBox` and `OutBox`, and both are maintained by the `Runtime`. ## Next Steps ### Recommended We recommend you continue with the next step in the 'Getting Started' series: - AEA and web frameworks ### Relevant Deep-Dives Most AEA development focuses on developing the `Skills` and `Protocols` necessary for an AEA to deliver against its economic objectives. Understanding `Protocols` is core to developing your own agent. You can learn more about the `Protocols` agents use to communicate with each other and how they are created in the following section: - Protocols Most of an AEA developer's time is spent on `Skill` development. `Skills` are the core business logic components of an AEA. Check out the following guide to learn more: - Skills In most cases, one of the available `Connection` packages can be used. Occasionally, you might develop your own `Connection`: - Connections ================================================ FILE: docs/core-components-2.md ================================================ # Core components - Part 2 The AEA framework consists of several core components, some required to run an AEA and others optional. In Core Components - Part 1 we described the common components each AEA uses. In this page, we will look at more advanced components. ## Required Components Used by AEAs ### Decision Maker Decision Maker of an AEA The `DecisionMaker` can be thought of as a `Wallet` manager plus "economic brain" of the AEA. It is responsible for the AEA's crypto-economic security and goal management, and it contains the preference and ownership representation of the AEA. The decision maker is the only component with access to the `Wallet`'s private keys. You can learn more about the decision maker here. In its simplest form, the decision maker acts like a `Wallet` with `Handler` that reacts to the messages it receives from the skills. ### Wallet The `Wallet` contains the private-public key pairs used by the AEA. Skills do not have access to the wallet, only the decision maker does. The agent has two sets of private keys, as configured in the `aea-config.yaml`: - `private_key_paths`: This is a dictionary mapping identifiers to the file paths of private keys used in the AEA. For each identifier, e.g. `fetchai`, the AEA can have one private key. The private keys listed here are available in the `Decision Maker` and the associated public keys and addresses are available in all skills. The AEA uses these keys to sign transactions and messages. These keys usually hold the AEAs funds. - `connection_private_key_paths`: This is a dictionary mapping identifiers to the file paths of private keys used in connections. For each identifier, e.g. `fetchai`, the `Multiplexer` can have one private key. The private keys listed here are available in the connections. The connections use these keys to secure message transport, for instance. It is the responsibility of the AEA's user to safeguard the keys used and ensure that keys are only used in a single AEA. Using the same key across different AEAs will lead to various failure modes. Private keys can be encrypted at rest. The CLI commands used for interacting with the wallet allow specifying a password for encryption/decryption. ### Identity The `Identity` is an abstraction that represents the identity of an AEA in the Open Economic Framework, backed by public-key cryptography. It contains the AEA's addresses as well as its name. The identity can be accessed in a `Skill` via the `AgentContext`. ## Optional Components Used by AEAs ### Contracts Contracts of an AEA `Contracts` wrap smart contracts for third-party decentralized ledgers. In particular, they provide wrappers around the API or ABI of a smart contract. They expose an API to abstract implementation specifics of the ABI from the `Skills`. `Contracts` usually contain the logic to create contract transactions and make contract calls. `Contracts` can be added as packages. For more details on `Contracts` also read the `Contract` guide here. ## Putting it Together Taken together, the core components from this section and the first part provide the following simplified illustration of an AEA: Simplified illustration of an AEA ## Next Steps ### Recommended We recommend you continue with the next step in the 'Getting Started' series: - How AEAs talk to each other - Interaction protocols ### Relevant Deep-Dives Understanding the decision maker is vital to developing a goal oriented and crypto-economically safe AEA. You can learn more about the `DecisionMaker` in the following section: - Decision Maker Understanding `Contracts` is important when developing AEAs that make commitments or use smart contracts for other purposes. You can learn more about the `Contracts` agents use in the following section: - Contracts ================================================ FILE: docs/core-components.md ================================================ # Core Components AEAs can be made from various components, much like legos, and these components can be of differing types. Below are some of the more important types of components an agent can have. ## Skill A **Skill** is an isolated, self-contained, (and preferably atomic) functionality that AEAs can take on board to expand their capability. Skills contain the proactive and reactive behaviour that ultimately makes it possible for an AEA to deliver economic value to its owner. A Skill encapsulates implementations of three base classes `Handler`, `Behaviour`, `Model`, and is closely related with `Task`: - Handler: Handlers implement AEAs' **reactive** behaviour. If an AEA understands a protocol referenced in a received `Envelope`, this envelope is sent to the corresponding handler which executes the AEA's reaction to this message. - Behaviour: Behaviours implement AEAs' **proactiveness**, encapsulating actions which further an AEA's goals, and are initiated by internals of the AEA rather than external events. - Model: Encapsulate arbitrary objects and is made available to all components of the skill. - Task: Tasks encapsulate background work internal to the AEA. A skill can read (parts of) an AEA's state and propose actions to the AEA according to its specific logic. As such, more than one skill could exist per protocol, competing with each other in suggesting to the AEA the best course of actions to take. For instance, an AEA which is trading goods, could subscribe to more than one skill, where each corresponds to a different trading strategy. The framework places no limits on the complexity of `Skills`. They can implement simple (e.g. if-this-then-that) logic or be complex (e.g. a deep learning model or reinforcement learning agent). The framework provides one default `error` skill. Additional `Skills` can be added as packages. For more details on skills, head over to the `Skill` guide . ## Protocol A **Protocol** defines the structure and nature of an interaction that can happen between agents, or between components of an agent. You can think of a protocol as the language that two agents speak and a skill for this protocol as a particular way of speaking this language. From a game-theoretic viewpoint, a protocol defines the rules of a game and a skill for this protocol defines a particular strategy for playing this game. Protocols define agent-to-agent as well as component-to-component interactions within AEAs. As such, they include: - `Messages`: defining the syntax of messages. - `Serialization`: defining how a message is encoded for transport. - `Dialogues`: defines rules over sequences of messages. The framework provides one `default` protocol. This protocol provides a bare-bones implementation which includes a `DefaultMessage` class and associated `DefaultSerializer` and `DefaultDialogue` classes. Additional protocols for new types of interactions, can be added as packages. For more details on protocols, you can read the protocol guide. To learn how you can easily automate protocol definition, head to the guide for the protocol generator. Protocol specific messages, wrapped in `Envelopes`, are sent and received to other agents, agent components and services via **Connections**. ## Connection **Connections** act as interfaces between an agent and the outside world. As such, a connection allows the agent to communicate with some entity outside of it, for example, another agent, a traditional HTTP server, a database, a reinforcement learning training environment, a blockchain, etc. Where necessary, a Connection is responsible for translating between the framework specific `Envelope` with its contained message and the external service or third-party protocol (e.g. HTTP). The framework provides one default `stub` connection. It implements an I/O reader and writer to send messages to the agent from a local file. Additional connections can be added as packages. For more details on `Connections` read the `Connection` guide. ================================================ FILE: docs/css/admonitions.css ================================================ :root { --md-admonition-icon--target: url('data:image/svg+xml;charset=utf-8,') } .md-typeset .admonition.target, .md-typeset details.target { border-color: rgb(255, 225, 0); } .md-typeset .target > .admonition-title, .md-typeset .target > summary { background-color: rgba(255, 225, 0, 0.1); } .md-typeset .target > .admonition-title::before, .md-typeset .target > summary::before { background-color: rgb(255, 225, 0); -webkit-mask-image: var(--md-admonition-icon--target); mask-image: var(--md-admonition-icon--target); } ================================================ FILE: docs/css/my-styles.css ================================================ /* overriding colours */ [data-md-color-scheme="default"] { --md-primary-fg-color: #4051b5; --md-typeset-color: #525252; --md-a-color:#1d6ff5; } [data-md-color-scheme="slate"] { --md-primary-fg-color: #fff; --md-typeset-color: #d0d6fc; --md-a-color:#5997fd; } /*overriding the header*/ .md-header-nav__button.md-logo img { width: auto!important; } .md-nav__button .md-logo{ width: auto!important; } .md-header__button.md-logo img { width: auto !important; } @media screen and (max-width: 76.1875em) { .md-nav--primary .md-nav__title[for="__drawer"] { background-color: #202943 !important; } .md-nav--primary .md-nav__title[for=__drawer] img{ width: 180px; height: auto; } } .md-header{ background-color:#202943!important; padding: 10px; height: auto; } /* Overriding font header, link, paragraph styling */ .md-typeset h3, .md-typeset h1, .md-typeset h2 { margin-bottom: 0.4em; color: var(--md-primary-fg-color); font-weight: 400; } .md-typeset a { color: var(--md-a-color); } .black-link{ color: black!important; } .md-nav__item .md-nav__link--active { padding: 4px 8px; position: relative; left: -8px; background: #E9EBFC; border-radius: 16px; width: fit-content; color: rgb(64, 81, 181) } @media screen and (max-width: 76.1875em) { .md-nav__item .md-nav__link--active { left: 0; } } ================================================ FILE: docs/debug.md ================================================ # Debugging There are multiple ways in which to configure your AEA for debugging during development. We focus on the standard Python approach here. ## Using `pdb` stdlib You can add a debugger anywhere in your code: ``` python import pdb; pdb.set_trace() ``` Then simply run you AEA with the `--skip-consistency-check` mode: ``` bash aea -s run ``` For more guidance on how to use `pdb` check out the documentation. ## Using an IDE - For VSCode modify the `launch.json` to include the following information: ``` json { "version": "0.2.0", "configurations": [ { "name": "aea run", "type": "python", "request": "launch", "program": "PATH_TO_VIRTUAL_ENV/bin/aea", "args": ["-v","DEBUG","--skip-consistency-check","run"], "cwd": "CWD", "console": "integratedTerminal" } ] } ``` where `PATH_TO_VIRTUAL_ENV` should be replaced with the path to the virtual environment and `CWD` with the working directory for the agent to debug (where the `aea-config.yaml` file is). ================================================ FILE: docs/decision-maker-transaction.md ================================================ # Create Decision-Maker Transaction This guide can be considered as a part 2 of the the stand-alone transaction demo. The main difference is that now we are going to use the decision-maker to sign the transaction. First, import the libraries and the set the constant values. (Get the `packages` directory from the AEA repository `svn export https://github.com/fetchai/agents-aea.git/trunk/packages`.) ``` python import logging import time from threading import Thread from typing import Optional, cast from aea_ledger_fetchai import FetchAICrypto from aea.aea_builder import AEABuilder from aea.configurations.base import PublicId, SkillConfig from aea.crypto.helpers import create_private_key from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet from aea.helpers.transaction.base import RawTransaction, Terms from aea.identity.base import Identity from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue from aea.skills.base import Handler, Model, Skill, SkillContext from packages.fetchai.protocols.signing.dialogues import SigningDialogue from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage from tests.conftest import get_wealth_if_needed logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) FETCHAI_PRIVATE_KEY_FILE_1 = "fetchai_private_key_1.txt" FETCHAI_PRIVATE_KEY_FILE_2 = "fetchai_private_key_2.txt" ``` ## Create a Private Key and an AEA To have access to the decision-maker, which is responsible for signing transactions, we need to create an AEA. We can create an AEA with the builder, providing it with a private key we generate first. ``` python # Create a private key create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_1 ) # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added builder = AEABuilder() builder.set_name("my_aea") builder.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_1) # Create our AEA my_aea = builder.build() ``` ## Add a Simple Skill Add a simple skill with a signing handler and the signing dialogues. ``` python # add a simple skill with handler skill_context = SkillContext(my_aea.context) skill_config = SkillConfig(name="simple_skill", author="fetchai", version="0.1.0") signing_handler = SigningHandler( skill_context=skill_context, name="signing_handler" ) signing_dialogues_model = SigningDialogues( skill_context=skill_context, name="signing_dialogues", self_address=str(skill_config.public_id), ) simple_skill = Skill( skill_config, skill_context, handlers={signing_handler.name: signing_handler}, models={signing_dialogues_model.name: signing_dialogues_model}, ) my_aea.resources.add_skill(simple_skill) ``` ## Create a Second Identity ``` python # create a second identity create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_2 ) counterparty_wallet = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_2}) get_wealth_if_needed(counterparty_wallet.addresses["fetchai"]) counterparty_identity = Identity( name="counterparty_aea", addresses=counterparty_wallet.addresses, public_keys=counterparty_wallet.public_keys, default_address_key=FetchAICrypto.identifier, ) ``` ## Create the Signing Message Next, we are creating the signing message and sending it to the decision-maker. ``` python # create signing message for decision maker to sign terms = Terms( ledger_id=FetchAICrypto.identifier, sender_address=my_aea.identity.address, counterparty_address=counterparty_identity.address, amount_by_currency_id={"FET": -1}, quantities_by_good_id={"some_service": 1}, nonce="some_nonce", fee_by_currency_id={"FET": 0}, ) get_wealth_if_needed(terms.sender_address) signing_dialogues = cast(SigningDialogues, skill_context.signing_dialogues) stub_transaction = LedgerApis.get_transfer_transaction( terms.ledger_id, terms.sender_address, terms.counterparty_address, terms.sender_payable_amount, terms.sender_fee, terms.nonce, ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), raw_transaction=RawTransaction(FetchAICrypto.identifier, stub_transaction), terms=terms, ) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.create_with_message("decision_maker", signing_msg), ) assert signing_dialogue is not None my_aea.context.decision_maker_message_queue.put_nowait(signing_msg) ``` ## Run the Agent Finally, we are running the agent and expect the signed transaction to be printed in the terminal. ``` python # Set the AEA running in a different thread try: logger.info("STARTING AEA NOW!") t = Thread(target=my_aea.start) t.start() # Let it run long enough to interact with the decision maker time.sleep(1) finally: # Shut down the AEA logger.info("STOPPING AEA NOW!") my_aea.stop() t.join() ``` After the completion of the signing, we get the signed transaction. ## More Details To be able to register a handler that reads the internal messages, we have to create a class at the end of the file which processes the signing messages. ``` python class SigningDialogues(Model, BaseSigningDialogues): """Signing dialogues model.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return SigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class SigningHandler(Handler): """Implement the signing handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """ Implement the handler teardown. :return: None """ def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a signing message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.info("transaction signing was successful.") logger.info(signing_msg.signed_transaction) def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) ``` You can find the full code for this example below: ??? note "Transaction via decision-maker full code:" ``` python import logging import time from threading import Thread from typing import Optional, cast from aea_ledger_fetchai import FetchAICrypto from aea.aea_builder import AEABuilder from aea.configurations.base import PublicId, SkillConfig from aea.crypto.helpers import create_private_key from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet from aea.helpers.transaction.base import RawTransaction, Terms from aea.identity.base import Identity from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue from aea.skills.base import Handler, Model, Skill, SkillContext from packages.fetchai.protocols.signing.dialogues import SigningDialogue from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage from tests.conftest import get_wealth_if_needed logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) FETCHAI_PRIVATE_KEY_FILE_1 = "fetchai_private_key_1.txt" FETCHAI_PRIVATE_KEY_FILE_2 = "fetchai_private_key_2.txt" def run(): """Run demo.""" # Create a private key create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_1 ) # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added builder = AEABuilder() builder.set_name("my_aea") builder.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_1) # Create our AEA my_aea = builder.build() # add a simple skill with handler skill_context = SkillContext(my_aea.context) skill_config = SkillConfig(name="simple_skill", author="fetchai", version="0.1.0") signing_handler = SigningHandler( skill_context=skill_context, name="signing_handler" ) signing_dialogues_model = SigningDialogues( skill_context=skill_context, name="signing_dialogues", self_address=str(skill_config.public_id), ) simple_skill = Skill( skill_config, skill_context, handlers={signing_handler.name: signing_handler}, models={signing_dialogues_model.name: signing_dialogues_model}, ) my_aea.resources.add_skill(simple_skill) # create a second identity create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_2 ) counterparty_wallet = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_2}) get_wealth_if_needed(counterparty_wallet.addresses["fetchai"]) counterparty_identity = Identity( name="counterparty_aea", addresses=counterparty_wallet.addresses, public_keys=counterparty_wallet.public_keys, default_address_key=FetchAICrypto.identifier, ) # create signing message for decision maker to sign terms = Terms( ledger_id=FetchAICrypto.identifier, sender_address=my_aea.identity.address, counterparty_address=counterparty_identity.address, amount_by_currency_id={"FET": -1}, quantities_by_good_id={"some_service": 1}, nonce="some_nonce", fee_by_currency_id={"FET": 0}, ) get_wealth_if_needed(terms.sender_address) signing_dialogues = cast(SigningDialogues, skill_context.signing_dialogues) stub_transaction = LedgerApis.get_transfer_transaction( terms.ledger_id, terms.sender_address, terms.counterparty_address, terms.sender_payable_amount, terms.sender_fee, terms.nonce, ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), raw_transaction=RawTransaction(FetchAICrypto.identifier, stub_transaction), terms=terms, ) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.create_with_message("decision_maker", signing_msg), ) assert signing_dialogue is not None my_aea.context.decision_maker_message_queue.put_nowait(signing_msg) # Set the AEA running in a different thread try: logger.info("STARTING AEA NOW!") t = Thread(target=my_aea.start) t.start() # Let it run long enough to interact with the decision maker time.sleep(1) finally: # Shut down the AEA logger.info("STOPPING AEA NOW!") my_aea.stop() t.join() class SigningDialogues(Model, BaseSigningDialogues): """Signing dialogues model.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return SigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class SigningHandler(Handler): """Implement the signing handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """ Implement the handler teardown. :return: None """ def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a signing message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.info("transaction signing was successful.") logger.info(signing_msg.signed_transaction) def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) if __name__ == "__main__": run() ``` ================================================ FILE: docs/decision-maker.md ================================================ # Decision Maker The `DecisionMaker` can be thought of like a wallet manager plus "economic brain" of the AEA. It is responsible for the AEA's crypto-economic security and goal management, and it contains the preference and ownership representation of the AEA. The decision maker is the only component which has access to the wallet's private keys. ## Interaction with Skills Skills communicate with the decision maker via `Messages`. At present, the decision maker processes messages of two protocols: - `SigningMessage`: it is used by skills to propose a transaction to the decision-maker for signing. - `StateUpdateMessage`: it is used to initialize the decision maker with preferences and ownership states. It can also be used to update the ownership states in the decision maker if the settlement of transaction takes place. A message, say `msg`, is sent to the decision maker like so from any skill: ``` python self.context.decision_maker_message_queue.put_nowait(msg) ``` The decision maker processes messages and can accept or reject them. To process `Messages` from the decision maker in a given skill you need to create a `Handler`, in particular a `SigningHandler` like so: ``` python class SigningHandler(Handler): protocol_id = SigningMessage.protocol_id def handle(self, message: Message): """ Handle a signing message. :param message: the signing message from the decision maker. """ # code to handle the message ``` ## Custom `DecisionMaker` The framework implements a default `DecisionMakerHandler` and an advanced `DecisionMakerHandler`. You can also implement your own and mount it. No further configuration is needed to use the default. To use the advanced decision maker handler, add the following configuration to the `aea-config.yaml` of your AEA (on page 1): ``` yaml decision_maker_handler: config: {} dotted_path: "aea.decision_maker.gop:DecisionMakerHandler" file_path: null ``` The easiest way to add a custom decision maker handler is to run the following command to scaffold a custom `DecisionMakerHandler`: ``` bash aea scaffold decision-maker-handler ``` You can then implement your own custom logic to process messages and interact with the `Wallet`. !!! note For examples how to use these concepts have a look at the `tac_` skills. These functionalities are experimental and subject to change. ================================================ FILE: docs/defining-data-models.md ================================================ # Defining Data Models In this section, we explain how to define _data models_, an important component of the OEF Search & Discovery. It allows agents to describe themselves and to discover the services/resources they are interested in. In a sentence, a `DataModel` is a set of `attributes`, and a `Description` of a service/resource is an assignment of those attributes. All you need to specify data models and descriptions (that is, instances of the data model) can be found in the `aea.helpers.search` module. ## Attributes At the lowest level of our data model language, we have the `Attribute`. An attribute is an abstract definition of a property. It is identified by a `name`, that must be unique in a given data model (that is, we can't have two attributes that share the same name). Every attribute has a `type`, that specifies the domain of the property, that is, the possible values that the attribute can assume. At the moment, we support five types of attributes: - strings - integers - booleans - floats - locations, i.e. instances of `Location` (pairs of (latitude, longitude)) An attribute can be `optional`, in the sense that instantiation of the attribute is not mandatory by the instances of the data model. Finally, every attribute might have a `description` that explains the purpose of the attribute. **Example**: suppose we have a bookshop, and we want to describe the books we sell. Presumably, we would like to include: the following properties of our books: - The `title` - The `author` - The `genre` (e.g. science fiction, horror) - The `year of publication` - The `average rating` (average of the ratings between 0 and 5) - The `ISBN` code - If it can be sold as an e-book. For each of these fields, we can define an attribute by using `Attribute`: ``` python from aea.helpers.search.models import Attribute, Location attr_title = Attribute("title", str, True, "The title of the book.") attr_author = Attribute("author", str, True, "The author of the book.") attr_genre = Attribute("genre", str, True, "The genre of the book.") attr_year = Attribute("year", int, True, "The year of publication of the book.") attr_avg_rat = Attribute("average_rating", float, False, "The average rating of the book.") attr_isbn = Attribute("ISBN", str, True, "The ISBN.") attr_ebook = Attribute("ebook_available", bool, False, "If the book can be sold as an e-book.") attr_bookshop = Attribute("bookshop_pos", Location, False, "The location of the bookshop where you can find the book") ``` Let's focus on the parameters of the `Attribute` constructor: 1. the first one is the name of the attribute. It is needed to instantiate a data model and to define queries over it. 2. the second one is the type of the attribute. It specifies the domain of the possible values the attribute can assume. E.g. the attribute `year` can only be an integer, whereas the `average_rating` can only be a floating-point number. The supported types are: `str`, `int`, `bool`, `float` and `Location`. 3. the third one is a boolean that specifies whether the attribute is _always required_ or it _can be omitted_. For example, we might not be able to specify the `ebook_available` attribute, maybe because it's not applicable to some kind of books. 4. the fourth parameter is the description, that is a short description of the purpose of the attribute. ## Data Models A _data model_ is just a set of _attributes_. The class that implements the data model is `DataModel`. **Example**: let's continue with the example of the bookshop. Once we've defined the attributes, we'd like to group them in the same structure. We can do it in the following way: ``` python from aea.helpers.search.models import DataModel book_model = DataModel("book", [ attr_title, attr_author, attr_genre, attr_year, attr_avg_rat, attr_isbn, attr_ebook, attr_bookshop ], "A data model to describe books.") ``` A `DataModel` requires: 1. a _name_ (in the example the name is `"book"`) used to refer to the data model. 2. a _list of attributes_, that constitutes the abstract data model. 3. an (optional) _description_ about the purpose of the data model. ## Description A `Description` is just an _instantiation of a data model_. That is, we specify a value to every attribute belonging to the data model we are interested in. The class that implements the description is `Description`. **Example**: now we have all we need to create a little catalogue about our books: ``` python from aea.helpers.search.models import Description It = Description({ "title" : "It", "author": "Stephen King", "genre": "horror", "year": 1986, "average_rating": 4.5, "ISBN": "0-670-81302-8", "ebook_available": True, "bookshop_pos": Location(52.2057092, 0.1183431) }, book_model) _1984 = Description({ "title" : "1984", "author": "George Orwell", "genre": "novel", "year": 1949, "ISBN": "978-0451524935", "ebook_available": False }, book_model) ``` We defined the descriptions for two books, namely `It` and `_1984`, that refers to a data model. The attributes are instantiated with a dictionary that has: - as keys, the name of the attributes. - as values, the values associated with the attributes. Notice that in the latter book we omitted the `average_rating` field. We are allowed to do that because of the `average_rating` attribute is not mandatory. ================================================ FILE: docs/demos.md ================================================ # Demos We provide demo guides for multiple use-cases, each one involving several AEAs interacting in a different scenario. These demos serve to highlight the concept of AEAs as well as provide inspiration for developers. Demos should not be taken as production ready software, although every care is taken to fix bugs when reported. Demos are alphabetically sorted, we recommend you start with the weather skills demo. ================================================ FILE: docs/deployment.md ================================================ # Deployment The easiest way to run an AEA is using your development environment. If you would like to run an AEA from a browser you can use Google Colab. This gist can be opened in Colab and implements the quick start. For deployment, we recommend you use Docker. ## Deployment using a Docker Image First, we fetch a directory containing a Dockerfile and some dependencies: ``` bash svn export https://github.com/fetchai/agents-aea/branches/main/deploy-image cd deploy-image ``` Then follow the `README.md` contained in the folder. ## Deployment using Kubernetes For an example of how to use Kubernetes navigate to our TAC deployment example. ================================================ FILE: docs/design-principles.md ================================================ # Design Principles The AEA framework development is guided by the following 8 principles: - **Accessibility**: ease of use. - **Modularity**: encourages module creation, sharing and reuse. - **Openness**: easily extensible with third-party libraries. - **Conciseness**: conceptually simple. - **Value-driven**: drives immediate value. - **Low entry barriers**: leverages existing programming languages and web protocols. - **Safety**: safe for the user (economically speaking). - **Goal-alignment**: seamless facilitation of users' preferences and goals. ================================================ FILE: docs/development-setup.md ================================================ # Development Setup An AEA consists of packages . When developing, it helps to be able to save packages in a local package registry, rather than pushing them to remote registry. This guide helps you set up a local package registry and configure the working directory for development. There are two ways to write code for an AEA: 1. independent of a concrete AEA project, write individual packages 2. from within an AEA project, write packages for that AEA ## Approach 1 To prepare a directory (henceforth working directory) for development with the AEA framework you can take a few steps: - Either, manually: - Ensure you start with an empty working directory to avoid any unnecessary side effects. - In your working directory, create an empty folder called `packages`. This folder will act as the local registry for packages. - In your working directory, create a `.env` file with the constant `PYTHONPATH=$PYTHONPATH:path_to_packages_dir` where `path_to_packages_dir` is the path to the `packages` folder in your working directory. - Or, automated: - Fork our template repo for AEA development. Then clone it to your machine. - Depending on your editor, you might take further steps: - VS Code: The Python Extension in VS Code can be configured to include additional paths in the Python path. The extension has a setting for `python.envFile` which specifies the path to a file containing environment variable definitions. The default is set to `"python.envFile": "${workspaceFolder}/.env"`. Provide the path to the `.env` file in the above settings. In the `.env` file, add the `PYTHONPATH` constant defined above. Then close VS Code and re-open it for the settings to take effect. After developing a package, you can add it to an AEA project in the working directory (e.g. `aea create AGENT_NAME && cd AGENT_NAME && aea add --local PACKAGE_TYPE PUBLIC_ID` will create a new AEA project `AGENT_NAME` and add the package of type `PACKAGE_TYPE` with public id `PUBLIC_ID` to it.) ## Approach 2 It is also possible to develop directly in an AEA project: - Prepare a directory (henceforth working directory) for development. - Create a new project `aea create AGENT_NAME && cd AGENT_NAME` - Scaffold a new package `aea scaffold --with-symlinks PACKAGE_TYPE PACKAGE_NAME`. This will create the package scaffold under the directory `{PACKAGE_TYPE}s` and create symlinks to ensure package import paths line up with the folder structure. The symlinks are not needed to run the AEA. They are purely for your IDE. - In your working directory, create a `.env` file with the constant `PYTHONPATH=$PYTHONPATH:path_to_project_dir` where `path_to_project_dir` is the path to the AEA project contained in your working directory. - Depending on your editor, you might take further steps: - VS Code: The Python Extension in VS Code can be configured to include additional paths in the Python path. The extension has a setting for `python.envFile` which specifies the path to a file containing environment variable definitions. The default is set to `"python.envFile": "${workspaceFolder}/.env"`. Provide the path to the `.env` file in the above settings. In the `.env` file, add the `PYTHONPATH` constant defined above. Then close VS Code and re-open it for the settings to take effect. ## General Advice This advice partially overlaps with the previous two sections: - When developing a specific AEA, it might be helpful to publish/push or fetch/add from local registry. From your working directory/AEA project, simply execute the usual AEA CLI commands. The CLI will first search in the `packages` directory, then in the remote AEA registry. You can explicitly point to local registry by providing flag `--local` or `--remote` to only point to remote registry (see here for more details on CLI commands). - When working on an AEA, it may help to provide a symbolic link to the `packages` directory, so that the import paths are detected by your editor. Simply create an empty file with `touch packages` in your AEA project, then create a symbolic link to the `packages` directory with `ln -s ../packages packages`. - Alternatively, it can help to provide symbolic links within an AEA to align import paths with folder structure. Simply create an empty file with `touch packages` in your AEA project, then create a symbolic link to `ln -s vendor packages`. ================================================ FILE: docs/diagram.md ================================================ # Architectural Diagram The framework has two distinctive parts. - A **core** that is developed by the Fetch.ai team as well as external contributors. - **Extensions** (also known as **packages**) developed by any developer. Currently, the framework supports four types of packages which can be added to the core as modules: - Skills encapsulate logic that deliver economic value to the AEA. Skills are the main focus of the framework's extensibility. - Protocols define the structure of agent-to-agent and component-to-component interactions (messages and dialogues) for agents. - Connections provide interfaces for the agent to connect with the outside world. They wrap SDKs or APIs and provide interfaces to networks, ledgers and other services. - Contracts wrap smart contracts for Fetch.ai and third-party decentralized ledgers. The following figure illustrates the framework's architecture: Simplified illustration of an AEA The execution is broken down in more detail below: Execution of an AEA The agent operation breaks down into three parts: - **Setup**: calls the `setup()` method of all registered resources - **Operation**: - Agent loop (Thread 1 - Asynchronous agent loop): - `react()`: this function grabs all Envelopes waiting in the `InBox` queue and calls the `handle()` method on the Handler(s) responsible for them. - `act()`: this function calls the `act()` method of all registered Behaviours. - `update()`: this function enqueues scheduled tasks for execution with the `TaskManager` and executes the decision maker. - Task loop (Thread 2- Synchronous): executes available tasks - Decision maker loop (Thread 3- Synchronous): processes internal messages - Multiplexer (Thread 4 - Asynchronous event loop): processes incoming and outgoing messages across several connections asynchronously. - **Teardown**: calls the `teardown()` method of all registered resources To prevent a developer from blocking the main loop with custom skill code, an execution time limit is applied to every `Behaviour.act` and `Handler.handle` call. By default, the execution limit is set to `0` seconds, which disables the feature. You can set the limit to a strictly positive value (e.g. `0.1` seconds) to test your AEA for production readiness. If the `act` or `handle` time exceed this limit, the call will be terminated. An appropriate message is added to the logs in the case of some code execution being terminated. ================================================ FILE: docs/ecosystem.md ================================================ # Agent Ecosystem AEAs are situated within a larger ecosystem comprised of various other systems and technology layers. The AEA, OEF, and Ledger systems ## Agent Communication Network (ACN) ACN is a peer-to-peer communication network for agents. It allows AEAs to send and receive envelopes between each other. The implementation builds on the open-source libp2p library. A distributed hash table is used by all participating peers to maintain a mapping between agents' cryptographic addresses and their network addresses. Agents can receive messages from other agents if they are both connected to the ACN (see here for an example). ## Search and Discovery An sOEF node allows agents to discover each other. In particular, agents can register themselves and the services they offer, and can search for agents who offer specific services. For two agents to be able to find each other, at least one must register itself on the sOEF and the other must query the sOEF node for it. Detailed documentation is provided here. ## Ledgers Ledgers enable AEAs to store transactions, for example involving the transfer of funds to each other, or the execution of smart contracts. They optionally ensure the truth and integrity of agent to agent interactions. Although a ledger can, in principle, be used to store structured data (e.g. training data in a machine learning model), in most cases the resulting costs and privacy implications do not make this sustainable. Instead, usually only references to structured data - often in the form of hashes - are stored on a ledger, and the actual data is stored off-chain. The Python implementation of the AEA Framework currently integrates with three ledgers: - Fetch.ai ledger - Ethereum ledger - Cosmos ledger Furthermore, the framework makes it straightforward for any developer to create a ledger plugin, adding support for another ledger. ### AEAs as Second Layer Technology The following presentation discusses how AEAs can be seen as second layer technology to ledgers. ================================================ FILE: docs/erc1155-skills.md ================================================ # Contract Deploy and Interact The AEA `erc1155_deploy` and `erc1155_client` skills demonstrate an interaction between two AEAs which use a smart contract. - The `erc1155_deploy` skill deploys the smart contract, creates and mints items. - The `erc1155_client` skill signs a transaction to complete a trustless trade with its counterparty. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ## Discussion The scope of this guide is demonstrating how you can deploy a smart contract and interact with it using AEAs. In this specific demo, you create two AEAs. One deploys and creates tokens inside a smart contract. The other signs a transaction to complete an atomic swap. The smart contract used is ERC1155 with a one-step atomic swap functionality. This means the trade between the two AEAs can be trustless. !!! note This is only for demonstrative purposes since the AEA deploying the contract also has the ability to mint tokens. In reality, the transfer of tokens from the AEA signing the transaction is worthless. ## Demo ### Create the Deployer AEA Fetch the AEA that will deploy the contract: ``` bash aea fetch fetchai/erc1155_deployer:0.34.5 cd erc1155_deployer aea install aea build ``` ??? note "Alternatively, create from scratch:" Create the AEA that will deploy the contract. ``` bash aea create erc1155_deployer cd erc1155_deployer aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/erc1155_deploy:0.31.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-cosmos": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` And change the default ledger: ``` bash aea config set agent.default_ledger ethereum ``` Create a private key for the deployer AEA and add it for Ethereum use: ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` Create a private key for the P2P connection: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Create the Client AEA In another terminal, fetch the client AEA which will receive some tokens from the deployer. ``` bash aea fetch fetchai/erc1155_client:0.34.5 cd erc1155_client aea install aea build ``` ??? note "Alternatively, create from scratch:" Create the AEA that will get some tokens from the deployer. ``` bash aea create erc1155_client cd erc1155_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/erc1155_client:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-cosmos": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` And change the default ledger: ``` bash aea config set agent.default_ledger ethereum ``` Create a private key for the client AEA and add it for Ethereum use: ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` Create a private key for the P2P connection: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ## Run Ganache Execute the following command to run Ganache: ``` bash docker run -p 8545:8545 trufflesuite/ganache-cli:latest --verbose --gasPrice=0 --gasLimit=0x1fffffffffffff --account="$(cat erc1155_deployer/ethereum_private_key.txt),1000000000000000000000" --account="$(cat erc1155_client/ethereum_private_key.txt),1000000000000000000000" ``` Wait some time for the wealth creation to be mined on Ropsten. Check your wealth: ``` bash aea get-wealth ethereum ``` You should get `1000000000000000000000`. !!! note If no wealth appears after a while, then try funding the private key directly using a web faucet. ## Update SOEF Configurations for both AEAs Update the SOEF configuration in both AEA projects: ``` bash aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum ``` ## Run the AEAs First, run the deployer AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of this address. Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address. The output will be something like `/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAm2JPsUX1Su59YVDXJQizYkNSe8JCusqRpLeeTbvY76fE5`. This is the entry peer address for the local agent communication network created by the deployer. This AEA then performs the following steps: - deploys the smart contract - creates a batch of items in the smart contract - mints a batch of items in the smart contract At some point you should see the log output: ``` bash registering service on SOEF. ``` At this point, configure the client AEA to connect to the same local ACN created by the deployer by running the following command in the client's terminal, replacing `SOME_ADDRESS` with the value you noted above: ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` Then, run the client AEA: ``` bash aea run ``` You will see that after discovery, the two AEAs exchange information about the transaction and the client at the end signs and sends the signature to the deployer AEA to send it to the network. !!! note Transactions on Ropsten can take a significant amount of time! If you run the example a second time, and the previous transaction is still pending, it can lead to a failure. The warning message `Cannot verify whether transaction improves utility. Assuming it does!` can be ignored. ## Delete the AEAs When you're done, stop the agents (`CTRL+C`), go up a level and delete the AEAs. ``` bash cd .. aea delete erc1155_deployer aea delete erc1155_client ``` ## Communication This diagram shows the communication between the various entities in this interaction: ``` mermaid sequenceDiagram participant Search participant Erc1155_contract participant Client_AEA participant Deployer_AEA participant Blockchain activate Search activate Erc1155_contract activate Client_AEA activate Deployer_AEA activate Blockchain Deployer_AEA->>Blockchain: deployes smart contract Deployer_AEA->>ERC1155_contract: creates tokens Deployer_AEA->>ERC1155_contract: mint tokens Deployer_AEA->>Search: register_service Client_AEA->>Search: search Search-->>Client_AEA: list_of_agents Client_AEA->>Deployer_AEA: call_for_proposal Deployer_AEA->>Client_AEA: inform_message Client_AEA->>Deployer_AEA: signature Deployer_AEA->>Blockchain: send_transaction Client_AEA->>ERC1155_contract: asks_balance deactivate Search deactivate Erc1155_contract deactivate Client_AEA deactivate Deployer_AEA deactivate Blockchain ``` ================================================ FILE: docs/generic-skills-step-by-step.md ================================================ # Trade between Two AEAs This guide is a step-by-step introduction to building AEAs that advertise their static and dynamic data, find other AEAs with required data, negotiate terms of trade, and carry out trades via ledger transactions. If you simply want to run the resulting AEAs go here. ## Dependencies (Required) Follow the Preliminaries and Installation sections from the AEA quick start. ## Reference Code (Optional) This step-by-step guide goes through the creation of two AEAs which are already developed by Fetch.ai. You can get the finished AEAs, and compare your code against them, by following the next steps: ``` bash aea fetch fetchai/generic_seller:0.29.5 cd generic_seller aea eject skill fetchai/generic_seller:0.28.6 cd .. ``` ``` bash aea fetch fetchai/generic_buyer:0.30.5 cd generic_buyer aea eject skill fetchai/generic_buyer:0.27.6 cd .. ``` ## Simplification Step To keep file paths consistent with the reference code, we suggest you initialize your local author as `fetchai` for the purpose of this demo only: ``` bash aea init --reset --author fetchai ``` ## Generic Seller AEA ### Step 1: Create the AEA Create a new AEA by typing the following command in the terminal: ``` bash aea create my_generic_seller cd my_generic_seller aea install ``` Our newly created AEA is inside the current working directory. Let’s create our new skill that will handle the sale of data. Type the following command: ``` bash aea scaffold skill generic_seller ``` The `scaffold skill` command creates the correct structure for a new skill inside our AEA project. You can locate the newly created skill under the "skills" folder (`my_generic_seller/skills/generic_seller/`), and it must contain the following files: - `__init__.py` - `behaviours.py` - `handlers.py` - `my_model.py` - `skills.yaml` ### Step 2: Create the Behaviour A `Behaviour` class contains the business logic specific to actions initiated by the AEA, rather than reactions to other events. Open the `behaviours.py` file (`my_generic_seller/skills/generic_seller/behaviours.py`) and replace the stub code with the following: ``` python from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_seller.dialogues import ( LedgerApiDialogues, OefSearchDialogues, ) from packages.fetchai.skills.generic_seller.strategy import GenericStrategy DEFAULT_SERVICES_INTERVAL = 60.0 DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericServiceRegistrationBehaviour(TickerBehaviour): """This class implements a behaviour.""" def __init__(self, **kwargs: Any): """Initialise the behaviour.""" services_interval = kwargs.pop( "services_interval", DEFAULT_SERVICES_INTERVAL ) # type: int self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=services_interval, **kwargs) self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """Implement the setup.""" strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx: ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=strategy.ledger_id, address=cast(str, self.context.agent_addresses.get(strategy.ledger_id)), ) self.context.outbox.put_message(message=ledger_api_msg) self._register_agent() def act(self) -> None: """Implement the act.""" self._retry_failed_registration() def teardown(self) -> None: """Implement the task teardown.""" self._unregister_service() self._unregister_agent() def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """Register the agent's service.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_register_service_description() self._register(description, "registering agent's service on the SOEF.") def register_genus(self) -> None: """Register the agent's personality genus.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """Unregister service from the SOEF.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering service from SOEF.") def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") ``` This `TickerBehaviour` registers (see `setup` method) and de-registers (see `teardown` method) our AEA’s service on the SOEF search node at regular tick intervals (here 60 seconds). By registering, the AEA becomes discoverable to other AEAs. In `setup`, prior to registrations, we send a message to the ledger connection to check the account balance for the AEA's address on the configured ledger. ### Step 3: Create the Handler So far, we have tasked the AEA with sending register/unregister requests to the SOEF search node. However at present, the AEA has no way of handling the responses it receives from the search node, or in fact messages sent by any other AEA. We have to specify the logic to negotiate with another AEA based on the strategy we want our AEA to follow. The following diagram illustrates the negotiation flow that we want this AEA to use, as well as interactions with a search node and the blockchain between a `seller_AEA` and a `buyer_AEA`. ``` mermaid sequenceDiagram participant Search participant Buyer_AEA participant Seller_AEA participant Blockchain activate Buyer_AEA activate Search activate Seller_AEA activate Blockchain Seller_AEA->>Search: register_service Buyer_AEA->>Search: search Search-->>Buyer_AEA: list_of_agents Buyer_AEA->>Seller_AEA: call_for_proposal Seller_AEA->>Buyer_AEA: propose Buyer_AEA->>Seller_AEA: accept Seller_AEA->>Buyer_AEA: match_accept loop Once with LedgerConnection Buyer_AEA->>Buyer_AEA: Get raw transaction from ledger api end loop Once with DecisionMaker Buyer_AEA->>Buyer_AEA: Get signed transaction from decision maker end loop Once with LedgerConnection Buyer_AEA->>Buyer_AEA: Send transaction and get digest from ledger api Buyer_AEA->>Blockchain: transfer_funds end Buyer_AEA->>Seller_AEA: send_transaction_digest Seller_AEA->>Blockchain: check_transaction_status Seller_AEA->>Buyer_AEA: send_data deactivate Buyer_AEA deactivate Search deactivate Seller_AEA deactivate Blockchain ``` In our case, `my_generic_seller` is the `Seller_AEA` in the above figure. Let us now implement a `Handler` to deal with incoming messages. Open the `handlers.py` file (`my_generic_seller/skills/generic_seller/handlers.py`) and replace the stub code with the following: ``` python from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import TransactionDigest from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) from packages.fetchai.skills.generic_seller.dialogues import ( DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.generic_seller.strategy import GenericStrategy LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericFipaHandler(Handler): """This class implements a FIPA handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ fipa_msg = cast(FipaMessage, message) # recover dialogue fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return # handle message if fipa_msg.performative == FipaMessage.Performative.CFP: self._handle_cfp(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.DECLINE: self._handle_decline(fipa_msg, fipa_dialogue, fipa_dialogues) elif fipa_msg.performative == FipaMessage.Performative.ACCEPT: self._handle_accept(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.INFORM: self._handle_inform(fipa_msg, fipa_dialogue) else: self._handle_invalid(fipa_msg, fipa_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" ``` The code above contains the logic for handling `FipaMessages` received by the `my_generic_seller` AEA. We use `FipaDialogues` (more on this below) to keep track of the progress of the negotiation dialogue between the `my_generic_seller` AEA and the `my_generic_buyer` AEA. In the above `handle` method, we first check if a received message belongs to an existing dialogue or if we have to create a new dialogue (the `recover dialogue` part). Once this is done, we break down the AEA's response to each type of negotiation message, as indicated by the message's performative (the `handle message` part). Therefore, we implement the AEA's response to each negotiation message type in a different handler function. Below the unused `teardown` function, we continue by adding the following function: ``` python def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. :param fipa_msg: the message """ self.context.logger.info( "received invalid fipa message={}, unidentified dialogue.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=fipa_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": fipa_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) ``` The above code handles an unidentified dialogue by responding to the sender with a `DefaultMessage` containing the appropriate error information. The next code block handles `CFP` (call-for-proposal) negotiation messages. Paste the following code below the `_handle_unidentified_dialogue` function: ``` python def _handle_cfp(self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle the CFP. If the CFP matches the supplied services then send a PROPOSE, otherwise send a DECLINE. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received CFP from sender={}".format(fipa_msg.sender[-5:]) ) strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_matching_supply(fipa_msg.query): proposal, terms, data_for_sale = strategy.generate_proposal_terms_and_data( fipa_msg.query, fipa_msg.sender ) fipa_dialogue.data_for_sale = data_for_sale fipa_dialogue.terms = terms self.context.logger.info( "sending a PROPOSE with proposal={} to sender={}".format( proposal.values, fipa_msg.sender[-5:] ) ) proposal_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.PROPOSE, target_message=fipa_msg, proposal=proposal, ) self.context.outbox.put_message(message=proposal_msg) else: self.context.logger.info( "declined the CFP from sender={}".format(fipa_msg.sender[-5:]) ) decline_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=fipa_msg, ) self.context.outbox.put_message(message=decline_msg) ``` The above code sends a `PROPOSE` message back to the buyer as a response to its `CFP` if the requested services match our seller agent's supplied services, otherwise it will respond with a `DECLINE` message. The next code-block handles the decline message we receive from the buyer. Add the following code below the `_handle_cfp`function: ``` python def _handle_decline( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue, fipa_dialogues: FipaDialogues, ) -> None: """ Handle the DECLINE. Close the dialogue. :param fipa_msg: the message :param fipa_dialogue: the dialogue object :param fipa_dialogues: the dialogues object """ self.context.logger.info( "received DECLINE from sender={}".format(fipa_msg.sender[-5:]) ) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) ``` If we receive a decline message from the buyer we close the dialogue and terminate this conversation with `my_generic_buyer`. Alternatively, we might receive an `ACCEPT` message. In order to handle this option add the following code below the `_handle_decline` function: ``` python def _handle_accept( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the ACCEPT. Respond with a MATCH_ACCEPT_W_INFORM which contains the address to send the funds to. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received ACCEPT from sender={}".format(fipa_msg.sender[-5:]) ) info = {"address": fipa_dialogue.terms.sender_address} match_accept_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, target_message=fipa_msg, info=info, ) self.context.logger.info( "sending MATCH_ACCEPT_W_INFORM to sender={} with info={}".format( fipa_msg.sender[-5:], info, ) ) self.context.outbox.put_message(message=match_accept_msg) ``` When `my_generic_buyer` accepts the `Proposal` we send it and sends an `ACCEPT` message, we have to respond with another message (`MATCH_ACCEPT_W_INFORM`) to match the acceptance of the terms of trade and to inform the buyer of the address we would like it to send the funds to. Lastly, we must handle an `INFORM` message, which the buyer uses to inform us that it has indeed sent the funds to the provided address. Add the following code at the end of the file: ``` python def _handle_inform( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the INFORM. If the INFORM message contains the transaction_digest then verify that it is settled, otherwise do nothing. If the transaction is settled, send the data, otherwise do nothing. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received INFORM from sender={}".format(fipa_msg.sender[-5:]) ) strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx and "transaction_digest" in fipa_msg.info.keys(): self.context.logger.info( "checking whether transaction={} has been received ...".format( fipa_msg.info["transaction_digest"] ) ) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, transaction_digest=TransactionDigest( fipa_dialogue.terms.ledger_id, fipa_msg.info["transaction_digest"] ), ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.outbox.put_message(message=ledger_api_msg) elif strategy.is_ledger_tx: self.context.logger.warning( "did not receive transaction digest from sender={}.".format( fipa_msg.sender[-5:] ) ) elif not strategy.is_ledger_tx and "Done" in fipa_msg.info.keys(): inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=fipa_msg, info=fipa_dialogue.data_for_sale, ) self.context.outbox.put_message(message=inform_msg) fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.SUCCESSFUL, fipa_dialogue.is_self_initiated ) self.context.logger.info( "transaction confirmed, sending data={} to buyer={}.".format( fipa_dialogue.data_for_sale, fipa_msg.sender[-5:], ) ) else: self.context.logger.warning( "did not receive transaction confirmation from sender={}.".format( fipa_msg.sender[-5:] ) ) ``` In the above code, we check the `INFORM` message. If it contains a transaction digest, then we verify that the transaction matches the proposal the buyer accepted. If the transaction is valid and we received the funds, then we send the data to the buyer. Otherwise, we do not send the data. The remaining handlers are as follows: ``` python def _handle_invalid( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle a fipa message of invalid performative. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle fipa message of performative={} in dialogue={}.".format( fipa_msg.performative, fipa_dialogue ) ) class GenericLedgerApiHandler(Handler): """Implement the ledger handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ fipa_dialogue = ledger_api_dialogue.associated_fipa_dialogue is_settled = LedgerApis.is_transaction_settled( fipa_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.receipt ) is_valid = LedgerApis.is_transaction_valid( fipa_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.transaction, fipa_dialogue.terms.sender_address, fipa_dialogue.terms.counterparty_address, fipa_dialogue.terms.nonce, fipa_dialogue.terms.counterparty_payable_amount, ) if is_settled and is_valid: last_message = cast( Optional[FipaMessage], fipa_dialogue.last_incoming_message ) if last_message is None: raise ValueError("Cannot retrieve last fipa message.") inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=last_message, info=fipa_dialogue.data_for_sale, ) self.context.outbox.put_message(message=inform_msg) fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.SUCCESSFUL, fipa_dialogue.is_self_initiated ) self.context.logger.info( "transaction confirmed, sending data={} to buyer={}.".format( fipa_dialogue.data_for_sale, last_message.sender[-5:], ) ) else: self.context.logger.info( "transaction_receipt={} not settled or not valid, aborting".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class GenericOefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( GenericServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( GenericServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) registration_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ``` The `GenericLedgerApiHandler` deals with `LedgerApiMessages` from the ledger connection and the `GenericOefSearchHandler` handles `OefSearchMessages` from the SOEF connection. ### Step 4: Create the Strategy Next, we are going to create the strategy that we want our `my_generic_seller` AEA to follow. Rename the `my_model.py` file (`my_generic_seller/skills/generic_seller/my_model.py`) to `strategy.py` and replace the stub code with the following: ``` python import uuid from typing import Any, Dict, Optional, Tuple from aea.common import Address from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, SIMPLE_SERVICE_MODEL, ) from aea.helpers.search.models import Description, Location, Query from aea.helpers.transaction.base import Terms from aea.skills.base import Model DEFAULT_IS_LEDGER_TX = True DEFAULT_UNIT_PRICE = 4 DEFAULT_SERVICE_ID = "generic_service" DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_DATA = {"key": "seller_service", "value": "generic_service"} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "seller"} DEFAULT_HAS_DATA_SOURCE = False DEFAULT_DATA_FOR_SALE = { "some_generic_data_key": "some_generic_data_value" } # type: Optional[Dict[str, Any]] class GenericStrategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ ledger_id = kwargs.pop("ledger_id", None) currency_id = kwargs.pop("currency_id", None) self._is_ledger_tx = kwargs.pop("is_ledger_tx", DEFAULT_IS_LEDGER_TX) self._unit_price = kwargs.pop("unit_price", DEFAULT_UNIT_PRICE) self._service_id = kwargs.pop("service_id", DEFAULT_SERVICE_ID) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} self._simple_service_data = { self._set_service_data["key"]: self._set_service_data["value"] } self._has_data_source = kwargs.pop("has_data_source", DEFAULT_HAS_DATA_SOURCE) data_for_sale_ordered = kwargs.pop("data_for_sale", DEFAULT_DATA_FOR_SALE) data_for_sale = { str(key): str(value) for key, value in data_for_sale_ordered.items() } super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) if currency_id is None: currency_id = self.context.currency_denominations.get(self._ledger_id, None) enforce( currency_id is not None, f"Currency denomination for ledger_id={self._ledger_id} not specified.", ) self._currency_id = currency_id enforce( self.context.agent_addresses.get(self._ledger_id, None) is not None, "Wallet does not contain cryptos for provided ledger id.", ) self._data_for_sale = data_for_sale ``` In the above code snippet, we initialise the strategy class by trying to read the variables specific to the strategy from a YAML configuration file. If any variable is not provided, some default values will be used. The following properties and methods deal with different aspects of the strategy. They should be relatively self-descriptive. Add them under the initialization of the strategy class: ``` python @property def data_for_sale(self) -> Dict[str, str]: """Get the data for sale.""" if self._has_data_source: return self.collect_from_data_source() # pragma: nocover return self._data_for_sale @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def is_ledger_tx(self) -> bool: """Check whether or not tx are settled on a ledger.""" return self._is_ledger_tx def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_service_description(self) -> Description: """ Get the simple service description. :return: a description of the offered services """ description = Description( self._simple_service_data, data_model=SIMPLE_SERVICE_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description def is_matching_supply(self, query: Query) -> bool: """ Check if the query matches the supply. :param query: the query :return: bool indicating whether matches or not """ return query.check(self.get_service_description()) def generate_proposal_terms_and_data( # pylint: disable=unused-argument self, query: Query, counterparty_address: Address ) -> Tuple[Description, Terms, Dict[str, str]]: """ Generate a proposal matching the query. :param query: the query :param counterparty_address: the counterparty of the proposal. :return: a tuple of proposal, terms and the weather data """ data_for_sale = self.data_for_sale sale_quantity = len(data_for_sale) seller_address = self.context.agent_addresses[self.ledger_id] total_price = sale_quantity * self._unit_price if self.is_ledger_tx: tx_nonce = LedgerApis.generate_tx_nonce( identifier=self.ledger_id, seller=seller_address, client=counterparty_address, ) else: tx_nonce = uuid.uuid4().hex # pragma: nocover proposal = Description( { "ledger_id": self.ledger_id, "price": total_price, "currency_id": self._currency_id, "service_id": self._service_id, "quantity": sale_quantity, "tx_nonce": tx_nonce, } ) terms = Terms( ledger_id=self.ledger_id, sender_address=seller_address, counterparty_address=counterparty_address, amount_by_currency_id={self._currency_id: total_price}, quantities_by_good_id={self._service_id: -sale_quantity}, is_sender_payable_tx_fee=False, nonce=tx_nonce, fee_by_currency_id={self._currency_id: 0}, ) return proposal, terms, data_for_sale def collect_from_data_source(self) -> Dict[str, str]: """Implement the logic to communicate with the sensor.""" raise NotImplementedError ``` The helper private function `collect_from_data_source` is where we read data from a sensor or if there are no sensor we use some default data provided (see the `data_for_sale` property). ### Step 5: Create the Dialogues To keep track of the structure and progress of interactions, including negotiations with a buyer AEA and interactions with search nodes and ledgers, we use dialogues. Create a new file in the skill folder (`my_generic_seller/skills/generic_seller/`) and name it `dialogues.py`. Inside this file add the following code: ``` python from typing import Any, Dict, Optional, Type from aea.common import Address from aea.exceptions import AEAEnforceError, enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class FipaDialogue(BaseFipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("data_for_sale", "_terms") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseFipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self.data_for_sale = None # type: Optional[Dict[str, str]] self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get terms.""" if self._terms is None: raise AEAEnforceError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.SELLER BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_fipa_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_fipa_dialogue = None # type: Optional[FipaDialogue] @property def associated_fipa_dialogue(self) -> FipaDialogue: """Get associated_fipa_dialogue.""" if self._associated_fipa_dialogue is None: raise AEAEnforceError("FipaDialogue not set!") return self._associated_fipa_dialogue @associated_fipa_dialogue.setter def associated_fipa_dialogue(self, fipa_dialogue: FipaDialogue) -> None: """Set associated_fipa_dialogue""" enforce(self._associated_fipa_dialogue is None, "FipaDialogue already set!") self._associated_fipa_dialogue = fipa_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ``` The `FipaDialogues` class contains negotiation dialogues with each `my_generic_buyer` AEA (and other AEAs) and exposes a number of helpful methods to manage them. This helps us match messages to the dialogues they belong to, access previous messages and enable us to identify possible communications problems between the `my_generic_seller` AEA and the `my_generic_buyer` AEA. It also keeps track of the data that we offer for sale during the proposal phase. The `FipaDialogues` class extends `BaseFipaDialogues`, which itself derives from the base `Dialogues` class. Similarly, the `FipaDialogue` class extends `BaseFipaDialogue` which itself derives from the base `Dialogue` class. To learn more about dialogues have a look here. ### Step 6: Update the YAML Files Since we made so many changes to our AEA we have to update the `skill.yaml` (at `my_generic_seller/skills/generic_seller/skill.yaml`). Make sure you update your `skill.yaml` with the following configuration: ``` yaml name: generic_seller author: fetchai version: 0.1.0 type: skill description: The weather station skill implements the functionality to sell weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmPb5kHYZyhUN87EKmuahyGqDGgqVdGPyfC1KpGC3xfmcP __init__.py: QmTSEedzQySy2nzRCY3F66CBSX52f8s3pWHZTejX4hKC9h behaviours.py: QmS9sPCv2yBnhWsmHeaCptpApMtYZipbR39TXixeGK64Ks dialogues.py: QmdTW8q1xQ7ajFVsWmuV62ypoT5J2b6Hkyz52LFaWuMEtd handlers.py: QmQnQhSaHPUYaJut8bMe2LHEqiZqokMSVfCthVaqrvPbdi strategy.py: QmYTUsfv64eRQDevCfMUDQPx2GCtiMLFdacN4sS1E4Fdfx fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 skills: [] behaviours: service_registration: args: services_interval: 20 class_name: GenericServiceRegistrationBehaviour handlers: fipa: args: {} class_name: GenericFipaHandler ledger_api: args: {} class_name: GenericLedgerApiHandler oef_search: args: {} class_name: GenericOefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: data_for_sale: generic: data has_data_source: false is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: generic_service service_id: generic_service unit_price: 10 class_name: GenericStrategy is_abstract: false dependencies: {} ``` We must pay attention to the models and in particular the strategy’s variables. Here we can change the price we would like to sell each data reading for, or the currency we would like to transact with. Lastly, the dependencies are the third party packages we need to install in order to get readings from the sensor. Finally, we fingerprint our new skill: ``` bash aea fingerprint skill fetchai/generic_seller:0.1.0 ``` This will hash each file and save the hash in the fingerprint. This way, in the future we can easily track if any of the files have changed. ## Generic Buyer AEA ### Step 1: Create the AEA In a new terminal, create a new AEA by typing the following command in the terminal: ``` bash aea create my_generic_buyer cd my_generic_buyer aea install ``` Our newly created AEA is inside the current working directory. Let’s create a new skill for purchasing data. Type the following command: ``` bash aea scaffold skill generic_buyer ``` This command creates the correct structure for a new skill inside our AEA project. You can locate the newly created skill under the skills folder (`my_generic_buyer/skills/generic_buyer/`) and it must contain the following files: - `__init__.py` - `behaviours.py` - `handlers.py` - `my_model.py` - `skills.yaml` ### Step 2: Create the Behaviour Open the `behaviours.py` file (`my_generic_buyer/skills/generic_buyer/behaviours.py`) and replace the stub code with the following: ``` python from typing import Any, List, Optional, Set, cast from aea.protocols.dialogue.base import DialogueLabel from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogue, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy DEFAULT_MAX_PROCESSING = 120 DEFAULT_TX_INTERVAL = 2.0 DEFAULT_SEARCH_INTERVAL = 5.0 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericSearchBehaviour(TickerBehaviour): """This class implements a search behaviour.""" def __init__(self, **kwargs: Any): """Initialize the search behaviour.""" search_interval = cast( float, kwargs.pop("search_interval", DEFAULT_SEARCH_INTERVAL) ) super().__init__(tick_interval=search_interval, **kwargs) def setup(self) -> None: """Implement the setup for the behaviour.""" strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx: ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=strategy.ledger_id, address=cast(str, self.context.agent_addresses.get(strategy.ledger_id)), ) self.context.outbox.put_message(message=ledger_api_msg) else: strategy.is_searching = True def act(self) -> None: """Implement the act.""" strategy = cast(GenericStrategy, self.context.strategy) if not strategy.is_searching: return transaction_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) remaining_transactions_count = len(transaction_behaviour.waiting) if remaining_transactions_count > 0: self.context.logger.info( f"Transaction behaviour has {remaining_transactions_count} transactions remaining. Skipping search!" ) return strategy.update_search_query_params() query = strategy.get_location_and_service_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) self.context.outbox.put_message(message=oef_search_msg) def teardown(self) -> None: """Implement the task teardown.""" class GenericTransactionBehaviour(TickerBehaviour): """A behaviour to sequentially submit transactions to the blockchain.""" def __init__(self, **kwargs: Any): """Initialize the transaction behaviour.""" tx_interval = cast( float, kwargs.pop("transaction_interval", DEFAULT_TX_INTERVAL) ) self.max_processing = cast( float, kwargs.pop("max_processing", DEFAULT_MAX_PROCESSING) ) self.processing_time = 0.0 self.waiting: List[FipaDialogue] = [] self.processing: Optional[LedgerApiDialogue] = None self.timedout: Set[DialogueLabel] = set() super().__init__(tick_interval=tx_interval, **kwargs) def setup(self) -> None: """Setup behaviour.""" def act(self) -> None: """Implement the act.""" if self.processing is not None: if self.processing_time <= self.max_processing: # already processing self.processing_time += self.tick_interval return self._timeout_processing() if len(self.waiting) == 0: # nothing to process return self._start_processing() def _start_processing(self) -> None: """Process the next transaction.""" fipa_dialogue = self.waiting.pop(0) self.context.logger.info( f"Processing transaction, {len(self.waiting)} transactions remaining" ) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, terms=fipa_dialogue.terms, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.processing_time = 0.0 self.processing = ledger_api_dialogue self.context.logger.info( f"requesting transfer transaction from ledger api for message={ledger_api_msg}..." ) self.context.outbox.put_message(message=ledger_api_msg) def teardown(self) -> None: """Teardown behaviour.""" def _timeout_processing(self) -> None: """Timeout processing.""" if self.processing is None: return self.timedout.add(self.processing.dialogue_label) self.waiting.append(self.processing.associated_fipa_dialogue) self.processing_time = 0.0 self.processing = None def finish_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Finish processing. :param ledger_api_dialogue: the ledger api dialogue """ if self.processing == ledger_api_dialogue: self.processing_time = 0.0 self.processing = None return if ledger_api_dialogue.dialogue_label not in self.timedout: raise ValueError( f"Non-matching dialogues in transaction behaviour: {self.processing} and {ledger_api_dialogue}" ) self.timedout.remove(ledger_api_dialogue.dialogue_label) self.context.logger.debug( f"Timeout dialogue in transaction processing: {ledger_api_dialogue}" ) # don't reset, as another might be processing def failed_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Failed processing. Currently, we retry processing indefinitely. :param ledger_api_dialogue: the ledger api dialogue """ self.finish_processing(ledger_api_dialogue) self.waiting.append(ledger_api_dialogue.associated_fipa_dialogue) ``` This `TickerBehaviour` will send a search query to the SOEF search node at regular tick intervals. ### Step 3: Create the Handler So far, the AEA is tasked with sending search queries to the SOEF search node. However, currently the AEA has no way of handling the responses it receives from the SOEF or messages from other agents. Let us now implement `Handlers` to deal with the expected incoming messages. Open the `handlers.py` file (`my_generic_buyer/skills/generic_buyer/handlers.py`) and add the following code (replacing the stub code already present in the file): ``` python import pprint from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.generic_buyer.behaviours import GenericTransactionBehaviour from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericFipaHandler(Handler): """This class implements a FIPA handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ fipa_msg = cast(FipaMessage, message) # recover dialogue fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return # handle message if fipa_msg.performative == FipaMessage.Performative.PROPOSE: self._handle_propose(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.DECLINE: self._handle_decline(fipa_msg, fipa_dialogue, fipa_dialogues) elif fipa_msg.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM: self._handle_match_accept(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.INFORM: self._handle_inform(fipa_msg, fipa_dialogue, fipa_dialogues) else: self._handle_invalid(fipa_msg, fipa_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" ``` You will see that we are following similar logic to the `generic_seller` when we develop the `generic_buyer`’s side of the negotiation. First, we create a new dialogue and store it in the dialogues class. Then we are checking what kind of message we received by checking its performative. So lets start creating our handlers: ``` python def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. :param fipa_msg: the message """ self.context.logger.info( "received invalid fipa message={}, unidentified dialogue.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=fipa_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": fipa_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) ``` The above code handles messages referencing unidentified dialogues and responds with an error message to the sender. Next we will handle the `PROPOSE` message received from the `my_generic_seller` AEA: ``` python def _handle_propose( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the propose. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received proposal={} from sender={}".format( fipa_msg.proposal.values, fipa_msg.sender[-5:], ) ) strategy = cast(GenericStrategy, self.context.strategy) acceptable = strategy.is_acceptable_proposal(fipa_msg.proposal) affordable = strategy.is_affordable_proposal(fipa_msg.proposal) if acceptable and affordable: self.context.logger.info( "accepting the proposal from sender={}".format(fipa_msg.sender[-5:]) ) terms = strategy.terms_from_proposal(fipa_msg.proposal, fipa_msg.sender) fipa_dialogue.terms = terms accept_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.ACCEPT, target_message=fipa_msg, ) self.context.outbox.put_message(message=accept_msg) else: self.context.logger.info( "declining the proposal from sender={}".format(fipa_msg.sender[-5:]) ) decline_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=fipa_msg, ) self.context.outbox.put_message(message=decline_msg) ``` When we receive a proposal, we have to check if we have the funds to complete the transaction and if the proposal is acceptable based on our strategy. If the proposal is not affordable or acceptable, we respond with a `DECLINE` message. Otherwise, we send an `ACCEPT` message to the seller. The next code-block handles the `DECLINE` message that we may receive from the seller as a response to our `CFP` or `ACCEPT` messages: ``` python def _handle_decline( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue, fipa_dialogues: FipaDialogues, ) -> None: """ Handle the decline. :param fipa_msg: the message :param fipa_dialogue: the fipa dialogue :param fipa_dialogues: the fipa dialogues """ self.context.logger.info( "received DECLINE from sender={}".format(fipa_msg.sender[-5:]) ) target_message = fipa_dialogue.get_message_by_id(fipa_msg.target) if not target_message: raise ValueError("Can not find target message!") # pragma: nocover declined_performative = target_message.performative if declined_performative == FipaMessage.Performative.CFP: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_CFP, fipa_dialogue.is_self_initiated ) if declined_performative == FipaMessage.Performative.ACCEPT: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) ``` The above code terminates each dialogue with the specific AEA and stores the state of the terminated dialogue (whether it was terminated after a `CFP` or an `ACCEPT`). If `my_generic_seller` AEA wants to move on with the sale, it will send a `MATCH_ACCEPT` message. In order to handle this we add the following code: ``` python def _handle_match_accept( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the match accept. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received MATCH_ACCEPT_W_INFORM from sender={} with info={}".format( fipa_msg.sender[-5:], fipa_msg.info ) ) strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx: transfer_address = fipa_msg.info.get("address", None) if transfer_address is not None and isinstance(transfer_address, str): fipa_dialogue.terms.counterparty_address = ( # pragma: nocover transfer_address ) tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.waiting.append(fipa_dialogue) else: inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=fipa_msg, info={"Done": "Sending payment via bank transfer"}, ) self.context.outbox.put_message(message=inform_msg) self.context.logger.info( "informing counterparty={} of payment.".format(fipa_msg.sender[-5:]) ) ``` The first thing we are checking is if we enabled our AEA to transact with a ledger. If so, we add this negotiation to the queue of transactions to be processed. If not, we simulate non-ledger payment by sending an `inform` to the seller that the payment is done (say via bank transfer). Lastly, we need to handle `INFORM` messages. This is the message that will have our data: ``` python def _handle_inform( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue, fipa_dialogues: FipaDialogues, ) -> None: """ Handle the match inform. :param fipa_msg: the message :param fipa_dialogue: the fipa dialogue :param fipa_dialogues: the fipa dialogues """ self.context.logger.info( "received INFORM from sender={}".format(fipa_msg.sender[-5:]) ) if len(fipa_msg.info.keys()) >= 1: data = fipa_msg.info data_string = pprint.pformat(data)[:1000] self.context.logger.info(f"received the following data={data_string}") fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.SUCCESSFUL, fipa_dialogue.is_self_initiated ) strategy = cast(GenericStrategy, self.context.strategy) strategy.successful_trade_with_counterparty(fipa_msg.sender, data) else: self.context.logger.info( "received no data from sender={}".format(fipa_msg.sender[-5:]) ) def _handle_invalid( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle a fipa message of invalid performative. :param fipa_msg: the message :param fipa_dialogue: the fipa dialogue """ self.context.logger.warning( "cannot handle fipa message of performative={} in dialogue={}.".format( fipa_msg.performative, fipa_dialogue ) ) ``` We now need to add handlers for messages received from the `DecisionMaker` and the SOEF search node. We need one handler for each type of protocol we use. To handle the messages in the `oef_search` protocol used by the SOEF search node we add the following code in the same file (`my_generic_buyer/skills/generic_buyer/handlers.py`): ``` python class GenericOefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_msg, oef_search_dialogue ) ) def _handle_search( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle the search response. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ if len(oef_search_msg.agents) == 0: self.context.logger.info( f"found no agents in dialogue={oef_search_dialogue}, continue searching." ) return strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_stop_searching_on_result: self.context.logger.info( "found agents={}, stopping search.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) strategy.is_searching = False # stopping search else: self.context.logger.info( "found agents={}.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) query = strategy.get_service_query() fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) counterparties = strategy.get_acceptable_counterparties(oef_search_msg.agents) for counterparty in counterparties: cfp_msg, _ = fipa_dialogues.create( counterparty=counterparty, performative=FipaMessage.Performative.CFP, query=query, ) self.context.outbox.put_message(message=cfp_msg) self.context.logger.info( "sending CFP to agent={}".format(counterparty[-5:]) ) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ``` When we receive a message from the SOEF search node of a type `OefSearchMessage.Performative.SEARCH_RESULT`, we are passing the details to the relevant handler method. In the `_handle_search` function, we are checking that the response contains some agents, and we stop the search if it does. We pick our first agent and send a `CFP` message. The last handlers we need are the `GenericSigningHandler` and the `GenericLedgerApiHandler`. These handlers are responsible for `SigningMessages` that we receive from the `DecisionMaker`, and `LedgerApiMessages` that we receive from the ledger connection, respectively. ``` python class GenericSigningHandler(Handler): """Implement the signing handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue last_ledger_api_msg = ledger_api_dialogue.last_incoming_message if last_ledger_api_msg is None: raise ValueError("Could not retrieve last message in ledger api dialogue") ledger_api_msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, target_message=last_ledger_api_msg, signed_transaction=signing_msg.signed_transaction, ) self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) signing_msg_ = cast( Optional[SigningMessage], signing_dialogue.last_outgoing_message ) if ( signing_msg_ is not None and signing_msg_.performative == SigningMessage.Performative.SIGN_TRANSACTION ): tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) class GenericLedgerApiHandler(Handler): """Implement the ledger handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ strategy = cast(GenericStrategy, self.context.strategy) if ledger_api_msg.balance > 0: self.context.logger.info( "starting balance on {} ledger={}.".format( strategy.ledger_id, ledger_api_msg.balance, ) ) strategy.balance = ledger_api_msg.balance strategy.is_searching = True else: self.context.logger.warning( f"you have no starting balance on {strategy.ledger_id} ledger! Stopping skill {self.skill_id}." ) self.context.is_active = False def _handle_raw_transaction( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of raw_transaction performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(ledger_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=ledger_api_msg.raw_transaction, terms=ledger_api_dialogue.associated_fipa_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) ledger_api_msg_ = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.logger.info("checking transaction is settled.") self.context.outbox.put_message(message=ledger_api_msg_) def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ fipa_dialogue = ledger_api_dialogue.associated_fipa_dialogue is_settled = LedgerApis.is_transaction_settled( fipa_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.receipt ) tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) if is_settled: tx_behaviour.finish_processing(ledger_api_dialogue) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ledger_api_msg_ is None: raise ValueError( # pragma: nocover "Could not retrieve last ledger_api message" ) fipa_msg = cast(Optional[FipaMessage], fipa_dialogue.last_incoming_message) if fipa_msg is None: raise ValueError("Could not retrieve last fipa message") inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=fipa_msg, info={"transaction_digest": ledger_api_msg_.transaction_digest.body}, ) self.context.outbox.put_message(message=inform_msg) self.context.logger.info( "transaction confirmed, informing counterparty={} of transaction digest.".format( fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], ) ) else: tx_behaviour.failed_processing(ledger_api_dialogue) self.context.logger.info( "transaction_receipt={} not settled or not valid, aborting".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ( ledger_api_msg_ is not None and ledger_api_msg_.performative != LedgerApiMessage.Performative.GET_BALANCE ): tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) ``` ### Step 4: Create the Strategy We are going to create the strategy that we want our AEA to follow. Rename the `my_model.py` file (in `my_generic_buyer/skills/generic_buyer/`) to `strategy.py` and replace the stub code with the following: ``` python from typing import Any, Dict, List, Tuple from aea.common import Address from aea.exceptions import enforce from aea.helpers.search.generic import SIMPLE_SERVICE_MODEL from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.helpers.transaction.base import Terms from aea.skills.base import Model DEFAULT_IS_LEDGER_TX = True DEFAULT_MAX_UNIT_PRICE = 5 DEFAULT_MAX_TX_FEE = 2 DEFAULT_SERVICE_ID = "generic_service" DEFAULT_MIN_QUANTITY = 1 DEFAULT_MAX_QUANTITY = 100 DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "seller_service", "search_value": "generic_service", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 DEFAULT_MAX_NEGOTIATIONS = 2 class GenericStrategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ ledger_id = kwargs.pop("ledger_id", None) currency_id = kwargs.pop("currency_id", None) self._is_ledger_tx = kwargs.pop("is_ledger_tx", DEFAULT_IS_LEDGER_TX) self._max_unit_price = kwargs.pop("max_unit_price", DEFAULT_MAX_UNIT_PRICE) self._min_quantity = kwargs.pop("min_quantity", DEFAULT_MIN_QUANTITY) self._max_quantity = kwargs.pop("max_quantity", DEFAULT_MAX_QUANTITY) self._max_tx_fee = kwargs.pop("max_tx_fee", DEFAULT_MAX_TX_FEE) self._service_id = kwargs.pop("service_id", DEFAULT_SERVICE_ID) self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self._max_negotiations = kwargs.pop( "max_negotiations", DEFAULT_MAX_NEGOTIATIONS ) self._is_stop_searching_on_result = kwargs.pop("stop_searching_on_result", True) super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) if currency_id is None: currency_id = self.context.currency_denominations.get(self._ledger_id, None) enforce( currency_id is not None, f"Currency denomination for ledger_id={self._ledger_id} not specified.", ) self._currency_id = currency_id self._is_searching = False self._balance = 0 ``` Similar to the seller AEA, we initialize the strategy class by trying to read the strategy variables from the YAML file, and if not possible, use some default values. In the following snippet, the two methods after the properties are related to the OEF search service. Add this snippet under the initialization of the strategy class: ``` python @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def is_ledger_tx(self) -> bool: """Check whether or not tx are settled on a ledger.""" return self._is_ledger_tx @property def is_stop_searching_on_result(self) -> bool: """Check if search is stopped on result.""" return self._is_stop_searching_on_result @property def is_searching(self) -> bool: """Check if the agent is searching.""" return self._is_searching @is_searching.setter def is_searching(self, is_searching: bool) -> None: """Check if the agent is searching.""" enforce(isinstance(is_searching, bool), "Can only set bool on is_searching!") self._is_searching = is_searching @property def balance(self) -> int: """Get the balance.""" return self._balance @balance.setter def balance(self, balance: int) -> None: """Set the balance.""" self._balance = balance @property def max_negotiations(self) -> int: """Get the maximum number of negotiations the agent can start.""" return self._max_negotiations def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType("distance", (self._agent_location, self._radius)) ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query def get_service_query(self) -> Query: """ Get the service query of the agent. :return: the query """ service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query([service_key_filter], model=SIMPLE_SERVICE_MODEL) return query ``` The following code block checks if the proposal that we received is acceptable according to a strategy: ``` python def is_acceptable_proposal(self, proposal: Description) -> bool: """ Check whether it is an acceptable proposal. :param proposal: a description :return: whether it is acceptable """ result = ( all( key in proposal.values for key in [ "ledger_id", "currency_id", "price", "service_id", "quantity", "tx_nonce", ] ) and proposal.values["ledger_id"] == self.ledger_id and proposal.values["price"] > 0 and proposal.values["quantity"] >= self._min_quantity and proposal.values["quantity"] <= self._max_quantity and proposal.values["price"] <= proposal.values["quantity"] * self._max_unit_price and proposal.values["currency_id"] == self._currency_id and proposal.values["service_id"] == self._service_id and isinstance(proposal.values["tx_nonce"], str) and proposal.values["tx_nonce"] != "" ) return result ``` The `is_affordable_proposal` method in the following code block checks if we can afford the transaction based on the funds we have in our wallet on the ledger. The rest of the methods are self-explanatory. ``` python def is_affordable_proposal(self, proposal: Description) -> bool: """ Check whether it is an affordable proposal. :param proposal: a description :return: whether it is affordable """ if self.is_ledger_tx: payable = proposal.values.get("price", 0) + self._max_tx_fee result = self.balance >= payable else: result = True return result def get_acceptable_counterparties( self, counterparties: Tuple[str, ...] ) -> Tuple[str, ...]: """ Process counterparties and drop unacceptable ones. :param counterparties: a tuple of counterparties :return: list of counterparties """ valid_counterparties: List[str] = [] for idx, counterparty in enumerate(counterparties): if idx < self.max_negotiations: valid_counterparties.append(counterparty) return tuple(valid_counterparties) def terms_from_proposal( self, proposal: Description, counterparty_address: Address ) -> Terms: """ Get the terms from a proposal. :param proposal: the proposal :param counterparty_address: the counterparty :return: terms """ buyer_address = self.context.agent_addresses[proposal.values["ledger_id"]] terms = Terms( ledger_id=proposal.values["ledger_id"], sender_address=buyer_address, counterparty_address=counterparty_address, amount_by_currency_id={ proposal.values["currency_id"]: -proposal.values["price"] }, quantities_by_good_id={ proposal.values["service_id"]: proposal.values["quantity"] }, is_sender_payable_tx_fee=True, nonce=proposal.values["tx_nonce"], fee_by_currency_id={proposal.values["currency_id"]: self._max_tx_fee}, ) return terms def successful_trade_with_counterparty( self, counterparty: str, data: Dict[str, str] ) -> None: """ Do something on successful trade. :param counterparty: the counterparty address :param data: the data """ def update_search_query_params(self) -> None: """Update agent location and query for search.""" ``` ### Step 5: Create the Dialogues As mentioned during the creation of the seller AEA, we should keep track of the various interactions an AEA has with others and this is done via dialogues. Create a new file and name it `dialogues.py` (in `my_generic_buyer/skills/generic_buyer/`). Inside this file add the following code: ``` python from typing import Any, Optional, Type from aea.common import Address from aea.exceptions import AEAEnforceError, enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class FipaDialogue(BaseFipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ( "_terms", "_associated_ledger_api_dialogue", ) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseFipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get terms.""" if self._terms is None: raise AEAEnforceError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseFipaDialogue.Role.BUYER BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_fipa_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_fipa_dialogue = None # type: Optional[FipaDialogue] @property def associated_fipa_dialogue(self) -> FipaDialogue: """Get associated_fipa_dialogue.""" if self._associated_fipa_dialogue is None: raise AEAEnforceError("FipaDialogue not set!") return self._associated_fipa_dialogue @associated_fipa_dialogue.setter def associated_fipa_dialogue(self, fipa_dialogue: FipaDialogue) -> None: """Set associated_fipa_dialogue""" enforce(self._associated_fipa_dialogue is None, "FipaDialogue already set!") self._associated_fipa_dialogue = fipa_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_ledger_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_ledger_api_dialogue = None # type: Optional[LedgerApiDialogue] @property def associated_ledger_api_dialogue(self) -> LedgerApiDialogue: """Get associated_ledger_api_dialogue.""" if self._associated_ledger_api_dialogue is None: raise AEAEnforceError("LedgerApiDialogue not set!") return self._associated_ledger_api_dialogue @associated_ledger_api_dialogue.setter def associated_ledger_api_dialogue( self, ledger_api_dialogue: LedgerApiDialogue ) -> None: """Set associated_ledger_api_dialogue""" enforce( self._associated_ledger_api_dialogue is None, "LedgerApiDialogue already set!", ) self._associated_ledger_api_dialogue = ledger_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ``` The various dialogues classes in the above code snippet store dialogues with other AEAs, services and components, (e.g. SOEF search node via the `fetchai/soef` connection, ledgers via the `fetchai/ledger` connection and the decision maker). They expose useful methods to manipulate these interactions, access previous messages, and enable us to identify possible communications problems between `my_generic_seller` and `my_generic_buyer` AEAs. ### Step 6: Update the YAML Files After making so many changes to our skill, we have to update the `skill.yaml` configuration file so it reflects our newly created classes, and contains the values used by the strategy. Make sure `skill.yaml` contains the following configuration: ``` yaml name: generic_buyer author: fetchai version: 0.1.0 type: skill description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmTR91jm7WfJpmabisy74NR5mc35YXjDU1zQAUKZeHRw8L __init__.py: QmU5vrC8FipyjfS5biNa6qDWdp4aeH5h4YTtbFDmCg8Chj behaviours.py: QmNwvSjEz4kzM3gWtnKbZVFJc2Z85Nb748CWAK4C4Sa4nT dialogues.py: QmNen91qQDWy4bNBKrB3LabAP5iRf29B8iwYss4NB13iNU handlers.py: QmZfudXXbdiREiViuwPZDXoQQyXT2ySQHdF7psQsohZXQy strategy.py: QmcrwaEWvKHDCNti8QjRhB4utJBJn5L8GpD27Uy9zHwKhY fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: search: args: search_interval: 5 class_name: GenericSearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: GenericTransactionBehaviour handlers: fipa: args: {} class_name: GenericFipaHandler ledger_api: args: {} class_name: GenericLedgerApiHandler oef_search: args: {} class_name: GenericOefSearchHandler signing: args: {} class_name: GenericSigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 service_id: generic_service stop_searching_on_result: true class_name: GenericStrategy is_abstract: false dependencies: {} ``` We must pay attention to the models and the strategy’s variables. Here we can change the price we would like to buy each reading at, the maximum transaction fee we are prepared to pay, and so on. Finally, we fingerprint our new skill: ``` bash aea fingerprint skill fetchai/generic_buyer:0.1.0 ``` This will hash each file and save the hash in the fingerprint. This way, in the future we can easily track if any of the files have changed. ## Run the AEAs ### Create Private Keys For each AEA, create a private key: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Update the AEA Configurations In both AEAs run: ``` bash aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` ### Fund the Buyer AEA Create some wealth for your buyer on the Fetch.ai testnet (this operation might take a while). ``` bash aea generate-wealth fetchai --sync ``` ### Run Seller AEA Add the remaining packages for the seller AEA, then run it: ``` bash aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add protocol fetchai/fipa:1.1.7 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea run ``` Once you see a message of the form `To join its network use multiaddr: ['SOME_ADDRESS']` take note of the address. #### Run Buyer AEA Add the remaining packages for the buyer AEA: ``` bash aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add protocol fetchai/fipa:1.1.7 aea add protocol fetchai/signing:1.1.7 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 ``` Then, update the configuration of the buyer AEA's P2P connection: ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` where `SOME_ADDRESS` is replaced accordingly. Then run the buyer AEA: ``` bash aea run ``` You will see that the AEAs negotiate and then transact using the Dorado testnet. ## Delete the AEAs When you are done, go up a level and delete the AEAs. ``` bash cd .. aea delete my_generic_seller aea delete my_generic_buyer ``` ## Next Steps You have completed the "Getting Started" series. Congratulations! The following guide provides some hints on AEA development setup. ### Recommended We recommend you build your own AEA next. There are many helpful guides and demos in the documentation, and a developer community on Discord. Speak to you there! ================================================ FILE: docs/generic-skills.md ================================================ # Generic Skills The AEA generic buyer and seller skills demonstrate an interaction between two AEAs: - An AEA that provides a (data selling) service. - An AEA that demands this service. ## Discussion The scope of this guide is demonstrating how to create easily configurable AEAs. The buyer AEA finds the seller, negotiates the terms of trade, and if successful purchases the data by sending payment. The seller AEA sells the service specified in its `skill.yaml` file, delivering it to the buyer upon receiving payment. Note that these agents do not utilize a smart contract but interact with a ledger to complete a transaction. Moreover, in this setup, the buyer agent has to trust the seller to send the data upon successful payment. The corresponding packages can be customised to allow for a database or sensor to be defined from which data is loaded. This is done by first modifying the `has_data_source` variable in `skill.yaml` file of the `generic_seller` skill to `True`. Then you have to provide an implementation for the `collect_from_data_source(self)` method in the `strategy.py` file. More detailed instructions is beyond the scope of this guide. ## Communication The following diagram shows the communication between various entities in this interaction. ``` mermaid sequenceDiagram participant Search participant Buyer_AEA participant Seller_AEA participant Blockchain activate Buyer_AEA activate Search activate Seller_AEA activate Blockchain Seller_AEA->>Search: register_service Buyer_AEA->>Search: search_agents Search-->>Buyer_AEA: list_of_agents Buyer_AEA->>Seller_AEA: call_for_proposal Seller_AEA->>Buyer_AEA: propose Buyer_AEA->>Seller_AEA: accept Seller_AEA->>Buyer_AEA: match_accept Buyer_AEA->>Blockchain: transfer_funds Buyer_AEA->>Seller_AEA: send_transaction_hash Seller_AEA->>Blockchain: check_transaction_status Seller_AEA->>Buyer_AEA: send_data deactivate Buyer_AEA deactivate Search deactivate Seller_AEA deactivate Blockchain ``` ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ## Demo Instructions ### Create the Seller AEA First, fetch the seller AEA: ``` bash aea fetch fetchai/generic_seller:0.29.5 --alias my_seller_aea cd my_seller_aea aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the seller from scratch: ``` bash aea create my_seller_aea cd my_seller_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/generic_seller:0.28.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ### Create the Buyer AEA Then, in another terminal fetch the buyer AEA: ``` bash aea fetch fetchai/generic_buyer:0.30.5 --alias my_buyer_aea cd my_buyer_aea aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the buyer from scratch: ``` bash aea create my_buyer_aea cd my_buyer_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/generic_buyer:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ### Add Keys for the Seller AEA Create the private key for the seller AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Add Keys and Generate Wealth for the Buyer AEA The buyer needs to have some wealth to purchase the data from the seller. First, create the private key for the buyer AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Then, create some wealth for your buyer based on the network you want to transact with. On the Fetch.ai `Dorado` network: ``` bash aea generate-wealth fetchai ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Update the Skill Configurations The default skill configurations assume that the transaction is settled against the Fetch.ai ledger. In the generic seller's skill configuration file (`my_seller_aea/vendor/fetchai/skills/generi_seller/skill.yaml`) the `data_for_sale` is the data the seller AEA is offering for sale. In the following case, this is a one item dictionary where key is `generic` and value is `data`. Furthermore, the `service_data` is used to register the seller's service in the SOEF search node and make your agent discoverable. ``` yaml models: ... strategy: args: currency_id: FET data_for_sale: generic: data has_data_source: false is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: generic_service service_id: generic_service unit_price: 10 class_name: GenericStrategy ``` The generic buyer skill configuration file (`my_buyer_aea/vendor/fetchai/skills/generic_buyer/skill.yaml`) includes the `search_query` which has to match the `service_data` of the seller. ``` yaml models: ... strategy: args: currency_id: FET is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_tx_fee: 3550000000000000 max_unit_price: 20 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 service_id: generic_service class_name: GenericStrategy ``` ### Update the Skill Configurations Both skills are abstract skills, make them instantiable: ``` bash cd my_seller_aea aea config set vendor.fetchai.skills.generic_seller.is_abstract false --type bool ``` ``` bash cd my_buyer_aea aea config set vendor.fetchai.skills.generic_buyer.is_abstract false --type bool ``` ## Run the AEAs First, run the seller AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of this address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the seller. Then, configure the buyer to connect to this same local ACN by running the following command in the buyer terminal, replacing `SOME_ADDRESS` with the value you noted above: ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` Then run the buyer AEA: ``` bash aea run ``` You will see that the AEAs negotiate and then transact using the Fetch.ai testnet. ## Delete the AEAs When you're done, stop the agents (`CTRL+C`), go up a level and delete the AEAs. ``` bash cd .. aea delete my_seller_aea aea delete my_buyer_aea ``` ================================================ FILE: docs/generic-storage.md ================================================ # Generic Storage The AEA generic storage: description and usage. ## AEA Generic Storage AEA generic storage allows AEA skill's components to store data permanently and use it any time. The primary scenario: to save AEA data on shutdown and load back on startup. Generic storage provides an API for general data manipulation in key-object style. ## Configuration Storage is enabled by providing in the agent configuration (`aea-config.yaml`) an optional `storage_uri`. The storage URI consists of the backend name and string data provided to selected backend. The storage URI schema is `://[Optional string]` Example: `storage_uri: sqlite://./some_file.db` tells the AEA to use SQLite backend and store data in `./some_file.db`. Supported backends: - SQLite - bundled with python simple SQL engine that uses file or in-memory storage. ## Dialogues and Storage Integration One of the most useful cases is the integration of the dialogues subsystem and storage. It helps maintain dialogues state during agent restarts and reduced memory requirements due to the offloading feature. ### Keep Terminal State Dialogues The Dialogues class has the optional boolean argument `keep_terminal_state_dialogues` which specifies whether a dialogue which has reached its terminal state is kept in memory or not. If `keep_terminal_state_dialogues` is `False`, dialogues that reach a terminal state are removed from memory and can not be used anymore. If `keep_terminal_state_dialogues` is `True`, dialogues that reach a terminal state are kept in memory or storage (if configured). If storage is configured, all dialogues in memory are stored on agent stop and restored on agent start. It is useful to save memory with terminated dialogues that will (possibly) be never used again. Default behaviour on keep terminals state dialogues is set according to the protocol specification but can be set explicitly with skill configuration section. Skill configuration to keep terminated dialogues for `DefaultDialogues`. Example: ### Dialogues Dump/Restore on Agent Restart If storage is enabled then all the dialogues present in memory will be stored on agent's teardown and loaded on agent's start. ### Offload Terminal State Dialogues If keep options is set and storage is available dialogues in terminal state will be dumped to generic storage and removed from memory. This option helps to save memory and handle terminated dialogues with the same functionality as when they are kept in memory. All the active dialogues will be stored and loaded during agent restart. All the terminated offloaded dialogues will stay in storage on agent restart. To enable dialogues offloading `keep_terminal_state_dialogues` has to be enabled and storage configured. ## Manual Usage with Skill Components Handlers, Behaviours and Models are able to use storage if enabled. Storage is available with skill context: `self.context.storage` if `self.context.storage` is not None, storage is enabled and ready to use. Generic storage consists of two parts: objects and collections. Objects consist of the `object_id` (unique string) and object body. The object body is any JSON friendly python data type: `list`, `dict`, `int`, `float`, `string`, `bool`. Collection is a group of the objects, objects data types can vary in the same collection. Collection name is name consists of letters, numbers and `_`. To get/put specific object collection instance should be used. ``` python my_collection = self.context.storage.get_sync_connection('my_collection') ``` Collection instance provide set of methods to handle data objects. List of collection methods: ``` python def put(self, object_id: str, object_body: JSON_TYPES) -> None: """ Put object into collection. :param object_id: str object id :param object_body: python dict, json compatible. :return: None """ def get(self, object_id: str) -> Optional[JSON_TYPES]: """ Get object from the collection. :param object_id: str object id :return: dict if object exists in collection otherwise None """ def remove(self, object_id: str) -> None: """ Remove object from the collection. :param object_id: str object id :return: None """ def find(self, field: str, equals: EQUALS_TYPE) -> List[OBJECT_ID_AND_BODY]: """ Get objects from the collection by filtering by field value. :param field: field name to search: example "parent.field" :param equals: value field should be equal to :return: List of object bodies """ def list(self) -> List[OBJECT_ID_AND_BODY]: """ List all objects with keys from the collection. :return: Tuple of objects keys, bodies. """ ``` Simple behaviour example: It saves the `datetime` string of the first act and print it to stdout. ``` python class TestBehaviour(TickerBehaviour): """Simple behaviour to count how many acts were called.""" def setup(self) -> None: """Set up behaviour.""" def act(self) -> None: """Make an action.""" if not (self.context.storage and self.context.storage.is_connected): return collection = self.context.storage.get_sync_collection('my_collection') first_call_datetime = collection.get("first_call_ts") if not first_call_ts: # there is no object with "first_call_ts" id. first_call_datetime = str(datetime.datetime.now()) col.put(first_call_ts, first_call_datetime) print("Act was called for the first time on:", first_call_datetime) ``` Please, pay attention: `datetime` object is not JSON friendly and can not be stored directly. it should be transformed to `timestamp` or string before put into the storage. ================================================ FILE: docs/glossary.md ================================================ # Glossary This glossary defines a number of terms commonly used across the documentation. For the definitions of framework components consult the API docs. - **AEA (Autonomous Economic Agent)**: An AEA is "an intelligent agent acting on an owner's behalf, with limited or no interference, and whose goal is to generate economic value to its owner". AEAs are a special type of agent. [more] - **Software Agent**: a software agent is a computer program that acts on behalf of an entity (e.g. individual, organisation, business). [more] - **sOEF (Simple Open Economic Framework)**: The simple-OEF, or sOEF, is a search and discovery service for autonomous economic agents. [more] - **ACN (Agent Communication Network)**: The ACN is a peer-to-peer communication network for autonomous economic agents. [more] ================================================ FILE: docs/gym-example.md ================================================ # Gym Example The `gym` example demonstrates the AEA framework's flexibility with respect to Reinforcement Learning using OpenAI's `gym` framework. ## Discussion There is no immediate use case for this example as you can train an RL agent without the AEA proxy layer just fine (and faster). However, the example decouples the RL agent from the `gym.Env` allowing them to run in separate execution environments, potentially owned by different entities. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. Download the necessary directories into your working directory: ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/examples svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` Install the `gym` and `numpy` library. ``` bash pip install numpy gym ``` ## Demo Instructions ### Run the Example ``` bash python examples/gym_ex/train.py ``` Notice the usual RL setup, i.e. the fit method of the RL agent has the typical signature and a familiar implementation. Note how `train.py` demonstrates how easy it is to use an AEA agent as a proxy layer between an OpenAI `gym.Env` and a standard RL agent. It is just one line of code to introduce the proxy agent and proxy environment! ``` python from gyms.env import BanditNArmedRandom from proxy.env import ProxyEnv from rl.agent import RLAgent if __name__ == "__main__": NB_GOODS = 10 NB_PRICES_PER_GOOD = 100 NB_STEPS = 4000 # Use any gym.Env compatible environment: gym_env = BanditNArmedRandom(nb_bandits=NB_GOODS, nb_prices_per_bandit=NB_PRICES_PER_GOOD) # Pass the gym environment to a proxy environment: proxy_env = ProxyEnv(gym_env) # Use any RL agent compatible with the gym environment and call the fit method: rl_agent = RLAgent(nb_goods=NB_GOODS) rl_agent.fit(env=proxy_env, nb_steps=NB_STEPS) ``` ================================================ FILE: docs/gym-skill.md ================================================ # Gym Skill The AEA gym skill demonstrates how a custom Reinforcement Learning agent, that uses OpenAI's gym library, may be embedded into an AEA skill and connection. ## Discussion The gym skills demonstrate how to wrap a Reinforcement Learning agent in a skill. The example decouples the RL agent from the `gym.Env` allowing them to run in separate execution environments, potentially owned by different entities. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. Download the necessary directories into your working directory: ``` bash mkdir gym_skill_agent svn export https://github.com/fetchai/agents-aea.git/trunk/examples ``` Install the `gym` and `numpy` library. ``` bash pip install numpy gym ``` ## Demo Instructions ### Create the AEA First, fetch the gym AEA: ``` bash aea fetch fetchai/gym_aea:0.26.5 --alias my_gym_aea cd my_gym_aea aea install ``` ??? note "Alternatively, create from scratch:" ### Create the AEA In the root directory, create the gym AEA and enter the project. ``` bash aea create my_gym_aea cd my_gym_aea ``` ### Add the gym skill ``` bash aea add skill fetchai/gym:0.21.6 ``` ### Set gym connection as default ``` bash aea config set agent.default_connection fetchai/gym:0.20.6 ``` ### Install the skill dependencies To install the `gym` package, a dependency of the gym skill, from PyPI run ``` bash aea install ``` ### Set up the Training Environment #### Copy the Gym Environment to the AEA Directory ``` bash mkdir gyms cp -a ../examples/gym_ex/gyms/. gyms/ ``` #### Update the Connection Configuration ``` bash aea config set vendor.fetchai.connections.gym.config.env 'gyms.env.BanditNArmedRandom' ``` #### Create and Add a Private Key ``` bash aea generate-key fetchai aea add-key fetchai ``` ### Run the AEA with the Gym Connection ``` bash aea run ``` You will see the gym training logs. AEA gym training logs ### Delete the AEA When you're done, you can go up a level and delete the AEA. ``` bash cd .. aea delete my_gym_aea ``` ## Communication This diagram shows the communication between the AEA and the gym environment ``` mermaid sequenceDiagram participant AEA participant Environment activate AEA activate Environment AEA->>Environment: reset loop learn AEA->>Environment: act Environment->>AEA: percept end AEA->>Environment: close deactivate AEA deactivate Environment ``` ## Skill Architecture The skill consists of two core components: `GymHandler` and `GymTask`. In the `setup` method of the `GymHandler` the `GymTask` is initialized, as well as its `setup` and `execute` methods called. The handler, which is registered against the `GymMessage.protocol_id` then filters for messages of that protocol with the performative `GymMessage.Performative.PERCEPT`. These messages are passed to the `proxy_env_queue` of the task. The `GymTask` is responsible for training the RL agent. In particular, `MyRLAgent` is initialized and trained against `ProxyEnv`. The `ProxyEnv` instantiates a `gym.Env` class and therefore implements its API. This means the proxy environment is compatible with any `gym` compatible RL agent. However, unlike other environments it only acts as a proxy and does not implement an environment of its own. It allows for the decoupling of the process environment of the `gym.env` from the process environment of the RL agent. The actual `gym.env` against which the agent is trained is wrapped by the `gym` connection. The proxy environment and gym connection communicate via a protocol, the `gym` protocol. Note, it would trivially be possible to implement the `gym` environment in another AEA; this way one AEA could provide `gym` environments as a service. Naturally, the overhead created by the introduction of the extra layers causes a higher latency when training the RL agent. In this particular skill, which chiefly serves for demonstration purposes, we implement a very basic RL agent. The agent trains a model of price of `n` goods: it aims to discover the most likely price of each good. To this end, the agent randomly selects one of the `n` goods on each training step and then chooses as an `action` the price which it deems is most likely accepted. Each good is represented by an id and the possible price range `[1,100]` divided into 100 integer bins. For each price bin, a `PriceBandit` is created which models the likelihood of this price. In particular, a price bandit maintains a beta distribution. The beta distribution is initialized to the uniform distribution. Each time the price associated with a given `PriceBandit` is accepted or rejected the distribution maintained by the `PriceBandit` is updated. For each good, the agent can therefore over time learn which price is most likely. Gym skill illustration The illustration shows how the RL agent only interacts with the proxy environment by sending it `action (A)` and receiving `observation (O)`, `reward (R)`, `done (D)` and `info (I)`. ================================================ FILE: docs/http-connection-and-skill.md ================================================ # HTTP Connection ## Description The HTTP client and HTTP server connections enable an AEA to communicate with external servers, respectively clients, via HTTP. The HTTP client connection receives request envelops from an agent's skill, translates each into an HTTP request and sends it to a server external to the agent. If it receives an HTTP response from the server within a timeout window, it translates it into a response envelope, and sends this back to the relevant skill inside the agent. The HTTP server connection allows you to run a server inside the connection itself which accepts requests from clients external to the agent. The HTTP server connection validates requests it receives against a provided OpenAPI file. It translates each valid request into an envelope and sends it to the skill specified in the `connections` configuration. If it receives a valid response envelope from the skill within a timeout window, the connection translates the response envelope into an HTTP response and serves it to the client. ## HTTP Client The `fetchai/simple_data_request:0.14.6` skill demonstrates a simple use case of the HTTP Client connection. The `HttpRequestBehaviour` in `behaviours.py` periodically sends HTTP envelops to the HTTP client connection. Its `act()` method, periodically called, simply calls `_generate_http_request` which contains the logic for enqueueing an HTTP request envelop. The `HttpHandler` in `handler.py` is a basic handler for dealing with HTTP response envelops received from the HTTP client connection. In the `handle()` method, the responses are dealt with by the private `_handle_response` method which essentially logs the response and adds the body of the response into the skill's shared state. ## HTTP Server Create a new AEA: ``` bash aea create my_aea cd my_aea ``` Add the http server connection package: ``` bash aea add connection fetchai/http_server:0.23.6 ``` Update the default connection: ``` bash aea config set agent.default_connection fetchai/http_server:0.23.6 ``` Modify the `api_spec_path`: ``` bash aea config set vendor.fetchai.connections.http_server.config.api_spec_path "../examples/http_ex/petstore.yaml" ``` Ensure the file exists under the specified path! Create and add a private key: ``` bash aea generate-key fetchai aea add-key fetchai ``` Install the dependencies: ``` bash aea install ``` Write and add your skill: ``` bash aea scaffold skill http_echo ``` You can implement a simple http echo skill (modelled after the standard echo skill) which prints out the content of received messages and responds with success. First, delete the `my_model.py` and `behaviour.py` files (in `my_aea/skills/http_echo/`). The server will be purely reactive, so you only need the `handlers.py` file, and the `dialogues.py` to record the state of the dialogues. Update `skill.yaml` accordingly, so set `models: {}` and `behaviours: {}`. Next implement a basic handler which prints the received envelopes and responds. Then, replace the content of `handlers.py` with the following code snippet, after having replaced the placeholder `YOUR_USERNAME` with the author username (i.e. the output of `aea config get agent.author`): ``` python import json from typing import cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.YOUR_USERNAME.skills.http_echo.dialogues import ( DefaultDialogues, HttpDialogue, HttpDialogues, ) class HttpHandler(Handler): """This implements the echo handler.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ http_msg = cast(HttpMessage, message) # recover dialogue http_dialogues = cast(HttpDialogues, self.context.http_dialogues) http_dialogue = cast(HttpDialogue, http_dialogues.update(http_msg)) if http_dialogue is None: self._handle_unidentified_dialogue(http_msg) return # handle message if http_msg.performative == HttpMessage.Performative.REQUEST: self._handle_request(http_msg, http_dialogue) else: self._handle_invalid(http_msg, http_dialogue) def _handle_unidentified_dialogue(self, http_msg: HttpMessage) -> None: """ Handle an unidentified dialogue. :param http_msg: the message """ self.context.logger.info( "received invalid http message={}, unidentified dialogue.".format(http_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=http_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"http_message": http_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_request( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle a Http request. :param http_msg: the http message :param http_dialogue: the http dialogue """ self.context.logger.info( "received http request with method={}, url={} and body={!r}".format( http_msg.method, http_msg.url, http_msg.body, ) ) if http_msg.method == "get": self._handle_get(http_msg, http_dialogue) elif http_msg.method == "post": self._handle_post(http_msg, http_dialogue) def _handle_get(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb GET. :param http_msg: the http message :param http_dialogue: the http dialogue """ http_response = http_dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_msg, version=http_msg.version, status_code=200, status_text="Success", headers=http_msg.headers, body=json.dumps({"tom": {"type": "cat", "age": 10}}).encode("utf-8"), ) self.context.logger.info("responding with: {}".format(http_response)) self.context.outbox.put_message(message=http_response) def _handle_post(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb POST. :param http_msg: the http message :param http_dialogue: the http dialogue """ http_response = http_dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_msg, version=http_msg.version, status_code=200, status_text="Success", headers=http_msg.headers, body=http_msg.body, ) self.context.logger.info("responding with: {}".format(http_response)) self.context.outbox.put_message(message=http_response) def _handle_invalid( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle an invalid http message. :param http_msg: the http message :param http_dialogue: the http dialogue """ self.context.logger.warning( "cannot handle http message of performative={} in dialogue={}.".format( http_msg.performative, http_dialogue ) ) def teardown(self) -> None: """Implement the handler teardown.""" ``` Moreover, add a `dialogues.py` file with the following code: ``` python from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) HttpDialogue = BaseHttpDialogue class HttpDialogues(Model, BaseHttpDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseHttpDialogue.Role.SERVER BaseHttpDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ``` Then, update the `skill.yaml` accordingly: ``` yaml handlers: http_handler: args: {} class_name: HttpHandler models: default_dialogues: args: {} class_name: DefaultDialogues http_dialogues: args: {} class_name: HttpDialogues ``` Run the fingerprinter (note, you will have to replace the author name with your author handle): ``` bash aea fingerprint skill fetchai/http_echo:0.21.6 ``` Moreover, we need to tell to the `http_server` connection to what skill the HTTP requests should be forwarded. In our case, this is the `http_echo` that you have just scaffolded. Its public id will be `/http_echo:0.1.0`. ``` bash aea config set vendor.fetchai.connections.http_server.config.target_skill_id "$(aea config get agent.author)/http_echo:0.1.0" ``` You can now run the AEA: ``` bash aea run ``` In a separate terminal, you can create a client and communicate with the server: ``` python import requests response = requests.get('http://127.0.0.1:8000') response.status_code # >>> 404 # we receive a not found since the path is not available in the api spec response = requests.get('http://127.0.0.1:8000/pets') response.status_code # >>> 200 response.content # >>> b'{"tom": {"type": "cat", "age": 10}}' response = requests.post('http://127.0.0.1:8000/pets') response.status_code # >>> 200 response.content # >>> b'' ``` ================================================ FILE: docs/identity.md ================================================ # Identity !!! note This section is incomplete and will soon be updated. The AEAs currently use the addresses associated with their private-public key pairs to identify themselves. Keys of an AEA To learn how to generate a private-public key pair check out the relevant CLI commands . To learn more about public-key cryptography check out Wikipedia. An AEA can provide evidence of its identity using third-party solutions. We have implemented a demo using Aries Hyperledger Cloud Agent which is available here and another demo using Yoti which is available here. ================================================ FILE: docs/index.md ================================================ # AEA Framework Documentation !!! target "Vision" Our aim with the AEA framework is to enable businesses of all sizes, from independent developers to large corporations and consortiums, to create and deploy agent-based solutions in various domains, thus contributing to and advancing a decentralized mixed-initiative economy: one whose actors are both humans and machines. ## What is an AEA? !!! info "Definition" An Autonomous Economic Agent (AEA) is an intelligent agent that acts on its owner's behalf, with limited or no interference, and whose goal is to generate economic value for its owner. Breaking it down: **AGENT**: An AEA represents an individual, organisation or object and looks after their interests. **AUTONOMOUS**: AEAs operate independently of constant input from their owners and act autonomously to achieve their goals. **ECONOMIC**: AEAs have a narrow and specific focus: creating economic value for their owner. ## What Can You Do with AEAs? [//]: # (AEAs have the potential of being the next "apps", by enabling p2p. Most importantly, ) Some examples of the kinds of applications you can build with AEAs: **Automation** : AEAs can automate well-defined processes in different domains, such as supply chain, mobility, finance, ... **Micro-transactions** : AEAs make it economically viable to execute trade involving small values. An example is use-cases with many small sellers (e.g. of data) on the supply side. **Wallet** : AEAs can simplify interactions with blockchains. By acting as "smart wallets", they can hide away the majority of the complexities involved in using blockchains for end users. **IoT** : Agents representing objects in the IoT (Internet of Things) space. For example, AEAs paired with hardware devices such as drones, laptops, heat sensors, etc., providing control and receiving data from the device. An example is a thermometer agent. **Web 2.0 <--> Web 3.0 interface** : Agents that interface and bridge the gap between existing (Web 2.0) and new (Web 3.0) economic models. An example is an AEA that communicates with HTTP clients/servers. **Traders** : Agents with access to some data sources that sell the data, access to the data, or access to the usage of the data. An example is an AEA that continuously sells data to another AEA, who in turn uses it to improve their reinforcement learning model. ## Who is This For? The AEA technology is for anyone who wants to build or contribute to a "mixed-initiative economy": one whose actors are humans as well as machines. This includes (amongst others): developers, data scientists and machine learning experts, economists, students, academics and researchers (in Artificial Intelligence, Machine Learning, Multi-Agent Systems, etc), engineers, and so forth. ## The AEA Framework The AEA framework is a development suite which equips you with an efficient and accessible set of tools for building and running AEAs and their components. The framework attempts to make agent development as straightforward an experience as possible, similar to what popular web frameworks enable for web development. Some of the characteristics of the AEA framework are: - **Python**: Using Python as an approachable programming language improves the on-boarding for those who just want to get started with agent development. - **Open source**: The framework is open source and licensed under Apache 2.0. - **Modular**: Modularity is at the heart of the framework's design. This makes it easy to extend the framework, add new functionality, and re-use others' contributions, therefore reducing the development cost. - **Blockchain ready**: Integration with blockchains is baked into the framework, enabling the creation of agents that take full advantage of the blockchain technology. - **Modern**: The framework is built from and can be integrated with the latest technologies (e.g. asynchronous programming, blockchains and smart contracts, machine-learning ready, ...). ## The Ecosystem Though they can work in isolation, AEAs are truly valuable when situated in a wider ecosystem consisting of tools and infrastructure that enable them to cooperate and compete, and interact with services as well as traditional or modern systems. These include: - The Agent Communication Network (ACN): A peer-to-peer communication infrastructure that enables AEAs to directly communicate with one another without any intermediaries. - The sOEF: A search and discovery system allowing AEAs to register themselves and the services they offer, and search for agents who offer specific services. - The AEA Registry: A space to store and share AEAs or individual agent components for anyone to find and use. - Blockchains: AEAs can use blockchains as a financial and commitment layer. Each ledger plug-in provided by the framework adds the ability for AEAs to interact with a specific ledger, such as the Fetch.ai blockchain or Ethereum. - Smart Contracts: Contract packages are wrappers around smart contracts that allow AEAs to interact with them through a common interface. ## How to get involved? There are many ways for you to get involved. You can create agents, develop new agent components, extend existing components, and contribute to the development of the framework or other related tools. Please refer to the Contribution and Development guides. ## Next Steps To get started developing your own AEA, check out the getting started section. To learn more about some of the distinctive characteristics of agent-oriented development, check out the guide on agent-oriented development. If you would like to develop an AEA in a language different to Python then check out our language agnostic AEA definition. If you want to run a demo, check out the demo guides. ## Help us Improve !!! note This developer documentation is a work in progress. If you spot any errors please open an issue on Github or contact us in the developer Discord channel. ================================================ FILE: docs/install.md ================================================ # Installation !!! Info "Platforms" The AEA framework can be used on `Windows`, `Ubuntu/Debian` and `MacOS`. ## System Requirements 1. You need Python 3.8, 3.9 or 3.10 on your system. 2. GCC installation is also required: === "Ubuntu" ``` bash apt-get install gcc ``` === "MacOS X (with Homebrew)" ``` bash brew install gcc ``` === "Windows (with choco)" ``` bash choco install mingw ``` ??? tip "Tips" - **Ubuntu/Debian**: install Python headers, depending on the Python version you have installed on your machine. For example for Python 3.8: ``` bash sudo apt-get install python3.8-dev ``` - **Windows**: install tools for Visual Studio. ### Alternatively: Use Docker We also provide a Docker image with all the needed dependencies. 1. Pull the image: ``` bash docker pull fetchai/aea-user:latest ``` 2. Run the image with your current local directory mounted as a docker volume. This allows you to keep your agents local while working on them from within the docker container: === "Linux and MacOs" ``` bash docker run -it -v $(pwd):/agents --workdir=/agents fetchai/aea-user:latest ``` === "Windows" ``` powershell docker run -it -v %cd%:/agents --workdir=/agents fetchai/aea-user:latest ``` Once successfully logged into the docker container, you can follow the rest of the guide the same way as if not using docker. ## For Agent Development ### Preliminaries 1. Create a new working directory. Let's call it `my_aea_projects`. This is where you will create your agent projects. 2. Inside `my_aea_projects`, add an empty directory called `packages`. This is a local registry for your agents' components. You should now have the following directory structure: ```bash my_aea_projects └── packages ``` !!! tip "Alternatively, clone a template repo:" Instead of the above, you can clone the template repo as described in `Approach 1` in the development setup guide. #### Virtual Environment Unless you are using the docker image, we highly recommend using a virtual environment so that your setup is isolated from the rest of your system. This prevents clashes and ensures consistency across dependencies. You can use any common virtual environment manager for Python, such as `pipenv` and `poetry`. If you do not have either, install one. Once installed, create a new virtual environment in the `my_aea_projects` directory and enter it: === "pipenv" Use any Python version supported in the command: ``` bash pipenv --python 3.9 && pipenv shell ``` === "poetry" ``` bash poetry init -n && poetry shell ``` ### Installation The latest version of the Python implementation of the AEA Framework is: PyPI !!! info "Note" If you are upgrading your AEA project from a previous version of the AEA framework, make sure you check out the upgrading notes. #### Using pip Install the AEA framework using pip: === "bash/windows" ``` bash pip install aea[all] ``` === "zsh" ``` zsh pip install 'aea[all]' ``` ??? tip "Troubleshooting" To ensure no cache is used, add `--force --no-cache-dir` to the installation command. #### Using pipx Install the AEA framework using pipx: ``` bash pipx install aea[all] ``` ## For Contributing to the AEA Framework To contribute to the development of the framework or related tools (e.g. ACN), please refer to the Contribution and Development guides in our GitHub repository. ## Other Tools You Might Need Depending on what you want to do, you might need extra tools on your system: - To use the Agent Communication Network (ACN) for peer-to-peer communication between agents (e.g. using the `fetchai/p2p_libp2p` connection) you will need Golang 1.14.2 or higher. - The framework uses Google Protocol Buffers for message serialization. If you want to develop protocols, install the protobuf compiler on your system. The version you install must match the `protobuf` library installed with the project (see pyproject.toml). - To update fingerprint hashes of packages, you will need the IPFS daemon. ================================================ FILE: docs/interaction-protocol.md ================================================ # How AEAs Talk to Each Other - Interaction Protocols Although one can imagine scenarios where single AEAs pursue their goals in isolation without interacting with other AEAs, there is no doubt that by working together, AEAs have the potential of achieving much more, especially when taking into account agents' heterogeneity, specialisations, and differing and often complimentary local views of the environment. Interactions in the AEA world are in the form of communication. This is influenced by established practices in the field of multi-agent systems and the prominent speech-act theory which suggests that a communicative expression is not only about transferring information from the speaker to the hearer, but that there may be meanings and commitments beyond the statement's appearance. Therefore, speech may more suitably be considered as action. For example, "I hereby appoint you as chairman" is not just a sequence of words, but an action done by the speaker with wide-ranging consequences for the hearer and any other audience to that sentence. Interaction protocols are thus possible communication scenarios between agents or agent components (specifically, skills and connections). There are multiple types of interactions an AEA can have: - AEA-to-AEA interactions. You can find some examples in the demo section. - Interactions between an AEA's internal components. Interaction protocols Usually, an interaction involves three types of framework packages: skills, protocols and connections. ## Examples ### Example 1: Negotiation The generic buyer/seller skills use the `fetchai/fipa` protocol which defines the negotiation dialogue between two AEAs. The `fetchai/generic_buyer` and `fetchai/generic_seller` skills implement specific strategies for engaging in such negotiations, by providing the logic for producing negotiation messages to be sent, handling negotiation messages received. The `fetchai/p2p_libp2p` connection is then used for connecting to the agent communication network enabling two AEAs with these skills to deliver negotiation messages to each other. ### Example 2: AEA <> Web Client In the http connection guide we demonstrate how an AEA with a http server connection (e.g. `fetchai/http_server`) receives http payloads from web clients, translates them to messages conforming with the `fetchai/http` protocol and passes it to a skill (e.g. `fetchai/http_echo`) to process. The `fetchai/http` protocol in this case is used for communication between the connection and the skill. ### Example 3 : AEA <> 3rd Party Server The `fetchai/http_client` connection can be used to make requests to third party servers. In this case, a skill containing the logic for the production of http requests would create messages conforming with the `fetchai/http` protocol and sends it to the `fetchai/http_client` connection which in turn translates it into http payload and sends it to the destination server. Note that in general, third party SDKs can be wrapped in a connection and shared with other developers as a package. Often this also involves creating a custom protocol to enforce the type of interactions permitted between skills and the connection wrapping the SDK. ## Next Steps ### Recommended We recommend you continue with the next step in the 'Getting Started' series: - Trade between two AEAs ### Relevant Deep-Dives Most AEA development focuses on developing the `Skills` and `Protocols` necessary for an AEA to deliver against its economic objectives and implement interaction protocols. Understanding `Protocols` is core to developing your own agent. You can learn more about the `Protocols` agents use to communicate with each other and how they are created in the following section: - Protocols Most of an AEA developer's time is spent on `Skill` development. `Skills` are the core business logic components of an AEA. Check out the following guide to learn more: - Skills In most cases, one of the available `Connection` packages can be used. Occasionally, you might develop your own `Connection`: - Connections ================================================ FILE: docs/known-limits.md ================================================ # Known Limitations The AEA framework makes a multitude of tradeoffs. Here we present an incomplete list of known limitations: - The `AEABuilder` checks the consistency of packages at the `add` stage. However, it does not currently check the consistency again at the `load` stage. This means, if a package is tampered with after it is added to the `AEABuilder` then these inconsistencies might not be detected by the `AEABuilder`. - The `AEABuilder` assumes that packages with public ids of identical author and package name have a matching version. As a result, if a developer uses a package with matching author and package name but different version in the public id, then the `AEABuilder` will not detect this and simply use the last loaded package. - The order in which `setup` and `teardown` are called on the skills, and `act` is called on the behaviours, is not guaranteed. Skills should be designed to work independently. Where skills use the `shared_context` to exchange information they must do so safely. ================================================ FILE: docs/language-agnostic-definition.md ================================================ # Language Agnostic Definition Currently, there is an implementation of the AEA framework in Python which enables the development of AEAs in Python, and allows AEAs which are built with it to run. However, AEAs can be developed in different programming languages. This is further backed by the idea that agent-based solutions are suited for multi-stakeholder environments where the different AEAs may be developed independently of one another, resulting in heterogeneous systems. This means that in principle, there could be different implementations of the AEA framework, in various programming languages and for different platforms. However, to ensure that AEAs under any implementation are compatible with one another and able to interact, they must satisfy specific definitions. In this page, we compile a set of definitions which any AEA independent of its implementation must satisfy in order to be able to interact with other AEAs. An AEA, in technical terms, must satisfy the following requirements: - It MUST be capable of receiving and sending `Envelopes` which satisfy the following protobuf schema: ``` proto syntax = "proto3"; package aea.base.v0_1_0; message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; bytes message = 4; string uri = 5; } ``` The format for the above fields are as follows: - `to` and `sender`: an address derived from the private key of a secp256k1-compatible elliptic curve - `protocol_id`: this must match a defined regular expression (see below) - `message`: a bytes string representing a serialized message in the specified protocol - `URI`: follows this syntax - It MUST implement each protocol's `message` with the required meta-fields: ``` proto syntax = "proto3"; package aea.base.v0_1_0; import "google/protobuf/struct.proto"; message DialogueMessage { int32 message_id = 1; string dialogue_starter_reference = 2; string dialogue_responder_reference = 3; int32 target = 4; bytes content = 5; } message Message { oneof message { google.protobuf.Struct body = 1; DialogueMessage dialogue_message = 2; } } message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; bytes message = 4; string uri = 5; } ``` where `content` is replaced with the protocol specific content (see here for details). - It MUST implement protocols according to their specification (see here for details). - It SHOULD implement the `fetchai/default:1.1.7` protocol which satisfies the following protobuf schema: ``` proto syntax = "proto3"; package aea.fetchai.default.v1_0_0; message DefaultMessage{ // Custom Types message ErrorCode{ enum ErrorCodeEnum { UNSUPPORTED_PROTOCOL = 0; DECODING_ERROR = 1; INVALID_MESSAGE = 2; UNSUPPORTED_SKILL = 3; INVALID_DIALOGUE = 4; } ErrorCodeEnum error_code = 1; } // Performatives and contents message Bytes_Performative{ bytes content = 1; } message Error_Performative{ ErrorCode error_code = 1; string error_msg = 2; map error_data = 3; } message End_Performative{ } oneof performative{ Bytes_Performative bytes = 5; End_Performative end = 6; Error_Performative error = 7; } } ``` - The protocol id MUST match the following regular expression: `^([a-zA-Z_][a-zA-Z0-9_]{0,127})/([a-zA-Z_][a-zA-Z0-9_]{0,127})(:((any|latest|((0|[1-9]\d*))\.((0|[1-9]\d*))\.((0|[1-9]\d*))(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)))?$` - It is recommended that it processes `Envelopes` asynchronously. Note, the specification regarding the processing of messages does not impose any particular implementation, and the AEA can be designed to process envelopes either synchronously and asynchronously. However, asynchronous message handling enables the agent to be more responsive and scalable in maintaining many concurrent dialogues with its peers. - It MUST have an identity in the form of, at a minimum, an address derived from a public key and its associated private key (where the elliptic curve must be of type SECP256k1). - It SHOULD implement handling of errors using the `fetchai/default:1.1.7` protocol. The protobuf schema is given above. - It MUST implement the following principles when handling messages: - It MUST ALWAYS handle incoming envelopes/messages and NEVER raise an exception when decoding and validating the message. This ensures another AEA cannot cause the agent to fail by sending a malicious envelope/message. - It MUST NEVER handle outgoing messages and ALWAYS raise an exception when validating the message. An exception implies that the handler is resolving a bug in the implementation. !!! note Additional constraints will be added soon! ================================================ FILE: docs/ledger-integration.md ================================================ # Ledger & Crypto APIs In this section, we show you how to integrate the AEA with the Fetch.ai and third-party ledgers. ## Ledger Support For a ledger to be considered _supported_ in the framework, three abstract base classes need to be implemented: - the `LedgerApi` class wraps the API to talk to the ledger and its helper methods - the `Crypto` class wraps the API to perform cryptographic operations for the relevant ledger - the `FaucetApi` class wraps the API to talk to a faucet on a testnet These three classes have their own registries, which allow the developer to import the relevant object where needed. ## Ledger Plug-in Architecture The AEA framework provides a plug-in mechanism to support ledger functionalities in an easily extendable way. At import time, the framework will load all the crypto plug-ins available in the current Python environment. A _crypto plug-in_ is a Python package which declares some specific `setuptools` "entry points" in its `setup.py` script. In particular, there are three types of entry points the framework looks up: - `aea.ledger_apis`, which points to instantiable classes implementing the `LedgerApi` interface; - `aea.cryptos`, which points to instantiable classes implementing the `Crypto` interface; - `aea.faucet_apis`, which points to instantiable classes implementing the `FaucetApi` interface. This is an example of `setup.py` script for a ledger plug-in `aea-ledger-myledger`: ``` python # sample ./setup.py file from setuptools import setup setup( name="aea-ledger-myledger", packages=["aea_ledger_myledger"], # plugins must depend on 'aea' install_requires=["aea"], # add other dependencies... # the following makes a plugin available to aea entry_points={ "aea.cryptos": ["myledger = aea_ledger_myledger:MyLedgerCrypto"], "aea.ledger_apis": ["myledger = aea_ledger_myledger:MyLedgerApi"], "aea.faucet_apis": ["myledger = aea_ledger_myledger:MyLedgerFaucetApi"], }, # PyPI classifier for AEA plugins classifiers=["Framework :: AEA"], ) ``` By convention, such plug-in packages should be named `aea-ledger-${LEDGER_ID}`, and the importable package name `aea_ledger_${LEDGER_ID}`. In the example above, the package name is `aea-ledger-myledger`, and the importable package name is `aea_ledger_myledger`. You can search for AEA ledger plug-ins on PyPI: https://pypi.org/search/?q=aea-ledger ## Maintained Plug-ins At the moment, the framework natively supports the following three ledgers: - Fetch.ai: PyPI package: `aea-ledger-fetchai`, and source code. - Ethereum: PyPI package: `aea-ledger-ethereum`, and source code. - Cosmos: PyPI package: `aea-ledger-cosmos`, and source code. However, support for additional ledgers can be added to the framework at runtime. ## Examples - Examples of how to interact with the crypto registry: ``` python from aea.crypto.registries import crypto_registry, make_crypto, register_crypto # by default we can use the native cryptos fetchai_crypto = make_crypto("fetchai") # we can check what cryptos are registered crypto_registry.supported_ids # we can also add a new crypto to the registry register_crypto(id_="my_ledger_id", entry_point="some.dotted.path:MyLedgerCrypto") # and then make it anywhere my_ledger_crypto = make_crypto("my_ledger_id") ``` - Examples of how to interact with the ledger API registry: ``` python from aea.crypto.registries import ledger_apis_registry, make_ledger_api, register_ledger_api # by default we can use the native ledger apis CONFIG = {"network": "testnet"} fetchai_ledger_api = make_ledger_api("fetchai", **CONFIG) # we can check what ledger apis are registered ledger_apis_registry.supported_ids # we can also add a new ledger api to the registry register_ledger_api(id_="my_ledger_id", entry_point="some.dotted.path:MyLedgerApi") # and then make it anywhere my_ledger_api = make_ledger_api("my_ledger_id") ``` - Examples of how to interact with the faucet API registry: ``` python from aea.crypto.registries import faucet_apis_registry, make_faucet_api, register_faucet_api # by default we can use the native faucet apis CONFIG = dict(poll_interval=1.0) fetchai_faucet_api = make_faucet_api("fetchai", **CONFIG) # we can check what faucet apis are registered faucet_apis_registry.supported_ids # we can also add a new faucet api to the registry register_faucet_api(id_="my_ledger_id", entry_point="some.dotted.path:MyLedgerFaucetApi") # and then make it anywhere my_faucet_api = make_faucet_api("my_ledger_id") ``` The framework wraps all `LedgerApi` classes and exposes them in the `LedgerApis` classes. The framework also wraps the crypto APIs to create identities on both ledgers and exposes them in the `Wallet`. The separation between the `Crypto` and `LedgerApi` is fundamental to the framework design. In particular, the object which holds the private key is separated from the object which interacts with the ledger. This design pattern is repeated throughout the framework: the decision maker is the only entity with access to the AEA's `Wallet` whilst `LedgerApis` are accessible by all skills. ## Stargate World - Fetch.ai Testnet for Agents Stargate World is our stable, public testnet for the Fetch Ledger v2. As such, most developers will be interacting with this testnet. This is specifically designed and supported for AEA development. | Parameter | Value | |----------------|-----------------------------------------------------------------------------------------------| | Chain ID | dorado-1 | | Denomination | atestfet | | Decimals | 18 | | Version | v0.8.x | | RPC Endpoint | | | REST Endpoint | | | Block Explorer | https://explore-dorado.fetch.ai | | Token Faucet | Use block explorer | You can access more details on docs section. The configurations can be specified for the `fetchai/ledger:0.21.5` connection. ## CosmWasm Supporting Chains The Fetch.ai networks use CosmWasm for smart contract support. ================================================ FILE: docs/limits.md ================================================ # Limitations of v1 This document describes some of the limitations of `v1` of the AEA framework and tradeoffs made in its design. ## Rejected Ideas ### Handlers Implemented as Behaviours Handlers can be considered a special cases of a "behaviour that listens for specific events to happen". One could implement `Handler` classes in terms of `Behaviours`, after having implemented the feature that behaviours can be activated after an event happens (e.g. receiving a message of a certain protocol). This was rejected in favour of a clear separation of concerns, and to avoid purely reactive (handlers) and proactive (behaviours) components to be conflated into one concept. The proposal would also add complexity to behaviour development. ### Multiple Versions of the Same Package The framework does not allow for the usage of multiple versions of the same package in a given project. Although one could re-engineer the project to allow for this, it does introduce significant additional complexities. Furthermore, Python modules are by design only allowed to exist as one version in a given process. Hence, it seems sensible to maintain this approach in the AEA. ## Potential Extensions, Considered yet not Decided ### Alternative Skill Design For very simple skills, the splitting of skills into `Behaviour`, `Handler`, `Model` and `Task` classes can add unnecessary complexity to the framework and a counter-intuitive responsibility split. The splitting also implies the framework needs to introduce the `SkillContext` object to allow for access to data across the skill. Furthermore, the framework requires implementing all functionality in `SkillComponent` classes `Handler`, `Behaviour` or `Model`. This approach is consistent and transparent, however it creates a lot of boilerplate code for simple skills. Hence, for some use cases it would be useful to have a single `Skill` class with abstract methods `setup`, `act`, `handle` and `teardown`. Then the developer can decide how to split up their code. ``` python class SkillTemplate(SimpleSkill): protocol_ids: Optional[List[PublicId]] = None def setup(): # setup skill def handle(message: Message): # handle messages def act(): for b in behaviours: b.act() def teardown(): # teardown skill ``` Alternatively, we could use decorators to let a developer define whether a function is part of a handler or behaviour. That way, a single file with a number of functions could implement a skill. (Behind the scenes this would utilise a number of virtual `Behaviour` and `Handler` classes provided by the framework). The downside of this approach is that it does not advocate for much modularity on the skill level. Part of the role of a framework is to propose a common way to do things. The above approach can cause for a larger degree of heterogeneity in the skill design which makes it harder for developers to understand each other's code. The separation between all four base classes does exist both in convention *and* at the code level. Handlers deal with skill-external events (messages), behaviours deal with scheduled events (ticks), models represent data and tasks are used to manage long-running business logic. By adopting strong convention around skill development we allow for the framework to take a more active role in providing guarantees. E.g. handlers' and behaviours' execution can be limited to avoid them being blocking, models can be persisted and recreated, tasks can be executed with different task backends. The opinionated approach is thought to allow for better scaling. ### Further Modularity for Skill Level Code Currently, we have three levels of modularity: - PyPI packages - framework packages: protocols, contracts, connections and skills - framework plugins: CLI, ledger We could consider having a fourth level: common behaviours, handlers, models exposed as modules which can then speed up skill development. ### "promise" Pattern Given the asynchronous nature of the framework, it is often hard to implement reactions to specific messages, without making a "fat" handler. Take the example of a handler for a certain type of message `A` for a certain protocol `p`. The handler for protocol `p` would look something like this: ``` python class PHandler: ... def handle(msg): if message type is A: self._handle_a(msg) ``` However, it could be helpful to overwrite this handler reaction with another callback (e.g. consider this in context): ``` python # callable that handles the reply def my_callback(msg): # handle reply self.context.outbox.put_message(message, handler_func=my_callback, failure_func=...) ``` This feature would introduce additional complexity for the framework to correctly wire up the callbacks and messages with the dialogues. ### CLI using Standard Lib Removing the click dependency from the CLI would further reduce the dependencies in the AEA framework which is overall desirable. ### Metadata vs Configurations The current approach uses `yaml` files to specify both metadata and component configuration. It would be desirable to introduce the following separation: - package metadata - package default developer configuration - package default user configuration A user can only configure a subset of the configuration. The developer should be able to define these constraints for the user. Similarly, a developer cannot modify all fields in a package, some of them are determined by the framework. ### Configuring Agent Goal Setup By default, the agent's goals are implicitly defined by its skills and the configurations thereof. This is because the default decision maker signs every message and transaction presented to it. It is already possible to design a custom decision maker. However, more work needs to be done to understand how to improve the usability and configuration of the decision maker. In this context different types of decision makers can be implemented for the developer/user. ### Connection Status Monitoring Currently, connections are responsible for managing their own status after they have been "connected" by the `Multiplexer`. Developers writing connections must take care to properly set its connection status at all times and manage any disconnection. It would potentially be desirable to offer different policies to deal with connection problems on the multiplexer level: - disconnect one, keep others alive - disconnect all - try to reconnect indefinitely ### Agent Snapshots on Teardown or Error Currently, the developer must implement snapshots on the component level. It would be desirable if the framework offered more help to persist the agent state on teardown or error. ### Dialogues Management The current implementation of Dialogues is verbose. Developers often need to subclass `Dialogues` and `Dialogue` classes. More effort can be made to simplify and streamline dialogues management. ### Instantiate Multiple Instances of the Same Class of `SkillComponent` Currently, configuration and metadata of a package are conflated making it not straightforward to run one package component with multiple sets of configuration. It could be desirable to configure an agent to run a given package with multiple different configurations. This feature could be problematic with respect to component-to-component messaging which currently relies on component ids, which are bound to the package and not its instance. ### Containerized Agents Agent management, especially when many of them live on the same host, can be cumbersome. The framework should provide more utilities for these large-scale use cases. But a proper isolation of the agent environment is something that helps also simple use cases. A new software architecture, somehow inspired to the Docker system. The CLI only involves the initialization of the building of the agent (think of it as the specification of the `Dockerfile`: the `Agentfile`), but the actual build and run are done by the AEA engine, a daemon process analogous of the Docker Engine, which exposes APIs for these operations. Users and developers would potentially like to run many AEAs of different versions and with differences in the versions of their dependencies. It is not possible to import different versions of the same Python (PyPI) package in the same process in a clean way. However, in different processes this is trivial with virtual environments. It would be desirable to consider this in the context of a container solution for agents. ### Dependency Light Version of the AEA Framework The `v1` of the Python AEA implementation makes every effort to minimise the amount of third-party dependencies. However, some dependencies remain to lower development time. It would be desirable to further reduce the dependencies, and potentially have an implementation that only relies on the Python standard library. This could be taken further, and a reduced spec version for micropython could be designed. ### Compiled AEA Python is not a compiled language. However, various projects attempt this, e.g. Nuitka and it would be desirable to explore how useful and practical this would be in the context of AEA. ### DID Integration It would be great to integrate DID in the framework design, specifically identification of packages (most urgently protocols). Other projects and standards worth reviewing in the context (in particular with respect to identity): - ERC 725: Ethereum Identity Standard and here. - ERC 735: Claim Holder ### Optimise Protocol Schemas and Messages The focus of protocol development was on extensibility and compatibility, not on optimisation. For instance, the dialogue references use inefficient string representations. ### Constraints on Primitive Types in Protocols The protocol generator currently does not support custom constraints. The framework could add support for custom constraints for the protocol generator and specification. There are many types of constraints that could be supported in specification and generator. One could perhaps add support based on the popularity of specific constraints from users/developers. Example constraints: - strings following specific regular expression format (e.g. all lower case, any arbitrary regex format) - max number of elements on lists/sets - keys in one `dict` type be equal to keys in another `dict` type - other logical constraints, e.g. as supported in ontological languages - support for bounds (i.e. min, max) for numerical types (i.e. `int` and `float`) in protocol specification. Example syntax: - `pt:int[0, ]` - `pt:float[1.0, 10.0]` - `pt:int[-1000, 1000]` - `pt:float[, 0]` This would automatically enable support for signed/unsigned `int` and `float`. This syntax would allow for unbounded positive/negative/both, or arbitrary bounds to be placed on numerical types. Currently, the developer has to specify a custom type to implement any constraints on primitive types. ### Sub-protocols & Multi-Party Interactions Protocols can be allowed to depend on each other. Similarly, protocols might have multiple parties. Furthermore, a turn-taking function that specifies who's turn it is at any given point in the dialogue could be added. Then the current `fipa` setup is a specific case of turn-taking where the turn shifts after a player sends a single move (unique-reply). But generally, it does not have to be like this. Players could be allowed to send multiple messages until the turn shifts, or until they send specific speech-acts (multiple-replies). ### Timeouts in Protocols Protocols currently do not implement the concept of timeouts. We leave it to the skill developer to implement any time-specific protocol rules. ### Framework Internal Messages The activation/deactivation of skills and addition/removal of components is implemented in a "passive" way - the skill posts a request in its skill context queue (in the case of new behaviours), or it just sets a flag (in case of activation/deactivation of skills). One could consider that a skill can send requests to the framework, via the internal protocol, to modify its resources or its status. The `DecisionMaker` or the `Filter` can be the components that take such actions. This is a further small but meaningful step toward an actor-based model for agent internals. ### Ledger Transaction Management Currently, the framework does not manage any aspect of submitting multiple transactions to the ledgers. This responsibility is left to skills. Additionally, the ledger APIs/contract APIs take the ledger as a reference to determine the nonce for a transaction. If a new transaction is sent before a previous transaction has been processed then the nonce will not be incremented correctly for the second transaction. This can lead to submissions of multiple transactions with the same nonce, and therefore failure of subsequent transactions. A naive approach would involve manually incrementing the nonce and then submitting transactions into the pool with the correct nonce for eventual inclusion. The problem with this approach is that any failure of a transaction will cause none of the subsequent transactions to be processed for some ledgers (). To recover from a transaction failure not only the failed transaction would need to be handled, but potentially also all subsequent transactions. It is easy to see that logic required to recover from a transaction failure early in a sequence can be arbitrarily complex (involving potentially new negotiations between agents, new signatures having to be generated etc.). A further problem with the naive approach is that it (imperfectly) replicates the ledger state (with respect to (subset of state of) a specific account). A simple solution looks as follows: each time a transaction is constructed (requiring a new nonce) the transaction construction is queued until all previous transactions have been included in the ledger or failed. This way, at any one time the agent has only at most one transaction pending with the ledger. Benefits: simple to understand and maintain, transaction only enter the mempool when they are ready for inclusion which has privacy benefits over submitting a whole sequence of transaction at once. Downside: at most one transaction per block. This approach is currently used and implemented across all the reference skills. Related, the topic of latency in transactions. State channels provide a solution. E.g. Perun. There could also be an interesting overlap with our protocols here. ### Unsolved Problems in `Multiplexer` - `AgentLoop` Interplay Problem 1: connection generates too many messages in a short amount of time, that are not consumed by the multiplexer Solution: Can be solved by slowing down connections receive method called, controlled by the inbox messages amount Side effects: Most of the connections should have an internal queue because there is no synchronization between internal logic and multiplexer connection `receive` calls. Problem 2: the send method can take a long time (because send retries logic in connection) Solution: Currently, we apply timeouts on send. Other solutions could be considered, like parallelization. Problem 3: too many messages are produced by a skill. Solution: Raise an exception on outbox is full or slow down agent loop? ## ACN ### Agent Mobility on ACN If a peer-client or full client switches peer, then the DHT is not updated properly at the moment under certain conditions. ### Mailbox Connection The two available connections `p2p_libp2p` and `p2p_libp2p_client` imply that the agent is continuously connected and therefore must have uninterrupted network access and the resources to maintain a connection. For more lightweight implementations, a mailbox connection is desirable, as outlined in the ACN documentation. ================================================ FILE: docs/logging.md ================================================ # Logging The AEA framework supports flexible logging capabilities with the standard Python logging library. In this tutorial, we configure logging for an AEA. First, create your AEA. ``` bash aea create my_aea cd my_aea ``` The `aea-config.yaml` file should look like this. ``` yaml agent_name: my_aea author: fetchai version: 0.1.0 description: '' license: Apache-2.0 aea_version: 0.6.0 fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/stub:0.21.3 contracts: [] protocols: - fetchai/default:1.1.7 skills: - fetchai/error:0.18.6 default_connection: fetchai/stub:0.21.3 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} ``` By updating the `logging_config` section, you can configure the loggers of your application. The format of this section is specified in the `logging.config` module. At this section you'll find the definition of the configuration dictionary schema. Below is an example of the `logging_config` value. ``` yaml logging_config: version: 1 disable_existing_loggers: False formatters: standard: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: standard level: DEBUG filename: logconfig.log console: class: logging.StreamHandler formatter: standard level: DEBUG loggers: aea: handlers: - logfile - console level: DEBUG propagate: False ``` This configuration will set up a logger with name `aea`. It prints both on console and on file with a format specified by the `standard` formatter. ## Streaming to Browser It is possible to configure the AEA to stream logs to a browser. First, add the following configuration to your AEA: ``` yaml logging_config: version: 1 disable_existing_loggers: false formatters: standard: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: http: class: logging.handlers.HTTPHandler formatter: standard level: INFO host: localhost:5000 url: /stream method: POST loggers: aea: handlers: - http level: INFO propagate: false ``` Second, create a log server: ``` python # -*- coding: utf-8 -*- """A simple flask server to serve logs.""" import datetime import itertools import queue from flask import Flask, Response, request, stream_with_context def format_log(log_dict): """Format a log record.""" date = datetime.datetime.fromtimestamp(float(log_dict["created"])) formatted_log = f"[{date.isoformat()}] [{log_dict['levelname']}] {log_dict['name']}: {log_dict['msg']}" return formatted_log def create_app(): """Create Flask app for streaming logs.""" all_logs = [] unread_logs = queue.Queue() app = Flask(__name__) @app.route("/") def index(): """Stream logs to client.""" def generate(): # stream old logs div = "
{}
" for old_row in all_logs: yield div.format(old_row) # stream unread logs while True: row = unread_logs.get() all_logs.append(row) yield f"
{row}
" rows = generate() title = "

Waiting for logs...

" return Response(stream_with_context(itertools.chain([title], rows))) @app.route("/stream", methods=["POST"]) def stream(): """Save log record from AEA.""" log_record_formatted = format_log(dict(request.form)) unread_logs.put(log_record_formatted) return {}, 200 app.run() if __name__ == "__main__": create_app() ``` Save the script in a file called `server.py`, install flask with `pip install flask` and run the server with `python server.py`. Third, run your AEA and visit `localhost:5000` in your browser. ================================================ FILE: docs/message-routing.md ================================================ # Message Routing Message routing can be split up into the routing of incoming and outgoing `Messages`. It is important to keep in mind that interaction protocols can be maintained between agents (agent to agent) and between components of the AEA (component to component). In the former case, the `to`/`sender` fields of the `Envelope` are agent addresses which must follow the address standard of agents, in the latter case they are component public ids. Crucially, both addresses must reference the same type: agent or component. ## Incoming `Messages` - `Connections` receive or create `Envelopes` which they deposit in the `InBox` - for agent-to-agent communication only, the `Multiplexer` keeps track of the `connection_id` via which the `Envelope` was received. - the `AgentLoop` picks `Envelopes` off the `InBox` - the `AEA` tries to decode the message; errors are handled by the `ErrorHandler` - `Messages` are dispatched based on two rules: 1. checks if `to` field can be interpreted as `skill_id`, if so uses that together with the `protocol_id` to dispatch to the protocol's `Handler` in the specified `Skill`, else 2. uses the `protocol_id` to dispatch to the protocol's `Handler` in all skills supporting the protocol. !!! note For agent-to-agent communication it is advisable to have a single skill implement a given protocol. Skills can then forward the messages via skill-to-skill communication to other skills where required. Otherwise, received agent-to-agent messages will be forwarded to all skills implementing a handler for the specified protocol and the developer needs to take care to handle them appropriately (e.g. avoid multiple replies to a single message). ## Outgoing `Messages` - `Skills` deposit `Messages` in `OutBox` - `OutBox` constructs an `Envelope` from the `Message` - `Multiplexer` assigns messages to relevant `Connection` based on the following rules: 1. Component to component messages are routed by their `component_id` 2. Agent to agent messages are routed following four rules: 1. checks if `EnvelopeContext` exists and specifies a `Connection`, if so uses that else 2. checks which connection handled the last message from `sender`, if present uses that else 3. checks if default routing is specified for the `protocol_id` referenced in the `Envelope`, if so uses that else 4. sends to default `Connection`. - `Connections` can process `Envelopes` directly or encode them for transport to another agent. ## Usage of the `EnvelopeContext` The `EnvelopeContext` is used to maintain agent-to-agent communication only and is managed almost entirely by the framework. The developer can set the `EnvelopeContext` explicitly for the first message in a dialogue to achieve targeted routing to connections (see 2. for outgoing messages). This is relevant when the same agent can be reached via multiple connections. The `EnvelopeContext` is not sent to another agent. ================================================ FILE: docs/ml-skills.md ================================================ # ML Skills The AEA ML (machine learning) skills demonstrate an interaction between two AEAs, one purchasing data from the other and training a machine learning model with it. There are two types of AEAs: - The `ml_data_provider` which sells training data. - The `ml_model_trainer` which purchases data and trains a model ## Discussion This demo aims to demonstrate the integration of a simple AEA with machine learning using the AEA framework. The `ml_data_provider` AEA provides some sample data and delivers to the client upon payment. Once the client receives the data, it trains a model. This process can be found in `tasks.py`. This demo does not utilize a smart contract. As a result, the ledger interaction is only for completing a transaction. Since the AEA framework enables using third-party libraries from PyPI, we can directly reference any external dependencies. The `aea install` command installs all dependencies an AEA needs that is listed in one of its skills' YAML file. ## Communication This diagram shows the communication between the two AEAs. ``` mermaid sequenceDiagram participant ml_model_trainer participant ml_data_provider participant Search participant Ledger activate ml_model_trainer activate ml_data_provider activate Search activate Ledger ml_data_provider->>Search: register_service ml_model_trainer->>Search: search Search-->>ml_model_trainer: list_of_agents ml_model_trainer->>ml_data_provider: call_for_terms ml_data_provider->>ml_model_trainer: terms ml_model_trainer->>Ledger: request_transaction ml_model_trainer->>ml_data_provider: accept (incl transaction_hash) ml_data_provider->>Ledger: check_transaction_status ml_data_provider->>ml_model_trainer: data loop train ml_model_trainer->>ml_model_trainer: tran_model end deactivate ml_model_trainer deactivate ml_data_provider deactivate Search deactivate Ledger ``` ## Option 1: AEA Manager Approach Follow this approach when using the AEA Manager Desktop app. Otherwise, skip and follow the CLI approach below. ### Preparation Instructions - Install the AEA Manager. - Install Tensorflow ### Demo Instructions The following steps assume you have launched the AEA Manager Desktop app. 1. Add a new AEA called `ml_data_provider` with public id `fetchai/ml_data_provider:0.28.0`. 2. Add another new AEA called `ml_model_trainer` with public id `fetchai/ml_model_trainer:0.29.0`. 3. Copy the address from the `ml_model_trainer` into your clip board. Then go to the Dorado block explorer and request some test tokens via `Get Funds`. 4. Run the `ml_data_provider` AEA. Navigate to its logs and copy the multiaddress displayed. 5. Navigate to the settings of the `ml_model_trainer` and under `components > connection >` `fetchai/p2p_libp2p:0.22.0` update as follows (make sure to replace the placeholder with the multiaddress): ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` 6. Run the `ml_model_trainer`. In the AEA's logs, you should see the agents trading successfully, and the training agent training its machine learning model using the data purchased. The trainer keeps purchasing data and training its model until stopped. ## Option 2: CLI Approach Follow this approach when using the `aea` CLI. ### Preparation Instructions #### Dependencies - Follow the Preliminaries and Installation sections from the AEA quick start. - Install Tensorflow ### Demo Instructions #### Create Data Provider AEA First, fetch the data provider AEA: ``` bash aea fetch fetchai/ml_data_provider:0.32.5 cd ml_data_provider aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the data provider from scratch: ``` bash aea create ml_data_provider cd ml_data_provider aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/ml_data_provider:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` #### Create Model Trainer AEA Then, fetch the model trainer AEA: ``` bash aea fetch fetchai/ml_model_trainer:0.33.5 cd ml_model_trainer aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the model trainer from scratch: ``` bash aea create ml_model_trainer cd ml_model_trainer aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/ml_train:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` #### Add Keys for the Data Provider AEA First, create the private key for the data provider AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` #### Add Keys and Generate Wealth for the Model Trainer AEA The model trainer needs to have some wealth to purchase the data from the data provider. First, create the private key for the model trainer AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Then, create some wealth for your model trainer based on the network you want to transact with. On the Fetch.ai `Dorado` network: ``` bash aea generate-wealth fetchai ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` #### Run both AEAs Run both AEAs from their respective terminals. First, run the data provider AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the ML data provider. Then, in the ML model trainer, run this command (replace `SOME_ADDRESS` with the correct value as described above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` This allows the model trainer to connect to the same local agent communication network as the data provider. Then run the model trainer AEA: ``` bash aea run ``` You can see that the AEAs find each other, negotiate and eventually trade. After the trade, the model trainer AEA trains its ML model using the data it has purchased. This AEA keeps purchasing data and training its model until stopped. #### Cleaning up When you're finished, delete your AEAs: ``` bash cd .. aea delete ml_data_provider aea delete ml_model_trainer ``` ================================================ FILE: docs/modes.md ================================================ # Modes of Running an AEA We can run an AEA in multiple modes thanks to the configurable design of the framework. The AEA contains two runnable parts, the `AgentLoop`, which operates the skills, and the Multiplexer, which operates the connections. The `AgentLoop` can be configured to run in `async` or `sync` mode. The `Multiplexer` by default runs in `async` mode. The AEA itself, can be configured to run in `async` mode, if both the `Multiplexer` and `AgentLoop` have the same mode, or in `threaded` mode. The latter ensures that `AgentLoop` and `Multiplexer` are run in separate threads. ================================================ FILE: docs/multi-agent-manager.md ================================================ # Multi Agent Manager The `MultiAgentManager` allows managing multiple agent projects programmatically. ## Setup We instantiate the manager by providing it with the working directory in which to operate and starting it: ``` python import os from pathlib import Path from aea.manager import MultiAgentManager WORKING_DIR = "mam" manager = MultiAgentManager(WORKING_DIR) manager.start_manager() ``` ## Adding Projects We first add a couple of finished AEA project: ``` python from aea.configurations.base import PublicId weather_station_id = PublicId.from_str("fetchai/weather_station:0.32.5") weather_client_id = PublicId.from_str("fetchai/weather_client:0.33.5") manager.add_project(weather_station_id) manager.add_project(weather_client_id) weather_station_name = weather_station_id.name weather_client_name = weather_client_id.name ``` ## Adding Agent Instances Add the agent instances ``` python agent_overrides = { "private_key_paths": {"fetchai": "fetchai_private_key.txt"}, "connection_private_key_paths": {"fetchai": "fetchai_connection_private_key.txt"} } p2p_public_id = PublicId.from_str("fetchai/p2p_libp2p:0.27.5") soef_public_id = PublicId.from_str("fetchai/soef:0.27.6") component_overrides = [{ **p2p_public_id.json, "type": "connection", "cert_requests": [{ "identifier": "acn", "ledger_id": "fetchai", "not_after": '2022-01-01', "not_before": '2021-01-01', "public_key": "fetchai", "message_format": "{public_key}", "save_path": "conn_cert.txt" }] }, { **soef_public_id.json, "type": "connection", "config": { "token_storage_path": "soef_token.txt" } }] manager.add_agent(weather_station_id, component_overrides=component_overrides, agent_overrides=agent_overrides) agent_overrides = { "private_key_paths": {"fetchai": "fetchai_private_key.txt"}, "connection_private_key_paths": {"fetchai": "fetchai_connection_private_key.txt"} } component_overrides = [{ **p2p_public_id.json, "type": "connection", "config": { "delegate_uri": "127.0.0.1:11001", "entry_peers": ['/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAkzgZYyk25XjAhmgXcdMbahrHYi18uuAzHuxPn1KkdmLRw'], "local_uri": "127.0.0.1:9001", "public_uri": "127.0.0.1:9001", }, "cert_requests": [{ "identifier": "acn", "ledger_id": "fetchai", "not_after": '2022-01-01', "not_before": '2021-01-01', "public_key": "fetchai", "message_format": "{public_key}", "save_path": "conn_cert.txt" }] }, { **soef_public_id.json, "type": "connection", "config": { "token_storage_path": "soef_token.txt" } }] manager.add_agent(weather_client_id, component_overrides=component_overrides, agent_overrides=agent_overrides) ``` Save the following private keys in the respective files. ``` python FET_PRIVATE_KEY_STATION = b"72d3149f5689f0749eaec5ebf6dba5deeb1e89b93ae1c58c71fd43dfaa231e87" FET_PRIVATE_KEY_PATH_STATION = Path(manager.data_dir, weather_station_name, "fetchai_private_key.txt").absolute() FET_PRIVATE_KEY_PATH_STATION.write_bytes(FET_PRIVATE_KEY_STATION) FET_CONNECTION_PRIVATE_KEY_STATION = b"bf529acb2546e13615ef6004c48e393f0638a5dc0c4979631a9a4bc554079f6f" FET_CONNECTION_PRIVATE_KEY_PATH_STATION = Path(manager.data_dir, weather_station_name, "fetchai_connection_private_key.txt").absolute() FET_CONNECTION_PRIVATE_KEY_PATH_STATION.write_bytes(FET_CONNECTION_PRIVATE_KEY_STATION) FET_PRIVATE_KEY_CLIENT = b"589839ae54b71b8754a7fe96b52045364077c28705a1806b74441debcae16e0a" FET_PRIVATE_KEY_PATH_CLIENT = Path(manager.data_dir, weather_client_name, "fetchai_private_key.txt").absolute() FET_PRIVATE_KEY_PATH_CLIENT.write_bytes(FET_PRIVATE_KEY_CLIENT) FET_CONNECTION_PRIVATE_KEY_CLIENT = b"c9b38eff57f678f5ab5304447997351edb08eceb883267fa4ad849074bec07e4" FET_CONNECTION_PRIVATE_KEY_PATH_CLIENT = Path(manager.data_dir, weather_client_name, "fetchai_connection_private_key.txt").absolute() FET_CONNECTION_PRIVATE_KEY_PATH_CLIENT.write_bytes(FET_CONNECTION_PRIVATE_KEY_CLIENT) ``` ## Running the Agents ``` python import time manager.start_agent(weather_station_id.name) # wait for ~10 seconds for peer node to go live time.sleep(10.0) manager.start_agent(weather_client_id.name) time.sleep(5.0) ``` ## Stopping the Agents ``` python manager.stop_all_agents() ``` ## Cleaning up ``` python manager.stop_manager() ``` ## Limitations The `MultiAgentManager` can only be used with compatible package versions, in particular the same package (with respect to author and name) cannot be used in different versions. If you want to run multiple agents with differing versions of the same package then use the `aea launch` command in the multi-processing mode, or simply launch each agent individually with `aea run`. ================================================ FILE: docs/multiplexer-standalone.md ================================================ # Use Multiplexer Stand-Alone The `Multiplexer` can be used stand-alone. This way a developer can utilise the protocols and connections independent of the `Agent` or `AEA` classes. First, import the Python and application specific libraries and set the static variables. (Get the `packages` directory from the AEA repository `svn export https://github.com/fetchai/agents-aea.git/trunk/packages`.) ``` python import os import time from copy import copy from threading import Thread from typing import Optional from aea.configurations.base import ConnectionConfig from aea.helpers.file_io import write_with_lock from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.protocols.default.message import DefaultMessage INPUT_FILE = "input.txt" OUTPUT_FILE = "output.txt" ``` ## Instantiate a `Multiplexer` A `Multiplexer` only needs a list of connections. The `StubConnection` is a simple connection which reads from and writes to file. ``` python # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # create the connection and multiplexer objects configuration = ConnectionConfig( input_file=INPUT_FILE, output_file=OUTPUT_FILE, connection_id=StubConnection.connection_id, ) stub_connection = StubConnection( configuration=configuration, data_dir=".", identity=Identity("some_agent", "some_address", "some_public_key"), ) multiplexer = Multiplexer([stub_connection], protocols=[DefaultMessage]) ``` ## Start the `Multiplexer` We can run a multiplexer by calling, `connect()` which starts the 'receive' and 'send' loops. We run the multiplexer from a different thread so that we can still use the main thread to pass it messages. ``` python try: # Set the multiplexer running in a different thread t = Thread(target=multiplexer.connect) t.start() # Wait for everything to start up for _ in range(20): if multiplexer.is_connected: break time.sleep(1) else: raise Exception("Not connected") ``` ## Send and Receive an Envelope We use the input and output text files to send an envelope to our agent and receive a response ``` python # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( "multiplexer,some_agent,fetchai/default:1.0.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: write_with_lock(f, message_text) # Wait for the envelope to get processed for _ in range(20): if not multiplexer.in_queue.empty(): break time.sleep(1) else: raise Exception("No message!") # get the envelope envelope = multiplexer.get() # type: Optional[Envelope] assert envelope is not None # Inspect its contents print( "Envelope received by Multiplexer: sender={}, to={}, protocol_specification_id={}, message={}".format( envelope.sender, envelope.to, envelope.protocol_specification_id, envelope.message, ) ) # Create a mirrored response envelope response_envelope = copy(envelope) response_envelope.to = envelope.sender response_envelope.sender = envelope.to # Send the envelope back multiplexer.put(response_envelope) # Read the output envelope generated by the multiplexer with open(OUTPUT_FILE, "r") as f: print("Envelope received from Multiplexer: " + f.readline()) ``` ## Shutdown Finally, stop our multiplexer and wait for it to finish ``` python finally: # Shut down the multiplexer multiplexer.disconnect() t.join() ``` ## Your Turn Now it is your turn to develop a simple use case which utilises the `Multiplexer` to send and receive Envelopes. ## Entire Code Listing If you just want to copy and paste the entire script in you can find it here: ??? note "Click here to see full listing:" ``` python import os import time from copy import copy from threading import Thread from typing import Optional from aea.configurations.base import ConnectionConfig from aea.helpers.file_io import write_with_lock from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.protocols.default.message import DefaultMessage INPUT_FILE = "input.txt" OUTPUT_FILE = "output.txt" def run(): """Run demo.""" # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # create the connection and multiplexer objects configuration = ConnectionConfig( input_file=INPUT_FILE, output_file=OUTPUT_FILE, connection_id=StubConnection.connection_id, ) stub_connection = StubConnection( configuration=configuration, data_dir=".", identity=Identity("some_agent", "some_address", "some_public_key"), ) multiplexer = Multiplexer([stub_connection], protocols=[DefaultMessage]) try: # Set the multiplexer running in a different thread t = Thread(target=multiplexer.connect) t.start() # Wait for everything to start up for _ in range(20): if multiplexer.is_connected: break time.sleep(1) else: raise Exception("Not connected") # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( "multiplexer,some_agent,fetchai/default:1.0.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: write_with_lock(f, message_text) # Wait for the envelope to get processed for _ in range(20): if not multiplexer.in_queue.empty(): break time.sleep(1) else: raise Exception("No message!") # get the envelope envelope = multiplexer.get() # type: Optional[Envelope] assert envelope is not None # Inspect its contents print( "Envelope received by Multiplexer: sender={}, to={}, protocol_specification_id={}, message={}".format( envelope.sender, envelope.to, envelope.protocol_specification_id, envelope.message, ) ) # Create a mirrored response envelope response_envelope = copy(envelope) response_envelope.to = envelope.sender response_envelope.sender = envelope.to # Send the envelope back multiplexer.put(response_envelope) # Read the output envelope generated by the multiplexer with open(OUTPUT_FILE, "r") as f: print("Envelope received from Multiplexer: " + f.readline()) finally: # Shut down the multiplexer multiplexer.disconnect() t.join() if __name__ == "__main__": run() ``` ================================================ FILE: docs/oracle-demo.md ================================================ # Oracle Skills This demo shows how an AEA can be used to maintain an oracle and how another AEA can request the oracle value. ## Discussion **Oracle agents** are agents that have permission to update or validate updates to state variables in a smart contract and whose goal is to accurately estimate or predict some real world quantity or quantities. This demonstration shows how to set up a simple oracle agent who deploys an oracle contract and updates the contract with a token price fetched from a public API. It also shows how to create an oracle client agent that can request the value from the oracle contract. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ## Demo ### Create the Oracle AEA Fetch the AEA that will deploy and update the oracle contract. ``` bash aea fetch fetchai/coin_price_oracle:0.17.6 cd coin_price_oracle aea install ``` ??? note "Alternatively, create from scratch (and customize the data source):" Create the AEA that will deploy the contract. ``` bash aea create coin_price_oracle cd coin_price_oracle aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/ledger:0.21.5 aea add connection fetchai/prometheus:0.9.6 aea add skill fetchai/advanced_data_request:0.7.6 aea add skill fetchai/simple_oracle:0.16.5 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/ledger:0.21.5 aea install ``` Set the URL for the data request skill: ``` bash aea config set --type str vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url "https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd" ``` Specify the name and JSON path of the data to fetch from the API: ``` bash aea config set --type list vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs '[{"name": "price", "json_path": "fetch-ai.usd"}]' ``` Set the name of the oracle value in the simple oracle skill: ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.oracle_value_name price ``` Then update the agent configuration with the default routing: ``` bash aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5" }' ``` Update the default ledger. ``` bash aea config set agent.default_ledger fetchai ``` Set the following configuration for the oracle skill: ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id fetchai aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function update_oracle_value ``` This demo runs on the `fetchai` ledger by default. Set the following variable for use in the configuration steps: ``` bash LEDGER_ID=fetchai ``` ??? note "Alternatively, configure the agent to use an ethereum ledger:" ``` bash LEDGER_ID=ethereum ``` Update the default ledger. ``` bash aea config set agent.default_ledger ethereum ``` Set the following configuration for the oracle skill: ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id ethereum aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function updateOracleValue ``` Additionally, create the private key for the oracle AEA. Generate and add a key for use with the ledger: ``` bash aea generate-key $LEDGER_ID --add-key ``` If running on a testnet (not including Ganache), generate some wealth for your AEA: ``` bash aea generate-wealth $LEDGER_ID ``` ### Create the Oracle Client AEA From a new terminal (in the same top-level directory), fetch the AEA that will deploy the oracle client contract and call the function that requests the coin price from the oracle contract. ``` bash aea fetch fetchai/coin_price_oracle_client:0.12.6 cd coin_price_oracle_client aea install ``` ??? note "Alternatively, create from scratch:" Create the AEA that will deploy the contract. ``` bash aea create coin_price_oracle_client cd coin_price_oracle_client aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/simple_oracle_client:0.13.5 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/ledger:0.21.5 aea install ``` Then update the agent configuration with the default routing: ``` bash aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5" }' ``` Set the default ledger: ``` bash aea config set agent.default_ledger fetchai ``` Set the following configuration for the oracle client skill: ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.ledger_id fetchai aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.query_function query_oracle_value ``` Similar to above, set a temporary variable `LEDGER_ID=fetchai` or `LEDGER_ID=ethereum`. ??? note "Follow these steps to configure for an ethereum ledger:" Set the default ledger: ``` bash aea config set agent.default_ledger ethereum ``` Set the following configuration for the oracle client skill: ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.ledger_id ethereum aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.query_function queryOracleValue ``` Create the private key for the oracle client AEA. Generate and add a key for use on the ledger: ``` bash aea generate-key $LEDGER_ID --add-key ``` If running on a testnet (not including Ganache), generate some wealth for your AEA: ``` bash aea generate-wealth $LEDGER_ID ``` ### Configuring a Ledger The oracle AEAs require either a locally running ledger node or a connection to a remote ledger. By default, they are configured to use the latest `fetchai` testnet. ??? note "Follow these steps to configure local Ethereum test node:" The easiest way to test the oracle agents on an Ethereum-based ledger to set up a local test node using Ganache. This can be done by running the following docker command from the directory you started from (in a new terminal). This command will also fund the accounts of the AEAs: ``` bash docker run -p 8545:8545 trufflesuite/ganache-cli:latest --verbose --gasPrice=0 --gasLimit=0x1fffffffffffff --account="$(cat coin_price_oracle/ethereum_private_key.txt),1000000000000000000000" --account="$(cat coin_price_oracle_client/ethereum_private_key.txt),1000000000000000000000" ``` Run the following Python script (with web3 installed) from the top-level directory to deploy a mock Fetch ERC20 contract and give some test FET to the client agent. ``` python import json import os from web3 import Web3 FILE_DIR = os.path.dirname(os.path.realpath(__file__)) CONTRACT_PATH = os.path.join(FILE_DIR, "coin_price_oracle_client/vendor/fetchai/contracts/fet_erc20/build/FetERC20Mock.json") ORACLE_PRIVATE_KEY_PATH = os.path.join(FILE_DIR, "coin_price_oracle/ethereum_private_key.txt") CLIENT_PRIVATE_KEY_PATH = os.path.join(FILE_DIR, "coin_price_oracle_client/ethereum_private_key.txt") # Solidity source code with open(CONTRACT_PATH) as file: compiled_sol = json.load(file) # web3.py instance w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545')) # Import oracle account from private key and set to default account with open(ORACLE_PRIVATE_KEY_PATH) as file: private_key = file.read() oracle_account = w3.eth.account.privateKeyToAccount(private_key) w3.eth.defaultAccount = oracle_account.address # Import client account from private key with open(CLIENT_PRIVATE_KEY_PATH) as file: private_key = file.read() client_account = w3.eth.account.privateKeyToAccount(private_key) # Deploy mock Fetch ERC20 contract FetERC20Mock = w3.eth.contract(abi=compiled_sol['abi'], bytecode=compiled_sol['bytecode']) # Submit the transaction that deploys the contract tx_hash = FetERC20Mock.constructor( name="FetERC20Mock", symbol="MFET", initialSupply=int(1e23), decimals_=18).transact() # Wait for the transaction to be mined, and get the transaction receipt tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash) # Print out the contract address print("FetERC20Mock contract deployed at:", tx_receipt.contractAddress) # Get deployed contract fet_erc20_mock = w3.eth.contract(address=tx_receipt.contractAddress, abi=compiled_sol['abi']) # Transfer some test FET to oracle client account tx_hash = fet_erc20_mock.functions.transfer(client_account.address, int(1e20)).transact() tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash) ``` Set the ERC20 contract address for the oracle AEA ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.erc20_address $ERC20_ADDRESS ``` as well as for the oracle client AEA ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.erc20_address $ERC20_ADDRESS ``` where `ERC20_ADDRESS` is in the output of the script above. ### Run the Oracle AEA Run the oracle agent. This will deploy a contract to the testnet, grant oracle permissions to the AEA's wallet address, and periodically update the contract with the latest price of FET (or whichever coin was specified). ``` bash aea run ``` After a few moments, you should see the following notices in the logs: ``` bash info: [coin_price_oracle] Oracle contract successfully deployed at address: ... ... info: [coin_price_oracle] Oracle role successfully granted! ... info: [coin_price_oracle] Oracle value successfully updated! ``` The oracle contract will continue to be updated with the latest retrieved coin price at the default time interval (every 15 seconds). ### Set the ERC20 and Oracle Contract Addresses for the Oracle Client AEA ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.oracle_contract_address $ORACLE_ADDRESS ``` where `ORACLE_ADDRESS` should be set to the address shown in the oracle AEA logs: ``` bash Oracle contract successfully deployed at address: ORACLE_ADDRESS ``` ### Run the Oracle Client AEA Run the oracle client agent. This will deploy an oracle client contract to the testnet, approve the contract to spend tokens on behalf of the AEA, and periodically call the contract function that requests the latest price of FET (or whichever coin was specified). ``` bash aea run ``` After a few moments, you should see the following notices in the logs: ``` bash info: [coin_price_oracle_client] Oracle client contract successfully deployed at address: ... ... info: [coin_price_oracle_client] Oracle value successfully requested! ``` The AEA will continue to request the latest coin price at the default time interval (every 15 seconds). ================================================ FILE: docs/orm-integration.md ================================================ # ORM Integration This guide demonstrates how to configure an AEA to interact with a database using `python-sql` objects. ## Discussion Object-relational-mapping (ORM) is the idea of being able to write SQL queries, using the object-oriented paradigm of your preferred programming language. The scope of this guide is to demonstrate how you can create an easily configurable AEA that reads data from a database using ORMs. - We assume, that you followed the guide for the thermometer-skills. - We assume, that we have a database `genericdb.db` with table name `data`. This table contains the following columns `timestamp` and `thermometer`. - We assume, that we have a hardware thermometer sensor that adds the readings in the `genericdb` database (although you can follow the guide without having access to a sensor). Since the AEA framework enables us to use third-party libraries hosted on PyPI we can directly reference the external dependencies. The `aea install` command will install each dependency that the specific AEA needs and which is listed in the skill's YAML file. ## Communication This diagram shows the communication between the various entities in the case where the thermometer data is successfully sold by the seller AEA to the buyer. ``` mermaid sequenceDiagram participant Search participant Buyer_AEA participant Seller_AEA participant Blockchain activate Buyer_AEA activate Search activate Seller_AEA activate Blockchain Seller_AEA->>Search: register_service Buyer_AEA->>Search: search Search-->>Buyer_AEA: list_of_agents Buyer_AEA->>Seller_AEA: call_for_proposal Seller_AEA->>Buyer_AEA: propose Buyer_AEA->>Seller_AEA: accept Seller_AEA->>Buyer_AEA: match_accept Buyer_AEA->>Blockchain: transfer_funds Buyer_AEA->>Seller_AEA: send_transaction_hash Seller_AEA->>Blockchain: check_transaction_status Seller_AEA->>Buyer_AEA: send_data deactivate Buyer_AEA deactivate Search deactivate Seller_AEA deactivate Blockchain ``` ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ## Demo Instructions This demo involves a true ledger transaction on Fetch.ai's `testnet` network or Ethereum's `ropsten`. This demo assumes the buyer trusts the seller AEA to send the data upon successful payment. ### Create the Seller AEA First, fetch the seller AEA which provides thermometer data: ``` bash aea fetch fetchai/thermometer_aea:0.30.5 --alias my_thermometer_aea cd my_thermometer_aea aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the seller from scratch: ``` bash aea create my_thermometer_aea cd my_thermometer_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ### Create the Buyer Client In another terminal, fetch the buyer AEA: ``` bash aea fetch fetchai/thermometer_client:0.32.5 --alias my_thermometer_client cd my_thermometer_client aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the car data client from scratch: ``` bash aea create my_thermometer_client cd my_thermometer_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer_client:0.26.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ### Add Keys for the Seller AEA First, create the private key for the seller AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Add Keys and Generate Wealth for the Buyer AEA The buyer needs to have some wealth to purchase the thermometer data. First, create the private key for the buyer AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Then, create some wealth for the buyer based on the network you want to transact with. On the Fetch.ai `Dorado` network: ``` bash aea generate-wealth fetchai ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Update the Seller and Buyer AEA Skill Configurations In `my_thermometer_aea/vendor/fetchai/skills/thermometer/skill.yaml`, replace the `data_for_sale` with your data: ``` yaml models: ... strategy: args: currency_id: FET data_for_sale: temperature: 26 has_data_source: false is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: thermometer_data service_id: thermometer_data unit_price: 10 class_name: Strategy dependencies: SQLAlchemy: {} ``` The `service_data` is used to register the service in the SOEF search node and make your agent discoverable. In `my_thermometer_client/vendor/fetchai/skills/thermometer_client/skill.yaml`) ensure you have matching data. ``` yaml models: ... strategy: args: currency_id: FET is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_tx_fee: 3550000000000000 max_unit_price: 20 search_query: constraint_type: == search_key: seller_service search_value: thermometer_data search_radius: 5.0 service_id: thermometer_data class_name: Strategy ``` After changing the skill configuration files you should run the following command for both agents to install each dependency: ``` bash aea install ``` ### Modify the Seller's Strategy Before being able to modify a package we need to eject it from vendor: ``` bash aea eject skill fetchai/thermometer:0.27.6 ``` This will move the package to your `skills` directory and reset the version to `0.1.0` and the author to your author handle. Open `strategy.py` (in `my_thermometer_aea/skills/thermometer/strategy.py`) and make the following modifications: Import the newly installed `sqlalchemy` library in your strategy. ``` python import sqlalchemy as db ``` Then modify your strategy's `__init__` function to match the following code: ``` python class Strategy(GenericStrategy): """This class defines a strategy for the agent.""" def __init__(self, **kwargs) -> None: """ Initialize the strategy of the agent. :param register_as: determines whether the agent registers as seller, buyer or both :param search_for: determines whether the agent searches for sellers, buyers or both :return: None """ self._db_engine = db.create_engine("sqlite:///genericdb.db") self._tbl = self.create_database_and_table() self.insert_data() super().__init__(**kwargs) ``` At the end of the file modify the `collect_from_data_source` function: ``` python def collect_from_data_source(self) -> Dict[str, str]: """Implement the logic to collect data.""" connection = self._db_engine.connect() query = db.select([self._tbl]) result_proxy = connection.execute(query) data_points = result_proxy.fetchall() return {"data": json.dumps(list(map(tuple, data_points)))} ``` Also, create two new functions, one that creates a connection with the database, and another that populates the database with some fake data. This is needed in the case you do not have access to an actual thermometer sensor that inserts data in the database. ``` python def create_database_and_table(self): """Creates a database and a table to store the data if not exists.""" metadata = db.MetaData() tbl = db.Table( "data", metadata, db.Column("timestamp", db.Integer()), db.Column("temprature", db.String(255), nullable=False), ) metadata.create_all(self._db_engine) return tbl def insert_data(self): """Insert data in the database.""" connection = self._db_engine.connect() for _ in range(10): query = db.insert(self._tbl).values( # nosec timestamp=time.time(), temprature=str(random.randrange(10, 25)) ) connection.execute(query) ``` After modifying the skill we need to fingerprint it: ``` bash aea fingerprint skill {YOUR_AUTHOR_HANDLE}/thermometer:0.1.0 ``` ### Run Both AEAs First, run the thermometer (seller) AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of this address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the thermometer AEA. Then, configure the thermometer client (buyer) to connect to this same local ACN by running the following command in the buyer terminal, replacing `SOME_ADDRESS` with the value you noted above: ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` Then run the thermometer client AEA: ``` bash aea run ``` You will see that the AEAs negotiate and then transact using the configured testnet. ## Delete the AEAs When you're done, stop the agents (`CTRL+C`), go up a level and delete the AEAs. ``` bash cd .. aea delete my_thermometer_aea aea delete my_thermometer_client ``` ================================================ FILE: docs/p2p-connection.md ================================================ # P2P Connection The `fetchai/p2p_libp2p:0.27.5` connection allows AEAs to create a peer-to-peer communication network. In particular, the connection creates an overlay network which maps agents' public keys to IP addresses. ## Local Demo First, make sure you have installed the crypto plugin of the target test-net. E.g. for Fetch.AI: ``` bash pip install aea-ledger-fetchai ``` ### Create and Run the Genesis AEA Create one AEA as follows: ``` bash aea create my_genesis_aea cd my_genesis_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea install aea build ``` Establish the proof of representation: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection aea issue-certificates ``` Run the AEA: ``` bash aea run --connections fetchai/p2p_libp2p:0.27.5 ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the genesis AEA. ### Create and Run Another AEA Create a second AEA: ``` bash aea create my_other_aea cd my_other_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea install aea build ``` Establish the proof of representation: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection aea issue-certificates ``` Provide the AEA with the information it needs to find the genesis: ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` Here `SOME_ADDRESS` needs to be replaced with the list of multi addresses displayed in the log output of the genesis AEA. Run the AEA: ``` bash aea run --connections fetchai/p2p_libp2p:0.27.5 ``` You can inspect the `libp2p_node.log` log files of the AEA to see how they discover each other. !!! note Currently `p2p_libp2p` connection limits the total message size to 3 MB. ## Local Demo with Skills Explore the demo section for further examples. ## Deployed Agent Communication Network You can connect to the deployed public test network by adding one or multiple of the following addresses as the `p2p_libp2p` connection's `entry_peers`: ``` yaml /dns4/acn.fetch.ai/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx ``` ``` yaml /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW ``` Specifically, in an AEA's configuration `aea-config.yaml` add the above addresses for `entry_peers` as follows: ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null entry_peers: [/dns4/acn.fetch.ai/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx,/dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW] public_uri: null local_uri: 127.0.0.1:9001 ``` Note, this configuration change must be made for all agents attempting to communicate with each other via the Agent Communication Network. For example, in demos involving two agents, both agents will need the above modifications to their respective `aea-config.yaml` file. However, remember to use different ports in `local_uri.` This will allow both agents to default to this communication network without the added overhead of opening ports and specifying hosts on the individual host machines running each agent. ## Configuring the `connection.yaml` Entries To learn more about how to configure your `fetchai/p2p_libp2p:0.27.5` connection consult the `README.md` file supplied with the connection package. ## Running Go Peer Standalone You can run a peer node in _standalone mode_; that is, as a Go process with no dependency on the AEA framework. To facilitate such a deployment, we provide a script `run_acn_node_standalone.py` and a corresponding Dockerfile. First, you need to build the node's binary (`libp2p_node`) either: - locally ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/packages/fetchai/connections/p2p_libp2p cd p2p_libp2p go build chmod +x libp2p_node ``` Make sure you satisfy the system requirements. - or within a docker image using the provided Dockerfile: ``` bash docker build -t acn_node_standalone -f scripts/acn/Dockerfile . ``` Next, to run the node binary in standalone mode, it requires values for the following entries: - `AEA_P2P_ID`: the node's private key, will be used as its identity - `AEA_P2P_URI`: the local host and port to use by node - `AEA_P2P_URI_PUBLIC`: the URI under which the peer is publicly reachable - `AEA_P2P_DELEGATE_URI`: the URI under which the peer receives delegate connections - `AEA_P2P_ENTRY_URIS`: an optionally supplied list of comma-separated (`,`) entry multiaddresses for the peer to bootstrap The script allows different methods to pass these values to the node: - As environment variables exported in the format `=` for each entry. Then: ``` bash python3 run_acn_node_standalone.py libp2p_node --config-from-env ``` - Using an environment file containing the entries and their values in the format `=`, one entry per line. Then: ``` bash python3 run_acn_node_standalone.py libp2p_node --config-from-file ``` or ``` bash docker run -v :/acn/acn_config -it acn_node_standalone --config-from-file /acn/acn_config ``` - Using command line arguments: ``` bash python3 run_acn_node_standalone.py libp2p_node --key-file \ --uri --uri-external \ --uri-delegate \ --entry-peers-maddrs ... ``` or ``` bash docker run -v :/acn/key.txt -it acn_node_standalone --key-file /acn/key.txt \ --uri --uri-external \ --uri-delegate \ --entry-peers-maddrs ... ``` Note that the script will always save the configuration of the running node as a file under the name `.acn_config` in the current working directory. This can be handy when you want the exact same configuration for future runs of the node. ================================================ FILE: docs/package-imports.md ================================================ # File Structure An agent that is generated using the AEA framework is a modular system with different connections, contracts, protocols and skills. ## An AEA Project's File Structure The file structure of an AEA is fixed. The top level directory has the AEA's name. Below is a `aea-config.yaml` configuration file, then directories containing the connections, contracts, protocols, and skills developed by the developer as part of the given project. The connections, contracts, protocols and skills used from the registry (local or remote - added via `aea fetch` or `aea add`) are located in `vendor` and sorted by author. Build artefacts are placed in the `.build/` directory and certificates are placed in the `.certs/` directory. Finally, there are files containing the private keys of the AEA. When we create a new agent with the command `aea create my_aea` we create the file structure that looks like the following: ``` bash aea_name/ aea-config.yaml YAML configuration of the AEA fetchai_private_key.txt The private key file connections/ Directory containing all the connections developed as part of the given project. connection_1/ First connection ... ... connection_n/ nth connection contracts/ Directory containing all the contracts developed as part of the given project. connection_1/ First connection ... ... connection_n/ nth connection protocols/ Directory containing all the protocols developed as part of the given project. protocol_1/ First protocol ... ... protocol_m/ mth protocol skills/ Directory containing all the skills developed as part of the given project. skill_1/ First skill ... ... skill_k/ kth skill vendor/ Directory containing all the added resources from the registry, sorted by author. author_1/ Directory containing all the resources added from author_1 connections/ Directory containing all the added connections from author_1 ... ... protocols/ Directory containing all the added protocols from author_1 ... ... skills/ Directory containing all the added skills from author_1 ... ... ``` The developer can create new directories where necessary but the core structure must remain the same. ## AEA Configuration YAML The `aea-config.yaml` is the top level configuration file of an AEA. It defines the global configurations as well as the component/package dependencies of the AEA. In some sense, the AEA can therefore be understood as an orchestrator of components. For the AEA to use a package, the `public_id` for the package must be listed in the `aea-config.yaml` file, e.g. ``` yaml connections: - fetchai/stub:0.21.3 ``` The above shows a part of the `aea-config.yaml`. If you see the connections, you will see that we follow a pattern of `author/name_package:version` to identify each package, also referred to as `public_id`. Here the `author` is the author of the package. ## Vendor and Package Directories The `vendor` folder contains the packages from the registry (local or remote) which have been developed by ourselves, other authors or Fetch.ai and are placed in different namespaces according to the author name. The packages we develop as part of the given AEA project are in the respective `connections/`, `contracts/`, `protocols/`, and `skills/` folders. In the above configuration example, the package is authored by Fetch.ai and is located inside the `vendor/fetchai/connections` folder. ## Importing Modules from Packages The way we import modules from packages inside the agent is in the form of `packages.{author}.{package_type}.{package_name}.{module_name}`. So for the above example, the import path is `packages.fetchai.connections.stub.{module_name}`. The framework loads the modules from the local agent project and adds them to Python's `sys.modules` under the respective path. We use a custom package management approach for the AEAs rather than the default Python one as it provides us with more flexibility, especially when it comes to extension beyond the Python ecosystem. ## Python Dependencies of Packages Python dependencies of packages are specified in their respective configuration files under `dependencies`. They will be installed when `aea install` is run on an agent project. ## Create a Package If you want to create a package, you can use the CLI command `aea scaffold connection/contract/protocol/skill [name]` and this will create the package and put it inside the respective folder based on the command for example if we `scaffold` skill with the name `my_skill` it will be located inside the folder skills in the root directory of the agent (`my_aea/skills/my_skill`). ## Use Published Packages from the Registry If you want to use a finished package, you can use a package from the registry. There or two registries. The remote registry operated by Fetch.ai and a local registry stub. The local registry stub is a directory called `packages` which contains packages in a nested structure with authors on the top level, followed by the package type, then package name. An example of such a directory is the `packages` directory located in the AEA repository. The local registry is useful for development. You can use the CLI to interact with the registry. By default, the CLI points to the remote registry. You can point it to the local registry via the flag `--local`. ## Package Versioning By default, the AEA can only handle one version per package. That is, a project should never use both `some_author/some_package_name:0.1.0` and `some_author/some_package_name:0.2.0`. If two AEA packages with the same author and name but different versions are used in the same Python process, then only the code from one of the packages (generally not deterministic) will be available in `sys.modules`. This can lead to inconsistencies and exceptions at runtime. ================================================ FILE: docs/performance-benchmark.md ================================================ # Performance Benchmark Test AEA framework performance. ## What is it? The benchmark module is a set of tools to measure execution time, CPU load and memory usage of the AEA Python code. It produces text reports and draws charts to present the results. ## How does it Work? The framework: - spawns a dedicated process for each test run to execute the function to test. - measures CPU and RAM usage periodically. - waits for function exits or terminates them by timeout. - repeats test execution multiple times to get more accurate results. ## How to Use Steps to run a test: - Write a function you would like to test with all arguments you would like to parametrise, add some doc strings. - Split the function into two parts: 'prepare' and 'performance' part. The 'prepare' part will not be included in the measurement. - Add `BenchmarkControl` support, to notify framework to start measurement. - Import `TestCli` class, `TestCli().run(function_to_be_tested)` - Call it from console to get text results. ### Simple Example `cpuburn` - simple test of CPU load depends on idle sleep time. Shows how much CPU consumed during the execution. ``` python import time from benchmark.framework.benchmark import BenchmarkControl from benchmark.framework.cli import TestCli def cpu_burn(benchmark: BenchmarkControl, run_time=10, sleep=0.0001) -> None: """ Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop :return: None """ benchmark.start() start_time = time.time() while True: time.sleep(sleep) if time.time() - start_time >= run_time: break if __name__ == "__main__": TestCli(cpu_burn).run() ``` Run it with `python ./benchmark/cases/cpu_burn.py --help` to get help about usage. ``` bash Usage: cpu_burn.py [OPTIONS] [ARGS]... Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop :return: None ARGS is function arguments in format: `run_time,sleep` default ARGS is `10,0.0001` Options: --timeout FLOAT Executor timeout in seconds [default: 10.0] --period FLOAT Period for measurement [default: 0.1] -N, --num-executions INTEGER Number of runs for each case [default: 1] -P, --plot INTEGER X axis parameter idx --help Show this message and exit. ``` Run it with `python ./benchmark/cases/cpu_burn.py` to start with default parameters. ``` bash Test execution timeout: 10.0 Test execution measure period: 0.1 Tested function name: cpu_burn Tested function description: Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop :return: None Tested function argument names: ['run_time', 'sleep'] Tested function argument default values: [10, 0.0001] == Report created 2020-04-27 15:14:56.076549 == Arguments are `[10, 0.0001]` Number of runs: 1 Number of time terminated: 0 Time passed (seconds): 10.031443119049072 ± 0 cpu min (%): 0.0 ± 0 cpu max (%): 10.0 ± 0 cpu mean (%): 3.4 ± 0 mem min (kb): 53.98828125 ± 0 mem max (kb): 53.98828125 ± 0 mem mean (kb): 53.98828125 ± 0 ``` Here you can see test report for default arguments set. Run with multiple arguments set, multiple repeats and draw a chart on resources `python ./benchmark/cases/cpu_burn.py -N 5 -P 1 3,0.00001 3,0.001 3,0.01` Report is: ``` bash Test execution timeout: 10.0 Test execution measure period: 0.1 Tested function name: cpu_burn Tested function description: Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop :return: None Tested function argument names: ['run_time', 'sleep'] Tested function argument default values: [10, 0.0001] == Report created 2020-04-27 15:38:17.849535 == Arguments are `(3, 1e-05)` Number of runs: 5 Number of time terminated: 0 Time passed (seconds): 3.0087939262390138 ± 0.0001147521277690166 cpu min (%): 0.0 ± 0.0 cpu max (%): 11.0 ± 2.23606797749979 cpu mean (%): 6.2 ± 0.18257418583505522 mem min (kb): 54.0265625 ± 0.11180339887498948 mem max (kb): 54.0265625 ± 0.11180339887498948 mem mean (kb): 54.0265625 ± 0.11180339887498948 == Report created 2020-04-27 15:38:32.947308 == Arguments are `(3, 0.001)` Number of runs: 5 Number of time terminated: 0 Time passed (seconds): 3.014109659194946 ± 0.0004416575764579524 cpu min (%): 0.0 ± 0.0 cpu max (%): 8.0 ± 2.7386127875258306 cpu mean (%): 1.9986666666666666 ± 0.002981423969999689 mem min (kb): 53.9890625 ± 0.10431954926750306 mem max (kb): 53.9890625 ± 0.10431954926750306 mem mean (kb): 53.9890625 ± 0.10431954926750306 == Report created 2020-04-27 15:38:48.067511 == Arguments are `(3, 0.01)` Number of runs: 5 Number of time terminated: 0 Time passed (seconds): 3.0181806087493896 ± 0.0022409499756841883 cpu min (%): 0.0 ± 0.0 cpu max (%): 1.0 ± 2.23606797749979 cpu mean (%): 0.06666666666666667 ± 0.14907119849998599 mem min (kb): 53.9078125 ± 0.11487297672320501 mem max (kb): 53.9078125 ± 0.11487297672320501 mem mean (kb): 53.9078125 ± 0.11487297672320501 ``` Chart is drawn for argument 1: sleep: Char over argument 1 - sleep value The most interesting part is CPU usage, as you can see CPU usage decreases with increasing value of idle sleep. Memory usage and execution time can slightly differ per case execution. ## Requirements for Tested Function - The first function's argument has to be `benchmark: BenchmarkControl` which is passed by default by the framework. - All arguments except the fist one have to set default values. - Function doc string is required, it used for help information. - `benchmark.start()` has to be called once in the function body to start measurement. The timeout is counted from this point! - All the "prepare part" in the function that should not be measured has to be placed before `benchmark.start()` - Code to be measured has to go after `benchmark.start()` - Try to avoid infinitive loops and assume the test should exit after a while. ## Execution Options - To pass an arguments set just provide it as a comma separated string like `10,0.1` - To pass several argument sets just separate them by white space `10,0.1 20,0.2` - `--timeout FLOAT` is test execution timeout in seconds. If the test takes more time, it will be terminated. - `--period FLOAT` is measurement interval in seconds, how often to make CPU and RAM usage measurements. - `-N, --num-executions INTEGER` - how many times to run the same argument set to make result more accurate. - `-P, --plot INTEGER` - Draw a chart, using values in the argument on the X axis, argument positions started with 0, argument benchmark not counted. For example `-P 0` will use `run_time` values, `-P 1` will use `sleep` values, and so on. ## Limitations Currently, the benchmark framework does not measure resources consumed by subprocess spawned in python code. So try to keep one process solutions during tests. Asynchronous functions or coroutines are not supported directly. So you have to set up an event loop inside test function and start loop manually. ## Testing AEA: Handlers Example Test react speed on specific messages amount. ``` python def react_speed_in_loop(benchmark: BenchmarkControl, inbox_amount=1000) -> None: """ Test inbox message processing in a loop. :param benchmark: benchmark special parameter to communicate with executor :param inbox_amount: num of inbox messages for every agent :return: None """ skill_definition = { "handlers": {"dummy_handler": DummyHandler} } aea_test_wrapper = AEATestWrapper( name="dummy agent", skills=[skill_definition], ) for _ in range(inbox_amount): aea_test_wrapper.put_inbox(aea_test_wrapper.dummy_envelope()) aea_test_wrapper.set_loop_timeout(0.0) benchmark.start() aea_test_wrapper.start_loop() while not aea_test_wrapper.is_inbox_empty(): time.sleep(0.1) aea_test_wrapper.stop_loop() ``` Create AEA wrapper with specified handler: ``` python skill_definition = { "handlers": {"dummy_handler": DummyHandler} } aea_test_wrapper = AEATestWrapper( name="dummy agent", skills=[skill_definition], ) ``` Populate inbox with dummy messages: ``` python for _ in range(inbox_amount): aea_test_wrapper.put_inbox(aea_test_wrapper.dummy_envelope()) ``` Set timeout `0`, for maximum messages processing speed: `aea_test_wrapper.set_loop_timeout(0.0)` Start benchmark: `benchmark.start()` Start/stop AEA: ``` python aea_test_wrapper.start() ... aea_test_wrapper.stop() ``` Wait till messages present in inbox: ``` python while not aea_test_wrapper.is_inbox_empty(): time.sleep(0.1) ``` ================================================ FILE: docs/por.md ================================================ # Proof of Representation An AEA can use several key pairs. In particular, it can use different keys for securing its communication and for engaging in exchange. In the ACN we make use of this fact. To be able to signal to other agents that the address derived from one key pair is allowed to represent the agent controlling the other key pair, the key pair which is being represented must sign a message to prove that the other key pair is allowed to represent it. The `aea issue-certificates` command allows to create this association. The proof of representation feature is used in the context of the `fetchai/p2p_libp2p` and `fetchai/p2p_libp2p_client` connection. In the former connection, the configuration YAML specifies a `cert_requests` field: ``` yaml cert_requests: - identifier: acn ledger_id: fetchai not_after: '2023-01-01' not_before: '2022-01-01' public_key: fetchai message_format: '{public_key}' save_path: .certs/conn_cert.txt ``` The `identifier` refers to the environment for which the signature is generated, here `acn`. The `ledger_id` refers to the key pair to be used from the `private_key_paths` specified in `aea-config.yaml` for signing. The `not_after` and `not_before` fields specify constraints on the validity of the signature. The `public_key` can specify either the identifier of the key pair in `connection_private_key_paths`, of which the public key is signed, or it can contain the to be signed public key in plain text. The `save_path` specifies the path where the certificate is to be saved at. In the above example, the connection requests a certificate which is a signature of the `fetchai` public key in `connection_private_key_paths` with the `fetchai` key pair in `private_key_paths`. The validity of the signature will be constrained to the year `2021` for the environment `acn`. ================================================ FILE: docs/prometheus.md ================================================ # Prometheus Monitoring AEAs can create and update prometheus metrics for remote monitoring by sending messages to the prometheus connection `fetchai/prometheus:0.9.6`. To see this working in an agent, fetch and run the `coin_price_feed` agent and check `localhost:9090/metrics` to see the latest values of the metrics `num_retrievals` and `num_requests`: ``` bash aea fetch fetchai/coin_price_feed:0.15.5 cd coin_price_feed aea install aea build aea run ``` You can then instruct a prometheus server running on the same computing cluster as a deployed agent to scrape these metrics for remote monitoring and visualisation with the Prometheus/Grafana toolset. To use this connection, add a model `prometheus_dialogues` to your skill to handle the metrics configuration and messages to the prometheus connection. ??? note "Click here for example:" ``` python class PrometheusDialogues(Model, BasePrometheusDialogues): """The dialogues class keeps track of all prometheus dialogues.""" def __init__(self, **kwargs) -> None: """ Initialize dialogues. :return: None """ self.enabled = kwargs.pop("enabled", False) self.metrics = kwargs.pop("metrics", []) Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return PrometheusDialogue.Role.AGENT BasePrometheusDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ``` Then configure your metrics in the `skill.yaml` file. For example (from the `advanced_data_request` skill): ``` yaml models: prometheus_dialogues: args: enabled: true metrics: - name: num_retrievals type: Gauge description: Number of price quotes retrieved labels: {} - name: num_requests type: Gauge description: Number of price quote requests served labels: {} class_name: PrometheusDialogues ``` Add a metric `metric_name` of type `metric_type` {`Gauge`, `Counter`, ...} and description `description` by sending a message with performative `ADD_METRIC` to the prometheus connection: ``` python def add_prometheus_metric( self, metric_name: str, metric_type: str, description: str, labels: Dict[str, str], ) -> None: """ Add a prometheus metric. :param metric_name: the name of the metric to add. :param type: the type of the metric. :param description: a description of the metric. :param labels: the metric labels. :return: None """ # context prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) # prometheus update message message, _ = prom_dialogues.create( counterparty=str(PROM_CONNECTION_ID), performative=PrometheusMessage.Performative.ADD_METRIC, type=metric_type, title=metric_name, description=description, labels=labels, ) # send message self.context.outbox.put_message(message=message) ``` where `PROM_CONNECTION_ID` should be imported to your skill as follows: ``` python from packages.fetchai.connections.prometheus.connection import ( PUBLIC_ID as PROM_CONNECTION_ID, ) ``` Update metric `metric_name` with update function `update_func` {`inc`, `set`, `observe`, ...} and value `value` by sending a message with performative `UPDATE_METRIC` to the prometheus connection: ``` python def update_prometheus_metric( self, metric_name: str, update_func: str, value: float, labels: Dict[str, str], ) -> None: """ Update a prometheus metric. :param metric_name: the name of the metric. :param update_func: the name of the update function (e.g. inc, dec, set, ...). :param value: the value to provide to the update function. :param labels: the metric labels. :return: None """ # context prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) # prometheus update message message, _ = prom_dialogues.create( counterparty=str(PROM_CONNECTION_ID), performative=PrometheusMessage.Performative.UPDATE_METRIC, title=metric_name, callable=update_func, value=value, labels=labels, ) # send message self.context.outbox.put_message(message=message) ``` Initialize the metrics from the configuration file in the behaviour setup: ``` python def setup(self) -> None: """Implement the setup of the behaviour""" prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) if prom_dialogues.enabled: for metric in prom_dialogues.metrics: self.context.logger.info("Adding Prometheus metric: " + metric["name"]) self.add_prometheus_metric( metric["name"], metric["type"], metric["description"], dict(metric["labels"]), ``` Then call the `update_prometheus_metric` function from the appropriate places. For example, the following code in `handlers.py` for the `advanced_data_request` skill updates the number of http requests served: ``` python if self.context.prometheus_dialogues.enabled: self.context.behaviours.advanced_data_request_behaviour.update_prometheus_metric( "num_requests", "inc", 1.0, {} ) ``` Finally, you can add a `PrometheusHandler` to your skill to process response messages from the prometheus connection. ??? note "Click here for example:" ``` python class PrometheusHandler(Handler): """This class handles responses from the prometheus server.""" SUPPORTED_PROTOCOL = PrometheusMessage.protocol_id def __init__(self, **kwargs): """Initialize the handler.""" super().__init__(**kwargs) self.handled_message = None def setup(self) -> None: """Set up the handler.""" if self.context.prometheus_dialogues.enabled: self.context.logger.info("setting up PrometheusHandler") def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ message = cast(PrometheusMessage, message) # recover dialogue prometheus_dialogues = cast( PrometheusDialogues, self.context.prometheus_dialogues ) prometheus_dialogue = cast( PrometheusDialogue, prometheus_dialogues.update(message) ) if prometheus_dialogue is None: self._handle_unidentified_dialogue(message) return self.handled_message = message if message.performative == PrometheusMessage.Performative.RESPONSE: self.context.logger.debug( f"Prometheus response ({message.code}): {message.message}" ) else: self.context.logger.debug( f"got unexpected prometheus message: Performative = {PrometheusMessage.Performative}" ) def _handle_unidentified_dialogue(self, msg: Message) -> None: """ Handle an unidentified dialogue. :param msg: the unidentified message to be handled :return: None """ self.context.logger.info( "received invalid message={}, unidentified dialogue.".format(msg) ) def teardown(self) -> None: """ Teardown the handler. :return: None """ ``` ================================================ FILE: docs/protocol-generator.md ================================================ # Generating Protocols ## How to Run First make sure you are inside your AEA's folder (see here on how to create a new agent). Then run ``` bash aea generate protocol ``` where `` is the path to a protocol specification file. If there are no errors, this command will generate the protocol and place it in your AEA project. The name of the protocol's directory will match the protocol name given in the specification. The author will match the registered author in the CLI. The generator currently produces the following files (assuming the name of the protocol in the specification is `sample`): 1. `message.py`: defines messages valid under the `sample` protocol 2. `serialisation.py`: defines how messages are serialized/deserialized 3. `__init__.py`: makes the directory a package 4. `protocol.yaml`: contains package information about the `sample` protocol 5. `sample.proto` protocol buffer schema file 6. `sample_pb2.py`: the generated protocol buffer implementation 7. `custom_types.py`: stub implementations for custom types (created only if the specification contains custom types) ### Full Mode vs Protobuf Only Mode Currently, the generator can operate in _full mode_ for Python, creating a complete protocol package (files 1 to 7 above) from a protocol specification. The generator also has a _protobuf only mode_ which only creates the protocol buffer schema and implementation files (files 5 and 6 above). The languages supported in the _protobuf only mode_ and their respective ids are below: - go: `go` - c++: `cpp` - java: `java` - c#: `csharp` - ruby: `ruby` - objective-c: `objc` - javascript: `js` To use the generator in protobuf only mode for any of the above languages: ``` bash aea generate protocol --l ``` where `` is a language id. The protocol buffer compiler requires a plugin to generate Go code. Install it with: !!! note Note the protocol buffer compiler `protoc` that the generator uses requires a plugin to produce `go` code. Follow this instruction. ## Protocol Specification A protocol can be described in a YAML file. This is called a _protocol specification_. The following is an example protocol specification: ``` yaml --- name: two_party_negotiation author: fetchai version: 0.1.0 description: An example of a protocol specification that describes a protocol for bilateral negotiation. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' speech_acts: cfp: query: ct:Query propose: price: pt:float proposal: pt:dict[pt:str, pt:str] conditions: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:str], pt:set[pt:str]]] resources: pt:list[pt:bytes] accept: {} decline: {} ... --- ct:Query: | bytes query_bytes = 1; ... --- initiation: [cfp] reply: cfp: [propose, decline] propose: [propose, accept, decline] accept: [] decline: [] termination: [accept, decline] roles: {buyer, seller} end_states: [agreement_reached, agreement_unreached] keep_terminal_state_dialogues: true ... ``` Each protocol specification must follow the YAML format, and have a minimum of one and a maximum of three YAML documents (each YAML document is enclosed within --- and ...). ### Basic Protocol Detail and Messages Syntax The first YAML document is mandatory in any protocol specification. It contains some basic information about the protocol and describes the syntax of communicative messages allowed under this protocol. The allowed fields and what they represent are: - `name`: The name of the protocol (written in snake_case) - `author`: The creator of the protocol - `version`: The current version of the protocol - `license`: Licensing information - `aea_version`: The version(s) of the framework that support this protocol. The format is described here. - `description`: A short description of the protocol - `protocol_specification_id`: The id which identifies the protocol for over-the-wire transport. This id is decoupled from the `protocol_id` (`{author}/{name}:{version}`) which is tied to the Python implementation. All of the above fields are mandatory and each is a key/value pair, where both key and value are YAML strings. Additionally, the first YAML document of a protocol specification must describe the syntax of valid messages according to this protocol. Therefore, it must contain another mandatory `speech-acts` field which defines the set of _performatives_ valid under this protocol, and a set of _contents_ for each performative. A _performative_ defines the _type_ of a message (e.g. propose, accept) and has a set of _contents_ (or parameters) of varying types. The format of the `speech-act` is as follows: `speech-act` is a dictionary, where each key is a **unique** _performative_ (YAML string), and the value is a _content_ dictionary. If a performative does not have any content, then its content dictionary is empty, for instance `accept` and `decline` in the specification above. A content dictionary in turn has key/value pairs, where each key is the name of a content (YAML string) and the value is its type (YAML string). For example, the `cfp` (short for 'call for proposal') performative has one content whose name is `query` and whose type is `ct:Query`. #### Types The specific types which could be assigned to contents in a protocol specification are described in the table below. Types are either user defined (i.e. custom types) or primitive: - Custom types are prepended with `ct:` and their format is described using regular expression in the table below. - Primitive types are prepended with `pt:`. There are different categories of primitive types. For example, `` such as integers and booleans, `` such as sets and lists, and so on. Primitive types are compositional: - For example, consider `pt:set[...]` under ``, i.e. an unordered collection of elements without duplicates. A `pt:set[...]` describes the type of its elements (called "sub-type") in square brackets. The subtype of a `pt:set[...]` must be a `` (e.g. `pt:int`, `pt:bool`). - In describing the format of types, `/` between two subtypes should be treated as "or". For example, the subtype of a `pt:optional[...]` is either a ``, ``, ``, `` or ``. A multi type denotes an "or" separated set of subtypes. For example, a content whose type is specified as `pt:union[pt:str, pt:int]` should either be `pt:int` or `pt:float`. An optional type `pt:optional[...]` assigned to a content means the content's existence is optional, but if it is present, its type must match `pt:optional[...]`'s subtype. | Type | Code | Format | Example | In Python | |-------------------------------------|---------|---------------------------------------------------------------|------------------------------------------|------------------------------------| | Custom types1 | `` | `ct:RegExp(^[A-Z][a-zA-Z0-9]*$)` | `ct:DataModel` | Custom Class | | Primitive types | `` | `pt:bytes` | `pt:bytes` | `bytes` | | | | `pt:int` | `pt:int` | `int` | | | | `pt:float` | `pt:float` | `float` | | | | `pt:bool` | `pt:bool` | `bool` | | | | `pt:str` | `pt:str` | `str` | | Primitive collection types | `` | `pt:set[]` | `pt:set[pt:str]` | `FrozenSet[str]` | | | | `pt:list[]` | `pt:list[pt:int]` | `Tuple[int, ...]`* | | Primitive mapping types2 | `` | `pt:dict[, ]` | `pt:dict[pt:str, pt:bool]` | `Dict[str, bool]` | | Multi types | `` | `pt:union[///, ..., ///]` | `pt:union[ct:DataModel, pt:set[pt:str]]` | `Union[DataModel, FrozenSet[str]]` | | Optional types | `` | `pt:optional[////]` | `pt:optional[pt:bool]` | `Optional[bool]` | * This is how variable length tuples containing elements of the same type are declared in Python; see here. ### Protocol Buffer Schema Currently, the AEA framework does not officially support describing custom types in a programming language independent format. This means that if a protocol specification includes custom types, the required serialisation logic must be provided manually. Therefore, if any of the contents declared in `speech-acts` is of a custom type, the specification must then have a second YAML document, containing the protocol buffer schema code for each custom type. You can see an example of the second YAML document in the above protocol specification. ### Dialogues You can optionally specify the structure of dialogues conforming to your protocol in a third YAML document in the specification. The allowed fields and what they represent are: - `initiation`: The list of initial performatives - `reply`: The reply structure of speech-acts - `termination`: The list of terminal performatives - `roles`: The roles of players participating in a dialogue - `end_states`: The possible outcomes a terminated dialogue. - `keep_terminal_state_dialogues`: whether to keep or drop a terminated dialogue. When a storage backend is configured, the dialogues will be persisted in storage when kept. All of the above fields are mandatory. `initiation` is a YAML list, containing the performatives which can be used to start a dialogue. `reply` specifies for every performative, what its valid replies are. If a performative `per_1` is a valid reply to another `per_2`, this means a message with performative `per_1` can target a message whose performative is `per_2`. `reply` is a YAML dictionary, where the keys are the performatives (YAML string) defined in `speech-acts`. For each performative key, its value is a list of performatives which are defined to be a valid reply. For example, valid replies to `cfp` are `propose` and `decline`. `termination` is a YAML list, containing the performatives which terminate a dialogue. Once any of these performatives are used in a dialogue, the dialogue is terminated and no other messages may be added to it. `roles` is a YAML set, containing the roles players participating in dialogues can take. `roles` may contain one or two roles, each role being a YAML string. If there are two roles, each participant has a distinguished role in the dialogue (e.g. buyer and seller in the above specification). If there is only one role, then both participants in a dialogue have this same role. `end_states` lists the final states a terminated dialogue may have. `end_states` is a YAML list of strings. `keep_terminal_state_dialogues` has a boolean value and specifies whether the terminated dialogues of this protocol are to be kept or discarded. ## Design Guidelines 1. `initiation` and `termination` cannot be empty. 2. Make sure that when defining `reply`, you include every speech-act you specified under `speech_acts`. If any of the speech-acts does not have a reply, indicate that with an empty list `[]` similar to `accept` and `decline` in the specification above. 3. If a speech-act is listed in `termination`, it must not have any replies in `reply`. The reason is simple: a terminal speech-act terminates a dialogue and so its reply can never be used. 4. If a speech-act replies to no other speech-acts, it should be listed in `initiation` otherwise it could never be used in a dialogue (neither to a start a dialogue with, nor as a reply to another speech-act). ### Notes 1. Currently, there is no way to describe custom types in a programming language independent format. This means that if a protocol specification includes custom types, the required implementations must be provided manually. _ Before generating the protocol, the protocol buffer schema code for every custom type must be provided in the protocol specification. - Once the generator is called, it produces a `custom_types` module containing stub implementations for every custom type in the specification. The user must then modify this module and add implementations for every custom type in the specification. This includes implementations of how an object of a custom type can be encoded and decoded using protocol buffer. - Note, currently the way custom types are dealt with in the generator is admittedly inconvenient. The reason is, the generator does not know the structure of custom types and how they may be serialized/deserialized. Although this approach works, it is only a temporary solution until further work on a programming language-independent type description language is finished (similar to how the generator is designed to be a programming language-independent protocol description language). 2. Currently, the first element in `pt:dict` cannot be a ``, `pt:float` or `pt:bytes`. This is because of a constraint in protocol buffer version 3 which is the framework's underlying serialisation mechanism. In a future version, we may address this limitation, in which case we will relax this constraint. 3. In protocol buffer version 3, which is the version used by the generator, there is no way to check whether an optional field (i.e. contents of type `pt:optional[...]`) has been set or not (see discussion here). In proto3, all optional fields are assigned a default value (e.g. `0` for integers types, `false` for boolean types, etc). Therefore, given an optional field whose value is the default value, there is no way to know from the optional field itself, whether it is not set, or in fact is set but its value happens to be the default value. Because of this, in the generated protocol schema file (the `.proto` file), for every optional content there is a second field that declares whether this field is set or not. We will maintain this temporary solution until a cleaner alternative is found. 4. Be aware that currently, using the generated protocols in python, there might be some rounding errors when serialising and then deserializing values of `pt:float` contents. ## Demo Instructions First, create a new AEA project: ``` bash aea create my_aea cd my_aea ``` Second, run the generator on the sample specification: ``` bash aea generate protocol ../examples/protocol_specification_ex/sample.yaml ``` This will generate the protocol and place it in your AEA project. Third, try generating other protocols by first defining a specification, then running the generator. ================================================ FILE: docs/protocol.md ================================================ # Protocols `Protocols` define the structure of agent-to-agent and component-to-component interactions, which in the AEA world, are in the form of communication. To learn more about interactions and interaction protocols, see here. Protocols in the AEA world provide definitions for: - `messages` defining the structure and syntax of messages; - `serialization` defining how a message is encoded/decoded for transport; and optionally - `dialogues` defining the structure of dialogues formed from exchanging series of messages. Protocol simplified The framework provides a `default` protocol. This protocol provides a bare-bones implementation for an AEA protocol which includes a `DefaultMessage` class and associated `DefaultSerializer` and `DefaultDialogue` classes. Additional protocols - i.e. a new type of interaction - can be added as packages or generated with the protocol generator. We highly recommend you to **not** attempt writing your protocol manually as they tend to have involved logic; always use existing packages or the protocol generator! ## Components of a Protocol A protocol package contains the following files: - `__init__.py` - `message.py`, which defines message representation - `serialization.py`, which defines the encoding and decoding logic - two protobuf related files It optionally also contains - `dialogues.py`, which defines the structure of dialogues formed from the exchange of a series of messages - `custom_types.py`, which defines custom types All protocols are for point to point interactions between two agents or agent-like services. ## Metadata Each `Message` in an interaction protocol has a set of default fields: - `dialogue_reference: Tuple[str, str]`, a reference of the dialogue the message is part of. The first part of the tuple is the reference assigned to by the agent who first initiates the dialogue (i.e. sends the first message). The second part of the tuple is the reference assigned to by the other agent. The default value is `("", "")`. - `message_id: int`, the identifier of the message in a dialogue. The default value is `1`. - `target: int`, the id of the message this message is replying to. The default value is `0`. - `performative: Enum`, the purpose/intention of the message. - `sender: Address`, the address of the sender of this message. - `to: Address`, the address of the receiver of this message. The default values for `message_id` and `target` assume the message is the first message in a dialogue. Therefore, the `message_id` is set to `1` indicating the first message in the dialogue and `target` is `0` since the first message is the only message that does not reply to any other. By default, the values of `dialogue_reference`, `message_id`, `target` are set. However, most interactions involve more than one message being sent as part of the interaction and potentially multiple simultaneous interactions utilising the same protocol. In those cases, the `dialogue_reference` allows different interactions to be identified as such. The `message_id` and `target` are used to keep track of messages and their replies. For instance, on receiving of a message with `message_id=1` and `target=0`, the responding agent could respond with another with `message_id=2` and `target=1` replying to the first message. In particular, `target` holds the id of the message being replied to. This can be the preceding message, or an older one. ## Contents Each message may optionally have any number of contents of varying types. ## Dialogue Rules Protocols can optionally have a dialogue module. A _dialogue_, respectively _dialogues_ object, maintains the state of a single, respectively, all dialogues associated with a protocol. The framework provides a number of helpful classes which implement most of the logic to maintain dialogues, namely the `Dialogue` and `Dialogues` base classes. ## Custom Protocol The developer can generate custom protocols with the protocol generator. This lets the developer specify the speech-acts as well as optionally the dialogue structure (e.g. roles of agents participating in a dialogue, the states a dialogue may end in, and the reply structure of the speech-acts in a dialogue). We highly recommend you **do not** attempt to write your own protocol code; always use existing packages or the protocol generator! ## `fetchai/default:1.1.7` Protocol The `fetchai/default:1.1.7` protocol is meant to be implemented by every AEA. It serves AEA to AEA interaction and includes three message performatives: ``` python from enum import Enum class Performative(Enum): """Performatives for the default protocol.""" BYTES = "bytes" END = "end" ERROR = "error" def __str__(self): """Get the string representation.""" return self.value ``` - The `DefaultMessage` of performative `DefaultMessage.Performative.BYTES` is used to send payloads of byte strings to other AEAs. An example is: ``` python from packages.fetchai.protocols.default.message import DefaultMessage msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content=b"This is a bytes payload", ) ``` - The `DefaultMessage` of performative `DefaultMessage.Performative.ERROR` is used to notify other AEAs of errors in an interaction, including errors with other protocols, by including an `error_code` in the payload: ``` python class ErrorCode(Enum): """This class represents an instance of ErrorCode.""" UNSUPPORTED_PROTOCOL = 0 DECODING_ERROR = 1 INVALID_MESSAGE = 2 UNSUPPORTED_SKILL = 3 INVALID_DIALOGUE = 4 ``` An example is: ``` python msg = DefaultMessage( performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="This protocol is not supported by this AEA.", error_data={"unsupported_msg": b"serialized unsupported protocol message"}, ) ``` - The `DefaultMessage` of performative `DefaultMessage.Performative.END` is used to terminate a default protocol dialogue. An example is: ``` python from packages.fetchai.protocols.default.message import DefaultMessage msg = DefaultMessage( performative=DefaultMessage.Performative.END, ) ``` Each AEA's `fetchai/error:0.18.6` skill utilises the `fetchai/default:1.0.0` protocol for error handling. ## `fetchai/oef_search:1.1.7` Protocol The `fetchai/oef_search:1.1.7` protocol is used by AEAs to interact with an SOEF search node to register and unregister their own services and search for services registered by other agents. The `fetchai/oef_search:1.1.7` protocol definition includes an `OefSearchMessage` with the following message types: ``` python class Performative(Enum): """Performatives for the oef_search protocol.""" REGISTER_SERVICE = "register_service" UNREGISTER_SERVICE = "unregister_service" SEARCH_SERVICES = "search_services" OEF_ERROR = "oef_error" SEARCH_RESULT = "search_result" SUCCESS = "success" def __str__(self): """Get string representation.""" return self.value ``` We show some example messages below: - To register a service, we require a reference to the dialogue in string form (used to keep different dialogues apart), for instance ``` python my_dialogue_reference = "a_unique_register_service_dialogue_reference" ``` and a description of the service we would like to register, for instance ``` python from aea.helpers.search.models import Description my_service_data = {"country": "UK", "city": "Cambridge"} my_service_description = Description( my_service_data, data_model=my_data_model, ) ``` where we use, for instance ``` python from aea.helpers.search.generic import GenericDataModel data_model_name = "location" data_model = { "attribute_one": { "name": "country", "type": "str", "is_required": True, }, "attribute_two": { "name": "city", "type": "str", "is_required": True, }, } my_data_model = GenericDataModel(data_model_name, data_model) ``` We can then create the message to register this service: ``` python msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, dialogue_reference=(my_dialogue_reference, ""), service_description=my_service_description, ) ``` - To unregister a service, we require a reference to the dialogue in string form, for instance ``` python my_dialogue_reference = "a_unique_unregister_service_dialogue_reference" ``` the description of the service we would like to unregister, say `my_service_description` from above and construct the message: ``` python msg = OefSearchMessage( performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, dialogue_reference=(my_dialogue_reference, ""), service_description=my_service_description, ) ``` - To search a service, we similarly require a reference to the dialogue in string form, and then the query we would like the search node to evaluate, for instance ``` python from aea.helpers.search.models import Constraint, ConstraintType, Query query_data = { "search_term": "country", "search_value": "UK", "constraint_type": "==", } query = Query( [ Constraint( query_data["search_term"], ConstraintType( query_data["constraint_type"], query_data["search_value"], ), ) ], model=None, ) ``` We can then create the message to search these services: ``` python oef_msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, dialogue_reference=(my_dialogue_reference, ""), query=query, ) ``` - The SOEF search node will respond with a message `msg` of type `OefSearchMessage` with performative `OefSearchMessage.Performative.SEARCH_RESULT`. To access the tuple of agents which match the query, simply use `msg.agents`. In particular, this will return the agent addresses matching the query. The agent address can then be used to send a message to the agent utilising the P2P agent communication network and any protocol other than `fetchai/oef_search:1.0.0`. - If the SOEF search node encounters any errors with the messages you send, it will return an `OefSearchMessage` of performative `OefSearchMessage.Performative.OEF_ERROR` and indicate the error operation encountered: ``` python class OefErrorOperation(Enum): """This class represents an instance of OefErrorOperation.""" REGISTER_SERVICE = 0 UNREGISTER_SERVICE = 1 SEARCH_SERVICES = 2 SEND_MESSAGE = 3 OTHER = 10000 ``` ## `fetchai/fipa:1.1.7` Protocol This protocol provides classes and functions necessary for communication between AEAs via a variant of the FIPA Agent Communication Language. The `fetchai/fipa:1.1.7` protocol definition includes a `FipaMessage` with the following performatives: ``` python class Performative(Enum): """Performatives for the fipa protocol.""" ACCEPT = "accept" ACCEPT_W_INFORM = "accept_w_inform" CFP = "cfp" DECLINE = "decline" END = "end" INFORM = "inform" MATCH_ACCEPT = "match_accept" MATCH_ACCEPT_W_INFORM = "match_accept_w_inform" PROPOSE = "propose" def __str__(self): """Get the string representation.""" return self.value ``` `FipaMessages` are constructed with a `performative`, `dialogue_reference`, `message_id`, and `target` as well as the `kwargs` specific to each message performative. ``` python def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs, ) ``` The `fetchai/fipa:1.1.7` protocol also defines a `FipaDialogue` class which specifies the valid reply structure and provides other helper methods to maintain dialogues. For examples of the usage of the `fetchai/fipa:1.1.7` protocol check out the generic skills step by step guide. ### Fipa Dialogue Below, we give an example of a dialogue between two agents. In practice; both dialogues would be maintained in the respective agent. We first create concrete implementations of `FipaDialogue` and `FipaDialogues` for the buyer and seller: ``` python from aea.common import Address from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel from packages.fetchai.protocols.fipa.dialogues import FipaDialogue, FipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage class BuyerDialogue(FipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ FipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self.proposal = None # type: Optional[Description] class BuyerDialogues(FipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseFipaDialogue.Role.BUYER FipaDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class SellerDialogue(FipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ FipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self.proposal = None # type: Optional[Description] class SellerDialogues(FipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.SELLER FipaDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) ``` Next, we can imitate a dialogue between the buyer and the seller. We first instantiate the dialogues models: ``` python buyer_address = "buyer_address_stub" seller_address = "seller_address_stub" buyer_dialogues = BuyerDialogues(buyer_address) seller_dialogues = SellerDialogues(seller_address) ``` First, the buyer creates a message destined for the seller and updates the dialogues: ``` python cfp_msg = FipaMessage( message_id=1, dialogue_reference=buyer_dialogues.new_self_initiated_dialogue_reference(), target=0, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) cfp_msg.counterparty = seller_addr # Extends the outgoing list of messages. buyer_dialogue = buyer_dialogues.update(cfp_msg) ``` If the message has been correctly constructed, the `buyer_dialogue` will be returned, otherwise it will be `None`. In a skill, the message could now be sent: ``` python # In a skill we would do: # self.context.outbox.put_message(message=cfp_msg) ``` However, here we simply continue with the seller: ``` python # change the incoming message field & counterparty cfp_msg.is_incoming = True cfp_msg.counterparty = buyer_address ``` In the skill, the above two lines will be done by the framework; you can simply receive the message in the handler. We update the seller's dialogues model next to generate a new dialogue: ``` python # Creates a new dialogue for the seller side based on the income message. seller_dialogue = seller_dialogues.update(cfp_msg) ``` Next, the seller can generate a proposal: ``` python # Generate a proposal message to send to the buyer. proposal = Description({"foo1": 1, "bar1": 2}) message_id = cfp_msg.message_id + 1 target = cfp_msg.message_id proposal_msg = FipaMessage( message_id=message_id, dialogue_reference=seller_dialogue.dialogue_label.dialogue_reference, target=target, performative=FipaMessage.Performative.PROPOSE, proposal=proposal, ) proposal_msg.counterparty = cfp_msg.counterparty # Then we update the dialogue seller_dialogue.update(proposal_msg) ``` In a skill, the message could now be sent: ``` python # In a skill we would do: # self.context.outbox.put_message(message=proposal_msg) ``` The dialogue can continue like this. To retrieve a dialogue for a given message, we can do the following: ``` python retrieved_dialogue = seller_dialogues.get_dialogue(cfp_msg) ``` ================================================ FILE: docs/query-language.md ================================================ # The Query Language We recommend reading Defining a Data Model before reading this section. Along with the Data Model language, the AEA framework offers the possibility to specify _queries_ defined over data models. The `aea.helpers.search` module implements the API that allows you to build queries. In one sentence, a `Query` is a set of _constraints_, defined over a _data model_. The outcome is a set of _description_ (that is, instances of `Description`) _matching_ with the query. That is, all the description whose attributes satisfy the constraints in the query. In the next sections, we describe how to build queries. ## Constraints A `Constraint` is associated with an _attribute name_ and imposes restrictions on the domain of that attribute. That is, it imposes some limitations on the values the attribute can assume. We have different types of constraints: - _relation_ constraints: - the author of the book must be _Stephen King_ - the publication year must be greater than 1990 - _set_ constraints: - the genre must fall into the following set of genres: _Horror_, _Science fiction_, _Non-fiction_. - _range_ constraints: - the average rating must be between 3.5 and 4.5 - _distance_ constraints: - the nearest bookshop must be within a distance from a given location. The class that implements the constraint concept is `Constraint` In the following, we show how to define them. ### Relation There are several `ConstraintTypes` that allows you to impose specific values for the attributes. The types of relation constraints are: - Equal: `==` - Not Equal: `!=` - Less than: `<` - Less than or Equal: `<=` - Greater than: `>` - Greater than or Equal: `>=` **Examples**: using the attributes we used before: ``` python from aea.helpers.search.models import Constraint, ConstraintType # all the books whose author is Stephen King Constraint("author", ConstraintType("==", "Stephen King")) # all the books that are not of the genre Horror Constraint("genre", ConstraintType("!=", "Horror")) # all the books published before 1990 Constraint("year", ConstraintType("<", 1990)) # the same of before, but including 1990 Constraint("year", ConstraintType("<=", 1990)) # all the books with rating greater than 4.0 Constraint("average_rating", ConstraintType(">", 4.0)) # all the books published after 2000, included Constraint("year", ConstraintType(">=", 2000)) ``` ### Set The _set_ is a constraint type that allows you to restrict the values of the attribute in a specific set. There are two kind of _set_ constraints: - In (a set of values): `in` - Not in (a set of values): `not_in` **Examples**: ``` python from aea.helpers.search.models import Constraint, ConstraintType # all the books whose genre is one of `Horror`, `Science fiction`, `Non-fiction` Constraint("genre", ConstraintType("in", ("horror", "science fiction", "non-fiction"))) # all the books that have not been published neither in 1990, nor in 1995, nor in 2000 Constraint("year", ConstraintType("not_in", (1990, 1995, 2000))) ``` ## Range The _range_ is a constraint type that allows you to restrict the values of the attribute in a given range. **Examples**: ``` python from aea.helpers.search.models import Constraint, ConstraintType # all the books whose title is between 'A' and 'B' (alphanumeric order) Constraint("title", ConstraintType("within", ("A", "B"))) # all the books that have been published between 1960 and 1970 Constraint("genre", ConstraintType("within", (1960, 1970))) ``` ### Distance The _distance_ is a constraint type that allows you to put a limit on a `Location` attribute type. More specifically, you can set a maximum distance from a given location (the _centre_), such that will be considered only the instances whose location attribute value is within a distance from the centre. **Examples**: ``` python from aea.helpers.search.models import Constraint, ConstraintType, Description, Location # define a location of interest, e.g. the Tour Eiffel tour_eiffel = Location(48.8581064, 2.29447) # find all the locations close to the Tour Eiffel within 1 km close_to_tour_eiffel = Constraint("position", ConstraintType("distance", (tour_eiffel, 1.0))) # Le Jules Verne, a famous restaurant close to the Tour Eiffel, satisfies the constraint. le_jules_verne_restaurant = Location(48.8579675, 2.2951849) close_to_tour_eiffel.check(Description({"position": le_jules_verne_restaurant})) # gives `True` # The Colosseum does not satisfy the constraint (farther than 1 km from the Tour Eiffel). colosseum = Location(41.8902102, 12.4922309) close_to_tour_eiffel.check(Description({"position": colosseum})) # gives `False` ``` ## Constraint Expressions The constraints mentioned above can be combined with the common logical operators (i.e. and, or and not), yielding more complex expression. In particular, we can specify any conjunction/disjunction/negations of the previous constraints or composite `ConstraintExpressions`, e.g.: - books that belong to _Horror_ **and** has been published after 2000, but **not** published by _Stephen King_. - books whose author is **either** _J. K. Rowling_ **or** _J. R. R. Tolkien_ The classes that implement these operators are `Not`, `And` and `Or`. ### Not The `Not` is a constraint expression that allows you to specify a negation of a constraint expression. The `Not` constraint is satisfied whenever its subexpression is _not_ satisfied. **Example**: ``` python from aea.helpers.search.models import Constraint, ConstraintType, Not # all the books whose year of publication is not between 1990 and 2000 Not(Constraint("year", ConstraintType("within", (1990, 2000)))) ``` ### And The `And` is a constraint type that allows you to specify a conjunction of constraints over an attribute. That is, the `And` constraint is satisfied whenever all the subexpressions that constitute the _and_ are satisfied. Notice: the number of subexpressions must be **at least** 2. **Example**: ``` python from aea.helpers.search.models import Constraint, ConstraintType, And # all the books whose title is between 'I' and 'J' (alphanumeric order) but not equal to 'It' And([Constraint("title", ConstraintType("within", ("I", "J"))), Constraint("title", ConstraintType("!=", "It"))]) ``` ### Or The class `Or` is a constraint type that allows you to specify a disjunction of constraints. That is, the `Or` constraint is satisfied whenever at least one of the constraints that constitute the `or` is satisfied. Notice: the number of subexpressions must be **at least** 2. **Example**: ``` python from aea.helpers.search.models import Constraint, ConstraintType, Or # all the books that have been published either before the year 1960 or after the year 1970 Or([Constraint("year", ConstraintType("<", 1960)), Constraint("year", ConstraintType(">", 1970))]) ``` ## Queries A _query_ is simply a _list of constraint expressions_, interpreted as a conjunction (that is, a matching description with the query must satisfy _every_ constraint expression.) **Examples**: ``` python from aea.helpers.search.models import Query, Constraint, ConstraintType # query all the books written by Stephen King published after 1990, and available as an e-book: Query([ Constraint("author", ConstraintType("==", "Stephen King")), Constraint("year", ConstraintType(">=", 1990)), Constraint("ebook_available", ConstraintType("==", True)) ], book_model) ``` Where `book_model` is the `DataModel` object. However, the data model is an optional parameter, but to avoid ambiguity is recommended to include it. ### The ``check`` Method The `Query` class supports a way to check whether a `Description` matches with the query. This method is called `Query.check`. Examples: ``` python from aea.helpers.search.models import Query, Constraint, ConstraintType from aea.helpers.search.models import Description q = Query([ Constraint("author", ConstraintType("==", "Stephen King")), Constraint("year", ConstraintType(">=", 1990)), Constraint("ebook_available", ConstraintType("==", True)) ]) # With a query, you can check that a `Description` object satisfies the constraints. q.check(Description({"author": "Stephen King", "year": 1991, "ebook_available": True})) # True q.check(Description({"author": "George Orwell", "year": 1948, "ebook_available": False})) # False ``` ### Validity A `Query` object must satisfy some conditions in order to be instantiated. - The list of constraints expressions can't be empty; must have at least one constraint expression. - If the data model is specified: - For every constraint expression that constitute the query, check if they are _valid with respect to the data model_. A `ConstraintExpr` `c` (that is, one of `And`, `Or`, `Not`, `Constraint`) is _valid with respect to a_ `DataModel` if: - If `c` is an instance of `And`, `Or` or `Not`, then every subexpression of `c` must be valid (with respect to the data model); - If `c` is an instance of `Constraint`, then: - if the constraint type is one of `<`, `<=`, `>`, `>=`, the value in the constructor must be one of `str`, `int` or `float`. - if the constraint type is a `within`, then the types in the range must be one of `int`, `str`, `float` or `Location`. - if the constraint type is a `distance`, then the only valid type is `Location`. - if the constraint type is a `in`, then the types supported are `str`, `int`, `float`, `bool`, `Location`. Notice though that a set of ``bool`` is trivial, so you may find yourself more comfortable by using other alternatives. - for the other constraint types, i.e. `==` and `!=`, the value can be one of the allowed types for `Attribute`, that is `str`, `int`, `float`, `bool`, `Location`. - Moreover, when `c` is a `Constraint`, the attribute must have a consistent type with respect to the data model. E.g. consider a `Constraint` like: ``` python Constraint("foo", ConstraintType("==", True)) ``` Consider a `DataModel` where there is an `Attribute` `"foo"` of type `str`. Then the constraint is not compatible with the mentioned data model, because the constraint expect an equality comparison with a boolean `True`, instead of a `str`. ================================================ FILE: docs/questions-and-answers.md ================================================ # Q&A ??? question "What is an AEA?" AEA stands for "Autonomous Economic Agent". An AEA can represent an individual, organisation or object and looks after its owner's interests. AEAs act independently of constant user input and autonomously execute actions to achieve their prescribed goals. Their purpose is to create economic value for their owners. ??? question "How do AEAs talk to each other when they do not know each other?" For an Autonomous Economic Agent (AEA) to talk to other AEAs, it first needs to find them. Once it does, it should ensure that they both use the same protocol for communication, and if so, they then have to send messages to each other. The AEA framework, together with some of the services it provides, address all three problems. You can read more about search and discovery here, protocols here, and the Agent Communication Network (ACN) here. ??? question "How does an AEA use blockchain?" The AEA framework enables agents to interact with blockchains to settle transactions. Currently, the framework has native support for three different networks: _Fetch.ai_, _Ethereum_ and _Cosmos_. You can read more about the framework's integration with the different blockchains here and gain a high level overview here. ??? question "How does one install third party libraries?" The framework supports the use of third-party libraries hosted on PyPI. You can directly reference the external dependencies of an AEA package (e.g. skill) in its configuration file. From inside an AEA's project directory, the `install` command can be used to install all the dependencies of the AEA which are listed in the configuration files belonging to any of its packages. ??? question "How does one connect to a database?" You have two options to connect to a database: using the built-in storage solution or using a custom ORM (object-relational mapping) library and backend. The use of the built-in storage is explained here. For a detailed example of how to use an ORM, follow the ORM guide. ??? question "How does one connect a frontend?" There are multiple options. The most obvious is using an HTTP server connection and creating a client that communicates with this connection. You can find a more detailed discussion here. ??? question "Is the AEA framework ideal for agent-based modelling?" The goal of agent-based modelling (ABM) is to study the unknown (often complex) behaviour of systems comprised of agents with known (much simpler) behaviour. ABM is a popular technique for studying biological and social systems. Despite some similarities between ABM and the AEA framework, the two have fundamentally different goals. ABM's goal is not the design of agents or solving specific practical or engineering problems. Although it would be potentially possible, it would likely be inefficient to use the AEA framework for that kind of problems. You can find more details on the application areas of the AEA framework here. ??? question "When a new AEA is created, is the `vendor` folder populated with some default packages?" All AEA projects by default hold the `fetchai/default:1.1.7`, `fetchai/state_update:1.1.7` and `fetchai/signing:1.1.7` protocols. These (as all other packages installed from the registry) are placed in the `vendor` folder. You can find more details about the file structure here. ??? question "Is there a standardization for private key files?" Currently, the private keys are stored in `.txt` files. This is temporary and will be improved soon. ??? question "How to use the same protocol in different skills?" The details of envelope/message routing by the AEA framework are discussed in this guide. ??? question "Why does the AEA framework use its own package registry?" AEA packages could be described as personalized plugins for the AEA runtime. They are not like a library - they have no direct use outside the context of the framework - and therefore are not suitable for distribution via PyPI. ================================================ FILE: docs/quickstart.md ================================================ # AEA Quick Start If you want to create Autonomous Economic Agents (AEAs) that can act independently of constant user input and autonomously execute actions to achieve their objective, you can use the AEA framework. This example will take you through a simple AEA to familiarise you with the basics of the framework. ## Echo Skill Demo This is a simple demo that introduces you to the main components of an AEA. The fastest way to have your first AEA is to fetch one that already exists! ``` bash aea fetch fetchai/my_first_aea:0.28.5 cd my_first_aea ``` To learn more about the folder structure of an AEA project read on here. ??? note "Alternatively: step by step install:" **Create a new AEA** First, create a new AEA project and enter it. ``` bash aea create my_first_aea cd my_first_aea ``` **Add the stub connection** Second, add the stub connection to the project. ``` bash aea add connection fetchai/stub:0.21.3 ``` **Add the echo skill** Third, add the echo skill to the project. ``` bash aea add skill fetchai/echo:0.20.6 ``` This copies the fetchai/echo:0.20.6 skill code containing the "behaviours", and "handlers" into the project, ready to run. The identifier of the skill fetchai/echo:0.20.6 consists of the name of the author of the skill, followed by the skill name and its version. ### Echo Skill Just like humans, AEAs can have _skills_ to achieve their tasks. As an agent developer, you can create skills to add to your own AEAs. You can also choose to publish your skills so others add them to their AEAs. More details on skills can be found on this page . The above agent has an echo skill, fetched from the registry, which simply echoes any messages it receives back to its sender. ### Communication via Envelopes and Messages AEAs use envelopes containing messages for communication. To learn more, check out the next section. ### Stub Connection Besides skills, AEAs may have one or more _connections_ enabling them to interface with entities in the outside world. For example, an HTTP client connection allows an AEA to communicate with HTTP servers. To read more about connections see this page. In this demo, we use the stub connection (`fetchai/stub0.15.0`) to send envelopes to and receive envelopes from the AEA. A stub connection provides an I/O reader and writer. It uses two files for communication: one for incoming envelopes and the other for outgoing envelopes. The AEA waits for a new envelope posted to the file `my_first_aea/input_file`, and adds a response to the file `my_first_aea/output_file`. The format of each envelope is the following: ``` bash TO,SENDER,PROTOCOL_ID,ENCODED_MESSAGE, ``` For example: ``` bash recipient_aea,sender_aea,fetchai/default:1.0.0,\x08\x01\x12\x011*\x07\n\x05hello, ``` ### Install AEA Dependencies ``` bash aea install ``` ### Add and Create a Private Key All AEAs need a private key to run. Add one now: ``` bash aea generate-key fetchai aea add-key fetchai ``` ### Run the AEA Run the AEA. ``` bash aea run ``` You will see the echo skill running in the terminal window (an output similar to the one below). ``` bash _ _____ _ / \ | ____| / \ / _ \ | _| / _ \ / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ v1.1.1 Starting AEA 'my_first_aea' in 'async' mode ... info: Echo Handler: setup method called. info: Echo Behaviour: setup method called. info: [my_first_aea]: Start processing messages... info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ... ``` The framework first calls the `setup` methods in the skill's `Handler` and `Behaviour` classes in that order; after which it repeatedly calls the `act` method of `Behaviour` class. This is the main agent loop in action. #### Add a Message to the Input File You can send the AEA a message wrapped in an envelope using the CLI's `interact` command. From a different terminal and same directory (ensure you are in the same virtual environment: `pipenv shell`): ``` bash cd my_first_aea aea interact ``` You can now send messages to this AEA via an interactive tool by typing anything into the prompt and hitting enter twice (once to send the message and once more to check for a response). Let us send `hello` to this AEA (type `hello` and press enter twice). In the original terminal, you will see the `Echo Handler` dealing with this envelope and its contained message. You should see an output similar to the one below but with a different `dialogue_reference`. ``` bash info: Echo Behaviour: act method called. info: Echo Handler: message=Message(dialogue_reference=('1', '') message_id=1 target=0 performative=bytes content=b'hello'), sender=my_first_aea_interact info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ``` ??? note "Manual approach:" Optionally, from a different terminal and same directory (i.e. the `my_first_aea` project), you can send the AEA a message wrapped in an envelope via the input file. ``` bash echo 'my_first_aea,sender_aea,fetchai/default:1.0.0,\x12\x10\x08\x01\x12\x011*\t*\x07\n\x05hello,' >> input_file ``` You will see the Echo Handler dealing with the envelope and responding with the same message to the output_file, and also decoding the Base64 encrypted message in this case. ``` bash info: Echo Behaviour: act method called. Echo Handler: message=Message(sender=sender_aea,to=my_first_aea,content=b'hello',dialogue_reference=('1', ''),message_id=1,performative=bytes,target=0), sender=sender_aea info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ``` Note, due to the dialogue reference having to be incremented, you can only send the above envelope once! This approach does not work in conjunction with the aea interact command. ### Stop the AEA You can stop an AEA by pressing `CTRL C`. Once you do, you should see the AEA being interrupted and then calling the `teardown()` methods: ``` bash info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ^C my_first_aea interrupted! my_first_aea stopping ... info: Echo Handler: teardown method called. info: Echo Behaviour: teardown method called. ``` ### Write a Test for the AEA We can write an end-to-end test for the AEA utilising helper classes provided by the framework. ??? note "Writing tests:" The following test class replicates the preceding demo and tests its correct behaviour. The `AEATestCase` classes are a tool for AEA developers to write useful end-to-end tests of their AEAs. First, get the `packages` directory from the AEA repository (execute from the working directory which contains the my_first_aea folder): ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` Then write the test: ``` python import signal import time from aea.common import Address from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue from packages.fetchai.protocols.default.dialogues import DefaultDialogue, DefaultDialogues from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.default.serialization import DefaultSerializer from aea.test_tools.test_cases import AEATestCase class TestEchoSkill(AEATestCase): """Test that echo skill works.""" def test_echo(self): """Run the echo skill sequence.""" process = self.run_agent() is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" # add sending and receiving envelope from input/output files sender_aea = "sender_aea" def role_from_first_message( message: Message, receiver_address: Address ) -> Dialogue.Role: return DefaultDialogue.Role.AGENT dialogues = DefaultDialogues(sender_aea, role_from_first_message) message_content = b"hello" message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, dialogue_reference=dialogues.new_self_initiated_dialogue_reference(), content=message_content, ) sent_envelope = Envelope( to=self.agent_name, sender=sender_aea, protocol_id=message.protocol_id, message=DefaultSerializer().encode(message), ) self.send_envelope_to_agent(sent_envelope, self.agent_name) time.sleep(2.0) received_envelope = self.read_envelope_from_agent(self.agent_name) assert sent_envelope.to == received_envelope.sender assert sent_envelope.sender == received_envelope.to assert sent_envelope.protocol_id == received_envelope.protocol_id received_message = DefaultMessage.serializer.decode(received_envelope.message) assert message.content == received_message.content check_strings = ( "Echo Handler: setup method called.", "Echo Behaviour: setup method called.", "Echo Behaviour: act method called.", "content={}".format(message_content), ) missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) assert ( self.is_successfully_terminated() ), "Echo agent wasn't successfully terminated." ``` Place the above code into a file test.py in your AEA project directory (the same level as the aea-config.yaml file). To run, execute the following: ``` bash pytest test.py ``` ### Delete the AEA Delete the AEA from the parent directory (`cd ..` to go to the parent directory). ``` bash aea delete my_first_aea ``` ## Next Steps To gain an understanding of the core components of the framework, please continue to the next page: - Core components - Part 1 For more demos, use cases or step-by-step guides, please check the following: - Generic skill use case - Weather skill demo - Generic step by step guide ================================================ FILE: docs/raspberry-set-up.md ================================================ # Build an AEA on a Raspberry Pi This guide explains how to run an AEA inside a Raspberry Pi. ## Prerequisites - Raspberry Pi 4 (You can also use Raspberry Pi3 b or Raspberry Pi3 b+) - Internet connection (preferably wireless to minimise the number of wires connecting into your device) ## Preparing the Raspberry Pi The easiest and recommended way to get started is to download and unzip our custom AEA Raspberry Pi Image, which includes the AEA installation as well as the most common dependencies. However, you can also do the installation manually, and if you have a new Raspberry Pi, you can boot the system using the included SD card and skip the next section. ## Raspberry Pi Imager Raspberry Pi Imager is a way to write to an SD card for easy installation on a Raspberry Pi. First download the tool from this link. Then follow this guide to set up your SD card. When you get to the step of choosing an operating system, select the downloaded and unzipped AEA Raspberry Pi Image (`AEA_RPI.IMG`), or for a manual installation, select the latest Raspberry Pi OS. Once you have set up your SD card, plug it into your Raspberry Pi, connect the power and boot up. ## Booting up with the AEA Raspberry Pi Image After booting up, you may be prompted to log in as the `aea` user and the password is `fetch`. Next, navigate to settings menu to set up your internet connection. Your Raspberry Pi is now ready to run an AEA! You can find some preloaded demos in the folder `~/aea/demos`. To run these demos, navigate to one of the sub-folders and enter `aea run`. ## Booting up with the Raspberry Pi OS for Manual Installation When you first boot your Raspberry Pi, you will be prompted to enter a password for the Raspberry Pi and your Wi-Fi password so the device can access the internet. You may also be given the option to update the operating system and software. We recommend that you let the system update. Once finished you will be prompted to restart. Even if your Raspberry Pi updated itself, we recommend that you make sure it is completely up-to-date using the terminal. Open a Terminal window (your Raspberry Pi might restart a few times during this process): ``` bash sudo apt update -y sudo apt-get update sudo apt-get dist-upgrade ``` ## Install Common Dependencies ``` bash sudo apt install cmake golang -y ``` ## Install Less Common Dependencies (optional) For some of the more advanced AEAs that make use of SciPy, such as the Car Park Detector, you will need some additional dependencies. ??? note "Install additional dependencies with the enclosed steps:" Install additional dependencies ``` bash sudo apt install gfortran libatlas-base-dev libopenblas-dev -y ``` Increase the swap space for the SciPy installation: ``` bash sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024 sudo /sbin/mkswap /var/swap.1 sudo chmod 600 /var/swap.1 sudo /sbin/swapon /var/swap.1 ``` Install NumPy and scikit-image (including SciPy) ``` bash pip install numpy --upgrade pip install scikit-image ``` Revert to default swap space ``` bash sudo swapoff /var/swap.1 sudo rm /var/swap.1 ``` ## Install the AEA Framework Add to the local `PATH` environment variable (this will happen automatically the next time you log in): ``` bash export PATH="$HOME/.local/bin:$PATH" ``` Finally, install the AEA framework from PyPI: ``` bash pip install aea[all] ``` Check to make sure installation was successful: ``` bash aea --version ``` Your Raspberry Pi is now ready to run an AEA! ================================================ FILE: docs/runtime-cost.md ================================================ # Profiling ## Measuring Runtime Cost It is important to emphasise the fact that the AEA is a framework, so ultimately its running cost will highly depend on the number and type of components which are being run as part of a given AEA. The other cost factor is determined by the cost of running the core framework itself and how fast and efficient the framework is in interconnecting the components. These observations can provide guidance on what to report as part of the cost of running an AEA. Here is a list of suggestion on how to measure the cost of running an AEA: - the cost of running the framework itself: by running a minimal agent with an idle loop (the default one) with no connections, skills or protocols and measuring memory usage and CPU consumption as a baseline. - the cost of interconnecting components: by running an agent with a basic skill (e.g. `fetchai/echo`) and measuring memory usage and CPU consumption relative to number of messages exchanged as well as bandwidth. - the cost of basic components: dialogues memory relative to number of messages, SOEF connection baseline memory usage, P2P connection baseline memory usage, smart contract baseline memory usage The `aea run --profiling SECONDS` command can be used to report measures in all of the above scenarios. ================================================ FILE: docs/scaffolding.md ================================================ # Scaffolding Packages ## Scaffold Generator The scaffold generator builds out the directory structure required when adding new skills, protocols, contracts and connections to the AEA. For example, create a new AEA project (add the author flag using your own author handle if this is your first project using the `aea` package). ``` bash aea create my_aea --author "fetchai" cd my_aea ``` Then, enter into your project directory and scaffold your project skill, protocol, or connection. ### Scaffold a Skill ``` bash aea scaffold skill my_skill ``` ### Scaffold a Protocol ``` bash aea scaffold protocol my_protocol ``` ### Scaffold a Contract ``` bash aea scaffold contract my_contract ``` ### Scaffold a Connection ``` bash aea scaffold connection my_connection ``` After running the above commands, you are able to develop your own skill, protocol, contract and connection. Once you have made changes to your scaffolded packages, make sure you update the fingerprint of the package: ``` bash aea fingerprint [package_name] [public_id] ``` Then you are ready to run the AEA. ================================================ FILE: docs/security.md ================================================ # Security The AEA framework takes every care to follow best practice around security. The following advice will help you when writing your own code: - Many potential common security vulnerabilities can be caught by static code analysis. We recommend you use `safety`, `pylint` and `bandit` to analyse your code. - Don't use relative import paths, these can lead to malicious code being executed. - Try to avoid using the `subprocess` module. If needed, make sure you sanitise commands passed to `subprocess`. - Try to avoid using the `pickle` module. Pickle should never be used for agent-to-agent communication protocols. - By design, the framework prevents skill code from accessing private keys directly, as they are not reachable from the skill execution context through attribute getters. However, if the flag `-p` or the option `--password` are not used when generating private keys for an AEA project via the aea CLI tool, the private keys will be stored in plaintext. This allows the skills to access them via interaction with the OS file system. We recommend to always specify a password to encrypt private keys by using the flag argument. ================================================ FILE: docs/setup.md ================================================ # Setting up Once you successfully install the AEA framework, you can set it up for agent development. ## Specify Author Handle You need an author handle before being able to develop agents or agent components. This handle is used in the `author` field of any agent or component you create. AEAs and their components can be developed by anyone and pushed to the AEA registry for others to use. To publish packages to the registry, you also need to register your author handle. ### Pick Author Handle and Register If you are intending to use the registry: ``` bash aea init --register ``` This will let you pick a new author handle and register it at the same time. ### Pick Author Handle Only If you are unsure whether you will need a registry account, or intending not to use it, simply pick a new author handle: ``` bash aea init ``` ### Register Author Handle To register an already created author handle with the AEA registry: ``` bash aea register ``` !!! info "Note" The author handle is your unique author (or developer) name in the AEA ecosystem. ================================================ FILE: docs/simple-oef-usage.md ================================================ # SOEF Connection You can use the SOEF in the agent framework by using the SOEF connection as a package in your agent project. ## Add the SOEF Package Check out the CLI guide on details how to add a connection. You will want to add the `fetchai/soef:0.27.6` connection package. ## Register your Agent and its Services ### Register Agent Location To register your agent's location, you have to send a message in the `fetchai/oef_search:1.0.0` protocol to the SOEF connection. First, define a data model for location data: ``` python from aea.helpers.search.models import Attribute, DataModel, Location AGENT_LOCATION_MODEL = DataModel( "location_agent", [Attribute("location", Location, True, "The location where the agent is.")], "A data model to describe location of an agent.", ) ``` It is important to use this exact data model, as the SOEF connection can only process specific data models. Second, create a location object: ``` python from aea.helpers.search.models import Location agent_location = Location(52.2057092, 2.1183431) ``` Third, construct a service description instance with location and data model: ``` python from aea.helpers.search.models import Description service_instance = {"location": agent_location} service_description = Description( service_instance, data_model=AGENT_LOCATION_MODEL ) ``` Finally, construct a message and send it: ``` python from packages.fetchai.protocols.oef_search.message import OefSearchMessage message = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) ``` In case everything is registered OK, you will not receive any message back. If something goes wrong you will receive an error message with performative `OefSearchMessage.Performative.OEF_ERROR`. ### Register Personality Pieces To register personality pieces, you have to use a specific data model: ``` python from aea.helpers.search.models import Attribute, DataModel, Location AGENT_PERSONALITY_MODEL = DataModel( "personality_agent", [ Attribute("piece", str, True, "The personality piece key."), Attribute("value", str, True, "The personality piece value."), ], "A data model to describe the personality of an agent.", ) ``` An example follows: ``` python service_instance = {"piece": "genus", "value": "service"} service_description = Description( service_instance, data_model=AGENT_PERSONALITY_MODEL ) ``` ### Register Services To set some service key and value you have to use a specific data model: ``` python SET_SERVICE_KEY_MODEL = DataModel( "set_service_key", [ Attribute("key", str, True, "Service key name."), Attribute("value", str, True, "Service key value."), ], "A data model to set service key.", ) ``` An example follows: ``` python service_instance = {"key": "test", "value": "test"} service_description = Description( service_instance, data_model=SET_SERVICE_KEY_MODEL ) ``` ### Remove Service Key To remove service key have to use a specific data model: ``` python REMOVE_SERVICE_KEY_MODEL = DataModel( "remove_service_key", [Attribute("key", str, True, "Service key name.")], "A data model to remove service key.", ) ``` An example follows: ``` python service_instance = {"key": "test"} service_description = Description( service_instance, data_model=REMOVE_SERVICE_KEY_MODEL ) ``` !!! note Currently, the soef does not allow for multiple registrations to be combined into a single command. ## Perform a Search To perform a search for services registered you have to define a search query consisting of constraints. The location constraints is required, personality pieces or services keys constraints are optional. An example follows: ``` python from aea.helpers.search.models import ( Constraint, ConstraintType, Location, Query, ) radius = 0.1 close_to_my_service = Constraint( "location", ConstraintType("distance", (agent_location, radius)) ) personality_filters = [ Constraint("genus", ConstraintType("==", "vehicle")), Constraint( "classification", ConstraintType("==", "mobility.railway.train") ), ] service_key_filters = [ Constraint("test", ConstraintType("==", "test")), ] closeness_query = Query( [close_to_my_service] + personality_filters + service_key_filters ) message = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=closeness_query, ) ``` In case of error, you will receive a message with `OefSearchMessage.Performative.OEF_ERROR`. In case of successful search you will receive a message with performative `OefSearchMessage.Performative.SEARCH_RESULT` and the list of matched agents addresses. ## Generic Command To send a generic command request to the SOEF use the following (here on the example of setting a declared name): ``` python import urllib AGENT_GENERIC_COMMAND_MODEL = DataModel( "generic_command", [ Attribute("command", str, True, "Command name to execute."), Attribute("parameters", str, False, "Url encoded parameters string."), ], "A data model to describe the generic soef command.", ) declared_name = "new_declared_name" service_description = Description( { "command": "set_declared_name", "parameters": urllib.parse.urlencode({"name": declared_name}), }, data_model=AGENT_GENERIC_COMMAND_MODEL, ) message = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) ``` ================================================ FILE: docs/simple-oef.md ================================================ # Simple-OEF: Agent Search and Discovery The full documentation is available here. ================================================ FILE: docs/skill-guide.md ================================================ # Build your First Skill - Search & Discovery This guide will take you through the development of your first skill. It will teach you, how to connect the AEA to the digital world, register the AEA and search for other AEAs. Although one can imagine scenarios where a single AEA pursues its goals in isolation without interacting with other AEAs, there is no doubt that by working together, AEAs can achieve much more. To do so, an AEA must be seen and found by other AEAs so that they can trade and do other useful things. Fetch.ai’s search-and-discovery mechanism, the simple OEF (or SOEF, for short) lets your agents register, be discovered, and find other agents. You can then negotiate using the AEA framework’s peer-to-peer network (ACN) and trade. This guide covers getting your AEA connected to the SOEF, and describing your AEA to make itself visible. Registering your AEA with the SOEF involves setting a name, a genus (a high-level description of what the agent represents, e.g. `vehicle`, `building` or `service`), a classification (for example `infrastructure.railway.train`) and other descriptors to further fine-tune the kind of service your AEA offers (for example, the agent's position, whether it buys or sells, and other descriptive items). The more you describe your AEA, the easier it is for others to find it using specific filters. ## Dependencies (Required) Follow the Preliminaries and Installation sections from the AEA quick start. ## Step 1: Setup We will first create an AEA and add a scaffold skill, which we call `my_search`. ``` bash aea create my_aea && cd my_aea aea scaffold skill my_search ``` In the following steps, we replace the scaffolded `Behaviour` and `Handler` in `my_aea/skills/my_search` with our implementation. We will build a simple skill which lets the AEA send a search query to the SOEF search node and process the resulting response. ## Step 2: Develop a Behaviour A `Behaviour` class contains the business logic specific to actions initiated by the AEA rather than reactions to other events. In this example, we implement a simple search behaviour. Each time, `act()` gets called by the main agent loop, we will send a search request to the SOEF search node via the P2P communication network. ``` python from typing import cast from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.my_search.dialogues import OefSearchDialogues DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "seller_service", "search_value": "generic_service", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 class MySearchBehaviour(TickerBehaviour): """This class provides a simple search behaviour.""" def __init__(self, **kwargs): """Initialize the search behaviour.""" search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) agent_location = Location(latitude=location["latitude"], longitude=location["longitude"]) radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) close_to_my_service = Constraint( "location", ConstraintType("distance", (agent_location, radius)) ) service_key_filter = Constraint( search_query["search_key"], ConstraintType( search_query["constraint_type"], search_query["search_value"], ), ) self.query = Query([close_to_my_service, service_key_filter]) super().__init__(**kwargs) self.sent_search_count = 0 def setup(self) -> None: """ Implement the setup. :return: None """ self.context.logger.info( "setting up MySearchBehaviour" ) def act(self) -> None: """ Implement the act. :return: None """ self.sent_search_count += 1 oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) self.context.logger.info( "sending search request to OEF search node, search_count={}".format( self.sent_search_count ) ) search_request, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.query, ) self.context.outbox.put_message(message=search_request) def teardown(self) -> None: """ Implement the task teardown. :return: None """ self.context.logger.info( "tearing down MySearchBehaviour" ) ``` Searches are proactive and, as such, well placed in a `Behaviour`. Specifically, we subclass the `TickerBehaviour` as it allows us to repeatedly search at a defined tick interval. We place this code in `my_aea/skills/my_search/behaviours.py`. Ensure you replace the `fetchai` author in this line `from packages.fetchai.skills.my_search.dialogues import OefSearchDialogues` with your author handle (run `aea init` to set or check the author name). !!! note The import paths to agent packages, for example `packages.fetchai.skills.my_search.dialogues` above, are not actual paths. Package files always reside in your AEA's folder, either under a specific package directory (e.g. connection, protocol, skill) if the package is custom-built, or under `vendor` if it is pulled from the registry. These paths are virtual and created automatically when an AEA is run. See this page for more details. ## Step 3: Develop a Handler So far, we have tasked the AEA with sending search requests to the SOEF search node. However, we have no way of handling the responses sent to the AEA by the SOEF search node at the moment. The AEA would simply respond to the SOEF search node via the default `error` skill which sends all unrecognised envelopes back to the sender. Let us now implement a `Handler` to deal with the incoming search responses. ``` python from typing import Optional, cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.my_search.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) class MySearchHandler(Handler): """This class provides a simple search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id def __init__(self, **kwargs): """Initialize the handler.""" super().__init__(**kwargs) self.received_search_count = 0 def setup(self) -> None: """Set up the handler.""" self.context.logger.info( "setting up MySearchHandler" ) def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue :return: None """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_msg, oef_search_dialogue ) ) def _handle_search( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle the search response. :param agents: the agents returned by the search :return: None """ self.received_search_count += 1 nb_agents_found = len(oef_search_msg.agents) self.context.logger.info( "found number of agents={}, received search count={}".format( nb_agents_found, self.received_search_count ) ) self.context.logger.info( "number of search requests sent={} vs. number of search responses received={}".format( self.context.behaviours.my_search_behaviour.sent_search_count, self.received_search_count, ) ) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue :return: None """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) def teardown(self) -> None: """ Teardown the handler. :return: None """ self.context.logger.info( "tearing down MySearchHandler" ) ``` We create a handler which is registered for the `oef_search` protocol. Whenever it receives a search result, we log the number of agents returned by the search - the agents matching the search query - and update the counter of received searches. We also implement a trivial check on the difference between the amount of search requests sent and responses received. Note, how the handler simply reacts to incoming events (i.e. messages). It could initiate further actions, however, they are still reactions to the upstream search event. Also note, how we have access to other objects in the skill via `self.context`, the `SkillContext`. We place this code in `my_aea/skills/my_search/handlers.py`. Ensure you replace the `fetchai` author in this line `from packages.fetchai.skills.my_search.dialogues import (` with your author handle (run `aea init` to set or check the author name). ## Step 4: Add Dialogues Model We have implemented a behaviour and a handler. We now implement a `Model`, in particular we implement the `Dialogue` and `Dialogues` classes. These ensure that the message flow satisfies the `fetchai/oef_search:1.0.0` protocol and keep track of the individual messages being sent and received. ``` python from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Address, Model from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs) -> None: """ Initialize dialogues. :param agent_address: the address of the agent for whom dialogues are maintained :return: None """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ``` We add this code in the file `my_aea/skills/my_search/my_model.py`, replacing its original content. We then rename `my_aea/skills/my_search/my_model.py` to `my_aea/skills/my_search/dialogues.py`. ## Step 5: Create the Configuration File Based on our skill components above, we create the following configuration file. ``` yaml name: my_search author: fetchai version: 0.1.0 type: skill description: A simple search skill utilising the SOEF search node. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/oef_search:1.1.7 skills: [] behaviours: my_search_behaviour: args: location: latitude: 51.5194 longitude: 0.127 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 tick_interval: 5 class_name: MySearchBehaviour handlers: my_search_handler: args: {} class_name: MySearchHandler models: oef_search_dialogues: args: {} class_name: OefSearchDialogues dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false ``` Ensure, you replace the author field with your author name! (Run `aea init` to set or check the author name.) Importantly, the keys `my_search_behaviour` and `my_search_handler` are used in the above handler to access these skill components at runtime via the context. We also set the `tick_interval` of the `TickerBehaviour` to `5` seconds. We place this code in `my_aea/skills/my_search/skill.yaml`. Similarly, we replace `my_aea/skills/my_search/__init__.py` as follows: ``` python # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2019 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the error skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/my_search:0.1.0") ``` Again, ensure the author field matches your own. ## Step 6: Update Fingerprint To run an AEA with new or modified code, you need to update the fingerprint of the new/modified components. In this case, we need to fingerprint our skill: ``` bash aea fingerprint skill fetchai/my_search:0.1.0 ``` Ensure, you use the correct author name to reference your skill (here we use `fetchai` as the author.) ## Step 7: Add the OEF Protocol and Connection Our AEA does not have the OEF protocol yet so let's add it. ``` bash aea add protocol fetchai/oef_search:1.1.7 ``` This adds the protocol to our AEA and makes it available on the path `packages.fetchai.protocols...`. At this point we need to add the SOEF and P2P connections to allow the AEA to communicate with the SOEF node and other AEAs, install the AEA's dependencies, and configure the AEA: ``` bash aea add connection fetchai/soef:0.27.6 aea add connection fetchai/p2p_libp2p:0.27.5 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` The last command will ensure that search requests are processed by the correct connection. ## Step 8: Run a Service Provider AEA In order for this AEA to find another AEA when searching, the second AEA (let's call it the service provider AEA) must exist and have been registered with the SOEF. From a different terminal window, we fetch a finished service provider AEA and install its Python dependencies: ``` bash aea fetch fetchai/simple_service_registration:0.32.5 && cd simple_service_registration && aea install && aea build ``` This AEA will simply register a location service on the SOEF search node so we can search for it. We first create the private key for the service provider AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` Then we run the AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr: ['SOME_ADDRESS']` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the `simple_service_registration` (service provider) AEA. ??? note "Click here to see full code and guide for this AEA:" We use a `TickerBehaviour` to update the service registration at regular intervals. The following code is placed in `behaviours.py`. ``` python from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogues, ) from packages.fetchai.skills.simple_service_registration.strategy import Strategy DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 DEFAULT_SERVICES_INTERVAL = 30.0 class ServiceRegistrationBehaviour(TickerBehaviour): """This class implements a behaviour.""" def __init__(self, **kwargs: Any) -> None: """Initialise the behaviour.""" services_interval = kwargs.pop( "services_interval", DEFAULT_SERVICES_INTERVAL ) # type: int self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=services_interval, **kwargs) self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """ Implement the setup. :return: None """ self._register_agent() def act(self) -> None: """ Implement the act. :return: None """ self._retry_failed_registration() def teardown(self) -> None: """ Implement the task teardown. :return: None """ self._unregister_service() self._unregister_agent() def _retry_failed_registration(self) -> None: """ Retry a failed registration. :return: None """ if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration :return: None """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """ Register the agent's location. :return: None """ strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """ Register the agent's service. :return: None """ strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_service_description() self._register(description, "registering agent's service on the SOEF.") def register_genus(self) -> None: """ Register the agent's personality genus. :return: None """ strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """ Register the agent's personality classification. :return: None """ strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """ Unregister service from the SOEF. :return: None """ strategy = cast(Strategy, self.context.strategy) description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering service from SOEF.") def _unregister_agent(self) -> None: """ Unregister agent from the SOEF. :return: None """ strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") ``` We create a Model type strategy class and place it in strategy.py. We use a generic data model to register the service. As part of the registration we register a location and a key pair describing our service. ``` python from typing import Any from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import Description, Location from aea.skills.base import Model DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_DATA = {"key": "seller_service", "value": "generic_service"} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "seller"} class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :return: None """ location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} super().__init__(**kwargs) def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description ``` We create a Model type dialogue class and place it in dialogues.py. These classes ensure that the message flow satisfies the fetchai/oef_search:1.0.0 protocol and keep track of the individual messages being sent and received. ``` python from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param agent_address: the address of the agent for whom dialogues are maintained :return: None """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ``` Finally, we have a handler, placed in handlers.py. The handler deals with handling any error messages which might occur during service registration: ``` python from typing import Optional, cast from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_registration.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """ Implement the handler teardown. :return: None """ def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue :return: None """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue :return: None """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service, ) registration_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue :return: None """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ``` The associated skill.yaml is: ``` yaml name: simple_service_registration author: fetchai version: 0.20.0 type: skill description: The simple service registration skills is a skill to register a service. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmUgCcR7sDBQeeCBRKwDT7tPBTi3t4zSibyEqR3xdQUKmh __init__.py: QmZd48HmYDr7FMxNaVeGfWRvVtieEdEV78hd7h7roTceP2 behaviours.py: QmQHf6QL5aBtLJ34D2tdcbjJLbzom9gaA3HWgRn3rWyigM dialogues.py: QmTT9dvFhWt6qvxjwBfMFDTrgEtgWbvgANYafyRg2BXwcR handlers.py: QmZqPt8toGbJgTT6NZBLxjkusrQCZ8GmUEwcmqZ1sd7DpG strategy.py: QmVXfQpk4cjDw576H2ELE12tEiN5brPkwvffvcTeMbsugA fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/oef_search:1.1.7 skills: [] behaviours: service: args: max_soef_registration_retries: 5 services_interval: 30 class_name: ServiceRegistrationBehaviour handlers: oef_search: args: {} class_name: OefSearchHandler models: oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: generic_service class_name: Strategy dependencies: {} is_abstract: false ``` ## Step 9: Run the Search AEA First, create the private key for the search AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` Then, in the search AEA, run this command (replace `SOME_ADDRESS` with the correct value as described above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAm1uJpFsqSgHStJdtTBPpDme1fo8uFEvvY182D2y89jQuj"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` This allows the search AEA to connect to the same local agent communication network as the service registration AEA. We can then launch our AEA. ``` bash aea run ``` We can see that the AEA sends search requests to the SOEF search node and receives search responses from the SOEF search node. The search response returns one or more agents (the service provider and potentially other agents which match the query). We stop the AEA with `CTRL + C`. ## Next Steps ### Recommended We recommend you continue with the next step in the 'Getting Started' series: - Core components (Part 2) ### Relevant Deep-Dives This guide goes through a more elaborate scenario than the one on this page, where after finding each other, the two AEAs negotiate and trade via a ledger. ================================================ FILE: docs/skill-testing.md ================================================ # Testing Skills In this guide, we describe some of the tools the framework offers for testing skills. ## The `BaseSkillTestCase` Class The framework offers a `BaseSkillTestCase` class which you can subclass and write your test cases with. Let us assume you want to test the `my_behaviour` behaviour of a `CustomSkill` skill you have developed. You can create a `TestMyBehaviour` class which inherits `BaseSkillTestCase` as below: ``` python import asyncio from asyncio import Queue from pathlib import Path from types import SimpleNamespace from typing import cast from aea.configurations.constants import DEFAULT_LEDGER from aea.context.base import AgentContext from aea.crypto.ledger_apis import DEFAULT_CURRENCY_DENOMINATIONS from aea.identity.base import Identity from aea.multiplexer import AsyncMultiplexer, OutBox, Multiplexer from aea.skills.tasks import TaskManager from aea.test_tools.test_skill import BaseSkillTestCase class TestMyBehaviour(BaseSkillTestCase): """Test my_behaviours of the custom skill.""" path_to_skill = Path("path_to_this_skill") ``` ### Specifying Skill Path You must then specify the path to your skill directory via `path_to_skill` to allow the skill to be loaded and tested. This must be the directory in which `skill.yaml` of your skill resides. ### Setting up Each Test You can add a `setup()` class method to set the environment up for each of your tests. This code will be executed before every test method. If you do include this method, you must call the `setup()` method of the `BaseSkillTestCase` class via `super().setup()`. ``` python @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.my_behaviour = cast( MyBehaviour, cls._skill.skill_context.behaviours.my_behaviour ) ``` In the above, we make the `my_behaviour` behaviour object accessible for every test. ### Skill and Skill Context The skill object itself is exposed via a property. So you can access the skill object by `self.skill` and by extension all of its attributes. This crucially includes the complete `skill_context`. This means that for example, every component of the skill (e.g. behaviours, handlers, models) can be accessed via the skill context. In the above code snippet, `my_behavior` is accessed and exposed as a class attribute. Note accessing the skill context is slightly different in the above because it is a class method. If this was a test method, you could access the behaviour via `self.skill.skill_context.behaviours.my_behaviour`. ### Dummy Agent Context The loaded skill is also fed a dummy `agent_context` complete with an `identity`, `outbox`, `decision_maker_queue` and so on, to allow the skill to be properly loaded and have access to everything it requires to function. The `agent_context` object fed to the skill is shown below: ``` python _multiplexer = AsyncMultiplexer() _multiplexer._out_queue = (asyncio.Queue()) agent_context = AgentContext( identity=Identity("test_agent_name", "test_agent_address", "test_agent_public_key"), connection_status=_multiplexer.connection_status, outbox=OutBox(cast(Multiplexer, cls._multiplexer)), decision_maker_message_queue=Queue(), decision_maker_handler_context=SimpleNamespace(), task_manager=TaskManager(), default_ledger_id=DEFAULT_LEDGER, currency_denominations={}, default_connection=None, default_routing={}, search_service_address="dummy_search_service_address", decision_maker_address="dummy_decision_maker_address", data_dir="." ) ``` ### Some Useful Skill Attributes Some of the useful objects you can access in your test class for the loaded skill are below: - `self.skill.skill_context.agent_address`: this is the agent identity the skill uses and is set to `"test_agent_address"`. - `self.skill.skill_context.search_service_address`: this is the address of the search service and is set to `"dummy_search_service_address"`. - `self.skill.skill_context.skill_id`: this is the id of the skill. - `self.skill.skill_context.decision_maker_address`: this is the address of the decision maker and is set to `"dummy_decision_maker_address"`. ### Some Useful `BaseSkillTestCase` Methods There are a number of methods that `BaseSkillTestCase` offers to make testing skills easier. Some of these are mentioned below. For the rest, consult the API for `BaseSkillTestCase`: - `self.get_quantity_in_outbox()`: gives you the number of messages which are in the outbox. After running a part of the skill which is expected to send messages, you can use this method to assert the correct number of messages are indeed sent. - `self.get_message_from_outbox()`: gives you the last message in the outbox. Together with the above, you can use this method to grab the last message sent by the skill code you tested and check this is indeed the expected message. - `self.message_has_attributes(actual_message: Message, message_type: Type[Message], **kwargs,)`: you can use this method in tandem with the above method to check that a message has the attributes you expect it to have. You have to supply it with the actual message (e.g. using `self.get_message_from_outbox()`), specify its expected type (e.g. `FipaMessage`), and any other attribute you expect the message to have (e.g. `message_id` is 1) may be provided via keyword arguments. - `self.build_incoming_message`: this is an especially useful method to test handlers. Since handlers handle incoming messages, you can create an incoming message using this method to feed it to the handler and test its execution. #### Checking Logger Output You can check the output of your skill's `logger` by mocking it using `unittest.mock` before executing a part of your skill as such: ``` python import logging from unittest import mock with mock.patch.object(self.my_behaviour.context.logger, "log") as mock_logger: self.my_behaviour.act() mock_logger.assert_any_call(logging.INFO, "some_logger_message") ``` In the above, we mock the logger before running `my_behaviour`'s `act()` method and check that the string `"some_logger_message"` is indeed passed to the logger. ## Next Steps You can consult the `fetchai/generic_buyer` and `fetchai/generic_seller` skills and their associated tests here to study how `BaseSkillTestCase` can help you in testing your skills. You can also refer to the API to study the different methods `BaseSkillTestCase` makes available to make testing your skills easier. ================================================ FILE: docs/skill.md ================================================ # Skills `Skills` are the core focus of the framework's extensibility as they implement business logic to deliver economic value for the AEA. They are self-contained capabilities that AEAs can dynamically take on board, in order to expand their effectiveness in different situations. Skill components of an AEA A skill encapsulates implementations of the three abstract base classes `Handler`, `Behaviour`, `Model`, and is closely related with the abstract base class `Task`: - `Handler`: each skill has zero, one or more `Handler` objects, each responsible for the registered messaging protocol. Handlers implement AEAs' **reactive** behaviour. If the AEA understands the protocol referenced in a received `Envelope`, the `Handler` reacts appropriately to the corresponding message. Each `Handler` is responsible for only one protocol. A `Handler` is also capable of dealing with internal messages (see next section). - `Behaviour`: zero, one or more `Behaviours` encapsulate actions which further the AEAs goal and are initiated by internals of the AEA, rather than external events. Behaviours implement AEAs' **pro-activeness**. The framework provides a number of abstract base classes implementing different types of behaviours (e.g. cyclic/one-shot/finite-state-machine/etc.). - `Model`: zero, one or more `Models` that inherit from the `Model` class. `Models` encapsulate custom objects which are made accessible to any part of a skill via the `SkillContext`. - `Task`: zero, one or more `Tasks` encapsulate background work internal to the AEA. `Task` differs from the other three in that it is not a part of skills, but `Task`s are declared in or from skills if a packaging approach for AEA creation is used. A skill can read (parts of) the state of the AEA (as summarised in the `AgentContext`), and propose actions to the AEA according to its specific logic. As such, more than one skill could exist per protocol, competing with each other in suggesting to the AEA the best course of actions to take. In technical terms this means skills are horizontally arranged. For instance, an AEA who is trading goods, could subscribe to more than one skill, where each skill corresponds to a different trading strategy. The skills could then read the preference and ownership state of the AEA, and independently suggest profitable transactions. The framework places no limits on the complexity of skills. They can implement simple (e.g. `if-this-then-that`) or complex (e.g. a deep learning model or reinforcement learning agent). The framework provides one default skill, called `error`. Additional skills can be added as packages. ## Independence of Skills Skills are `horizontally layered`, that is they run independently of each other. They also cannot access each other's state. Two skills can communicate with each other in two ways. The skill context provides access via `self.context.shared_state` to a key-value store which allows skills to share state. A skill can also define as a callback another skill in a message to the decision maker. ## Context The skill has a `SkillContext` object which is shared by all `Handler`, `Behaviour`, and `Model` objects. The skill context also has a link to the `AgentContext`. The `AgentContext` provides read access to AEA specific information like the public key and address of the AEA, its preferences and ownership state. It also provides access to the `OutBox`. This means it is possible to, at any point, grab the `context` and have access to the code in other parts of the skill and the AEA. For example, in the `ErrorHandler(Handler)` class, the code often grabs a reference to its context and by doing so can access initialised and running framework objects such as an `OutBox` for putting messages into. ``` python self.context.outbox.put_message(message=reply) ``` Moreover, you can read/write to the _agent context namespace_ by accessing the attribute `SkillContext.namespace`. Importantly, however, a skill does not have access to the context of another skill or protected AEA components like the `DecisionMaker`. ## What to Code Each of the skill classes has three methods that must be implemented. All of them include a `setup()` and `teardown()` method which the developer must implement. Then there is a specific method that the framework requires for each class. ### `handlers.py` There can be none, one or more `Handler` class per skill. `Handler` classes can receive `Message` objects of one protocol type only. However, `Handler` classes can send `Envelope` objects of any type of protocol they require. - `handle(self, message: Message)`: is where the skill receives a `Message` of the specified protocol and decides what to do with it. A handler can be registered in one way: - By declaring it in the skill configuration file `skill.yaml` (see below). It is possible to register new handlers dynamically by enqueuing new `Handler` instances in the queue `context.new_handlers`, e.g. in a skill component we can write: ``` python self.context.new_handlers.put(MyHandler(name="my_handler", skill_context=self.context)) ``` ### `behaviours.py` Conceptually, a `Behaviour` class contains the business logic specific to initial actions initiated by the AEA rather than reactions to other events. There can be one or more `Behaviour` classes per skill. The developer must create a subclass from the abstract class `Behaviour` to create a new `Behaviour`. - `act(self)`: is how the framework calls the `Behaviour` code. A behaviour can be registered in two ways: - By declaring it in the skill configuration file `skill.yaml` (see below) - In any part of the code of the skill, by enqueuing new `Behaviour` instances in the queue `context.new_behaviours`. In that case, `setup`is not called by the framework, as the behaviour will be added after the AEA setup is complete. The framework supports different types of behaviours: - `OneShotBehaviour`: this behaviour is executed only once. - `TickerBehaviour`: the `act()` method is called every `tick_interval`. E.g. if the `TickerBehaviour` subclass is instantiated There is another category of behaviours, called `CompositeBehaviour`: - `SequenceBehaviour`: a sequence of `Behaviour` classes, executed one after the other. - `FSMBehaviour`: a state machine of `State` behaviours. A state is in charge of scheduling the next state. If your behaviour fits one of the above, we suggest subclassing your behaviour class with that behaviour class. Otherwise, you can always subclass the general-purpose `Behaviour` class. Follows an example of a custom behaviour: ``` python from aea.skills.behaviours import OneShotBehaviour class HelloWorldBehaviour(OneShotBehaviour): def setup(self): """This method is called once, when the behaviour gets loaded.""" def act(self): """This methods is called in every iteration of the agent main loop.""" print("Hello, World!") def teardown(self): """This method is called once, when the behaviour is teared down.""" ``` If we want to register this behaviour dynamically, in any part of the skill code (i.e. wherever the skill context is available), we can write: ``` python self.context.new_behaviours.put(HelloWorldBehaviour(name="hello_world", skill_context=self.context)) ``` Or, equivalently to the previous two code blocks: ``` python def hello(): print("Hello, World!") self.context.new_behaviours.put(OneShotBehaviour(act=hello, name="hello_world", skill_context=self.context)) ``` The callable passed to the `act` parameter is equivalent to the implementation of the `act` method described above. The framework is then in charge of registering the behaviour and scheduling it for execution. ### `tasks.py` Conceptually, a `Task` is where the developer codes any internal tasks the AEA requires. There can be one or more `Task` classes per skill. The developer subclasses abstract class `Task` to create a new `Task`. - `execute(self)`: is how the framework calls a `Task`. The `Task` class implements the functor pattern. An instance of the `Task` class can be invoked as if it were an ordinary function. Once completed, it will store the result in the property `result`. Raises error if the task has not been executed yet, or an error occurred during computation. We suggest using the `task_manager`, accessible through the skill context, to manage long-running tasks. The task manager uses `multiprocessing` to schedule tasks, so be aware that the changes on the task object will not be updated. Here's an example: In `tasks.py`: ``` python from aea.skills.tasks import Task def nth_prime_number(n: int) -> int: """A naive algorithm to find the n_th prime number.""" assert n > 0 primes = [2] num = 3 while len(primes) < n: for p in primes: if num % p == 0: break else: primes.append(num) num += 2 return primes[-1] class LongTask(Task): def setup(self): """Set the task up before execution.""" def execute(self, n: int): return nth_prime_number(n) def teardown(self): """Clean the task up after execution.""" ``` In `behaviours.py`: ``` python from aea.skills.behaviours import TickerBehaviour from packages.my_author_name.skills.my_skill.tasks import LongTask class MyBehaviour(TickerBehaviour): def setup(self): """Setup behaviour.""" my_task = LongTask() task_id = self.context.task_manager.enqueue_task(my_task, args=(10000, )) self.async_result = self.context.task_manager.get_task_result(task_id) # type: multiprocessing.pool.AsyncResult def act(self): """Act implementation.""" if self.async_result.ready() is False: print("The task is not finished yet.") else: completed_task = self.async_result.get() # type: LongTask print("The result is:", completed_task.result) # Stop the skill self.context.is_active = False def teardown(self): """Teardown behaviour.""" ``` ### Models The developer might want to add other classes on the context level which are shared equally across the `Handler`, `Behaviour` and `Task` classes. To this end, the developer can subclass an abstract `Model`. These models are made available on the context level upon initialization of the AEA. Say, the developer has a class called `SomeModel` ``` python class SomeModel(Model): ... ``` Then, an instance of this class is available on the context level like so: ``` python some_model = self.context.some_model ``` ### Skill Configuration Each skill has a `skill.yaml` configuration file which lists all `Behaviour`, `Handler`, and `Task` objects pertaining to the skill. It also details the protocol types used in the skill and points to shared modules, i.e. modules of type `Model`, which allow custom classes within the skill to be accessible in the skill context. ``` yaml name: echo authors: fetchai version: 0.1.0 license: Apache-2.0 behaviours: echo: class_name: EchoBehaviour args: tick_interval: 1.0 handlers: echo: class_name: EchoHandler args: foo: bar models: {} dependencies: {} protocols: - fetchai/default:1.1.7 ``` ## Error Skill All AEAs have a default `error` skill that contains error handling code for a number of scenarios: - Received envelopes with unsupported protocols - Received envelopes with unsupported skills (i.e. protocols for which no handler is registered) - Envelopes with decoding errors - Invalid messages with respect to the registered protocol The error skill relies on the `fetchai/default:1.0.0` protocol which provides error codes for the above. ## Custom Error Handler The framework implements a default `ErrorHandler`. You can implement your own and mount it. The easiest way to do this is to run the following command to scaffold a custom `ErrorHandler`: ``` bash aea scaffold error-handler ``` Now you will see a file called `error_handler.py` in the AEA project root. You can then implement your own custom logic to process messages. ================================================ FILE: docs/standalone-transaction.md ================================================ # Create Stand-Alone Transaction In this guide, we will generate some wealth for the Fetch.ai testnet and create a standalone transaction. After the completion of the transaction, we get the transaction digest. With this we can search for the transaction on the block explorer This guide requires the `aea-ledger-fetchai` plug-in installed in your Python environment: ``` bash pip install aea-ledger-fetchai ``` First, import the python and application specific libraries and set the static variables. ``` python import logging from aea_ledger_fetchai import FetchAICrypto from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) FETCHAI_PRIVATE_KEY_FILE_1 = "fetchai_private_key_1.txt" FETCHAI_PRIVATE_KEY_FILE_2 = "fetchai_private_key_2.txt" ``` ## Create the Private Keys ``` python # Create a private keys create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_1 ) create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_2 ) ``` ## Create the Wallets Once we created the private keys we need to generate the wallets. ``` python # Set up the wallets wallet_1 = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_1}) wallet_2 = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_2}) ``` ## Generate Wealth Since we want to send funds from `wallet_1` to `wallet_2`, we need to generate some wealth for the `wallet_1`. We can do this with the following code ``` python # Generate some wealth try_generate_testnet_wealth( FetchAICrypto.identifier, wallet_1.addresses[FetchAICrypto.identifier] ) ``` ## Send Transaction Finally, we create a transaction that sends the funds to the `wallet_2` ``` python # Create the transaction and send it to the ledger. tx_nonce = LedgerApis.generate_tx_nonce( FetchAICrypto.identifier, wallet_2.addresses.get(FetchAICrypto.identifier), wallet_1.addresses.get(FetchAICrypto.identifier), ) transaction = LedgerApis.get_transfer_transaction( identifier=FetchAICrypto.identifier, sender_address=wallet_1.addresses.get(FetchAICrypto.identifier), destination_address=wallet_2.addresses.get(FetchAICrypto.identifier), amount=1, tx_fee=1, tx_nonce=tx_nonce, ) signed_transaction = wallet_1.sign_transaction( FetchAICrypto.identifier, transaction ) transaction_digest = LedgerApis.send_signed_transaction( FetchAICrypto.identifier, signed_transaction ) logger.info("Transaction complete.") logger.info("The transaction digest is {}".format(transaction_digest)) ``` ??? note "Stand-alone transaction full code:" ``` python import logging from aea_ledger_fetchai import FetchAICrypto from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) FETCHAI_PRIVATE_KEY_FILE_1 = "fetchai_private_key_1.txt" FETCHAI_PRIVATE_KEY_FILE_2 = "fetchai_private_key_2.txt" def run(): """Run demo.""" # Create a private keys create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_1 ) create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_2 ) # Set up the wallets wallet_1 = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_1}) wallet_2 = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_2}) # Generate some wealth try_generate_testnet_wealth( FetchAICrypto.identifier, wallet_1.addresses[FetchAICrypto.identifier] ) logger.info( "Sending amount to {}".format(wallet_2.addresses.get(FetchAICrypto.identifier)) ) # Create the transaction and send it to the ledger. tx_nonce = LedgerApis.generate_tx_nonce( FetchAICrypto.identifier, wallet_2.addresses.get(FetchAICrypto.identifier), wallet_1.addresses.get(FetchAICrypto.identifier), ) transaction = LedgerApis.get_transfer_transaction( identifier=FetchAICrypto.identifier, sender_address=wallet_1.addresses.get(FetchAICrypto.identifier), destination_address=wallet_2.addresses.get(FetchAICrypto.identifier), amount=1, tx_fee=1, tx_nonce=tx_nonce, ) signed_transaction = wallet_1.sign_transaction( FetchAICrypto.identifier, transaction ) transaction_digest = LedgerApis.send_signed_transaction( FetchAICrypto.identifier, signed_transaction ) logger.info("Transaction complete.") logger.info("The transaction digest is {}".format(transaction_digest)) if __name__ == "__main__": run() ``` ================================================ FILE: docs/step-one.md ================================================ # Ways to Build an AEA There are a number of ways to build an AEA: - To start with, we recommended you build an AEA project step-by-step with the CLI tool as demonstrated in the quick start guide and described here. - Using the CLI `aea fetch` command, pull in an already built project and run as is or extend it to your needs. - The last option is to build an AEA programmatically as described here. Sometimes, an AEA is more than is required for the task at hand. In particular, an AEA is much more than just an agent. In those cases, we suggest you have a look at the following two guides: - the AEA vs Agents guide shows the difference between an agent and an AEA in code, - the Use multiplexer standalone guide shows how to use the multiplexer on its own to receive and send envelopes. ================================================ FILE: docs/tac-skills-contract.md ================================================ # TAC Skills Ledger-Based The AEA TAC - trading agent competition - skills demonstrate an interaction between multiple AEAs in a game. There are two types of AEAs: - The `tac_controller` which coordinates the game. - The `tac_participant` AEAs which compete in the game. The `tac_participant` AEAs trade tokens with each other to maximize their utility. ## Discussion This demo shows how agents negotiate autonomously with each other while they pursue their goals by participating in the Trading Agents Competition (TAC). The demo can be run against Fetchai or Ethereum ledger. Transactions are validated on an ERC1155 smart contract on the Fetchai Dorado or a local Ganache Ethereum testnet. In the following video we discuss the framework and TAC in more detail: ## Communication There are two types of interactions: - between the controller and participants (game management communication) - between the participants (negotiations) ### Registration Communication This diagram shows the communication between the various entities during the registration phase. ``` mermaid sequenceDiagram participant Agent_2 participant Agent_1 participant Search participant Controller activate Search activate Controller Controller->>Search: register_service activate Agent_1 Agent_1->>Search: search Search-->>Agent_1: controller Agent_1->>Controller: register activate Agent_2 Agent_2->>Search: search Search-->>Agent_2: controller Agent_2->>Controller: register Controller->>Agent_1: game_data Controller->>Agent_2: game_data deactivate Agent_1 deactivate Agent_2 deactivate Search deactivate Controller ``` ### Transaction Communication This diagram shows the communication between two AEAs and a controller. In this case, we have a `Seller_Agent` which is set up as a seller (and registers itself as such with the controller during the registration phase). We also have the `Searching_Agent` which is set up to search for sellers. ``` mermaid sequenceDiagram participant Buyer_Agent participant Seller_Agent participant Search participant Controller activate Buyer_Agent activate Seller_Agent activate Search activate Controller Seller_Agent->>Search: register_service Buyer_Agent->>Search: search Search-->>Buyer_Agent: list_of_agents Buyer_Agent->>Seller_Agent: call_for_proposal Seller_Agent->>Buyer_Agent: proposal Buyer_Agent->>Seller_Agent: accept Seller_Agent->>Buyer_Agent: match_accept Seller_Agent->>Controller: transaction Controller->>Controller: transaction_execution Controller->>Seller_Agent: confirm_transaction Controller->>Buyer_Agent: confirm_transaction deactivate Buyer_Agent deactivate Seller_Agent deactivate Search deactivate Controller ``` In the above case, the proposal received contains a set of goods to sell and an associated price. The buyer AEA needs to determine if this is a good deal for them, and if so, it accepts. There is an equivalent diagram for seller AEAs set up to search for buyers and their interaction with AEAs which are registered as buyers. In that scenario, the proposal will instead be a list of goods that the buyer wishes to buy and the price it is willing to pay for them. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ## Demo Instructions (Fetchai) Follow this instruction to run TAC against the fetch.ai Dorado testnet. ### Fetch TAC Controller AEA In the root directory, fetch the controller AEA: ``` bash aea fetch fetchai/tac_controller_contract:0.32.5 cd tac_controller_contract aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the controller from scratch: ``` bash aea create tac_controller_contract cd tac_controller_contract aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_control_contract:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set vendor.fetchai.connections.soef.config.chain_identifier fetchai_v2_misc aea config set --type bool vendor.fetchai.skills.tac_control.is_abstract true aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "fetchai", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` ### Fetch the TAC Participant AEAs In separate terminals, in the root directory, fetch at least two participants: ``` bash aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_one cd tac_participant_one aea install aea build cd .. aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_two cd tac_participant_two aea install aea build ``` ??? note "Alternatively, create from scratch:" In a separate terminal, in the root directory, create at least two tac participant AEAs: ``` bash aea create tac_participant_one aea create tac_participant_two ``` Build participant one: ``` bash cd tac_participant_one aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set vendor.fetchai.connections.soef.config.chain_identifier fetchai_v2_misc aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "fetchai", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` Then, build participant two: ``` bash cd tac_participant_two aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set vendor.fetchai.connections.soef.config.chain_identifier fetchai_v2_misc aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "fetchai", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` ### Add Keys for All AEAs For every AEA in the competition (controller and participants): First generate and add a private key: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Then create and add a separate private key for secure communication: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Update the Game Parameters in the Controller In the tac controller project, get and set the registration start time (set it to at least 5 minutes in the future): ``` bash aea config get vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time '01 01 2020 00:01' ``` To set the registration time, you may find handy the following command: ``` bash aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time "$(date -d "5 minutes" +'%d %m %Y %H:%M')" ``` ### Update the Connection Parameters Update the connection parameters of the TAC participants to allow them to connect to the same local agent communication network as the TAC controller. First, retrieve controller's local ACN address by running the following in the controller agent's project terminal: ``` bash aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri ``` Then, in participant one, run this command (replace `SOME_ADDRESS` with the value you retrieved above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` Do the same in participant two (beware of the different port numbers): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11002", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" }' ``` ## Fund Agents' Accounts Retrieve the address of each agent (in each terminal): ``` bash aea get-address fetchai ``` Go to the Dorado block explorer and request some test tokens via `Get Funds`. To check the wealth of an AEA, use: ``` bash aea get-wealth fetchai ``` ### Run the AEAs First, launch the `tac_contract_controller` then the participants by executing the following from their respective terminals: ``` bash aea run ``` The CLI tool supports launching several agents at once. For example, assuming you followed the tutorial, you can launch both TAC participant agents as follows from the root directory (ensure you run the controller agent first as above): ``` bash aea launch tac_participant_one tac_participant_two ``` You may want to try `--multithreaded` option in order to run the agents in the same process. ### Cleaning up When you're finished, delete your AEAs: ``` bash aea delete tac_controller_contract aea delete tac_participant_one aea delete tac_participant_two ``` ## Demo Instructions (Ethereum) Follow this instruction to run TAC against a local Ganache Ethereum test-net. ### Create TAC Controller AEA In the root directory, fetch the controller AEA: ``` bash aea fetch fetchai/tac_controller_contract:0.32.5 cd tac_controller_contract aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the controller from scratch: ``` bash aea create tac_controller_contract cd tac_controller_contract aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_control_contract:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set --type bool vendor.fetchai.skills.tac_control.is_abstract true aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` ### Fetch the TAC Participant AEAs In separate terminals, in the root directory, fetch at least two participants: ``` bash aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_one cd tac_participant_one aea install aea build cd .. aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_two cd tac_participant_two aea install aea build ``` ??? note "Alternatively, create from scratch:" In a separate terminal, in the root directory, create at least two tac participant AEAs: ``` bash aea create tac_participant_one aea create tac_participant_two ``` Build participant one: ``` bash cd tac_participant_one aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "ethereum", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` Then, build participant two: ``` bash cd tac_participant_two aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "ethereum", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` ### Configure the Agents to Use Ethereum Run the following in every AEA's terminal: ``` bash aea config set agent.default_ledger ethereum json=$(printf '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt"}]') aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests "$json" aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum ``` ### Add Keys for All AEAs For every AEA in the competition (controller and participants): First generate and add a private key: ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` Then create and add a separate private key for secure communication: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Update the Game Parameters in the Controller In the tac controller project, get and set the registration start time (set it to at least 5 minutes in the future): ``` bash aea config get vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time '01 01 2020 00:01' ``` To set the registration time, you may find handy the following command: ``` bash aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time "$(date -d "5 minutes" +'%d %m %Y %H:%M')" ``` ### Update the Connection Parameters Update the connection parameters of the TAC participants to allow them to connect to the same local agent communication network as the TAC controller. First, retrieve controller's local ACN address by running the following in the controller agent's project terminal: ``` bash aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri ``` Then, in participant one, run this command (replace `SOME_ADDRESS` with the value you retrieved above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` Do the same in participant two (beware of the different port numbers): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11002", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" }' ``` ## Fund Agents' Accounts Run a local Ganache Ethereum test-net with funds for the addresses of the three AEAs in this demo: ``` bash docker run -p 8545:8545 trufflesuite/ganache-cli:latest --verbose --gasPrice=0 --gasLimit=0x1fffffffffffff --account="$(cat tac_controller_contract/ethereum_private_key.txt),1000000000000000000000" --account="$(cat tac_participant_one/ethereum_private_key.txt),1000000000000000000000" --account="$(cat tac_participant_two/ethereum_private_key.txt),1000000000000000000000" ``` To check the wealth of an AEA, use: ``` bash aea get-wealth ethereum ``` You should get `1000000000000000000000`. ### Run the AEAs First, launch the `tac_contract_controller` then the participants by executing the following from their respective terminals: ``` bash aea run ``` The CLI tool supports launching several agents at once. For example, assuming you followed the tutorial, you can launch both TAC participant agents as follows from the root directory (ensure you run the controller agent first as above): ``` bash aea launch tac_participant_one tac_participant_two ``` You may want to try `--multithreaded` option in order to run the agents in the same process. ### Cleaning up When you're finished, delete your AEAs: ``` bash aea delete tac_controller_contract aea delete tac_participant_one aea delete tac_participant_two ``` ================================================ FILE: docs/tac-skills.md ================================================ # TAC Skills The AEA TAC - trading agent competition - skills demonstrate an interaction between multiple AEAs in a game. There are two types of AEAs: - The `tac_controller` which coordinates the game. - The `tac_participant` AEAs which compete in the game. The `tac_participant` AEAs trade tokens with each other to maximize their utility. ## Discussion The scope of this specific demo is to demonstrate how the agents negotiate autonomously with each other while they pursue their goals by playing a game of TAC. Another AEA has the role of the controller, responsible for calculating the revenue for each participant and checking if the transaction messages are valid. Transactions are settled with the controller agent rather than against a public ledger. ## Communication There are two types of interactions: - between the participants and the controller, the game communication - between the participants, the negotiation ### Registration Communication This diagram shows the communication between the various entities during the registration phase. ``` mermaid sequenceDiagram participant Agent_2 participant Agent_1 participant Search participant Controller activate Search activate Controller Controller->>Search: register_service activate Agent_1 Agent_1->>Search: search Search-->>Agent_1: controller Agent_1->>Controller: register activate Agent_2 Agent_2->>Search: search Search-->>Agent_2: controller Agent_2->>Controller: register Controller->>Controller: start_game Controller->>Agent_1: game_data Controller->>Agent_2: game_data deactivate Agent_1 deactivate Agent_2 deactivate Search deactivate Controller ``` ### Transaction Communication This diagram shows the communication between two AEAs and the controller. In this case, we have an AEA in the role of the seller, referred to as `Seller_Agent`. We also have an AEA in the role of the buyer, referred to as `Buyer_Agent`. During a given TAC, an AEA can be in both roles simultaneously in different bilateral interactions. ``` mermaid sequenceDiagram participant Buyer_Agent participant Seller_Agent participant Search participant Controller activate Buyer_Agent activate Seller_Agent activate Search activate Controller Seller_Agent->>Search: register_service Buyer_Agent->>Search: search Search-->>Buyer_Agent: list_of_agents Buyer_Agent->>Seller_Agent: call_for_proposal Seller_Agent->>Buyer_Agent: proposal Buyer_Agent->>Seller_Agent: accept Seller_Agent->>Buyer_Agent: match_accept Seller_Agent->>Controller: transaction Controller->>Controller: transaction_execution Controller->>Seller_Agent: confirm_transaction Controller->>Buyer_Agent: confirm_transaction deactivate Buyer_Agent deactivate Seller_Agent deactivate Search deactivate Controller ``` In the above case, the proposal received contains a set of good which the seller wishes to sell and a cost of them. The buyer AEA needs to determine if this is a good deal for them and if so, it accepts. There is an equivalent diagram for seller AEAs set up to search for buyers and their interaction with AEAs which are registered as buyers. In that scenario, the proposal will instead, be a list of goods that the buyer wishes to buy and the price it is willing to pay for them. ## Option 1: AEA Manager Approach Follow this approach when using the AEA Manager Desktop app. Otherwise, skip and follow the CLI approach below. ### Preparation Instructions Install the AEA Manager. ### Demo Instructions The following steps assume you have launched the AEA Manager Desktop app. 1. Add a new AEA called `controller` with public id `fetchai/tac_controller:0.26.0`. 2. Add another new AEA called `participant_1` with public id `fetchai/tac_participant:0.28.0`. 3. Add another new AEA called `participant_2` with public id `fetchai/tac_participant:0.28.0`. 4. Navigate to the settings of `controller` and under `components > skill >` `fetchai/fetchai/tac_controller:0.22.0` `> models > parameters > args` update `registration_start_time` to the time you want TAC to begin (e.g. 2 minutes in the future) 5. Run the `controller` AEA. Navigate to its logs and copy the multiaddress displayed. Stop the `controller`. 6. Navigate to the settings of `participant_1` and under `components > connection >` `fetchai/p2p_libp2p:0.22.0` update as follows (make sure to replace the placeholder with the multiaddress): ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` 7. Navigate to the settings of `participant_2` and under `components > connection >` `fetchai/p2p_libp2p:0.22.0` update as follows (make sure to replace the placeholder with the multiaddress): ``` bash { "delegate_uri": "127.0.0.1:11002", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" } ``` 8. You may add more participants by repeating steps 3 (with an updated name) and 6 (bumping the port numbers. See the difference between steps 5 and 6). 9. Run the `controller`, then `participant_1` and `participant_2` (and any other participants you added). In the `controller`'s log, you should see the details of the transactions participants submit as well as changes in their scores and holdings. In participants' logs, you should see the agents trading. ## Option 2: CLI Approach Follow this approach when using the `aea` CLI. ## Preparation Instructions ### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ## Demo Instructions ### Create TAC Controller AEA In the root directory, fetch the controller AEA: ``` bash aea fetch fetchai/tac_controller:0.30.5 cd tac_controller aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the controller from scratch: ``` bash aea create tac_controller cd tac_controller aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_control:0.25.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ### Create the TAC Participant AEAs In a separate terminal, in the root directory, fetch at least two participants: ``` bash aea fetch fetchai/tac_participant:0.32.5 --alias tac_participant_one cd tac_participant_one aea install aea build cd .. aea fetch fetchai/tac_participant:0.32.5 --alias tac_participant_two cd tac_participant_two aea build ``` ??? note "Alternatively, create from scratch:" In a separate terminal, in the root directory, create at least two tac participant AEAs: ``` bash aea create tac_participant_one aea create tac_participant_two ``` Build participant one: ``` bash cd tac_participant_one aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea install aea build ``` Then, build participant two: ``` bash cd tac_participant_two aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea install aea build ``` ### Add Keys for All AEAs Create the private key for the AEA for Fetch.ai `Dorado`: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` ### Update the Game Parameters in the Controller Navigate to the tac controller project, then use the command line to get and set the start time (set it to at least two minutes in the future): ``` bash aea config get vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time aea config set vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time '01 01 2020 00:01' ``` To set the registration time, you may find handy the following command: ``` bash aea config set vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time "$(date -d "2 minutes" +'%d %m %Y %H:%M')" ``` ### Update the Connection Parameters Briefly run the controller AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) Then, in the participant one, run this command (replace `SOME_ADDRESS` with the correct value as described above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` Do the same in participant two (beware of the different port numbers): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11002", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" }' ``` This allows the TAC participants to connect to the same local agent communication network as the TAC controller. ### Run the AEAs First, launch the `tac_controller`: ``` bash aea run ``` The CLI tool supports the launch of several agents at once. For example, assuming you followed the tutorial, you can launch both the TAC agents as follows from the root directory: ``` bash aea launch tac_participant_one tac_participant_two ``` You may want to try `--multithreaded` option in order to run the agents in the same process. ### Cleaning up When you're finished, delete your AEAs: ``` bash aea delete tac_controller aea delete tac_participant_one aea delete tac_participant_two ``` ================================================ FILE: docs/tac.md ================================================ # TAC External App !!! note This app is no longer maintained. The original TAC has its own repo. Follow the instructions below to build and run the TAC demo. ## Requirements Make sure you are running Docker and Docker Compose. ## Quick Start Clone the repo to include submodules. ``` bash git clone git@github.com:fetchai/agents-tac.git --recursive && cd agents-tac ``` Check you have `pipenv`. ``` bash which pipenv ``` If you don't have it, install it. Instructions are here. Create and launch a virtual environment. ``` bash pipenv --python 3.7 && pipenv shell ``` Install the dependencies. ``` bash pipenv install ``` Install the package. ``` bash python setup.py install ``` Run the launch script. This may take a while. ``` bash python scripts/launch.py ``` The Visdom server is now running. The controller GUI at http://localhost:8097 provides real time insights. In the Environment tab, make sure you have the `tac_controller` environment selected. AEA Visdom UI ## Alternative Build and Run In a new terminal window, clone the repo, build the sandbox, and launch it. ``` bash git clone git@github.com:fetchai/agents-tac.git --recursive && cd agents-tac pipenv --python 3.7 && pipenv shell python setup.py install cd sandbox && docker-compose build docker-compose up ``` In a new terminal window, enter the virtual environment, and connect a template agent to the sandbox. ``` bash pipenv shell python templates/v1/basic.py --name my_agent --dashboard ``` Click through to the controller GUI. ## Possible Gotchas Stop all running containers before restart. ``` bash docker stop $(docker ps -q) ``` To remove all images, run the following command. ``` bash # mac docker ps -q | xargs docker stop ; docker system prune -a ``` ================================================ FILE: docs/thermometer-skills.md ================================================ # Thermometer Skills The AEA thermometer skills demonstrate an interaction between two AEAs, one purchasing temperature data from the other. - The provider of thermometer data (the `thermometer`). - The buyer of thermometer data (the `thermometer_client`). ## Discussion This demo aims to demonstrate how to create a very simple AEA with the usage of the AEA framework and a thermometer sensor. The thermometer AEA will read data from the sensor each time a client requests the data and will deliver it to the client upon payment. To keep the demo simple, we avoided the usage of a database since this would increase the complexity. As a result, the AEA can provide only one reading from the sensor. This demo does not utilise a smart contract. As a result, the ledger interaction is only for completing a transaction. ## Communication This diagram shows the communication between the various entities as data is successfully sold by the thermometer AEA to the client AEA. ``` mermaid sequenceDiagram participant Search participant Client_AEA participant Thermometer_AEA participant Blockchain activate Client_AEA activate Search activate Thermometer_AEA activate Blockchain Thermometer_AEA->>Search: register_service Client_AEA->>Search: search Search-->>Client_AEA: list_of_agents Client_AEA->>Thermometer_AEA: call_for_proposal Thermometer_AEA->>Client_AEA: propose Client_AEA->>Thermometer_AEA: accept Thermometer_AEA->>Client_AEA: match_accept Client_AEA->>Blockchain: transfer_funds Client_AEA->>Thermometer_AEA: send_transaction_hash Thermometer_AEA->>Blockchain: check_transaction_status Thermometer_AEA->>Client_AEA: send_data deactivate Client_AEA deactivate Search deactivate Thermometer_AEA deactivate Blockchain ``` ## Option 1: AEA Manager Approach Follow this approach when using the AEA Manager Desktop app. Otherwise, skip and follow the CLI approach below. ### Preparation Instructions Install the AEA Manager. ### Demo Instructions The following steps assume you have launched the AEA Manager Desktop app. 1. Add a new AEA called `my_thermometer_aea` with public id `fetchai/thermometer_aea:0.30.5`. 2. Add another new AEA called `my_thermometer_client` with public id `fetchai/thermometer_client:0.32.5`. 3. Copy the address from the `my_thermometer_client` into your clip board. Then go to the Dorado block explorer and request some test tokens via `Get Funds`. 4. Run the `my_thermometer_aea` AEA. Navigate to its logs and copy the multiaddress displayed. 5. Navigate to the settings of the `my_thermometer_client` and under `components > connection >` `fetchai/p2p_libp2p:0.22.0` update as follows (make sure to replace the placeholder with the multiaddress): ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` 6. Run the `my_thermometer_client`. In the AEA's logs, you should see the agent trading successfully. ## Option 2: CLI Approach Follow this approach when using the `aea` CLI. ### Preparation Instructions #### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ### Demo Instructions A demo to run the thermometer scenario with a true ledger transaction This demo assumes the buyer trusts the seller AEA to send the data upon successful payment. #### Create Thermometer AEA First, fetch the thermometer AEA: ``` bash aea fetch fetchai/thermometer_aea:0.30.5 --alias my_thermometer_aea cd my_thermometer_aea aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the thermometer AEA from scratch: ``` bash aea create my_thermometer_aea cd my_thermometer_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer:0.27.6 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` #### Create Thermometer Client Then, fetch the thermometer client AEA: ``` bash aea fetch fetchai/thermometer_client:0.32.5 --alias my_thermometer_client cd my_thermometer_client aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the thermometer client from scratch: ``` bash aea create my_thermometer_client cd my_thermometer_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer_client:0.26.6 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` #### Add Keys for the Thermometer AEA First, create the private key for the thermometer AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` #### Add Keys and Generate Wealth for the Thermometer Client AEA The thermometer client needs to have some wealth to purchase the thermometer information. First, create the private key for the thermometer client AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Then, create some wealth for your thermometer client based on the network you want to transact with. On the Fetch.ai `testnet` network: ``` bash aea generate-wealth fetchai ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` #### Run both AEAs Run both AEAs from their respective terminals. First, run the thermometer AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the thermometer AEA. Then, in the thermometer client, run this command (replace `SOME_ADDRESS` with the correct value as described above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` This allows the thermometer client to connect to the same local agent communication network as the thermometer AEA. Then run the thermometer client AEA: ``` bash aea run ``` You can see that the AEAs find each other, negotiate and eventually trade. #### Cleaning up When you're done, go up a level and delete the AEAs. ``` bash cd .. aea delete my_thermometer_aea aea delete my_thermometer_client ``` ================================================ FILE: docs/trust.md ================================================ # Trust Minimisation AEA applications have different requirements for _trustlessness_ or _trust minimisation_. For example, using the AEA weather skills demo _without_ ledger payments means that the client has to trust the weather station to send the weather data it purchased and that this data is in fact valid. Similarly, the weather station must trust that the client somehow sends the payment amount to which they agreed. A step-up, if you run the weather skills demo with a ledger (e.g. Fetch.ai or Ethereum) then the client must still trust that the weather station sends valid data. However, all payment transactions are executed via the public ledger. This means the weather station no longer needs to trust the client for payment and can verify whether the transactions take place on the public ledger. We can further minimise trust requirements by incorporating a third party as an arbitrator or escrow implemented in a smart contract to further reduce trust requirements. However, in the current weather skills demo, there are limits to trustlessness as the station ultimately offers unverifiable data. Another example of minimising trust, is applications with (non-fungible) token transactions involving atomic swaps where trustlessness is clearly satisfied (e.g. in the TAC demo). ================================================ FILE: docs/upgrading.md ================================================ # Upgrading This page provides some tips on how to upgrade AEA projects between different versions of the AEA framework. For full release notes check the AEA repo. The primary tool for upgrading AEA projects is the `aea upgrade` command in the CLI. Below we describe the additional manual steps required to upgrade between different versions: ## `v1.2.2` to `v1.2.3` Ensure you update the plugins to their latest version (all plugins are changed in this release) Update the packages to the latest versions (especially protocols). Update development environment ## `v1.2.0` to `v1.2.2` Ensure you update the plugins to their latest version (all plugins are changed in this release) Update the packages to the latest versions (especially protocols). Regenerate your own written protocols (protocol generator was updated) ## `v1.1.1` to `v1.2.0` Ensure you update the plugins to their latest version (fetchai and cosmos plugins are changed in this release) Update the packages to the latest versions (especially p2p_libp2p related packages are updated) Check packages’ and agents’ configurations are correct (e.g. the fetchai test-net name is changed for the Dorado network) ## `v1.1.0` to `v1.1.1` No backwards incompatible changes. Upgrade fetchai/p2p_libp2p connection package to the latest version which fixes a slow DHT lookup problem We advise everyone to upgrade their `fetchai` packages and plugins to get the latest fixes. ## `v1.0.2` to `v1.1.0` No backwards incompatible changes. We advise everyone to upgrade their `fetchai` packages and plugins to get the latest fixes. ## `v1.0.1` to `v1.0.2` No backwards incompatible changes. We advise everyone to upgrade their `fetchai` packages and plugins to get the latest fixes. ## `v1.0.0` to `v1.0.1` No backwards incompatible changes. We advise everyone to upgrade their `fetchai` packages to get the latest fixes. ## `v1.0.0rc2` to `v1.0.0` No backwards incompatible changes to component development. We advise everyone to upgrade to `v1` as soon as possible. When upgrading from versions below `v1.0.0rc1` first upgrade to the first release candidate, then to `v1`. ## `v1.0.0rc1` to `v1.0.0rc2` No backwards incompatible changes to component development. Various configuration changes introduced in `v1.0.0rc1` are now enforced strictly. ## `v0.11.1` to `v1.0.0rc1` No backwards incompatible changes to component development. The `aea-config.yaml` now requires the field `required_ledgers` which must specify all ledgers for which private keys are required to run the agent. Please add it to your project. The `registry_path` field has been removed from the `aea-config.yaml`. Please remove it from your project. All packages provided by author `fetchai` must be upgraded. ## `v0.11.0` to `v0.11.1` No backwards incompatible changes. ## `v0.10.1` to `v0.11.0` Take special care when upgrading to `v0.11.0`. We introduced several breaking changes in preparation for `v1`! ### CLI GUI We removed the CLI GUI. It was not used by anyone as far as we know and needs to be significantly improved. Soon we will release the AEA Manager App to make up for this. ### Message Routing Routing has been completely revised and simplified. The new message routing logic is described here. When upgrading take the following steps: - For agent-to-agent communication: ensure the default routing and default connection are correctly defined and that the dialogues used specify the agent's address as the `self_address`. This is most likely already the case. Only in some edge cases will you need to use an `EnvelopeContext` to target a connection different from the one specified in the `default_routing` map. - For component-to-component communication: there is now only one single way to route component to component (skill to skill, skill to connection, connection to skill) messages, this is by specifying the component id in string form in the `sender`/`to` field. The `EnvelopeContext` can no longer be used, messages are routed based on their target (`to` field). Ensure that dialogues in skills set the `skill_id` as the `self_address` (in connections they need to set the `connection_id`). ### Agent Configuration and Ledger Plugins Agent configuration files have a new optional field, `dependencies`, analogous to `dependencies` field in other AEA packages. The default value is the empty object `{}`. The field will be made mandatory in the next release. Crypto modules have been extracted and released as independent plug-ins, released on PyPI. In particular: - Fetch.ai crypto classes have been released in the `aea-ledger-fetchai` package; - Ethereum crypto classes have been released in the `aea-ledger-ethereum` package; - Cosmos crypto classes have been released in the `aea-ledger-cosmos` package. If an AEA project, or an AEA package, makes use of crypto functionalities, it will be needed to add the above packages as PyPI dependencies with version specifiers ranging from the latest minor and the latest minor + 1 (excluded). E.g. if the latest version if `0.1.0`, the version specifier should be `<0.2.0,>=0.1.0`: ``` yaml dependencies: aea-ledger-cosmos: version: <2.0.0,>=1.0.0 aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ``` The version specifier sets are important, as these plug-ins, at version `0.1.0`, depend on a specific range of the `aea` package. Then, running `aea install` inside the AEA project should install them in the current Python environment. For more, read the guide on ledger plugins. ## `v0.10.0` to `v0.10.1` No backwards incompatible changes for skill and connection development. ## `v0.9.2` to `v0.10.0` Skill development sees no backward incompatible changes. Connection development requires updating the keyword arguments of the constructor: the new `data_dir` argument must be defined. Protocol specifications now need to contain a `protocol_specification_id` in addition to the public id. The `protocol_specification_id` is used for identifying Envelopes during transport. By being able to set the id independently of the protocol id, backwards compatibility in the specification (and therefore wire format) can be maintained even when the Python implementation changes. Please update to the latest packages by running `aea upgrade` and then re-generating your own protocols. ## `v0.9.1` to `v0.9.2` No backwards incompatible changes for skill and connection development. ## `v0.9.0` to `v0.9.1` No backwards incompatible changes for skill and connection development. ## `v0.8.0` to `v0.9.0` This release introduces proof of representation in the ACN. You will need to upgrade to the latest `fetchai/p2p_libp2p` or `fetchai/p2p_libp2p_client` connection and then use two key pairs, one for your AEA's decision maker and one for the connection. Please update to the latest packages by running `aea upgrade`. ## `v0.7.5` to `v0.8.0` Minimal backwards incompatible changes for skill and connection development: - The semantics of the `<`, `<=`, `>` and `>=` relations in `ConstraintTypes` are simplified. - Protocols now need to correctly define terminal states. Regenerate your protocol to identify if your protocol's dialogue rules are valid. Please update to the latest packages by running `aea upgrade`. ## `v0.7.4` to `v0.7.5` No backwards incompatible changes for skill and connection development. ## `v0.7.3` to `v0.7.4` No backwards incompatible changes for skill and connection development. ## `v0.7.2` to `v0.7.3` No backwards incompatible changes for skill and connection development. ## `v0.7.1` to `v0.7.2` No backwards incompatible changes for skill and connection development. ## `v0.7.0` to `v0.7.1` To improve performance, in particular optimize memory usage, we refactored the `Message` and `Dialogue` classes. This means all protocols need to be bumped to the latest version or regenerated using the `aea generate protocol` command in the CLI. ## `v0.6.3` to `v0.7.0` Multiple breaking changes require action in this order: - Custom configuration overrides in `aea-config.yaml` are now identified via `public_id` rather than `author`, `name` and `version` individually. Please replace the three fields with the equivalent `public_id`. - Run `aea upgrade` command to upgrade your project's dependencies. Note, you still do have to manually update the public ids under `default_routing` and `default_connection` in `aea-config.yaml` as well as the public ids in the non-vendor packages. - Previously, connection `fetchai/stub`, skill `fetchai/error` and protocols `fetchai/default`, `fetchai/signing` and `fetchai/state_update` where part of the AEA distribution. Now they need to be fetched from registry. If you create a new project with `aea create` then this happens automatically. For existing projects, add the dependencies explicitly if not already present. You also must update the import paths as follows: - `aea.connections.stub` > `packages.fetchai.connections.stub` - `aea.protocols.default` > `packages.fetchai.protocols.default` - `aea.protocols.signing` > `packages.fetchai.protocols.signing` - `aea.protocols.state_update` > `packages.fetchai.protocols.state_update` - `aea.skills.error` > `packages.fetchai.skills.error` - If you use custom protocols, regenerate them. - In your own skills' `__init__.py` files add the public id (updating the string as appropriate): ``` python from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("author/name:0.1.0") ``` - The `fetchai/http` protocol's `bodyy` field has been renamed to `body`. - Skills can now specify `connections` as dependencies in the configuration YAML. ## `v0.6.2` to `v0.6.3` A new `upgrade` command is introduced to upgrade agent projects and components to their latest versions on the registry. To use the command first upgrade the AEA PyPI package to the latest version, then enter your project and run `aea upgrade`. The project's vendor dependencies will be updated where possible. ## `v0.6.1` to `v0.6.2` No public APIs have been changed. ## `v0.6.0` to `v0.6.1` The `soef` connection and `oef_search` protocol have backward incompatible changes. ## `v0.5.4` to `v0.6.0` ### `Dialogue` and `Dialogues` API Updates The dialogue and dialogues APIs have changed significantly. The constructor is different for both classes and there are now four primary methods for the developer: - `Dialogues.create`: this method is used to create a new dialogue and message: ``` python cfp_msg, fipa_dialogue = fipa_dialogues.create( counterparty=opponent_address, performative=FipaMessage.Performative.CFP, query=query, ) ``` The method will raise if the provided arguments are inconsistent. - `Dialogues.create_with_message`: this method is used to create a new dialogue from a message: ``` python fipa_dialogue = fipa_dialogues.create_with_message( counterparty=opponent_address, initial_message=cfp_msg ) ``` The method will raise if the provided arguments are inconsistent. - `Dialogues.update`: this method is used to handle messages passed by the framework: ``` python fipa_dialogue = fipa_dialogues.update( message=cfp_msg ) ``` The method will return a valid dialogue if it is a valid message, otherwise it will return `None`. - `Dialogue.reply`: this method is used to reply within a dialogue: ``` python proposal_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.PROPOSE, target_message=cfp_msg, proposal=proposal, ) ``` The method will raise if the provided arguments are inconsistent. The new methods significantly reduce the lines of code needed to maintain a dialogue. They also make it easier for the developer to construct valid dialogues and messages. ### `FetchAICrypto` - Default Crypto The `FetchAICrypto` has been upgraded to the default crypto. Update your `default_ledger` to `fetchai`. ### Private Key File Naming The private key files are now consistently named with the `ledger_id` followed by `_private_key.txt` (e.g. `fetchai_private_key.txt`). Rename your existing files to match this pattern. ### Type in Package YAML The package YAML files now contain a type field. This must be added for the loading mechanism to work properly. ### Moved Address Type The address type has moved to `aea.common`. The import paths must be updated. ## `v0.5.3` to `v0.5.4` The contract base class was slightly modified. If you have implemented your own contract package you need to update it accordingly. The dialogue reference nonce is now randomly generated. This can result in previously working but buggy implementations (which relied on the order of dialogue reference nonces) to now fail. ## `v0.5.2` to `v0.5.3` Connection states and logger usage in connections where updated. If you have implemented your own connection package you need to update it accordingly. Additional dialogue consistency checks where enabled. This can result in previously working but buggy implementations to now fail. ## `v0.5.1` to `0.5.2` No public APIs have been changed. ## `v0.5.0` to `0.5.1` No public APIs have been changed. ## `v0.4.1` to `0.5.0` A number of breaking changes where introduced which make backwards compatibility of skills rare. - Ledger APIs `LedgerApis` have been removed from the AEA constructor and skill context. `LedgerApis` are now exposed in the `LedgerConnection` (`fetchai/ledger`). To communicate with the `LedgerApis` use the `fetchai/ledger_api` protocol. This allows for more flexibility (anyone can add another `LedgerAPI` to the registry and execute it with the connection) and removes dependencies from the core framework. - Skills can now depend on other skills. As a result, skills have a new required configuration field in `skill.yaml` files, by default empty: `skills: []`. ## `v0.4.0` to `v0.4.1` There are no upgrade requirements if you use the CLI based approach to AEA development. Connections are now added via `Resources` to the AEA, not the AEA constructor directly. For programmatic usage remove the list of connections from the AEA constructor and instead add the connections to resources. ## `v0.3.3` to `v0.4.0` - Message sending in the skills has been updated. In the past you had to construct messages, then serialize them and place them in an envelope: ``` python cfp_msg = FipaMessage(...) self.context.outbox.put_message( to=opponent_addr, sender=self.context.agent_address, protocol_id=FipaMessage.protocol_id, message=FipaSerializer().encode(cfp_msg), ) # or cfp_msg = FipaMessage(...) envelope = Envelope( to=opponent_addr, sender=self.context.agent_address, protocol_id=FipaMessage.protocol_id, message=FipaSerializer().encode(cfp_msg), ) self.context.outbox.put(envelope) ``` Now this has been simplified to: ``` python cfp_msg = FipaMessage(...) cfp_msg.counterparty = opponent_addr self.context.outbox.put_message(message=cfp_msg) ``` You must update your skills as the old implementation is no longer supported. - Connection constructors have been simplified. In the past you had to implement both the `__init__` as well as the `from_config` methods of a Connection. Now you only have to implement the `__init__` method which by default at load time now receives the following keyword arguments: `configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore`. See for example in the scaffold connection: ``` python class MyScaffoldConnection(Connection): """Proxy to the functionality of the SDK or API.""" connection_id = PublicId.from_str("fetchai/scaffold:0.1.0") def __init__( self, configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, ): """ Initialize a connection to an SDK or API. :param configuration: the connection configuration. :param crypto_store: object to access the connection crypto objects. :param identity: the identity object. """ super().__init__( configuration=configuration, crypto_store=crypto_store, identity=identity ) ``` As a result of this feature, you are now able to pass key-pairs to your connections via the `CryptoStore`. You must update your connections as the old implementation is no longer supported. ================================================ FILE: docs/wealth.md ================================================ # Generating Wealth To fund an AEA for testing on a test-net you need to request some test tokens from a faucet. First, make sure you have installed the crypto plugin of the target test-net. E.g. for Fetch.AI: ``` bash pip install aea-ledger-fetchai ``` And for Ethereum: ``` bash pip install aea-ledger-ethereum ``` Add a private key to the agent ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` or ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` !!! note If you already have keys in your project, the commands prompt you to confirm whether to replace the existing keys. ## Using a Faucet Website First, print the address: ``` bash aea get-address fetchai ``` or ``` bash aea get-address ethereum ``` This will print the address to the console. Copy the address into the clipboard and request test tokens from the faucet here for Fetch.ai or here for Ethereum. It will take a while for the tokens to become available. Second, after some time, check the wealth associated with the address: ``` bash aea get-wealth fetchai ``` or ``` bash aea get-wealth ethereum ``` ## Using the CLI Simply generate wealth via the CLI: ``` bash aea generate-wealth fetchai ``` or ``` bash aea generate-wealth ethereum ``` !!! note This approach can be unreliable for non-fetchai test nets. ================================================ FILE: docs/weather-skills.md ================================================ # Weather Skills The AEA weather skills demonstrate an interaction between two AEAs. - The provider of weather data (the `weather_station`). - The buyer of weather data (the `weather_client`). ## Discussion The scope of the specific demo is to demonstrate how to create a simple AEA with the usage of the AEA framework and a database. The `weather_station` AEA will read data from the database, that is populated with readings from a weather station, based on the requested dates and will deliver the data to the client upon payment. This demo does not utilize a smart contract. As a result, we interact with a ledger only to complete a transaction. You can use this AEA as an example of how to read data from a database and advertise these to possible clients. ## Communication This diagram shows the communication between the various entities as data is successfully sold by the weather station AEA to the client. ``` mermaid sequenceDiagram participant Search participant Client_AEA participant Weather_AEA participant Blockchain activate Client_AEA activate Search activate Weather_AEA activate Blockchain Weather_AEA->>Search: register_service Client_AEA->>Search: search Search-->>Client_AEA: list_of_agents Client_AEA->>Weather_AEA: call_for_proposal Weather_AEA->>Client_AEA: propose Client_AEA->>Weather_AEA: accept Weather_AEA->>Client_AEA: match_accept Client_AEA->>Blockchain: transfer_funds Client_AEA->>Weather_AEA: send_transaction_hash Weather_AEA->>Blockchain: check_transaction_status Weather_AEA->>Client_AEA: send_data deactivate Client_AEA deactivate Search deactivate Weather_AEA deactivate Blockchain ``` ## Option 1: AEA Manager Approach Follow this approach when using the AEA Manager Desktop app. Otherwise, skip and follow the CLI approach below. ### Preparation Instructions Install the AEA Manager. ### Demo Instructions The following steps assume you have launched the AEA Manager Desktop app. 1. Add a new AEA called `my_weather_station` with public id `fetchai/weather_station:0.32.5`. 2. Add another new AEA called `my_weather_client` with public id `fetchai/weather_client:0.33.5`. 3. Copy the address from the `my_weather_client` into your clip board. Then go to the Dorado block explorer and request some test tokens via `Get Funds`. 4. Run the `my_weather_station` AEA. Navigate to its logs and copy the multiaddress displayed. 5. Navigate to the settings of the `my_weather_client` and under `components > connection >` `fetchai/p2p_libp2p:0.22.0` update as follows (make sure to replace the placeholder with the multiaddress): ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` 6. Run the `my_weather_client`. In the AEA's logs, you should see the agent trading successfully. ## Option 2: CLI Approach Follow this approach when using the `aea` CLI. ### Preparation Instructions #### Dependencies Follow the Preliminaries and Installation sections from the AEA quick start. ### Demo Instructions A demo to run the same scenario but with a true ledger transaction on Fetch.ai `testnet` or Ethereum `ropsten` network. This demo assumes the buyer trusts the seller AEA to send the data upon successful payment. #### Create the Weather Station First, fetch the AEA that will provide weather measurements: ``` bash aea fetch fetchai/weather_station:0.32.5 --alias my_weather_station cd my_weather_station aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the weather station from scratch: ``` bash aea create my_weather_station cd my_weather_station aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/weather_station:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` #### Create the Weather Client In another terminal, fetch the AEA that will query the weather station: ``` bash aea fetch fetchai/weather_client:0.33.5 --alias my_weather_client cd my_weather_client aea install aea build ``` ??? note "Alternatively, create from scratch:" The following steps create the weather client from scratch: ``` bash aea create my_weather_client cd my_weather_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/weather_client:0.26.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` #### Add Keys for the Weather Station AEA First, create the private key for the weather station AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` #### Add Keys and Generate Wealth for the Weather Client AEA The weather client needs to have some wealth to purchase the service from the weather station. First, create the private key for the weather client AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai `Dorado` use: ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` Then, create some wealth for your weather client based on the network you want to transact with. On the Fetch.ai `Dorado` network: ``` bash aea generate-wealth fetchai ``` Next, create a private key used to secure the AEA's communications: ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` Finally, certify the key for use by the connections that request that: ``` bash aea issue-certificates ``` #### Run the AEAs Run both AEAs from their respective terminals. First, run the weather station AEA: ``` bash aea run ``` Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` to retrieve the address.) This is the entry peer address for the local agent communication network created by the weather station. Then, in the weather client, run this command (replace `SOME_ADDRESS` with the correct value as described above): ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` This allows the weather client to connect to the same local agent communication network as the weather station. Then run the weather client AEA: ``` bash aea run ``` You will see that the AEAs negotiate and then transact using the selected ledger. #### Cleaning up When you're done, go up a level and delete the AEAs. ``` bash cd .. aea delete my_weather_station aea delete my_weather_client ``` ================================================ FILE: examples/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Examples using the AEA framework.""" ================================================ FILE: examples/aealite_go/README.md ================================================ #  A simple example using the Golang `aealite` module ================================================ FILE: examples/aealite_go/default/default.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.11.4 // source: default.proto package aea_fetchai_default_v0_1_0 import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type DefaultMessage_ErrorCode_ErrorCodeEnum int32 const ( DefaultMessage_ErrorCode_UNSUPPORTED_PROTOCOL DefaultMessage_ErrorCode_ErrorCodeEnum = 0 DefaultMessage_ErrorCode_DECODING_ERROR DefaultMessage_ErrorCode_ErrorCodeEnum = 1 DefaultMessage_ErrorCode_INVALID_MESSAGE DefaultMessage_ErrorCode_ErrorCodeEnum = 2 DefaultMessage_ErrorCode_UNSUPPORTED_SKILL DefaultMessage_ErrorCode_ErrorCodeEnum = 3 DefaultMessage_ErrorCode_INVALID_DIALOGUE DefaultMessage_ErrorCode_ErrorCodeEnum = 4 ) // Enum value maps for DefaultMessage_ErrorCode_ErrorCodeEnum. var ( DefaultMessage_ErrorCode_ErrorCodeEnum_name = map[int32]string{ 0: "UNSUPPORTED_PROTOCOL", 1: "DECODING_ERROR", 2: "INVALID_MESSAGE", 3: "UNSUPPORTED_SKILL", 4: "INVALID_DIALOGUE", } DefaultMessage_ErrorCode_ErrorCodeEnum_value = map[string]int32{ "UNSUPPORTED_PROTOCOL": 0, "DECODING_ERROR": 1, "INVALID_MESSAGE": 2, "UNSUPPORTED_SKILL": 3, "INVALID_DIALOGUE": 4, } ) func (x DefaultMessage_ErrorCode_ErrorCodeEnum) Enum() *DefaultMessage_ErrorCode_ErrorCodeEnum { p := new(DefaultMessage_ErrorCode_ErrorCodeEnum) *p = x return p } func (x DefaultMessage_ErrorCode_ErrorCodeEnum) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (DefaultMessage_ErrorCode_ErrorCodeEnum) Descriptor() protoreflect.EnumDescriptor { return file_default_proto_enumTypes[0].Descriptor() } func (DefaultMessage_ErrorCode_ErrorCodeEnum) Type() protoreflect.EnumType { return &file_default_proto_enumTypes[0] } func (x DefaultMessage_ErrorCode_ErrorCodeEnum) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use DefaultMessage_ErrorCode_ErrorCodeEnum.Descriptor instead. func (DefaultMessage_ErrorCode_ErrorCodeEnum) EnumDescriptor() ([]byte, []int) { return file_default_proto_rawDescGZIP(), []int{0, 0, 0} } type DefaultMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Performative: // *DefaultMessage_Bytes // *DefaultMessage_End // *DefaultMessage_Error Performative isDefaultMessage_Performative `protobuf_oneof:"performative"` } func (x *DefaultMessage) Reset() { *x = DefaultMessage{} if protoimpl.UnsafeEnabled { mi := &file_default_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DefaultMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*DefaultMessage) ProtoMessage() {} func (x *DefaultMessage) ProtoReflect() protoreflect.Message { mi := &file_default_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DefaultMessage.ProtoReflect.Descriptor instead. func (*DefaultMessage) Descriptor() ([]byte, []int) { return file_default_proto_rawDescGZIP(), []int{0} } func (m *DefaultMessage) GetPerformative() isDefaultMessage_Performative { if m != nil { return m.Performative } return nil } func (x *DefaultMessage) GetBytes() *DefaultMessage_Bytes_Performative { if x, ok := x.GetPerformative().(*DefaultMessage_Bytes); ok { return x.Bytes } return nil } func (x *DefaultMessage) GetEnd() *DefaultMessage_End_Performative { if x, ok := x.GetPerformative().(*DefaultMessage_End); ok { return x.End } return nil } func (x *DefaultMessage) GetError() *DefaultMessage_Error_Performative { if x, ok := x.GetPerformative().(*DefaultMessage_Error); ok { return x.Error } return nil } type isDefaultMessage_Performative interface { isDefaultMessage_Performative() } type DefaultMessage_Bytes struct { Bytes *DefaultMessage_Bytes_Performative `protobuf:"bytes,5,opt,name=bytes,proto3,oneof"` } type DefaultMessage_End struct { End *DefaultMessage_End_Performative `protobuf:"bytes,6,opt,name=end,proto3,oneof"` } type DefaultMessage_Error struct { Error *DefaultMessage_Error_Performative `protobuf:"bytes,7,opt,name=error,proto3,oneof"` } func (*DefaultMessage_Bytes) isDefaultMessage_Performative() {} func (*DefaultMessage_End) isDefaultMessage_Performative() {} func (*DefaultMessage_Error) isDefaultMessage_Performative() {} // Custom Types type DefaultMessage_ErrorCode struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ErrorCode DefaultMessage_ErrorCode_ErrorCodeEnum `protobuf:"varint,1,opt,name=error_code,json=errorCode,proto3,enum=aea.fetchai.default.v0_1_0.DefaultMessage_ErrorCode_ErrorCodeEnum" json:"error_code,omitempty"` } func (x *DefaultMessage_ErrorCode) Reset() { *x = DefaultMessage_ErrorCode{} if protoimpl.UnsafeEnabled { mi := &file_default_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DefaultMessage_ErrorCode) String() string { return protoimpl.X.MessageStringOf(x) } func (*DefaultMessage_ErrorCode) ProtoMessage() {} func (x *DefaultMessage_ErrorCode) ProtoReflect() protoreflect.Message { mi := &file_default_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DefaultMessage_ErrorCode.ProtoReflect.Descriptor instead. func (*DefaultMessage_ErrorCode) Descriptor() ([]byte, []int) { return file_default_proto_rawDescGZIP(), []int{0, 0} } func (x *DefaultMessage_ErrorCode) GetErrorCode() DefaultMessage_ErrorCode_ErrorCodeEnum { if x != nil { return x.ErrorCode } return DefaultMessage_ErrorCode_UNSUPPORTED_PROTOCOL } // Performatives and contents type DefaultMessage_Bytes_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Content []byte `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` } func (x *DefaultMessage_Bytes_Performative) Reset() { *x = DefaultMessage_Bytes_Performative{} if protoimpl.UnsafeEnabled { mi := &file_default_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DefaultMessage_Bytes_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*DefaultMessage_Bytes_Performative) ProtoMessage() {} func (x *DefaultMessage_Bytes_Performative) ProtoReflect() protoreflect.Message { mi := &file_default_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DefaultMessage_Bytes_Performative.ProtoReflect.Descriptor instead. func (*DefaultMessage_Bytes_Performative) Descriptor() ([]byte, []int) { return file_default_proto_rawDescGZIP(), []int{0, 1} } func (x *DefaultMessage_Bytes_Performative) GetContent() []byte { if x != nil { return x.Content } return nil } type DefaultMessage_Error_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ErrorCode *DefaultMessage_ErrorCode `protobuf:"bytes,1,opt,name=error_code,json=errorCode,proto3" json:"error_code,omitempty"` ErrorMsg string `protobuf:"bytes,2,opt,name=error_msg,json=errorMsg,proto3" json:"error_msg,omitempty"` ErrorData map[string][]byte `protobuf:"bytes,3,rep,name=error_data,json=errorData,proto3" json:"error_data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *DefaultMessage_Error_Performative) Reset() { *x = DefaultMessage_Error_Performative{} if protoimpl.UnsafeEnabled { mi := &file_default_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DefaultMessage_Error_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*DefaultMessage_Error_Performative) ProtoMessage() {} func (x *DefaultMessage_Error_Performative) ProtoReflect() protoreflect.Message { mi := &file_default_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DefaultMessage_Error_Performative.ProtoReflect.Descriptor instead. func (*DefaultMessage_Error_Performative) Descriptor() ([]byte, []int) { return file_default_proto_rawDescGZIP(), []int{0, 2} } func (x *DefaultMessage_Error_Performative) GetErrorCode() *DefaultMessage_ErrorCode { if x != nil { return x.ErrorCode } return nil } func (x *DefaultMessage_Error_Performative) GetErrorMsg() string { if x != nil { return x.ErrorMsg } return "" } func (x *DefaultMessage_Error_Performative) GetErrorData() map[string][]byte { if x != nil { return x.ErrorData } return nil } type DefaultMessage_End_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *DefaultMessage_End_Performative) Reset() { *x = DefaultMessage_End_Performative{} if protoimpl.UnsafeEnabled { mi := &file_default_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DefaultMessage_End_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*DefaultMessage_End_Performative) ProtoMessage() {} func (x *DefaultMessage_End_Performative) ProtoReflect() protoreflect.Message { mi := &file_default_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DefaultMessage_End_Performative.ProtoReflect.Descriptor instead. func (*DefaultMessage_End_Performative) Descriptor() ([]byte, []int) { return file_default_proto_rawDescGZIP(), []int{0, 3} } var File_default_proto protoreflect.FileDescriptor var file_default_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x22, 0x89, 0x07, 0x0a, 0x0e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x55, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x64, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x55, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0xef, 0x01, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x61, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x42, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x7f, 0x0a, 0x0d, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x4b, 0x49, 0x4c, 0x4c, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x44, 0x49, 0x41, 0x4c, 0x4f, 0x47, 0x55, 0x45, 0x10, 0x04, 0x1a, 0x2e, 0x0a, 0x12, 0x42, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x1a, 0xb1, 0x02, 0x0a, 0x12, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x6b, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4c, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x3c, 0x0a, 0x0e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x12, 0x0a, 0x10, 0x45, 0x6e, 0x64, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_default_proto_rawDescOnce sync.Once file_default_proto_rawDescData = file_default_proto_rawDesc ) func file_default_proto_rawDescGZIP() []byte { file_default_proto_rawDescOnce.Do(func() { file_default_proto_rawDescData = protoimpl.X.CompressGZIP(file_default_proto_rawDescData) }) return file_default_proto_rawDescData } var file_default_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_default_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_default_proto_goTypes = []interface{}{ (DefaultMessage_ErrorCode_ErrorCodeEnum)(0), // 0: aea.fetchai.default.v0_1_0.DefaultMessage.ErrorCode.ErrorCodeEnum (*DefaultMessage)(nil), // 1: aea.fetchai.default.v0_1_0.DefaultMessage (*DefaultMessage_ErrorCode)(nil), // 2: aea.fetchai.default.v0_1_0.DefaultMessage.ErrorCode (*DefaultMessage_Bytes_Performative)(nil), // 3: aea.fetchai.default.v0_1_0.DefaultMessage.Bytes_Performative (*DefaultMessage_Error_Performative)(nil), // 4: aea.fetchai.default.v0_1_0.DefaultMessage.Error_Performative (*DefaultMessage_End_Performative)(nil), // 5: aea.fetchai.default.v0_1_0.DefaultMessage.End_Performative nil, // 6: aea.fetchai.default.v0_1_0.DefaultMessage.Error_Performative.ErrorDataEntry } var file_default_proto_depIdxs = []int32{ 3, // 0: aea.fetchai.default.v0_1_0.DefaultMessage.bytes:type_name -> aea.fetchai.default.v0_1_0.DefaultMessage.Bytes_Performative 5, // 1: aea.fetchai.default.v0_1_0.DefaultMessage.end:type_name -> aea.fetchai.default.v0_1_0.DefaultMessage.End_Performative 4, // 2: aea.fetchai.default.v0_1_0.DefaultMessage.error:type_name -> aea.fetchai.default.v0_1_0.DefaultMessage.Error_Performative 0, // 3: aea.fetchai.default.v0_1_0.DefaultMessage.ErrorCode.error_code:type_name -> aea.fetchai.default.v0_1_0.DefaultMessage.ErrorCode.ErrorCodeEnum 2, // 4: aea.fetchai.default.v0_1_0.DefaultMessage.Error_Performative.error_code:type_name -> aea.fetchai.default.v0_1_0.DefaultMessage.ErrorCode 6, // 5: aea.fetchai.default.v0_1_0.DefaultMessage.Error_Performative.error_data:type_name -> aea.fetchai.default.v0_1_0.DefaultMessage.Error_Performative.ErrorDataEntry 6, // [6:6] is the sub-list for method output_type 6, // [6:6] is the sub-list for method input_type 6, // [6:6] is the sub-list for extension type_name 6, // [6:6] is the sub-list for extension extendee 0, // [0:6] is the sub-list for field type_name } func init() { file_default_proto_init() } func file_default_proto_init() { if File_default_proto != nil { return } if !protoimpl.UnsafeEnabled { file_default_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DefaultMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_default_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DefaultMessage_ErrorCode); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_default_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DefaultMessage_Bytes_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_default_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DefaultMessage_Error_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_default_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DefaultMessage_End_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_default_proto_msgTypes[0].OneofWrappers = []interface{}{ (*DefaultMessage_Bytes)(nil), (*DefaultMessage_End)(nil), (*DefaultMessage_Error)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_default_proto_rawDesc, NumEnums: 1, NumMessages: 6, NumExtensions: 0, NumServices: 0, }, GoTypes: file_default_proto_goTypes, DependencyIndexes: file_default_proto_depIdxs, EnumInfos: file_default_proto_enumTypes, MessageInfos: file_default_proto_msgTypes, }.Build() File_default_proto = out.File file_default_proto_rawDesc = nil file_default_proto_goTypes = nil file_default_proto_depIdxs = nil } ================================================ FILE: examples/aealite_go/default/default.proto ================================================ syntax = "proto3"; package aea.fetchai.default.v0_1_0; message DefaultMessage{ // Custom Types message ErrorCode{ enum ErrorCodeEnum { UNSUPPORTED_PROTOCOL = 0; DECODING_ERROR = 1; INVALID_MESSAGE = 2; UNSUPPORTED_SKILL = 3; INVALID_DIALOGUE = 4; } ErrorCodeEnum error_code = 1; } // Performatives and contents message Bytes_Performative{ bytes content = 1; } message Error_Performative{ ErrorCode error_code = 1; string error_msg = 2; map error_data = 3; } message End_Performative{ } oneof performative{ Bytes_Performative bytes = 5; End_Performative end = 6; Error_Performative error = 7; } } ================================================ FILE: examples/gym_ex/README.md ================================================ # RL agent & gym ## Dependencies This example requires `numpy` and `gym` in addition to the dependencies of the `aea` package. ## Run From root execute: ` python examples/gym_ex/train.py ` which has the usual RL setup (that is, the `fit` method of the `RLAgent` has the typical signature and familiar implementation). ================================================ FILE: examples/gym_ex/gyms/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the gym environment.""" ================================================ FILE: examples/gym_ex/gyms/env.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Training environment for multi armed bandit.""" from typing import List, Tuple import gym import numpy as np from gym import spaces # type: ignore BanditId = int Price = int Action = Tuple[BanditId, Price] Observation = type(None) Reward = float Done = bool Info = dict Feedback = Tuple[Observation, Reward, Done, Info] class BanditEnv(gym.Env): """Base environment for n-armed bandits.""" def __init__( self, nb_bandits: int, nb_prices_per_bandit: int, reward_params: List[Tuple[float, int]], ): """ Initialize the environment. :param nb_bandits: number of bandits :param nb_prices_per_bandit: number of prices per bandit :param reward_params: single param or tuple of params for the reward distribution """ self.nb_bandits = nb_bandits self.nb_prices_per_bandit = nb_prices_per_bandit self.reward_params = reward_params self.action_space = spaces.Tuple( ( spaces.Discrete(self.nb_bandits), spaces.Discrete(self.nb_prices_per_bandit), ) ) # an action is specifying one of nb_bandits and specifying a price for the bandit. self.observation_space = ( spaces.Space() ) # None type space. agents only get a reward back. self.seed() # type: ignore # seed environment randomness # pylint: disable=no-member # used in aries skills @staticmethod def reset() -> Observation: # type:ignore # pylint: disable=arguments-differ """ Reset the environment. :return: an observation """ observation = None # we purposefully make this explicit here return observation def step(self, action: Action) -> Feedback: # type: ignore """ Execute one time step within the environment. :param action: the id of the bandit chosen :return: a Tuple containing the Feedback of Observation, Reward, Done and Info """ if not self.action_space.contains(action): raise ValueError("This is not a valid action.") bandit = action[0] offered_price = action[1] # defaults observation = None done = False info = {} # type: Info cutoff_price = np.random.normal( self.reward_params[bandit][0], self.reward_params[bandit][1] ) if offered_price > cutoff_price: reward = 1.0 else: reward = 0.0 return observation, reward, done, info def render( # pylint: disable=arguments-differ self, mode: str = "human", close: int = False ) -> None: """ Render the environment to the screen. :param mode: the rendering mode :param close: a bool, true if ending """ class BanditNArmedRandom(BanditEnv): """N-armed bandit randomly initialized.""" def __init__( self, nb_bandits: int = 10, nb_prices_per_bandit: int = 100, stdev: int = 1, seed: int = 42, ): """ Initialize the environment. :param nb_bandits: number of bandits. :param nb_prices_per_bandit: number of prices per bandit. :param stdev: standard deviation of the normal distribution. :param seed: the seed to initialize np random (not the env!) """ np.random.seed(seed) reward_params = [] # type: List[Tuple[float, int]] for _i in range(nb_bandits): # Mean m is pulled from a uniform distribution over [0, bound). To induce a normal distribution with params # (m, 1). mean = np.random.uniform(0, nb_prices_per_bandit) reward_params.append((mean, stdev)) # type: ignore BanditEnv.__init__( self, nb_bandits=nb_bandits, nb_prices_per_bandit=nb_prices_per_bandit, reward_params=reward_params, ) ================================================ FILE: examples/gym_ex/proxy/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the proxies.""" ================================================ FILE: examples/gym_ex/proxy/agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This contains the proxy agent class.""" import os import sys from queue import Queue import gym from aea.agent import Agent from aea.configurations.base import ConnectionConfig from aea.helpers.base import locate from aea.identity.base import Identity from aea.mail.base import Envelope sys.modules["packages.fetchai.connections.gym"] = locate( # isort:skip "packages.fetchai.connections.gym" ) from packages.fetchai.connections.gym.connection import ( # noqa: E402 # pylint: disable=wrong-import-position GymConnection, ) ADDRESS = "some_address" PUBLIC_KEY = "some_public_key" class ProxyAgent(Agent): """This class implements a proxy agent to be used by a proxy environment.""" def __init__(self, name: str, gym_env: gym.Env, proxy_env_queue: Queue) -> None: """ Instantiate the agent. :param name: the name of the agent :param gym_env: gym environment :param proxy_env_queue: the queue of the proxy environment """ identity = Identity(name, ADDRESS, PUBLIC_KEY) configuration = ConnectionConfig(connection_id=GymConnection.connection_id) super().__init__( identity, [ GymConnection( gym_env, identity=identity, configuration=configuration, data_dir=os.getcwd(), ) ], period=0.01, ) self.proxy_env_queue = proxy_env_queue def setup(self) -> None: """Set up the agent.""" def act(self) -> None: """Perform actions.""" def handle_envelope(self, envelope: Envelope) -> None: """ Handle envelope. :param envelope: the envelope """ if envelope is not None: self.proxy_env_queue.put(envelope) def teardown(self) -> None: """Tear down the agent.""" ================================================ FILE: examples/gym_ex/proxy/env.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This contains the proxy gym environment.""" import sys import time from queue import Queue from threading import Thread from typing import Any, Optional, Tuple, cast import gym from aea.common import Address from aea.helpers.base import locate from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue sys.modules["packages.fetchai.connections.gym"] = locate( # isort:skip "packages.fetchai.connections.gym" ) sys.modules["packages.fetchai.protocols.gym"] = locate( # isort:skip "packages.fetchai.protocols.gym" ) from packages.fetchai.connections.gym.connection import ( # noqa: E402 # pylint: disable=wrong-import-position GymConnection, ) from packages.fetchai.connections.gym.connection import ( # noqa: E402 # pylint: disable=wrong-import-position PUBLIC_ID as GYM_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.gym.dialogues import ( # noqa: E402 # pylint: disable=wrong-import-position GymDialogue as BaseGymDialogue, ) from packages.fetchai.protocols.gym.dialogues import ( # noqa: E402 # pylint: disable=wrong-import-position GymDialogues as BaseGymDialogues, ) from packages.fetchai.protocols.gym.message import ( # noqa: E402 # pylint: disable=wrong-import-position GymMessage, ) from .agent import ProxyAgent # noqa: E402 # pylint: disable=wrong-import-position Action = Any Observation = Any Reward = float Done = bool Info = dict Feedback = Tuple[Observation, Reward, Done, Info] GymDialogue = BaseGymDialogue GymDialogues = BaseGymDialogues def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseGymDialogue.Role.AGENT class ProxyEnv(gym.Env): """This class implements a proxy gym environment.""" def __init__(self, gym_env: gym.Env) -> None: """ Instantiate the proxy environment. :param gym_env: gym environment """ super().__init__() self._queue: Queue = Queue() self._action_counter: int = 0 self.gym_address = str(GYM_CONNECTION_PUBLIC_ID) self._agent = ProxyAgent( name="proxy", gym_env=gym_env, proxy_env_queue=self._queue ) self._agent_thread = Thread(target=self._agent.start) self._active_dialogue = None # type: Optional[GymDialogue] self.gym_skill = "fetchai/gym:0.1.0" self.gym_dialogues = GymDialogues(self.gym_skill, role_from_first_message) @property def active_dialogue(self) -> GymDialogue: """Get the active dialogue.""" if self._active_dialogue is None: raise RuntimeError("active dialogue was not set") return self._active_dialogue def step(self, action: Action) -> Feedback: # type: ignore """ Run one time-step of the environment's dynamics. Mirrors the standard 'step' method of a gym environment. - The action is given to _encode_action, which does the necessary conversion to an envelope. - The envelope is given to the outbox of the proxy agent. - The method blocks until the _queue returns an envelope. - The envelope is decoded with _decode_percept to a message. - The message is converted into the standard observation, reward, done and info via _message_to_percept :param action: the action sent to the step method (e.g. the output of an RL algorithm) :return: a Tuple containing the Feedback of Observation, Reward, Done and Info """ self._action_counter += 1 step_id = self._action_counter self._encode_and_send_action(action, step_id) # Wait (blocking!) for the response envelope from the environment in_envelope = self._queue.get(block=True, timeout=None) # type: Envelope msg = self._decode_percept(in_envelope, step_id) observation, reward, done, info = self._message_to_percept(msg) return observation, reward, done, info def render(self, mode: str = "human") -> None: """ Render the environment. :param mode: the run mode """ if self._agent.runtime.multiplexer.default_connection is None: raise RuntimeError("Default connection was not set") cast( GymConnection, self._agent.runtime.multiplexer.default_connection ).channel.gym_env.render( mode ) # type: ignore def reset(self) -> None: # type: ignore # pylint: disable=arguments-differ """Reset the environment.""" if not self._agent.runtime.multiplexer.is_connected: self._connect() gym_msg, gym_dialogue = self.gym_dialogues.create( counterparty=self.gym_address, performative=GymMessage.Performative.RESET, ) gym_dialogue = cast(GymDialogue, gym_dialogue) self._active_dialogue = gym_dialogue self._agent.outbox.put_message(message=gym_msg) # Wait (blocking!) for the response envelope from the environment in_envelope = self._queue.get(block=True, timeout=None) # type: Envelope self._decode_status(in_envelope) def close(self) -> None: """Close the environment.""" last_msg = self.active_dialogue.last_message if last_msg is None: raise ValueError("Cannot retrieve last message.") gym_msg = self.active_dialogue.reply( performative=GymMessage.Performative.CLOSE, target_message=last_msg, ) self._agent.outbox.put_message(message=gym_msg) self._disconnect() def _connect(self) -> None: """Connect to this proxy environment. It starts a proxy agent that can interact with the framework.""" if self._agent_thread.is_alive(): raise ValueError("Agent already running.") self._agent_thread.start() while not self._agent.runtime.is_running: # check agent completely running time.sleep(0.01) def _disconnect(self) -> None: """Disconnect from this proxy environment. It stops the proxy agent and kills its thread.""" self._agent.stop() self._agent_thread.join() def _encode_and_send_action(self, action: Action, step_id: int) -> None: """ Encode the 'action' sent to the step function and send. :param action: the action that is the output of an RL algorithm. :param step_id: the step id """ last_msg = self.active_dialogue.last_message if last_msg is None: raise ValueError("Cannot retrieve last message.") gym_msg = self.active_dialogue.reply( performative=GymMessage.Performative.ACT, target_message=last_msg, action=GymMessage.AnyObject(action), step_id=step_id, ) # Send the message via the proxy agent and to the environment self._agent.outbox.put_message(message=gym_msg) def _decode_percept(self, envelope: Envelope, expected_step_id: int) -> GymMessage: """ Receive the response from the gym environment in the form of an envelope and decode it. The response is a PERCEPT message containing the usual 'observation', 'reward', 'done', 'info' parameters. :param envelope: the envelope :param expected_step_id: the expected step id :return: a message received as a response to the action performed in apply_action. """ if envelope is not None: if ( envelope.protocol_specification_id == GymMessage.protocol_specification_id ): gym_msg = cast(GymMessage, envelope.message) gym_dialogue = self.gym_dialogues.update(gym_msg) if not gym_dialogue: raise ValueError("Could not udpate dialogue.") if not gym_dialogue == self.active_dialogue: raise ValueError("Dialogue does not match.") if ( gym_msg.performative == GymMessage.Performative.PERCEPT and gym_msg.step_id == expected_step_id ): return gym_msg raise ValueError( "Unexpected performative or no step_id: {}".format( gym_msg.performative ) ) raise ValueError( "Unknown protocol_specification_id: {}".format( envelope.protocol_specification_id ) ) raise ValueError("Missing envelope.") def _decode_status(self, envelope: Envelope) -> None: """ Receive the response from the gym environment in the form of an envelope and decode it. The response is a STATUS message. :param envelope: the envelope :return: a message received as a response to the action performed in apply_action. """ if envelope is not None: if ( envelope.protocol_specification_id == GymMessage.protocol_specification_id ): gym_msg = cast(GymMessage, envelope.message) gym_dialogue = self.gym_dialogues.update(gym_msg) if not gym_dialogue: raise ValueError("Could not udpate dialogue.") if not gym_dialogue == self.active_dialogue: raise ValueError("Dialogue does not match.") if ( gym_msg.performative == GymMessage.Performative.STATUS and gym_msg.content.get("reset", "failure") == "success" ): return None raise ValueError( "Unexpected performative or no step_id: {}".format( gym_msg.performative ) ) raise ValueError( "Unknown protocol_id: {}".format(envelope.protocol_specification_id) ) raise ValueError("Missing envelope.") @staticmethod def _message_to_percept(message: GymMessage) -> Feedback: """ Transform the message received from the gym environment into observation, reward, done, info. :param message: the message received as a response to the action performed in apply_action. :return: the standard feedback (observation, reward, done, info) of a gym environment. """ observation = cast(Any, message.observation.any) reward = cast(float, message.reward) done = cast(bool, message.done) info = cast(dict, message.info.any) return observation, reward, done, info ================================================ FILE: examples/gym_ex/rl/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the rl modules.""" ================================================ FILE: examples/gym_ex/rl/agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This contains the rl agent class.""" import random from typing import Dict, Tuple import gym import numpy as np BanditId = int Price = int Action = Tuple[BanditId, Price] Observation = type(None) Reward = float Done = bool Info = dict Feedback = Tuple[Observation, Reward, Done, Info] class PriceBandit: """A class for a multi-armed bandit model of price.""" def __init__(self, price: float, beta_a: float = 1.0, beta_b: float = 1.0): """ Instantiate a price bandit object. :param price: the price this bandit is modelling :param beta_a: the a parameter of the beta distribution :param beta_b: the b parameter of the beta distribution """ self.price = price # default params imply a uniform random prior self.beta_a = beta_a self.beta_b = beta_b def sample(self) -> int: """ Sample from the bandit. :return: the sampled value """ return round(np.random.beta(self.beta_a, self.beta_b)) def update(self, outcome: float) -> None: """ Update the bandit. :param outcome: the outcome used for updating """ self.beta_a += outcome self.beta_b += 1 - outcome class GoodPriceModel: """A class for a price model of a good.""" def __init__(self, bound: int = 100): """Instantiate a good price model.""" self.price_bandits = dict( (price, PriceBandit(price)) for price in range(bound + 1) ) def update(self, outcome: float, price: int) -> None: """ Update the respective bandit. :param price: the price to be updated :param outcome: the negotiation outcome """ bandit = self.price_bandits[price] bandit.update(outcome) def get_price_expectation(self) -> int: """ Get best price. :return: the winning price """ max_sample = -1 winning_price = 0 for price, bandit in self.price_bandits.items(): sample = bandit.sample() if sample > max_sample: max_sample = sample winning_price = price return winning_price class RLAgent: """This class is a reinforcement learning agent that uses a Multi-Armed Bandit algorithm.""" def __init__(self, nb_goods: int = 10) -> None: """ Instantiate the RL agent. :param nb_goods: number of goods """ self.good_price_models = dict( (good_id, GoodPriceModel()) for good_id in range(nb_goods) ) # type: Dict[int, GoodPriceModel] def _pick_an_action(self) -> Action: """ Pick an action. :return: agent's action """ # Get the good good_id = self._get_random_next_good() # Pick the best price based on own model. good_price_model = self.good_price_models[good_id] price = good_price_model.get_price_expectation() action = (good_id, price) return action def _update_model( # pylint: disable=unused-argument self, observation: Observation, reward: Reward, done: Done, info: Info, action: Action, ) -> None: """ Update the model. :param: observation: agent's observation of the current environment :param: reward: amount of reward returned after previous action :param: done: whether the episode has ended :param: info: auxiliary diagnostic information :param: action: action the agent performed on the environment to which the response was the above 4 """ good_id, price = action # Update the price model: good_price_model = self.good_price_models[good_id] good_price_model.update(reward, price) def _get_random_next_good(self) -> int: """ Get the next good for trading (randomly). :return: a random good """ return random.choice(list(self.good_price_models.keys())) # nosec def fit(self, env: gym.Env, nb_steps: int) -> None: """ Train the agent on the given proxy environment. :param env: the gym environment in which the agent is trained :param nb_steps: number of training steps to be performed """ action_counter = 0 # for the BanditEnv example, the episode will always be 1. # In general that's not the case, but for completeness # we implemented a training loop that supports learning across many episodes. episode_counter = 0 nb_steps_digits = len(str(nb_steps)) while action_counter < nb_steps: env.reset() done = False episode_counter += 1 while not done and action_counter < nb_steps: action = self._pick_an_action() action_counter += 1 obs, reward, done, info, *_ = env.step(action) # type: ignore self._update_model(obs, reward, done, info, action) # type: ignore if action_counter % 10 == 0: print( ( "Step {:" + str(nb_steps_digits) + "}/{}, episode={}, action={:7}, reward={}" ).format( action_counter, nb_steps, episode_counter, str(action), reward, # type: ignore ) ) env.close() ================================================ FILE: examples/gym_ex/train.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Training a multi armed bandit rl agent using the aea framework.""" import argparse from gyms.env import BanditNArmedRandom # noqa: I201 from proxy.env import ProxyEnv # noqa: I201 from rl.agent import RLAgent # noqa: I201 DEFAULT_NB_GOODS = 10 DEFAULT_NB_PRICES_PER_GOOD = 100 DEFAULT_NB_STEPS = 4000 parser = argparse.ArgumentParser("train", description="Train an RL agent.") parser.add_argument( "--nb-steps", type=int, default=DEFAULT_NB_STEPS, help="The number of training steps.", ) parser.add_argument( "--nb-goods", type=int, default=DEFAULT_NB_GOODS, help="The number of goods." ) parser.add_argument( "--nb-prices-per-good", type=int, default=DEFAULT_NB_PRICES_PER_GOOD, help="The number of prices per goods.", ) if __name__ == "__main__": arguments = parser.parse_args() # Use any gym.Env compatible environment: gym_env = BanditNArmedRandom( nb_bandits=arguments.nb_goods, nb_prices_per_bandit=arguments.nb_prices_per_good ) # Pass the gym environment to a proxy environment: proxy_env = ProxyEnv(gym_env) # Use any RL agent compatible with the gym environment and call the fit method: rl_agent = RLAgent(nb_goods=arguments.nb_goods) rl_agent.fit(env=proxy_env, nb_steps=arguments.nb_steps) ================================================ FILE: examples/http_ex/petstore.yaml ================================================ openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore license: name: MIT servers: - url: '' paths: /pets: get: summary: List all pets operationId: listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer format: int32 responses: '200': description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/Pets" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" post: summary: Create a pet operationId: createPets tags: - pets responses: '201': description: Null response default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById tags: - pets parameters: - name: petId in: path required: true description: The id of the pet to retrieve schema: type: string responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "#/components/schemas/Pet" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" components: schemas: Pet: type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string Pets: type: array items: $ref: "#/components/schemas/Pet" Error: type: object required: - code - message properties: code: type: integer format: int32 message: type: string ================================================ FILE: examples/ml_ex/model.json ================================================ {"name": "my_model", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 28, 28], "dtype": "float32", "sparse": false, "name": "pictures"}, "name": "pictures", "inbound_nodes": []}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 128, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense_1", "inbound_nodes": [[["pictures", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "activations", "trainable": true, "dtype": "float32", "units": 10, "activation": "softmax", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "activations", "inbound_nodes": [[["dense_1", 0, 0, {}]]]}], "input_layers": [["pictures", 0, 0]], "output_layers": [["activations", 0, 0]]} ================================================ FILE: examples/protocol_specification_ex/sample.yaml ================================================ --- name: two_party_negotiation author: fetchai version: 0.1.0 description: An example of a protocol specification that describes a protocol for bilateral negotiation. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/two_party_negotiation:0.1.0 speech_acts: cfp: query: ct:Query propose: price: pt:float proposal: pt:dict[pt:str, pt:str] conditions: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:str], pt:set[pt:str]]] resources: pt:list[pt:bytes] accept: {} decline: {} ... --- ct:Query: | bytes query_bytes = 1; ... --- initiation: [cfp] reply: cfp: [propose, decline] propose: [propose, accept, decline] accept: [] decline: [] termination: [accept, decline] roles: {buyer, seller} end_states: [agreement_reached, agreement_unreached] keep_terminal_state_dialogues: true ... ================================================ FILE: examples/tac_deploy/.aea/cli_config.yaml ================================================ auth_token: author: registry_path: "packages" ================================================ FILE: examples/tac_deploy/Dockerfile ================================================ FROM python:3.8-alpine USER root ARG USE_CLIENT ENV USE_CLIENT=$USE_CLIENT RUN apk add --no-cache make git bash # cryptography: https://cryptography.io/en/latest/installation/#alpine RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev # https://stackoverflow.com/a/57485724 RUN apk add --update --no-cache py3-numpy py3-scipy py3-pillow ENV PYTHONPATH "${PYTHONPATH}:/usr/lib/python3.8/site-packages" # golang RUN apk add --no-cache go # aea installation RUN python -m pip install --upgrade pip RUN pip install --upgrade --force-reinstall aea[all]==1.1.1 # directories and aea cli config COPY /.aea /home/.aea WORKDIR /home/agents COPY ./packages /home/agents/packages # aea build script COPY /build.sh /build.sh RUN ["chmod", "+x", "/build.sh"] RUN [ "/build.sh" ] # optionally, specify any ports to expose here # EXPOSE 9000 # aea entrypoint script COPY /entrypoint.sh /entrypoint.sh RUN ["chmod", "+x", "/entrypoint.sh"] CMD [ "/entrypoint.sh" ] ================================================ FILE: examples/tac_deploy/README.md ================================================ # Tac Deployment image The TAC deployment deploys one controller and `n` tac participants. ## Build the image First, ensure the specifications in `.env` match your requirements. Then, to build the image run: ``` bash docker build -t tac-deploy -f Dockerfile . --no-cache ``` ## Run locally Specify preferred amount of tac participants agents in `.env` file, e.g.: ``` bash PARTICIPANTS_AMOUNT=5 ``` Run: ``` bash docker run --env-file .env -v "$(pwd)/data:/data" -ti tac-deploy ``` ## Run in the cloud (here using GCloud) GCloud should be [configured](https://cloud.google.com/sdk/docs/initializing) first! ### Push image Tag the image first with the latest tag: ``` bash docker image tag tac-deploy gcr.io/fetch-ai-sandbox/tac_deploy:0.0.14 ``` Push it to remote repo: ``` bash docker push gcr.io/fetch-ai-sandbox/tac_deploy:0.0.14 ``` ### Run it manually Run it ``` bash kubectl run tac-deploy-{SOMETHING} --image=gcr.io/fetch-ai-sandbox/tac_deploy:0.0.13 --env="PARTICIPANTS_AMOUNT=5" --attach ``` Or simply restart existing deployment and latest image will be used with default configurations (see below): ``` bash kubectl delete pod tac-deploy-{SOMETHING} ``` ### Manipulate container To access the container run: ``` bash kubectl exec tac-deploy-{SOMETHING} -ti -- /bin/sh ``` To remove all logs and all keys: ``` bash cd ../../data find . -name \*.log -type f -delete find . -name \*.txt -type f -delete ``` ### Full deployment First, push the latest image, as per above. Second, update the `tac-deployment.yaml` file with the correct image tag and configurations and then run: ``` bash kubectl apply -f ./tac-deployment.yaml ``` Check for pods list: ``` bash kubectl get pods ``` To fetch logs: ``` bash kubectl cp tac-deploy-{SOMETHING}:/data ./output_dir ``` To delete deployment: ``` bash kubectl delete deployment tac-deploy ``` ### Analysing Logs Handy commands to analyse logs: ``` bash grep -rl 'TAKE CARE! Circumventing controller identity check!' output_dir/ | sort grep -rl 'TAKE CARE! Circumventing controller identity check!' output_dir/ | wc -l grep -rnw 'SOEF Network Connection Error' output_dir/ | wc -l grep -rnw 'SOEF Server Bad Response Error' output_dir/ | wc -l grep -rnw ' Connection reset by peer' output_dir/ | wc -l grep -rnw 'Failure during pipe closing.' output_dir/ | wc -l grep -rnw "Couldn't connect to libp2p process within timeout" output_dir/ | wc -l grep -rnw 'Exception on connect:' output_dir/ | wc -l grep -rnw 'Exception' output_dir/ | wc -l grep -rnw 'connect to libp2p process within timeout' output_dir/ | wc -l grep -rnw 'handling valid transaction' output_dir/tac_controller/ | wc -l grep -rnw 'received invalid tac message' output_dir/tac_controller/ | wc -l ``` ================================================ FILE: examples/tac_deploy/build.sh ================================================ #!/bin/bash set -e if [ -z "$USE_CLIENT" ]; then USE_CLIENT=false fi echo USE_CLIENT $USE_CLIENT if [ -z "$PACKAGES_SOURCE" ]; then PACKAGES_SOURCE="--remote" fi echo PACKAGES_SOURCE $PACKAGES_SOURCE mkdir /data # setup the agent aea fetch $PACKAGES_SOURCE fetchai/tac_controller:latest cd tac_controller if [[ "$USE_CLIENT" == "true" ]] then aea remove connection fetchai/p2p_libp2p aea add $PACKAGES_SOURCE connection fetchai/p2p_libp2p_client aea config set agent.default_connection fetchai/p2p_libp2p_client:0.18.0 fi aea install aea build cd .. aea fetch $PACKAGES_SOURCE fetchai/tac_participant:latest --alias tac_participant_template cd tac_participant_template if [[ "$USE_CLIENT" == "true" ]] then aea remove connection fetchai/p2p_libp2p aea add $PACKAGES_SOURCE connection fetchai/p2p_libp2p_client aea config set agent.default_connection fetchai/p2p_libp2p_client:0.18.0 fi aea install aea build cd .. ================================================ FILE: examples/tac_deploy/entrypoint.sh ================================================ #!/bin/bash set -e LEDGER=fetchai PEER="/dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW" TAC_NAME='v'$((10 + $RANDOM % 1000)) TAC_SERVICE=tac_service_$TAC_NAME BASE_PORT=10000 BASE_DIR=/data OLD_DIR=/$(date "+%d_%m_%Y_%H%M") cp -R "$BASE_DIR" "$OLD_DIR" if [ -z "$COMPETITION_TIMEOUT" ]; then COMPETITION_TIMEOUT=86400 fi echo COMPETITION_TIMEOUT $COMPETITION_TIMEOUT if [ -z "$INACTIVITY_TIMEOUT" ]; then INACTIVITY_TIMEOUT=3600 fi echo INACTIVITY_TIMEOUT $INACTIVITY_TIMEOUT if [ -z "$PARTICIPANTS_AMOUNT" ]; then PARTICIPANTS_AMOUNT=2 fi echo PARTICIPANTS_AMOUNT $PARTICIPANTS_AMOUNT if [ -z "$MINUTES_TILL_START" ]; then MINUTES_TILL_START=2 fi echo MINUTES_TILL_START $MINUTES_TILL_START if [ -z "$SEARCH_INTERVAL_GAME" ]; then SEARCH_INTERVAL_GAME=20 fi echo SEARCH_INTERVAL_GAME $SEARCH_INTERVAL_GAME if [ -z "$SEARCH_INTERVAL_TRADING" ]; then SEARCH_INTERVAL_TRADING=600 fi echo SEARCH_INTERVAL_TRADING $SEARCH_INTERVAL_TRADING if [ -z "$CLEANUP_INTERVAL" ]; then CLEANUP_INTERVAL=1800 fi echo CLEANUP_INTERVAL $CLEANUP_INTERVAL if [ -z "$NODE_CONNECTION_TIMEOUT" ]; then NODE_CONNECTION_TIMEOUT=30 fi echo NODE_CONNECTION_TIMEOUT $NODE_CONNECTION_TIMEOUT if [ -z "$LOG_LEVEL" ]; then LOG_LEVEL=INFO fi echo LOG_LEVEL $LOG_LEVEL if [ -z "$CLEAR_LOG_DATA_ON_LAUNCH" ]; then CLEAR_LOG_DATA_ON_LAUNCH=true fi echo CLEAR_LOG_DATA_ON_LAUNCH $CLEAR_LOG_DATA_ON_LAUNCH if [ -z "$CLEAR_KEY_DATA_ON_LAUNCH" ]; then CLEAR_KEY_DATA_ON_LAUNCH=false fi echo CLEAR_KEY_DATA_ON_LAUNCH $CLEAR_KEY_DATA_ON_LAUNCH if [ "$CLEAR_LOG_DATA_ON_LAUNCH" == true ]; then find "$BASE_DIR" -name \*.log -type f -delete fi if [ "$CLEAR_KEY_DATA_ON_LAUNCH" == true ]; then find "$BASE_DIR" -name \*.txt -type f -delete fi if [ -z "$USE_CLIENT" ]; then USE_CLIENT=false fi function generate_key (){ ledger=$1 prefix=$2 base_dir=$3 connection=$4 if [ $connection = '1' ]; then connection='_connection' else connection='' fi filename="${base_dir}/${prefix}_${ledger}${connection}_private_key.txt" if [ -e "${filename}" ]; then echo > /dev/null else aea generate-key $ledger $filename $con_option fi echo ${filename} } function set_agent(){ name=$1 port=$2 echo name $name port $port agent_data_dir=$BASE_DIR/$name mkdir -p $agent_data_dir key_file_name=$(generate_key $LEDGER $name $agent_data_dir 0) aea add-key fetchai $key_file_name key_file_name=$(generate_key $LEDGER $name $agent_data_dir 1) aea add-key fetchai $key_file_name --connection if [ "$USE_CLIENT" == "false" ]; then json=$(printf '{"log_file": "%s", "delegate_uri": null, "entry_peers": ["%s"], "local_uri": "127.0.0.1:%s", "public_uri": null, "node_connection_timeout": '%i'}' "$agent_data_dir/libp2p_node.log" "$PEER" "$port" "$(($NODE_CONNECTION_TIMEOUT))") aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config "$json" fi aea issue-certificates log_file=$agent_data_dir/$name.log json=$(printf '{"version": 1, "formatters": {"standard": {"format": ""}}, "handlers": {"console": {"class": "logging.StreamHandler", "formatter": "standard", "level": "%s"}, "file": {"class": "logging.FileHandler", "filename": "%s", "mode": "w", "level": "%s", "formatter": "standard"}}, "loggers": {"aea": {"level": "%s", "handlers": ["file"]}}}' "$LOG_LEVEL" "$log_file" "$LOG_LEVEL" "$LOG_LEVEL") aea config set --type dict agent.logging_config "$json" aea config set agent.logging_config.formatters.standard.format '%(asctime)s [%(levelname)s]: %(message)s' aea config set vendor.fetchai.connections.soef.config.token_storage_path $agent_data_dir/soef_token.txt aea config set agent.skill_exception_policy just_log aea config set agent.connection_exception_policy just_log } function set_tac_name (){ json=$(printf '{"key": "tac", "value": "%s"}' $TAC_NAME) aea config set --type dict vendor.fetchai.skills.tac_control.models.parameters.args.service_data "$json" } function set_participant(){ agent_id=$1 agent_name=$2 echo "cp -r tac_participant_template $agent_name" cp -r tac_participant_template $agent_name cd $agent_name # cause set agent name is not allowed! sed -e "s/tac_participant_template/$agent_name/" -i ./aea-config.yaml set_agent $agent_name $(expr $BASE_PORT + $agent_id) aea config set vendor.fetchai.skills.tac_negotiation.behaviours.clean_up.args.tick_interval $CLEANUP_INTERVAL aea config set vendor.fetchai.skills.tac_negotiation.behaviours.tac_negotiation.args.search_interval $SEARCH_INTERVAL_TRADING aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.service_key $TAC_SERVICE aea config set vendor.fetchai.skills.tac_participation.behaviours.tac_search.args.tick_interval $SEARCH_INTERVAL_GAME aea config set vendor.fetchai.skills.tac_participation.models.game.args.search_query.search_value $TAC_NAME cd .. } agents_list='' for i in $(seq $PARTICIPANTS_AMOUNT); do agent_name="tac_participant_$i" set_participant $i $agent_name agents_list="$agent_name $agents_list" done cd tac_controller set_agent tac_controller $BASE_PORT set_tac_name datetime_start=$(date -d@"$(( `date +%s`+$MINUTES_TILL_START*60))" "+%d %m %Y %H:%M") aea config set vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time "$datetime_start" aea config set vendor.fetchai.skills.tac_control.models.parameters.args.competition_timeout $COMPETITION_TIMEOUT aea config set vendor.fetchai.skills.tac_control.models.parameters.args.inactivity_timeout $INACTIVITY_TIMEOUT cd .. echo "Launching agents..." aea launch tac_controller $agents_list ================================================ FILE: examples/tac_deploy/packages/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Package registry for the AEA framework.""" ================================================ FILE: examples/tac_deploy/tac-deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: tac-deploy spec: selector: matchLabels: app: tac-deploy replicas: 1 template: metadata: labels: app: tac-deploy namespace: aea-research spec: nodeSelector: # type: agent-test kubernetes.io/os: linux containers: - name: tac-deploy-container image: gcr.io/fetch-ai-sandbox/tac_deploy:0.0.14 resources: requests: memory: "12000000Ki" cpu: "3700m" limits: memory: "12000000Ki" cpu: "3700m" env: - name: PARTICIPANTS_AMOUNT value: "70" - name: MINUTES_TILL_START value: "10" - name: COMPETITION_TIMEOUT value: "86400" - name: INACTIVITY_TIMEOUT value: "3600" - name: SEARCH_INTERVAL_GAME value: "20" - name: SEARCH_INTERVAL_TRADING value: "600" - name: CLEANUP_INTERVAL value: "1800" - name: NODE_CONNECTION_TIMEOUT value: "30" - name: LOG_LEVEL value: "INFO" - name: CLEAR_LOG_DATA_ON_LAUNCH value: "true" - name: CLEAR_KEY_DATA_ON_LAUNCH value: "true" - name: USE_CLIENT value: "false" volumeMounts: - name: tac-deploy-data-vol mountPath: /data volumes: - name: tac-deploy-data-vol persistentVolumeClaim: claimName: tac-deploy-data-vol --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: tac-deploy-data-vol spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi ================================================ FILE: examples/tac_deploy/tac_run.sh ================================================ #!/bin/bash -e minutes_to_start=$1 participants=$2 mode=$3 registry=$4 acn_node=$5 identifier=$(python -c "import uuid; print(uuid.uuid4().hex)") tac_name=tac_$identifier PEER="" agents="" registry_flag="--local" help() { echo " Use tac_run.sh [mode:noncontract,fetchai,ethereum] [registry:local,remote] [acn_node:local,public] minutes_to_start - int number_of_participants - int, min 2 mode - which mode to run Tac in: noncontract, fetchai, ethereum. noncontract is the default registry - local or remote, local is the default acn_node - local or public, local is the default " exit 1; } function empty_lines { echo " " } print_options(){ start_time=$(date) echo "Starting Tac instance: Start time: ${start_time} * minutes_to_start - ${minutes_to_start} * number_of_participants - ${participants} * mode - ${mode} * registry - ${registry} * acn_node - ${acn_node} * tac_name(autogenerated) - ${tac_name} " } check_options(){ if [ -z "$minutes_to_start" ]; then minutes_to_start=1 fi if [ -z "$participants" ]; then participants=2 fi case "$registry" in local) registry_flag="--local" ;; remote) registry_flag="--remote" ;; "") registry=local registry_flag="--local" ;; *) echo "Incorrect registry!" help ;; esac case "$acn_node" in local) PEER="" ;; public) PEER="/dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW" ;; "") acn_node=local PEER="" ;; *) echo "Incorrect acn node option!" help ;; esac case "$mode" in fetchai|ethereum) key_type=$mode tac_controller_name="tac_controller_contract" tac_controller_skill_name="tac_control_contract" tac_participant_name="tac_participant_contract" ;; ""|noncontract) key_type=fetchai mode="noncontract" tac_controller_name="tac_controller" tac_controller_skill_name="tac_control" tac_participant_name="tac_participant" ;; *) echo "Incorrect mode option!" help ;; esac } generate_keys(){ echo "Generating ${key_type} keys..." aea generate-key $key_type aea add-key $key_type ${key_type}_private_key.txt aea config set agent.default_ledger ${key_type} aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection echo "Keys are generated." } set_p2p_connection(){ peer=$1 port=$2 public_url=$3 if [ -z "$peer" ]; then # no peer provided peer= else peer="\"$peer\"" fi if [ -z "$public_url" ]; then # no peer provided public_url=null else public_url="\"$public_url\"" fi json=$(printf '{"delegate_uri": null, "entry_peers": [%s], "local_uri": "127.0.0.1:1%0.4d", "public_uri": %s}' "$peer" "$port" "$public_url") aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config "$json" aea config get vendor.fetchai.connections.p2p_libp2p.config json=$(printf '[{"identifier": "acn", "ledger_id": "%s", "not_after": "2030-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt"}]' "$key_type") aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests "$json" } set_tac_name(){ echo "Set tac name" skill_name=$1 json=$(printf '{"key": "tac", "value": "%s"}' $tac_name) aea config set --type dict vendor.fetchai.skills.$skill_name.models.parameters.args.service_data "$json" aea config get vendor.fetchai.skills.$skill_name.models.parameters.args.service_data } set_aea(){ aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea install aea build aea issue-certificates } set_PEER(){ case "$acn_node" in public) # do nothing ;; local) PEER=`aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri` ;; esac } create_controller(){ echo "Creating controller..." rm -rf $tac_controller_name aea fetch $registry_flag fetchai/$tac_controller_name:latest cd $tac_controller_name empty_lines generate_keys empty_lines set_p2p_connection "$PEER" "0" "127.0.0.1:10000" empty_lines set_tac_name "$tac_controller_skill_name" set_aea set_PEER cd .. } create_participants(){ for i in $(seq $participants); do create_participant $i done } create_participant(){ i=$1 agent=tac_participant_$i agents=$(echo $agent $agents) rm -rf $agent aea fetch $registry_flag fetchai/${tac_participant_name}:latest --alias $agent cd $agent empty_lines generate_keys empty_lines set_p2p_connection "$PEER" "$i" aea config set vendor.fetchai.skills.tac_participation.models.game.args.search_query.search_value $tac_name aea config get vendor.fetchai.skills.tac_participation.models.game.args.search_query set_aea cd .. } set_tac_time(){ empty_lines time_diff=$(printf '+%sM' "$minutes_to_start") datetime_now=$(date "+%d %m %Y %H:%M") datetime_start=$([ "$(uname)" = Linux ] && date --date="$minutes_to_start minutes" "+%d %m %Y %H:%M" ||date -v $time_diff "+%d %m %Y %H:%M") echo "Now:" $datetime_now "Start:" $datetime_start cd $tac_controller_name aea config set vendor.fetchai.skills.${tac_controller_skill_name}.models.parameters.args.registration_start_time "$datetime_start" echo "Start time set:" $(aea config get vendor.fetchai.skills.${tac_controller_skill_name}.models.parameters.args.registration_start_time) cd .. } create_agents(){ create_controller empty_lines create_participants empty_lines set_tac_time echo "agents created" } run_agents(){ empty_lines echo "Use \"aea launch $tac_controller_name $agents\" to run agents" } check_options print_options create_agents run_agents ================================================ FILE: firebase.json ================================================ { "hosting": { "public": "site", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ] } } ================================================ FILE: install_packages.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The install packages script""" import re import subprocess import sys from pathlib import Path from typing import List from tomlkit import loads as loads_toml def _load_groups(): data = loads_toml(Path("pyproject.toml").read_text()) return list(data["tool"]["poetry"]["group"].keys()) def _load_dependencies() -> List[str]: groups = ",".join(_load_groups()) text = subprocess.check_output( f"poetry export --with {groups}", shell=True, text=True ) text = text.replace("\\\n", " ") lines = text.splitlines() lines = [i.split(" ")[0] for i in lines] return lines RE = re.compile("(.*)[=><]") if __name__ == "__main__": packages = sys.argv[1:] requirements = _load_dependencies() to_install = [] for package in packages: for requirement in requirements: if re.match(f"^{package}([<>=].*)?$", requirement): to_install.append(requirement.strip()) if not to_install: raise ValueError("No packages found to install") print("installing", ", ".join(to_install)) subprocess.check_call([sys.executable, "-m", "pip", "install", *to_install]) ================================================ FILE: libs/go/aea_end2end/Makefile ================================================ build: main.go go build ================================================ FILE: libs/go/aea_end2end/README.md ================================================ # End-to-end example Go <> Python ## Run example Ensure all dependencies are installed: python, aea and Golang. To launch the buyer agent run: `./run_buyer.sh` In another terminal, to launch the seller agent run: `./run_seller.sh` After a while both agents get connected to the ACN, perform a `fetchai/fipa` message exchange and show `FIPA INTERACTION COMPLETE` in output logs. Terminate every agent with `ctrl+c`. ## Generate protocol To generate a protocol, use the following approach: ``` bash aea create temp_agent cd temp_agent aea generate protocol -l PATH_TO_SPEC ``` ================================================ FILE: libs/go/aea_end2end/fipa_dummy_seller.env ================================================ export AEA_LEDGER_ID="fetchai" export AEA_ADDRESS="fetch1x9v67meyfq4pkgy2n2yf6797cfkul327kpclqr" export AEA_PUBLIC_KEY="02ac514ba70de60ed5c30f90e3acdfc958ecb416d9676706bf013228abfb2c2816" export AEA_PRIVATE_KEY="6d8d2b87d987641e2ca3f1991c1cccf08a118759e81fabdbf7e8484f27af015e" export AEA_P2P_POR_SERVICE_ID="acn" export AEA_P2P_POR_LEDGER_ID="fetchai" export AEA_P2P_POR_PEER_PUBKEY="0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7" export AEA_P2P_POR_SIGNATURE="avzHfL/fjMidvweJJKjtBUiqJ2+6aDUq8MoNRBi9nDI/lWleIX3ftRf6Sx5UWmxcS0SW03IVrf1iKTXA5zeA0g==" export AEA_P2P_DELEGATE_HOST="acn.fetch.ai" export AEA_P2P_DELEGATE_PORT=11000 ================================================ FILE: libs/go/aea_end2end/go.mod ================================================ module seller_agent go 1.14 replace aealite => ../aealite require ( aealite v0.0.0-00010101000000-000000000000 github.com/golang/protobuf v1.4.2 github.com/sirupsen/logrus v1.8.1 google.golang.org/protobuf v1.25.0 ) ================================================ FILE: libs/go/aea_end2end/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.9.25 h1:mMiw/zOOtCLdGLWfcekua0qPrJTe7FVIiHJ4IKNTfR0= github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.18/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= github.com/ipfs/go-ipns v0.0.2/go.mod h1:WChil4e0/m9cIINWLxZe1Jtf77oz5L05rO2ei/uKJ5U= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.12.0/go.mod h1:FpHZrfC1q7nA8jitvdjKBDF31hguaC676g/nT9PgQM0= github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= github.com/libp2p/go-libp2p-autonat v0.4.0/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA= github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.6.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.5 h1:aEgbIcPGsKy6zYcC+5AJivYFedhYa4sW7mIpWpUaLKw= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.11.1/go.mod h1:5ojtR2acDPqh/jXf5orWy8YGb8bHQDS+qeDcoscL/PI= github.com/libp2p/go-libp2p-kbucket v0.4.7/go.mod h1:XyVo99AfQH0foSf176k4jY1xUJ2+jUJIZCSDm7r2YKk= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-mplex v0.3.0/go.mod h1:l9QWxRbbb5/hQMECEb908GbS9Sm2UAR2KFZKUJEynEs= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4= github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.3.1/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.3.1/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.0/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= ================================================ FILE: libs/go/aea_end2end/main.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package main import ( aea "aealite" connections "aealite/connections" protocols "aealite/protocols" "log" "os" "os/signal" local_protocols "seller_agent/protocols" proto "google.golang.org/protobuf/proto" ) func getRole(protocols.ProtocolMessageInterface, protocols.Address) protocols.Role { return protocols.Role("some") } func makeSellerDialogues(address string) *protocols.Dialogues { initialPerformatives := []protocols.Performative{"cfp"} terminalPerformatives := []protocols.Performative{"decline", "end"} validReplies := map[protocols.Performative][]protocols.Performative{ "cfp": []protocols.Performative{"propose", "decline"}, "propose": []protocols.Performative{ "accept", "accept_w_inform", "decline", "propose", }, "accept": []protocols.Performative{ "decline", "match_accept", "match_accept_w_inform", }, "accept_w_inform": []protocols.Performative{ "decline", "match_accept", "match_accept_w_inform", }, "decline": []protocols.Performative{}, "match_accept": []protocols.Performative{"inform", "end"}, "match_accept_w_inform": []protocols.Performative{"inform", "end"}, "inform": []protocols.Performative{"inform", "end"}, "end": []protocols.Performative{}, } dialogues := protocols.NewDialogues( protocols.Address(address), getRole, false, "my dialogues", initialPerformatives, terminalPerformatives, validReplies, ) return dialogues } func handleEnvelope( envelope *protocols.Envelope, dialogues *protocols.Dialogues, agent *aea.Agent, ) { fipaProtocolID := "fetchai/fipa:1.0.0" if envelope.GetProtocolId() != fipaProtocolID { log.Printf("Not Fipa message!.") return } fipaMessage := &local_protocols.FipaMessage{} dialogueMessageWrapped, err := protocols.GetDialogueMessageWrappedAndSetContentFromEnvelope( envelope, fipaMessage, ) if err != nil { log.Printf("Failed to get dialogue message message: %s", err) return } dialogue, err := dialogues.Update(dialogueMessageWrapped) if err != nil { log.Printf("Failed to update dialogue: %s", err) return } switch dialogueMessageWrapped.Performative() { case "cfp": { msg, err := dialogue.Reply("propose", dialogueMessageWrapped, nil) if err != nil { log.Printf("Failed to reply dialogue: %s", err) return } q := &protocols.Query_Instance{ Model: &protocols.Query_DataModel{ Name: "string", Attributes: []*protocols.Query_Attribute{}, Description: "desc", }, Values: []*protocols.Query_KeyValue{}, } out, err := proto.Marshal(q) if err != nil { log.Printf("Failed to encode q: %s", err) return } proposeMsg := &local_protocols.FipaMessage{ Performative: &local_protocols.FipaMessage_Propose{ Propose: &local_protocols.FipaMessage_Propose_Performative{ Proposal: &local_protocols.FipaMessage_Description{DescriptionBytes: out}, }, }, } content, err := proto.Marshal(proposeMsg) if err != nil { log.Printf("Failed to encode content: %s", err) return } replyEnvelope, err := protocols.MakeResponseEnvelope(msg, fipaProtocolID, content) if err != nil { log.Printf("Failed to make envelope %s", err) return } err = agent.Put(replyEnvelope) if err != nil { log.Printf("Error on send reply: %s", err) return } } case "accept": { msg, err := dialogue.Reply("match_accept", dialogueMessageWrapped, nil) if err != nil { log.Printf("Failed to reply dialogue: %s", err) return } matchMsg := &local_protocols.FipaMessage{ Performative: &local_protocols.FipaMessage_MatchAccept{}, } content, err := proto.Marshal(matchMsg) if err != nil { log.Printf("Failed to encode content: %s", err) return } replyEnvelope, err := protocols.MakeResponseEnvelope(msg, fipaProtocolID, content) if err != nil { log.Printf("Failed to make envelope %s", err) return } err = agent.Put(replyEnvelope) if err != nil { log.Printf("Error on send reply: %s", err) return } } case "end": { log.Print("It's done") log.Print("FIPA INTERACTION COMPLETE") } default: { log.Printf("Unsupported performative: %s", dialogueMessageWrapped.Performative()) return } } log.Print("envelope handled successfully") } func getEnvelopeAndProcess( dialogues *protocols.Dialogues, agent *aea.Agent) { for { envelope := agent.Get() log.Printf("got incoming envelope: %s", envelope.String()) handleEnvelope(envelope, dialogues, agent) } } func main() { var err error // env file if len(os.Args) != 2 { log.Print("Usage: main ENV_FILE") os.Exit(1) } envFile := os.Args[1] log.Print("Agent starting ...") // Create agent agent := aea.Agent{} // Set connection agent.Connection = &connections.P2PClientApi{} // Initialise agent from environment file (first arg to process) err = agent.InitFromEnv(envFile) if err != nil { log.Fatal("Failed to initialise agent", err) } log.Print("successfully initialized AEA!") err = agent.Start() if err != nil { log.Fatal("Failed to start agent", err) } log.Print("successfully started AEA!") log.Print("My Address is ", agent.Address()) dialogues := makeSellerDialogues(agent.Address()) go getEnvelopeAndProcess(dialogues, &agent) // Wait until Ctrl+C or a termination call is done. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c err = agent.Stop() if err != nil { log.Fatal("Failed to stop agent", err) } log.Print("Agent stopped") } ================================================ FILE: libs/go/aea_end2end/pexpect_popen.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Wrapper for Pexpect to use in tests.""" import os import platform import signal import sys import time from typing import List, Optional, Union from pexpect.exceptions import EOF, TIMEOUT # type: ignore from pexpect.popen_spawn import PopenSpawn # type: ignore from aea.helpers.base import send_control_c class PexpectWrapper(PopenSpawn): """Utility class to make aea cli test easier.""" def __init__(self, *args, **kwargs): """Init pexpect.spawn.""" if platform.system() != "Windows": kwargs["preexec_fn"] = os.setsid super().__init__(*args, **kwargs) def control_c(self) -> None: """Send control c to process started.""" time.sleep(0.1) # sometimes it's better to wait a bit send_control_c(self.proc, True) @property def returncode(self) -> Optional[Union[int, str]]: """Get return code of finished process.""" return self.proc.poll() # type: ignore def wait_to_complete(self, timeout: float = 5) -> None: """Wait process to complete. Terminate automatically after timeout. Set returncode to terminated if terminated. :param timeout: how many seconds wait process to finish before kill. """ if self.proc.poll() is not None: # type: ignore return start_time = time.time() while start_time + timeout > time.time() and self.proc.poll() is None: # type: ignore time.sleep(0.001) if self.proc.poll() is None: # type: ignore self.terminate(force=True) self.wait() self.exitstatus = "Terminated!" # type: ignore def expect_all( self, pattern_list: List[str], timeout: float = 10, strict: bool = True ) -> None: """ Wait for all patterns appear in process output. :param pattern_list: list of string to expect :param timeout: timeout in seconds :param strict: if non strict, it allows regular expression """ pattern_list = list(pattern_list) start_time = time.time() while pattern_list: time_spent = time.time() - start_time if time_spent > timeout: raise TIMEOUT(timeout) if strict: idx = self.expect_exact(pattern_list, timeout - time_spent) else: idx = self.expect(pattern_list, timeout - time_spent) pattern_list.pop(idx) def wait_eof(self, timeout: float = 10) -> None: """ Wait for EOF of the process. :param timeout: timeout in seconds """ self.expect(EOF, timeout=timeout) def terminate(self, *args, **kwargs) -> None: """Terminate process.""" if self.proc.poll() is None: self.kill(signal.SIGKILL) ================================================ FILE: libs/go/aea_end2end/protocols/fipa.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.11.4 // source: fipa.proto package protocols import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type FipaMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Performative: // *FipaMessage_Accept // *FipaMessage_AcceptWInform // *FipaMessage_Cfp // *FipaMessage_Decline // *FipaMessage_End // *FipaMessage_Inform // *FipaMessage_MatchAccept // *FipaMessage_MatchAcceptWInform // *FipaMessage_Propose Performative isFipaMessage_Performative `protobuf_oneof:"performative"` } func (x *FipaMessage) Reset() { *x = FipaMessage{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage) ProtoMessage() {} func (x *FipaMessage) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage.ProtoReflect.Descriptor instead. func (*FipaMessage) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0} } func (m *FipaMessage) GetPerformative() isFipaMessage_Performative { if m != nil { return m.Performative } return nil } func (x *FipaMessage) GetAccept() *FipaMessage_Accept_Performative { if x, ok := x.GetPerformative().(*FipaMessage_Accept); ok { return x.Accept } return nil } func (x *FipaMessage) GetAcceptWInform() *FipaMessage_Accept_W_Inform_Performative { if x, ok := x.GetPerformative().(*FipaMessage_AcceptWInform); ok { return x.AcceptWInform } return nil } func (x *FipaMessage) GetCfp() *FipaMessage_Cfp_Performative { if x, ok := x.GetPerformative().(*FipaMessage_Cfp); ok { return x.Cfp } return nil } func (x *FipaMessage) GetDecline() *FipaMessage_Decline_Performative { if x, ok := x.GetPerformative().(*FipaMessage_Decline); ok { return x.Decline } return nil } func (x *FipaMessage) GetEnd() *FipaMessage_End_Performative { if x, ok := x.GetPerformative().(*FipaMessage_End); ok { return x.End } return nil } func (x *FipaMessage) GetInform() *FipaMessage_Inform_Performative { if x, ok := x.GetPerformative().(*FipaMessage_Inform); ok { return x.Inform } return nil } func (x *FipaMessage) GetMatchAccept() *FipaMessage_Match_Accept_Performative { if x, ok := x.GetPerformative().(*FipaMessage_MatchAccept); ok { return x.MatchAccept } return nil } func (x *FipaMessage) GetMatchAcceptWInform() *FipaMessage_Match_Accept_W_Inform_Performative { if x, ok := x.GetPerformative().(*FipaMessage_MatchAcceptWInform); ok { return x.MatchAcceptWInform } return nil } func (x *FipaMessage) GetPropose() *FipaMessage_Propose_Performative { if x, ok := x.GetPerformative().(*FipaMessage_Propose); ok { return x.Propose } return nil } type isFipaMessage_Performative interface { isFipaMessage_Performative() } type FipaMessage_Accept struct { Accept *FipaMessage_Accept_Performative `protobuf:"bytes,5,opt,name=accept,proto3,oneof"` } type FipaMessage_AcceptWInform struct { AcceptWInform *FipaMessage_Accept_W_Inform_Performative `protobuf:"bytes,6,opt,name=accept_w_inform,json=acceptWInform,proto3,oneof"` } type FipaMessage_Cfp struct { Cfp *FipaMessage_Cfp_Performative `protobuf:"bytes,7,opt,name=cfp,proto3,oneof"` } type FipaMessage_Decline struct { Decline *FipaMessage_Decline_Performative `protobuf:"bytes,8,opt,name=decline,proto3,oneof"` } type FipaMessage_End struct { End *FipaMessage_End_Performative `protobuf:"bytes,9,opt,name=end,proto3,oneof"` } type FipaMessage_Inform struct { Inform *FipaMessage_Inform_Performative `protobuf:"bytes,10,opt,name=inform,proto3,oneof"` } type FipaMessage_MatchAccept struct { MatchAccept *FipaMessage_Match_Accept_Performative `protobuf:"bytes,11,opt,name=match_accept,json=matchAccept,proto3,oneof"` } type FipaMessage_MatchAcceptWInform struct { MatchAcceptWInform *FipaMessage_Match_Accept_W_Inform_Performative `protobuf:"bytes,12,opt,name=match_accept_w_inform,json=matchAcceptWInform,proto3,oneof"` } type FipaMessage_Propose struct { Propose *FipaMessage_Propose_Performative `protobuf:"bytes,13,opt,name=propose,proto3,oneof"` } func (*FipaMessage_Accept) isFipaMessage_Performative() {} func (*FipaMessage_AcceptWInform) isFipaMessage_Performative() {} func (*FipaMessage_Cfp) isFipaMessage_Performative() {} func (*FipaMessage_Decline) isFipaMessage_Performative() {} func (*FipaMessage_End) isFipaMessage_Performative() {} func (*FipaMessage_Inform) isFipaMessage_Performative() {} func (*FipaMessage_MatchAccept) isFipaMessage_Performative() {} func (*FipaMessage_MatchAcceptWInform) isFipaMessage_Performative() {} func (*FipaMessage_Propose) isFipaMessage_Performative() {} // Custom Types type FipaMessage_Description struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields DescriptionBytes []byte `protobuf:"bytes,1,opt,name=description_bytes,json=descriptionBytes,proto3" json:"description_bytes,omitempty"` } func (x *FipaMessage_Description) Reset() { *x = FipaMessage_Description{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Description) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Description) ProtoMessage() {} func (x *FipaMessage_Description) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Description.ProtoReflect.Descriptor instead. func (*FipaMessage_Description) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 0} } func (x *FipaMessage_Description) GetDescriptionBytes() []byte { if x != nil { return x.DescriptionBytes } return nil } type FipaMessage_Query struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields QueryBytes []byte `protobuf:"bytes,1,opt,name=query_bytes,json=queryBytes,proto3" json:"query_bytes,omitempty"` } func (x *FipaMessage_Query) Reset() { *x = FipaMessage_Query{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Query) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Query) ProtoMessage() {} func (x *FipaMessage_Query) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Query.ProtoReflect.Descriptor instead. func (*FipaMessage_Query) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 1} } func (x *FipaMessage_Query) GetQueryBytes() []byte { if x != nil { return x.QueryBytes } return nil } // Performatives and contents type FipaMessage_Cfp_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Query *FipaMessage_Query `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` } func (x *FipaMessage_Cfp_Performative) Reset() { *x = FipaMessage_Cfp_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Cfp_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Cfp_Performative) ProtoMessage() {} func (x *FipaMessage_Cfp_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Cfp_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Cfp_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 2} } func (x *FipaMessage_Cfp_Performative) GetQuery() *FipaMessage_Query { if x != nil { return x.Query } return nil } type FipaMessage_Propose_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Proposal *FipaMessage_Description `protobuf:"bytes,1,opt,name=proposal,proto3" json:"proposal,omitempty"` } func (x *FipaMessage_Propose_Performative) Reset() { *x = FipaMessage_Propose_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Propose_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Propose_Performative) ProtoMessage() {} func (x *FipaMessage_Propose_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Propose_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Propose_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 3} } func (x *FipaMessage_Propose_Performative) GetProposal() *FipaMessage_Description { if x != nil { return x.Proposal } return nil } type FipaMessage_Accept_W_Inform_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Info map[string]string `protobuf:"bytes,1,rep,name=info,proto3" json:"info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *FipaMessage_Accept_W_Inform_Performative) Reset() { *x = FipaMessage_Accept_W_Inform_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Accept_W_Inform_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Accept_W_Inform_Performative) ProtoMessage() {} func (x *FipaMessage_Accept_W_Inform_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Accept_W_Inform_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Accept_W_Inform_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 4} } func (x *FipaMessage_Accept_W_Inform_Performative) GetInfo() map[string]string { if x != nil { return x.Info } return nil } type FipaMessage_Match_Accept_W_Inform_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Info map[string]string `protobuf:"bytes,1,rep,name=info,proto3" json:"info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *FipaMessage_Match_Accept_W_Inform_Performative) Reset() { *x = FipaMessage_Match_Accept_W_Inform_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Match_Accept_W_Inform_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Match_Accept_W_Inform_Performative) ProtoMessage() {} func (x *FipaMessage_Match_Accept_W_Inform_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Match_Accept_W_Inform_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Match_Accept_W_Inform_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 5} } func (x *FipaMessage_Match_Accept_W_Inform_Performative) GetInfo() map[string]string { if x != nil { return x.Info } return nil } type FipaMessage_Inform_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Info map[string]string `protobuf:"bytes,1,rep,name=info,proto3" json:"info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *FipaMessage_Inform_Performative) Reset() { *x = FipaMessage_Inform_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Inform_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Inform_Performative) ProtoMessage() {} func (x *FipaMessage_Inform_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Inform_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Inform_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 6} } func (x *FipaMessage_Inform_Performative) GetInfo() map[string]string { if x != nil { return x.Info } return nil } type FipaMessage_Accept_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *FipaMessage_Accept_Performative) Reset() { *x = FipaMessage_Accept_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Accept_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Accept_Performative) ProtoMessage() {} func (x *FipaMessage_Accept_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Accept_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Accept_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 7} } type FipaMessage_Decline_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *FipaMessage_Decline_Performative) Reset() { *x = FipaMessage_Decline_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Decline_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Decline_Performative) ProtoMessage() {} func (x *FipaMessage_Decline_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Decline_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Decline_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 8} } type FipaMessage_Match_Accept_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *FipaMessage_Match_Accept_Performative) Reset() { *x = FipaMessage_Match_Accept_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_Match_Accept_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_Match_Accept_Performative) ProtoMessage() {} func (x *FipaMessage_Match_Accept_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_Match_Accept_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_Match_Accept_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 9} } type FipaMessage_End_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *FipaMessage_End_Performative) Reset() { *x = FipaMessage_End_Performative{} if protoimpl.UnsafeEnabled { mi := &file_fipa_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *FipaMessage_End_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*FipaMessage_End_Performative) ProtoMessage() {} func (x *FipaMessage_End_Performative) ProtoReflect() protoreflect.Message { mi := &file_fipa_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use FipaMessage_End_Performative.ProtoReflect.Descriptor instead. func (*FipaMessage_End_Performative) Descriptor() ([]byte, []int) { return file_fipa_proto_rawDescGZIP(), []int{0, 10} } var File_fipa_proto protoreflect.FileDescriptor var file_fipa_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x22, 0x86, 0x0e, 0x0a, 0x0b, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x52, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x6b, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x77, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x57, 0x5f, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x57, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x49, 0x0a, 0x03, 0x63, 0x66, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x43, 0x66, 0x70, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x03, 0x63, 0x66, 0x70, 0x12, 0x55, 0x0a, 0x07, 0x64, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x07, 0x64, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x49, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x64, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x52, 0x0a, 0x06, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x06, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x63, 0x0a, 0x0c, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x7c, 0x0a, 0x15, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x77, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x47, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x57, 0x5f, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x57, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x55, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x1a, 0x3a, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x1a, 0x28, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x10, 0x43, 0x66, 0x70, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x40, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x64, 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x4c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x1a, 0xb8, 0x01, 0x0a, 0x1c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x57, 0x5f, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x5f, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x57, 0x5f, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x1a, 0x37, 0x0a, 0x09, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0xc4, 0x01, 0x0a, 0x22, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x57, 0x5f, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x65, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x51, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x57, 0x5f, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x1a, 0x37, 0x0a, 0x09, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0xa6, 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x56, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x66, 0x65, 0x74, 0x63, 0x68, 0x61, 0x69, 0x2e, 0x66, 0x69, 0x70, 0x61, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x46, 0x69, 0x70, 0x61, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x1a, 0x37, 0x0a, 0x09, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x15, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x1a, 0x16, 0x0a, 0x14, 0x44, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x1a, 0x1b, 0x0a, 0x19, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x1a, 0x12, 0x0a, 0x10, 0x45, 0x6e, 0x64, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_fipa_proto_rawDescOnce sync.Once file_fipa_proto_rawDescData = file_fipa_proto_rawDesc ) func file_fipa_proto_rawDescGZIP() []byte { file_fipa_proto_rawDescOnce.Do(func() { file_fipa_proto_rawDescData = protoimpl.X.CompressGZIP(file_fipa_proto_rawDescData) }) return file_fipa_proto_rawDescData } var file_fipa_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_fipa_proto_goTypes = []interface{}{ (*FipaMessage)(nil), // 0: aea.fetchai.fipa.v1_0_0.FipaMessage (*FipaMessage_Description)(nil), // 1: aea.fetchai.fipa.v1_0_0.FipaMessage.Description (*FipaMessage_Query)(nil), // 2: aea.fetchai.fipa.v1_0_0.FipaMessage.Query (*FipaMessage_Cfp_Performative)(nil), // 3: aea.fetchai.fipa.v1_0_0.FipaMessage.Cfp_Performative (*FipaMessage_Propose_Performative)(nil), // 4: aea.fetchai.fipa.v1_0_0.FipaMessage.Propose_Performative (*FipaMessage_Accept_W_Inform_Performative)(nil), // 5: aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative (*FipaMessage_Match_Accept_W_Inform_Performative)(nil), // 6: aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative (*FipaMessage_Inform_Performative)(nil), // 7: aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative (*FipaMessage_Accept_Performative)(nil), // 8: aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_Performative (*FipaMessage_Decline_Performative)(nil), // 9: aea.fetchai.fipa.v1_0_0.FipaMessage.Decline_Performative (*FipaMessage_Match_Accept_Performative)(nil), // 10: aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_Performative (*FipaMessage_End_Performative)(nil), // 11: aea.fetchai.fipa.v1_0_0.FipaMessage.End_Performative nil, // 12: aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative.InfoEntry nil, // 13: aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative.InfoEntry nil, // 14: aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative.InfoEntry } var file_fipa_proto_depIdxs = []int32{ 8, // 0: aea.fetchai.fipa.v1_0_0.FipaMessage.accept:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_Performative 5, // 1: aea.fetchai.fipa.v1_0_0.FipaMessage.accept_w_inform:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative 3, // 2: aea.fetchai.fipa.v1_0_0.FipaMessage.cfp:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Cfp_Performative 9, // 3: aea.fetchai.fipa.v1_0_0.FipaMessage.decline:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Decline_Performative 11, // 4: aea.fetchai.fipa.v1_0_0.FipaMessage.end:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.End_Performative 7, // 5: aea.fetchai.fipa.v1_0_0.FipaMessage.inform:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative 10, // 6: aea.fetchai.fipa.v1_0_0.FipaMessage.match_accept:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_Performative 6, // 7: aea.fetchai.fipa.v1_0_0.FipaMessage.match_accept_w_inform:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative 4, // 8: aea.fetchai.fipa.v1_0_0.FipaMessage.propose:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Propose_Performative 2, // 9: aea.fetchai.fipa.v1_0_0.FipaMessage.Cfp_Performative.query:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Query 1, // 10: aea.fetchai.fipa.v1_0_0.FipaMessage.Propose_Performative.proposal:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Description 12, // 11: aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative.info:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative.InfoEntry 13, // 12: aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative.info:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative.InfoEntry 14, // 13: aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative.info:type_name -> aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative.InfoEntry 14, // [14:14] is the sub-list for method output_type 14, // [14:14] is the sub-list for method input_type 14, // [14:14] is the sub-list for extension type_name 14, // [14:14] is the sub-list for extension extendee 0, // [0:14] is the sub-list for field type_name } func init() { file_fipa_proto_init() } func file_fipa_proto_init() { if File_fipa_proto != nil { return } if !protoimpl.UnsafeEnabled { file_fipa_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Description); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Query); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Cfp_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Propose_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Accept_W_Inform_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Match_Accept_W_Inform_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Inform_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Accept_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Decline_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_Match_Accept_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_fipa_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FipaMessage_End_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_fipa_proto_msgTypes[0].OneofWrappers = []interface{}{ (*FipaMessage_Accept)(nil), (*FipaMessage_AcceptWInform)(nil), (*FipaMessage_Cfp)(nil), (*FipaMessage_Decline)(nil), (*FipaMessage_End)(nil), (*FipaMessage_Inform)(nil), (*FipaMessage_MatchAccept)(nil), (*FipaMessage_MatchAcceptWInform)(nil), (*FipaMessage_Propose)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_fipa_proto_rawDesc, NumEnums: 0, NumMessages: 15, NumExtensions: 0, NumServices: 0, }, GoTypes: file_fipa_proto_goTypes, DependencyIndexes: file_fipa_proto_depIdxs, MessageInfos: file_fipa_proto_msgTypes, }.Build() File_fipa_proto = out.File file_fipa_proto_rawDesc = nil file_fipa_proto_goTypes = nil file_fipa_proto_depIdxs = nil } ================================================ FILE: libs/go/aea_end2end/protocols/fipa.proto ================================================ syntax = "proto3"; package aea.fetchai.fipa.v1_0_0; option go_package = "seller_agent/protocols"; message FipaMessage{ // Custom Types message Description{ bytes description_bytes = 1; } message Query{ bytes query_bytes = 1; } // Performatives and contents message Cfp_Performative{ Query query = 1; } message Propose_Performative{ Description proposal = 1; } message Accept_W_Inform_Performative{ map info = 1; } message Match_Accept_W_Inform_Performative{ map info = 1; } message Inform_Performative{ map info = 1; } message Accept_Performative{ } message Decline_Performative{ } message Match_Accept_Performative{ } message End_Performative{ } oneof performative{ Accept_Performative accept = 5; Accept_W_Inform_Performative accept_w_inform = 6; Cfp_Performative cfp = 7; Decline_Performative decline = 8; End_Performative end = 9; Inform_Performative inform = 10; Match_Accept_Performative match_accept = 11; Match_Accept_W_Inform_Performative match_accept_w_inform = 12; Propose_Performative propose = 13; } } ================================================ FILE: libs/go/aea_end2end/protocols/fipa.yaml ================================================ --- name: fipa author: fetchai version: 1.0.0 description: A protocol for FIPA ACL. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/fipa:1.0.0 speech_acts: cfp: query: ct:Query propose: proposal: ct:Description accept_w_inform: info: pt:dict[pt:str, pt:str] match_accept_w_inform: info: pt:dict[pt:str, pt:str] inform: info: pt:dict[pt:str, pt:str] accept: {} decline: {} match_accept: {} end: {} ... --- ct:Query: | bytes query_bytes = 1; ct:Description: | bytes description_bytes = 1; ... --- initiation: [cfp] reply: cfp: [propose, decline] propose: [accept, accept_w_inform, decline, propose] accept: [decline, match_accept, match_accept_w_inform] accept_w_inform: [decline, match_accept, match_accept_w_inform] decline: [] match_accept: [inform, end] match_accept_w_inform: [inform, end] inform: [inform, end] end: [] termination: [decline, end] roles: {seller, buyer} end_states: [successful, declined_cfp, declined_propose, declined_accept] keep_terminal_state_dialogues: true ... ================================================ FILE: libs/go/aea_end2end/run_buyer.sh ================================================ #!/bin/bash source fipa_dummy_seller.env SELLER_ADDR=${AEA_ADDRESS} aea --registry-path ../../../packages fetch fetchai/fipa_dummy_buyer --local cd fipa_dummy_buyer aea build aea config set vendor.fetchai.skills.fipa_dummy_buyer.behaviours.initializer.args.opponent_address $SELLER_ADDR aea generate-key fetchai aea add-key fetchai aea add-key fetchai --connection aea issue-certificates aea -v DEBUG run cd ../ rm -fr ./fipa_dummy_buyer ================================================ FILE: libs/go/aea_end2end/run_seller.sh ================================================ #!/bin/bash go build ./seller_agent fipa_dummy_seller.env ================================================ FILE: libs/go/aea_end2end/test_fipa_end2end.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the end to end test for dialogues on python and golang.""" import asyncio import os import sys from pathlib import Path from tempfile import TemporaryDirectory from threading import Thread from typing import Any, Optional, cast import pytest from aea_ledger_fetchai import FetchAICrypto from pexpect.exceptions import EOF from aea.aea_builder import AEABuilder from aea.common import Address from aea.configurations.base import SkillConfig from aea.helpers.base import cd from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Handler, Skill, SkillContext from aea.skills.behaviours import TickerBehaviour from aea.test_tools.test_cases import AEATestCaseEmpty from libs.go.aea_end2end.pexpect_popen import PexpectWrapper from packages.fetchai.connections.p2p_libp2p.connection import P2PLibp2pConnection from packages.fetchai.protocols.fipa.dialogues import FipaDialogue, FipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from tests.common.utils import run_in_thread, wait_for_condition from tests.conftest import _make_libp2p_connection class BuyerDialogues(FipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :param self_address: address of the dialogues maintainer """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.BUYER FipaDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class BuyerBehaviour(TickerBehaviour): """Test buyer behaviour.""" addr: str def setup(self) -> None: """Set up behaviour.""" self.is_started = False self.was_called = False def act(self) -> None: """Make an action.""" dialogues = cast(FipaDialogues, self.context.dialogues) if not self.is_started or self.was_called: return cfp_msg, _ = dialogues.create( counterparty=self.addr, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) self.context.outbox.put_message(cfp_msg) self.was_called = True print("MESSAGE SENT") def start(self, addr: Address) -> None: """Set counterpart address and start sending CFP.""" self.addr = addr self.is_started = True class BuyerHandler(Handler): """Test BuyerHandler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id got_proposal: bool def setup(self) -> None: """Set up behaviour.""" self.done = False def teardown(self) -> None: """Tear down handler.""" def handle(self, message) -> None: """Handle an evelope.""" dialogues = cast(FipaDialogues, self.context.dialogues) if message.performative == FipaMessage.Performative.PROPOSE: buyer_dialogue = dialogues.update(message) if not buyer_dialogue: print("error on propose message dialogue update") return accept_msg = buyer_dialogue.reply( performative=FipaMessage.Performative.ACCEPT, target_message=message ) self.context.outbox.put_message(message=accept_msg) elif message.performative == FipaMessage.Performative.MATCH_ACCEPT: buyer_dialogue = dialogues.update(message) if not buyer_dialogue: print("error on MATCH_ACCEPT message dialogue update", message) print(dialogues._dialogues_storage.dialogues_in_active_state) return self.done = True end_msg = buyer_dialogue.reply( performative=FipaMessage.Performative.END, target_message=message ) self.context.outbox.put_message(message=end_msg) else: print("unsupported performative", message.performative) class Base(AEATestCaseEmpty): """Base class for test case.""" package_registry_src_rel = Path(os.path.abspath("../../../packages")) @classmethod def setup_class(cls) -> None: """Setup agent.""" super(Base, cls).setup_class() cls.add_item("connection", "fetchai/p2p_libp2p:0.21.0") cls.add_item("protocol", "fetchai/fipa:1.0.0") cls.generate_private_key() cls.add_private_key() cls.add_private_key(connection=True) cls.run_cli_command("build", cwd=cls._get_cwd()) cls.run_cli_command("issue-certificates", cwd=cls._get_cwd()) ROOT = Path(__file__).parent ENV_FILE_NAME = "1.env" exec_filename: Path = ROOT / Path("seller_agent") ENV_TEMPLATE = """ export AEA_LEDGER_ID="fetchai" export AEA_ADDRESS="fetch1x9v67meyfq4pkgy2n2yf6797cfkul327kpclqr" export AEA_PUBLIC_KEY="02ac514ba70de60ed5c30f90e3acdfc958ecb416d9676706bf013228abfb2c2816" export AEA_PRIVATE_KEY="6d8d2b87d987641e2ca3f1991c1cccf08a118759e81fabdbf7e8484f27af015e" export AEA_P2P_POR_SERVICE_ID="acn" export AEA_P2P_POR_LEDGER_ID="fetchai" export AEA_P2P_POR_PEER_PUBKEY="{peer_pubkey}" export AEA_P2P_POR_SIGNATURE="{signature}" export AEA_P2P_DELEGATE_HOST="localhost" export AEA_P2P_DELEGATE_PORT=11234 """ class FipaSellerAgent: """Threaded FIPA Seller agent.""" thread: Thread loop: asyncio.AbstractEventLoop proc: PexpectWrapper connection_node: P2PLibp2pConnection temp_dir: Any @classmethod def start(cls, multi_addr: Address): """Test build example, run, terminate.""" """Run agent.""" cls.loop = asyncio.new_event_loop() cls.temp_dir = TemporaryDirectory() cls.connection_node = _make_libp2p_connection( data_dir=cls.temp_dir.name, delegate=True, entry_peers=[str(multi_addr)] ) cls.loop.run_until_complete(cls.connection_node.node.start()) priv_key_file = Path(cls.temp_dir.name) / "priv_key.txt" priv_key_file.write_text( "6d8d2b87d987641e2ca3f1991c1cccf08a118759e81fabdbf7e8484f27af015e" ) crypto = FetchAICrypto(str(priv_key_file)) signature = crypto.sign_message(cls.connection_node.node.pub.encode()) env_file = Path(cls.temp_dir.name) / "test.env" env_file.write_text( ENV_TEMPLATE.format( peer_pubkey=cls.connection_node.node.pub, signature=signature ) ) if exec_filename.exists(): exec_filename.unlink() proc = PexpectWrapper( # nosec ["go", "build"], cwd=str(ROOT), env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) proc.expect(pattern=EOF, timeout=30) assert proc.returncode == 0 assert exec_filename.exists() cls.proc = PexpectWrapper( # nosec [str(exec_filename), str(env_file)], cwd=str(ROOT), env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) cls.proc.expect("successfully initialized AEA!", timeout=20) cls.proc.expect("successfully started AEA!", timeout=20) return "fetch1x9v67meyfq4pkgy2n2yf6797cfkul327kpclqr" @classmethod def wait_for_envelope(cls): cls.proc.expect("envelope handled successfully", timeout=20) @classmethod def stop(cls): """Stop agent and tear down.""" node_log = Path(cls.temp_dir.name) / "libp2p_node_10234.log" print(node_log.read_text()) cls.loop.run_until_complete(cls.connection_node.node.stop()) cls.proc.terminate() if exec_filename.exists(): exec_filename.unlink() cls.temp_dir.cleanup() class TestFipaEnd2End(Base): """Test that echo skill works.""" def test_run(self): """Run the echo skill sequence.""" result = self.invoke( "get-multiaddress", "fetchai", "-c", "-i", "fetchai/p2p_libp2p:0.21.0", "-u", "public_uri", ) assert result.exit_code == 0 multi_addr = result.stdout.strip() result = self.invoke("get-address", "fetchai",) assert result.exit_code == 0 my_addr = result.stdout.strip() builder = AEABuilder.from_aea_project(self._get_cwd()) skill_context = SkillContext() skill_context.dialogues = BuyerDialogues(my_addr) # type: ignore behaviour = BuyerBehaviour(name="behaviour", skill_context=skill_context) handler = BuyerHandler(name="handler", skill_context=skill_context) test_skill = Skill( SkillConfig(name="test_skill", author="fetchai"), skill_context=skill_context, handlers={"handler": handler}, behaviours={"behaviour": behaviour}, ) builder.add_component_instance(test_skill) with cd(self._get_cwd()): agent = builder.build() skill_context.set_agent_context(agent.context) try: with run_in_thread(agent.start, timeout=120, on_exit=agent.stop): wait_for_condition(lambda: agent.is_running, timeout=30) addr = FipaSellerAgent.start(multi_addr) behaviour.start(addr) wait_for_condition(lambda: behaviour.was_called, timeout=30) wait_for_condition(lambda: handler.done, timeout=30) FipaSellerAgent.proc.expect("It's done", timeout=30) finally: FipaSellerAgent.stop() FipaSellerAgent.proc.wait_eof() if __name__ == "__main__": pytest.main([__file__, "-s"]) ================================================ FILE: libs/go/aealite/Makefile ================================================ test: go test -p 1 -timeout 0 -count 1 -v ./... ================================================ FILE: libs/go/aealite/README.md ================================================ # `aealite` `aealite` is a lightweight implementation of an AEA library in Golang. ## Usage example ``` golang package main import ( "log" "os" "os/signal" aea "aealite" connections "aealite/connections" ) func main() { var err error // env file if len(os.Args) != 2 { log.Print("Usage: main ENV_FILE") os.Exit(1) } envFile := os.Args[1] log.Print("Agent starting ...") // Create agent agent := aea.Agent{} // Set connection agent.Connection = &connections.P2PClientApi{} // Initialise agent from environment file (first arg to process) err = agent.InitFromEnv(envFile) if err != nil { log.Fatal("Failed to initialise agent", err) } log.Print("successfully initialized AEA!") err = agent.Start() if err != nil { log.Fatal("Failed to start agent", err) } log.Print("successfully started AEA!") // // Send envelope to target // agent.Put(envel) // // Print out received envelopes // go func() { // for envel := range agent.Queue() { // envelope := envel // logger.Info().Msgf("received envelope: %s", envelope) // } // }() // Wait until Ctrl+C or a termination call is done. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c err = agent.Stop() if err != nil { log.Fatal("Failed to stop agent", err) } log.Print("Agent stopped") } ``` ## Development To run all tests run: ``` bash go test -p 1 -timeout 0 -count 1 -v ./... ``` To lint: ``` bash golines . -w golangci-lint run ``` To generate protoc files: ``` bash cd .. protoc -I="aealite/protocols/" --go_out="." aealite/protocols/acn.proto protoc -I="aealite/protocols/" --go_out="." aealite/protocols/base.proto cd aealite ``` ================================================ FILE: libs/go/aealite/agent.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aealite import ( "log" connections "aealite/connections" protocols "aealite/protocols" wallet "aealite/wallet" ) const ( DefaultLedger = "fetchai" ) type Agent struct { Wallet *wallet.Wallet Connection connections.Connection } func (agent *Agent) InitFromEnv(envFile string) error { if agent.Connection == nil { log.Fatal("Must set connection on agent before calling InitFromEnv().") } agent.Wallet = &wallet.Wallet{} err := agent.Wallet.InitFromEnv(envFile) if err != nil { log.Fatal("Error initialising identity.") } err = agent.Connection.InitFromEnv(envFile) if err != nil { log.Fatal("Error initialising connection.") } return nil } func (agent *Agent) Address() string { return agent.Wallet.Address } func (agent *Agent) Start() error { return agent.Connection.Connect() } func (agent *Agent) Put(envelope *protocols.Envelope) error { return agent.Connection.Put(envelope) } func (agent *Agent) Get() *protocols.Envelope { return agent.Connection.Get() } func (agent *Agent) Stop() error { err := agent.Connection.Disconnect() if err != nil { log.Fatal(err) } return err } ================================================ FILE: libs/go/aealite/agent_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aealite import ( "os" "testing" connections "aealite/connections" protocols "aealite/protocols" ) const ( EnvTestFile = "test_env_file.env" ) var ( ledgerId = "fetchai" address = "fetch1x9v67meyfq4pkgy2n2yf6797cfkul327kpclqr" publicKey = "02ac514ba70de60ed5c30f90e3acdfc958ecb416d9676706bf013228abfb2c2816" privateKey = "6d8d2b87d987641e2ca3f1991c1cccf08a118759e81fabdbf7e8484f27af015e" testProtocolId = "test_protocol_id" testMessage = []byte{0x00} ) // TestAgent apis func TestAgent(t *testing.T) { os.Args = []string{"cmd", EnvTestFile} agent := Agent{} if agent.Connection != nil { t.Fatal("Agent connection not empty") } // set p2p client agent.Connection = &connections.P2PClientApi{} // initialise err := agent.InitFromEnv("test_env_file.env") if err != nil { t.Fatal("Failed to initialise agent", err) } if agent.Wallet == nil { t.Fatal("Wallet not set on Agent") } if agent.Wallet.LedgerId != ledgerId { t.Fatal("Wallet.LedgerId not set") } if agent.Wallet.Address != address { t.Fatal("Wallet.Address not set") } if agent.Wallet.PublicKey != publicKey { t.Fatal("Wallet.PublicKey not set") } if agent.Wallet.PrivateKey != privateKey { t.Fatal("Wallet.PrivateKey not set") } if !agent.Connection.Initialised() { t.Fatal("connection not initialised") } err = agent.Start() if err != nil { t.Fatal("Failed to start agent", err) } outEnvelope := &protocols.Envelope{ To: agent.Address(), Sender: agent.Address(), ProtocolId: testProtocolId, Message: testMessage, } err = agent.Put(outEnvelope) if err != nil { t.Fatal("Failed to send envelope", err) } inEnvelope := agent.Get() if inEnvelope == nil { t.Fatal("Failed to get envelope") } if (inEnvelope.Sender != outEnvelope.Sender) || (inEnvelope.To != outEnvelope.To) { t.Fatal("Envelopes don't match") } err = agent.Stop() if err != nil { t.Fatal("Failed to stop agent", err) } } ================================================ FILE: libs/go/aealite/connections/acn/acn.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package acn import ( "errors" "fmt" "os" "strings" "time" "github.com/rs/zerolog" proto "google.golang.org/protobuf/proto" ) var logger zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: "15:04:05.000", }). With().Timestamp(). Str("package", "AeaApiACN"). Logger() func ignore(err error) { if err != nil { logger.Error().Str("err", err.Error()).Msgf("IGNORED: %s", err.Error()) } } type ACNError struct { ErrorCode Status_ErrCode Err error } func (err *ACNError) Error() string { return err.Err.Error() } func DecodeAcnMessage(buf []byte) (string, *AeaEnvelopePerformative, *StatusBody, *ACNError) { response := &AcnMessage{} err := proto.Unmarshal(buf, response) msg_type := "" if err != nil { logger.Error().Str("err", err.Error()).Msgf("while decoding acn message") return msg_type, nil, nil, &ACNError{ErrorCode: ERROR_DECODE, Err: err} } // response is either a LookupResponse or Status var aeaEnvelope *AeaEnvelopePerformative = nil var status *StatusBody = nil switch pl := response.Performative.(type) { case *AeaEnvelope: aeaEnvelope = pl.AeaEnvelope msg_type = "aea_envelope" case *Status: status = pl.Status.Body msg_type = "status" default: err = fmt.Errorf("unexpected ACN Message: %s", response) logger.Error().Msg(err.Error()) return msg_type, nil, nil, &ACNError{ErrorCode: ERROR_UNEXPECTED_PAYLOAD, Err: err} } return msg_type, aeaEnvelope, status, nil } func WaitForStatus(ch chan *StatusBody, timeout time.Duration) (*StatusBody, error) { select { case m := <-ch: return m, nil case <-time.After(timeout): err := errors.New("ACN send acknowledge timeout") logger.Error().Msg(err.Error()) return nil, err } } func SendAcnSuccess(pipe Pipe) error { status := &StatusBody{Code: SUCCESS} performative := &StatusPerformative{Body: status} msg := &AcnMessage{ Performative: &Status{Status: performative}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on encoding acn status message") return err } err = pipe.Write(buf) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on sending acn status message") } return err } func SendAcnError(pipe Pipe, error_msg string, err_codes ...Status_ErrCode) error { var err_code Status_ErrCode if len(err_codes) == 0 { err_code = ERROR_GENERIC } else { err_code = err_codes[0] } status := &StatusBody{Code: err_code, Msgs: []string{error_msg}} msg := &AcnMessage{ Performative: &Status{Status: &StatusPerformative{Body: status}}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on encoding acn status message") return err } err = pipe.Write(buf) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on sending acn status message") } return err } func EncodeAcnEnvelope(envelope_bytes []byte, record *AgentRecord) ([]byte, error) { var performative *AeaEnvelopePerformative if record != nil { performative = &AeaEnvelopePerformative{Envelope: envelope_bytes, Record: record} } else { performative = &AeaEnvelopePerformative{Envelope: envelope_bytes} } msg := &AcnMessage{ Performative: &AeaEnvelope{AeaEnvelope: performative}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("while serializing envelope bytes: %s", envelope_bytes) } return buf, err } type StatusQueue interface { AddAcnStatusMessage(status *StatusBody, counterpartyID string) } func ReadAgentRegistrationMessage(pipe Pipe) (*RegisterPerformative, error) { var register *RegisterPerformative buf, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msgf("while receiving agent's registration request") return nil, err } msg := &AcnMessage{} err = proto.Unmarshal(buf, msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("couldn't deserialize acn registration message") // TOFIX(LR) setting Msgs to err.Error is potentially a security vulnerability acn_send_error := SendAcnError(pipe, err.Error(), ERROR_DECODE) ignore(acn_send_error) return nil, err } switch pl := msg.Performative.(type) { case *Register: register = pl.Register default: err = errors.New("unexpected payload") acn_send_error := SendAcnError(pipe, err.Error(), ERROR_UNEXPECTED_PAYLOAD) ignore(acn_send_error) return nil, err } return register, nil } func SendEnvelopeMessageAndWaitForStatus( pipe Pipe, envelope_bytes []byte, acn_status_chan chan *StatusBody, acnStatusTimeout time.Duration, ) error { err := SendEnvelopeMessage(pipe, envelope_bytes, nil) if err != nil { return err } status, err := WaitForStatus(acn_status_chan, acnStatusTimeout) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on envelope sent status wait") return err } if status.Code != SUCCESS { logger.Error(). Str("op", "send_envelope"). Msgf("acn confirmation status is not Status Success: %d.", status.Code) return fmt.Errorf( "send envelope: acn confirmation status is not Status Success: %d", status.Code, ) } return err } func ReadLookupRequest(pipe Pipe) (string, error) { buf, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msgf("while reading message from stream") return "", err } msg := &AcnMessage{} err = proto.Unmarshal(buf, msg) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("couldn't deserialize acn lookup request message") // TOFIX(LR) setting Msgs to err.Error is potentially a security vulnerability acn_send_error := SendAcnError(pipe, err.Error(), ERROR_DECODE) ignore(acn_send_error) return "", err } // Get LookupRequest message var lookupRequest *LookupRequestPerformative switch pl := msg.Performative.(type) { case *LookupRequest: lookupRequest = pl.LookupRequest default: err = errors.New("unexpected payload") acn_send_error := SendAcnError(pipe, err.Error(), ERROR_UNEXPECTED_PAYLOAD) ignore(acn_send_error) return "", err } reqAddress := lookupRequest.AgentAddress return reqAddress, nil } func SendLookupRequest(pipe Pipe, address string) error { lookupRequest := &LookupRequestPerformative{AgentAddress: address} msg := &AcnMessage{ Performative: &LookupRequest{LookupRequest: lookupRequest}, } buf, err := proto.Marshal(msg) if err != nil { return err } err = pipe.Write([]byte(buf)) return err } func ReadLookupResponse(pipe Pipe) (*AgentRecord, error) { buf, err := pipe.Read() if err != nil { return nil, err } response := &AcnMessage{} err = proto.Unmarshal(buf, response) if err != nil { return nil, err } var lookupResponse *LookupResponsePerformative = nil var status *StatusPerformative = nil switch pl := response.Performative.(type) { case *LookupResponse: lookupResponse = pl.LookupResponse case *Status: status = pl.Status default: err = errors.New("unexpected Acn Message") logger.Error().Str("err", err.Error()).Msgf("couldn't deserialize acn lookup response message") return nil, err } if status != nil { err = errors.New( "Failed agent lookup response " + status.Body.Code.String() + " : " + strings.Join( status.Body.Msgs, ":", ), ) return nil, err } return lookupResponse.Record, nil } func SendLookupResponse(pipe Pipe, record *AgentRecord) error { lookupResponse := &LookupResponsePerformative{Record: record} response := &AcnMessage{ Performative: &LookupResponse{LookupResponse: lookupResponse}, } buf, err := proto.Marshal(response) if err != nil { return err } err = pipe.Write(buf) return err } func SendEnvelopeMessage(pipe Pipe, envelope_bytes []byte, record *AgentRecord) error { acnMsgBytes, err := EncodeAcnEnvelope(envelope_bytes, record) if err != nil { return err } err = pipe.Write(acnMsgBytes) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on pipe write") return err } return nil } func SendAgentRegisterMessage(pipe Pipe, agentRecord *AgentRecord) error { registration := &RegisterPerformative{Record: agentRecord} msg := &AcnMessage{ Performative: &Register{Register: registration}, } buf, err := proto.Marshal(msg) if err != nil { return err } err = pipe.Write(buf) if err != nil { return err } status, err := ReadAcnStatus(pipe) if err != nil { return err } if status.Code != SUCCESS { return errors.New("Registration failed: " + strings.Join(status.Msgs, ":")) } return nil } func ReadAcnStatus(pipe Pipe) (*StatusBody, error) { buf, err := pipe.Read() if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on pipe read") return nil, err } response := &AcnMessage{} err = proto.Unmarshal(buf, response) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on acn decode") return nil, err } // response is expected to be a Status var status *StatusPerformative switch pl := response.Performative.(type) { case *Status: status = pl.Status default: err = errors.New("unexpected Acn Message") return nil, err } return status.Body, nil } func ReadEnvelopeMessage(pipe Pipe) (*AeaEnvelopePerformative, error) { buf, err := pipe.Read() if err != nil { return nil, err } messageType, envelope, _, acnErr := DecodeAcnMessage(buf) if acnErr != nil { err = SendAcnError( pipe, acnErr.Error(), acnErr.ErrorCode, ) ignore(err) return nil, acnErr.Err } if messageType != "aea_envelope" { return nil, errors.New("unexpected payload for acn message") } return envelope, nil } func PerformAddressLookup(pipe Pipe, address string) (*AgentRecord, error) { err := SendLookupRequest(pipe, address) if err != nil { return nil, err } return ReadLookupResponse(pipe) } ================================================ FILE: libs/go/aealite/connections/acn/pipe_iface.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package acn type Pipe interface { Connect() error Read() ([]byte, error) Write(data []byte) error //Close() error } ================================================ FILE: libs/go/aealite/connections/acn/protocol.go ================================================ package acn import acn_protocol "aealite/protocols/acn/v1_0_0" type StatusBody = acn_protocol.AcnMessage_StatusBody type AgentRecord = acn_protocol.AcnMessage_AgentRecord type AcnMessage = acn_protocol.AcnMessage type LookupRequest = acn_protocol.AcnMessage_LookupRequest type LookupResponse = acn_protocol.AcnMessage_LookupResponse type Status = acn_protocol.AcnMessage_Status type LookupRequestPerformative = acn_protocol.AcnMessage_Lookup_Request_Performative type LookupResponsePerformative = acn_protocol.AcnMessage_Lookup_Response_Performative type StatusPerformative = acn_protocol.AcnMessage_Status_Performative type RegisterPerformative = acn_protocol.AcnMessage_Register_Performative type Register = acn_protocol.AcnMessage_Register type AeaEnvelope = acn_protocol.AcnMessage_AeaEnvelope type AeaEnvelopePerformative = acn_protocol.AcnMessage_Aea_Envelope_Performative const ERROR_DECODE = acn_protocol.AcnMessage_StatusBody_ERROR_DECODE const SUCCESS = acn_protocol.AcnMessage_StatusBody_SUCCESS const ERROR_UNEXPECTED_PAYLOAD = acn_protocol.AcnMessage_StatusBody_ERROR_UNEXPECTED_PAYLOAD const ERROR_AGENT_NOT_READY = acn_protocol.AcnMessage_StatusBody_ERROR_AGENT_NOT_READY const ERROR_UNKNOWN_AGENT_ADDRESS = acn_protocol.AcnMessage_StatusBody_ERROR_UNKNOWN_AGENT_ADDRESS const ERROR_GENERIC = acn_protocol.AcnMessage_StatusBody_ERROR_GENERIC const ERROR_WRONG_AGENT_ADDRESS = acn_protocol.AcnMessage_StatusBody_ERROR_WRONG_AGENT_ADDRESS const ERROR_UNSUPPORTED_LEDGER = acn_protocol.AcnMessage_StatusBody_ERROR_UNSUPPORTED_LEDGER const ERROR_WRONG_PUBLIC_KEY = acn_protocol.AcnMessage_StatusBody_ERROR_WRONG_PUBLIC_KEY const ERROR_INVALID_PROOF = acn_protocol.AcnMessage_StatusBody_ERROR_INVALID_PROOF type Status_ErrCode = acn_protocol.AcnMessage_StatusBody_StatusCodeEnum ================================================ FILE: libs/go/aealite/connections/connections.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package connections import ( protocols "aealite/protocols" ) type Socket interface { Connect() error Read() ([]byte, error) Write(data []byte) error Disconnect() error } type Connection interface { InitFromEnv(envFile string) error Connect() error Get() *protocols.Envelope Put(envelope *protocols.Envelope) error Disconnect() error Initialised() bool } ================================================ FILE: libs/go/aealite/connections/p2pclient.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package connections import ( "context" "errors" "fmt" "log" "math" "math/rand" "os" "strconv" "strings" "time" protocols "aealite/protocols" wallet "aealite/wallet" acn "aealite/connections/acn" "github.com/joho/godotenv" "github.com/rs/zerolog" proto "google.golang.org/protobuf/proto" ) const retryAttempts = 5 const acnStatusTimeout = 5 * time.Second var logger zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: "15:04:05.000", }). With().Timestamp(). Str("package", "P2PClientApi"). Logger() var ( DefaultAttempts = uint(10) DefaultOnRetry = func(n uint, err error) {} DefaultRetryIf = IsRecoverable DefaultDelay = 500 * time.Millisecond DefaultMaxJitter = 100 * time.Millisecond DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay) DefaultLastErrorOnly = false DefaultContext = context.Background() MaxDelay = 1000 * time.Millisecond ) type P2PClientConfig struct { host string port uint16 } type P2PClientApi struct { clientConfig *P2PClientConfig agentRecord *acn.AgentRecord socket Socket outQueue chan *protocols.Envelope closing bool connected bool initialised bool acn_status_chan chan *acn.StatusBody } func (client *P2PClientApi) InitFromEnv(envFile string) error { zerolog.TimeFieldFormat = time.RFC3339Nano if client.connected { return nil } client.connected = false client.initialised = false logger.Debug().Msgf("env_file: %s", envFile) err := godotenv.Overload(envFile) if err != nil { log.Fatal("Error loading env file") } address := os.Getenv("AEA_ADDRESS") publicKey := os.Getenv("AEA_PUBLIC_KEY") agentRecord := &acn.AgentRecord{Address: address, PublicKey: publicKey} agentRecord.ServiceId = os.Getenv("AEA_P2P_POR_SERVICE_ID") agentRecord.LedgerId = os.Getenv("AEA_P2P_POR_LEDGER_ID") agentRecord.PeerPublicKey = os.Getenv("AEA_P2P_POR_PEER_PUBKEY") agentRecord.Signature = os.Getenv("AEA_P2P_POR_SIGNATURE") ok, err := wallet.VerifyLedgerSignature( agentRecord.LedgerId, []byte(agentRecord.PeerPublicKey), agentRecord.Signature, agentRecord.PublicKey, ) if err != nil { log.Fatal("Could not verify signature." + err.Error()) } if !ok { log.Fatal("Invalid signature.") } client.agentRecord = agentRecord host := os.Getenv("AEA_P2P_DELEGATE_HOST") port := os.Getenv("AEA_P2P_DELEGATE_PORT") portConv, err := strconv.ParseUint(port, 10, 16) if err != nil { panic(err) } client.clientConfig = &P2PClientConfig{host: host, port: uint16(portConv)} client.socket = NewSocket(client.clientConfig.host, client.clientConfig.port, client.agentRecord.PeerPublicKey) client.initialised = true return nil } func (client *P2PClientApi) Put(envelope *protocols.Envelope) error { envelopeBytes, err := proto.Marshal(envelope) if err != nil { logger.Error().Str("err", err.Error()).Msgf("while serializing envelope: %s", envelope) return err } return acn.SendEnvelopeMessageAndWaitForStatus(client.socket, envelopeBytes, client.acn_status_chan, acnStatusTimeout) } func (client *P2PClientApi) Get() *protocols.Envelope { return <-client.outQueue } func (client *P2PClientApi) Queue() <-chan *protocols.Envelope { return client.outQueue } func (client *P2PClientApi) Connected() bool { return client.connected } func (client *P2PClientApi) Initialised() bool { return client.initialised } func (client *P2PClientApi) Disconnect() error { client.closing = true err := client.stop() if err != nil { logger.Error().Str("err", err.Error()). Msg("error while disconnecting P2PClientApi") return err } close(client.outQueue) close(client.acn_status_chan) client.connected = false return nil } func (client *P2PClientApi) Connect() error { err := client.socket.Connect() if err != nil { logger.Error().Str("err", err.Error()). Msg("while connecting to socket") return err } err = client.registerWithRetry() if err != nil { logger.Error().Str("err", err.Error()). Msg("while registering with retry to p2p node") err_ := client.stop() if err_ != nil { logger.Error().Str("err", err_.Error()). Msg("while handling other error") } return err } logger.Info().Msg("successfully registered on node") client.closing = false client.outQueue = make(chan *protocols.Envelope, 10) client.acn_status_chan = make(chan *acn.StatusBody, 10) go client.listenForEnvelopes() logger.Info().Msg("connected to p2p node") client.connected = true return nil } func (client *P2PClientApi) registerWithRetry() error { var n uint //default config := &Config{ attempts: DefaultAttempts, delay: DefaultDelay, maxJitter: DefaultMaxJitter, onRetry: DefaultOnRetry, retryIf: DefaultRetryIf, delayType: DefaultDelayType, lastErrorOnly: DefaultLastErrorOnly, context: DefaultContext, } var errorLog = make(Error, 1) context_ := context.Background() lastErrIndex := n for n < retryAttempts { err := client.register() if err != nil { errorLog[lastErrIndex] = unpackUnrecoverable(err) // if this is last attempt - don't wait if n == retryAttempts-1 { break } delayTime := config.delayType(n, err, config) if config.maxDelay > 0 && delayTime > config.maxDelay { delayTime = config.maxDelay } select { case <-time.After(delayTime): case <-context_.Done(): return context_.Err() } } else { return nil } n++ } return errorLog } func (client *P2PClientApi) register() error { return acn.SendAgentRegisterMessage(client.socket, client.agentRecord) } func (client *P2PClientApi) listenForEnvelopes() { for { envel, err := client.HandleAcnMessageFromPipe() if err != nil && !client.closing { logger.Error().Str("err", err.Error()).Msg("while receiving envelope") logger.Info().Msg("disconnecting") if !client.closing { err_ := client.stop() if err_ != nil { logger.Error().Str("err", err_.Error()).Msg("while handling other error") } } return } if envel == nil { // got acn status, not an envelope continue } if envel.To != client.agentRecord.Address { logger.Error(). Str("err", "To ("+envel.To+") must match registered address"). Msg("while processing envelope") continue } logger.Debug().Msgf("received envelope for agent") client.outQueue <- envel if client.closing { return } } } func (client *P2PClientApi) stop() error { return client.socket.Disconnect() } // Error type represents list of errors in retry type Error []error // Error method return string representation of Error // Implements error interface func (e Error) Error() string { logWithNumber := make([]string, lenWithoutNil(e)) for i, l := range e { if l != nil { logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error()) } } return fmt.Sprintf("All attempts fail:\n%s", strings.Join(logWithNumber, "\n")) } func lenWithoutNil(e Error) (count int) { for _, v := range e { if v != nil { count++ } } return } // WrappedErrors returns the list of errors that this Error is wrapping. func (e Error) WrappedErrors() []error { return e } type unrecoverableError struct { error } // Unrecoverable wraps an error in `unrecoverableError` struct func Unrecoverable(err error) error { return unrecoverableError{err} } // IsRecoverable checks if error is an instance of `unrecoverableError` func IsRecoverable(err error) bool { _, isUnrecoverable := err.(unrecoverableError) return !isUnrecoverable } func unpackUnrecoverable(err error) error { if unrecoverable, isUnrecoverable := err.(unrecoverableError); isUnrecoverable { return unrecoverable.error } return err } // DelayTypeFunc is called to return the next delay to wait after the retriable function fails on `err` after `n` attempts. type DelayTypeFunc func(n uint, err error, config *Config) time.Duration // Function signature of retry if function type RetryIfFunc func(error) bool // Function signature of OnRetry function // n = count of attempts type OnRetryFunc func(n uint, err error) type Config struct { attempts uint delay time.Duration maxDelay time.Duration maxJitter time.Duration onRetry OnRetryFunc retryIf RetryIfFunc delayType DelayTypeFunc lastErrorOnly bool context context.Context maxBackOffN uint } // CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc { const maxInt64 = uint64(math.MaxInt64) return func(n uint, err error, config *Config) time.Duration { var total uint64 for _, delay := range delays { total += uint64(delay(n, err, config)) if total > maxInt64 { total = maxInt64 } } return time.Duration(total) } } // BackOffDelay is a DelayType which increases delay between consecutive retries func BackOffDelay(n uint, _ error, config *Config) time.Duration { // 1 << 63 would overflow signed int64 (time.Duration), thus 62. const max uint = 62 if config.maxBackOffN == 0 { if config.delay <= 0 { config.delay = 1 } config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay)))) } if n > config.maxBackOffN { n = config.maxBackOffN } return config.delay << n } // RandomDelay is a DelayType which picks a random delay up to config.maxJitter func RandomDelay(_ uint, _ error, config *Config) time.Duration { return time.Duration(rand.Int63n(int64(config.maxJitter))) } func (client *P2PClientApi) HandleAcnMessageFromPipe() (*protocols.Envelope, error) { pipe := client.socket envelope := &protocols.Envelope{} var acn_err error data, err := pipe.Read() if err != nil { return nil, err } msg_type, acn_envelope, status, acnErr := acn.DecodeAcnMessage(data) if acnErr != nil { logger.Error().Str("err", acnErr.Error()).Msg("while handling acn message") acn_err = acn.SendAcnError( pipe, acnErr.Error(), acnErr.ErrorCode, ) if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return envelope, acnErr } switch msg_type { case "aea_envelope": { err = proto.Unmarshal(acn_envelope.Envelope, envelope) if err != nil { logger.Error().Str("err", err.Error()).Msg("while decoding envelope") acn_err = acn.SendAcnError( pipe, "error on decoding envelope", acn.ERROR_DECODE, ) if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return envelope, err } err = acn.SendAcnSuccess(pipe) return envelope, err } case "status": { logger.Debug().Msgf("got acn status %d", status.Code) client.acn_status_chan <- status return nil, nil } default: { acn_err = acn.SendAcnError(pipe, "Unsupported ACN message") if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return nil, errors.New("unsupported ACN message") } } } ================================================ FILE: libs/go/aealite/connections/p2pclient_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package connections import ( "os" "testing" ) const ( EnvTestFile = "../test_env_file.env" ) var ( address = "fetch1x9v67meyfq4pkgy2n2yf6797cfkul327kpclqr" public_key = "02ac514ba70de60ed5c30f90e3acdfc958ecb416d9676706bf013228abfb2c2816" por_service_id = "acn" por_ledger_id = "fetchai" por_peer_public_key = "0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7" por_signature = "avzHfL/fjMidvweJJKjtBUiqJ2+6aDUq8MoNRBi9nDI/lWleIX3ftRf6Sx5UWmxcS0SW03IVrf1iKTXA5zeA0g==" delegate_host = "acn.fetch.ai" delegate_port = uint16(11000) ) // TestP2PClientApiInit func TestP2PClientApiInit(t *testing.T) { os.Args = []string{"cmd", EnvTestFile} client := &P2PClientApi{} // initialise err := client.InitFromEnv(EnvTestFile) if err != nil { t.Fatal("Failed to initialise client", err) } if client.clientConfig == nil { t.Fatal("client_config not set", err) } if client.clientConfig.host != delegate_host { t.Fatal("client_config.host not set", err) } if client.clientConfig.port != delegate_port { t.Fatal("client_config.port not set", err) } if client.agentRecord == nil { t.Fatal("client.agent_record not set") } if client.agentRecord.ServiceId != por_service_id { t.Fatal("agent_record.ServiceId not set") } if client.agentRecord.LedgerId != por_ledger_id { t.Fatal("agent_record.LedgerId not set") } if client.agentRecord.Address != address { t.Fatal("agent_record.Address not set") } if client.agentRecord.PublicKey != public_key { t.Fatal("agent_record.PublicKey not set") } if client.agentRecord.PeerPublicKey != por_peer_public_key { t.Fatal("agent_record.PublicKey not set") } if client.agentRecord.Signature != por_signature { t.Fatal("agent_record.Signature not set") } if client.socket == nil { t.Fatal("client.socket not set") } if !client.initialised { t.Fatal("client not initialised") } if client.connected { t.Fatal("client connected") } err = client.Connect() if err != nil { t.Fatal("client connect failed") } if !client.connected { t.Fatal("client not connected") } err = client.Disconnect() if err != nil { t.Fatal("client disconnect failed") } if client.connected { t.Fatal("client connected") } } ================================================ FILE: libs/go/aealite/connections/tcpsocket.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package connections import ( wallet "aealite/wallet" "crypto/ecdsa" "crypto/elliptic" "crypto/tls" "crypto/x509" "encoding/binary" "errors" "strconv" ) type TCPSocketChannel struct { address string port uint16 conn *tls.Conn peerPublicKey string } func (sock *TCPSocketChannel) Connect() error { var err error conf := &tls.Config{ InsecureSkipVerify: true, } sock.conn, err = tls.Dial("tcp", sock.address+":"+strconv.FormatInt(int64(sock.port), 10), conf) if err != nil { return err } state := sock.conn.ConnectionState() var cert *x509.Certificate for _, v := range state.PeerCertificates { cert = v } pub := cert.PublicKey.(*ecdsa.PublicKey) publicKeyBytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y) signature, err := sock.Read() logger.Debug().Msgf("got signature %d bytes", len(signature)) if err != nil { return err } pubkey, err := wallet.PubKeyFromFetchAIPublicKey(sock.peerPublicKey) if err != nil { return err } ok, err := pubkey.Verify(publicKeyBytes, signature) if err != nil { return err } if !ok { return errors.New("tls signature check failed") } return nil } func (sock *TCPSocketChannel) Read() ([]byte, error) { buf := make([]byte, 4) _, err := sock.conn.Read(buf) if err != nil { return buf, err } size := binary.BigEndian.Uint32(buf) buf = make([]byte, size) _, err = sock.conn.Read(buf) return buf, err } func (sock *TCPSocketChannel) Write(data []byte) error { size := uint32(len(data)) buf := make([]byte, 4, 4+size) binary.BigEndian.PutUint32(buf, size) buf = append(buf, data...) _, err := sock.conn.Write(buf) logger.Debug().Msgf("wrote data to pipe: %d bytes", size) return err } func (sock *TCPSocketChannel) Disconnect() error { return sock.conn.Close() } func NewSocket(address string, port uint16, peerPublicKey string) Socket { return &TCPSocketChannel{address: address, port: port, peerPublicKey: peerPublicKey} } ================================================ FILE: libs/go/aealite/go.mod ================================================ module aealite go 1.14 require ( github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 github.com/ethereum/go-ethereum v1.10.17 github.com/golang/protobuf v1.4.3 github.com/joho/godotenv v1.3.0 github.com/libp2p/go-libp2p-core v0.8.5 github.com/multiformats/go-multiaddr v0.3.1 // indirect github.com/rs/zerolog v1.20.0 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 google.golang.org/protobuf v1.25.0 gotest.tools v2.2.0+incompatible ) ================================================ FILE: libs/go/aealite/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0 h1:MSskdM4/xJYcFzy0altH/C/xHopifpWzHUi1JeVI34Q= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-libp2p-core v0.8.5 h1:aEgbIcPGsKy6zYcC+5AJivYFedhYa4sW7mIpWpUaLKw= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= github.com/multiformats/go-multiaddr v0.3.1 h1:1bxa+W7j9wZKTZREySx1vPMs2TqrYWjVZ7zE6/XLG1I= github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.14 h1:QoBceQYQQtNUuf6s7wHxnE2c8bhbMqhfGzNI032se/I= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= ================================================ FILE: libs/go/aealite/helpers/base.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package helpers // Generic a generic type (every type implements at least zero methods). type Generic interface{} /* Set implementation of a set of generic types. It uses the built-in 'map' type, which is based on hash tables: https://golang.org/src/runtime/map.go This guarantees (amortized) constant-time complexity for addition, deletion, and lookup. */ type Set struct { container map[Generic]bool // container: a private container. } //AddFromArray adds elements to the set from an array func (set *Set) AddFromArray(array []Generic) { for _, element := range array { set.Add(element) } } // ToArray gives an array of 'interface{}' built from the set. func (set *Set) ToArray() []interface{} { keys := make([]interface{}, 0, len(set.container)) for k := range set.container { keys = append(keys, k) } return keys } // Add adds an element. func (set *Set) Add(element Generic) { set.container[element] = true } // Remove removes an element. func (set *Set) Remove(element Generic) { delete(set.container, element) } // Contains checks an element is in the set. func (set *Set) Contains(element Generic) bool { val, ok := set.container[element] return val && ok } // Size returns the size of the set. func (set *Set) Size() int { return len(set.container) } // Copy instantiate a new copy of the set. func (set *Set) Copy() Set { newSet := NewSet() for element := range set.container { newSet.Add(element) } return newSet } // Difference computes the difference from set 1 to set 2 func Difference(set1 Set, set2 Set) Set { result := set1.Copy() for element := range set2.container { result.Remove(element) } return result } // NewSet returns a new set. func NewSet() Set { result := Set{} result.container = make(map[Generic]bool) return result } // NewSetFromArray returns a new set initialized from an array. func NewSetFromArray(array []interface{}) Set { result := NewSet() result.AddFromArray(fromInterfaceToGenericArray(array)) return result } func fromInterfaceToGenericArray(array []interface{}) []Generic { newArray := make([]Generic, len(array)) for index, element := range array { newArray[index] = element } return newArray } ================================================ FILE: libs/go/aealite/helpers/base_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package helpers import ( "testing" ) func checkExpectedSize(t *testing.T, set *Set, expectedSize int) { if actualSize := set.Size(); actualSize != expectedSize { t.Fatalf("expected size %d, got %d", expectedSize, actualSize) } } func checkIn(t *testing.T, set *Set, element Generic) { if !set.Contains(element) { t.Fatalf("expected to find element %s, but not found it", element) } } func checkNotIn(t *testing.T, set *Set, element Generic) { if set.Contains(element) { t.Fatalf("expected to find element %s, but not found it", element) } } func TestSet(t *testing.T) { set := NewSet() element1 := "hello" element2 := 42 element3 := struct { Name string Surname string }{"Alan", "Turing"} checkNotIn(t, &set, element1) checkNotIn(t, &set, element2) checkNotIn(t, &set, element3) checkExpectedSize(t, &set, 0) set.Add(element1) checkIn(t, &set, element1) checkNotIn(t, &set, element2) checkNotIn(t, &set, element3) checkExpectedSize(t, &set, 1) set.Add(element2) set.Add(element3) checkIn(t, &set, element1) checkIn(t, &set, element2) checkIn(t, &set, element3) checkExpectedSize(t, &set, 3) set.Remove(element1) set.Remove(element2) set.Remove(element3) checkNotIn(t, &set, element1) checkNotIn(t, &set, element2) checkNotIn(t, &set, element3) checkExpectedSize(t, &set, 0) } func TestSetFromArray(t *testing.T) { elements := []interface{}{"hello", 42, "world", "world"} set := NewSetFromArray(elements) expectedSize := 3 actualSize := set.Size() if expectedSize != actualSize { t.Fatalf("expected %v, found %v", expectedSize, actualSize) } } ================================================ FILE: libs/go/aealite/protocols/acn/v1_0_0/acn.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.11.4 // source: acn.proto package aea_aea_acn_v1_0_0 import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type AcnMessage_StatusBody_StatusCodeEnum int32 const ( // common (0x) AcnMessage_StatusBody_SUCCESS AcnMessage_StatusBody_StatusCodeEnum = 0 AcnMessage_StatusBody_ERROR_UNSUPPORTED_VERSION AcnMessage_StatusBody_StatusCodeEnum = 1 AcnMessage_StatusBody_ERROR_UNEXPECTED_PAYLOAD AcnMessage_StatusBody_StatusCodeEnum = 2 AcnMessage_StatusBody_ERROR_GENERIC AcnMessage_StatusBody_StatusCodeEnum = 3 AcnMessage_StatusBody_ERROR_DECODE AcnMessage_StatusBody_StatusCodeEnum = 4 // register (1x) AcnMessage_StatusBody_ERROR_WRONG_AGENT_ADDRESS AcnMessage_StatusBody_StatusCodeEnum = 10 AcnMessage_StatusBody_ERROR_WRONG_PUBLIC_KEY AcnMessage_StatusBody_StatusCodeEnum = 11 AcnMessage_StatusBody_ERROR_INVALID_PROOF AcnMessage_StatusBody_StatusCodeEnum = 12 AcnMessage_StatusBody_ERROR_UNSUPPORTED_LEDGER AcnMessage_StatusBody_StatusCodeEnum = 13 // lookup & delivery (2x) AcnMessage_StatusBody_ERROR_UNKNOWN_AGENT_ADDRESS AcnMessage_StatusBody_StatusCodeEnum = 20 AcnMessage_StatusBody_ERROR_AGENT_NOT_READY AcnMessage_StatusBody_StatusCodeEnum = 21 ) // Enum value maps for AcnMessage_StatusBody_StatusCodeEnum. var ( AcnMessage_StatusBody_StatusCodeEnum_name = map[int32]string{ 0: "SUCCESS", 1: "ERROR_UNSUPPORTED_VERSION", 2: "ERROR_UNEXPECTED_PAYLOAD", 3: "ERROR_GENERIC", 4: "ERROR_DECODE", 10: "ERROR_WRONG_AGENT_ADDRESS", 11: "ERROR_WRONG_PUBLIC_KEY", 12: "ERROR_INVALID_PROOF", 13: "ERROR_UNSUPPORTED_LEDGER", 20: "ERROR_UNKNOWN_AGENT_ADDRESS", 21: "ERROR_AGENT_NOT_READY", } AcnMessage_StatusBody_StatusCodeEnum_value = map[string]int32{ "SUCCESS": 0, "ERROR_UNSUPPORTED_VERSION": 1, "ERROR_UNEXPECTED_PAYLOAD": 2, "ERROR_GENERIC": 3, "ERROR_DECODE": 4, "ERROR_WRONG_AGENT_ADDRESS": 10, "ERROR_WRONG_PUBLIC_KEY": 11, "ERROR_INVALID_PROOF": 12, "ERROR_UNSUPPORTED_LEDGER": 13, "ERROR_UNKNOWN_AGENT_ADDRESS": 20, "ERROR_AGENT_NOT_READY": 21, } ) func (x AcnMessage_StatusBody_StatusCodeEnum) Enum() *AcnMessage_StatusBody_StatusCodeEnum { p := new(AcnMessage_StatusBody_StatusCodeEnum) *p = x return p } func (x AcnMessage_StatusBody_StatusCodeEnum) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AcnMessage_StatusBody_StatusCodeEnum) Descriptor() protoreflect.EnumDescriptor { return file_acn_proto_enumTypes[0].Descriptor() } func (AcnMessage_StatusBody_StatusCodeEnum) Type() protoreflect.EnumType { return &file_acn_proto_enumTypes[0] } func (x AcnMessage_StatusBody_StatusCodeEnum) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AcnMessage_StatusBody_StatusCodeEnum.Descriptor instead. func (AcnMessage_StatusBody_StatusCodeEnum) EnumDescriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 1, 0} } type AcnMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Performative: // *AcnMessage_AeaEnvelope // *AcnMessage_LookupRequest // *AcnMessage_LookupResponse // *AcnMessage_Register // *AcnMessage_Status Performative isAcnMessage_Performative `protobuf_oneof:"performative"` } func (x *AcnMessage) Reset() { *x = AcnMessage{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage) ProtoMessage() {} func (x *AcnMessage) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage.ProtoReflect.Descriptor instead. func (*AcnMessage) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0} } func (m *AcnMessage) GetPerformative() isAcnMessage_Performative { if m != nil { return m.Performative } return nil } func (x *AcnMessage) GetAeaEnvelope() *AcnMessage_Aea_Envelope_Performative { if x, ok := x.GetPerformative().(*AcnMessage_AeaEnvelope); ok { return x.AeaEnvelope } return nil } func (x *AcnMessage) GetLookupRequest() *AcnMessage_Lookup_Request_Performative { if x, ok := x.GetPerformative().(*AcnMessage_LookupRequest); ok { return x.LookupRequest } return nil } func (x *AcnMessage) GetLookupResponse() *AcnMessage_Lookup_Response_Performative { if x, ok := x.GetPerformative().(*AcnMessage_LookupResponse); ok { return x.LookupResponse } return nil } func (x *AcnMessage) GetRegister() *AcnMessage_Register_Performative { if x, ok := x.GetPerformative().(*AcnMessage_Register); ok { return x.Register } return nil } func (x *AcnMessage) GetStatus() *AcnMessage_Status_Performative { if x, ok := x.GetPerformative().(*AcnMessage_Status); ok { return x.Status } return nil } type isAcnMessage_Performative interface { isAcnMessage_Performative() } type AcnMessage_AeaEnvelope struct { AeaEnvelope *AcnMessage_Aea_Envelope_Performative `protobuf:"bytes,5,opt,name=aea_envelope,json=aeaEnvelope,proto3,oneof"` } type AcnMessage_LookupRequest struct { LookupRequest *AcnMessage_Lookup_Request_Performative `protobuf:"bytes,6,opt,name=lookup_request,json=lookupRequest,proto3,oneof"` } type AcnMessage_LookupResponse struct { LookupResponse *AcnMessage_Lookup_Response_Performative `protobuf:"bytes,7,opt,name=lookup_response,json=lookupResponse,proto3,oneof"` } type AcnMessage_Register struct { Register *AcnMessage_Register_Performative `protobuf:"bytes,8,opt,name=register,proto3,oneof"` } type AcnMessage_Status struct { Status *AcnMessage_Status_Performative `protobuf:"bytes,9,opt,name=status,proto3,oneof"` } func (*AcnMessage_AeaEnvelope) isAcnMessage_Performative() {} func (*AcnMessage_LookupRequest) isAcnMessage_Performative() {} func (*AcnMessage_LookupResponse) isAcnMessage_Performative() {} func (*AcnMessage_Register) isAcnMessage_Performative() {} func (*AcnMessage_Status) isAcnMessage_Performative() {} // Custom Types type AcnMessage_AgentRecord struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ServiceId string `protobuf:"bytes,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` LedgerId string `protobuf:"bytes,2,opt,name=ledger_id,json=ledgerId,proto3" json:"ledger_id,omitempty"` Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` PublicKey string `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` PeerPublicKey string `protobuf:"bytes,5,opt,name=peer_public_key,json=peerPublicKey,proto3" json:"peer_public_key,omitempty"` Signature string `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` NotBefore string `protobuf:"bytes,7,opt,name=not_before,json=notBefore,proto3" json:"not_before,omitempty"` NotAfter string `protobuf:"bytes,8,opt,name=not_after,json=notAfter,proto3" json:"not_after,omitempty"` } func (x *AcnMessage_AgentRecord) Reset() { *x = AcnMessage_AgentRecord{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_AgentRecord) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_AgentRecord) ProtoMessage() {} func (x *AcnMessage_AgentRecord) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_AgentRecord.ProtoReflect.Descriptor instead. func (*AcnMessage_AgentRecord) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 0} } func (x *AcnMessage_AgentRecord) GetServiceId() string { if x != nil { return x.ServiceId } return "" } func (x *AcnMessage_AgentRecord) GetLedgerId() string { if x != nil { return x.LedgerId } return "" } func (x *AcnMessage_AgentRecord) GetAddress() string { if x != nil { return x.Address } return "" } func (x *AcnMessage_AgentRecord) GetPublicKey() string { if x != nil { return x.PublicKey } return "" } func (x *AcnMessage_AgentRecord) GetPeerPublicKey() string { if x != nil { return x.PeerPublicKey } return "" } func (x *AcnMessage_AgentRecord) GetSignature() string { if x != nil { return x.Signature } return "" } func (x *AcnMessage_AgentRecord) GetNotBefore() string { if x != nil { return x.NotBefore } return "" } func (x *AcnMessage_AgentRecord) GetNotAfter() string { if x != nil { return x.NotAfter } return "" } type AcnMessage_StatusBody struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Code AcnMessage_StatusBody_StatusCodeEnum `protobuf:"varint,1,opt,name=code,proto3,enum=aea.aea.acn.v1_0_0.AcnMessage_StatusBody_StatusCodeEnum" json:"code,omitempty"` Msgs []string `protobuf:"bytes,2,rep,name=msgs,proto3" json:"msgs,omitempty"` } func (x *AcnMessage_StatusBody) Reset() { *x = AcnMessage_StatusBody{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_StatusBody) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_StatusBody) ProtoMessage() {} func (x *AcnMessage_StatusBody) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_StatusBody.ProtoReflect.Descriptor instead. func (*AcnMessage_StatusBody) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 1} } func (x *AcnMessage_StatusBody) GetCode() AcnMessage_StatusBody_StatusCodeEnum { if x != nil { return x.Code } return AcnMessage_StatusBody_SUCCESS } func (x *AcnMessage_StatusBody) GetMsgs() []string { if x != nil { return x.Msgs } return nil } // Performatives and contents type AcnMessage_Register_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Record *AcnMessage_AgentRecord `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Register_Performative) Reset() { *x = AcnMessage_Register_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Register_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Register_Performative) ProtoMessage() {} func (x *AcnMessage_Register_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Register_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Register_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 2} } func (x *AcnMessage_Register_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Lookup_Request_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AgentAddress string `protobuf:"bytes,1,opt,name=agent_address,json=agentAddress,proto3" json:"agent_address,omitempty"` } func (x *AcnMessage_Lookup_Request_Performative) Reset() { *x = AcnMessage_Lookup_Request_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Lookup_Request_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Lookup_Request_Performative) ProtoMessage() {} func (x *AcnMessage_Lookup_Request_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Lookup_Request_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Lookup_Request_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 3} } func (x *AcnMessage_Lookup_Request_Performative) GetAgentAddress() string { if x != nil { return x.AgentAddress } return "" } type AcnMessage_Lookup_Response_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Record *AcnMessage_AgentRecord `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Lookup_Response_Performative) Reset() { *x = AcnMessage_Lookup_Response_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Lookup_Response_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Lookup_Response_Performative) ProtoMessage() {} func (x *AcnMessage_Lookup_Response_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Lookup_Response_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Lookup_Response_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 4} } func (x *AcnMessage_Lookup_Response_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Aea_Envelope_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Envelope []byte `protobuf:"bytes,1,opt,name=envelope,proto3" json:"envelope,omitempty"` Record *AcnMessage_AgentRecord `protobuf:"bytes,2,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Aea_Envelope_Performative) Reset() { *x = AcnMessage_Aea_Envelope_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Aea_Envelope_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Aea_Envelope_Performative) ProtoMessage() {} func (x *AcnMessage_Aea_Envelope_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Aea_Envelope_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Aea_Envelope_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 5} } func (x *AcnMessage_Aea_Envelope_Performative) GetEnvelope() []byte { if x != nil { return x.Envelope } return nil } func (x *AcnMessage_Aea_Envelope_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Status_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Body *AcnMessage_StatusBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` } func (x *AcnMessage_Status_Performative) Reset() { *x = AcnMessage_Status_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Status_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Status_Performative) ProtoMessage() {} func (x *AcnMessage_Status_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Status_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Status_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 6} } func (x *AcnMessage_Status_Performative) GetBody() *AcnMessage_StatusBody { if x != nil { return x.Body } return nil } var File_acn_proto protoreflect.FileDescriptor var file_acn_proto_rawDesc = []byte{ 0x0a, 0x09, 0x61, 0x63, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x22, 0xea, 0x0c, 0x0a, 0x0a, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x61, 0x65, 0x61, 0x5f, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x65, 0x61, 0x5f, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x65, 0x61, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x63, 0x0a, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x66, 0x0a, 0x0f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x84, 0x02, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x74, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x1a, 0x9e, 0x03, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x4c, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x22, 0xad, 0x02, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x49, 0x43, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x04, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0b, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x10, 0x0c, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x44, 0x47, 0x45, 0x52, 0x10, 0x0d, 0x12, 0x1f, 0x0a, 0x1b, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x14, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x15, 0x1a, 0x5b, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x42, 0x0a, 0x1b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0x62, 0x0a, 0x1c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x7b, 0x0a, 0x19, 0x41, 0x65, 0x61, 0x5f, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x54, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x0e, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_acn_proto_rawDescOnce sync.Once file_acn_proto_rawDescData = file_acn_proto_rawDesc ) func file_acn_proto_rawDescGZIP() []byte { file_acn_proto_rawDescOnce.Do(func() { file_acn_proto_rawDescData = protoimpl.X.CompressGZIP(file_acn_proto_rawDescData) }) return file_acn_proto_rawDescData } var file_acn_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_acn_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_acn_proto_goTypes = []interface{}{ (AcnMessage_StatusBody_StatusCodeEnum)(0), // 0: aea.aea.acn.v1_0_0.AcnMessage.StatusBody.StatusCodeEnum (*AcnMessage)(nil), // 1: aea.aea.acn.v1_0_0.AcnMessage (*AcnMessage_AgentRecord)(nil), // 2: aea.aea.acn.v1_0_0.AcnMessage.AgentRecord (*AcnMessage_StatusBody)(nil), // 3: aea.aea.acn.v1_0_0.AcnMessage.StatusBody (*AcnMessage_Register_Performative)(nil), // 4: aea.aea.acn.v1_0_0.AcnMessage.Register_Performative (*AcnMessage_Lookup_Request_Performative)(nil), // 5: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_Performative (*AcnMessage_Lookup_Response_Performative)(nil), // 6: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative (*AcnMessage_Aea_Envelope_Performative)(nil), // 7: aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative (*AcnMessage_Status_Performative)(nil), // 8: aea.aea.acn.v1_0_0.AcnMessage.Status_Performative } var file_acn_proto_depIdxs = []int32{ 7, // 0: aea.aea.acn.v1_0_0.AcnMessage.aea_envelope:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative 5, // 1: aea.aea.acn.v1_0_0.AcnMessage.lookup_request:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_Performative 6, // 2: aea.aea.acn.v1_0_0.AcnMessage.lookup_response:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative 4, // 3: aea.aea.acn.v1_0_0.AcnMessage.register:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Register_Performative 8, // 4: aea.aea.acn.v1_0_0.AcnMessage.status:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Status_Performative 0, // 5: aea.aea.acn.v1_0_0.AcnMessage.StatusBody.code:type_name -> aea.aea.acn.v1_0_0.AcnMessage.StatusBody.StatusCodeEnum 2, // 6: aea.aea.acn.v1_0_0.AcnMessage.Register_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 2, // 7: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 2, // 8: aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 3, // 9: aea.aea.acn.v1_0_0.AcnMessage.Status_Performative.body:type_name -> aea.aea.acn.v1_0_0.AcnMessage.StatusBody 10, // [10:10] is the sub-list for method output_type 10, // [10:10] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_acn_proto_init() } func file_acn_proto_init() { if File_acn_proto != nil { return } if !protoimpl.UnsafeEnabled { file_acn_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_AgentRecord); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_StatusBody); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Register_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Lookup_Request_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Lookup_Response_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Aea_Envelope_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Status_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_acn_proto_msgTypes[0].OneofWrappers = []interface{}{ (*AcnMessage_AeaEnvelope)(nil), (*AcnMessage_LookupRequest)(nil), (*AcnMessage_LookupResponse)(nil), (*AcnMessage_Register)(nil), (*AcnMessage_Status)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_acn_proto_rawDesc, NumEnums: 1, NumMessages: 8, NumExtensions: 0, NumServices: 0, }, GoTypes: file_acn_proto_goTypes, DependencyIndexes: file_acn_proto_depIdxs, EnumInfos: file_acn_proto_enumTypes, MessageInfos: file_acn_proto_msgTypes, }.Build() File_acn_proto = out.File file_acn_proto_rawDesc = nil file_acn_proto_goTypes = nil file_acn_proto_depIdxs = nil } ================================================ FILE: libs/go/aealite/protocols/acn/v1_0_0/acn.proto ================================================ syntax = "proto3"; package aea.aea.acn.v1_0_0; option go_package = "libp2p_node/protocols/acn/v1_0_0"; message AcnMessage{ // Custom Types message AgentRecord{ string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; } message StatusBody{ enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; } // Performatives and contents message Register_Performative{ AgentRecord record = 1; } message Lookup_Request_Performative{ string agent_address = 1; } message Lookup_Response_Performative{ AgentRecord record = 1; } message Aea_Envelope_Performative{ bytes envelope = 1; AgentRecord record = 2; } message Status_Performative{ StatusBody body = 1; } oneof performative{ Aea_Envelope_Performative aea_envelope = 5; Lookup_Request_Performative lookup_request = 6; Lookup_Response_Performative lookup_response = 7; Register_Performative register = 8; Status_Performative status = 9; } } ================================================ FILE: libs/go/aealite/protocols/acn/v1_0_0/acn.yaml ================================================ --- name: acn author: fetchai version: 1.0.0 description: The protocol used for envelope delivery on the ACN. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: aea/acn:1.0.0 speech_acts: register: record: ct:AgentRecord lookup_request: agent_address: pt:str lookup_response: record: ct:AgentRecord aea_envelope: envelope: pt:bytes record: ct:AgentRecord status: body: ct:StatusBody ... --- ct:AgentRecord: string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; ct:StatusBody: | enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; ... --- initiation: [register, lookup_request, aea_envelope] reply: register: [status] lookup_request: [lookup_response, status] aea_envelope: [status] status: [] lookup_response: [] termination: [status, lookup_response] roles: {node} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ================================================ FILE: libs/go/aealite/protocols/base.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.11.4 // source: base.proto package protocols import ( proto "github.com/golang/protobuf/proto" _struct "github.com/golang/protobuf/ptypes/struct" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type DialogueMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields MessageId int32 `protobuf:"varint,1,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` DialogueStarterReference string `protobuf:"bytes,2,opt,name=dialogue_starter_reference,json=dialogueStarterReference,proto3" json:"dialogue_starter_reference,omitempty"` DialogueResponderReference string `protobuf:"bytes,3,opt,name=dialogue_responder_reference,json=dialogueResponderReference,proto3" json:"dialogue_responder_reference,omitempty"` Target int32 `protobuf:"varint,4,opt,name=target,proto3" json:"target,omitempty"` Content []byte `protobuf:"bytes,5,opt,name=content,proto3" json:"content,omitempty"` } func (x *DialogueMessage) Reset() { *x = DialogueMessage{} if protoimpl.UnsafeEnabled { mi := &file_base_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DialogueMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*DialogueMessage) ProtoMessage() {} func (x *DialogueMessage) ProtoReflect() protoreflect.Message { mi := &file_base_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DialogueMessage.ProtoReflect.Descriptor instead. func (*DialogueMessage) Descriptor() ([]byte, []int) { return file_base_proto_rawDescGZIP(), []int{0} } func (x *DialogueMessage) GetMessageId() int32 { if x != nil { return x.MessageId } return 0 } func (x *DialogueMessage) GetDialogueStarterReference() string { if x != nil { return x.DialogueStarterReference } return "" } func (x *DialogueMessage) GetDialogueResponderReference() string { if x != nil { return x.DialogueResponderReference } return "" } func (x *DialogueMessage) GetTarget() int32 { if x != nil { return x.Target } return 0 } func (x *DialogueMessage) GetContent() []byte { if x != nil { return x.Content } return nil } type Message struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Message: // *Message_Body // *Message_DialogueMessage Message isMessage_Message `protobuf_oneof:"message"` } func (x *Message) Reset() { *x = Message{} if protoimpl.UnsafeEnabled { mi := &file_base_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Message) String() string { return protoimpl.X.MessageStringOf(x) } func (*Message) ProtoMessage() {} func (x *Message) ProtoReflect() protoreflect.Message { mi := &file_base_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Message.ProtoReflect.Descriptor instead. func (*Message) Descriptor() ([]byte, []int) { return file_base_proto_rawDescGZIP(), []int{1} } func (m *Message) GetMessage() isMessage_Message { if m != nil { return m.Message } return nil } func (x *Message) GetBody() *_struct.Struct { if x, ok := x.GetMessage().(*Message_Body); ok { return x.Body } return nil } func (x *Message) GetDialogueMessage() *DialogueMessage { if x, ok := x.GetMessage().(*Message_DialogueMessage); ok { return x.DialogueMessage } return nil } type isMessage_Message interface { isMessage_Message() } type Message_Body struct { Body *_struct.Struct `protobuf:"bytes,1,opt,name=body,proto3,oneof"` } type Message_DialogueMessage struct { DialogueMessage *DialogueMessage `protobuf:"bytes,2,opt,name=dialogue_message,json=dialogueMessage,proto3,oneof"` } func (*Message_Body) isMessage_Message() {} func (*Message_DialogueMessage) isMessage_Message() {} type Envelope struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields To string `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` ProtocolId string `protobuf:"bytes,3,opt,name=protocol_id,json=protocolId,proto3" json:"protocol_id,omitempty"` Message []byte `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` Uri string `protobuf:"bytes,5,opt,name=uri,proto3" json:"uri,omitempty"` } func (x *Envelope) Reset() { *x = Envelope{} if protoimpl.UnsafeEnabled { mi := &file_base_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Envelope) String() string { return protoimpl.X.MessageStringOf(x) } func (*Envelope) ProtoMessage() {} func (x *Envelope) ProtoReflect() protoreflect.Message { mi := &file_base_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Envelope.ProtoReflect.Descriptor instead. func (*Envelope) Descriptor() ([]byte, []int) { return file_base_proto_rawDescGZIP(), []int{2} } func (x *Envelope) GetTo() string { if x != nil { return x.To } return "" } func (x *Envelope) GetSender() string { if x != nil { return x.Sender } return "" } func (x *Envelope) GetProtocolId() string { if x != nil { return x.ProtocolId } return "" } func (x *Envelope) GetMessage() []byte { if x != nil { return x.Message } return nil } func (x *Envelope) GetUri() string { if x != nil { return x.Uri } return "" } var File_base_proto protoreflect.FileDescriptor var file_base_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x61, 0x65, 0x61, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe2, 0x01, 0x0a, 0x0f, 0x44, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x64, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x64, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x40, 0x0a, 0x1c, 0x64, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x64, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x92, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x48, 0x00, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x4d, 0x0a, 0x10, 0x64, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x44, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x0f, 0x64, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x75, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x7f, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x42, 0x13, 0x5a, 0x11, 0x61, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_base_proto_rawDescOnce sync.Once file_base_proto_rawDescData = file_base_proto_rawDesc ) func file_base_proto_rawDescGZIP() []byte { file_base_proto_rawDescOnce.Do(func() { file_base_proto_rawDescData = protoimpl.X.CompressGZIP(file_base_proto_rawDescData) }) return file_base_proto_rawDescData } var file_base_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_base_proto_goTypes = []interface{}{ (*DialogueMessage)(nil), // 0: aea.base.v0_1_0.DialogueMessage (*Message)(nil), // 1: aea.base.v0_1_0.Message (*Envelope)(nil), // 2: aea.base.v0_1_0.Envelope (*_struct.Struct)(nil), // 3: google.protobuf.Struct } var file_base_proto_depIdxs = []int32{ 3, // 0: aea.base.v0_1_0.Message.body:type_name -> google.protobuf.Struct 0, // 1: aea.base.v0_1_0.Message.dialogue_message:type_name -> aea.base.v0_1_0.DialogueMessage 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_base_proto_init() } func file_base_proto_init() { if File_base_proto != nil { return } if !protoimpl.UnsafeEnabled { file_base_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DialogueMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_base_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Message); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_base_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Envelope); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_base_proto_msgTypes[1].OneofWrappers = []interface{}{ (*Message_Body)(nil), (*Message_DialogueMessage)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_base_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_base_proto_goTypes, DependencyIndexes: file_base_proto_depIdxs, MessageInfos: file_base_proto_msgTypes, }.Build() File_base_proto = out.File file_base_proto_rawDesc = nil file_base_proto_goTypes = nil file_base_proto_depIdxs = nil } ================================================ FILE: libs/go/aealite/protocols/base.proto ================================================ syntax = "proto3"; package aea.base.v0_1_0; option go_package = "aealite/protocols"; import "google/protobuf/struct.proto"; message DialogueMessage { int32 message_id = 1; string dialogue_starter_reference = 2; string dialogue_responder_reference = 3; int32 target = 4; bytes content = 5; } message Message { oneof message { google.protobuf.Struct body = 1; DialogueMessage dialogue_message = 2; } } message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; bytes message = 4; string uri = 5; } ================================================ FILE: libs/go/aealite/protocols/dialogue.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "aealite/helpers" "errors" "fmt" ) type Role string type EndStates string const ( NonceBytesNb = 32 Role1 Role = "role1" Role2 Role = "role2" StartingMessageId MessageId = 1 StartingTarget MessageId = 0 UnassignedDialogueReference = "" DialogueLabelStringSeparator = "_" ) /* Utility methods */ func max(list []MessageId) MessageId { max := list[0] for i := 1; i < len(list); i++ { if max < list[i] { max = list[i] } } return max } func abs(id MessageId) MessageId { if id < 0 { return -id } return id } /* Definition of main data types */ type Rules struct { initialPerformatives helpers.Set terminalPerformatives helpers.Set validReplies map[Performative]helpers.Set } func NewRules(initialPerformatives []Performative, terminalPerformatives []Performative, validReplies map[Performative][]Performative) Rules { initialPerformativesSet := helpers.NewSet() for _, initialPeformative := range initialPerformatives { initialPerformativesSet.Add(initialPeformative) } terminalPerformativesSet := helpers.NewSet() for _, terminalPerformative := range terminalPerformatives { terminalPerformativesSet.Add(terminalPerformative) } validRepliesMap := make(map[Performative]helpers.Set) for performative, validPerformatives := range validReplies { set := helpers.NewSet() for _, validPerformative := range validPerformatives { set.Add(validPerformative) } validRepliesMap[performative] = set } rules := Rules{ initialPerformatives: initialPerformativesSet, terminalPerformatives: terminalPerformativesSet, validReplies: validRepliesMap, } return rules } type DialogueInterface interface { // getters DialogueLabel() DialogueLabel IncompleteDialogueLabel() DialogueLabel DialogueLabels() [2]DialogueLabel SelfAddress() Address Role() Role Rules() Rules getMessageClass() *ProtocolMessageInterface IsSelfInitiated() bool LastIncomingMessage() *ProtocolMessageInterface LastOutgoingMessage() *ProtocolMessageInterface LastMessage() *ProtocolMessageInterface IsEmpty() bool Reply( Performative, ProtocolMessageInterface, MessageId, ) (ProtocolMessageInterface, error) String() string counterPartyFromMessage(*ProtocolMessageInterface) Address isMessageBySelf(*ProtocolMessageInterface) bool isMessageByOther(*ProtocolMessageInterface) bool hasMessageId(MessageId) bool update(*ProtocolMessageInterface) isBelongingToDialogue(*ProtocolMessageInterface) bool validateNextMessage(*ProtocolMessageInterface) error basicValidations(*ProtocolMessageInterface) error basicValidationInitialMessage(*ProtocolMessageInterface) error basicValidationNonInitialMessage(*ProtocolMessageInterface) error validateMessageTarget(*ProtocolMessageInterface) string validateMessageId(*ProtocolMessageInterface) string getMessageById(MessageId) *ProtocolMessageInterface getOutgoingNextMessageId() MessageId getIncomingNextMessageId() MessageId updateDialogueLabel(DialogueLabel) error customValidation(*ProtocolMessageInterface) error } /* Dialogue definition and methods */ type Dialogue struct { dialogueLabel DialogueLabel // dialogueLabel: the dialogue label for this dialogue role Role // role: the role of the agent this dialogue is maintained for selfAddress Address // selfAddress: the address of the entity for whom this dialogue is maintained outgoingMessages []ProtocolMessageInterface // outgoingMessages: list of outgoing messages incomingMessages []ProtocolMessageInterface // incomingMessages: list of incoming messages lastMessageId MessageId // lastMessageId: the last message id for this dialogue. orderedMessageIds []MessageId // orderedMessageIds: the ordered message ids. rules Rules // rules: the rules for this dialogue terminalStateCallbacks []func(*Dialogue) // terminalStateCallbacks: the callbacks to be called when the dialogue reaches a terminal state. } // DialogueLabel return the dialogue label. func (dialogue *Dialogue) DialogueLabel() DialogueLabel { return dialogue.dialogueLabel } // IncompleteDialogueLabel return the incomplete dialogue label. func (dialogue *Dialogue) IncompleteDialogueLabel() DialogueLabel { return dialogue.dialogueLabel.IncompleteVersion() } // DialogueLabels get the dialogue labels (incomplete and complete, if it exists). func (dialogue *Dialogue) DialogueLabels() [2]DialogueLabel { return [2]DialogueLabel{dialogue.dialogueLabel, dialogue.IncompleteDialogueLabel()} } // SelfAddress get the address of the entity for whom this dialogues is maintained. func (dialogue *Dialogue) SelfAddress() Address { return dialogue.selfAddress } // Role get the agent's role in the dialogue. func (dialogue *Dialogue) Role() Role { return dialogue.role } // Rules get the dialogue rules. func (dialogue *Dialogue) Rules() Rules { return dialogue.rules } func (dialogue *Dialogue) AddTerminalStateCallback(fn func(*Dialogue)) { dialogue.terminalStateCallbacks = append(dialogue.terminalStateCallbacks, fn) } // IsSelfInitiated Check whether the agent initiated the dialogue. func (dialogue *Dialogue) IsSelfInitiated() bool { return dialogue.dialogueLabel.dialogueStarterAddress != dialogue.dialogueLabel.dialogueOpponentAddress } func (dialogue *Dialogue) LastIncomingMessage() ProtocolMessageInterface { if length := len(dialogue.incomingMessages); length > 0 { return dialogue.incomingMessages[length-1] } return nil } func (dialogue *Dialogue) LastOutgoingMessage() ProtocolMessageInterface { if length := len(dialogue.outgoingMessages); length > 0 { return dialogue.outgoingMessages[length-1] } return nil } func (dialogue *Dialogue) LastMessage() ProtocolMessageInterface { // check if message id is unset if dialogue.lastMessageId == 0 { return nil } lastIncomingMessage := dialogue.LastIncomingMessage() if lastIncomingMessage != nil && lastIncomingMessage.MessageId() == dialogue.lastMessageId { return lastIncomingMessage } return dialogue.LastOutgoingMessage() } func (dialogue *Dialogue) isEmpty() bool { return len(dialogue.outgoingMessages) == 0 && len(dialogue.incomingMessages) == 0 } func (dialogue *Dialogue) counterPartyFromMessage(message ProtocolMessageInterface) Address { if dialogue.isMessageBySelf(message) { return message.To() } return message.Sender() } func (dialogue *Dialogue) isMessageBySelf(message ProtocolMessageInterface) bool { return message.Sender() == dialogue.selfAddress } func (dialogue *Dialogue) hasMessageId(messageId MessageId) bool { msg := dialogue.getMessageById(messageId) return msg != nil } func (dialogue *Dialogue) update(message ProtocolMessageInterface) error { if !(message).HasSender() { // the error is safe to ignore thanks to the above check _ = (message).SetSender(dialogue.selfAddress) } isBelongingToDialogue := dialogue.isBelongingToDialogue(message) if !isBelongingToDialogue { return errors.New("message does not belong to this dialogue") } if err := dialogue.validateNextMessage(message); err != nil { return err } if dialogue.isMessageBySelf(message) { dialogue.outgoingMessages = append(dialogue.outgoingMessages, message) } else { dialogue.incomingMessages = append(dialogue.incomingMessages, message) } // update last message id dialogue.lastMessageId = message.MessageId() // append message ids in ordered manner dialogue.orderedMessageIds = append(dialogue.orderedMessageIds, message.MessageId()) performative := message.Performative() if dialogue.rules.terminalPerformatives.Contains(performative) { for _, fn := range dialogue.terminalStateCallbacks { fn(dialogue) } } return nil } func (dialogue *Dialogue) isBelongingToDialogue(message ProtocolMessageInterface) bool { opponent := dialogue.counterPartyFromMessage(message) var label DialogueLabel if dialogue.IsSelfInitiated() { label = DialogueLabel{ dialogueReference: DialogueReference{ message.DialogueReference().dialogueStarterReference, UnassignedDialogueReference, }, dialogueOpponentAddress: opponent, dialogueStarterAddress: dialogue.selfAddress, } } else { label = DialogueLabel{ dialogueReference: message.DialogueReference(), dialogueOpponentAddress: opponent, dialogueStarterAddress: opponent, } } result := dialogue.checkLabelBelongsToDialogue(label) return result } func (dialogue *Dialogue) Reply( performative Performative, targetMessage ProtocolMessageInterface, targetPtr *MessageId, ) (ProtocolMessageInterface, error) { lastMessage := dialogue.LastMessage() if lastMessage == nil { return nil, errors.New("cannot reply in an empty dialogue") } var target MessageId msgIsNone := targetMessage == nil targetIsNone := targetPtr == nil if msgIsNone && !targetIsNone { target = *targetPtr targetMessage = dialogue.getMessageById(*targetPtr) } else if msgIsNone && targetIsNone { targetMessage = lastMessage target = lastMessage.MessageId() } else if !msgIsNone && targetIsNone { target = targetMessage.MessageId() } else if !msgIsNone && !targetIsNone { target = *targetPtr if target != targetMessage.MessageId() { return nil, errors.New("the provided target and target_message do not match") } } if targetMessage == nil { return nil, errors.New("no target message found") } if !dialogue.hasMessageId(target) { return nil, errors.New("the target message does not exist in this dialogue") } reply := DialogueMessageWrapper{ dialogueReference: dialogue.dialogueLabel.dialogueReference, messageId: dialogue.getOutgoingNextMessageId(), sender: dialogue.selfAddress, to: dialogue.dialogueLabel.dialogueOpponentAddress, target: target, performative: performative, } err := dialogue.update(&reply) if err != nil { return nil, err } return &reply, nil } func (dialogue *Dialogue) validateNextMessage(message ProtocolMessageInterface) error { err := dialogue.basicValidation(message) if err != nil { return err } // check if custom validation return nil } func (dialogue *Dialogue) checkLabelBelongsToDialogue(label DialogueLabel) bool { return label == dialogue.dialogueLabel || label == dialogue.dialogueLabel.IncompleteVersion() } func (dialogue *Dialogue) basicValidation(message ProtocolMessageInterface) error { if dialogue.isEmpty() { return dialogue.basicValidationInitialMessage(message) } return dialogue.basicValidationNonInitialMessage(message) } func (dialogue *Dialogue) basicValidationInitialMessage( message ProtocolMessageInterface, ) error { dialogueReference := message.DialogueReference() messageId := message.MessageId() performative := message.Performative() expectedReference := dialogue.dialogueLabel.dialogueReference.dialogueStarterReference actualReference := dialogueReference.dialogueStarterReference if expectedReference != actualReference { return fmt.Errorf( "invalid dialogue_reference.dialogueStarterReference: expected %s, found %s", expectedReference, actualReference, ) } if messageId != StartingMessageId { return fmt.Errorf("invalid message id: expected %v, found %v", StartingMessageId, messageId) } err := dialogue.validateMessageTarget(message) if err != nil { return err } //check if performative exists in initial performatives if !dialogue.rules.initialPerformatives.Contains(performative) { return errors.New("invalid initial performative") } // The initial message passes basic validation -> no errors return nil } func (dialogue *Dialogue) basicValidationNonInitialMessage( message ProtocolMessageInterface, ) error { dialogueReference := message.DialogueReference() expectedReference := dialogue.dialogueLabel.dialogueReference.dialogueStarterReference actualReference := dialogueReference.dialogueStarterReference if expectedReference != actualReference { return fmt.Errorf( "invalid dialogue_reference.dialogueStarterReference: expected %s, found %s", expectedReference, actualReference, ) } err := dialogue.validateMessageId(message) if err != nil { return err } err = dialogue.validateMessageTarget(message) if err != nil { return err } // The non-initial message passes basic validation. return nil } func (dialogue *Dialogue) validateMessageTarget(message ProtocolMessageInterface) error { target := message.Target() performative := message.Performative() if message.MessageId() == StartingMessageId { if target == StartingTarget { return nil } return fmt.Errorf("invalid target: expected 0, found %v", target) } if message.MessageId() != StartingMessageId && target == StartingTarget { return fmt.Errorf("invalid target: expected a non-zero integer, found %v", target) } var latestIds []MessageId var lastIncomingMessage = dialogue.LastIncomingMessage() if lastIncomingMessage != nil { latestIds = append(latestIds, abs(lastIncomingMessage.MessageId())) } var lastOutgoingMessage = dialogue.LastOutgoingMessage() if lastOutgoingMessage != nil { latestIds = append(latestIds, abs(lastOutgoingMessage.MessageId())) } if absoluteTarget, maxLatestIds := abs(target), max(latestIds); absoluteTarget > maxLatestIds { return fmt.Errorf("invalid target: expected a value less than or equal to %v. Found %v", maxLatestIds, absoluteTarget) } targetMessage := dialogue.getMessageById(target) if targetMessage == nil { return fmt.Errorf("invalid target %v: target message can not be found.", target) } targetPerformative := targetMessage.Performative() // check performatives setValidReplies := dialogue.rules.validReplies[targetPerformative] if !setValidReplies.Contains(performative) { return fmt.Errorf("invalid performative: '%s' is not a valid reply", performative) } return nil } func (dialogue *Dialogue) validateMessageId(message ProtocolMessageInterface) error { var nextMessageId MessageId isOutgoing := message.To() != dialogue.selfAddress if isOutgoing { nextMessageId = dialogue.getOutgoingNextMessageId() } else { nextMessageId = dialogue.getIncomingNextMessageId() } if actual := message.MessageId(); actual != nextMessageId { return fmt.Errorf("invalid message id: expected %v, found %v", nextMessageId, actual) } return nil } func (dialogue *Dialogue) getMessageById(messageId MessageId) ProtocolMessageInterface { if dialogue.isEmpty() { return nil } if messageId == 0 { // message id == 0 is invalid return nil } var messagesList []ProtocolMessageInterface if (messageId > 0) == dialogue.IsSelfInitiated() { messagesList = dialogue.outgoingMessages } else { messagesList = dialogue.incomingMessages } if len(messagesList) == 0 { return nil } absoluteMessageId := abs(messageId) absoluteLastMessageId := abs(messagesList[len(messagesList)-1].MessageId()) if absoluteMessageId > absoluteLastMessageId { return nil } return messagesList[absoluteMessageId-1] } func (dialogue *Dialogue) getOutgoingNextMessageId() MessageId { nextMessageId := StartingMessageId if dialogue.LastOutgoingMessage() != nil { nextMessageId = abs(dialogue.LastOutgoingMessage().MessageId()) + 1 } if !dialogue.IsSelfInitiated() { nextMessageId = 0 - nextMessageId } return nextMessageId } func (dialogue *Dialogue) getIncomingNextMessageId() MessageId { nextMessageId := StartingMessageId if dialogue.LastIncomingMessage() != nil { nextMessageId = abs(dialogue.lastMessageId) + 1 } if dialogue.IsSelfInitiated() { nextMessageId = 0 - nextMessageId } return nextMessageId } func (dialogue *Dialogue) updateDialogueLabel(finalDialogueLabel DialogueLabel) error { if dialogue.dialogueLabel.DialogueResponderReference() == UnassignedDialogueReference && finalDialogueLabel.DialogueResponderReference() == UnassignedDialogueReference { return errors.New("dialogue label cannot be updated") } dialogue.dialogueLabel = finalDialogueLabel return nil } func NewDialogue(dialogueLabel DialogueLabel, selfAddress Address, role Role, initialPerformatives []Performative, terminalPerformatives []Performative, validReplies map[Performative][]Performative) Dialogue { dialogue := Dialogue{ dialogueLabel: dialogueLabel, selfAddress: selfAddress, role: role, } dialogue.incomingMessages = make([]ProtocolMessageInterface, 0) dialogue.outgoingMessages = make([]ProtocolMessageInterface, 0) dialogue.orderedMessageIds = make([]MessageId, 0) dialogue.rules = NewRules(initialPerformatives, terminalPerformatives, validReplies) dialogue.terminalStateCallbacks = make([]func(*Dialogue), 0) return dialogue } ================================================ FILE: libs/go/aealite/protocols/dialogue_label.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "bytes" "encoding/json" "fmt" "strings" ) type DialogueReference struct { dialogueStarterReference string dialogueResponderReference string } func (dialogueReference *DialogueReference) DialogueStarterReference() string { return dialogueReference.dialogueStarterReference } func (dialogueReference *DialogueReference) DialogueResponderReference() string { return dialogueReference.dialogueResponderReference } type DialogueLabel struct { dialogueReference DialogueReference dialogueOpponentAddress Address dialogueStarterAddress Address } // DialogueReference Get the dialogue reference. func (dialogueLabel *DialogueLabel) DialogueReference() DialogueReference { return dialogueLabel.dialogueReference } // DialogueStarterReference Get the dialogue starter reference. func (dialogueLabel *DialogueLabel) DialogueStarterReference() string { return dialogueLabel.dialogueReference.DialogueStarterReference() } // DialogueResponderReference Get the dialogue responder reference. func (dialogueLabel *DialogueLabel) DialogueResponderReference() string { return dialogueLabel.dialogueReference.DialogueResponderReference() } // IsSelfInitiated Check whether the agent initiated the dialogue. func (dialogueLabel *DialogueLabel) IsSelfInitiated() bool { return dialogueLabel.dialogueStarterAddress != dialogueLabel.dialogueOpponentAddress } // DialogueOpponentAddress Get the dialogue opponent address. func (dialogueLabel *DialogueLabel) DialogueOpponentAddress() Address { return dialogueLabel.dialogueOpponentAddress } // DialogueStarterAddress Get the dialogue starter address. func (dialogueLabel *DialogueLabel) DialogueStarterAddress() Address { return dialogueLabel.dialogueStarterAddress } // IncompleteVersion Get the incomplete version of the label. func (dialogueLabel *DialogueLabel) IncompleteVersion() DialogueLabel { return DialogueLabel{ DialogueReference{dialogueLabel.DialogueStarterReference(), UnassignedDialogueReference}, dialogueLabel.dialogueOpponentAddress, dialogueLabel.dialogueStarterAddress, } } // MarshalJSON custom DialogueLabel JSON serializer func (dialogueLabel DialogueLabel) MarshalJSON() ([]byte, error) { data := map[string]string{ "dialogue_starter_reference": dialogueLabel.DialogueStarterReference(), "dialogue_responder_reference": dialogueLabel.DialogueResponderReference(), "dialogue_opponent_addr": string(dialogueLabel.DialogueOpponentAddress()), "dialogue_starter_addr": string(dialogueLabel.DialogueStarterAddress()), } buffer := bytes.NewBufferString("{") for key, value := range data { buffer.WriteString(fmt.Sprintf("\"%s\": \"%s\",", key, value)) } buffer.Truncate(buffer.Len() - 1) buffer.WriteString("}") return buffer.Bytes(), nil } // UnmarshalJSON custom DialogueLabel JSON deserializer func (dialogueLabel *DialogueLabel) UnmarshalJSON(b []byte) error { var data map[string]string err := json.Unmarshal(b, &data) if err != nil { return err } starterReference := data["dialogue_starter_reference"] responderReference := data["dialogue_responder_reference"] dialogueLabel.dialogueReference = DialogueReference{starterReference, responderReference} dialogueLabel.dialogueOpponentAddress = Address(data["dialogue_opponent_addr"]) dialogueLabel.dialogueStarterAddress = Address(data["dialogue_starter_addr"]) return nil } // String transform DialogueLabel to its string representation func (dialogueLabel *DialogueLabel) String() string { return strings.Join([]string{dialogueLabel.DialogueStarterReference(), dialogueLabel.DialogueResponderReference(), string(dialogueLabel.dialogueOpponentAddress), string(dialogueLabel.dialogueStarterAddress)}, DialogueLabelStringSeparator) } // FromString update a DialogueLabel from a string representation func (dialogueLabel *DialogueLabel) FromString(s string) error { result := strings.Split(s, DialogueLabelStringSeparator) if length := len(result); length != 4 { return fmt.Errorf("expected exactly 4 parts, got %d", length) } dialogueLabel.dialogueReference = DialogueReference{result[0], result[1]} dialogueLabel.dialogueOpponentAddress = Address(result[2]) dialogueLabel.dialogueStarterAddress = Address(result[3]) return nil } ================================================ FILE: libs/go/aealite/protocols/dialogue_label_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "encoding/json" "testing" "gotest.tools/assert" ) const ( senderAddress Address = "ba6b08b13043e83a962a3a5eeaad3b6c" counterPartyAddress Address = "1ba5cb6f46f426a27ec53064032419f1" starterReference string = "starterReference" responderReference string = "responderReference" ) // get a default dialogue label for testing purposes func getTestDialogueLabel() DialogueLabel { return DialogueLabel{ DialogueReference{starterReference, responderReference}, counterPartyAddress, senderAddress, } } // Test DialogueLabel initialization and getters func TestDialogueLabelGetters(t *testing.T) { starterReference := "starterReference" responderReference := "responderReference" dialogueReference := DialogueReference{starterReference, responderReference} label := DialogueLabel{ dialogueReference, counterPartyAddress, senderAddress, } assert.Equal(t, label.DialogueOpponentAddress(), counterPartyAddress) assert.Equal(t, label.DialogueStarterAddress(), senderAddress) assert.Equal(t, label.DialogueStarterReference(), starterReference) assert.Equal(t, label.DialogueResponderReference(), responderReference) assert.Equal(t, label.DialogueReference(), dialogueReference) } // Test getIncompleteVersion function func TestGetIncompleteVersion(t *testing.T) { label := getTestDialogueLabel() actualIncompleteVersion := label.IncompleteVersion() expectedIncompleteVersion := DialogueLabel{ DialogueReference{label.DialogueStarterReference(), UnassignedDialogueReference}, label.DialogueOpponentAddress(), label.DialogueStarterAddress(), } assert.Equal( t, actualIncompleteVersion, expectedIncompleteVersion, "getIncompleteVersion gave unexpected result.", ) } // Test marshalling and unmarshalling func TestMarshalAndUnmarshal(t *testing.T) { label := getTestDialogueLabel() data, err := json.Marshal(label) if err != nil { t.Fatalf("DialogueLabel JSON marshalling failed with error: %s", err.Error()) } result := DialogueLabel{} err = json.Unmarshal(data, &result) if err != nil { t.Fatalf("DialogueLabel JSON unmarshalling failed with error: %s", err.Error()) } assert.Equal( t, result, label, "the DialogueLabel parsed from JSON is not the same of the original one.", ) } // Test ToString and FromString methods. func TestToStringAndFromString(t *testing.T) { label := getTestDialogueLabel() result := DialogueLabel{} err := result.FromString(label.String()) if err != nil { t.Fatalf("Cannot parse string: %s", err.Error()) } if label != result { t.Fatal("the DialogueLabel parsed from string is not the same of the original one.") } } ================================================ FILE: libs/go/aealite/protocols/dialogue_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "reflect" "testing" ) func TestDialogue(t *testing.T) { label := getTestDialogueLabel() initialPerformatives := []Performative{"start"} terminalPerformatives := []Performative{"end"} validReplies := map[Performative][]Performative{"start": {"end"}} rules := NewRules(initialPerformatives, terminalPerformatives, validReplies) dialogue := NewDialogue( label, senderAddress, Role1, initialPerformatives, terminalPerformatives, validReplies, ) // test getters if dialogue.DialogueLabel() != label { t.Fatalf("unexpected return value of DialogueLabel()") } if dialogue.IncompleteDialogueLabel() != label.IncompleteVersion() { t.Fatalf("unexpected return value of IncompleteDialogueLabel()") } if dialogue.DialogueLabels() != [2]DialogueLabel{label, label.IncompleteVersion()} { t.Fatalf("unexpected return value of DialogueLabels()") } if dialogue.SelfAddress() != senderAddress { t.Fatalf("unexpected return value of SelfAddress()") } if dialogue.Role() != Role1 { t.Fatalf("unexpected return value of Role()") } if !reflect.DeepEqual(dialogue.Rules(), rules) { t.Fatalf("unexpected return value of Rules()") } if dialogue.LastIncomingMessage() != nil { t.Fatalf("unexpected return value of LastIncomingMessage(): the dialogue should be empty") } if dialogue.LastOutgoingMessage() != nil { t.Fatalf("unexpected return value of LastOutgoingMessage(): the dialogue should be empty") } if dialogue.LastMessage() != nil { t.Fatalf("unexpected return value of LastMessage(): the dialogue should be empty") } } ////func TestDialogue(t *testing.T) { // var performative Performative = "sample_performative" // // createing initital dialogue instance // message, dialogue := Create( // counterPartyAddress, // senderAddress, // performative, // []byte("initial message"), // ) // // cheking if message returned has a sender same as senderAddress // if address, err := message.HasSender(); err != nil { // log.Fatal(err) // } else { // if address != senderAddress { // log.Fatal("Error: Sender address invalid.", address, " ", senderAddress) // } // } // // cheking if message returned has a counter party same as counterPartyAddress // if address, err := message.HasCounterparty(); err != nil { // log.Fatal(err) // } else { // if address != counterPartyAddress { // log.Fatal("Error: CounterParty address invalid.") // } // } // // checking if length of outgoing messages list is 1 // if len(dialogue.outgoingMessages) != 1 { // log.Fatal( // "dialogue outgoing messages length is ", // len(dialogue.outgoingMessages), // " should be 1", // ) // } // // checking if length of incoming messages list is 0 // if len(dialogue.incomingMessages) != 0 { // log.Fatal( // "dialogue incoming messages length is ", // len(dialogue.incomingMessages), // " should be 0", // ) // } // if dialogue.IsEmpty() == true { // log.Fatal("dialogue should not be empty") // } // // fetch message id for next mesaage in the dialogue // var nextMessageId MessageId // if dialogue.selfAddress == senderAddress { // nextMessageId = dialogue.getOutgoingNextMessageId() // } else { // nextMessageId = dialogue.getIncomingNextMessageId() // } // // inititlaizing a new message and updating dialogue using it // newMessage := InitializeMessage( // counterPartyAddress, // senderAddress, // performative, // []byte("second message"), // dialogue.dialogueLabel.DialogueReference(), // nextMessageId, // dialogue.lastMessageId, // ) // dialogue.update(newMessage) // // checking if length of outgoing messages list is 2 // if len(dialogue.outgoingMessages) != 2 { // log.Fatal( // "dialogue outgoing messages length is ", // len(dialogue.outgoingMessages), // " should be 2", // ) // } // // checking if length of incoming messages list is 0 // if len(dialogue.incomingMessages) != 0 { // log.Fatal( // "dialogue incoming messages length is ", // len(dialogue.incomingMessages), // " should be 0", // ) // } //} ================================================ FILE: libs/go/aealite/protocols/dialogues.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "aealite/helpers" "crypto/rand" "encoding/hex" "errors" "fmt" ) const ( IncompleteDialogues = "incomplete_dialogues" TerminalDialoguesSuffix = "_terminal" ) /* Utility methods */ func generateDialogueNonce() string { hexValue := randomHex(NonceBytesNb) return hexValue } func randomHex(n int) string { bytes := make([]byte, n) if _, err := rand.Read(bytes); err != nil { return "" } return hex.EncodeToString(bytes) } func newSelfInitiatedDialogueReference() DialogueReference { return DialogueReference{generateDialogueNonce(), UnassignedDialogueReference} } type Dialogues struct { selfAddress Address endStates helpers.Set roleFromFirstMessage func(ProtocolMessageInterface, Address) Role keepTerminalStateDialogues bool dialogueName string dialogueStorage DialogueStorageInterface initialPerformatives []Performative terminalPerformatives []Performative validReplies map[Performative][]Performative } func (dialogues *Dialogues) IsKeepDialoguesInTerminalStates() bool { return dialogues.keepTerminalStateDialogues } func (dialogues *Dialogues) SelfAddress() (Address, error) { if dialogues.selfAddress == "" { return "", errors.New("'self address' is not set") } return dialogues.selfAddress, nil } func (dialogues *Dialogues) GetDialoguesWithCounterparty(counterparty Address) []*Dialogue { return dialogues.dialogueStorage.GetDialoguesWithCounterparty(counterparty) } func (dialogues *Dialogues) isMessageBySelf(message ProtocolMessageInterface) bool { return message.Sender() == dialogues.selfAddress } func (dialogues *Dialogues) isMessageByOther(message ProtocolMessageInterface) bool { return !dialogues.isMessageBySelf(message) } func (dialogues *Dialogues) counterpartyFromMessage(message ProtocolMessageInterface) Address { if dialogues.isMessageBySelf(message) { return message.To() } return message.Sender() } func (dialogues *Dialogues) Create( counterparty Address, performative Performative, body map[string]interface{}, ) (ProtocolMessageInterface, *Dialogue, error) { dialogueReference := newSelfInitiatedDialogueReference() initialMessage := DialogueMessageWrapper{ dialogueReference: dialogueReference, messageId: StartingMessageId, target: StartingTarget, performative: performative, } // safe to ignore errors as the message was just created _ = initialMessage.SetSender(dialogues.selfAddress) _ = initialMessage.SetTo(counterparty) dialogue, err := dialogues.createDialogue(counterparty, &initialMessage) if err != nil { return nil, nil, err } return &initialMessage, dialogue, nil } func (dialogues *Dialogues) CreateWithMessage( counterparty Address, initialMessage ProtocolMessageInterface, ) (*Dialogue, error) { err := initialMessage.SetSender(dialogues.selfAddress) if err != nil { return nil, err } err = initialMessage.SetTo(counterparty) if err != nil { return nil, err } return dialogues.createDialogue(counterparty, initialMessage) } func (dialogues *Dialogues) createDialogue( counterparty Address, initialMessage ProtocolMessageInterface, ) (*Dialogue, error) { dialogue, err := dialogues.createSelfInitiated(counterparty, initialMessage.DialogueReference(), dialogues.roleFromFirstMessage(initialMessage, dialogues.selfAddress)) if err != nil { return nil, err } err = dialogue.update(initialMessage) if err != nil { // if update fails, we don't propagate the error. return nil, nil } return dialogue, nil } func (dialogues *Dialogues) Update(message ProtocolMessageInterface) (*Dialogue, error) { if !(message.HasSender() && dialogues.isMessageByOther(message)) { return nil, errors.New( "invalid update: this method must only be used with a message by another agent", ) } if !message.HasTo() { return nil, errors.New("the message's 'to' field is not set") } if message.To() != dialogues.selfAddress { return nil, fmt.Errorf( "message 'to' and dialogue 'self address' do not match: got 'to=%s' expected 'to=%s'", message.To(), dialogues.selfAddress, ) } dialogueReference := message.DialogueReference() starterRefAssigned := dialogueReference.dialogueStarterReference != UnassignedDialogueReference responderRefAssigned := dialogueReference.dialogueResponderReference != UnassignedDialogueReference isStartingMsgId := message.MessageId() == StartingMessageId isStartingTarget := message.MessageId() == StartingTarget isInvalidLabel := !starterRefAssigned && responderRefAssigned isNewDialogue := starterRefAssigned && !responderRefAssigned && isStartingMsgId isIncompleteLabelAndNotInitialMsg := starterRefAssigned && !responderRefAssigned && !isStartingMsgId && !isStartingTarget var dialogue *Dialogue var err error if isInvalidLabel { dialogue = nil } else if isNewDialogue { dialogue, err = dialogues.createOpponentInitiated(message.Sender(), dialogueReference, dialogues.roleFromFirstMessage(message, dialogues.selfAddress)) if err != nil { // propagate the error return nil, err } } else if isIncompleteLabelAndNotInitialMsg { // we can allow a dialogue to have incomplete reference // as multiple messages can be sent before one is received with complete reference dialogue = dialogues.GetDialogue(message) } else { err = dialogues.completeDialogueReference(message) if err != nil { return nil, err } dialogue = dialogues.GetDialogue(message) } if dialogue != nil { err := dialogue.update(message) if err != nil { // invalid message for the dialogue found if isNewDialogue { // remove the newly created dialogue if the initial message is invalid dialogues.dialogueStorage.RemoveDialogue(dialogue.dialogueLabel) } dialogue = nil return dialogue, err } return dialogue, nil } // couldn't find the dialogue referenced by the message return nil, nil } func (dialogues *Dialogues) completeDialogueReference(message ProtocolMessageInterface) error { completeDialogueReference := message.DialogueReference() starterRef := completeDialogueReference.dialogueStarterReference responderRef := completeDialogueReference.dialogueResponderReference if !(starterRef != UnassignedDialogueReference && responderRef != UnassignedDialogueReference) { return errors.New("only complete dialogue references allowed") } incompleteDialogueReference := DialogueReference{ starterRef, UnassignedDialogueReference, } incompleteDialogueLabel := DialogueLabel{ incompleteDialogueReference, message.Sender(), dialogues.selfAddress, } if dialogues.dialogueStorage.IsDialoguePresent(incompleteDialogueLabel) && !(dialogues.dialogueStorage.IsInIncomplete(incompleteDialogueLabel)) { dialogue := dialogues.dialogueStorage.GetDialogue(incompleteDialogueLabel) if dialogue == nil { return errors.New("dialogue not found") } dialogues.dialogueStorage.RemoveDialogue(incompleteDialogueLabel) finalDialogueLabel := DialogueLabel{ completeDialogueReference, incompleteDialogueLabel.dialogueOpponentAddress, incompleteDialogueLabel.dialogueStarterAddress, } err := dialogue.updateDialogueLabel(finalDialogueLabel) if err != nil { // propagate error return err } dialogues.dialogueStorage.AddDialogue(dialogue) dialogues.dialogueStorage.SetIncompleteDialogue(incompleteDialogueLabel, finalDialogueLabel) } return nil } func (dialogues *Dialogues) GetDialogue(message ProtocolMessageInterface) *Dialogue { counterpartyFromMessage := dialogues.counterpartyFromMessage(message) dialogueReference := message.DialogueReference() selfInitiatedDialogueLabel := DialogueLabel{ dialogueReference, counterpartyFromMessage, dialogues.selfAddress, } otherInitiatedDialogueLabel := DialogueLabel{ dialogueReference, counterpartyFromMessage, counterpartyFromMessage, } selfInitiatedDialogueLabel = dialogues.getLatestLabel(selfInitiatedDialogueLabel) otherInitiatedDialogueLabel = dialogues.getLatestLabel(otherInitiatedDialogueLabel) selfInitiatedDialogue := dialogues.GetDialogueFromLabel(selfInitiatedDialogueLabel) otherInitiatedDialogue := dialogues.GetDialogueFromLabel(otherInitiatedDialogueLabel) if selfInitiatedDialogue != nil { return selfInitiatedDialogue } return otherInitiatedDialogue } func (dialogues *Dialogues) getLatestLabel(label DialogueLabel) DialogueLabel { return dialogues.dialogueStorage.GetLatestLabel(label) } func (dialogues *Dialogues) GetDialogueFromLabel(label DialogueLabel) *Dialogue { return dialogues.dialogueStorage.GetDialogue(label) } func (dialogues *Dialogues) createSelfInitiated( dialogueOpponentAddress Address, dialogueReference DialogueReference, role Role, ) (*Dialogue, error) { starterRef := dialogueReference.DialogueStarterReference() responderRef := dialogueReference.DialogueResponderReference() if !(starterRef != UnassignedDialogueReference && responderRef == UnassignedDialogueReference) { return nil, errors.New( "cannot initiate dialogue with preassigned dialogue_responder_reference", ) } incompleteDialogueLabel := DialogueLabel{ dialogueReference: dialogueReference, dialogueOpponentAddress: dialogueOpponentAddress, dialogueStarterAddress: dialogues.selfAddress, } dialogue, err := dialogues.create(incompleteDialogueLabel, role, nil) if err != nil { return nil, err } return dialogue, nil } func (dialogues *Dialogues) createOpponentInitiated(dialogueOpponentAddress Address, dialogueReference DialogueReference, role Role, ) (*Dialogue, error) { starterRef := dialogueReference.DialogueStarterReference() responderRef := dialogueReference.DialogueResponderReference() if !(starterRef != UnassignedDialogueReference && responderRef == UnassignedDialogueReference) { return nil, errors.New( "cannot initiate dialogue with preassigned dialogue_responder_reference", ) } incompleteDialogueLabel := DialogueLabel{ dialogueReference, dialogueOpponentAddress, dialogueOpponentAddress, } newDialogueReference := DialogueReference{ dialogueReference.dialogueStarterReference, generateDialogueNonce(), } completeDialogueLabel := DialogueLabel{ newDialogueReference, dialogueOpponentAddress, dialogueOpponentAddress, } dialogue, err := dialogues.create(incompleteDialogueLabel, role, &completeDialogueLabel) if err != nil { return nil, err } return dialogue, nil } func (dialogues *Dialogues) create( incompleteDialogueLabel DialogueLabel, role Role, completeDialogueLabel *DialogueLabel, ) (*Dialogue, error) { var dialogueLabel DialogueLabel if dialogues.dialogueStorage.IsInIncomplete(incompleteDialogueLabel) { return nil, errors.New("incomplete dialogue label already present") } if completeDialogueLabel == nil { dialogueLabel = incompleteDialogueLabel } else { copyLabel := *completeDialogueLabel dialogues.dialogueStorage.SetIncompleteDialogue(incompleteDialogueLabel, copyLabel) dialogueLabel = *completeDialogueLabel } if dialogues.dialogueStorage.IsDialoguePresent(dialogueLabel) { return nil, errors.New("dialogue label already present in dialogues") } dialogue := NewDialogue( dialogueLabel, dialogues.selfAddress, role, dialogues.initialPerformatives, dialogues.terminalPerformatives, dialogues.validReplies, ) dialogues.dialogueStorage.AddDialogue(&dialogue) return &dialogue, nil } func NewDialogues( selfAddress Address, roleFromFirstMessage func(ProtocolMessageInterface, Address) Role, keepTerminalStateDialogues bool, dialogueName string, initialPerformatives []Performative, terminalPerformatives []Performative, validReplies map[Performative][]Performative) *Dialogues { endStatesSet := helpers.NewSet() for _, endState := range terminalPerformatives { endStatesSet.Add(endState) } dialogues := Dialogues{ selfAddress: selfAddress, endStates: endStatesSet, roleFromFirstMessage: roleFromFirstMessage, keepTerminalStateDialogues: keepTerminalStateDialogues, dialogueName: dialogueName, initialPerformatives: initialPerformatives, terminalPerformatives: terminalPerformatives, validReplies: validReplies, } storage := NewSimpleDialogueStorage(&dialogues) dialogues.dialogueStorage = storage return &dialogues } ================================================ FILE: libs/go/aealite/protocols/message.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "errors" "log" proto "google.golang.org/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" ) type MessageId int type Address string type Performative string type ProtocolMessageInterface interface { Sender() Address SetSender(Address) error To() Address SetTo(Address) error MessageId() MessageId DialogueReference() DialogueReference Target() MessageId Performative() Performative Body() map[string]interface{} HasSender() bool HasTo() bool GetField(name string) interface{} //ValidPerformatives() []Performative TODO temporarily removed } type DialogueMessageWrapper struct { to Address sender Address dialogueReference DialogueReference messageId MessageId target MessageId performative Performative body map[string]interface{} //validPerformatives helpers.Set TODO understand how to set this } // InitFromProtobufAndPerformative initializes a message from a DialogueMessage protobuf message and a performative. // It unpacks 'message id', 'target' and 'dialogue reference'; moreover, // it decodes the content as a JSON object. // Returns error if: // - the JSON decoding fails // - the body does not contain the 'performative' // It performs side-effect on the method receiver. func (message *DialogueMessageWrapper) InitFromProtobufAndPerfofrmative( dialogueMessage *DialogueMessage, performativeStr string, ) error { message.messageId = MessageId(dialogueMessage.MessageId) message.target = MessageId(dialogueMessage.Target) message.dialogueReference = DialogueReference{ dialogueMessage.DialogueStarterReference, dialogueMessage.DialogueResponderReference, } message.target = MessageId(dialogueMessage.Target) message.performative = Performative(performativeStr) return nil } func (message *DialogueMessageWrapper) Sender() Address { return message.sender } func (message *DialogueMessageWrapper) SetSender(newAddress Address) error { if message.sender != "" { return errors.New("'sender' field already set") } message.sender = newAddress return nil } func (message *DialogueMessageWrapper) To() Address { return message.to } func (message *DialogueMessageWrapper) SetTo(newAddress Address) error { if message.to != "" { return errors.New("'to' field already set") } message.to = newAddress return nil } func (message *DialogueMessageWrapper) MessageId() MessageId { return message.messageId } func (message *DialogueMessageWrapper) DialogueReference() DialogueReference { return message.dialogueReference } func (message *DialogueMessageWrapper) Target() MessageId { return message.target } func (message *DialogueMessageWrapper) Performative() Performative { return message.performative } func (message *DialogueMessageWrapper) Body() map[string]interface{} { return message.body } func (message *DialogueMessageWrapper) HasSender() bool { return message.sender != "" } func (message *DialogueMessageWrapper) HasTo() bool { return message.sender != "" } // GetField returns the value of the field associated with the name // provided in input. If not present, then nil is returned. As we // don't know the type, the callre has to do a type assertion // in order to process the returned value. func (message *DialogueMessageWrapper) GetField(name string) interface{} { return message.body[name] } func GetPerformative(message MessageInterface) (string, error) { performative := "" m := message.ProtoReflect() m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { performative = fd.JSONName() return false }) if performative == "" { return performative, errors.New("can not determine performative") } return performative, nil } type MessageInterface interface { ProtoReflect() protoreflect.Message } func GetDialogueMessageWrappedAndSetContentFromEnvelope( envelope *Envelope, content_message MessageInterface, ) (*DialogueMessageWrapper, error) { data := envelope.GetMessage() message := &Message{} err := proto.Unmarshal(data, message) if err != nil { log.Printf("can not unmarshal message: %s", err) return nil, err } dialogue_message := message.GetDialogueMessage() err = proto.Unmarshal(dialogue_message.GetContent(), content_message) if err != nil { log.Printf("err on decode message content: %s", err) return nil, err } performative, err := GetPerformative(content_message) if err != nil { log.Printf("can not get performative: %s", err) return nil, err } dialogue_message_wrapper := DialogueMessageWrapper{} err = dialogue_message_wrapper.InitFromProtobufAndPerfofrmative(dialogue_message, performative) if err != nil { log.Printf("can not init dialogue wrapper: %s", err) return nil, err } err = dialogue_message_wrapper.SetSender(Address(envelope.GetSender())) if err != nil { log.Printf("can not set Sender: %s", err) return nil, err } err = dialogue_message_wrapper.SetTo(Address(envelope.GetTo())) if err != nil { log.Printf("can not set To: %s", err) return nil, err } return &dialogue_message_wrapper, nil } func MakeResponseEnvelope( wrappedMsgDialogue ProtocolMessageInterface, protocolID string, content []byte, ) (*Envelope, error) { dialogueRef := wrappedMsgDialogue.DialogueReference() message := Message{ Message: &Message_DialogueMessage{ DialogueMessage: &DialogueMessage{ MessageId: int32(wrappedMsgDialogue.MessageId()), DialogueStarterReference: dialogueRef.DialogueStarterReference(), DialogueResponderReference: dialogueRef.DialogueResponderReference(), Target: int32(wrappedMsgDialogue.Target()), Content: content, }, }, } out, err := proto.Marshal(&message) if err != nil { log.Print("marshal dialogue messge failed") return nil, err } env := &Envelope{ To: string(wrappedMsgDialogue.To()), Sender: string(wrappedMsgDialogue.Sender()), ProtocolId: protocolID, Message: out, Uri: "", } return env, nil } ================================================ FILE: libs/go/aealite/protocols/message_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "testing" "gotest.tools/assert" ) const ( DialogueStarterReference = "DialogueStarterReference" DialogueResponderReference = "DialogueResponderReference" ) func TestMessage(t *testing.T) { message := DialogueMessage{} message.MessageId = int32(StartingMessageId) message.DialogueStarterReference = DialogueStarterReference message.DialogueResponderReference = DialogueResponderReference message.Target = int32(StartingTarget) message.Content = []byte(`{"performative": "request", "data": "hello"}`) result := DialogueMessageWrapper{} err := result.InitFromProtobufAndPerfofrmative(&message, "request") if err != nil { t.Fatalf("Error: %s", err.Error()) } assert.Equal(t, result.messageId, StartingMessageId) assert.Equal(t, result.dialogueReference.dialogueStarterReference, DialogueStarterReference) assert.Equal(t, result.dialogueReference.dialogueResponderReference, DialogueResponderReference) assert.Equal(t, result.target, StartingTarget) assert.Equal(t, result.performative, Performative("request")) } ================================================ FILE: libs/go/aealite/protocols/search.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.11.4 // source: search.proto package protocols import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Query_Attribute_Type int32 const ( Query_Attribute_DOUBLE Query_Attribute_Type = 0 Query_Attribute_INT Query_Attribute_Type = 1 Query_Attribute_BOOL Query_Attribute_Type = 2 Query_Attribute_STRING Query_Attribute_Type = 3 Query_Attribute_LOCATION Query_Attribute_Type = 4 ) // Enum value maps for Query_Attribute_Type. var ( Query_Attribute_Type_name = map[int32]string{ 0: "DOUBLE", 1: "INT", 2: "BOOL", 3: "STRING", 4: "LOCATION", } Query_Attribute_Type_value = map[string]int32{ "DOUBLE": 0, "INT": 1, "BOOL": 2, "STRING": 3, "LOCATION": 4, } ) func (x Query_Attribute_Type) Enum() *Query_Attribute_Type { p := new(Query_Attribute_Type) *p = x return p } func (x Query_Attribute_Type) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Query_Attribute_Type) Descriptor() protoreflect.EnumDescriptor { return file_search_proto_enumTypes[0].Descriptor() } func (Query_Attribute_Type) Type() protoreflect.EnumType { return &file_search_proto_enumTypes[0] } func (x Query_Attribute_Type) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Query_Attribute_Type.Descriptor instead. func (Query_Attribute_Type) EnumDescriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 0, 0} } type Query_Relation_Operator int32 const ( Query_Relation_EQ Query_Relation_Operator = 0 // = Query_Relation_LT Query_Relation_Operator = 1 // < Query_Relation_LTEQ Query_Relation_Operator = 2 // <= Query_Relation_GT Query_Relation_Operator = 3 // > Query_Relation_GTEQ Query_Relation_Operator = 4 // >= Query_Relation_NOTEQ Query_Relation_Operator = 5 // !=, <> ) // Enum value maps for Query_Relation_Operator. var ( Query_Relation_Operator_name = map[int32]string{ 0: "EQ", 1: "LT", 2: "LTEQ", 3: "GT", 4: "GTEQ", 5: "NOTEQ", } Query_Relation_Operator_value = map[string]int32{ "EQ": 0, "LT": 1, "LTEQ": 2, "GT": 3, "GTEQ": 4, "NOTEQ": 5, } ) func (x Query_Relation_Operator) Enum() *Query_Relation_Operator { p := new(Query_Relation_Operator) *p = x return p } func (x Query_Relation_Operator) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Query_Relation_Operator) Descriptor() protoreflect.EnumDescriptor { return file_search_proto_enumTypes[1].Descriptor() } func (Query_Relation_Operator) Type() protoreflect.EnumType { return &file_search_proto_enumTypes[1] } func (x Query_Relation_Operator) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Query_Relation_Operator.Descriptor instead. func (Query_Relation_Operator) EnumDescriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 12, 0} } type Query_Set_Operator int32 const ( Query_Set_IN Query_Set_Operator = 0 Query_Set_NOTIN Query_Set_Operator = 1 ) // Enum value maps for Query_Set_Operator. var ( Query_Set_Operator_name = map[int32]string{ 0: "IN", 1: "NOTIN", } Query_Set_Operator_value = map[string]int32{ "IN": 0, "NOTIN": 1, } ) func (x Query_Set_Operator) Enum() *Query_Set_Operator { p := new(Query_Set_Operator) *p = x return p } func (x Query_Set_Operator) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Query_Set_Operator) Descriptor() protoreflect.EnumDescriptor { return file_search_proto_enumTypes[2].Descriptor() } func (Query_Set_Operator) Type() protoreflect.EnumType { return &file_search_proto_enumTypes[2] } func (x Query_Set_Operator) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Query_Set_Operator.Descriptor instead. func (Query_Set_Operator) EnumDescriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13, 0} } type Query struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Query) Reset() { *x = Query{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query) ProtoMessage() {} func (x *Query) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query.ProtoReflect.Descriptor instead. func (*Query) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0} } type Query_Attribute struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Type Query_Attribute_Type `protobuf:"varint,2,opt,name=type,proto3,enum=aea.search.v0_1_0.Query_Attribute_Type" json:"type,omitempty"` Required bool `protobuf:"varint,3,opt,name=required,proto3" json:"required,omitempty"` Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` } func (x *Query_Attribute) Reset() { *x = Query_Attribute{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Attribute) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Attribute) ProtoMessage() {} func (x *Query_Attribute) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Attribute.ProtoReflect.Descriptor instead. func (*Query_Attribute) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 0} } func (x *Query_Attribute) GetName() string { if x != nil { return x.Name } return "" } func (x *Query_Attribute) GetType() Query_Attribute_Type { if x != nil { return x.Type } return Query_Attribute_DOUBLE } func (x *Query_Attribute) GetRequired() bool { if x != nil { return x.Required } return false } func (x *Query_Attribute) GetDescription() string { if x != nil { return x.Description } return "" } type Query_DataModel struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Attributes []*Query_Attribute `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` } func (x *Query_DataModel) Reset() { *x = Query_DataModel{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_DataModel) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_DataModel) ProtoMessage() {} func (x *Query_DataModel) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_DataModel.ProtoReflect.Descriptor instead. func (*Query_DataModel) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 1} } func (x *Query_DataModel) GetName() string { if x != nil { return x.Name } return "" } func (x *Query_DataModel) GetAttributes() []*Query_Attribute { if x != nil { return x.Attributes } return nil } func (x *Query_DataModel) GetDescription() string { if x != nil { return x.Description } return "" } type Query_Location struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Lon float64 `protobuf:"fixed64,1,opt,name=lon,proto3" json:"lon,omitempty"` Lat float64 `protobuf:"fixed64,2,opt,name=lat,proto3" json:"lat,omitempty"` } func (x *Query_Location) Reset() { *x = Query_Location{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Location) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Location) ProtoMessage() {} func (x *Query_Location) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Location.ProtoReflect.Descriptor instead. func (*Query_Location) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 2} } func (x *Query_Location) GetLon() float64 { if x != nil { return x.Lon } return 0 } func (x *Query_Location) GetLat() float64 { if x != nil { return x.Lat } return 0 } type Query_Value struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Value: // *Query_Value_String_ // *Query_Value_Double // *Query_Value_Boolean // *Query_Value_Integer // *Query_Value_Location Value isQuery_Value_Value `protobuf_oneof:"value"` } func (x *Query_Value) Reset() { *x = Query_Value{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Value) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Value) ProtoMessage() {} func (x *Query_Value) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Value.ProtoReflect.Descriptor instead. func (*Query_Value) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 3} } func (m *Query_Value) GetValue() isQuery_Value_Value { if m != nil { return m.Value } return nil } func (x *Query_Value) GetString_() string { if x, ok := x.GetValue().(*Query_Value_String_); ok { return x.String_ } return "" } func (x *Query_Value) GetDouble() float64 { if x, ok := x.GetValue().(*Query_Value_Double); ok { return x.Double } return 0 } func (x *Query_Value) GetBoolean() bool { if x, ok := x.GetValue().(*Query_Value_Boolean); ok { return x.Boolean } return false } func (x *Query_Value) GetInteger() int64 { if x, ok := x.GetValue().(*Query_Value_Integer); ok { return x.Integer } return 0 } func (x *Query_Value) GetLocation() *Query_Location { if x, ok := x.GetValue().(*Query_Value_Location); ok { return x.Location } return nil } type isQuery_Value_Value interface { isQuery_Value_Value() } type Query_Value_String_ struct { String_ string `protobuf:"bytes,1,opt,name=string,proto3,oneof"` } type Query_Value_Double struct { Double float64 `protobuf:"fixed64,2,opt,name=double,proto3,oneof"` } type Query_Value_Boolean struct { Boolean bool `protobuf:"varint,3,opt,name=boolean,proto3,oneof"` } type Query_Value_Integer struct { Integer int64 `protobuf:"varint,4,opt,name=integer,proto3,oneof"` } type Query_Value_Location struct { Location *Query_Location `protobuf:"bytes,5,opt,name=location,proto3,oneof"` } func (*Query_Value_String_) isQuery_Value_Value() {} func (*Query_Value_Double) isQuery_Value_Value() {} func (*Query_Value_Boolean) isQuery_Value_Value() {} func (*Query_Value_Integer) isQuery_Value_Value() {} func (*Query_Value_Location) isQuery_Value_Value() {} type Query_KeyValue struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Value *Query_Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *Query_KeyValue) Reset() { *x = Query_KeyValue{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_KeyValue) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_KeyValue) ProtoMessage() {} func (x *Query_KeyValue) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_KeyValue.ProtoReflect.Descriptor instead. func (*Query_KeyValue) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 4} } func (x *Query_KeyValue) GetKey() string { if x != nil { return x.Key } return "" } func (x *Query_KeyValue) GetValue() *Query_Value { if x != nil { return x.Value } return nil } type Query_Instance struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Model *Query_DataModel `protobuf:"bytes,1,opt,name=model,proto3" json:"model,omitempty"` Values []*Query_KeyValue `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` } func (x *Query_Instance) Reset() { *x = Query_Instance{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Instance) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Instance) ProtoMessage() {} func (x *Query_Instance) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Instance.ProtoReflect.Descriptor instead. func (*Query_Instance) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 5} } func (x *Query_Instance) GetModel() *Query_DataModel { if x != nil { return x.Model } return nil } func (x *Query_Instance) GetValues() []*Query_KeyValue { if x != nil { return x.Values } return nil } type Query_StringPair struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields First string `protobuf:"bytes,1,opt,name=first,proto3" json:"first,omitempty"` Second string `protobuf:"bytes,2,opt,name=second,proto3" json:"second,omitempty"` } func (x *Query_StringPair) Reset() { *x = Query_StringPair{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_StringPair) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_StringPair) ProtoMessage() {} func (x *Query_StringPair) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_StringPair.ProtoReflect.Descriptor instead. func (*Query_StringPair) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 6} } func (x *Query_StringPair) GetFirst() string { if x != nil { return x.First } return "" } func (x *Query_StringPair) GetSecond() string { if x != nil { return x.Second } return "" } type Query_IntPair struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields First int64 `protobuf:"varint,1,opt,name=first,proto3" json:"first,omitempty"` Second int64 `protobuf:"varint,2,opt,name=second,proto3" json:"second,omitempty"` } func (x *Query_IntPair) Reset() { *x = Query_IntPair{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_IntPair) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_IntPair) ProtoMessage() {} func (x *Query_IntPair) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_IntPair.ProtoReflect.Descriptor instead. func (*Query_IntPair) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 7} } func (x *Query_IntPair) GetFirst() int64 { if x != nil { return x.First } return 0 } func (x *Query_IntPair) GetSecond() int64 { if x != nil { return x.Second } return 0 } type Query_DoublePair struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields First float64 `protobuf:"fixed64,1,opt,name=first,proto3" json:"first,omitempty"` Second float64 `protobuf:"fixed64,2,opt,name=second,proto3" json:"second,omitempty"` } func (x *Query_DoublePair) Reset() { *x = Query_DoublePair{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_DoublePair) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_DoublePair) ProtoMessage() {} func (x *Query_DoublePair) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_DoublePair.ProtoReflect.Descriptor instead. func (*Query_DoublePair) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 8} } func (x *Query_DoublePair) GetFirst() float64 { if x != nil { return x.First } return 0 } func (x *Query_DoublePair) GetSecond() float64 { if x != nil { return x.Second } return 0 } type Query_LocationPair struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields First *Query_Location `protobuf:"bytes,1,opt,name=first,proto3" json:"first,omitempty"` Second *Query_Location `protobuf:"bytes,2,opt,name=second,proto3" json:"second,omitempty"` } func (x *Query_LocationPair) Reset() { *x = Query_LocationPair{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_LocationPair) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_LocationPair) ProtoMessage() {} func (x *Query_LocationPair) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_LocationPair.ProtoReflect.Descriptor instead. func (*Query_LocationPair) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 9} } func (x *Query_LocationPair) GetFirst() *Query_Location { if x != nil { return x.First } return nil } func (x *Query_LocationPair) GetSecond() *Query_Location { if x != nil { return x.Second } return nil } type Query_Range struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Pair: // *Query_Range_StringPair // *Query_Range_IntegerPair // *Query_Range_DoublePair // *Query_Range_LocationPair Pair isQuery_Range_Pair `protobuf_oneof:"pair"` } func (x *Query_Range) Reset() { *x = Query_Range{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Range) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Range) ProtoMessage() {} func (x *Query_Range) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Range.ProtoReflect.Descriptor instead. func (*Query_Range) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 10} } func (m *Query_Range) GetPair() isQuery_Range_Pair { if m != nil { return m.Pair } return nil } func (x *Query_Range) GetStringPair() *Query_StringPair { if x, ok := x.GetPair().(*Query_Range_StringPair); ok { return x.StringPair } return nil } func (x *Query_Range) GetIntegerPair() *Query_IntPair { if x, ok := x.GetPair().(*Query_Range_IntegerPair); ok { return x.IntegerPair } return nil } func (x *Query_Range) GetDoublePair() *Query_DoublePair { if x, ok := x.GetPair().(*Query_Range_DoublePair); ok { return x.DoublePair } return nil } func (x *Query_Range) GetLocationPair() *Query_LocationPair { if x, ok := x.GetPair().(*Query_Range_LocationPair); ok { return x.LocationPair } return nil } type isQuery_Range_Pair interface { isQuery_Range_Pair() } type Query_Range_StringPair struct { StringPair *Query_StringPair `protobuf:"bytes,1,opt,name=string_pair,json=stringPair,proto3,oneof"` } type Query_Range_IntegerPair struct { IntegerPair *Query_IntPair `protobuf:"bytes,2,opt,name=integer_pair,json=integerPair,proto3,oneof"` } type Query_Range_DoublePair struct { DoublePair *Query_DoublePair `protobuf:"bytes,3,opt,name=double_pair,json=doublePair,proto3,oneof"` } type Query_Range_LocationPair struct { LocationPair *Query_LocationPair `protobuf:"bytes,4,opt,name=location_pair,json=locationPair,proto3,oneof"` } func (*Query_Range_StringPair) isQuery_Range_Pair() {} func (*Query_Range_IntegerPair) isQuery_Range_Pair() {} func (*Query_Range_DoublePair) isQuery_Range_Pair() {} func (*Query_Range_LocationPair) isQuery_Range_Pair() {} type Query_Distance struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Center *Query_Location `protobuf:"bytes,1,opt,name=center,proto3" json:"center,omitempty"` Distance float64 `protobuf:"fixed64,2,opt,name=distance,proto3" json:"distance,omitempty"` } func (x *Query_Distance) Reset() { *x = Query_Distance{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Distance) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Distance) ProtoMessage() {} func (x *Query_Distance) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Distance.ProtoReflect.Descriptor instead. func (*Query_Distance) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 11} } func (x *Query_Distance) GetCenter() *Query_Location { if x != nil { return x.Center } return nil } func (x *Query_Distance) GetDistance() float64 { if x != nil { return x.Distance } return 0 } type Query_Relation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Operator Query_Relation_Operator `protobuf:"varint,1,opt,name=operator,proto3,enum=aea.search.v0_1_0.Query_Relation_Operator" json:"operator,omitempty"` Value *Query_Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *Query_Relation) Reset() { *x = Query_Relation{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Relation) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Relation) ProtoMessage() {} func (x *Query_Relation) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Relation.ProtoReflect.Descriptor instead. func (*Query_Relation) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 12} } func (x *Query_Relation) GetOperator() Query_Relation_Operator { if x != nil { return x.Operator } return Query_Relation_EQ } func (x *Query_Relation) GetValue() *Query_Value { if x != nil { return x.Value } return nil } type Query_Set struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Operator Query_Set_Operator `protobuf:"varint,1,opt,name=operator,proto3,enum=aea.search.v0_1_0.Query_Set_Operator" json:"operator,omitempty"` Values *Query_Set_Values `protobuf:"bytes,2,opt,name=values,proto3" json:"values,omitempty"` } func (x *Query_Set) Reset() { *x = Query_Set{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Set) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Set) ProtoMessage() {} func (x *Query_Set) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Set.ProtoReflect.Descriptor instead. func (*Query_Set) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13} } func (x *Query_Set) GetOperator() Query_Set_Operator { if x != nil { return x.Operator } return Query_Set_IN } func (x *Query_Set) GetValues() *Query_Set_Values { if x != nil { return x.Values } return nil } type Query_ConstraintExpr struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Expression: // *Query_ConstraintExpr_Or_ // *Query_ConstraintExpr_And_ // *Query_ConstraintExpr_Not_ // *Query_ConstraintExpr_Constraint_ Expression isQuery_ConstraintExpr_Expression `protobuf_oneof:"expression"` } func (x *Query_ConstraintExpr) Reset() { *x = Query_ConstraintExpr{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_ConstraintExpr) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_ConstraintExpr) ProtoMessage() {} func (x *Query_ConstraintExpr) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_ConstraintExpr.ProtoReflect.Descriptor instead. func (*Query_ConstraintExpr) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 14} } func (m *Query_ConstraintExpr) GetExpression() isQuery_ConstraintExpr_Expression { if m != nil { return m.Expression } return nil } func (x *Query_ConstraintExpr) GetOr_() *Query_ConstraintExpr_Or { if x, ok := x.GetExpression().(*Query_ConstraintExpr_Or_); ok { return x.Or_ } return nil } func (x *Query_ConstraintExpr) GetAnd_() *Query_ConstraintExpr_And { if x, ok := x.GetExpression().(*Query_ConstraintExpr_And_); ok { return x.And_ } return nil } func (x *Query_ConstraintExpr) GetNot_() *Query_ConstraintExpr_Not { if x, ok := x.GetExpression().(*Query_ConstraintExpr_Not_); ok { return x.Not_ } return nil } func (x *Query_ConstraintExpr) GetConstraint() *Query_ConstraintExpr_Constraint { if x, ok := x.GetExpression().(*Query_ConstraintExpr_Constraint_); ok { return x.Constraint } return nil } type isQuery_ConstraintExpr_Expression interface { isQuery_ConstraintExpr_Expression() } type Query_ConstraintExpr_Or_ struct { Or_ *Query_ConstraintExpr_Or `protobuf:"bytes,1,opt,name=or_,json=or,proto3,oneof"` } type Query_ConstraintExpr_And_ struct { And_ *Query_ConstraintExpr_And `protobuf:"bytes,2,opt,name=and_,json=and,proto3,oneof"` } type Query_ConstraintExpr_Not_ struct { Not_ *Query_ConstraintExpr_Not `protobuf:"bytes,3,opt,name=not_,json=not,proto3,oneof"` } type Query_ConstraintExpr_Constraint_ struct { Constraint *Query_ConstraintExpr_Constraint `protobuf:"bytes,4,opt,name=constraint,proto3,oneof"` } func (*Query_ConstraintExpr_Or_) isQuery_ConstraintExpr_Expression() {} func (*Query_ConstraintExpr_And_) isQuery_ConstraintExpr_Expression() {} func (*Query_ConstraintExpr_Not_) isQuery_ConstraintExpr_Expression() {} func (*Query_ConstraintExpr_Constraint_) isQuery_ConstraintExpr_Expression() {} type Query_Model struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Constraints []*Query_ConstraintExpr `protobuf:"bytes,1,rep,name=constraints,proto3" json:"constraints,omitempty"` Model *Query_DataModel `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"` } func (x *Query_Model) Reset() { *x = Query_Model{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Model) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Model) ProtoMessage() {} func (x *Query_Model) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Model.ProtoReflect.Descriptor instead. func (*Query_Model) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 15} } func (x *Query_Model) GetConstraints() []*Query_ConstraintExpr { if x != nil { return x.Constraints } return nil } func (x *Query_Model) GetModel() *Query_DataModel { if x != nil { return x.Model } return nil } type Query_Set_Values struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Values: // *Query_Set_Values_String_ // *Query_Set_Values_Double // *Query_Set_Values_Boolean // *Query_Set_Values_Integer // *Query_Set_Values_Location Values isQuery_Set_Values_Values `protobuf_oneof:"values"` } func (x *Query_Set_Values) Reset() { *x = Query_Set_Values{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Set_Values) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Set_Values) ProtoMessage() {} func (x *Query_Set_Values) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Set_Values.ProtoReflect.Descriptor instead. func (*Query_Set_Values) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13, 0} } func (m *Query_Set_Values) GetValues() isQuery_Set_Values_Values { if m != nil { return m.Values } return nil } func (x *Query_Set_Values) GetString_() *Query_Set_Values_Strings { if x, ok := x.GetValues().(*Query_Set_Values_String_); ok { return x.String_ } return nil } func (x *Query_Set_Values) GetDouble() *Query_Set_Values_Doubles { if x, ok := x.GetValues().(*Query_Set_Values_Double); ok { return x.Double } return nil } func (x *Query_Set_Values) GetBoolean() *Query_Set_Values_Bools { if x, ok := x.GetValues().(*Query_Set_Values_Boolean); ok { return x.Boolean } return nil } func (x *Query_Set_Values) GetInteger() *Query_Set_Values_Ints { if x, ok := x.GetValues().(*Query_Set_Values_Integer); ok { return x.Integer } return nil } func (x *Query_Set_Values) GetLocation() *Query_Set_Values_Locations { if x, ok := x.GetValues().(*Query_Set_Values_Location); ok { return x.Location } return nil } type isQuery_Set_Values_Values interface { isQuery_Set_Values_Values() } type Query_Set_Values_String_ struct { String_ *Query_Set_Values_Strings `protobuf:"bytes,1,opt,name=string,proto3,oneof"` } type Query_Set_Values_Double struct { Double *Query_Set_Values_Doubles `protobuf:"bytes,2,opt,name=double,proto3,oneof"` } type Query_Set_Values_Boolean struct { Boolean *Query_Set_Values_Bools `protobuf:"bytes,3,opt,name=boolean,proto3,oneof"` } type Query_Set_Values_Integer struct { Integer *Query_Set_Values_Ints `protobuf:"bytes,4,opt,name=integer,proto3,oneof"` } type Query_Set_Values_Location struct { Location *Query_Set_Values_Locations `protobuf:"bytes,5,opt,name=location,proto3,oneof"` } func (*Query_Set_Values_String_) isQuery_Set_Values_Values() {} func (*Query_Set_Values_Double) isQuery_Set_Values_Values() {} func (*Query_Set_Values_Boolean) isQuery_Set_Values_Values() {} func (*Query_Set_Values_Integer) isQuery_Set_Values_Values() {} func (*Query_Set_Values_Location) isQuery_Set_Values_Values() {} type Query_Set_Values_Ints struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Values []int64 `protobuf:"varint,1,rep,packed,name=values,proto3" json:"values,omitempty"` } func (x *Query_Set_Values_Ints) Reset() { *x = Query_Set_Values_Ints{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Set_Values_Ints) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Set_Values_Ints) ProtoMessage() {} func (x *Query_Set_Values_Ints) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Set_Values_Ints.ProtoReflect.Descriptor instead. func (*Query_Set_Values_Ints) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13, 0, 0} } func (x *Query_Set_Values_Ints) GetValues() []int64 { if x != nil { return x.Values } return nil } type Query_Set_Values_Doubles struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Values []float64 `protobuf:"fixed64,1,rep,packed,name=values,proto3" json:"values,omitempty"` } func (x *Query_Set_Values_Doubles) Reset() { *x = Query_Set_Values_Doubles{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Set_Values_Doubles) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Set_Values_Doubles) ProtoMessage() {} func (x *Query_Set_Values_Doubles) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Set_Values_Doubles.ProtoReflect.Descriptor instead. func (*Query_Set_Values_Doubles) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13, 0, 1} } func (x *Query_Set_Values_Doubles) GetValues() []float64 { if x != nil { return x.Values } return nil } type Query_Set_Values_Strings struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` } func (x *Query_Set_Values_Strings) Reset() { *x = Query_Set_Values_Strings{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Set_Values_Strings) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Set_Values_Strings) ProtoMessage() {} func (x *Query_Set_Values_Strings) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Set_Values_Strings.ProtoReflect.Descriptor instead. func (*Query_Set_Values_Strings) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13, 0, 2} } func (x *Query_Set_Values_Strings) GetValues() []string { if x != nil { return x.Values } return nil } type Query_Set_Values_Bools struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Values []bool `protobuf:"varint,1,rep,packed,name=values,proto3" json:"values,omitempty"` } func (x *Query_Set_Values_Bools) Reset() { *x = Query_Set_Values_Bools{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Set_Values_Bools) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Set_Values_Bools) ProtoMessage() {} func (x *Query_Set_Values_Bools) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Set_Values_Bools.ProtoReflect.Descriptor instead. func (*Query_Set_Values_Bools) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13, 0, 3} } func (x *Query_Set_Values_Bools) GetValues() []bool { if x != nil { return x.Values } return nil } type Query_Set_Values_Locations struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Values []*Query_Location `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` } func (x *Query_Set_Values_Locations) Reset() { *x = Query_Set_Values_Locations{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_Set_Values_Locations) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_Set_Values_Locations) ProtoMessage() {} func (x *Query_Set_Values_Locations) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_Set_Values_Locations.ProtoReflect.Descriptor instead. func (*Query_Set_Values_Locations) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 13, 0, 4} } func (x *Query_Set_Values_Locations) GetValues() []*Query_Location { if x != nil { return x.Values } return nil } type Query_ConstraintExpr_Or struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Expression []*Query_ConstraintExpr `protobuf:"bytes,1,rep,name=expression,proto3" json:"expression,omitempty"` } func (x *Query_ConstraintExpr_Or) Reset() { *x = Query_ConstraintExpr_Or{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_ConstraintExpr_Or) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_ConstraintExpr_Or) ProtoMessage() {} func (x *Query_ConstraintExpr_Or) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_ConstraintExpr_Or.ProtoReflect.Descriptor instead. func (*Query_ConstraintExpr_Or) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 14, 0} } func (x *Query_ConstraintExpr_Or) GetExpression() []*Query_ConstraintExpr { if x != nil { return x.Expression } return nil } type Query_ConstraintExpr_And struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Expression []*Query_ConstraintExpr `protobuf:"bytes,1,rep,name=expression,proto3" json:"expression,omitempty"` } func (x *Query_ConstraintExpr_And) Reset() { *x = Query_ConstraintExpr_And{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_ConstraintExpr_And) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_ConstraintExpr_And) ProtoMessage() {} func (x *Query_ConstraintExpr_And) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_ConstraintExpr_And.ProtoReflect.Descriptor instead. func (*Query_ConstraintExpr_And) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 14, 1} } func (x *Query_ConstraintExpr_And) GetExpression() []*Query_ConstraintExpr { if x != nil { return x.Expression } return nil } type Query_ConstraintExpr_Not struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Expression *Query_ConstraintExpr `protobuf:"bytes,1,opt,name=expression,proto3" json:"expression,omitempty"` } func (x *Query_ConstraintExpr_Not) Reset() { *x = Query_ConstraintExpr_Not{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_ConstraintExpr_Not) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_ConstraintExpr_Not) ProtoMessage() {} func (x *Query_ConstraintExpr_Not) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_ConstraintExpr_Not.ProtoReflect.Descriptor instead. func (*Query_ConstraintExpr_Not) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 14, 2} } func (x *Query_ConstraintExpr_Not) GetExpression() *Query_ConstraintExpr { if x != nil { return x.Expression } return nil } type Query_ConstraintExpr_Constraint struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AttributeName string `protobuf:"bytes,1,opt,name=attribute_name,json=attributeName,proto3" json:"attribute_name,omitempty"` // Types that are assignable to Constraint: // *Query_ConstraintExpr_Constraint_Set_ // *Query_ConstraintExpr_Constraint_Range_ // *Query_ConstraintExpr_Constraint_Relation // *Query_ConstraintExpr_Constraint_Distance Constraint isQuery_ConstraintExpr_Constraint_Constraint `protobuf_oneof:"constraint"` } func (x *Query_ConstraintExpr_Constraint) Reset() { *x = Query_ConstraintExpr_Constraint{} if protoimpl.UnsafeEnabled { mi := &file_search_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Query_ConstraintExpr_Constraint) String() string { return protoimpl.X.MessageStringOf(x) } func (*Query_ConstraintExpr_Constraint) ProtoMessage() {} func (x *Query_ConstraintExpr_Constraint) ProtoReflect() protoreflect.Message { mi := &file_search_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Query_ConstraintExpr_Constraint.ProtoReflect.Descriptor instead. func (*Query_ConstraintExpr_Constraint) Descriptor() ([]byte, []int) { return file_search_proto_rawDescGZIP(), []int{0, 14, 3} } func (x *Query_ConstraintExpr_Constraint) GetAttributeName() string { if x != nil { return x.AttributeName } return "" } func (m *Query_ConstraintExpr_Constraint) GetConstraint() isQuery_ConstraintExpr_Constraint_Constraint { if m != nil { return m.Constraint } return nil } func (x *Query_ConstraintExpr_Constraint) GetSet_() *Query_Set { if x, ok := x.GetConstraint().(*Query_ConstraintExpr_Constraint_Set_); ok { return x.Set_ } return nil } func (x *Query_ConstraintExpr_Constraint) GetRange_() *Query_Range { if x, ok := x.GetConstraint().(*Query_ConstraintExpr_Constraint_Range_); ok { return x.Range_ } return nil } func (x *Query_ConstraintExpr_Constraint) GetRelation() *Query_Relation { if x, ok := x.GetConstraint().(*Query_ConstraintExpr_Constraint_Relation); ok { return x.Relation } return nil } func (x *Query_ConstraintExpr_Constraint) GetDistance() *Query_Distance { if x, ok := x.GetConstraint().(*Query_ConstraintExpr_Constraint_Distance); ok { return x.Distance } return nil } type isQuery_ConstraintExpr_Constraint_Constraint interface { isQuery_ConstraintExpr_Constraint_Constraint() } type Query_ConstraintExpr_Constraint_Set_ struct { Set_ *Query_Set `protobuf:"bytes,2,opt,name=set_,json=set,proto3,oneof"` } type Query_ConstraintExpr_Constraint_Range_ struct { Range_ *Query_Range `protobuf:"bytes,3,opt,name=range_,json=range,proto3,oneof"` } type Query_ConstraintExpr_Constraint_Relation struct { Relation *Query_Relation `protobuf:"bytes,4,opt,name=relation,proto3,oneof"` } type Query_ConstraintExpr_Constraint_Distance struct { Distance *Query_Distance `protobuf:"bytes,5,opt,name=distance,proto3,oneof"` } func (*Query_ConstraintExpr_Constraint_Set_) isQuery_ConstraintExpr_Constraint_Constraint() {} func (*Query_ConstraintExpr_Constraint_Range_) isQuery_ConstraintExpr_Constraint_Constraint() {} func (*Query_ConstraintExpr_Constraint_Relation) isQuery_ConstraintExpr_Constraint_Constraint() {} func (*Query_ConstraintExpr_Constraint_Distance) isQuery_ConstraintExpr_Constraint_Constraint() {} var File_search_proto protoreflect.FileDescriptor var file_search_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x22, 0xad, 0x1b, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0xdb, 0x01, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3f, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x49, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x42, 0x4f, 0x4f, 0x4c, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x04, 0x1a, 0x85, 0x01, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x2e, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6c, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6c, 0x61, 0x74, 0x1a, 0xbd, 0x01, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x18, 0x0a, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x07, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x07, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x12, 0x1a, 0x0a, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x52, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x7f, 0x0a, 0x08, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x3a, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x1a, 0x37, 0x0a, 0x07, 0x49, 0x6e, 0x74, 0x50, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x1a, 0x3a, 0x0a, 0x0a, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x1a, 0x82, 0x01, 0x0a, 0x0c, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x69, 0x72, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x1a, 0xb4, 0x02, 0x0a, 0x05, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x45, 0x0a, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x50, 0x61, 0x69, 0x72, 0x48, 0x00, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x50, 0x61, 0x69, 0x72, 0x12, 0x46, 0x0a, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x69, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x4c, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x69, 0x72, 0x48, 0x00, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x69, 0x72, 0x42, 0x06, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x1a, 0x61, 0x0a, 0x08, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x1a, 0xcb, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x41, 0x0a, 0x08, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x06, 0x0a, 0x02, 0x45, 0x51, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4c, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x54, 0x45, 0x51, 0x10, 0x02, 0x12, 0x06, 0x0a, 0x02, 0x47, 0x54, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x54, 0x45, 0x51, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x4f, 0x54, 0x45, 0x51, 0x10, 0x05, 0x1a, 0xf0, 0x05, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x12, 0x41, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xc9, 0x04, 0x0a, 0x06, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x45, 0x0a, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x07, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x07, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x12, 0x44, 0x0a, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x73, 0x48, 0x00, 0x52, 0x07, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x12, 0x4b, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1e, 0x0a, 0x04, 0x49, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x21, 0x0a, 0x07, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x01, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x21, 0x0a, 0x07, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x1f, 0x0a, 0x05, 0x42, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x08, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x46, 0x0a, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x1d, 0x0a, 0x08, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x4f, 0x54, 0x49, 0x4e, 0x10, 0x01, 0x1a, 0xd8, 0x06, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x12, 0x3d, 0x0a, 0x03, 0x6f, 0x72, 0x5f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x2e, 0x4f, 0x72, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x04, 0x61, 0x6e, 0x64, 0x5f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x2e, 0x41, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x03, 0x61, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x04, 0x6e, 0x6f, 0x74, 0x5f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x2e, 0x4e, 0x6f, 0x74, 0x48, 0x00, 0x52, 0x03, 0x6e, 0x6f, 0x74, 0x12, 0x54, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x1a, 0x4d, 0x0a, 0x02, 0x4f, 0x72, 0x12, 0x47, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x4e, 0x0a, 0x03, 0x41, 0x6e, 0x64, 0x12, 0x47, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x4e, 0x0a, 0x03, 0x4e, 0x6f, 0x74, 0x12, 0x47, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xaf, 0x02, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x73, 0x65, 0x74, 0x5f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x48, 0x00, 0x52, 0x03, 0x73, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x48, 0x00, 0x52, 0x08, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x42, 0x0c, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x8c, 0x01, 0x0a, 0x05, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x49, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x72, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x38, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x5f, 0x31, 0x5f, 0x30, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x42, 0x15, 0x48, 0x01, 0x5a, 0x11, 0x61, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_search_proto_rawDescOnce sync.Once file_search_proto_rawDescData = file_search_proto_rawDesc ) func file_search_proto_rawDescGZIP() []byte { file_search_proto_rawDescOnce.Do(func() { file_search_proto_rawDescData = protoimpl.X.CompressGZIP(file_search_proto_rawDescData) }) return file_search_proto_rawDescData } var file_search_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_search_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_search_proto_goTypes = []interface{}{ (Query_Attribute_Type)(0), // 0: aea.search.v0_1_0.Query.Attribute.Type (Query_Relation_Operator)(0), // 1: aea.search.v0_1_0.Query.Relation.Operator (Query_Set_Operator)(0), // 2: aea.search.v0_1_0.Query.Set.Operator (*Query)(nil), // 3: aea.search.v0_1_0.Query (*Query_Attribute)(nil), // 4: aea.search.v0_1_0.Query.Attribute (*Query_DataModel)(nil), // 5: aea.search.v0_1_0.Query.DataModel (*Query_Location)(nil), // 6: aea.search.v0_1_0.Query.Location (*Query_Value)(nil), // 7: aea.search.v0_1_0.Query.Value (*Query_KeyValue)(nil), // 8: aea.search.v0_1_0.Query.KeyValue (*Query_Instance)(nil), // 9: aea.search.v0_1_0.Query.Instance (*Query_StringPair)(nil), // 10: aea.search.v0_1_0.Query.StringPair (*Query_IntPair)(nil), // 11: aea.search.v0_1_0.Query.IntPair (*Query_DoublePair)(nil), // 12: aea.search.v0_1_0.Query.DoublePair (*Query_LocationPair)(nil), // 13: aea.search.v0_1_0.Query.LocationPair (*Query_Range)(nil), // 14: aea.search.v0_1_0.Query.Range (*Query_Distance)(nil), // 15: aea.search.v0_1_0.Query.Distance (*Query_Relation)(nil), // 16: aea.search.v0_1_0.Query.Relation (*Query_Set)(nil), // 17: aea.search.v0_1_0.Query.Set (*Query_ConstraintExpr)(nil), // 18: aea.search.v0_1_0.Query.ConstraintExpr (*Query_Model)(nil), // 19: aea.search.v0_1_0.Query.Model (*Query_Set_Values)(nil), // 20: aea.search.v0_1_0.Query.Set.Values (*Query_Set_Values_Ints)(nil), // 21: aea.search.v0_1_0.Query.Set.Values.Ints (*Query_Set_Values_Doubles)(nil), // 22: aea.search.v0_1_0.Query.Set.Values.Doubles (*Query_Set_Values_Strings)(nil), // 23: aea.search.v0_1_0.Query.Set.Values.Strings (*Query_Set_Values_Bools)(nil), // 24: aea.search.v0_1_0.Query.Set.Values.Bools (*Query_Set_Values_Locations)(nil), // 25: aea.search.v0_1_0.Query.Set.Values.Locations (*Query_ConstraintExpr_Or)(nil), // 26: aea.search.v0_1_0.Query.ConstraintExpr.Or (*Query_ConstraintExpr_And)(nil), // 27: aea.search.v0_1_0.Query.ConstraintExpr.And (*Query_ConstraintExpr_Not)(nil), // 28: aea.search.v0_1_0.Query.ConstraintExpr.Not (*Query_ConstraintExpr_Constraint)(nil), // 29: aea.search.v0_1_0.Query.ConstraintExpr.Constraint } var file_search_proto_depIdxs = []int32{ 0, // 0: aea.search.v0_1_0.Query.Attribute.type:type_name -> aea.search.v0_1_0.Query.Attribute.Type 4, // 1: aea.search.v0_1_0.Query.DataModel.attributes:type_name -> aea.search.v0_1_0.Query.Attribute 6, // 2: aea.search.v0_1_0.Query.Value.location:type_name -> aea.search.v0_1_0.Query.Location 7, // 3: aea.search.v0_1_0.Query.KeyValue.value:type_name -> aea.search.v0_1_0.Query.Value 5, // 4: aea.search.v0_1_0.Query.Instance.model:type_name -> aea.search.v0_1_0.Query.DataModel 8, // 5: aea.search.v0_1_0.Query.Instance.values:type_name -> aea.search.v0_1_0.Query.KeyValue 6, // 6: aea.search.v0_1_0.Query.LocationPair.first:type_name -> aea.search.v0_1_0.Query.Location 6, // 7: aea.search.v0_1_0.Query.LocationPair.second:type_name -> aea.search.v0_1_0.Query.Location 10, // 8: aea.search.v0_1_0.Query.Range.string_pair:type_name -> aea.search.v0_1_0.Query.StringPair 11, // 9: aea.search.v0_1_0.Query.Range.integer_pair:type_name -> aea.search.v0_1_0.Query.IntPair 12, // 10: aea.search.v0_1_0.Query.Range.double_pair:type_name -> aea.search.v0_1_0.Query.DoublePair 13, // 11: aea.search.v0_1_0.Query.Range.location_pair:type_name -> aea.search.v0_1_0.Query.LocationPair 6, // 12: aea.search.v0_1_0.Query.Distance.center:type_name -> aea.search.v0_1_0.Query.Location 1, // 13: aea.search.v0_1_0.Query.Relation.operator:type_name -> aea.search.v0_1_0.Query.Relation.Operator 7, // 14: aea.search.v0_1_0.Query.Relation.value:type_name -> aea.search.v0_1_0.Query.Value 2, // 15: aea.search.v0_1_0.Query.Set.operator:type_name -> aea.search.v0_1_0.Query.Set.Operator 20, // 16: aea.search.v0_1_0.Query.Set.values:type_name -> aea.search.v0_1_0.Query.Set.Values 26, // 17: aea.search.v0_1_0.Query.ConstraintExpr.or_:type_name -> aea.search.v0_1_0.Query.ConstraintExpr.Or 27, // 18: aea.search.v0_1_0.Query.ConstraintExpr.and_:type_name -> aea.search.v0_1_0.Query.ConstraintExpr.And 28, // 19: aea.search.v0_1_0.Query.ConstraintExpr.not_:type_name -> aea.search.v0_1_0.Query.ConstraintExpr.Not 29, // 20: aea.search.v0_1_0.Query.ConstraintExpr.constraint:type_name -> aea.search.v0_1_0.Query.ConstraintExpr.Constraint 18, // 21: aea.search.v0_1_0.Query.Model.constraints:type_name -> aea.search.v0_1_0.Query.ConstraintExpr 5, // 22: aea.search.v0_1_0.Query.Model.model:type_name -> aea.search.v0_1_0.Query.DataModel 23, // 23: aea.search.v0_1_0.Query.Set.Values.string:type_name -> aea.search.v0_1_0.Query.Set.Values.Strings 22, // 24: aea.search.v0_1_0.Query.Set.Values.double:type_name -> aea.search.v0_1_0.Query.Set.Values.Doubles 24, // 25: aea.search.v0_1_0.Query.Set.Values.boolean:type_name -> aea.search.v0_1_0.Query.Set.Values.Bools 21, // 26: aea.search.v0_1_0.Query.Set.Values.integer:type_name -> aea.search.v0_1_0.Query.Set.Values.Ints 25, // 27: aea.search.v0_1_0.Query.Set.Values.location:type_name -> aea.search.v0_1_0.Query.Set.Values.Locations 6, // 28: aea.search.v0_1_0.Query.Set.Values.Locations.values:type_name -> aea.search.v0_1_0.Query.Location 18, // 29: aea.search.v0_1_0.Query.ConstraintExpr.Or.expression:type_name -> aea.search.v0_1_0.Query.ConstraintExpr 18, // 30: aea.search.v0_1_0.Query.ConstraintExpr.And.expression:type_name -> aea.search.v0_1_0.Query.ConstraintExpr 18, // 31: aea.search.v0_1_0.Query.ConstraintExpr.Not.expression:type_name -> aea.search.v0_1_0.Query.ConstraintExpr 17, // 32: aea.search.v0_1_0.Query.ConstraintExpr.Constraint.set_:type_name -> aea.search.v0_1_0.Query.Set 14, // 33: aea.search.v0_1_0.Query.ConstraintExpr.Constraint.range_:type_name -> aea.search.v0_1_0.Query.Range 16, // 34: aea.search.v0_1_0.Query.ConstraintExpr.Constraint.relation:type_name -> aea.search.v0_1_0.Query.Relation 15, // 35: aea.search.v0_1_0.Query.ConstraintExpr.Constraint.distance:type_name -> aea.search.v0_1_0.Query.Distance 36, // [36:36] is the sub-list for method output_type 36, // [36:36] is the sub-list for method input_type 36, // [36:36] is the sub-list for extension type_name 36, // [36:36] is the sub-list for extension extendee 0, // [0:36] is the sub-list for field type_name } func init() { file_search_proto_init() } func file_search_proto_init() { if File_search_proto != nil { return } if !protoimpl.UnsafeEnabled { file_search_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Attribute); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_DataModel); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Location); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Value); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_KeyValue); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Instance); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_StringPair); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_IntPair); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_DoublePair); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_LocationPair); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Range); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Distance); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Relation); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Set); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_ConstraintExpr); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Model); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Set_Values); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Set_Values_Ints); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Set_Values_Doubles); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Set_Values_Strings); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Set_Values_Bools); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_Set_Values_Locations); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_ConstraintExpr_Or); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_ConstraintExpr_And); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_ConstraintExpr_Not); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_search_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Query_ConstraintExpr_Constraint); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_search_proto_msgTypes[4].OneofWrappers = []interface{}{ (*Query_Value_String_)(nil), (*Query_Value_Double)(nil), (*Query_Value_Boolean)(nil), (*Query_Value_Integer)(nil), (*Query_Value_Location)(nil), } file_search_proto_msgTypes[11].OneofWrappers = []interface{}{ (*Query_Range_StringPair)(nil), (*Query_Range_IntegerPair)(nil), (*Query_Range_DoublePair)(nil), (*Query_Range_LocationPair)(nil), } file_search_proto_msgTypes[15].OneofWrappers = []interface{}{ (*Query_ConstraintExpr_Or_)(nil), (*Query_ConstraintExpr_And_)(nil), (*Query_ConstraintExpr_Not_)(nil), (*Query_ConstraintExpr_Constraint_)(nil), } file_search_proto_msgTypes[17].OneofWrappers = []interface{}{ (*Query_Set_Values_String_)(nil), (*Query_Set_Values_Double)(nil), (*Query_Set_Values_Boolean)(nil), (*Query_Set_Values_Integer)(nil), (*Query_Set_Values_Location)(nil), } file_search_proto_msgTypes[26].OneofWrappers = []interface{}{ (*Query_ConstraintExpr_Constraint_Set_)(nil), (*Query_ConstraintExpr_Constraint_Range_)(nil), (*Query_ConstraintExpr_Constraint_Relation)(nil), (*Query_ConstraintExpr_Constraint_Distance)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_search_proto_rawDesc, NumEnums: 3, NumMessages: 27, NumExtensions: 0, NumServices: 0, }, GoTypes: file_search_proto_goTypes, DependencyIndexes: file_search_proto_depIdxs, EnumInfos: file_search_proto_enumTypes, MessageInfos: file_search_proto_msgTypes, }.Build() File_search_proto = out.File file_search_proto_rawDesc = nil file_search_proto_goTypes = nil file_search_proto_depIdxs = nil } ================================================ FILE: libs/go/aealite/protocols/search.proto ================================================ syntax = "proto3"; package aea.search.v0_1_0; option go_package = "aealite/protocols"; message Query { message Attribute { enum Type { DOUBLE = 0; INT = 1; BOOL = 2; STRING = 3; LOCATION = 4; } string name = 1; Type type = 2; bool required = 3; string description = 4; } message DataModel { string name = 1; repeated Attribute attributes = 2; string description = 3; } message Location { double lon = 1; double lat = 2; } message Value { oneof value { string string = 1; double double = 2; bool boolean = 3; int64 integer = 4; Location location = 5; } } message KeyValue { string key = 1; Value value = 2; } message Instance { DataModel model = 1; repeated KeyValue values = 2; } message StringPair { string first = 1; string second = 2; } message IntPair { int64 first = 1; int64 second = 2; } message DoublePair { double first = 1; double second = 2; } message LocationPair { Location first = 1; Location second = 2; } message Range { oneof pair { StringPair string_pair = 1; IntPair integer_pair = 2; DoublePair double_pair = 3; LocationPair location_pair = 4; } } message Distance { Location center = 1; double distance = 2; } message Relation { enum Operator { EQ = 0; // = LT = 1; // < LTEQ = 2; // <= GT = 3; // > GTEQ = 4; // >= NOTEQ = 5; // !=, <> } Operator operator = 1; Value value = 2; } message Set { message Values { message Ints { repeated int64 values = 1; } message Doubles { repeated double values = 1; } message Strings { repeated string values = 1; } message Bools { repeated bool values = 1; } message Locations { repeated Location values = 1; } oneof values { Strings string = 1; Doubles double = 2; Bools boolean = 3; Ints integer = 4; Locations location = 5; } } enum Operator { IN = 0; NOTIN = 1; } Operator operator = 1; Values values = 2; } message ConstraintExpr { message Or { repeated ConstraintExpr expression = 1; } message And { repeated ConstraintExpr expression = 1; } message Not { ConstraintExpr expression = 1; } message Constraint { string attribute_name = 1; oneof constraint { Set set_ = 2; Range range_ = 3; Relation relation = 4; Distance distance = 5; } } oneof expression { Or or_ = 1; And and_ = 2; Not not_ = 3; Constraint constraint = 4; } } message Model { repeated ConstraintExpr constraints = 1; DataModel model = 2; } } // option optimize_for = LITE_RUNTIME; option optimize_for = SPEED; ================================================ FILE: libs/go/aealite/protocols/storage.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import "aealite/helpers" type DialogueMap map[DialogueLabel]*Dialogue // Dialogue storage type DialogueStorageInterface interface { GetDialoguesInTerminalState() []*Dialogue GetDialoguesInActiveState() []*Dialogue IsTerminalDialoguesKept() bool DialogueTerminalStateCallback(*Dialogue) AddDialogue(dialogue *Dialogue) RemoveDialogue(dialogueLabel DialogueLabel) GetDialogue(label DialogueLabel) *Dialogue GetDialoguesWithCounterparty(counterparty Address) []*Dialogue IsInIncomplete(dialogueLabel DialogueLabel) bool SetIncompleteDialogue( incompleteDialogueLabel DialogueLabel, completeDialogueLabel DialogueLabel, ) IsDialoguePresent(dialogueLabel DialogueLabel) bool GetLatestLabel(dialogueLabel DialogueLabel) DialogueLabel } type SimpleDialogueStorage struct { dialogues *Dialogues dialoguesByDialogueLabel map[DialogueLabel]*Dialogue dialoguesByAddress map[Address][]*Dialogue incompleteToCompleteDialogueLabels map[DialogueLabel]DialogueLabel terminalStateDialogueLabels helpers.Set } /* methods */ func (dialogueStorage *SimpleDialogueStorage) GetDialoguesInTerminalState() []*Dialogue { result := make([]*Dialogue, 0) for _, labelInterface := range dialogueStorage.terminalStateDialogueLabels.ToArray() { label := labelInterface.(DialogueLabel) dialogue, ok := dialogueStorage.dialoguesByDialogueLabel[label] if ok { result = append(result, dialogue) } } return result } func (dialogueStorage *SimpleDialogueStorage) GetDialoguesInActiveState() []*Dialogue { result := make([]*Dialogue, 0) for label, dialogue := range dialogueStorage.dialoguesByDialogueLabel { if !dialogueStorage.terminalStateDialogueLabels.Contains(label) { result = append(result, dialogue) } } return result } func (dialogueStorage *SimpleDialogueStorage) IsTerminalDialoguesKept() bool { return dialogueStorage.dialogues.IsKeepDialoguesInTerminalStates() } func (dialogueStorage *SimpleDialogueStorage) DialogueTerminalStateCallback(dialogue *Dialogue) { if dialogueStorage.dialogues.IsKeepDialoguesInTerminalStates() { dialogueStorage.terminalStateDialogueLabels.Add(dialogue.dialogueLabel) } else { dialogueStorage.RemoveDialogue(dialogue.dialogueLabel) } } func (dialogueStorage *SimpleDialogueStorage) AddDialogue(dialogue *Dialogue) { dialogue.AddTerminalStateCallback(dialogueStorage.DialogueTerminalStateCallback) dialogueStorage.dialoguesByDialogueLabel[dialogue.dialogueLabel] = dialogue opponent := dialogue.dialogueLabel.dialogueOpponentAddress dialogueList, ok := dialogueStorage.dialoguesByAddress[opponent] if !ok { dialogueList = make([]*Dialogue, 0) dialogueStorage.dialoguesByAddress[opponent] = dialogueList } dialogueStorage.dialoguesByAddress[opponent] = append(dialogueList, dialogue) } func (dialogueStorage *SimpleDialogueStorage) RemoveDialogue(dialogueLabel DialogueLabel) { _, ok := dialogueStorage.dialoguesByDialogueLabel[dialogueLabel] delete(dialogueStorage.dialoguesByDialogueLabel, dialogueLabel) delete(dialogueStorage.incompleteToCompleteDialogueLabels, dialogueLabel) dialogueStorage.terminalStateDialogueLabels.Remove(dialogueLabel) if ok { array := dialogueStorage.dialoguesByAddress[dialogueLabel.dialogueOpponentAddress] dialogueStorage.dialoguesByAddress[dialogueLabel.dialogueOpponentAddress] = removeDialogueFromArray( array, dialogueLabel, ) } } func (dialogueStorage *SimpleDialogueStorage) GetDialogue(label DialogueLabel) *Dialogue { return dialogueStorage.dialoguesByDialogueLabel[label] } func (dialogueStorage *SimpleDialogueStorage) GetDialoguesWithCounterparty( counterparty Address, ) []*Dialogue { result := make([]*Dialogue, 0) result = append(result, dialogueStorage.dialoguesByAddress[counterparty]...) return result } func (dialogueStorage *SimpleDialogueStorage) IsInIncomplete(dialogueLabel DialogueLabel) bool { _, ok := dialogueStorage.incompleteToCompleteDialogueLabels[dialogueLabel] return ok } func (dialogueStorage *SimpleDialogueStorage) SetIncompleteDialogue( incompleteDialogueLabel DialogueLabel, completeDialogueLabel DialogueLabel, ) { dialogueStorage.incompleteToCompleteDialogueLabels[incompleteDialogueLabel] = completeDialogueLabel } func (dialogueStorage *SimpleDialogueStorage) IsDialoguePresent(dialogueLabel DialogueLabel) bool { _, ok := dialogueStorage.dialoguesByDialogueLabel[dialogueLabel] return ok } func (dialogueStorage *SimpleDialogueStorage) GetLatestLabel( dialogueLabel DialogueLabel, ) DialogueLabel { result, ok := dialogueStorage.incompleteToCompleteDialogueLabels[dialogueLabel] if !ok { result = dialogueLabel } return result } /* helper functions */ func removeDialogueFromArray(array []*Dialogue, dialogueLabel DialogueLabel) []*Dialogue { var index int var dialogue *Dialogue for index, dialogue = range array { if dialogue.dialogueLabel == dialogueLabel { break } } newArray := append(array[:index], array[index+1:]...) return newArray } /* global functions */ func NewSimpleDialogueStorage(dialogues *Dialogues) *SimpleDialogueStorage { result := SimpleDialogueStorage{ dialogues: dialogues, dialoguesByDialogueLabel: make(map[DialogueLabel]*Dialogue), dialoguesByAddress: make(map[Address][]*Dialogue), incompleteToCompleteDialogueLabels: make(map[DialogueLabel]DialogueLabel), terminalStateDialogueLabels: helpers.NewSet(), } return &result } ================================================ FILE: libs/go/aealite/protocols/storage_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols import ( "testing" ) // getTestDialogue builds a dialogue object for testing purposes. func getTestDialogue() Dialogue { return NewDialogue( getTestDialogueLabel(), senderAddress, Role1, []Performative{"request", "response"}, []Performative{"response"}, map[Performative][]Performative{ "request": {"response"}, }) } func TestStorage(t *testing.T) { storage := NewSimpleDialogueStorage(nil) dialogue := getTestDialogue() // Test storage methods without dialogues result := storage.GetDialogue(dialogue.dialogueLabel) if result != nil { t.Fatal("dialogue not expected") } // Test storage methods after a dialogue is added storage.AddDialogue(&dialogue) result = storage.GetDialogue(dialogue.dialogueLabel) if result == nil { t.Fatal("dialogue expected") } } ================================================ FILE: libs/go/aealite/protocols/versions.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package protocols const ( ACNProtocolVersion = "0.1.0" BaseProtocolVersion = "0.1.0" ) ================================================ FILE: libs/go/aealite/test_env_file.env ================================================ export AEA_LEDGER_ID="fetchai" export AEA_ADDRESS="fetch1x9v67meyfq4pkgy2n2yf6797cfkul327kpclqr" export AEA_PUBLIC_KEY="02ac514ba70de60ed5c30f90e3acdfc958ecb416d9676706bf013228abfb2c2816" export AEA_PRIVATE_KEY="6d8d2b87d987641e2ca3f1991c1cccf08a118759e81fabdbf7e8484f27af015e" export AEA_P2P_POR_SERVICE_ID="acn" export AEA_P2P_POR_LEDGER_ID="fetchai" export AEA_P2P_POR_PEER_PUBKEY="0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7" export AEA_P2P_POR_SIGNATURE="avzHfL/fjMidvweJJKjtBUiqJ2+6aDUq8MoNRBi9nDI/lWleIX3ftRf6Sx5UWmxcS0SW03IVrf1iKTXA5zeA0g==" export AEA_P2P_DELEGATE_HOST="acn.fetch.ai" export AEA_P2P_DELEGATE_PORT=11000 ================================================ FILE: libs/go/aealite/wallet/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package wallet import ( "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "fmt" "github.com/libp2p/go-libp2p-core/crypto" "golang.org/x/crypto/ripemd160" // nolint:staticcheck "golang.org/x/crypto/sha3" btcec "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/bech32" "github.com/ethereum/go-ethereum/common/hexutil" ethCrypto "github.com/ethereum/go-ethereum/crypto" ) var ( addressFromPublicKeyTable = map[string]func(string) (string, error){ "fetchai": FetchAIAddressFromPublicKey, "cosmos": CosmosAddressFromPublicKey, "ethereum": EthereumAddressFromPublicKey, } publicKeyFromPrivateKeyTable = map[string]func(string) (string, error){ "fetchai": FetchAIPublicKeyFromPrivateKey, } verifyLedgerSignatureTable = map[string]func([]byte, string, string) (bool, error){ "fetchai": VerifyFetchAISignatureBTC, "cosmos": VerifyFetchAISignatureBTC, "ethereum": VerifyEthereumSignatureETH, } ) // AddressFromPublicKey get wallet address from public key associated with ledgerId func AddressFromPublicKey(ledgerId string, publicKey string) (string, error) { if addressFromPublicKey, found := addressFromPublicKeyTable[ledgerId]; found { return addressFromPublicKey(publicKey) } return "", errors.New("Unsupported ledger " + ledgerId + "for AddressFromPublicKey") } // PublicKeyFromPrivateKey get public key from private key associated with ledgerId func PublicKeyFromPrivateKey(ledgerId string, privateKey string) (string, error) { if publicKeyFromPrivateKey, found := publicKeyFromPrivateKeyTable[ledgerId]; found { return publicKeyFromPrivateKey(privateKey) } return "", errors.New("Unsupported ledger " + ledgerId + "for PublicKeyFromPrivateKey") } // PubKeyFromFetchAIPublicKey create libp2p public key from fetchai hex encoded secp256k1 key func PubKeyFromFetchAIPublicKey(publicKey string) (crypto.PubKey, error) { hexBytes, _ := hex.DecodeString(publicKey) return crypto.UnmarshalSecp256k1PublicKey(hexBytes) } // FetchAIPublicKeyFromPubKey return FetchAI's format serialized public key func FetchAIPublicKeyFromPubKey(publicKey crypto.PubKey) (string, error) { raw, err := publicKey.Raw() if err != nil { return "", err } return hex.EncodeToString(raw), nil } // BTCPubKeyFromFetchAIPublicKey func BTCPubKeyFromFetchAIPublicKey(publicKey string) (*btcec.PublicKey, error) { pbkBytes, err := hex.DecodeString(publicKey) if err != nil { return nil, err } pbk, err := btcec.ParsePubKey(pbkBytes, btcec.S256()) return pbk, err } // BTCPubKeyFromEthereumPublicKey create libp2p public key from ethereum uncompressed // hex encoded secp256k1 key func BTCPubKeyFromEthereumPublicKey(publicKey string) (*btcec.PublicKey, error) { return BTCPubKeyFromUncompressedHex(publicKey[2:]) } // ConvertStrEncodedSignatureToDER // References: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L258 // - https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L47 func ConvertStrEncodedSignatureToDER(signature []byte) ([]byte, error) { if signature == nil { return []byte{}, errors.New("No signature provided.") } rb := signature[:len(signature)/2] sb := signature[len(signature)/2:] length := 6 + len(rb) + len(sb) sigDER := make([]byte, length) sigDER[0] = 0x30 sigDER[1] = byte(length - 2) sigDER[2] = 0x02 sigDER[3] = byte(len(rb)) offset := copy(sigDER[4:], rb) + 4 sigDER[offset] = 0x02 sigDER[offset+1] = byte(len(sb)) copy(sigDER[offset+2:], sb) return sigDER, nil } // ConvertDEREncodedSignatureToStr // References: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L258 // - https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L47 func ConvertDEREncodedSignatureToStr(signature []byte) ([]byte, error) { sig, err := btcec.ParseDERSignature(signature, btcec.S256()) if err != nil { return []byte{}, err } return append(sig.R.Bytes(), sig.S.Bytes()...), nil } // ParseFetchAISignature create btcec Signature from base64 formated, string (not DER) encoded RFC6979 signature func ParseFetchAISignature(signature string) (*btcec.Signature, error) { // First convert the signature into a DER one sigBytes, err := base64.StdEncoding.DecodeString(signature) if err != nil { return nil, err } sigDER, err := ConvertStrEncodedSignatureToDER(sigBytes) if err != nil { return nil, err } // Parse sigBTC, err := btcec.ParseSignature(sigDER, btcec.S256()) return sigBTC, err } // VerifyLedgerSignature verify signature of message using public key for supported ledgers func VerifyLedgerSignature( ledgerId string, message []byte, signature string, pubkey string, ) (bool, error) { verifySignature, found := verifyLedgerSignatureTable[ledgerId] if found { return verifySignature(message, signature, pubkey) } return false, errors.New("Unsupported ledger") } // VerifyFetchAISignatureBTC verify the RFC6967 string-encoded signature of message using FetchAI public key func VerifyFetchAISignatureBTC(message []byte, signature string, pubkey string) (bool, error) { // construct verifying key verifyKey, err := BTCPubKeyFromFetchAIPublicKey(pubkey) if err != nil { return false, err } // construct signature signatureBTC, err := ParseFetchAISignature(signature) if err != nil { return false, err } // verify signature messageHash := sha256.New() _, err = messageHash.Write([]byte(message)) if err != nil { return false, err } return signatureBTC.Verify(messageHash.Sum(nil), verifyKey), nil } // VerifyFetchAISignatureLibp2p verify RFC6967 string-encoded signature of message using FetchAI public key func VerifyFetchAISignatureLibp2p(message []byte, signature string, pubkey string) (bool, error) { // construct verifying key verifyKey, err := PubKeyFromFetchAIPublicKey(pubkey) if err != nil { return false, err } // Convert signature into DER encoding sigBytes, err := base64.StdEncoding.DecodeString(signature) if err != nil { return false, err } sigDER, err := ConvertStrEncodedSignatureToDER(sigBytes) if err != nil { return false, err } // verify signature return verifyKey.Verify(message, sigDER) } func SignFetchAI(message []byte, privKey string) (string, error) { signingKey, _, err := KeyPairFromFetchAIKey(privKey) if err != nil { return "", err } signature, err := signingKey.Sign(message) if err != nil { return "", err } strSignature, err := ConvertDEREncodedSignatureToStr(signature) if err != nil { return "", err } encodedSignature := base64.StdEncoding.EncodeToString(strSignature) return encodedSignature, nil } func signHashETH(data []byte) []byte { msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) return ethCrypto.Keccak256([]byte(msg)) } // RecoverAddressFromEthereumSignature verify the signature and returns the address of the signer // references: // - https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 // - https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L404 func RecoverAddressFromEthereumSignature(message []byte, signature string) (string, error) { // prepare signature sigBytes, err := hexutil.Decode(signature) if err != nil { return "", err } if sigBytes[64] != 27 && sigBytes[64] != 28 { return "", errors.New("invalid Ethereum signature (V is not 27 or 28)") } sigBytes[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 // recover verify key recoveredPubKey, err := ethCrypto.SigToPub(signHashETH(message), sigBytes) if err != nil { return "", err } return ethCrypto.PubkeyToAddress(*recoveredPubKey).Hex(), nil } // VerifyEthereumSignatureETH verify ethereum signature using ethereum public key func VerifyEthereumSignatureETH(message []byte, signature string, pubkey string) (bool, error) { // get expected signer address expectedAddress, err := EthereumAddressFromPublicKey(pubkey) if err != nil { return false, err } // recover signer address recoveredAddress, err := RecoverAddressFromEthereumSignature(message, signature) if err != nil { return false, err } if recoveredAddress != expectedAddress { return false, errors.New("Recovered and expected addresses don't match") } return true, nil } // KeyPairFromFetchAIKey key pair from hex encoded secp256k1 private key func KeyPairFromFetchAIKey(key string) (crypto.PrivKey, crypto.PubKey, error) { pkBytes, err := hex.DecodeString(key) if err != nil { return nil, nil, err } btcPrivateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes) prvKey, pubKey, err := crypto.KeyPairFromStdKey(btcPrivateKey) if err != nil { return nil, nil, err } return prvKey, pubKey, nil } // FetchAIAddressFromPublicKey get wallet address from hex encoded secp256k1 public key func FetchAIAddressFromPublicKey(publicKey string) (string, error) { return cosmosAddressFromPublicKeyWithPrefix("fetch", publicKey) } // CosmosAddressFromPublicKey get wallet address from hex encoded secp256k1 public key func CosmosAddressFromPublicKey(publicKey string) (string, error) { return cosmosAddressFromPublicKeyWithPrefix("cosmos", publicKey) } // cosmosAddressFromPublicKeyWithPrefix get wallet address from hex encoded secp256k1 public key // format from: https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L120 func cosmosAddressFromPublicKeyWithPrefix(prefix string, publicKey string) (string, error) { var addr string var err error hexBytes, err := hex.DecodeString(publicKey) if err != nil { return addr, err } hash := sha256.New() _, err = hash.Write(hexBytes) if err != nil { return addr, err } sha256Hash := hash.Sum(nil) hash = ripemd160.New() _, err = hash.Write(sha256Hash) if err != nil { return addr, err } ripemd160Hash := hash.Sum(nil) fiveBitsChar, err := bech32.ConvertBits(ripemd160Hash, 8, 5, true) if err != nil { return addr, err } addr, err = bech32.Encode(prefix, fiveBitsChar) return addr, err } // EthereumAddressFromPublicKey get wallet address from hex encoded secp256k1 public key // references: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/ethereum.py#L330 // - https://github.com/ethereum/go-ethereum/blob/master/crypto/crypto.go#L263 func EthereumAddressFromPublicKey(publicKey string) (string, error) { var addr string var err error hexBytes, err := hex.DecodeString(publicKey[2:]) if err != nil { return addr, err } hash := sha3.NewLegacyKeccak256() _, err = hash.Write(hexBytes) if err != nil { return addr, err } sha3KeccakHash := hash.Sum(nil) return encodeChecksumEIP55(sha3KeccakHash[12:]), nil } // encodeChecksumEIP55 EIP55-compliant hex string representation of the address // source: https://github.com/ethereum/go-ethereum/blob/master/common/types.go#L210 // reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md func encodeChecksumEIP55(address []byte) string { unchecksummed := hex.EncodeToString(address[:]) sha := sha3.NewLegacyKeccak256() _, err := sha.Write([]byte(unchecksummed)) if err != nil { fmt.Println("IGNORED:", err) } hash := sha.Sum(nil) result := []byte(unchecksummed) for i := 0; i < len(result); i++ { hashByte := hash[i/2] if i%2 == 0 { hashByte = hashByte >> 4 } else { hashByte &= 0xf } if result[i] > '9' && hashByte > 7 { result[i] -= 32 } } return "0x" + string(result) } // BTCPubKeyFromUncompressedHex get public key from secp256k1 hex encoded uncompressed representation func BTCPubKeyFromUncompressedHex(publicKey string) (*btcec.PublicKey, error) { b, err := hex.DecodeString(publicKey) if err != nil { return nil, err } pubBytes := make([]byte, 0, btcec.PubKeyBytesLenUncompressed) pubBytes = append(pubBytes, 0x4) // btcec.pubkeyUncompressed pubBytes = append(pubBytes, b...) return btcec.ParsePubKey(pubBytes, btcec.S256()) } func FetchAIPublicKeyFromPrivateKey(privateKey string) (string, error) { pkBytes, err := hex.DecodeString(privateKey) if err != nil { return "", err } _, btcPublicKey := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes) return hex.EncodeToString(btcPublicKey.SerializeCompressed()), nil } ================================================ FILE: libs/go/aealite/wallet/wallet.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package wallet import ( "log" "os" "github.com/joho/godotenv" "github.com/rs/zerolog" ) var logger zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: "15:04:05.000", }). With().Timestamp(). Str("package", "Wallet"). Logger() type Wallet struct { LedgerId string Address string PublicKey string PrivateKey string } func (wallet *Wallet) InitFromEnv(envFile string) error { logger.Debug().Msgf("env_file: %s", envFile) err := godotenv.Overload(envFile) if err != nil { logger.Error().Str("err", err.Error()). Msg("Error loading env file") return err } wallet.LedgerId = os.Getenv("AEA_LEDGER_ID") if wallet.LedgerId == "" { log.Fatal("No AEA_LEDGER_ID provided in env file.") } wallet.Address = os.Getenv("AEA_ADDRESS") wallet.PublicKey = os.Getenv("AEA_PUBLIC_KEY") wallet.PrivateKey = os.Getenv("AEA_PRIVATE_KEY") if wallet.PrivateKey == "" { log.Fatal("No AEA_PRIVATE_KEY provided in env file.") } public_key, err := PublicKeyFromPrivateKey(wallet.LedgerId, wallet.PrivateKey) if err != nil { log.Fatal("Could not derive public key.") } if (wallet.PublicKey != "") && (public_key != wallet.PublicKey) { log.Fatal("Derived and provided public_key don't match.") } wallet.PublicKey = public_key address, err := AddressFromPublicKey(wallet.LedgerId, wallet.PublicKey) if err != nil { log.Fatal("Could not derive address.") } if (wallet.Address != "") && (address != wallet.Address) { log.Fatal("Derived and provided address don't match.") } wallet.Address = address return nil } ================================================ FILE: libs/go/aealite/wallet/wallet_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package wallet import ( "testing" ) const ( EnvTestFile = "../test_env_file.env" ) var ( ledger_id = "fetchai" address = "fetch1x9v67meyfq4pkgy2n2yf6797cfkul327kpclqr" public_key = "02ac514ba70de60ed5c30f90e3acdfc958ecb416d9676706bf013228abfb2c2816" private_key = "6d8d2b87d987641e2ca3f1991c1cccf08a118759e81fabdbf7e8484f27af015e" ) // TestWallet func TestWallet(t *testing.T) { wallet := Wallet{} // initialise err := wallet.InitFromEnv(EnvTestFile) if err != nil { t.Fatal("Failed to initialise wallet", err) } if wallet.LedgerId != ledger_id { t.Fatal("Wallet.LedgerId not set") } if wallet.Address != address { t.Fatal("Wallet.Address not set") } if wallet.PublicKey != public_key { t.Fatal("Wallet.PublicKey not set") } if wallet.PrivateKey != private_key { t.Fatal("Wallet.PrivateKey not set") } } ================================================ FILE: libs/go/libp2p_node/.dockerignore ================================================ libp2p_node ================================================ FILE: libs/go/libp2p_node/Dockerfile ================================================ FROM golang:1.17.5-buster as builder USER root WORKDIR /build COPY ./ ./ RUN go build FROM scratch AS export-stage COPY --from=builder /build/libp2p_node libp2p_node ================================================ FILE: libs/go/libp2p_node/Makefile ================================================ test: go test -gcflags=-l -p 1 -timeout 0 -count 1 -covermode=atomic -coverprofile=coverage.txt -v ./... go tool cover -func=coverage.txt lint: golines . -w golangci-lint run build: go build install: go get -v -t -d ./... race_test: go test -gcflags=-l -p 1 -timeout 0 -count 1 -race -v ./... build_in_docker: DOCKER_BUILDKIT=1 docker build --output . . ================================================ FILE: libs/go/libp2p_node/README.md ================================================ # Libp2p Node The `libp2p_node` is an integral part of the ACN. ## ACN - Agent Communication Network The agent communication network (ACN) provides a system for agents to find each other and communicate, solely based on their wallet addresses. It addresses the message delivery problem. For more details check out the [docs](https://github.com/fetchai/agents-aea/blob/main/docs/acn.md). ## Development To run all tests run: ``` bash go test -p 1 -timeout 0 -count 1 -v ./... ``` To lint: ``` bash golines . -w golangci-lint run staticcheck ./... ``` For mocks generation: check ## Messaging patterns Interaction protocol ___ ACN ___ TCP/UDP/... ___ ### Messaging patterns inwards ACN Connection (`p2p_libp2p_client`) > Delegate Client > Relay Peer > Peer (Discouraged!) Connection (`p2p_libp2p_client`) > Delegate Client > Peer Connection (`p2p_libp2p`) > Relay Peer > Peer Connection (`p2p_libp2p`) > Peer ### Messaging patterns outwards ACN Peer > Relay Peer > Delegate Client > Connection (`p2p_libp2p_client`) (Discouraged!) Peer > Relay Peer > Connection (`p2p_libp2p`) Peer > Delegate Client > Connection (`p2p_libp2p_client`) Peer > Connection (`p2p_libp2p`) In total 4*4 = 16 patterns (practically: 3*3 = 9 patterns) ## Guarantees ACN should guarantee total ordering of messages for all agent pairs, independent of the type of connection and ACN messaging pattern used. ## Advanced feature (post `v1`) Furthermore, there is the agent mobility. An agent can move between entry-points (Relay Peer/Peer/Delegate Client). The ACN must ensure that all messaging patterns maintain total ordering of messages for agent pairs during the move. ## ACN protocols The ACN has the following protocols: - register - lookup - unregister (dealt with by DHT defaults) - DHT default protocols in libp2p - message delivery protocol ================================================ FILE: libs/go/libp2p_node/acn/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package acn import ( "errors" "fmt" "os" "strings" "time" acn_protocol "libp2p_node/protocols/acn/v1_0_0" "github.com/rs/zerolog" proto "google.golang.org/protobuf/proto" ) type StatusBody = acn_protocol.AcnMessage_StatusBody type AgentRecord = acn_protocol.AcnMessage_AgentRecord type AcnMessage = acn_protocol.AcnMessage type LookupRequest = acn_protocol.AcnMessage_LookupRequest type LookupResponse = acn_protocol.AcnMessage_LookupResponse type Status = acn_protocol.AcnMessage_Status type LookupRequestPerformative = acn_protocol.AcnMessage_Lookup_Request_Performative type LookupResponsePerformative = acn_protocol.AcnMessage_Lookup_Response_Performative type StatusPerformative = acn_protocol.AcnMessage_Status_Performative type RegisterPerformative = acn_protocol.AcnMessage_Register_Performative type Register = acn_protocol.AcnMessage_Register type AeaEnvelope = acn_protocol.AcnMessage_AeaEnvelope type AeaEnvelopePerformative = acn_protocol.AcnMessage_Aea_Envelope_Performative const ERROR_DECODE = acn_protocol.AcnMessage_StatusBody_ERROR_DECODE const SUCCESS = acn_protocol.AcnMessage_StatusBody_SUCCESS const ERROR_UNEXPECTED_PAYLOAD = acn_protocol.AcnMessage_StatusBody_ERROR_UNEXPECTED_PAYLOAD const ERROR_AGENT_NOT_READY = acn_protocol.AcnMessage_StatusBody_ERROR_AGENT_NOT_READY const ERROR_UNKNOWN_AGENT_ADDRESS = acn_protocol.AcnMessage_StatusBody_ERROR_UNKNOWN_AGENT_ADDRESS const ERROR_GENERIC = acn_protocol.AcnMessage_StatusBody_ERROR_GENERIC const ERROR_WRONG_AGENT_ADDRESS = acn_protocol.AcnMessage_StatusBody_ERROR_WRONG_AGENT_ADDRESS const ERROR_UNSUPPORTED_LEDGER = acn_protocol.AcnMessage_StatusBody_ERROR_UNSUPPORTED_LEDGER const ERROR_WRONG_PUBLIC_KEY = acn_protocol.AcnMessage_StatusBody_ERROR_WRONG_PUBLIC_KEY const ERROR_INVALID_PROOF = acn_protocol.AcnMessage_StatusBody_ERROR_INVALID_PROOF type Status_ErrCode = acn_protocol.AcnMessage_StatusBody_StatusCodeEnum var logger zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: "15:04:05.000", }). With().Timestamp(). Str("package", "AeaApiACN"). Logger() const CurrentVersion = "0.1.0" func ignore(err error) { if err != nil { logger.Error().Str("err", err.Error()).Msgf("IGNORED: %s", err.Error()) } } type ACNError struct { ErrorCode Status_ErrCode Err error } func (err *ACNError) Error() string { return err.Err.Error() } func DecodeAcnMessage(buf []byte) (string, *AeaEnvelopePerformative, *StatusBody, *ACNError) { response := &AcnMessage{} err := proto.Unmarshal(buf, response) msg_type := "" if err != nil { logger.Error().Str("err", err.Error()).Msgf("while decoding acn message") return msg_type, nil, nil, &ACNError{ErrorCode: ERROR_DECODE, Err: err} } // response is either a LookupResponse or Status var aeaEnvelope *AeaEnvelopePerformative = nil var status *StatusBody = nil switch pl := response.Performative.(type) { case *AeaEnvelope: aeaEnvelope = pl.AeaEnvelope msg_type = "aea_envelope" case *Status: status = pl.Status.Body msg_type = "status" default: err = fmt.Errorf("unexpected ACN Message: %s", response) logger.Error().Msg(err.Error()) return msg_type, nil, nil, &ACNError{ErrorCode: ERROR_UNEXPECTED_PAYLOAD, Err: err} } return msg_type, aeaEnvelope, status, nil } func WaitForStatus(ch chan *StatusBody, timeout time.Duration) (*StatusBody, error) { select { case m := <-ch: return m, nil case <-time.After(timeout): err := errors.New("ACN send acknowledge timeout") logger.Error().Msg(err.Error()) return nil, err } } func SendAcnSuccess(pipe Pipe) error { status := &StatusBody{Code: SUCCESS} performative := &StatusPerformative{Body: status} msg := &AcnMessage{ Performative: &Status{Status: performative}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on encoding acn status message") return err } err = pipe.Write(buf) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on sending acn status message") } return err } func SendAcnError(pipe Pipe, error_msg string, err_codes ...Status_ErrCode) error { var err_code Status_ErrCode if len(err_codes) == 0 { err_code = ERROR_GENERIC } else { err_code = err_codes[0] } status := &StatusBody{Code: err_code, Msgs: []string{error_msg}} msg := &AcnMessage{ Performative: &Status{Status: &StatusPerformative{Body: status}}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on encoding acn status message") return err } err = pipe.Write(buf) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on sending acn status message") } return err } func EncodeAcnEnvelope(envelope_bytes []byte, record *AgentRecord) ([]byte, error) { var performative *AeaEnvelopePerformative if record != nil { performative = &AeaEnvelopePerformative{Envelope: envelope_bytes, Record: record} } else { performative = &AeaEnvelopePerformative{Envelope: envelope_bytes} } msg := &AcnMessage{ Performative: &AeaEnvelope{AeaEnvelope: performative}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("while serializing envelope bytes: %s", envelope_bytes) } return buf, err } type Pipe interface { Connect() error Read() ([]byte, error) Write(data []byte) error Close() error } type StatusQueue interface { AddAcnStatusMessage(status *StatusBody, counterpartyID string) } func ReadAgentRegistrationMessage(pipe Pipe) (*RegisterPerformative, error) { var register *RegisterPerformative buf, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msgf("while receiving agent's registration request") return nil, err } msg := &AcnMessage{} err = proto.Unmarshal(buf, msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("couldn't deserialize acn registration message") // TOFIX(LR) setting Msgs to err.Error is potentially a security vulnerability acn_send_error := SendAcnError(pipe, err.Error(), ERROR_DECODE) ignore(acn_send_error) return nil, err } switch pl := msg.Performative.(type) { case *Register: register = pl.Register default: err = errors.New("Unexpected payload") acn_send_error := SendAcnError(pipe, err.Error(), ERROR_UNEXPECTED_PAYLOAD) ignore(acn_send_error) return nil, err } return register, nil } func SendEnvelopeMessageAndWaitForStatus( pipe Pipe, envelope_bytes []byte, acn_status_chan chan *StatusBody, acnStatusTimeout time.Duration, ) error { err := SendEnvelopeMessage(pipe, envelope_bytes, nil) if err != nil { return err } status, err := WaitForStatus(acn_status_chan, acnStatusTimeout) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on envelope sent status wait") return err } if status.Code != SUCCESS { logger.Error(). Str("op", "send_envelope"). Msgf("acn confirmation status is not Status Success: %d.", status.Code) return fmt.Errorf( "send envelope: acn confirmation status is not Status Success: %d", status.Code, ) } return err } func ReadLookupRequest(pipe Pipe) (string, error) { buf, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msgf("while reading message from stream") return "", err } msg := &AcnMessage{} err = proto.Unmarshal(buf, msg) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("couldn't deserialize acn lookup request message") // TOFIX(LR) setting Msgs to err.Error is potentially a security vulnerability acn_send_error := SendAcnError(pipe, err.Error(), ERROR_DECODE) ignore(acn_send_error) return "", err } // Get LookupRequest message var lookupRequest *LookupRequestPerformative switch pl := msg.Performative.(type) { case *LookupRequest: lookupRequest = pl.LookupRequest default: err = errors.New("Unexpected payload") acn_send_error := SendAcnError(pipe, err.Error(), ERROR_UNEXPECTED_PAYLOAD) ignore(acn_send_error) return "", err } reqAddress := lookupRequest.AgentAddress return reqAddress, nil } func SendLookupRequest(pipe Pipe, address string) error { lookupRequest := &LookupRequestPerformative{AgentAddress: address} msg := &AcnMessage{ Performative: &LookupRequest{LookupRequest: lookupRequest}, } buf, err := proto.Marshal(msg) if err != nil { return err } err = pipe.Write([]byte(buf)) return err } func ReadLookupResponse(pipe Pipe) (*AgentRecord, error) { buf, err := pipe.Read() if err != nil { return nil, err } response := &AcnMessage{} err = proto.Unmarshal(buf, response) if err != nil { return nil, err } var lookupResponse *LookupResponsePerformative = nil var status *StatusPerformative = nil switch pl := response.Performative.(type) { case *LookupResponse: lookupResponse = pl.LookupResponse case *Status: status = pl.Status default: err = errors.New("Unexpected Acn Message") logger.Error().Str("err", err.Error()).Msgf("couldn't deserialize acn lookup response message") return nil, err } if status != nil { err = errors.New( "Failed agent lookup response " + status.Body.Code.String() + " : " + strings.Join( status.Body.Msgs, ":", ), ) return nil, err } return lookupResponse.Record, nil } func SendLookupResponse(pipe Pipe, record *AgentRecord) error { lookupResponse := &LookupResponsePerformative{Record: record} response := &AcnMessage{ Performative: &LookupResponse{LookupResponse: lookupResponse}, } buf, err := proto.Marshal(response) if err != nil { return err } err = pipe.Write(buf) return err } func SendEnvelopeMessage(pipe Pipe, envelope_bytes []byte, record *AgentRecord) error { acnMsgBytes, err := EncodeAcnEnvelope(envelope_bytes, record) if err != nil { return err } err = pipe.Write(acnMsgBytes) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on pipe write") return err } return nil } func SendAgentRegisterMessage(pipe Pipe, agentRecord *AgentRecord) error { registration := &RegisterPerformative{Record: agentRecord} msg := &AcnMessage{ Performative: &Register{Register: registration}, } buf, err := proto.Marshal(msg) if err != nil { return err } err = pipe.Write(buf) if err != nil { return err } status, err := ReadAcnStatus(pipe) if err != nil { return err } if status.Code != SUCCESS { return errors.New("Registration failed: " + strings.Join(status.Msgs, ":")) } return nil } func ReadAcnStatus(pipe Pipe) (*StatusBody, error) { buf, err := pipe.Read() if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on pipe read") return nil, err } response := &AcnMessage{} err = proto.Unmarshal(buf, response) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on acn decode") return nil, err } // response is expected to be a Status var status *StatusPerformative switch pl := response.Performative.(type) { case *Status: status = pl.Status default: err = errors.New("Unexpected Acn Message") return nil, err } return status.Body, nil } func ReadEnvelopeMessage(pipe Pipe) (*AeaEnvelopePerformative, error) { buf, err := pipe.Read() if err != nil { return nil, err } messageType, envelope, _, acnErr := DecodeAcnMessage(buf) if acnErr != nil { err = SendAcnError( pipe, acnErr.Error(), acnErr.ErrorCode, ) ignore(err) return nil, acnErr.Err } if messageType != "aea_envelope" { return nil, errors.New("unexpected payload for acn message") } return envelope, nil } func PerformAddressLookup(pipe Pipe, address string) (*AgentRecord, error) { err := SendLookupRequest(pipe, address) if err != nil { return nil, err } return ReadLookupResponse(pipe) } ================================================ FILE: libs/go/libp2p_node/aea/api.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aea import ( "errors" "log" "net" "os" "strconv" "strings" "time" "github.com/joho/godotenv" "github.com/rs/zerolog" proto "google.golang.org/protobuf/proto" acn "libp2p_node/acn" common "libp2p_node/common" ) const AcnStatusTimeout = 15.0 * time.Second const SendQueueSize = 100 const OutQueueSize = 100 // code redandency to avoid import cycle var logger zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: "15:04:05.000", }). With().Timestamp(). Str("package", "AeaApi"). Logger() /* AeaApi type */ type AeaApi struct { msgin_path string msgout_path string agent_addr string agent_record *acn.AgentRecord id string entry_peers []string host string port uint16 host_public string port_public uint16 host_delegate string port_delegate uint16 host_monitoring string port_monitoring uint16 mailbox_uri string registrationDelay float64 recordsStoragePath string pipe common.Pipe out_queue chan *Envelope send_queue chan *Envelope closing bool connected bool sandbox bool standalone bool acn_status_chan chan *acn.StatusBody } func (aea AeaApi) MailboxUri() string { return aea.mailbox_uri } func (aea AeaApi) AeaAddress() string { return aea.agent_addr } func (aea AeaApi) PrivateKey() string { return aea.id } func (aea AeaApi) Address() (string, uint16) { return aea.host, aea.port } func (aea AeaApi) PublicAddress() (string, uint16) { return aea.host_public, aea.port_public } func (aea AeaApi) DelegateAddress() (string, uint16) { return aea.host_delegate, aea.port_delegate } func (aea AeaApi) MonitoringAddress() (string, uint16) { return aea.host_monitoring, aea.port_monitoring } func (aea AeaApi) EntryPeers() []string { return aea.entry_peers } func (aea AeaApi) AgentRecord() *acn.AgentRecord { return aea.agent_record } func (aea AeaApi) RegistrationDelayInSeconds() float64 { return aea.registrationDelay } func (aea AeaApi) RecordStoragePath() string { return aea.recordsStoragePath } func (aea AeaApi) Put(envelope *Envelope) error { if aea.standalone { errorMsg := "node running in standalone mode" logger.Warn().Msgf(errorMsg) return errors.New(errorMsg) } aea.send_queue <- envelope return nil } func (aea *AeaApi) Get() *Envelope { if aea.standalone { errorMsg := "node running in standalone mode" logger.Warn().Msgf(errorMsg) return nil } return <-aea.out_queue } func (aea *AeaApi) Queue() <-chan *Envelope { return aea.out_queue } func (aea *AeaApi) Connected() bool { return aea.connected || aea.standalone } func (aea *AeaApi) Stop() { aea.send_queue <- nil aea.closing = true aea.stop() close(aea.out_queue) close(aea.send_queue) } func (aea *AeaApi) Init() error { zerolog.TimeFieldFormat = time.RFC3339Nano if aea.sandbox { return nil } if aea.connected { return nil } aea.connected = false env_file := os.Args[1] logger.Debug().Msgf("env_file: %s", env_file) // get config err := godotenv.Overload(env_file) if err != nil { log.Fatal("Error loading env file") } aea.msgin_path = os.Getenv("AEA_TO_NODE") aea.msgout_path = os.Getenv("NODE_TO_AEA") aea.agent_addr = os.Getenv("AEA_AGENT_ADDR") aea.id = os.Getenv("AEA_P2P_ID") entry_peers := os.Getenv("AEA_P2P_ENTRY_URIS") uri := os.Getenv("AEA_P2P_URI") uri_public := os.Getenv("AEA_P2P_URI_PUBLIC") uri_delegate := os.Getenv("AEA_P2P_DELEGATE_URI") uri_monitoring := os.Getenv("AEA_P2P_URI_MONITORING") por_address := os.Getenv("AEA_P2P_POR_ADDRESS") if por_address != "" { record := &acn.AgentRecord{Address: por_address} record.PublicKey = os.Getenv("AEA_P2P_POR_PUBKEY") record.PeerPublicKey = os.Getenv("AEA_P2P_POR_PEER_PUBKEY") record.Signature = os.Getenv("AEA_P2P_POR_SIGNATURE") record.ServiceId = os.Getenv("AEA_P2P_POR_SERVICE_ID") record.LedgerId = os.Getenv("AEA_P2P_POR_LEDGER_ID") aea.agent_record = record } aea.mailbox_uri = os.Getenv("AEA_P2P_MAILBOX_URI") registrationDelay := os.Getenv("AEA_P2P_CFG_REGISTRATION_DELAY") aea.recordsStoragePath = os.Getenv("AEA_P2P_CFG_STORAGE_PATH") logger.Debug().Msgf("msgin_path: %s", aea.msgin_path) logger.Debug().Msgf("msgout_path: %s", aea.msgout_path) logger.Debug().Msgf("id: %s", aea.id) logger.Debug().Msgf("addr: %s", aea.agent_addr) logger.Debug().Msgf("entry_peers: %s", entry_peers) logger.Debug().Msgf("uri: %s", uri) logger.Debug().Msgf("uri public: %s", uri_public) logger.Debug().Msgf("uri delegate service: %s", uri_delegate) if aea.id == "" || uri == "" { err := errors.New("couldn't get AEA configuration: key and uri are required") logger.Error().Str("err", err.Error()).Msg("") return err } if aea.msgin_path == "" && aea.msgout_path == "" && aea.agent_addr == "" { aea.standalone = true } else if aea.msgin_path == "" || aea.msgout_path == "" || aea.agent_addr == "" { err := errors.New("couldn't get AEA configuration: pipes paths are required when agent address is provided") logger.Error().Str("err", err.Error()).Msg("") return err } // parse uri parts := strings.SplitN(uri, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host = parts[0] port, _ := strconv.ParseUint(parts[1], 10, 16) aea.port = uint16(port) // hack: test if port is taken addr, err := net.ResolveTCPAddr("tcp", uri) if err != nil { return err } listener, err := net.ListenTCP("tcp", addr) if err != nil { logger.Error().Str("err", err.Error()).Msgf("Uri already taken %s", uri) return err } listener.Close() // parse public uri if uri_public != "" { parts = strings.SplitN(uri_public, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri_public) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host_public = parts[0] port, _ = strconv.ParseUint(parts[1], 10, 16) aea.port_public = uint16(port) } else { aea.host_public = "" aea.port_public = 0 } // parse delegate uri if uri_delegate != "" { parts = strings.SplitN(uri_delegate, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri_delegate) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host_delegate = parts[0] port, _ = strconv.ParseUint(parts[1], 10, 16) aea.port_delegate = uint16(port) } else { aea.host_delegate = "" aea.port_delegate = 0 } // parse monitoring uri if uri_monitoring != "" { parts = strings.SplitN(uri_monitoring, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri_monitoring) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host_monitoring = parts[0] port, _ = strconv.ParseUint(parts[1], 10, 16) aea.port_monitoring = uint16(port) } else { aea.host_monitoring = "" aea.port_monitoring = 0 } // parse entry peers multiaddress if len(entry_peers) > 0 { aea.entry_peers = strings.SplitN(entry_peers, ",", -1) } // parse registration delay if registrationDelay == "" { aea.registrationDelay = 0.0 } else { delay, err := strconv.ParseFloat(registrationDelay, 32) if err != nil { logger.Error().Str("err", err.Error()).Msgf("malformed RegistrationDelay value") return err } aea.registrationDelay = delay } // setup pipe if !aea.standalone { aea.pipe = NewPipe(aea.msgin_path, aea.msgout_path) } aea.acn_status_chan = make(chan *acn.StatusBody, 1000) return nil } func (aea *AeaApi) Connect() error { if aea.standalone { logger.Info().Msg("Successfully running in standalone mode") return nil } // open pipes err := aea.pipe.Connect() if err != nil { logger.Error().Str("err", err.Error()). Msg("while connecting to pipe") return err } aea.closing = false //TOFIX(LR) trade-offs between bufferd vs unbuffered channel aea.out_queue = make(chan *Envelope, OutQueueSize) aea.send_queue = make(chan *Envelope, SendQueueSize) go aea.listenForEnvelopes() go aea.envelopeSendLoop() logger.Info().Msg("connected to agent") aea.connected = true return nil } func (aea *AeaApi) listenForEnvelopes() { //TOFIX(LR) add an exit strategy for { envel, err := HandleAcnMessageFromPipe(aea.pipe, aea, aea.AeaAddress()) var e *common.PipeError if errors.As(err, &e) { logger.Error(). Str("err", err.Error()). Msg("pipe error while receiving envelope. disconnect") logger.Info().Msg("disconnecting") if !aea.closing { aea.Stop() } return } if err != nil { logger.Error().Str("err", err.Error()).Msg("while receiving envelope. skip") continue } if envel == nil { // ACN STATUS MSG continue } if envel.Sender != aea.agent_record.Address { logger.Error(). Str("err", "Sender ("+envel.Sender+") must match registered address"). Msg("while processing envelope") // TODO send error back to agent continue } logger.Debug().Msgf("received envelope from agent") aea.out_queue <- envel if aea.closing { return } } } func (aea *AeaApi) envelopeSendLoop() { logger.Debug().Msg("send loop started") var err error for { envelope := <-aea.send_queue if envelope == nil { logger.Info().Msg("envelope is nil. exit send loop") return } err = aea.SendEnvelope(envelope) if err != nil { logger.Error().Str("err", err.Error()).Msg("while sending envelope") } else { logger.Debug().Msg("envelope sent") } if aea.closing { return } } } func (aea *AeaApi) stop() { err := aea.pipe.Close() if err != nil { logger.Error().Str("err", err.Error()).Msgf("on pipe close during aeaapi stop") } } /* Pipes helpers */ const CurrentVersion = "0.1.0" func MakeAcnMessageFromEnvelope(envelope *Envelope) ([]byte, error) { envelope_bytes, err := proto.Marshal(envelope) if err != nil { return envelope_bytes, err } return acn.EncodeAcnEnvelope(envelope_bytes, nil) } func (aea AeaApi) SendEnvelope(envelope *Envelope) error { return SendEnvelope(aea.pipe, aea.acn_status_chan, envelope, AcnStatusTimeout) } func SendEnvelope( pipe acn.Pipe, acn_status_chan chan *acn.StatusBody, envelope *Envelope, acnStatusTimeout time.Duration, ) error { envelope_bytes, err := proto.Marshal(envelope) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("while serializing envelope: %s", envelope.String()) return err } err = acn.SendEnvelopeMessageAndWaitForStatus( pipe, envelope_bytes, acn_status_chan, acnStatusTimeout, ) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on send envelope: %s", envelope.String()) return err } return nil } func (aea AeaApi) AddAcnStatusMessage(status *acn.StatusBody, counterpartyID string) { aea.acn_status_chan <- status logger.Info().Msgf("chan len is %d", len(aea.acn_status_chan)) } ================================================ FILE: libs/go/libp2p_node/aea/envelope.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc (unknown) // source: envelope.proto package aea import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Envelope struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields To string `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` ProtocolId string `protobuf:"bytes,3,opt,name=protocol_id,json=protocolId,proto3" json:"protocol_id,omitempty"` Message []byte `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` Uri string `protobuf:"bytes,5,opt,name=uri,proto3" json:"uri,omitempty"` } func (x *Envelope) Reset() { *x = Envelope{} if protoimpl.UnsafeEnabled { mi := &file_envelope_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Envelope) String() string { return protoimpl.X.MessageStringOf(x) } func (*Envelope) ProtoMessage() {} func (x *Envelope) ProtoReflect() protoreflect.Message { mi := &file_envelope_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Envelope.ProtoReflect.Descriptor instead. func (*Envelope) Descriptor() ([]byte, []int) { return file_envelope_proto_rawDescGZIP(), []int{0} } func (x *Envelope) GetTo() string { if x != nil { return x.To } return "" } func (x *Envelope) GetSender() string { if x != nil { return x.Sender } return "" } func (x *Envelope) GetProtocolId() string { if x != nil { return x.ProtocolId } return "" } func (x *Envelope) GetMessage() []byte { if x != nil { return x.Message } return nil } func (x *Envelope) GetUri() string { if x != nil { return x.Uri } return "" } var File_envelope_proto protoreflect.FileDescriptor var file_envelope_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x65, 0x61, 0x22, 0x7f, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_envelope_proto_rawDescOnce sync.Once file_envelope_proto_rawDescData = file_envelope_proto_rawDesc ) func file_envelope_proto_rawDescGZIP() []byte { file_envelope_proto_rawDescOnce.Do(func() { file_envelope_proto_rawDescData = protoimpl.X.CompressGZIP(file_envelope_proto_rawDescData) }) return file_envelope_proto_rawDescData } var file_envelope_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_envelope_proto_goTypes = []interface{}{ (*Envelope)(nil), // 0: aea.Envelope } var file_envelope_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_envelope_proto_init() } func file_envelope_proto_init() { if File_envelope_proto != nil { return } if !protoimpl.UnsafeEnabled { file_envelope_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Envelope); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_envelope_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_envelope_proto_goTypes, DependencyIndexes: file_envelope_proto_depIdxs, MessageInfos: file_envelope_proto_msgTypes, }.Build() File_envelope_proto = out.File file_envelope_proto_rawDesc = nil file_envelope_proto_goTypes = nil file_envelope_proto_depIdxs = nil } ================================================ FILE: libs/go/libp2p_node/aea/envelope.proto ================================================ syntax = "proto3"; //package libp2p_node.aea; package aea; message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; bytes message = 4; string uri = 5; } ================================================ FILE: libs/go/libp2p_node/aea/pipe.go ================================================ // +build windows linux darwin /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aea import ( "encoding/binary" "errors" common "libp2p_node/common" "math" "net" "strconv" ) type TCPSocketChannel struct { port uint16 conn net.Conn } func (sock *TCPSocketChannel) Connect() error { // open tcp connection var err error sock.conn, err = net.Dial("tcp", "127.0.0.1:"+strconv.FormatInt(int64(sock.port), 10)) if err != nil { return err } return nil } func (sock *TCPSocketChannel) Read() ([]byte, error) { // TOFIX(LR) duplicated code to avoid circular dep // utils.ReadBytesConn(sock.conn) buf := make([]byte, 4) _, err := sock.conn.Read(buf) if err != nil { return buf, err } size := binary.BigEndian.Uint32(buf) buf = make([]byte, size) _, err = sock.conn.Read(buf) return buf, err } func (sock *TCPSocketChannel) Write(data []byte) error { // TOFIX(LR) duplicated code to avoid circular dep // utils.WriteBytesConn(sock.conn, data) if len(data) > math.MaxInt32 { return errors.New("value too large") } size := uint32(len(data)) buf := make([]byte, 4, 4+size) binary.BigEndian.PutUint32(buf, size) buf = append(buf, data...) _, err := sock.conn.Write(buf) logger.Debug().Msgf("wrote data to pipe: %d bytes", size) return err } func (sock *TCPSocketChannel) Close() error { return sock.conn.Close() } func NewPipe(msgin_path string, msgout_path string) common.Pipe { port, _ := strconv.ParseUint(msgin_path, 10, 16) return &TCPSocketChannel{port: uint16(port)} } ================================================ FILE: libs/go/libp2p_node/aea/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aea import ( "errors" acn "libp2p_node/acn" common "libp2p_node/common" proto "google.golang.org/protobuf/proto" ) func HandleAcnMessageFromPipe( pipe common.Pipe, statusQueue acn.StatusQueue, counterpartyID string, ) (*Envelope, error) { envelope := &Envelope{} var acn_err error data, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msg("while receiving data") return nil, &common.PipeError{Err: err, Msg: "Pipe error during envelope read"} } msg_type, acn_envelope, status, acnErr := acn.DecodeAcnMessage(data) if acnErr != nil { logger.Error().Str("err", acnErr.Error()).Msg("while handling acn message") acn_err = acn.SendAcnError( pipe, acnErr.Error(), acnErr.ErrorCode, ) if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return envelope, acnErr } switch msg_type { case "aea_envelope": { err = proto.Unmarshal(acn_envelope.Envelope, envelope) if err != nil { logger.Error().Str("err", err.Error()).Msg("while decoding envelope") acn_err = acn.SendAcnError( pipe, "error on decoding envelope", acn.ERROR_DECODE, ) if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return envelope, err } err = acn.SendAcnSuccess(pipe) return envelope, err } case "status": { logger.Debug().Msgf("got acn status %d", status.Code) statusQueue.AddAcnStatusMessage(status, counterpartyID) return nil, nil } default: { acn_err = acn.SendAcnError(pipe, "Unsupported ACN message") if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return nil, errors.New("unsupported ACN message") } } } ================================================ FILE: libs/go/libp2p_node/common/common.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package common type PipeError struct { Err error Msg string } func (err *PipeError) Error() string { return err.Msg } func (err *PipeError) Unwrap() error { return err.Err } type Pipe interface { Connect() error Read() ([]byte, error) Write(data []byte) error Close() error } ================================================ FILE: libs/go/libp2p_node/dht/common/handlers.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtpeer provides an implementation of an Agent Communication Network node // using libp2p. It participates in data storage and routing for the network. // It offers RelayService for dhtclient and DelegateService for tcp clients. package common import ( "strings" "log" "github.com/libp2p/go-libp2p-core/network" "github.com/pkg/errors" "github.com/rs/zerolog" "google.golang.org/protobuf/proto" acn "libp2p_node/acn" aea "libp2p_node/aea" "libp2p_node/dht/dhtnode" utils "libp2p_node/utils" ) func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } //Abstract DHTHandler that provides logging function and handle for incoming envelopes and ACN address requests type DHTHandler interface { GetLoggers() (func(error) *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event) HandleAeaEnvelope(envel *aea.Envelope) *acn.ACNError HandleAeaAddressRequest(reqAddress string) (*acn.AgentRecord, *acn.ACNError) } //read ACN message, decode envelope, check PoR func receiveEnvelopeFromPeer(dhtHandler DHTHandler, stream network.Stream) (*aea.Envelope, error) { lerror, _, _, _ := dhtHandler.GetLoggers() streamPipe := utils.StreamPipe{Stream: stream} aeaEnvelope, err := acn.ReadEnvelopeMessage(streamPipe) if err != nil { lerror(err).Msg("while handling acn envelope message") return nil, err } envel := &aea.Envelope{} err = proto.Unmarshal(aeaEnvelope.Envelope, envel) if err != nil { lerror(err).Msg("while deserializing acn aea envelope message") ignore(acn.SendAcnError( streamPipe, "while deserializing acn aea envelope message", acn.ERROR_DECODE, )) return nil, err } remotePubkey, err := utils.FetchAIPublicKeyFromPubKey(stream.Conn().RemotePublicKey()) ignore(err) status, err := dhtnode.IsValidProofOfRepresentation( aeaEnvelope.Record, aeaEnvelope.Record.Address, remotePubkey, ) if err != nil || status.Code != acn.SUCCESS { if err == nil { err = errors.New(status.Code.String() + ":" + strings.Join(status.Msgs, ":")) } lerror(err).Msg("incoming envelope PoR is not valid") ignore(acn.SendAcnError(streamPipe, "incoming envelope PoR is not valid", status.Code)) return nil, err } return envel, nil } // handle envelope stream, handle acn protocol and call dhtHandler.HandleAeaEnvelope for incoming envelopes func HandleAeaEnvelopeStream(dhtHandler DHTHandler, stream network.Stream) { lerror, _, _, ldebug := dhtHandler.GetLoggers() //ldebug().Msgf("Got a new aea envelope stream") envel, err := receiveEnvelopeFromPeer(dhtHandler, stream) if err != nil { lerror(err).Msgf("while reading envelope from peer") stream.Close() return } ldebug().Msgf("Received envelope from peer %s", envel.String()) streamPipe := utils.StreamPipe{Stream: stream} acnError := dhtHandler.HandleAeaEnvelope(envel) if acnError != nil { err = acn.SendAcnError(streamPipe, acnError.Err.Error(), acnError.ErrorCode) ignore(err) err = stream.Close() ignore(err) return } err = acn.SendAcnSuccess(streamPipe) ignore(err) err = stream.Close() ignore(err) } // handle address request stream, handle acn protocol and call dhtHandler.HandleAeaAddressRequest for incoming requests func HandleAeaAddressStream(dhtHandler DHTHandler, stream network.Stream) { lerror, _, _, ldebug := dhtHandler.GetLoggers() //ldebug().Msg("Got a new aea address stream") // get LookupRequest streamPipe := utils.StreamPipe{Stream: stream} reqAddress, err := acn.ReadLookupRequest(streamPipe) if err != nil { lerror(err).Str("op", "resolve"). Msg("while reading message from stream") err = stream.Reset() ignore(err) return } ldebug(). Str("op", "resolve"). Str("target", reqAddress). Msg("Received query for addr") record, acnError := dhtHandler.HandleAeaAddressRequest(reqAddress) if acnError != nil { lerror(acnError.Err). Str("op", "resolve"). Str("target", reqAddress). Msgf("request address error") err = acn.SendAcnError(streamPipe, acnError.Err.Error(), acnError.ErrorCode) ignore(err) err = stream.Close() ignore(err) return } if record == nil { lerror(acnError.Err). Str("op", "resolve"). Str("target", reqAddress). Msgf("unexpected error. agent record is nil!") return } err = acn.SendLookupResponse(streamPipe, record) if err != nil { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msg("while sending agent record to peer") err = stream.Reset() ignore(err) } } ================================================ FILE: libs/go/libp2p_node/dht/dhtclient/dhtclient.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtclient provides implementation of a lightweight Agent Communication Network // node. It doesn't participate in network maintenance. It doesn't require a public IP // address either, as it relies on a DHTPeer (relay peer) to communicate with other peers. package dhtclient import ( "context" "log" "math/rand" "strings" "time" "github.com/pkg/errors" "github.com/rs/zerolog" "google.golang.org/protobuf/proto" libp2p "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" "github.com/multiformats/go-multiaddr" peerstore "github.com/libp2p/go-libp2p-core/peerstore" kaddht "github.com/libp2p/go-libp2p-kad-dht" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" acn "libp2p_node/acn" aea "libp2p_node/aea" common "libp2p_node/dht/common" "libp2p_node/dht/dhtnode" utils "libp2p_node/utils" ) func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } const ( newStreamTimeoutRelayPeer = 5 * 60 * time.Second // includes peer restart newStreamTimeout = 1 * 60 * time.Second // doesn't include peer restart bootstrapTimeout = 1 * 60 * time.Second // doesn't include peer restart sleepTimeDefaultDuration = 100 * time.Millisecond sleepTimeIncreaseMFactor = 2 // multiplicative increase reconnectTimeout = 5 * time.Second ) // Notifee Handle DHTClient network events type Notifee struct { myRelayPeer peer.AddrInfo myHost host.Host logger zerolog.Logger closing chan struct{} } // Listen called when network starts listening on an addr func (notifee *Notifee) Listen(network.Network, multiaddr.Multiaddr) {} // ListenClose called when network stops listening on an addr func (notifee *Notifee) ListenClose(network.Network, multiaddr.Multiaddr) {} // Connected called when a connection opened func (notifee *Notifee) Connected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Connected to peer %s", conn.RemotePeer().Pretty(), ) } // Disconnected called when a connection closed // Reconnects if connection is to relay peer and not currenctly closing connection. func (notifee *Notifee) Disconnected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Disconnected from peer %s", conn.RemotePeer().Pretty(), ) pinfo := notifee.myRelayPeer if conn.RemotePeer().Pretty() != pinfo.ID.Pretty() { return } notifee.myHost.Peerstore().AddAddrs(pinfo.ID, pinfo.Addrs, peerstore.PermanentAddrTTL) for { var err error select { case _, open := <-notifee.closing: if !open { return } default: notifee.logger.Warn().Msgf( "Lost connection to relay peer %s, reconnecting...", pinfo.ID.Pretty(), ) ctx, cancel := context.WithTimeout(context.Background(), reconnectTimeout) defer cancel() if err = notifee.myHost.Connect(ctx, pinfo); err == nil { break } time.Sleep(1 * time.Second) } if err == nil { break } } notifee.logger.Info().Msgf("Connection to relay peer %s reestablished", pinfo.ID.Pretty()) } // OpenedStream called when a stream opened func (notifee *Notifee) OpenedStream(network.Network, network.Stream) {} // ClosedStream called when a stream closed func (notifee *Notifee) ClosedStream(network.Network, network.Stream) {} // DHTClient A restricted libp2p node for the Agents Communication Network // It use a `DHTPeer` to communicate with other peers. type DHTClient struct { bootstrapPeers []peer.AddrInfo relayPeer peer.ID key crypto.PrivKey publicKey crypto.PubKey dht *kaddht.IpfsDHT routedHost *routedhost.RoutedHost myAgentAddress string myAgentRecord *acn.AgentRecord myAgentReady func() bool processEnvelope func(*aea.Envelope) error closing chan struct{} logger zerolog.Logger } // New creates a new DHTClient func New(opts ...Option) (*DHTClient, error) { var err error dhtClient := &DHTClient{} for _, opt := range opts { if err := opt(dhtClient); err != nil { return nil, err } } dhtClient.closing = make(chan struct{}) /* check correct configuration */ // private key if dhtClient.key == nil { return nil, errors.New("private key must be provided") } // agent address is mandatory if dhtClient.myAgentAddress == "" { return nil, errors.New("missing agent address") } // agent record is mandatory if dhtClient.myAgentRecord == nil { return nil, errors.New("missing agent record") } // check if the PoR is delivered for my public key myPublicKey, err := utils.FetchAIPublicKeyFromPubKey(dhtClient.publicKey) status, errPoR := dhtnode.IsValidProofOfRepresentation( dhtClient.myAgentRecord, dhtClient.myAgentRecord.Address, myPublicKey, ) if err != nil || errPoR != nil || status.Code != acn.SUCCESS { msg := "Invalid AgentRecord" if err != nil { msg += " - " + err.Error() } if errPoR != nil { msg += " - " + errPoR.Error() } return nil, errors.New(msg) } // bootstrap peers if len(dhtClient.bootstrapPeers) < 1 { return nil, errors.New("at least one boostrap peer should be provided") } // select a relay node randomly from entry peers rand.Seed(time.Now().Unix()) index := rand.Intn(len(dhtClient.bootstrapPeers)) dhtClient.relayPeer = dhtClient.bootstrapPeers[index].ID dhtClient.setupLogger() _, _, linfo, ldebug := dhtClient.GetLoggers() linfo().Msg("INFO Using as relay") /* setup libp2p node */ ctx := context.Background() // libp2p options libp2pOpts := []libp2p.Option{ libp2p.ListenAddrs(), libp2p.Identity(dhtClient.key), libp2p.DefaultTransports, libp2p.DefaultMuxers, libp2p.DefaultSecurity, libp2p.NATPortMap(), libp2p.EnableNATService(), libp2p.EnableRelay(), } // create a basic host basicHost, err := libp2p.New(ctx, libp2pOpts...) if err != nil { return nil, err } // create the dht dhtClient.dht, err = kaddht.New(ctx, basicHost, kaddht.Mode(kaddht.ModeClient)) if err != nil { return nil, err } // make the routed host dhtClient.routedHost = routedhost.Wrap(basicHost, dhtClient.dht) dhtClient.setupLogger() // connect to the booststrap nodes err = dhtClient.bootstrapLoopUntilTimeout() if err != nil { dhtClient.Close() return nil, err } // bootstrap the host err = dhtClient.dht.Bootstrap(ctx) if err != nil { dhtClient.Close() return nil, err } // register my address to relay peer err = dhtClient.registerAgentAddress() if err != nil { dhtClient.Close() return nil, err } dhtClient.routedHost.Network().Notify(&Notifee{ myRelayPeer: dhtClient.bootstrapPeers[index], myHost: dhtClient.routedHost, logger: dhtClient.logger, closing: dhtClient.closing, }) /* setup DHTClient message handlers */ // aea address lookup ldebug().Msg("DEBUG Setting /aea-address/0.1.0 stream...") dhtClient.routedHost.SetStreamHandler(dhtnode.AeaAddressStream, dhtClient.handleAeaAddressStream) // incoming envelopes stream ldebug().Msg("DEBUG Setting /aea/0.1.0 stream...") dhtClient.routedHost.SetStreamHandler(dhtnode.AeaEnvelopeStream, dhtClient.handleAeaEnvelopeStream) return dhtClient, nil } // bootstrapLoopUntilTimeout loops until connection to bootstrap peers established or timeout reached func (dhtClient *DHTClient) bootstrapLoopUntilTimeout() error { lerror, _, _, _ := dhtClient.GetLoggers() ctx, cancel := context.WithTimeout(context.Background(), bootstrapTimeout) defer cancel() err := utils.BootstrapConnect( ctx, dhtClient.routedHost, dhtClient.dht, dhtClient.bootstrapPeers, ) sleepTime := sleepTimeDefaultDuration for err != nil { lerror(err). Str("op", "bootstrap"). Msgf("couldn't open stream to bootstrap peer, retrying in %s", sleepTime) select { default: time.Sleep(sleepTime) sleepTime = sleepTime * sleepTimeIncreaseMFactor err = utils.BootstrapConnect( ctx, dhtClient.routedHost, dhtClient.dht, dhtClient.bootstrapPeers, ) case <-ctx.Done(): err = errors.New("bootstrap connect timeout reached") } } return err } // newStreamLoopUntilTimeout loops until stream to peer established or timeout reached func (dhtClient *DHTClient) newStreamLoopUntilTimeout( peerID peer.ID, streamType protocol.ID, timeout time.Duration, ) (network.Stream, error) { lerror, _, _, _ := dhtClient.GetLoggers() ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() stream, err := dhtClient.routedHost.NewStream(ctx, peerID, streamType) sleepTime := sleepTimeDefaultDuration disconnected := false for err != nil { disconnected = true lerror(err). Str("op", "route"). Msgf("couldn't open stream to peer %s, retrying in %s", peerID.Pretty(), sleepTime) select { default: time.Sleep(sleepTime) sleepTime = sleepTime * sleepTimeIncreaseMFactor stream, err = dhtClient.routedHost.NewStream(ctx, peerID, streamType) case <-ctx.Done(): err = errors.New("new stream loop timeout reached") } } if stream == nil && err == nil { return nil, errors.New("stream nil and err nil") } // register again in case of disconnection if disconnected { err = dhtClient.registerAgentAddress() } return stream, err } // setupLogger sets up a logger for the DHTClient func (dhtClient *DHTClient) setupLogger() { fields := map[string]string{ "package": "DHTClient", "relayid": dhtClient.relayPeer.Pretty(), } if dhtClient.routedHost != nil { fields["peerid"] = dhtClient.routedHost.ID().Pretty() } dhtClient.logger = utils.NewDefaultLoggerWithFields(fields) } // GetLoggers gets the various logger levels of the DHTClient func (dhtClient *DHTClient) GetLoggers() (func(error) *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event) { ldebug := dhtClient.logger.Debug linfo := dhtClient.logger.Info lwarn := dhtClient.logger.Warn lerror := func(err error) *zerolog.Event { if err == nil { return dhtClient.logger.Error().Str("err", "nil") } return dhtClient.logger.Error().Str("err", err.Error()) } return lerror, lwarn, linfo, ldebug } // Close stops the DHTClient // Closes the DHT and routedHost of the DHTClient func (dhtClient *DHTClient) Close() []error { var err error var status []error _, _, linfo, _ := dhtClient.GetLoggers() linfo().Msg("Stopping DHTClient...") close(dhtClient.closing) errappend := func(err error) { if err != nil { status = append(status, err) } } err = dhtClient.dht.Close() errappend(err) err = dhtClient.routedHost.Close() errappend(err) return status } // MultiAddr always return empty string func (dhtClient *DHTClient) MultiAddr() string { return "" } func (dhtClient *DHTClient) PeerID() string { return dhtClient.routedHost.ID().Pretty() } // RouteEnvelope routes the provided envelope to its destination contact peer func (dhtClient *DHTClient) RouteEnvelope(envel *aea.Envelope) error { lerror, lwarn, _, ldebug := dhtClient.GetLoggers() // only send envelopes from own agent if envel.Sender != dhtClient.myAgentAddress { err := errors.New("Sender (" + envel.Sender + ") must match registered address") lerror(err).Str("addr", dhtClient.myAgentAddress). Msgf("while routing envelope") return err } target := envel.To // TODO(LR) check if the record is valid if target == dhtClient.myAgentAddress { ldebug(). Str("op", "route"). Str("target", target). Msg("envelope destinated to my local agent...") for !dhtClient.myAgentReady() { ldebug(). Str("op", "route"). Str("target", target). Msg("agent not ready yet, sleeping for some time ...") time.Sleep(time.Duration(100) * time.Millisecond) } if dhtClient.processEnvelope != nil { err := dhtClient.processEnvelope(envel) if err != nil { return err } } else { lwarn(). Str("op", "route"). Str("target", target). Msgf("ProcessEnvelope not set, ignoring envelope %s", envel.String()) return nil } } // client can get addresses only through bootstrap peer stream, err := dhtClient.newStreamLoopUntilTimeout( dhtClient.relayPeer, dhtnode.AeaAddressStream, newStreamTimeoutRelayPeer, ) if err != nil { return err } ldebug(). Str("op", "route"). Str("target", target). Msg("requesting agent record from relay...") streamPipe := utils.StreamPipe{Stream: stream} record, err := acn.PerformAddressLookup(streamPipe, target) if err != nil { lerror(err).Str("op", "route").Str("target", target). Msgf("failed agent lookup") return err } valid, err := dhtnode.IsValidProofOfRepresentation(record, target, record.PeerPublicKey) if err != nil || valid.Code != acn.SUCCESS { errMsg := valid.Code.String() + " : " + strings.Join( valid.Msgs, ":", ) if err == nil { err = errors.New(errMsg) } else { err = errors.Wrap(err, valid.Code.String()+" : "+strings.Join(valid.Msgs, ":")) } lerror(err).Str("op", "route").Str("target", target). Msgf("invalid agent record") return err } stream.Close() // retrieve peerID peerID, err := utils.IDFromFetchAIPublicKey(record.PeerPublicKey) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("CRITICAL couldn't get peer ID from message") return errors.New("CRITICAL route - couldn't get peer ID from record peerID:" + err.Error()) } ldebug(). Str("op", "route"). Str("target", target). Msgf("got peer ID %s for agent Address", peerID.Pretty()) // TODO(LR): test if representative peer is relay peer, and skip the Connect if it is the case // TODO(DM): extract below multi-address creation for reuse and consistency multiAddr := "/p2p/" + dhtClient.relayPeer.Pretty() + "/p2p-circuit/p2p/" + peerID.Pretty() relayMultiaddr, err := multiaddr.NewMultiaddr(multiAddr) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msgf("while creating relay multiaddress %s", multiAddr) return err } peerRelayInfo := peer.AddrInfo{ ID: peerID, Addrs: []multiaddr.Multiaddr{relayMultiaddr}, } ldebug(). Str("op", "route"). Str("target", target). Msgf("connecting to target through relay %s", relayMultiaddr) if err = dhtClient.routedHost.Connect(context.Background(), peerRelayInfo); err != nil { lerror(err). Str("op", "route"). Str("target", target). Msgf("couldn't connect to target %s", peerID) return err } ldebug(). Str("op", "route"). Str("target", target). Msgf("opening stream to target %s", peerID) stream, err = dhtClient.newStreamLoopUntilTimeout( peerID, dhtnode.AeaEnvelopeStream, newStreamTimeout, ) if err != nil { return err } streamPipe = utils.StreamPipe{Stream: stream} envelBytes, err := proto.Marshal(envel) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("couldn't serialize envelope") errReset := stream.Reset() ignore(errReset) return err } err = acn.SendEnvelopeMessage(streamPipe, envelBytes, dhtClient.myAgentRecord) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("couldn't send envelope") errReset := stream.Reset() lerror(errReset). Msg("stream.Reset error") ignore(errReset) return err } // wait for response status, err := acn.ReadAcnStatus(streamPipe) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("while getting confirmation") errReset := stream.Reset() ignore(errReset) return err } stream.Close() if status.Code != acn.SUCCESS { err = errors.New( status.Code.String() + " : " + strings.Join( status.Msgs, ":", ), ) lerror(err). Str("op", "route"). Str("target", target). Msg("failed to deliver envelope") return err } // TODO(DM) check how we handle case when envelope not routable. return err } // handleAeaEnvelopeStream deals with incoming envelopes on the AeaEnvelopeStream // envelopes arrive from other peers (full or client) and are processed // by HandleAeaEnvelope func (dhtClient *DHTClient) handleAeaEnvelopeStream(stream network.Stream) { common.HandleAeaEnvelopeStream(dhtClient, stream) } // Callback to handle and route aea envelope comes from the aea envelope stream // return ACNError if message routing failed, otherwise nil. func (dhtClient *DHTClient) HandleAeaEnvelope(envel *aea.Envelope) *acn.ACNError { lerror, lwarn, _, _ := dhtClient.GetLoggers() var err error if envel.To == dhtClient.myAgentAddress && dhtClient.processEnvelope != nil { err = dhtClient.processEnvelope(envel) if err != nil { lerror(err).Msgf("while processing envelope by agent") return &acn.ACNError{ Err: errors.New("agent is not ready"), ErrorCode: acn.ERROR_AGENT_NOT_READY, } } } else { lwarn().Msgf("ignored envelope from unknown agent %s", envel.String()) return &acn.ACNError{Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS} } return nil } // handleAeaAddressStream deals with incoming envelopes on the AeaAddressStream // agent record lookup requests arrive from other peers (full or client) and are // served with the agent record (if applicable) func (dhtClient *DHTClient) handleAeaAddressStream(stream network.Stream) { common.HandleAeaAddressStream(dhtClient, stream) } func (dhtClient *DHTClient) HandleAeaAddressRequest( reqAddress string, ) (*acn.AgentRecord, *acn.ACNError) { lerror, _, _, ldebug := dhtClient.GetLoggers() ldebug(). Str("op", "resolve"). Str("target", reqAddress). Msg("Received query for addr") if reqAddress != dhtClient.myAgentAddress { lerror(errors.New("unknown agent address")). Str("op", "resolve"). Str("target", reqAddress). Msgf("requested address different from advertised one %s", dhtClient.myAgentAddress) return nil, &acn.ACNError{ Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS, } } else { return dhtClient.myAgentRecord, nil } } // registerAgentAddress registers agent address to relay peer func (dhtClient *DHTClient) registerAgentAddress() error { lerror, _, _, ldebug := dhtClient.GetLoggers() ldebug(). Str("op", "register"). Str("addr", dhtClient.myAgentAddress). Msg("opening stream aea-register to relay peer...") ctx, cancel := context.WithTimeout(context.Background(), newStreamTimeoutRelayPeer) defer cancel() stream, err := dhtClient.routedHost.NewStream( ctx, dhtClient.relayPeer, dhtnode.AeaRegisterRelayStream, ) if err != nil { lerror(err). Str("op", "register"). Str("addr", dhtClient.myAgentAddress). Msg("timeout, couldn't open stream to relay peer") return err } ldebug(). Str("op", "register"). Str("addr", dhtClient.myAgentAddress). Msgf("registering addr and peerID to relay peer") streamPipe := utils.StreamPipe{Stream: stream} err = acn.SendAgentRegisterMessage(streamPipe, dhtClient.myAgentRecord) if err != nil { errReset := stream.Close() ignore(errReset) return err } stream.Close() return nil } // ProcessEnvelope register a callback function for processing of envelopes // the function processes envelopes received in handleAeaEnvelopeStream func (dhtClient *DHTClient) ProcessEnvelope(fn func(*aea.Envelope) error) { dhtClient.processEnvelope = fn } ================================================ FILE: libs/go/libp2p_node/dht/dhtclient/dhtclient_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtclient import ( "testing" "time" "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/dht/dhtnode" "libp2p_node/dht/dhttests" utils "libp2p_node/utils" ) // const ( DefaultFetchAIKey = "04ab8ac134ec727917cf4f9e47685e84622151bc8b12838bc54c8ffe5d44d04a" DefaultFetchAIPublicKey = "03b07ef4513e3f0372245b3d6d474d871ba58eacaf3a2a07c487af6d82006b86b4" DefaultAgentKey = "f76137a61c1ad3ee8a0a9a185bc8e6fa51be1a2528f86042c11f9cc00880024a" DefaultAgentPublicKey = "021820ce23b5f3a6ef01988149e724af854f89d37b9cabc3b1702cc5287f617b92" DefaultAgentAddress = "fetch1ver6u7xdvkjy4dq8xxrkc6ualu98k7ykumv08q" EnvelopeDeliveryTimeout = 10 * time.Second DefaultLedger = dhtnode.DefaultLedger ) // TestNew dht client peer func TestNew(t *testing.T) { rxEnvelopesPeer := make(chan *aea.Envelope, 2) dhtPeer, cleanup, err := dhttests.NewDHTPeerWithDefaults(rxEnvelopesPeer) if err != nil { t.Fatal("Failed to create DHTPeer (required for DHTClient):", err) } defer cleanup() signature, err := utils.SignFetchAI([]byte(DefaultFetchAIPublicKey), DefaultAgentKey) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = DefaultAgentAddress record.PublicKey = DefaultAgentPublicKey record.PeerPublicKey = DefaultFetchAIPublicKey record.Signature = signature opts := []Option{ IdentityFromFetchAIKey(DefaultFetchAIKey), RegisterAgentAddress(record, func() bool { return true }), BootstrapFrom([]string{dhtPeer.MultiAddr()}), } t.Log(dhtPeer.MultiAddr()) dhtClient, err := New(opts...) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClient.Close() rxEnvelopesClient := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxEnvelopesClient <- envel return nil }) } // TestRouteEnvelopeToPeerAgent send envelope from DHTClient agent to DHTPeer agent func TestRouteEnvelopeToPeerAgent(t *testing.T) { rxEnvelopesPeer := make(chan *aea.Envelope, 2) dhtPeer, cleanup, err := dhttests.NewDHTPeerWithDefaults(rxEnvelopesPeer) if err != nil { t.Fatal("Failed to create DHTPeer (required for DHTClient):", err) } defer cleanup() signature, err := utils.SignFetchAI([]byte(DefaultFetchAIPublicKey), DefaultAgentKey) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = DefaultAgentAddress record.PublicKey = DefaultAgentPublicKey record.PeerPublicKey = DefaultFetchAIPublicKey record.Signature = signature opts := []Option{ IdentityFromFetchAIKey(DefaultFetchAIKey), RegisterAgentAddress(record, func() bool { return true }), BootstrapFrom([]string{dhtPeer.MultiAddr()}), } t.Log(dhtPeer.MultiAddr()) dhtClient, err := New(opts...) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClient.Close() rxEnvelopesClient := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxEnvelopesClient <- envel return nil }) if len(rxEnvelopesPeer) != 0 { t.Error("DHTPeer agent inbox should be empty") } err = dhtClient.RouteEnvelope(&aea.Envelope{ To: dhttests.DHTPeerDefaultAgentAddress, Sender: DefaultAgentAddress, }) if err != nil { t.Error("Failed to Route envelope to DHTPeer agent:", err) } timeout := time.After(EnvelopeDeliveryTimeout) select { case envel := <-rxEnvelopesPeer: t.Log("DHT received envelope", envel) case <-timeout: t.Error("Failed to Route envelope to DHTPeer agent") } } ================================================ FILE: libs/go/libp2p_node/dht/dhtclient/options.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtclient import ( acn "libp2p_node/acn" "libp2p_node/utils" ) // Option for dhtclient.New type Option func(*DHTClient) error // IdentityFromFetchAIKey for dhtclient.New func IdentityFromFetchAIKey(key string) Option { return func(dhtClient *DHTClient) error { var err error dhtClient.key, dhtClient.publicKey, err = utils.KeyPairFromFetchAIKey(key) if err != nil { return err } return nil } } // RegisterAgentAddress for dhtclient.New func RegisterAgentAddress(record *acn.AgentRecord, isReady func() bool) Option { return func(dhtClient *DHTClient) error { pbRecord := &acn.AgentRecord{} pbRecord.Address = record.Address pbRecord.PublicKey = record.PublicKey pbRecord.PeerPublicKey = record.PeerPublicKey pbRecord.Signature = record.Signature pbRecord.ServiceId = record.ServiceId pbRecord.LedgerId = record.LedgerId dhtClient.myAgentAddress = record.Address dhtClient.myAgentRecord = pbRecord dhtClient.myAgentReady = isReady return nil } } // BootstrapFrom for dhtclient.New func BootstrapFrom(entryPeers []string) Option { return func(dhtClient *DHTClient) error { var err error dhtClient.bootstrapPeers, err = utils.GetPeersAddrInfo(entryPeers) if err != nil { return err } return nil } } ================================================ FILE: libs/go/libp2p_node/dht/dhtnode/dhtnode.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtnode (in progress) contains the common interface between dhtpeer and dhtclient package dhtnode import "libp2p_node/aea" // DHTNode libp2p node interface type DHTNode interface { RouteEnvelope(*aea.Envelope) error ProcessEnvelope(func(*aea.Envelope) error) MultiAddr() string PeerID() string Close() []error } ================================================ FILE: libs/go/libp2p_node/dht/dhtnode/streams.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtnode const ( AeaNotifStream = "/aea-notif/0.1.0" AeaAddressStream = "/aea-address/0.1.0" AeaEnvelopeStream = "/aea/0.1.0" AeaRegisterRelayStream = "/aea-register/0.1.0" ) ================================================ FILE: libs/go/libp2p_node/dht/dhtnode/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtnode contains the common interface between dhtpeer and dhtclient // TODO: extraction of shared functionality is work in progress package dhtnode import ( "errors" "strings" acn "libp2p_node/acn" utils "libp2p_node/utils" ) const ( DefaultLedger = "fetchai" CurrentVersion = "0.1.0" ) var supportedLedgers = []string{"fetchai", "cosmos", "ethereum"} func IsValidProofOfRepresentation( record *acn.AgentRecord, agentAddress string, representativePeerPubKey string, ) (*acn.StatusBody, error) { // check agent address matches if record.Address != agentAddress { err := errors.New("Wrong agent address, expected " + agentAddress) response := &acn.StatusBody{ Code: acn.ERROR_WRONG_AGENT_ADDRESS, Msgs: []string{err.Error()}, } return response, err } // check if ledger is supported var found = false for _, supported := range supportedLedgers { if record.LedgerId == supported { found = true break } } if !found { err := errors.New( "Unsupported ledger " + record.LedgerId + ", expected " + strings.Join( supportedLedgers, ",", ), ) response := &acn.StatusBody{Code: acn.ERROR_UNSUPPORTED_LEDGER, Msgs: []string{err.Error()}} return response, err } // check public key matches if record.PeerPublicKey != representativePeerPubKey { err := errors.New("Wrong peer public key, expected " + representativePeerPubKey) response := &acn.StatusBody{Code: acn.ERROR_WRONG_PUBLIC_KEY, Msgs: []string{err.Error()}} return response, err } // check that agent address and public key match addrFromPubKey, err := utils.AgentAddressFromPublicKey(record.LedgerId, record.PublicKey) if err != nil || addrFromPubKey != record.Address { if err == nil { err = errors.New("agent address and public key don't match") } response := &acn.StatusBody{Code: acn.ERROR_WRONG_AGENT_ADDRESS} return response, err } // check that signature is valid ok, err := utils.VerifyLedgerSignature( record.LedgerId, []byte(record.PeerPublicKey), record.Signature, record.PublicKey, ) if !ok || err != nil { if err == nil { err = errors.New("signature is not valid") } response := &acn.StatusBody{Code: acn.ERROR_INVALID_PROOF} return response, err } // PoR is valid response := &acn.StatusBody{Code: acn.SUCCESS} return response, nil } ================================================ FILE: libs/go/libp2p_node/dht/dhtpeer/benchmarks_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( "bufio" "context" "flag" "net" "os" "testing" "time" "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/utils" "github.com/rs/zerolog" ) /* ********** * How to run * *********** $ go test -p 1 -count 20 libp2p_node/dht/dhtpeer/ -run=XXX -bench . -benchtime=20x -peers-keys-file=/path/to/file/benchmark_peers_keys.txt -agents-keys-file=/path/to/file/benchmark_agents_keys.txt */ var peersKeysFilePath string var agentsKeysFilePath string var tcpUri = "localhost:12345" func init() { flag.StringVar(&peersKeysFilePath, "peers-keys-file", "", "File with list of EC private keys") flag.StringVar( &agentsKeysFilePath, "agents-keys-file", "", "File with list of agents EC private keys", ) } /* ********************************** * baseline TCP connection benchmark * ********************************** */ // helpers func acceptAndEcho(server net.Listener) { for { conn, err := server.Accept() if err != nil { return } go func() { for { buf, err := utils.ReadBytesConn(conn) if err != nil { return } err = utils.WriteBytesConn(conn, buf) if err != nil { return } } }() } } func connect(uri string, b *testing.B) net.Conn { conn, err := net.Dial("tcp", uri) if err != nil { b.Fatal(err.Error()) } return conn } func sendAndReceive(conn net.Conn, buf []byte, b *testing.B) { err := utils.WriteBytesConn(conn, buf) if err != nil { b.Fatal(err.Error()) } _, err = utils.ReadBytesConn(conn) if err != nil { b.Fatal(err.Error()) } } func connectAndSend(buf []byte, b *testing.B) { conn := connect(tcpUri, b) sendAndReceive(conn, buf, b) conn.Close() } // benchs func BenchmarkBaselineTCPEcho(b *testing.B) { tcpServer, err := net.Listen("tcp", tcpUri) if err != nil { b.Fatal(err.Error()) } go acceptAndEcho(tcpServer) buf := make([]byte, 200) conn := connect(tcpUri, b) for i := 0; i < b.N; i++ { sendAndReceive(conn, buf, b) } b.StopTimer() tcpServer.Close() b.StartTimer() } func BenchmarkBaselineTCPConnectAndEcho(b *testing.B) { tcpServer, err := net.Listen("tcp", tcpUri) if err != nil { b.Fatal(err.Error()) } go acceptAndEcho(tcpServer) buf := make([]byte, 200) for i := 0; i < b.N; i++ { //var elapsed time.Duration //start := time.Now() b.ResetTimer() connectAndSend(buf, b) b.StopTimer() //elapsed = time.Since(start) //fmt.Println("Elapsed ", elapsed.String()) b.StartTimer() } b.StopTimer() tcpServer.Close() b.StartTimer() } /* ********************************** * Peer DHT operations benchmark * ********************************** */ // helpers func getKeysAndAddrs(b *testing.B) (peers []string, agents []string) { peersKeysFile, err := os.Open(peersKeysFilePath) if err != nil { b.Fatal(err) } defer peersKeysFile.Close() agentsKeysFile, err := os.Open(agentsKeysFilePath) if err != nil { b.Fatal(err) } defer agentsKeysFile.Close() ksc := bufio.NewScanner(peersKeysFile) asc := bufio.NewScanner(agentsKeysFile) peers = []string{} agents = []string{} for ksc.Scan() && asc.Scan() { peers = append(peers, ksc.Text()) agents = append(agents, asc.Text()) } return peers, agents } func setupLocalDHTPeerForBench( key string, agentKey string, dhtPort uint16, delegatePort uint16, entry []string, ) (*DHTPeer, func(), error) { /* peer, peerCleanup, err := SetupLocalDHTPeer(key, addr, dhtPort, delegatePort, entry) if err == nil { peer.SetLogLevel(zerolog.Disabled) utils.SetLoggerLevel(zerolog.Disabled) } return peer, peerCleanup, err */ opts := []Option{ LocalURI(DefaultLocalHost, dhtPort), PublicURI(DefaultLocalHost, dhtPort), IdentityFromFetchAIKey(key), EnableRelayService(), BootstrapFrom(entry), } if agentKey != "" { agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(agentKey) if err != nil { return nil, nil, err } agentAddress, err := utils.FetchAIAddressFromPublicKey(agentPubKey) if err != nil { return nil, nil, err } peerPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } signature, err := utils.SignFetchAI([]byte(peerPubKey), agentKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{} record.Address = agentAddress record.PublicKey = agentPubKey record.PeerPublicKey = peerPubKey record.Signature = signature opts = append(opts, RegisterAgentAddress(record, func() bool { return true })) } if delegatePort != 0 { opts = append(opts, EnableDelegateService(delegatePort)) } dhtPeer, err := New(opts...) if err != nil { return nil, nil, err } utils.SetLoggerLevel(zerolog.Disabled) return dhtPeer, func() { dhtPeer.Close() }, nil } func deployPeers(number uint16, b *testing.B) ([]*DHTPeer, []string) { peerKeys, agentsKeys := getKeysAndAddrs(b) peers := make([]*DHTPeer, 0, number) for i := uint16(0); i < number; i++ { entry := []string{} if i > 0 { entry = append(entry, peers[i-1].MultiAddr()) } peer, _, err := setupLocalDHTPeerForBench( peerKeys[i], agentsKeys[i], DefaultLocalPort+i, 0, entry, ) if err != nil { b.Fatal("Failed to initialize DHTPeer:", err) } peers = append(peers, peer) } return peers, agentsKeys } func closePeers(peers ...*DHTPeer) { for _, peer := range peers { peer.Close() } } func setupEchoServicePeers(peers ...*DHTPeer) { for _, peer := range peers { peer.ProcessEnvelope(func(envel *aea.Envelope) error { err := peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) } } // benchs func benchmarkAgentRegistration(npeers uint16, b *testing.B) { peers, addrs := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], "", DefaultLocalPort+npeers+1, 0, []string{peers[0].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } defer peerCleanup() for i := 0; i < b.N; i++ { b.ResetTimer() err = peer.RegisterAgentAddress(addrs[len(addrs)-1-i%len(addrs)]) if err != nil { b.Fail() } } } func benchmarkAgentLookup(npeers uint16, b *testing.B) { peers, addrs := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+npeers+1, 0, []string{peers[len(peers)-1].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } defer peerCleanup() ensureAddressAnnounced(peer) ctx, cancel_lookup := context.WithTimeout(context.Background(), 30*time.Second) defer cancel_lookup() for i := 0; i < b.N; i++ { b.ResetTimer() _, _, err = peer.lookupAddressDHT(ctx, addrs[len(peers)-1-i%len(peers)]) if err != nil { b.Fail() } } } func benchmarkPeerJoin(npeers uint16, b *testing.B) { peers, _ := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) for i := 0; i < b.N; i++ { b.ResetTimer() peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+npeers+1, 0, []string{peers[i%len(peers)].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } ensureAddressAnnounced(peer) b.StopTimer() peerCleanup() b.StartTimer() } } func benchmarkPeerEcho(npeers uint16, b *testing.B) { peers, addrs := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) setupEchoServicePeers(peers...) peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+npeers+1, 0, []string{peers[len(peers)-1].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } defer peerCleanup() ensureAddressAnnounced(peer) rxPeer := make(chan *aea.Envelope, 10) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return nil }) for i := 0; i < b.N; i++ { envel := &aea.Envelope{ To: addrs[len(peers)-1-i%len(peers)], Sender: AgentsTestAddresses[1], Message: make([]byte, 101), } b.ResetTimer() err = peer.RouteEnvelope(envel) if err != nil { b.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } <-rxPeer } } func BenchmarkAgentRegistration2(b *testing.B) { benchmarkAgentRegistration(2, b) } func BenchmarkAgentRegistration8(b *testing.B) { benchmarkAgentRegistration(8, b) } func BenchmarkAgentRegistration32(b *testing.B) { benchmarkAgentRegistration(32, b) } func BenchmarkAgentRegistration128(b *testing.B) { benchmarkAgentRegistration(128, b) } func BenchmarkAgentRegistration256(b *testing.B) { benchmarkAgentRegistration(256, b) } func BenchmarkAgentLookup2(b *testing.B) { benchmarkAgentLookup(2, b) } func BenchmarkAgentLookup8(b *testing.B) { benchmarkAgentLookup(8, b) } func BenchmarkAgentLookup32(b *testing.B) { benchmarkAgentLookup(32, b) } func BenchmarkAgentLookup128(b *testing.B) { benchmarkAgentLookup(128, b) } func BenchmarkAgentLookup256(b *testing.B) { benchmarkAgentLookup(256, b) } func BenchmarkPeerJoin2(b *testing.B) { benchmarkPeerJoin(2, b) } func BenchmarkPeerJoin8(b *testing.B) { benchmarkPeerJoin(8, b) } func BenchmarkPeerJoin32(b *testing.B) { benchmarkPeerJoin(32, b) } func BenchmarkPeerJoin128(b *testing.B) { benchmarkPeerJoin(128, b) } func BenchmarkPeerJoin256(b *testing.B) { benchmarkPeerJoin(256, b) } func BenchmarkPeerEcho2(b *testing.B) { benchmarkPeerEcho(2, b) } func BenchmarkPeerEcho8(b *testing.B) { benchmarkPeerEcho(8, b) } func BenchmarkPeerEcho32(b *testing.B) { benchmarkPeerEcho(32, b) } func BenchmarkPeerEcho128(b *testing.B) { benchmarkPeerEcho(128, b) } func BenchmarkPeerEcho256(b *testing.B) { benchmarkPeerEcho(256, b) } ================================================ FILE: libs/go/libp2p_node/dht/dhtpeer/dhtpeer.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtpeer provides an implementation of an Agent Communication Network node // using libp2p. It participates in data storage and routing for the network. // It offers RelayService for dhtclient and DelegateService for tcp clients. package dhtpeer import ( "bufio" "bytes" "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/binary" "encoding/pem" "fmt" "io" "log" "math/big" "net" "os" "strconv" "strings" "sync" "time" "github.com/btcsuite/btcd/btcec" "github.com/pkg/errors" "github.com/rs/zerolog" "google.golang.org/protobuf/proto" libp2p "github.com/libp2p/go-libp2p" cryptop2p "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" "github.com/multiformats/go-multiaddr" circuit "github.com/libp2p/go-libp2p-circuit" kaddht "github.com/libp2p/go-libp2p-kad-dht" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" acn "libp2p_node/acn" aea "libp2p_node/aea" common "libp2p_node/dht/common" "libp2p_node/dht/dhtnode" monitoring "libp2p_node/dht/monitoring" utils "libp2p_node/utils" ) const AcnStatusTimeout = 5.0 * time.Second const AcnStatusesQueueSize = 1000 const SlowQueueSize = 100 // panics if err is not nil func check(err error) { if err != nil { panic(err) } } func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } const ( addressLookupTimeout = 20 * time.Second timeoutBeforeMovingToSlowQueue = 3 * time.Second routingTableConnectionUpdateTimeout = 5 * time.Second newStreamTimeout = 10 * time.Second addressRegisterTimeout = 3 * time.Second addressRegistrationDelay = 0 * time.Second monitoringNamespace = "acn" metricDHTOpLatencyStore = "dht_op_latency_store" metricDHTOpLatencyLookup = "dht_op_latency_lookup" metricOpLatencyRegister = "op_latency_register" metricOpLatencyRoute = "op_latency_route" metricOpRouteCount = "op_route_count" metricOpRouteCountAll = "op_route_count_all" metricOpRouteCountSuccess = "op_route_count_success" metricServiceDelegateClientsCount = "service_delegate_clients_count" metricServiceDelegateClientsCountAll = "service_delegate_clients_count_all" metricServiceRelayClientsCount = "service_relay_clients_count" metricServiceRelayClientsCountAll = "service_relay_clients_count_all" defaultPersistentStoragePath = "./agent_records_store" ) var ( //latencyBucketsMilliSeconds = []float64{1., 10., 20., 50., 100., 200., 500., 1000.} latencyBucketsMicroSeconds = []float64{100., 500., 1e3, 1e4, 1e5, 5e5, 1e6} ) // DHTPeer A full libp2p node for the Agents Communication Network. // It is required to have a local address and a public one // and can acts as a relay for `DHTClient`. // Optionally, it provides delegate service for tcp clients. type DHTPeer struct { host string port uint16 publicHost string publicPort uint16 delegatePort uint16 monitoringPort uint16 enableRelay bool mailboxHostPort string mailboxServer *MailboxServer registrationDelay time.Duration key cryptop2p.PrivKey publicKey cryptop2p.PubKey localMultiaddr multiaddr.Multiaddr publicMultiaddr multiaddr.Multiaddr bootstrapPeers []peer.AddrInfo dht *kaddht.IpfsDHT routedHost *routedhost.RoutedHost tcpListener net.Listener cert *tls.Certificate sslSignature []byte addressAnnouncedMap map[string]bool addressAnnouncedMapLock sync.RWMutex // flag to announce addresses over network if peer connected to other peers enableAddressAnnouncement bool enableAddressAnnouncementWg *sync.WaitGroup enableAddressAnnouncementLock sync.RWMutex myAgentAddress string myAgentRecord *acn.AgentRecord myAgentReady func() bool dhtAddresses map[string]string acnStatuses map[string]chan *acn.StatusBody tcpAddresses map[string]net.Conn agentRecords map[string]*acn.AgentRecord acnStatusesLock sync.RWMutex dhtAddressesLock sync.RWMutex tcpAddressesLock sync.RWMutex agentRecordsLock sync.RWMutex // TOFIX(LR): maps and locks need refactoring for better abstraction processEnvelope func(*aea.Envelope) error persistentStoragePath string storage *os.File monitor monitoring.MonitoringService closing chan struct{} isClosing bool goroutines *sync.WaitGroup logger zerolog.Logger // syncMessages map[string]*sync.WaitGroup syncMessagesLock sync.RWMutex syncMessages map[string](chan *aea.Envelope) slow_queue chan *aea.Envelope } // New creates a new DHTPeer func New(opts ...Option) (*DHTPeer, error) { var err error dhtPeer := &DHTPeer{registrationDelay: addressRegistrationDelay, isClosing: false} dhtPeer.dhtAddresses = map[string]string{} dhtPeer.tcpAddresses = map[string]net.Conn{} dhtPeer.agentRecords = map[string]*acn.AgentRecord{} dhtPeer.acnStatuses = map[string]chan *acn.StatusBody{} dhtPeer.dhtAddressesLock = sync.RWMutex{} dhtPeer.tcpAddressesLock = sync.RWMutex{} dhtPeer.agentRecordsLock = sync.RWMutex{} dhtPeer.persistentStoragePath = defaultPersistentStoragePath dhtPeer.syncMessages = make(map[string](chan *aea.Envelope)) dhtPeer.addressAnnouncedMap = map[string]bool{} dhtPeer.addressAnnouncedMapLock = sync.RWMutex{} dhtPeer.enableAddressAnnouncementWg = &sync.WaitGroup{} dhtPeer.enableAddressAnnouncementLock = sync.RWMutex{} dhtPeer.mailboxHostPort = "" dhtPeer.slow_queue = make(chan *aea.Envelope, SlowQueueSize) for _, opt := range opts { if err := opt(dhtPeer); err != nil { return nil, err } } dhtPeer.closing = make(chan struct{}) dhtPeer.goroutines = &sync.WaitGroup{} /* check correct configuration */ // private key if dhtPeer.key == nil { return nil, errors.New("private key must be provided") } // local uri if dhtPeer.localMultiaddr == nil { return nil, errors.New("local host and port must be set") } // public uri if dhtPeer.publicMultiaddr == nil { return nil, errors.New("public host and port must be set") } // check if the PoR is delivered for my public key if dhtPeer.myAgentRecord != nil { myPublicKey, err := utils.FetchAIPublicKeyFromPubKey(dhtPeer.publicKey) status, errPoR := dhtnode.IsValidProofOfRepresentation( dhtPeer.myAgentRecord, dhtPeer.myAgentRecord.Address, myPublicKey, ) if err != nil || errPoR != nil || status.Code != acn.SUCCESS { errMsg := "Invalid AgentRecord" if err == nil { err = errors.New(errMsg) } else { err = errors.Wrap(err, errMsg) } if errPoR != nil { err = errors.Wrap(err, errPoR.Error()) } return nil, err } } /* setup libp2p node */ ctx := context.Background() // setup public uri as external address addressFactory := func(addrs []multiaddr.Multiaddr) []multiaddr.Multiaddr { return []multiaddr.Multiaddr{dhtPeer.publicMultiaddr} } // libp2p options libp2pOpts := []libp2p.Option{ libp2p.ListenAddrs(dhtPeer.localMultiaddr), libp2p.AddrsFactory(addressFactory), libp2p.Identity(dhtPeer.key), libp2p.DefaultTransports, libp2p.DefaultMuxers, libp2p.DefaultSecurity, libp2p.NATPortMap(), libp2p.EnableNATService(), libp2p.EnableRelay(circuit.OptHop), } // create a basic host basicHost, err := libp2p.New(ctx, libp2pOpts...) if err != nil { return nil, err } // create the dht dhtPeer.dht, err = kaddht.New(ctx, basicHost, kaddht.Mode(kaddht.ModeServer)) if err != nil { return nil, err } err = dhtPeer.makeSSLCertifiateAndSignature() if err != nil { return nil, err } // make the routed host dhtPeer.routedHost = routedhost.Wrap(basicHost, dhtPeer.dht) dhtPeer.setupLogger() lerror, _, linfo, ldebug := dhtPeer.GetLoggers() basicHost.Network().Notify(&Notifee{ logger: dhtPeer.logger, }) // connect to the booststrap nodes if len(dhtPeer.bootstrapPeers) > 0 { linfo().Msgf("Bootstrapping from %s", dhtPeer.bootstrapPeers) err = utils.BootstrapConnect(ctx, dhtPeer.routedHost, dhtPeer.dht, dhtPeer.bootstrapPeers) if err != nil { dhtPeer.Close() return nil, err } } // bootstrap the dht err = dhtPeer.dht.Bootstrap(ctx) if err != nil { dhtPeer.Close() return nil, err } linfo().Msgf("My Peer ID is %s", dhtPeer.PeerID()) linfo().Msg("successfully created libp2p node!") /* setup DHTPeer message handlers and services */ // setup monitoring dhtPeer.setupMonitoring() // relay service if dhtPeer.enableRelay { // Allow clients to register their agents addresses ldebug().Msg("Setting /aea-register/0.1.0 stream...") dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaRegisterRelayStream, dhtPeer.handleAeaRegisterStream) } // new peers connection notification, so that this peer can register its addresses dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaNotifStream, dhtPeer.handleAeaNotifStream) // Notify bootstrap peers if any for _, bPeer := range dhtPeer.bootstrapPeers { ctx := context.Background() s, err := dhtPeer.routedHost.NewStream(ctx, bPeer.ID, dhtnode.AeaNotifStream) if err != nil { lerror(err).Msgf("failed to open stream to notify bootstrap peer %s", bPeer.ID) dhtPeer.Close() return nil, err } _, err = s.Write([]byte(dhtnode.AeaNotifStream)) if err != nil { lerror(err).Msgf("failed to notify bootstrap peer %s", bPeer.ID) dhtPeer.Close() return nil, err } s.Close() } // initialize agents records persistent storage if dhtPeer.persistentStoragePath == defaultPersistentStoragePath { myPeerID, err := peer.IDFromPublicKey(dhtPeer.publicKey) ignore(err) dhtPeer.persistentStoragePath += "_" + myPeerID.Pretty() } nbr, err := dhtPeer.initAgentRecordPersistentStorage() if err != nil { return nil, errors.Wrap(err, "while initializing agent record storage") } if len(dhtPeer.bootstrapPeers) > 0 { for addr := range dhtPeer.dhtAddresses { err := dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Str("addr", addr). Msg("while announcing stored client address") } } } linfo().Msgf("successfully loaded %d agents", nbr) // if peer is joining an existing network, announce my agent address if set if len(dhtPeer.bootstrapPeers) > 0 { // there are some bootstrap peers so we can announce addresses to them dhtPeer.enableAddressAnnouncement = true if dhtPeer.myAgentAddress != "" && !dhtPeer.IsAddressAnnounced(dhtPeer.myAgentAddress) { ldebug().Msg("Address was announced on bootstrap peers") opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() err := dhtPeer.RegisterAgentAddress(dhtPeer.myAgentAddress) if err != nil { dhtPeer.Close() return nil, err } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) } } // aea addresses lookup ldebug().Msg("Setting /aea-address/0.1.0 stream...") dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaAddressStream, dhtPeer.handleAeaAddressStream) // incoming envelopes stream ldebug().Msg("Setting /aea/0.1.0 stream...") dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaEnvelopeStream, dhtPeer.handleAeaEnvelopeStream) // setup delegate service if dhtPeer.delegatePort != 0 { dhtPeer.launchDelegateService() ready := &sync.WaitGroup{} dhtPeer.goroutines.Add(1) ready.Add(1) go dhtPeer.handleDelegateService(ready) ready.Wait() } // check mailbox uri is set if len(dhtPeer.mailboxHostPort) != 0 { dhtPeer.launchMailboxService() } // start monitoring ready := &sync.WaitGroup{} ready.Add(1) go dhtPeer.startMonitoring(ready) ready.Wait() go dhtPeer.slowEnvelopeSendLoop() return dhtPeer, nil } // saveAgentRecordToPersistentStorage saves the agent record to persistent storage func (dhtPeer *DHTPeer) saveAgentRecordToPersistentStorage(record *acn.AgentRecord) error { msg := formatPersistentStorageLine(record) if len(msg) == 0 { return errors.New("while formating record " + record.String()) } size := uint32(len(msg)) buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, size) buf = append(buf, msg...) _, err := dhtPeer.storage.Write(buf) if err != nil { return errors.Wrap(err, "while writing record to persistent storage") } return nil } func parsePersistentStorageLine(line []byte) (*acn.AgentRecord, error) { record := &acn.AgentRecord{} err := proto.Unmarshal(line, record) return record, err } func formatPersistentStorageLine(record *acn.AgentRecord) []byte { msg, err := proto.Marshal(record) ignore(err) return msg } // initAgentRecordPersistentStorage loads agent records from persistent storage func (dhtPeer *DHTPeer) initAgentRecordPersistentStorage() (int, error) { var err error _, _, linfo, _ := dhtPeer.GetLoggers() linfo().Msg("Load records from store " + dhtPeer.persistentStoragePath) dhtPeer.storage, err = os.OpenFile( dhtPeer.persistentStoragePath, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0600, ) if err != nil { return 0, err } reader := bufio.NewReader(dhtPeer.storage) var counter int = 0 for { buf := make([]byte, 4) _, err = io.ReadFull(reader, buf) if err == io.EOF { break } if err != nil { return 0, errors.Wrap(err, "while loading agent records") } size := binary.BigEndian.Uint32(buf) line := make([]byte, size) _, err = io.ReadFull(reader, line) if err != nil { return 0, errors.Wrap(err, "while loading agent records") } record, err := parsePersistentStorageLine(line) if err != nil { return 0, errors.Wrap(err, "while loading agent records") } dhtPeer.agentRecords[record.Address] = record relayPeerID, err := utils.IDFromFetchAIPublicKey(record.PeerPublicKey) if err != nil { return 0, errors.Wrap(err, "While loading agent records") } dhtPeer.dhtAddresses[record.Address] = relayPeerID.Pretty() counter++ } return counter, nil } func (dhtPeer *DHTPeer) closeAgentRecordPersistentStorage() error { dhtPeer.agentRecordsLock.Lock() err := dhtPeer.storage.Close() dhtPeer.agentRecordsLock.Unlock() return err } func (dhtPeer *DHTPeer) setupMonitoring() { if dhtPeer.monitoringPort != 0 { dhtPeer.monitor = monitoring.NewPrometheusMonitoring( monitoringNamespace, dhtPeer.monitoringPort, ) } else { dhtPeer.monitor = monitoring.NewFileMonitoring(monitoringNamespace, false) } dhtPeer.addMonitoringMetrics() } func (dhtPeer *DHTPeer) startMonitoring(ready *sync.WaitGroup) { _, _, linfo, _ := dhtPeer.GetLoggers() linfo().Msg("Starting monitoring service: " + dhtPeer.monitor.Info()) go dhtPeer.monitor.Start() ready.Done() } func (dhtPeer *DHTPeer) addMonitoringMetrics() { buckets := latencyBucketsMicroSeconds var err error // acn primitives _, err = dhtPeer.monitor.NewHistogram(metricDHTOpLatencyStore, "Histogram for time to store a key in the DHT", buckets) ignore(err) _, err = dhtPeer.monitor.NewHistogram(metricDHTOpLatencyLookup, "Histogram for time to find a key in the DHT", buckets) ignore(err) // acn main service _, err = dhtPeer.monitor.NewHistogram(metricOpLatencyRegister, "Histogram for end-to-end time to register an agent in the acn", buckets) ignore(err) _, err = dhtPeer.monitor.NewHistogram( metricOpLatencyRoute, "Histogram for end-to-end time to route an envelope to its destination, excluding time to send envelope itself", buckets, ) ignore(err) _, err = dhtPeer.monitor.NewGauge(metricOpRouteCount, "Number of ongoing envelope routing requests") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricOpRouteCountAll, "Total number envelope routing requests, successful or not") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricOpRouteCountSuccess, "Total number envelope routed successfully") ignore(err) // acn delegate service _, err = dhtPeer.monitor.NewGauge(metricServiceDelegateClientsCount, "Number of active delagate connections") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricServiceDelegateClientsCountAll, "Number of all delagate clients, connected or disconnected") ignore(err) // acn relay service _, err = dhtPeer.monitor.NewGauge(metricServiceRelayClientsCount, "Number of active relay clients") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricServiceRelayClientsCountAll, "Total number of all relayed clients, connected or disconnected") ignore(err) } func (dhtPeer *DHTPeer) setupLogger() { fields := map[string]string{ "package": "DHTPeer", } if dhtPeer.routedHost != nil { fields["peerid"] = dhtPeer.routedHost.ID().Pretty() } dhtPeer.logger = utils.NewDefaultLoggerWithFields(fields) } func (dhtPeer *DHTPeer) PeerID() string { return dhtPeer.routedHost.ID().Pretty() } func (dhtPeer *DHTPeer) GetLoggers() (func(error) *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event) { ldebug := dhtPeer.logger.Debug linfo := dhtPeer.logger.Info lwarn := dhtPeer.logger.Warn lerror := func(err error) *zerolog.Event { if err == nil { return dhtPeer.logger.Error().Str("err", "nil") } return dhtPeer.logger.Error().Str("err", err.Error()) } return lerror, lwarn, linfo, ldebug } // SetLogLevel set utils logger level func (dhtPeer *DHTPeer) SetLogLevel(lvl zerolog.Level) { dhtPeer.logger = dhtPeer.logger.Level(lvl) } // Close stops the DHTPeer func (dhtPeer *DHTPeer) Close() []error { var err error var status []error _, _, linfo, _ := dhtPeer.GetLoggers() linfo().Msg("Stopping DHTPeer...") close(dhtPeer.closing) dhtPeer.isClosing = true //return status errappend := func(err error) { if err != nil { status = append(status, err) } } if dhtPeer.tcpListener != nil { err = dhtPeer.tcpListener.Close() errappend(err) dhtPeer.tcpAddressesLock.Lock() for _, conn := range dhtPeer.tcpAddresses { err = conn.Close() errappend(err) } dhtPeer.tcpAddressesLock.Unlock() } if dhtPeer.mailboxServer != nil { dhtPeer.mailboxServer.stop() } err = dhtPeer.dht.Close() errappend(err) err = dhtPeer.routedHost.Close() errappend(err) err = dhtPeer.closeAgentRecordPersistentStorage() errappend(err) //linfo().Msg("Stopping DHTPeer: waiting for goroutines to cancel...") // dhtPeer.goroutines.Wait() dhtPeer.syncMessagesLock.Lock() for _, channel := range dhtPeer.syncMessages { close(channel) } dhtPeer.syncMessagesLock.Unlock() close(dhtPeer.slow_queue) return status } // Generate selfsigned c509 certificate with temprorary key to be used with TLS server // We can not use peer private key cause it does not supported by golang TLS implementation // So we generate a new one and send session public key signature made with peer private key // snd client can validate it with peer public key/address func generate_x509_cert() (*tls.Certificate, error) { privBtcKey, err := btcec.NewPrivateKey(elliptic.P256()) if err != nil { return nil, errors.Wrap(err, "while creating new private key") } privKey := privBtcKey.ToECDSA() pubKey := &privKey.PublicKey ca := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ Organization: []string{"Acn Node"}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(1, 0, 0), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, }, BasicConstraintsValid: true, } ca.IsCA = true ca.KeyUsage |= x509.KeyUsageCertSign certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, pubKey, privKey) if err != nil { return nil, errors.Wrap(err, "while creating ca") } certPEM := new(bytes.Buffer) err = pem.Encode(certPEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) if err != nil { return nil, errors.Wrap(err, "while encoding cert pem") } privPEM := new(bytes.Buffer) b, err := x509.MarshalECPrivateKey(privKey) if err != nil { return nil, errors.Wrap(err, "while marshaling ec private key") } err = pem.Encode(privPEM, &pem.Block{ Type: "EC PRIVATE KEY", Bytes: b, }) if err != nil { return nil, errors.Wrap(err, "while encoding prive pem") } cert, err := tls.X509KeyPair(certPEM.Bytes(), privPEM.Bytes()) return &cert, err } // launchDelegateService launches the delegate service on the configured uri func (dhtPeer *DHTPeer) launchDelegateService() { var err error lerror, _, _, _ := dhtPeer.GetLoggers() config := &tls.Config{Certificates: []tls.Certificate{*dhtPeer.cert}} uri := dhtPeer.host + ":" + strconv.FormatInt(int64(dhtPeer.delegatePort), 10) listener, err := tls.Listen("tcp", uri, config) if err != nil { lerror(err).Msgf("while setting up listening tcp socket %s", uri) check(err) } if err != nil { lerror(err).Msgf("while generating tls signature") check(err) } dhtPeer.tcpListener = TLSListener{Listener: listener, Signature: dhtPeer.sslSignature} } // launchDelegateService launches the delegate service on the configured uri func (dhtPeer *DHTPeer) makeSSLCertifiateAndSignature() error { var err error lerror, _, _, _ := dhtPeer.GetLoggers() dhtPeer.cert, err = generate_x509_cert() if err != nil { lerror(err).Msgf("while generating tls certificate") return err } dhtPeer.sslSignature, err = makeSessionKeySignature(dhtPeer.cert, dhtPeer.key) if err != nil { lerror(err).Msgf("while generating tls certificate server signature") return err } return nil } // launchMailboxService launches the mailbox http service on the configured uri func (dhtPeer *DHTPeer) launchMailboxService() { _, _, linfo, _ := dhtPeer.GetLoggers() if len(dhtPeer.mailboxHostPort) == 0 { return } dhtPeer.mailboxServer = &MailboxServer{ addr: dhtPeer.mailboxHostPort, dhtPeer: dhtPeer, } linfo().Msgf("Starting mailbox service on %s", dhtPeer.mailboxHostPort) go dhtPeer.mailboxServer.start() } // Make signature for session public key using peer private key func makeSessionKeySignature(cert *tls.Certificate, privateKey cryptop2p.PrivKey) ([]byte, error) { cert_pub_key := cert.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey) cert_pub_key_bytes := elliptic.Marshal(cert_pub_key.Curve, cert_pub_key.X, cert_pub_key.Y) signature, err := privateKey.Sign(cert_pub_key_bytes) return signature, err } // handleDelegateService listens for new connections to delegate service and handles them func (dhtPeer *DHTPeer) handleDelegateService(ready *sync.WaitGroup) { defer dhtPeer.goroutines.Done() defer dhtPeer.tcpListener.Close() lerror, _, linfo, _ := dhtPeer.GetLoggers() done := false L: for { select { default: linfo().Msg("DelegateService listening for new connections...") if !done { done = true ready.Done() } conn, err := dhtPeer.tcpListener.Accept() if err != nil { if strings.Contains(err.Error(), "use of closed network connection") { // About using string comparison to get the type of err, // check https://github.com/golang/go/issues/4373 linfo().Msg("DelegateService Stopped.") } else { lerror(err).Msgf("while accepting a new connection") } } else { dhtPeer.goroutines.Add(1) go dhtPeer.handleNewDelegationConnection(conn) } case <-dhtPeer.closing: break L } } } func (dhtPeer *DHTPeer) CheckPOR(record *acn.AgentRecord) (*acn.StatusBody, error) { addr := record.Address myPubKey, err := utils.FetchAIPublicKeyFromPubKey(dhtPeer.publicKey) ignore(err) status, err := dhtnode.IsValidProofOfRepresentation(record, addr, myPubKey) return status, err } // handleNewDelegationConnection handles a new delegate connection // verifies agent record and registers agent in DHT, handles incoming envelopes // and forwards them for processing func (dhtPeer *DHTPeer) handleNewDelegationConnection(conn net.Conn) { defer dhtPeer.goroutines.Done() defer conn.Close() // to limit spamming time.Sleep(dhtPeer.registrationDelay) nbrConns, _ := dhtPeer.monitor.GetGauge(metricServiceDelegateClientsCount) nbrClients, _ := dhtPeer.monitor.GetCounter(metricServiceDelegateClientsCountAll) opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() lerror, _, linfo, _ := dhtPeer.GetLoggers() //linfo().Msgf("received a new connection from %s", conn.RemoteAddr().String()) // read agent registration message conPipe := utils.ConnPipe{Conn: conn} var register *acn.RegisterPerformative var err error register, err = acn.ReadAgentRegistrationMessage(conPipe) if err != nil { lerror(err).Msg("while receiving agent's registration request") nbrConns.Dec() return } linfo().Msgf("Received registration request %s", register) // Get Register message record := register.Record addr := record.Address linfo().Msgf("connection from %s established for Address %s", conn.RemoteAddr().String(), addr) // check if the PoR is valid status, err := dhtPeer.CheckPOR(record) if err != nil || status.Code != acn.SUCCESS { lerror(err).Msg("PoR is not valid") acn_send_error := acn.SendAcnError(conPipe, err.Error(), status.Code) ignore(acn_send_error) nbrConns.Dec() return } // TOFIX(LR) post-pone answer until address successfully registered err = acn.SendAcnSuccess(conPipe) if err != nil { nbrConns.Dec() return } // Add connection to map dhtPeer.agentRecordsLock.Lock() dhtPeer.agentRecords[addr] = record dhtPeer.agentRecordsLock.Unlock() dhtPeer.tcpAddressesLock.Lock() dhtPeer.tcpAddresses[addr] = conn dhtPeer.tcpAddressesLock.Unlock() dhtPeer.acnStatusesLock.Lock() dhtPeer.acnStatuses[addr] = make(chan *acn.StatusBody, AcnStatusesQueueSize) dhtPeer.acnStatusesLock.Unlock() //linfo().Msgf("announcing tcp client address %s...", addr) // TOFIX(LR) disconnect client? if dhtPeer.IsAddressAnnouncementEnabled() { err = dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Msgf("while announcing tcp client address %s to the dht", addr) return } } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) nbrConns.Inc() nbrClients.Inc() connPipe := utils.ConnPipe{Conn: conn} for { // read envelopes envel, err := aea.HandleAcnMessageFromPipe(connPipe, dhtPeer, addr) if err != nil { if err == io.EOF { linfo().Str( "addr", addr, ).Msgf( "connection closed by client: %s, stopping...", err.Error(), ) } else { lerror(err).Str("addr", addr).Msg("while reading envelope from client connection, aborting...") } nbrConns.Dec() break } if envel == nil { // ACN status continue } linfo().Str( "addr", addr, ).Msgf( "got envelope from delegate connection", ) if envel.Sender != addr { err = errors.New("Sender (" + envel.Sender + ") must match registered address") lerror(err).Str("addr", addr). Msg("while routing delegate client envelope") continue } // initializing pairwise channel queues and one go routine per pair pair := envel.To + envel.Sender dhtPeer.syncMessagesLock.Lock() _, ok := dhtPeer.syncMessages[pair] dhtPeer.syncMessagesLock.Unlock() if !ok { dhtPeer.syncMessagesLock.Lock() dhtPeer.syncMessages[pair] = make(chan *aea.Envelope, 1000) dhtPeer.syncMessagesLock.Unlock() // route envelope dhtPeer.goroutines.Add(1) go func() { defer dhtPeer.goroutines.Done() dhtPeer.syncMessagesLock.Lock() pair_range := dhtPeer.syncMessages[pair] dhtPeer.syncMessagesLock.Unlock() for e := range pair_range { err := dhtPeer.RouteEnvelope(e) if err != nil { lerror(err).Str("addr", addr). Msg("while routing delegate client envelope") // TODO() send error back } } }() } // add to queue (nonblocking - buffered queue) dhtPeer.syncMessagesLock.Lock() select { case dhtPeer.syncMessages[pair] <- envel: default: // send back! error fmt.Println("CHANNEL FULL, DISCARDING <<<-------- ", string(envel.Message)) } dhtPeer.syncMessagesLock.Unlock() } linfo().Str("addr", addr). Msg("delegate client disconnected") // Remove connection from map dhtPeer.tcpAddressesLock.Lock() delete(dhtPeer.tcpAddresses, addr) dhtPeer.tcpAddressesLock.Unlock() // TOFIX(LR) currently I am keeping the agent record dhtPeer.acnStatusesLock.Lock() delete(dhtPeer.acnStatuses, addr) dhtPeer.acnStatusesLock.Unlock() } // ProcessEnvelope register callback function func (dhtPeer *DHTPeer) ProcessEnvelope(fn func(*aea.Envelope) error) { dhtPeer.processEnvelope = fn } // MultiAddr libp2p multiaddr of the peer func (dhtPeer *DHTPeer) MultiAddr() string { multiAddr, _ := multiaddr.NewMultiaddr( fmt.Sprintf("/p2p/%s", dhtPeer.routedHost.ID().Pretty())) addrs := dhtPeer.routedHost.Addrs() if len(addrs) == 0 { return "" } return addrs[0].Encapsulate(multiAddr).String() } func (dhtPeer *DHTPeer) GetCertAndSignature() (*tls.Certificate, []byte) { return dhtPeer.cert, dhtPeer.sslSignature } // RouteEnvelope to its destination func (dhtPeer *DHTPeer) getEnvelopeAcnRecord(envel *aea.Envelope) (*acn.AgentRecord, error) { sender := envel.Sender dhtPeer.agentRecordsLock.RLock() localRec, existsLocal := dhtPeer.agentRecords[sender] dhtPeer.agentRecordsLock.RUnlock() var envelRec *acn.AgentRecord if sender == dhtPeer.myAgentAddress { envelRec = dhtPeer.myAgentRecord } else if dhtPeer.mailboxServer != nil && dhtPeer.mailboxServer.IsAddrRegistered(sender) { envelRec = dhtPeer.mailboxServer.GetAgentRecord(sender) } else if existsLocal { // TOFIX(LR) should acquire RLock envelRec = localRec } else { err := errors.New("Envelope sender is not registered locally " + sender) return nil, err } return envelRec, nil } func (dhtPeer *DHTPeer) RouteEnvelope(envel *aea.Envelope) error { lerror, lwarn, linfo, ldebug := dhtPeer.GetLoggers() routeCount, _ := dhtPeer.monitor.GetGauge(metricOpRouteCount) routeCountAll, _ := dhtPeer.monitor.GetCounter(metricOpRouteCountAll) routeCountSuccess, _ := dhtPeer.monitor.GetCounter(metricOpRouteCountSuccess) opLatencyRoute, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRoute) timer := dhtPeer.monitor.Timer() routeCount.Inc() routeCountAll.Inc() start := timer.NewTimer() ldebug().Str("addr", envel.To).Msgf("-> Routing envelope: %s", envel.String()) // get sender agent envelRec // TODO can change function signature to force the caller to provide the envelRec target := envel.To var err error envelRec, err := dhtPeer.getEnvelopeAcnRecord(envel) if err != nil { lerror(err).Str("op", "route").Str("addr", envel.To). Msg("") return err } dhtPeer.tcpAddressesLock.RLock() connDelegate, existsDelegate := dhtPeer.tcpAddresses[target] dhtPeer.tcpAddressesLock.RUnlock() if dhtPeer.mailboxServer != nil { if dhtPeer.mailboxServer.RouteEnvelope(envel) { linfo().Str("op", "route").Str("addr", target). Msg("route envelope destinated to mailbox registered agent...") return nil } } if target == dhtPeer.myAgentAddress { linfo().Str("op", "route").Str("addr", target). Msg("route envelope destinated to my local agent...") // TOFIX(LR) risk of infinite loop for dhtPeer.myAgentReady != nil && !dhtPeer.myAgentReady() { lwarn().Str("op", "route").Str("addr", target). Msg("agent not ready yet, sleeping for some time ...") time.Sleep(time.Duration(100) * time.Millisecond) } if dhtPeer.processEnvelope != nil { duration := timer.GetTimer(start) opLatencyRoute.Observe(float64(duration.Microseconds())) err := dhtPeer.processEnvelope(envel) routeCount.Dec() if err != nil { return err } routeCountSuccess.Inc() } else { lwarn().Str("op", "route").Str("addr", target). Msgf("ProcessEnvelope not set, ignoring envelope %s", envel.String()) return errors.New("Agent not ready") } } else if existsDelegate { linfo().Str("op", "route").Str("addr", target). Msgf("destination is a delegate client %s", connDelegate.RemoteAddr().String()) routeCount.Dec() routeCountSuccess.Inc() duration := timer.GetTimer(start) opLatencyRoute.Observe(float64(duration.Microseconds())) data, err := aea.MakeAcnMessageFromEnvelope(envel) if err != nil { lerror(err).Msgf("while serializing envelope: %s", envel) return err } err = utils.WriteBytesConn(connDelegate, data) if err != nil { lerror(err).Msgf("while writing envelope: %s", envel) return err } linfo().Str("addr", target).Msg("wait for acn ack") err = dhtPeer.AwaitAcnStatus(target) linfo().Str("addr", target).Msg("got acn ack") if err != nil { lerror(err).Msgf("while waiting acn ack for envelope: %s", envel) return err } } else { var peerID peer.ID var err error dhtPeer.dhtAddressesLock.RLock() sPeerID, exists := dhtPeer.dhtAddresses[target] dhtPeer.dhtAddressesLock.RUnlock() if exists { linfo().Str("op", "route").Str("addr", target). Msgf("destination is a relay client %s", sPeerID) peerID, err = peer.Decode(sPeerID) linfo().Str("op", "route").Str("addr", target). Msgf("relay client peer is %s", peerID) if err != nil { lerror(err).Str("op", "route").Str("addr", target). Msgf("CRITICAL couldn't parse peer id from relay client id") routeCount.Dec() return err } return dhtPeer._routeEnvelopeWithNewStream(envel, peerID, envelRec) } // not exists err = dhtPeer._routeEnvelopeDHTLookup(envel, timeoutBeforeMovingToSlowQueue) if err != nil { routeCount.Dec() if !dhtPeer.isClosing { lerror(err).Str("op", "route").Str("addr", target). Msg("while rooting and looking up address on the DHT. moving it to she slow queue") dhtPeer.slow_queue <- envel } else { lerror(err).Str("op", "route").Str("addr", target). Msg("while rooting and looking up address on the DHT. dht peer is closing. message dropped") } return nil } } return nil } func (dhtPeer *DHTPeer) slowEnvelopeSendLoop() { lerror, _, _, ldebug := dhtPeer.GetLoggers() var err error for { envel, ok := <-dhtPeer.slow_queue if !ok || dhtPeer.isClosing { ldebug().Msg("slow envelope send loop closed") return } if envel == nil { ldebug().Msg("got nil envelope. exit slow envelope send loop") return } ldebug().Str("addr", envel.To).Msgf("-> Routing slow envelope: %s", envel.String()) err = dhtPeer._routeEnvelopeDHTLookup(envel, addressLookupTimeout) if err != nil { lerror(err).Msgf("while sending slow envelope: %s", envel.String()) } else { ldebug().Str("addr", envel.To).Msgf("sent slow envelope: %s", envel.String()) } } } func (dhtPeer *DHTPeer) _routeEnvelopeDHTLookup( envel *aea.Envelope, lookup_timeout time.Duration, ) error { target := envel.To lerror, _, linfo, _ := dhtPeer.GetLoggers() envelRec, err := dhtPeer.getEnvelopeAcnRecord(envel) if err != nil { return err } linfo().Str("op", "route").Str("addr", target). Msg("did NOT find destination address locally, looking for it in the DHT...") ctx, cancel_lookup := context.WithTimeout(context.Background(), lookup_timeout) defer cancel_lookup() peerID, _, err := dhtPeer.lookupAddressDHT(ctx, target) // guarantees peerID has a valid PoR if err != nil { lerror(err).Str("op", "route").Str("addr", target). Msg("while looking up address on the DHT") return err } err = dhtPeer._routeEnvelopeWithNewStream(envel, peerID, envelRec) return err } func (dhtPeer *DHTPeer) _routeEnvelopeWithNewStream( envel *aea.Envelope, peerID peer.ID, envelRec *acn.AgentRecord, ) error { //linfo().Str("op", "route").Str("addr", target). // Msgf("got peer id '%s' for agent address", peerID.Pretty()) //linfo().Str("op", "route").Str("addr", target). // Msgf("opening stream to target %s...", peerID.Pretty()) lerror, _, linfo, _ := dhtPeer.GetLoggers() routeCount, _ := dhtPeer.monitor.GetGauge(metricOpRouteCount) opLatencyRoute, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRoute) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() var err error target := envel.To ctx, cancel := context.WithTimeout(context.Background(), newStreamTimeout) defer cancel() stream, err := dhtPeer.routedHost.NewStream(ctx, peerID, dhtnode.AeaEnvelopeStream) if err != nil { lerror(err).Str("op", "route").Str("addr", target). Msgf("timeout, couldn't open stream to target %s", peerID.Pretty()) routeCount.Dec() return err } duration := timer.GetTimer(start) opLatencyRoute.Observe(float64(duration.Microseconds())) linfo().Str("op", "route").Str("addr", target). Msgf("sending envelope to target peer %s...", peerID.Pretty()) envelBytes, err := proto.Marshal(envel) if err != nil { lerror(err). Str("op", "route"). Str("addr", target). Msg("couldn't serialize envelope") errReset := stream.Reset() ignore(errReset) routeCount.Dec() return err } streamPipe := utils.StreamPipe{Stream: stream} err = acn.SendEnvelopeMessage(streamPipe, envelBytes, envelRec) if err != nil { lerror(err). Str("op", "route"). Str("addr", target). Msg("couldn't send envelope") errReset := stream.Reset() ignore(errReset) routeCount.Dec() return err } // wait for response linfo().Str("op", "route").Str("addr", target). Msgf("waiting fro envelope delivery confirmation from target peer %s...", peerID.Pretty()) statusBody, err := acn.ReadAcnStatus(streamPipe) if err != nil { lerror(err). Str("op", "route"). Str("addr", target). Msg("failed to decode acn status") routeCount.Dec() return err } if statusBody.Code != acn.SUCCESS { err = errors.New(statusBody.Code.String() + " : " + strings.Join(statusBody.Msgs, ":")) lerror(err). Str("op", "route"). Str("addr", target). Msg("failed to deliver envelope") routeCount.Dec() return err } routeCount.Dec() return nil } /// TOFIX(LR) should return (*dhtnode) func (dhtPeer *DHTPeer) lookupAddressDHT( ctx context.Context, address string, ) (peer.ID, *acn.AgentRecord, error) { lerror, lwarn, linfo, _ := dhtPeer.GetLoggers() var err error dhtLookupLatency, _ := dhtPeer.monitor.GetHistogram(metricDHTOpLatencyLookup) timer := dhtPeer.monitor.Timer() addressCID, err := utils.ComputeCID(address) if err != nil { return "", nil, err } linfo().Str("op", "lookup").Str("addr", address). Msgf("Querying for providers for cid %s...", addressCID.String()) ctx, cancel := context.WithTimeout(ctx, addressLookupTimeout) defer cancel() //var elapsed time.Duration var provider peer.AddrInfo var stream network.Stream start := timer.NewTimer() noProvider := false for { providers := dhtPeer.dht.FindProvidersAsync(ctx, addressCID, 0) for provider = range providers { duration := timer.GetTimer(start) dhtLookupLatency.Observe(float64(duration.Microseconds())) //linfo().Str("op", "lookup").Str("addr", address). // Msgf("found provider %s after %s", provider, elapsed.String()) // Add peer to host PeerStore - the provider should be the holder of the address dhtPeer.routedHost.Peerstore().AddAddrs( provider.ID, provider.Addrs, peerstore.PermanentAddrTTL, ) //linfo().Str("op", "lookup").Str("addr", address). // Msgf("opening stream to the address provider %s...", provider) ctxConnect := context.Background() stream, err = dhtPeer.routedHost.NewStream( ctxConnect, provider.ID, dhtnode.AeaAddressStream, ) if err != nil { lwarn().Str("op", "lookup").Str("addr", address). Msgf("couldn't open stream to address provider %s: %s, looking up other providers...", provider, err.Error()) dhtPeer.routedHost.Peerstore().ClearAddrs(provider.ID) continue } streamPipe := utils.StreamPipe{Stream: stream} linfo().Str("op", "lookup").Str("addr", address). Msgf("getting agent record from provider %s...", provider) // perfrormaddress lookup record, err := acn.PerformAddressLookup(streamPipe, address) // Response is either a LookupResponse or Status ignore(stream.Reset()) stream.Close() if err != nil { lwarn().Str("op", "lookup").Str("addr", address). Msgf("Failed agent lookup from provider %s (%s), looking up other providers...", provider, err.Error()) continue } // lookupResponse must be set valid, err := dhtnode.IsValidProofOfRepresentation( record, address, record.PeerPublicKey, ) if err != nil || valid.Code != acn.SUCCESS { errMsg := valid.Code.String() + " : " + strings.Join(valid.Msgs, ":") if err == nil { err = errors.New(errMsg) } else { err = errors.Wrap(err, valid.Code.String()+" : "+strings.Join(valid.Msgs, ":")) } lwarn().Str("op", "lookup").Str("addr", address). Msgf("invalid agent record from provider %s (%s), looking up other providers...", provider, err.Error()) continue } peerid, err := utils.IDFromFetchAIPublicKey(record.PeerPublicKey) if err != nil { return "", nil, errors.New( "CRITICAL couldn't get peer ID from message:" + err.Error(), ) } return peerid, record, nil } if provider.ID == "" { msg := "didn't find any provider for address" if !noProvider { noProvider = true lwarn().Str("op", "lookup").Str("addr", address).Msgf("%s, retrying...", msg) } select { default: time.Sleep(200 * time.Millisecond) case <-ctx.Done(): err = errors.New(msg + " " + address + " within timeout") lerror(err).Str("op", "lookup").Str("addr", address).Msg("") return "", nil, err } } else { return "", nil, err } } } // handleAeaEnvelopeStream deals with incoming envelopes on the AeaEnvelopeStream // envelopes arrive from other peers (full or client) and are processed // by HandleAeaEnvelope func (dhtPeer *DHTPeer) handleAeaEnvelopeStream(stream network.Stream) { common.HandleAeaEnvelopeStream(dhtPeer, stream) } // Callback to handle and route aea envelope comes from the aea envelope stream // return ACNError if message routing failed, otherwise nil. func (dhtPeer *DHTPeer) HandleAeaEnvelope(envel *aea.Envelope) *acn.ACNError { var err error lerror, lwarn, linfo, _ := dhtPeer.GetLoggers() dhtPeer.tcpAddressesLock.RLock() connDelegate, existsDelegate := dhtPeer.tcpAddresses[envel.To] dhtPeer.tcpAddressesLock.RUnlock() if dhtPeer.mailboxServer != nil { if dhtPeer.mailboxServer.RouteEnvelope(envel) { linfo().Str("op", "route").Str("addr", envel.To). Msg("route envelope destinated to mailbox registered agent...") return nil } } if existsDelegate { linfo().Msgf( "Sending envelope to tcp delegate client %s...", connDelegate.RemoteAddr().String(), ) data, err := aea.MakeAcnMessageFromEnvelope(envel) if err != nil { lerror(err).Msgf("while serializing envelope: %s", envel) return &acn.ACNError{ Err: errors.New("serializing envelope error"), ErrorCode: acn.ERROR_DECODE, } } err = utils.WriteBytesConn(connDelegate, data) if err != nil { lerror( err, ).Msgf( "while sending envelope to tcp client %s", connDelegate.RemoteAddr().String(), ) return &acn.ACNError{ Err: errors.New("agent is not ready"), ErrorCode: acn.ERROR_AGENT_NOT_READY, } } err = dhtPeer.AwaitAcnStatus(envel.To) if err != nil { lerror( err, ).Msgf( "while awating acn ack on sending envelope to tcp client %s", connDelegate.RemoteAddr().String(), ) return &acn.ACNError{ Err: errors.New("while awating acn ack on sending envelope to tcp clien"), ErrorCode: acn.ERROR_AGENT_NOT_READY, } } } else if envel.To == dhtPeer.myAgentAddress { if dhtPeer.processEnvelope == nil { lerror(err).Msgf("while processing envelope by agent") return &acn.ACNError{Err: errors.New("agent is not ready"), ErrorCode: acn.ERROR_AGENT_NOT_READY} } linfo().Msg("Processing envelope by local agent...") err = dhtPeer.processEnvelope(envel) ignore(err) } else { lwarn().Msgf("ignored envelope %s", envel.String()) return &acn.ACNError{Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS} } // all good return nil } func (dhtPeer *DHTPeer) handleAeaAddressStream(stream network.Stream) { common.HandleAeaAddressStream(dhtPeer, stream) } func (dhtPeer *DHTPeer) HandleAeaAddressRequest( reqAddress string, ) (*acn.AgentRecord, *acn.ACNError) { lerror, _, linfo, _ := dhtPeer.GetLoggers() // Msg("Received query for addr") var sPeerID string var sRecord *acn.AgentRecord = nil dhtPeer.dhtAddressesLock.RLock() idRelay, existsRelay := dhtPeer.dhtAddresses[reqAddress] dhtPeer.dhtAddressesLock.RUnlock() dhtPeer.tcpAddressesLock.RLock() _, existsDelegate := dhtPeer.tcpAddresses[reqAddress] dhtPeer.tcpAddressesLock.RUnlock() dhtPeer.agentRecordsLock.RLock() localRec := dhtPeer.agentRecords[reqAddress] dhtPeer.agentRecordsLock.RUnlock() if reqAddress == dhtPeer.myAgentAddress { peerID, err := peer.IDFromPublicKey(dhtPeer.publicKey) if err != nil { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msgf("CRITICAL could not get peer ID from public key %s", dhtPeer.publicKey) } else { sPeerID = peerID.Pretty() sRecord = dhtPeer.myAgentRecord } } else if existsRelay { linfo().Str("op", "resolve").Str("addr", reqAddress). Msg("found address in my relay clients map") sPeerID = idRelay sRecord = localRec } else if dhtPeer.mailboxServer != nil && dhtPeer.mailboxServer.IsAddrRegistered(reqAddress) { sPeerID = idRelay sRecord = dhtPeer.mailboxServer.GetAgentRecord(reqAddress) } else if existsDelegate { linfo().Str("op", "resolve").Str("addr", reqAddress). Msgf("found address in my delegate clients map") peerID, err := peer.IDFromPublicKey(dhtPeer.publicKey) if err != nil { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msgf("CRITICAL could not get peer ID from public key %s", dhtPeer.publicKey) } else { sPeerID = peerID.Pretty() sRecord = localRec } } else { // needed when a relay client queries for a peer ID //linfo().Str("op", "resolve").Str("addr", reqAddress). // Msg("did NOT found the address locally, looking for it in the DHT...") peerID, peerRecord, err := dhtPeer.lookupAddressDHT(context.Background(), reqAddress) if err == nil { linfo().Str("op", "resolve").Str("addr", reqAddress). Msg("found address on the DHT") sPeerID = peerID.Pretty() sRecord = peerRecord } else { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msgf("did NOT find address locally or on the DHT.") return nil, &acn.ACNError{ Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS, } } } if sRecord == nil { return nil, &acn.ACNError{ Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS, } } // record found, send linfo().Str("op", "resolve").Str("addr", reqAddress). Msgf("sending agent record (%s) %s", sPeerID, sRecord) return sRecord, nil } func (dhtPeer *DHTPeer) handleAeaNotifStream(stream network.Stream) { lerror, _, _, ldebug := dhtPeer.GetLoggers() ldebug().Str("op", "notif"). Msgf("Got a new notif stream. peerid: %s", stream.Conn().RemotePeer().Pretty()) opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() // workaround: to avoid getting `failed to find any peer in table` // when calling dht.Provide (happens occasionally) ldebug().Msg("waiting for notifying peer to be added to dht routing table...") ctx, cancel := context.WithTimeout( context.Background(), routingTableConnectionUpdateTimeout, ) defer cancel() defer dhtPeer.enableAddressAnnouncementWg.Done() dhtPeer.enableAddressAnnouncementWg.Add(1) for dhtPeer.dht.RoutingTable().Find(stream.Conn().RemotePeer()) == "" { select { case <-ctx.Done(): lerror(nil). Msgf("timeout: notifying peer %s haven't been added to DHT routing table", stream.Conn().RemotePeer().Pretty()) return case <-time.After(time.Millisecond * 5): } } if dhtPeer.myAgentAddress != "" && !dhtPeer.IsAddressAnnounced(dhtPeer.myAgentAddress) { err := dhtPeer.RegisterAgentAddress(dhtPeer.myAgentAddress) if err != nil { lerror(err).Str("op", "notif"). Str("addr", dhtPeer.myAgentAddress). Msgf("while announcing my agent address") return } } if dhtPeer.enableRelay { dhtPeer.dhtAddressesLock.RLock() for addr := range dhtPeer.dhtAddresses { err := dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Str("op", "notif"). Str("addr", addr). Msg("while announcing relay client address") } } dhtPeer.dhtAddressesLock.RUnlock() } if dhtPeer.delegatePort != 0 { dhtPeer.tcpAddressesLock.RLock() for addr := range dhtPeer.tcpAddresses { err := dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Str("op", "notif"). Str("addr", addr). Msg("while announcing delegate client address") } } dhtPeer.tcpAddressesLock.RUnlock() } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) ldebug().Msgf("Address was announced: peerid: %s", stream.Conn().RemotePeer().Pretty()) // got a connection to a peer, so now we can allow address announcements dhtPeer.enableAddressAnnouncementLock.Lock() dhtPeer.enableAddressAnnouncement = true dhtPeer.enableAddressAnnouncementLock.Unlock() } func (dhtPeer *DHTPeer) IsAddressAnnouncementEnabled() bool { // wait if new peers connection establish in process dhtPeer.enableAddressAnnouncementWg.Wait() dhtPeer.enableAddressAnnouncementLock.Lock() isEnabled := dhtPeer.enableAddressAnnouncement dhtPeer.enableAddressAnnouncementLock.Unlock() return isEnabled } func (dhtPeer *DHTPeer) handleAeaRegisterStream(stream network.Stream) { lerror, _, linfo, _ := dhtPeer.GetLoggers() // to limit spamming time.Sleep(dhtPeer.registrationDelay) nbrClients, _ := dhtPeer.monitor.GetCounter(metricServiceRelayClientsCountAll) opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() //linfo().Str("op", "register"). // Msg("Got a new aea register stream") streamPipe := utils.StreamPipe{Stream: stream} // Get Register message register, err := acn.ReadAgentRegistrationMessage(streamPipe) if err != nil { lerror(err).Str("op", "register"). Msg("while reading relay client registration request from stream") err = stream.Reset() ignore(err) return } record := register.Record clientAddr := record.Address //linfo().Msgf("connection from %s established for Address %s", // stream.Conn().RemotePeer().Pretty(), clientAddr) // check if the PoR is valid clientPubKey, err := utils.FetchAIPublicKeyFromPubKey(stream.Conn().RemotePublicKey()) ignore(err) status, err := dhtnode.IsValidProofOfRepresentation(record, record.Address, clientPubKey) if err != nil || status.Code != acn.SUCCESS { lerror(err).Msg("PoR is not valid") acn_send_error := acn.SendAcnError(streamPipe, err.Error(), status.Code) ignore(acn_send_error) return } // TOFIX(LR) post-pone answer until address successfully registered err = acn.SendAcnSuccess(streamPipe) if err != nil { err = stream.Reset() ignore(err) return } stream.Close() nbrClients.Inc() //linfo().Str("op", "register"). // Str("addr", string(clientAddr)). // Msgf("Received address registration request for peer id %s", string(clientPeerID)) clientPeerID := stream.Conn().RemotePeer().Pretty() dhtPeer.agentRecordsLock.Lock() dhtPeer.agentRecords[clientAddr] = record dhtPeer.agentRecordsLock.Unlock() dhtPeer.dhtAddressesLock.Lock() dhtPeer.dhtAddresses[clientAddr] = clientPeerID err = dhtPeer.saveAgentRecordToPersistentStorage(record) if err != nil { lerror(err).Str("op", "register"). Str("addr", clientAddr). Msg("while saving agent record to persistent storage") } dhtPeer.dhtAddressesLock.Unlock() linfo().Str("op", "register"). Str("addr", clientAddr). Msgf("peer added: %s", clientPeerID) if dhtPeer.IsAddressAnnouncementEnabled() && !dhtPeer.IsAddressAnnounced(string(clientAddr)) { linfo().Str("op", "register"). Str("addr", clientAddr). Msgf("Announcing client address on behalf of %s...", clientPeerID) err = dhtPeer.RegisterAgentAddress(string(clientAddr)) if err != nil { //TOFIX(LR) remove agent from map, or don't add it unless announcement done lerror(err).Str("op", "register"). Str("addr", clientAddr). Msg("while announcing client address to the dht") return } } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) } func (dhtPeer *DHTPeer) RegisterAgentAddress(addr string) error { _, _, linfo, _ := dhtPeer.GetLoggers() if dhtPeer.IsAddressAnnounced(addr) { // already announced return nil } dhtPeer.setAddressAnnounced(addr) dhtStoreLatency, _ := dhtPeer.monitor.GetHistogram(metricDHTOpLatencyStore) timer := dhtPeer.monitor.Timer() addressCID, err := utils.ComputeCID(addr) if err != nil { return err } // TOFIX(LR) tune timeout ctx, cancel := context.WithTimeout(context.Background(), addressRegisterTimeout) defer cancel() linfo().Str("op", "register"). Str("addr", addr). Msgf("Announcing address to the dht with cid key %s", addressCID.String()) start := timer.NewTimer() err = dhtPeer.dht.Provide(ctx, addressCID, true) if err != context.DeadlineExceeded { duration := timer.GetTimer(start) dhtStoreLatency.Observe(float64(duration.Microseconds())) return err } return nil } func (dhtPeer *DHTPeer) IsAddressAnnounced(address string) bool { dhtPeer.addressAnnouncedMapLock.Lock() _, present := dhtPeer.addressAnnouncedMap[address] dhtPeer.addressAnnouncedMapLock.Unlock() return present } func (dhtPeer *DHTPeer) setAddressAnnounced(address string) { dhtPeer.addressAnnouncedMapLock.Lock() dhtPeer.addressAnnouncedMap[address] = true dhtPeer.addressAnnouncedMapLock.Unlock() } func (dhtPeer *DHTPeer) AddAcnStatusMessage(status *acn.StatusBody, counterpartyID string) { dhtPeer.acnStatusesLock.Lock() queue := dhtPeer.acnStatuses[counterpartyID] dhtPeer.acnStatusesLock.Unlock() queue <- status } func (dhtPeer *DHTPeer) AwaitAcnStatus(counterpartyID string) error { lerror, _, _, _ := dhtPeer.GetLoggers() dhtPeer.acnStatusesLock.Lock() counterParty := dhtPeer.acnStatuses[counterpartyID] dhtPeer.acnStatusesLock.Unlock() status, err := acn.WaitForStatus(counterParty, AcnStatusTimeout) if err != nil { lerror(err).Msg("timeout on status wait") return err } if status.Code != acn.SUCCESS { lerror(err).Msgf("bad status: %d", status.Code) return errors.New("bad status!") } return err } ================================================ FILE: libs/go/libp2p_node/dht/dhtpeer/dhtpeer_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/tls" "log" "math/rand" "net" "os" "path" "strconv" "testing" "time" "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/dht/dhtclient" "libp2p_node/dht/dhtnode" "libp2p_node/utils" "github.com/pkg/errors" "google.golang.org/protobuf/proto" ) /* DHTPeer and DHT network routing tests */ const ( DefaultLocalHost = "127.0.0.1" DefaultLocalPort = 2000 DefaultDelegatePort = 3000 EnvelopeDeliveryTimeout = 1 * time.Second DHTPeerSetupTimeout = 5 * time.Second DefaultLedger = dhtnode.DefaultLedger ) var ( FetchAITestKeys = []string{ "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", "92c36941ae78c1b93e5f4bebcf2b40be0af37573aa263ebb70b769ea235b88b6", "b6a8ff857c49b81895f18dd6dbd309e270906b75e2c290a721da48c5de4cba70", "91a90b5be4817c46e06f0e792dd9d9ef3ceb2dbb5ff5c45125153d289d515ce1", "5ee086c5c3df6f641e36e083769d6a03f918b33e4505b1102d2be7a75bb2ae0f", "6768d7918659c1699a379691381c19e55c3c13c49d30086e74a86524123659fb", "d31485403d0cce93b0c48a2fad2acae61a68396e93a602acfcd08dadd7ba12ae", "db533c3e74963a0571e962a4022a4ebce14ab5f240299b5350c83dd18549c1fd", "95aaa63bceeb0946c877c414e1f17119b8a975417924d83db8e281abd71820b2", "9427c1472b66f6abd94a6c246eee495e3709ec45882ae0badcbc71ad2cd8f8b2", } FetchAITestPublicKeys = []string{ "03b7e977f498dce004e2614764ff576e17cc6691135497e7bcb5d3441e816ba9e1", "02344c3f0e79f56aef8e167a6fea912745f1f770b66b4c5096040c0e8c9e3c68b3", "023d6021c001c7b562af8b6e54ace4f98b1b14170d7db4749ecab2b1f0e4252794", "02a0eb20ae23f2f78650b42dfafa6bf4e4752657905da8598b2c0806478e0bfa0d", "023db373d1fc21212f2f03fec1ddd49f193f54f71545e72f37c8a70ca20ef1622b", "03290b4e5dabcca2a994a8d63057f5c83f60d999ede181a8d9b42084e3bee256c2", "03510651fbb9d2ce5b7ae00968339055fbc552e565c54cce8c69f5a52209d3d6a7", "02c11df29b5873e0c37d1427c488ba84e5ccc57405d39299757cee06893ab8595d", "031545edc0fe81a17c77a391a343f95547745b28703bbe664e12c523e3272b637e", "02dd78522785e4175e7db9794b03adcdcfaf707153f307caa3368da5a30594d369", } AgentsTestKeys = []string{ "730c22474709a6d17cf11599a80413a84ddb691a3c7b11a6d8d47a2c024b7b56", "a085c5eeb39636a21c85a9bc667bae18bf3e327a220ecb3998e317b62ab20ec6", "0b7af750e7e96ceb9fe5582bdf9bdafae726427d34447f7245a084b6cf0aa5e5", "dffaa5a9779931a2c1194794e6e9a89787557d6cd708d84c74de20ec5e03a7bf", "509c4019dd96a337a36149031869e6de5db014ab9ae5d8097ac997ca8f10422a", "a385fa48b4f40a2f4ea66de88c0021532299865fe6137d765788f9f856e79453", "ff212371e454f8292fd3b13020a3910fc91002a7ab5eb3f297b71df6b7ff9bc1", "04289e97041fc025c103141909d2cce649944153822f032b646214a850363618", "116294510fba759d19af7a65b915467384258d997695ed7018d8c19d38c29412", "dc2f0238e65c0291bedae58cb1c013bd03e0f41f78e1779744ac401952ec2b51", } AgentsTestAddresses = []string{ "fetch1y39e4tec9fll66x2k7wed5qn7zhaneayjm55kk", "fetch1ufjmhth6dnhrckxrvk05lmt8s2vture23xvwjl", "fetch1dja5uazc9n7jpjm94rhmkkmcyv5nj3kt8aexgf", "fetch18v5lz9psp53akm26ztk3exytqfdvpnfdsyx232", "fetch10u6ra4qmukhf57xadv64jt9jhr9gdrg707x6l9", "fetch1hys3k2anw5mxe0y2vksccpe58jyk5gksrsjd60", "fetch1t07jnjjtlqa07mstg4gw9twjs2ddtqs3sgtx7c", "fetch18sxxgat6uaxqxvd7mgt99y7avyy3c24av36u2l", "fetch1sx2rmtndc5t97pn00x76sksrzgc9s2watpgw64", "fetch1mwd8n27t68svv4w5urztgw7e3kjh7nqkqz0j94", } ) /* DHT Network: DHTPeer-to-DHTPeer */ // TestRoutingDHTPeerToSelf dht peer with agent attached func TestRoutingDHTPeerToSelf(t *testing.T) { opts := []Option{ LocalURI(DefaultLocalHost, DefaultLocalPort), PublicURI(DefaultLocalHost, DefaultLocalPort), IdentityFromFetchAIKey(FetchAITestKeys[0]), EnableRelayService(), EnableDelegateService(DefaultDelegatePort), StoreRecordsTo(path.Join(os.TempDir(), "agents_records_"+randSeq(5))), } agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(AgentsTestKeys[0]) if err != nil { t.Fatal("Failed at DHTPeer initialization:", err) } signature, err := utils.SignFetchAI([]byte(FetchAITestPublicKeys[0]), AgentsTestKeys[0]) if err != nil { t.Fatal("Failed at DHTPeer initialization:", err) } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = AgentsTestAddresses[0] record.PublicKey = agentPubKey record.PeerPublicKey = FetchAITestPublicKeys[0] record.Signature = signature opts = append(opts, RegisterAgentAddress(record, func() bool { return true })) dhtPeer, err := New(opts...) if err != nil { t.Fatal("Failed at DHTPeer initialization:", err) } defer dhtPeer.Close() var rxEnvelopes []*aea.Envelope dhtPeer.ProcessEnvelope(func(envel *aea.Envelope) error { rxEnvelopes = append(rxEnvelopes, envel) return nil }) err = dhtPeer.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[0], }) if err != nil { t.Error("Failed to Route envelope to local Agent") } if len(rxEnvelopes) == 0 { t.Error("Failed to Route & Process envelope to local Agent") } } // TestRoutingDHTPeerToDHTPeerDirect from a dht peer to its bootstrap peer func TestRoutingDHTPeerToDHTPeerDirect(t *testing.T) { dhtPeer1, cleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup1() dhtPeer2, cleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{dhtPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup2() rxPeer1 := make(chan *aea.Envelope, 2) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) rxPeer2 := make(chan *aea.Envelope, 2) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(dhtPeer1, dhtPeer2) err = dhtPeer2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } expectEnvelope(t, rxPeer1) expectEnvelope(t, rxPeer2) } // TestRoutingDHTPeerToDHTPeerIndirect two dht peers connected to the same peer func TestRoutingDHTPeerToDHTPeerIndirect(t *testing.T) { entryPeer, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup() dhtPeer1, cleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup1() dhtPeer2, cleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup2() rxPeer1 := make(chan *aea.Envelope, 2) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) rxPeer2 := make(chan *aea.Envelope, 2) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(dhtPeer1, dhtPeer2) err = dhtPeer2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } expectEnvelope(t, rxPeer1) expectEnvelope(t, rxPeer2) } // TestRoutingDHTPeerToDHTPeerIndirectTwoHops two dht peers connected to different peers func TestRoutingDHTPeerToDHTPeerIndirectTwoHops(t *testing.T) { entryPeer1, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup() entryPeer2, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup() time.Sleep(1 * time.Second) dhtPeer1, cleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{entryPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup1() dhtPeer2, cleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[3], AgentsTestKeys[3], DefaultLocalPort+3, DefaultDelegatePort+3, []string{entryPeer2.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup2() rxPeer1 := make(chan *aea.Envelope, 2) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) rxPeer2 := make(chan *aea.Envelope, 2) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(dhtPeer1, dhtPeer2) err = dhtPeer2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } expectEnvelope(t, rxPeer1) expectEnvelope(t, rxPeer2) } // TestRoutingDHTPeerToDHTPeerFullConnectivity fully connected dht peers network func TestRoutingDHTPeerToDHTPeerFullConnectivity(t *testing.T) { peers := []*DHTPeer{} rxs := []chan *aea.Envelope{} for i := range FetchAITestKeys { peer, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[i], AgentsTestKeys[i], DefaultLocalPort+uint16(i), DefaultDelegatePort+uint16(i), func() []string { multiaddrs := []string{} for _, entryPeer := range peers { multiaddrs = append(multiaddrs, entryPeer.MultiAddr()) } return multiaddrs }(), ) if err != nil { t.Fatal("Failed to initialize DHTPeer", i, ":", err) } rx := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rx <- envel if string(envel.Message) == "ping" { err := peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) peers = append(peers, peer) rxs = append(rxs, rx) defer cleanup() } ensureAddressAnnounced(peers...) for i := range peers { for j := range peers { from := len(peers) - 1 - i target := j // Should be able to route to self though if from == target { continue } err := peers[from].RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[target], Sender: AgentsTestAddresses[from], Message: []byte("ping"), }) if err != nil { t.Error("Failed to RouteEnvelope from ", from, "to", target) } expectEnvelope(t, rxs[target]) expectEnvelope(t, rxs[from]) } } } /* DHT network: DHTClient */ // TestRoutingDHTClientToDHTPeer dht client to its bootstrap peer func TestRoutingDHTClientToDHTPeerX(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client, clientCleanup, err := SetupDHTClient( FetchAITestKeys[1], AgentsTestKeys[1], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup() rxPeer := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient := make(chan *aea.Envelope, 2) client.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient <- envel return nil }) time.Sleep(1 * time.Second) err = client.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTPeer:", err) } expectEnvelope(t, rxPeer) expectEnvelope(t, rxClient) } // TestRoutingDHTClientToDHTPeerIndirect dht client to dht peer different than its bootstrap one func TestRoutingDHTClientToDHTPeerIndirect(t *testing.T) { entryPeer, entryPeerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer entryPeerCleanup() peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() time.Sleep(1 * time.Second) client, clientCleanup, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup() rxPeer := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient := make(chan *aea.Envelope, 2) client.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient <- envel return nil }) ensureAddressAnnounced(entryPeer, peer) time.Sleep(1 * time.Second) err = client.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTPeer:", err) } expectEnvelope(t, rxPeer) expectEnvelope(t, rxClient) } // TestRoutingDHTClientToDHTClient dht client to dht client connected to the same peer func TestRoutingDHTClientToDHTClient(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client1, clientCleanup1, err := SetupDHTClient( FetchAITestKeys[1], AgentsTestKeys[1], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup2() rxClient1 := make(chan *aea.Envelope, 2) client1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient1 <- envel return client1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient2 := make(chan *aea.Envelope, 2) client2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient2 <- envel return nil }) time.Sleep(1 * time.Second) err = client2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTClient:", err) } expectEnvelope(t, rxClient1) expectEnvelope(t, rxClient2) } // TestRoutingDHTClientToDHTClientIndirect dht client to dht client connected to a different peer func TestRoutingDHTClientToDHTClientIndirect(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() client1, clientCleanup1, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDHTClient( FetchAITestKeys[3], AgentsTestKeys[3], []string{peer2.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup2() rxClient1 := make(chan *aea.Envelope, 2) client1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient1 <- envel return client1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient2 := make(chan *aea.Envelope, 2) client2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient2 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) time.Sleep(1 * time.Second) err = client2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTClient:", err) } expectEnvelope(t, rxClient1) expectEnvelope(t, rxClient2) } /* DHT network: DelegateClient */ // TestRoutingDelegateClientToDHTPeer func TestRoutingDelegateClientToDHTPeerX(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[1], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return nil }) err = client.Send(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } expectEnvelope(t, rxPeer) err = peer.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[0], }) if err != nil { t.Error("Failed to RouteEnvelope from peer to delegate client:", err) } expectEnvelope(t, client.Rx) } // TestRoutingDelegateClientToDHTPeerIndirect func TestRoutingDelegateClientToDHTPeerIndirect(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(3 * time.Second) client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer1 := make(chan *aea.Envelope, 20) peer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) err = client.Send(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } expectEnvelope(t, rxPeer1) err = peer1.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[0], }) if err != nil { t.Error("Failed to RouteEnvelope from peer to delegate client:", err) } expectEnvelope(t, client.Rx) } // TestMessageOrderingWithDelegateClient func TestMessageOrderingWithDelegateClient(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(1 * time.Second) client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer1 := make(chan *aea.Envelope, 20) peer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel return nil }) rxPeer2 := make(chan *aea.Envelope, 20) peer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) max := 10 i := 0 for x := 0; x < max; x++ { envelope := &aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[2], Message: []byte(strconv.Itoa(i)), } err = client.Send(envelope) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope) // time.Sleep(100 * time.Millisecond) envelope1 := &aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], Message: []byte(strconv.Itoa(i)), } err = client.Send(envelope1) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope1) // time.Sleep(100 * time.Millisecond) } // go func() { ii := 0 for x := 0; x < max; x++ { expectEnvelopeOrdered(t, rxPeer1, ii) ii++ ii++ } // }() // go func() { iii := 0 for x := 0; x < max; x++ { iii++ expectEnvelopeOrdered(t, rxPeer2, iii) iii++ } // }() print("STOP OF THE TEST") } // TestMessageOrderingWithDelegateClientTwoHops func TestMessageOrderingWithDelegateClientTwoHops(t *testing.T) { peer1Index := 0 peer2Index := 1 client1Index := 2 client2Index := 3 peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[peer1Index], AgentsTestKeys[peer1Index], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[peer2Index], AgentsTestKeys[peer2Index], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(1 * time.Second) client1, clientCleanup1, err := SetupDelegateClient( AgentsTestKeys[client1Index], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[peer1Index], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDelegateClient( AgentsTestKeys[client2Index], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[peer2Index], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup2() rxClient1 := make(chan *aea.Envelope, 20) client1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient1 <- envel return nil }) rxClient2 := make(chan *aea.Envelope, 20) client2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient2 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) max := 100 i := 0 for x := 0; x < max; x++ { envelope := &aea.Envelope{ To: AgentsTestAddresses[client2Index], Sender: AgentsTestAddresses[client1Index], Message: []byte(strconv.Itoa(i)), } err = client1.Send(envelope) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope) // time.Sleep(100 * time.Millisecond) envelope1 := &aea.Envelope{ To: AgentsTestAddresses[client1Index], Sender: AgentsTestAddresses[client2Index], Message: []byte(strconv.Itoa(i)), } err = client2.Send(envelope1) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope1) // time.Sleep(100 * time.Millisecond) } // go func() { ii := 0 for x := 0; x < max; x++ { expectEnvelopeOrdered(t, rxClient2, ii) ii++ ii++ } // }() // go func() { iii := 0 for x := 0; x < max; x++ { iii++ expectEnvelopeOrdered(t, rxClient1, iii) iii++ } // }() print("STOP OF THE TEST") } // TestRoutingDelegateClientToDHTPeerIndirectTwoHops func TestRoutingDelegateClientToDHTPeerIndirectTwoHops(t *testing.T) { entryPeer, entryPeerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer entryPeerCleanup() peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(1 * time.Second) client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[3], DefaultLocalHost, DefaultDelegatePort+2, FetchAITestPublicKeys[2], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer1 := make(chan *aea.Envelope, 2) peer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel return nil }) ensureAddressAnnounced(entryPeer, peer1, peer2) err = client.Send(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } expectEnvelope(t, rxPeer1) err = peer1.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[3], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to RouteEnvelope from peer to delegate client:", err) } expectEnvelope(t, client.Rx) } // TestRoutingDelegateClientToDelegateClient func TestRoutingDelegateClientToDelegateClient(t *testing.T) { _, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client1, clientCleanup1, err := SetupDelegateClient( AgentsTestKeys[1], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup2() time.Sleep(1 * time.Second) err = client1.Send(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client2.Rx) err = client2.Send(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client1.Rx) } // TestRoutingDelegateClientToDelegateClientIndirect func TestRoutingDelegateClientToDelegateClientIndirect(t *testing.T) { peer1, peer1Cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peer1Cleanup() peer2, peer2Cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peer2Cleanup() client1, clientCleanup1, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDelegateClient( AgentsTestKeys[3], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup2() ensureAddressAnnounced(peer1, peer2) err = client1.Send(&aea.Envelope{ To: AgentsTestAddresses[3], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client2.Rx) err = client2.Send(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client1.Rx) } // TestRoutingDelegateClientToDHTClientDirect func TestRoutingDelegateClientToDHTClientDirect(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() dhtClient, dhtClientCleanup, err := SetupDHTClient( FetchAITestKeys[1], AgentsTestKeys[1], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup() delegateClient, delegateClientCleanup, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup() rxClientDHT := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT <- envel return dhtClient.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) time.Sleep(1 * time.Second) err = delegateClient.Send(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTClient:", err) } expectEnvelope(t, rxClientDHT) expectEnvelope(t, delegateClient.Rx) } // TestRoutingDelegateClientToDHTClientIndirect func TestRoutingDelegateClientToDHTClientIndirect(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() dhtClient, dhtClientCleanup, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup() delegateClient, delegateClientCleanup, err := SetupDelegateClient( AgentsTestKeys[3], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup() rxClientDHT := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT <- envel return dhtClient.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) ensureAddressAnnounced(peer1, peer2) time.Sleep(3 * time.Second) err = delegateClient.Send(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTClient:", err) } expectEnvelope(t, rxClientDHT) expectEnvelope(t, delegateClient.Rx) } /* DHT network: all-to-all */ /* Network topology DHTClient ------- -- DelegateClient | | DHTClient ------- -- DelegateClient | | |-- DHTPeer --- DHTPeeer -- DHTPeer --- DHTPeer --| | | DelegateClient -- ------- DHTClient */ // TestRoutingAlltoAll func TestRoutingAllToAll(t *testing.T) { rxs := []chan *aea.Envelope{} send := []func(*aea.Envelope) error{} // setup DHTPeers dhtPeer1, dhtPeerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup1() rxPeerDHT1 := make(chan *aea.Envelope, 100) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT1 <- envel if string(envel.Message) == "ping" { err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT1) send = append(send, func(envel *aea.Envelope) error { return dhtPeer1.RouteEnvelope(envel) }) dhtPeer2, dhtPeerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{dhtPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup2() rxPeerDHT2 := make(chan *aea.Envelope, 100) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT2 <- envel if string(envel.Message) == "ping" { err := dhtPeer2.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT2) send = append(send, func(envel *aea.Envelope) error { return dhtPeer2.RouteEnvelope(envel) }) dhtPeer3, dhtPeerCleanup3, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{dhtPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup3() rxPeerDHT3 := make(chan *aea.Envelope, 100) dhtPeer3.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT3 <- envel if string(envel.Message) == "ping" { err := dhtPeer3.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT3) send = append(send, func(envel *aea.Envelope) error { return dhtPeer3.RouteEnvelope(envel) }) dhtPeer4, dhtPeerCleanup4, err := SetupLocalDHTPeer( FetchAITestKeys[3], AgentsTestKeys[3], DefaultLocalPort+3, DefaultDelegatePort+3, []string{dhtPeer2.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup4() rxPeerDHT4 := make(chan *aea.Envelope, 100) dhtPeer4.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT4 <- envel if string(envel.Message) == "ping" { err := dhtPeer4.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT4) send = append(send, func(envel *aea.Envelope) error { return dhtPeer4.RouteEnvelope(envel) }) // setup DHTClients dhtClient1, dhtClientCleanup1, err := SetupDHTClient( FetchAITestKeys[4], AgentsTestKeys[4], []string{dhtPeer3.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup1() rxClientDHT1 := make(chan *aea.Envelope, 100) dhtClient1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT1 <- envel if string(envel.Message) == "ping" { err := dhtClient1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDHT1) send = append(send, func(envel *aea.Envelope) error { return dhtClient1.RouteEnvelope(envel) }) dhtClient2, dhtClientCleanup2, err := SetupDHTClient( FetchAITestKeys[5], AgentsTestKeys[5], []string{dhtPeer3.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup2() rxClientDHT2 := make(chan *aea.Envelope, 100) dhtClient2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT2 <- envel if string(envel.Message) == "ping" { err := dhtClient2.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDHT2) send = append(send, func(envel *aea.Envelope) error { return dhtClient2.RouteEnvelope(envel) }) dhtClient3, dhtClientCleanup3, err := SetupDHTClient( FetchAITestKeys[6], AgentsTestKeys[6], []string{dhtPeer4.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup3() rxClientDHT3 := make(chan *aea.Envelope, 100) dhtClient3.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT3 <- envel if string(envel.Message) == "ping" { err := dhtClient3.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDHT3) send = append(send, func(envel *aea.Envelope) error { return dhtClient3.RouteEnvelope(envel) }) // setup DelegateClients delegateClient1, delegateClientCleanup1, err := SetupDelegateClient( AgentsTestKeys[7], DefaultLocalHost, DefaultDelegatePort+2, FetchAITestPublicKeys[2], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup1() rxClientDelegate1 := make(chan *aea.Envelope, 100) delegateClient1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDelegate1 <- envel if string(envel.Message) == "ping" { err := delegateClient1.Send(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDelegate1) send = append(send, func(envel *aea.Envelope) error { return delegateClient1.Send(envel) }) delegateClient2, delegateClientCleanup2, err := SetupDelegateClient( AgentsTestKeys[8], DefaultLocalHost, DefaultDelegatePort+3, FetchAITestPublicKeys[3], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup2() rxClientDelegate2 := make(chan *aea.Envelope, 100) delegateClient2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDelegate2 <- envel if string(envel.Message) == "ping" { err := delegateClient2.Send(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDelegate2) send = append(send, func(envel *aea.Envelope) error { return delegateClient2.Send(envel) }) delegateClient3, delegateClientCleanup3, err := SetupDelegateClient( AgentsTestKeys[9], DefaultLocalHost, DefaultDelegatePort+3, FetchAITestPublicKeys[3], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup3() rxClientDelegate3 := make(chan *aea.Envelope, 100) delegateClient3.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDelegate3 <- envel if string(envel.Message) == "ping" { err := delegateClient3.Send(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDelegate3) send = append(send, func(envel *aea.Envelope) error { return delegateClient3.Send(envel) }) // Send envelope from everyone to everyone else and expect an echo back ensureAddressAnnounced(dhtPeer1, dhtPeer2, dhtPeer3, dhtPeer4) for i := range AgentsTestAddresses { for j := range AgentsTestAddresses { from := len(AgentsTestAddresses) - 1 - i target := j // Should be able to route to self though if from == target { continue } err := send[from](&aea.Envelope{ To: AgentsTestAddresses[target], Sender: AgentsTestAddresses[from], Message: []byte("ping"), }) if err != nil { t.Error("Failed to RouteEnvelope from ", from, "to", target) } } } for i := range AgentsTestAddresses { for j := range AgentsTestAddresses { from := len(AgentsTestAddresses) - 1 - i target := j if from == target { continue } expectEnvelope(t, rxs[target]) expectEnvelope(t, rxs[from]) } } } /* Helpers TOFIX(LR) how to share test helpers between packages tests without having circular dependencies */ func randSeq(n int) string { rand.Seed(time.Now().UnixNano()) var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func SetupLocalDHTPeer( key string, agentKey string, dhtPort uint16, delegatePort uint16, entry []string, ) (*DHTPeer, func(), error) { opts := []Option{ LocalURI(DefaultLocalHost, dhtPort), PublicURI(DefaultLocalHost, dhtPort), IdentityFromFetchAIKey(key), EnableRelayService(), BootstrapFrom(entry), StoreRecordsTo(path.Join(os.TempDir(), "agents_records_"+randSeq(5))), } if agentKey != "" { agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(agentKey) if err != nil { return nil, nil, err } agentAddress, err := utils.FetchAIAddressFromPublicKey(agentPubKey) if err != nil { return nil, nil, err } peerPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } signature, err := utils.SignFetchAI([]byte(peerPubKey), agentKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = agentAddress record.PublicKey = agentPubKey record.PeerPublicKey = peerPubKey record.Signature = signature opts = append(opts, RegisterAgentAddress(record, func() bool { return true })) } if delegatePort != 0 { opts = append(opts, EnableDelegateService(delegatePort)) } dhtPeer, err := New(opts...) if err != nil { return nil, nil, err } return dhtPeer, func() { println("dhtpeer going to be closed!"); dhtPeer.Close() }, nil } // DHTClient func SetupDHTClient( key string, agentKey string, entry []string, ) (*dhtclient.DHTClient, func(), error) { agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(agentKey) if err != nil { return nil, nil, err } agentAddress, err := utils.FetchAIAddressFromPublicKey(agentPubKey) if err != nil { return nil, nil, err } peerPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } signature, err := utils.SignFetchAI([]byte(peerPubKey), agentKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = agentAddress record.PublicKey = agentPubKey record.PeerPublicKey = peerPubKey record.Signature = signature opts := []dhtclient.Option{ dhtclient.IdentityFromFetchAIKey(key), dhtclient.RegisterAgentAddress(record, func() bool { return true }), dhtclient.BootstrapFrom(entry), } dhtClient, err := dhtclient.New(opts...) if err != nil { println("dhtclient.New:", err.Error()) return nil, nil, err } return dhtClient, func() { println("dhtclient going to be closed!"); dhtClient.Close() }, nil } // Delegate tcp client for tests only type DelegateClient struct { AgentAddress string AgentKey string AgentPubKey string PeerPubKey string PoR string Rx chan *aea.Envelope Conn net.Conn processEnvelope func(*aea.Envelope) error acn_status_chan chan *acn.StatusBody } func (client *DelegateClient) AddAcnStatusMessage(status *acn.StatusBody, counterpartyID string) { //client.acn_status_chan <- status } func (client *DelegateClient) Close() error { return client.Conn.Close() } func (client *DelegateClient) Send(envelope *aea.Envelope) error { data, err := aea.MakeAcnMessageFromEnvelope(envelope) if err != nil { println("while serializing envelope:", err.Error()) return err } err = utils.WriteBytesConn(client.Conn, data) if err != nil { println("while sending envelope:", err.Error()) return err } return err } func (client *DelegateClient) ProcessEnvelope(fn func(*aea.Envelope) error) { client.processEnvelope = fn } func ValidateTLSSignature( tlsSignature []byte, sessionPubKey *ecdsa.PublicKey, peerPubKey string, ) error { sessionPubKeyBytes := elliptic.Marshal(sessionPubKey.Curve, sessionPubKey.X, sessionPubKey.Y) verifyKey, err := utils.PubKeyFromFetchAIPublicKey(peerPubKey) if err != nil { return err } ok, err := verifyKey.Verify(sessionPubKeyBytes, tlsSignature) if err != nil { return err } if !ok { return errors.New("error on signature validation for tls start") } return nil } func SetupDelegateClient( key string, host string, port uint16, peerPubKey string, ) (*DelegateClient, func(), error) { var err error client := &DelegateClient{} client.AgentKey = key client.acn_status_chan = make(chan *acn.StatusBody, 10000) pubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } client.AgentPubKey = pubKey address, err := utils.FetchAIAddressFromPublicKey(pubKey) if err != nil { return nil, nil, err } client.AgentAddress = address signature, err := utils.SignFetchAI([]byte(peerPubKey), key) if err != nil { return nil, nil, err } client.PoR = signature client.Rx = make(chan *aea.Envelope, 2) conf := &tls.Config{ InsecureSkipVerify: true, } client.Conn, err = tls.Dial("tcp", host+":"+strconv.FormatInt(int64(port), 10), conf) if err != nil { return nil, nil, err } certPubKey := client.Conn.(*tls.Conn).ConnectionState().PeerCertificates[0].PublicKey.(*ecdsa.PublicKey) tlsSignature, _ := utils.ReadBytesConn(client.Conn) err = ValidateTLSSignature(tlsSignature, certPubKey, peerPubKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = address record.PublicKey = pubKey record.PeerPublicKey = peerPubKey record.Signature = signature registration := &acn.RegisterPerformative{Record: record} msg := &acn.AcnMessage{ Performative: &acn.Register{Register: registration}, } data, err := proto.Marshal(msg) ignore(err) err = utils.WriteBytesConn(client.Conn, data) ignore(err) data, err = utils.ReadBytesConn(client.Conn) if err != nil { return nil, nil, err } response := &acn.AcnMessage{} err = proto.Unmarshal(data, response) if err != nil { return nil, nil, err } // Get Status message var status *acn.StatusPerformative switch pl := response.Performative.(type) { case *acn.Status: status = pl.Status default: return nil, nil, err } if status.Body.Code != acn.SUCCESS { println("Registration error:", status.Body.String()) return nil, nil, errors.New(status.Body.String()) } pipe := utils.ConnPipe{Conn: client.Conn} go func() { for { envel, err := aea.HandleAcnMessageFromPipe(pipe, client, "") if err != nil { break } if envel == nil { continue } _ = acn.SendAcnSuccess(pipe) if client.processEnvelope != nil { err = client.processEnvelope(envel) ignore(err) } else { client.Rx <- envel } } }() return client, func() { client.Close() }, nil } func expectEnvelope(t *testing.T, rx chan *aea.Envelope) { timeout := time.After(EnvelopeDeliveryTimeout) select { case envel := <-rx: t.Log("Received envelope", envel) case <-timeout: t.Error("Failed to receive envelope before timeout") } } func expectEnvelopeOrdered(t *testing.T, rx chan *aea.Envelope, counter int) { timeout := time.After(EnvelopeDeliveryTimeout) select { case envel := <-rx: t.Log("Received envelope", envel) if envel == nil { t.Log("Empty envelope. exit") return } message, _ := strconv.Atoi(string(envel.Message)) if message != counter { log.Fatalf("Expected counter %d received counter %d", counter, message) } case <-timeout: t.Error("Failed to receive envelope before timeout") } } func ensureAddressAnnounced(peers ...*DHTPeer) { for _, peer := range peers { ctx, cancel := context.WithTimeout(context.Background(), DHTPeerSetupTimeout) defer cancel() L: for !peer.IsAddressAnnounced(peer.myAgentAddress) { select { case <-ctx.Done(): break L case <-time.After(5 * time.Millisecond): } } } } func TestFetchAICrypto(t *testing.T) { publicKey := "02358e3e42a6ba15cf6b2ba6eb05f02b8893acf82b316d7dd9cda702b0892b8c71" address := "fetch19dq2mkcpp6x0aypxt9c9gz6n4fqvax0x9a7t5r" peerPublicKey := "027af21aff853b9d9589867ea142b0a60a9611fc8e1fae04c2f7144113fa4e938e" pySigStrCanonize := "N/GOa7/m3HU8/gpLJ88VCQ6vXsdrfiiYcqnNtF+c2N9VG9ZIiycykN4hdbpbOCGrChMYZQA3G1GpozsShrUBgg==" addressFromPublicKey, _ := utils.FetchAIAddressFromPublicKey(publicKey) if address != addressFromPublicKey { t.Error("[ERR] Addresses don't match") } else { t.Log("[OK] Agent address matches its public key") } valid, err := utils.VerifyFetchAISignatureBTC( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using BTC don't match %s", err.Error()) } valid, err = utils.VerifyFetchAISignatureLibp2p( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using LPP don't match %s", err.Error()) } } func TestEthereumCrypto(t *testing.T) { //privateKey := "0xb60fe8027fb82f1a1bd6b8e66d4400f858989a2c67428a4e7f589441700339b0" publicKey := "0xf753e5a9e2368e97f4db869a0d956d3ffb64672d6392670572906c786b5712ada13b6bff882951b3ba3dd65bdacc915c2b532efc3f183aa44657205c6c337225" address := "0xb8d8c62d4a1999b7aea0aebBD5020244a4a9bAD8" publicKeySignature := "0x304c2ba4ae7fa71295bfc2920b9c1268d574d65531f1f4d2117fc1439a45310c37ab75085a9df2a4169a4d47982b330a4387b1ded0c8881b030629db30bbaf3a1c" addFromPublicKey, err := utils.EthereumAddressFromPublicKey(publicKey) if err != nil || addFromPublicKey != address { t.Error( "Error when computing address from public key or address and public key don't match", ) } _, err = utils.BTCPubKeyFromEthereumPublicKey(publicKey) if err != nil { t.Errorf("While building BTC public key from string: %s", err.Error()) } /* ethSig, err := secp256k1.Sign(hashedPublicKey, hexutil.MustDecode(privateKey)) if err != nil { t.Error(err.Error()) } println(hexutil.Encode(ethSig)) hash := sha3.NewLegacyKeccak256() _, err = hash.Write([]byte(publicKey)) if err != nil { t.Error(err.Error()) } sha3KeccakHash := hash.Sum(nil) */ valid, err := utils.VerifyEthereumSignatureETH([]byte(publicKey), publicKeySignature, publicKey) if err != nil { t.Error(err.Error()) } if !valid { t.Errorf("Signer address don't match %s", addFromPublicKey) } } // Perform tests for tls signature generation and checking func TestTLSSignatureValidation(t *testing.T) { key1, pubKey, _ := utils.KeyPairFromFetchAIKey(FetchAITestKeys[0]) key2, _, _ := utils.KeyPairFromFetchAIKey(FetchAITestKeys[1]) peerPubKey, _ := utils.FetchAIPublicKeyFromPubKey(pubKey) cert, err := generate_x509_cert() sessionPubKey := cert.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey) if err != nil { t.Fatal("Failed to generate certificate") } // valid tlsSignature, err := makeSessionKeySignature(cert, key1) if err != nil { t.Fatal("Failed to make signature") } err = ValidateTLSSignature(tlsSignature, sessionPubKey, peerPubKey) if err != nil { t.Fatal("Signature is invalid, but expected to be ok") } //invalid tlsSignature, err = makeSessionKeySignature(cert, key2) if err != nil { t.Fatal("Failed to make signature") } err = ValidateTLSSignature(tlsSignature, sessionPubKey, peerPubKey) if err == nil { t.Fatal("Signature is valid, but shoud not") } } ================================================ FILE: libs/go/libp2p_node/dht/dhtpeer/mailbox.go ================================================ package dhtpeer import ( "context" "crypto/tls" "io/ioutil" acn "libp2p_node/acn" aea "libp2p_node/aea" "net/http" "strings" "sync" "time" "github.com/google/uuid" "google.golang.org/protobuf/proto" ) func (mailboxServer *MailboxServer) apiRegister(res http.ResponseWriter, req *http.Request) { var data []byte var body []byte var err error if req.Method != "POST" { data = []byte("invalid method") res.WriteHeader(400) _, err = res.Write(data) ignore(err) return } body, err = ioutil.ReadAll(req.Body) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) return } //get por if len(body) == 0 { res.WriteHeader(400) _, err = res.Write([]byte("Empty body")) ignore(err) return } record := &acn.AgentRecord{} err = proto.Unmarshal(body, record) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte("Error on agent record deserialize")) ignore(err) return } addr := record.Address // TODO: double register fix!!! //check por status, err := mailboxServer.dhtPeer.CheckPOR(record) if err != nil || status.Code != acn.SUCCESS { res.WriteHeader(400) _, err = res.Write([]byte("Invalid PoR")) ignore(err) return } uuid := strings.ReplaceAll(uuid.NewString(), "-", "") if mailboxServer.dhtPeer.IsAddressAnnouncementEnabled() { err = mailboxServer.dhtPeer.RegisterAgentAddress(addr) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte("failed to register address over dht")) ignore(err) return } } mailboxServer.lock.Lock() mailboxServer.agentRecords[addr] = record mailboxServer.sessions[uuid] = addr mailboxServer.envelopes[addr] = make([]*aea.Envelope, 0) mailboxServer.lock.Unlock() res.WriteHeader(200) _, err = res.Write([]byte(uuid)) ignore(err) } func (mailboxServer *MailboxServer) apiUnregister(res http.ResponseWriter, req *http.Request) { var data []byte var err error var addr string var sessionId string if req.Method != "GET" { data = []byte("invalid method") res.WriteHeader(400) _, err = res.Write(data) ignore(err) return } session_header, exists := req.Header["Session-Id"] if exists { sessionId = session_header[0] mailboxServer.lock.Lock() addr, exists = mailboxServer.sessions[sessionId] mailboxServer.lock.Unlock() } if !exists { res.WriteHeader(400) _, err = res.Write([]byte("invalid session_id header")) ignore(err) return } mailboxServer.lock.Lock() delete(mailboxServer.agentRecords, addr) delete(mailboxServer.sessions, sessionId) delete(mailboxServer.envelopes, addr) mailboxServer.lock.Unlock() } func (mailboxServer *MailboxServer) apiGetSignature(res http.ResponseWriter, req *http.Request) { var err error if req.Method != "GET" { res.WriteHeader(400) _, err = res.Write([]byte("invalid method")) ignore(err) return } res.WriteHeader(200) _, err = res.Write(mailboxServer.signature) ignore(err) } func (mailboxServer *MailboxServer) apiSendEnvelope(res http.ResponseWriter, req *http.Request) { var err error if req.Method != "POST" { res.WriteHeader(400) _, err = res.Write([]byte("invalid method")) ignore(err) return } session_header, exists := req.Header["Session-Id"] if exists { sessionId := session_header[0] mailboxServer.lock.Lock() _, exists = mailboxServer.sessions[sessionId] mailboxServer.lock.Unlock() } if !exists { res.WriteHeader(400) _, err = res.Write([]byte("invalid session_id header")) ignore(err) return } body, err := ioutil.ReadAll(req.Body) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) return } //getenvelope if len(body) == 0 { res.WriteHeader(400) _, err = res.Write([]byte("Empty body")) ignore(err) return } envelope := &aea.Envelope{} err = proto.Unmarshal(body, envelope) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) return } err = mailboxServer.dhtPeer.RouteEnvelope(envelope) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) } } func (mailboxServer *MailboxServer) apiGetEnvelope(res http.ResponseWriter, req *http.Request) { var buf []byte var envelopesList []*aea.Envelope var err error var addr string if req.Method != "GET" { res.WriteHeader(400) _, err = res.Write([]byte("invalid method")) ignore(err) return } session_header, exists := req.Header["Session-Id"] mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() if exists { sessionId := session_header[0] addr, exists = mailboxServer.sessions[sessionId] } if !exists { res.WriteHeader(400) _, err = res.Write([]byte("invalid session_id header")) ignore(err) return } envelopesList = mailboxServer.envelopes[addr] if len(envelopesList) == 0 { res.WriteHeader(200) return } envelope := envelopesList[0] buf, err = proto.Marshal(envelope) if err != nil { //log error return } res.WriteHeader(200) _, err = res.Write(buf) if err == nil { // all ok, remove the first envelope from slice mailboxServer.envelopes[addr] = mailboxServer.envelopes[addr][1:] } } type MailboxServer struct { addr string dhtPeer *DHTPeer httpServer *http.Server sessions map[string]string agentRecords map[string]*acn.AgentRecord envelopes map[string]([]*aea.Envelope) lock sync.RWMutex envelopesLimit int cert *tls.Certificate signature []byte } func (mailboxServer *MailboxServer) start() { var err error lerror, _, _, _ := mailboxServer.dhtPeer.GetLoggers() mailboxServer.envelopes = map[string][]*aea.Envelope{} mailboxServer.agentRecords = map[string]*acn.AgentRecord{} mailboxServer.sessions = map[string]string{} mailboxServer.lock = sync.RWMutex{} mailboxServer.envelopesLimit = 1000 mailboxServer.cert, mailboxServer.signature = mailboxServer.dhtPeer.GetCertAndSignature() mux := http.NewServeMux() mux.HandleFunc("/register", mailboxServer.apiRegister) mux.HandleFunc("/unregister", mailboxServer.apiUnregister) mux.HandleFunc("/get_envelope", mailboxServer.apiGetEnvelope) mux.HandleFunc("/send_envelope", mailboxServer.apiSendEnvelope) mux.HandleFunc("/ssl_signature", mailboxServer.apiGetSignature) tlsConfig := &tls.Config{Certificates: []tls.Certificate{*mailboxServer.cert}} mailboxServer.httpServer = &http.Server{ Addr: mailboxServer.addr, Handler: mux, TLSConfig: tlsConfig, } listener, err := tls.Listen("tcp", mailboxServer.addr, tlsConfig) if err != nil { lerror(err).Msgf("while setting mailbox tls") } err = mailboxServer.httpServer.Serve(listener) if err != nil { lerror(err).Msgf("while running mailbox http server") } } func (mailboxServer *MailboxServer) stop() { var err error lerror, _, _, _ := mailboxServer.dhtPeer.GetLoggers() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err = mailboxServer.httpServer.Shutdown(ctx) if err != nil { lerror(err).Msg("Error on mailbox http server shutdown") } } func (mailboxServer *MailboxServer) RouteEnvelope(envelope *aea.Envelope) bool { target := envelope.To _, _, linfo, _ := mailboxServer.dhtPeer.GetLoggers() linfo().Msgf("route to %s", target) mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() envelopesList, listExist := mailboxServer.envelopes[target] if !listExist { linfo().Msgf("route to %s. no target", target) return false } // check chan is full if mailboxServer.envelopesLimit != 0 && len(mailboxServer.envelopes[target]) >= mailboxServer.envelopesLimit { linfo().Msgf("Envelopes queue for %s is full. (%d envelopes)", target, mailboxServer.envelopesLimit) return false } mailboxServer.envelopes[target] = append(envelopesList, envelope) linfo().Msgf("route to %s. added to queue!", target) return true } func (mailboxServer *MailboxServer) IsAddrRegistered(addr string) bool { mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() _, listExist := mailboxServer.envelopes[addr] return listExist } func (mailboxServer *MailboxServer) GetAgentRecord(addr string) *acn.AgentRecord { if !mailboxServer.IsAddrRegistered(addr) { return nil } mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() return mailboxServer.agentRecords[addr] } ================================================ FILE: libs/go/libp2p_node/dht/dhtpeer/notifee.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2022 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtpeer provides an implementation of an Agent Communication Network node // using libp2p. It participates in data storage and routing for the network. // It offers RelayService for dhtclient and DelegateService for tcp clients. package dhtpeer import ( "github.com/libp2p/go-libp2p-core/network" "github.com/multiformats/go-multiaddr" "github.com/rs/zerolog" ) // Notifee Handle DHTClient network events type Notifee struct { logger zerolog.Logger } // Listen called when network starts listening on an addr func (notifee *Notifee) Listen(network.Network, multiaddr.Multiaddr) {} // ListenClose called when network stops listening on an addr func (notifee *Notifee) ListenClose(network.Network, multiaddr.Multiaddr) {} // Connected called when a connection opened func (notifee *Notifee) Connected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Connected to peer %s", conn.RemotePeer().Pretty(), ) } // Disconnected called when a connection closed // Reconnects if connection is to relay peer and not currenctly closing connection. func (notifee *Notifee) Disconnected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Disconnected from peer %s", conn.RemotePeer().Pretty(), ) } // OpenedStream called when a stream opened func (notifee *Notifee) OpenedStream(network.Network, network.Stream) {} // ClosedStream called when a stream closed func (notifee *Notifee) ClosedStream(network.Network, network.Stream) {} ================================================ FILE: libs/go/libp2p_node/dht/dhtpeer/options.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( "fmt" "time" "github.com/multiformats/go-multiaddr" "github.com/rs/zerolog" acn "libp2p_node/acn" utils "libp2p_node/utils" ) // Option for dhtpeer.New type Option func(*DHTPeer) error // IdentityFromFetchAIKey for dhtpeer.New func IdentityFromFetchAIKey(key string) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.key, dhtPeer.publicKey, err = utils.KeyPairFromFetchAIKey(key) if err != nil { return err } return nil } } // RegisterAgentAddress for dhtpeer.New func RegisterAgentAddress(record *acn.AgentRecord, isReady func() bool) Option { return func(dhtPeer *DHTPeer) error { pbRecord := &acn.AgentRecord{} pbRecord.Address = record.Address pbRecord.PublicKey = record.PublicKey pbRecord.PeerPublicKey = record.PeerPublicKey pbRecord.Signature = record.Signature pbRecord.ServiceId = record.ServiceId pbRecord.LedgerId = record.LedgerId dhtPeer.myAgentAddress = record.Address dhtPeer.myAgentRecord = pbRecord dhtPeer.myAgentReady = isReady return nil } } // BootstrapFrom for dhtpeer.New func BootstrapFrom(entryPeers []string) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.bootstrapPeers, err = utils.GetPeersAddrInfo(entryPeers) if err != nil { return err } return nil } } // LocalURI for dhtpeer.New func LocalURI(host string, port uint16) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.localMultiaddr, err = multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", host, port)) if err != nil { return err } dhtPeer.host = host dhtPeer.port = port return nil } } // PublicURI for dhtpeer.New func PublicURI(host string, port uint16) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.publicMultiaddr, err = multiaddr.NewMultiaddr(fmt.Sprintf("/dns4/%s/tcp/%d", host, port)) if err != nil { return err } dhtPeer.publicHost = host dhtPeer.publicPort = port return nil } } // EnableDelegateService for dhtpeer.New func EnableDelegateService(port uint16) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.delegatePort = port return nil } } // EnableMailboxService for dhtpeer.New func EnableMailboxService(hostport string) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.mailboxHostPort = hostport return nil } } // EnableRelayService for dhtpeer.New func EnableRelayService() Option { return func(dhtPeer *DHTPeer) error { dhtPeer.enableRelay = true return nil } } // LoggingLevel for dhtpeer.New func LoggingLevel(lvl zerolog.Level) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.logger = dhtPeer.logger.Level(lvl) return nil } } // EnablePrometheusMonitoring for dhtpeer.New func EnablePrometheusMonitoring(port uint16) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.monitoringPort = port return nil } } // WithRegistrationDelay for dhtpeer.New func WithRegistrationDelay(delay time.Duration) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.registrationDelay = delay return nil } } // StoreRecordsTo for dhtpeer.New func StoreRecordsTo(path string) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.persistentStoragePath = path return nil } } ================================================ FILE: libs/go/libp2p_node/dht/dhtpeer/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( utils "libp2p_node/utils" "net" ) type TLSListener struct { Listener net.Listener Signature []byte } func (listener TLSListener) Accept() (net.Conn, error) { con, err := listener.Listener.Accept() if err != nil { return con, err } err = utils.WriteBytesConn(con, listener.Signature) return con, err } func (listener TLSListener) Close() error { return listener.Listener.Close() } func (listener TLSListener) Addr() net.Addr { return listener.Listener.Addr() } ================================================ FILE: libs/go/libp2p_node/dht/dhttests/dhttests.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhttests offers utilities to facilitate tests of dhtpeer, dhtclient, and dhtnetwork packages package dhttests import ( "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/dht/dhtnode" "libp2p_node/dht/dhtpeer" "libp2p_node/utils" "log" "math/rand" "os" "path" ) // const ( DHTPeerDefaultLocalHost = "127.0.0.1" DHTPeerDefaultLocalPort = 2000 DHTPeerDefaultDelegatePort = 3000 DHTPeerDefaultFetchAIKey = "34604436e55b0eb99b5e62508433e172dd3ee133cf7a2fecb705e69611147605" DHTPeerDefaultFetchAIPublicKey = "039e883de988eededb9afaa4d3a6baec9ba74dd1cc237028e810569780b319940a" DHTPeerDefaultAgentKey = "719133dc740d76ff6d1d325e193f7cd63af4c8f3491bfe3010e58b0b58c77795" DHTPeerDefaultAgentPublicKey = "039623e63ba1617404b2abbe7bd94d24eb788335f870fac1ae4519e9bc359b7833" DHTPeerDefaultAgentAddress = "fetch134rg4n3wgmwctxsrm7gp6l65uwv6hxtxyfdwgw" ) func randSeq(n int) string { var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } // NewDHTPeerWithDefaults for testing func NewDHTPeerWithDefaults(inbox chan<- *aea.Envelope) (*dhtpeer.DHTPeer, func(), error) { opts := []dhtpeer.Option{ dhtpeer.LocalURI(DHTPeerDefaultLocalHost, DHTPeerDefaultLocalPort), dhtpeer.PublicURI(DHTPeerDefaultLocalHost, DHTPeerDefaultLocalPort), dhtpeer.IdentityFromFetchAIKey(DHTPeerDefaultFetchAIKey), dhtpeer.EnableRelayService(), dhtpeer.EnableDelegateService(DHTPeerDefaultDelegatePort), dhtpeer.StoreRecordsTo(path.Join(os.TempDir(), "agents_records_"+randSeq(5))), } signature, err := utils.SignFetchAI( []byte(DHTPeerDefaultFetchAIPublicKey), DHTPeerDefaultAgentKey, ) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: dhtnode.DefaultLedger} record.Address = DHTPeerDefaultAgentAddress record.PublicKey = DHTPeerDefaultAgentPublicKey record.PeerPublicKey = DHTPeerDefaultFetchAIPublicKey record.Signature = signature opts = append(opts, dhtpeer.RegisterAgentAddress(record, func() bool { return true })) dhtPeer, err := dhtpeer.New(opts...) if err != nil { return nil, nil, err } cleanup := func() { errs := dhtPeer.Close() if len(errs) > 0 { log.Println("ERROR while stopping DHTPeer:", errs) } } dhtPeer.ProcessEnvelope(func(envel *aea.Envelope) error { inbox <- envel return nil }) return dhtPeer, cleanup, nil } ================================================ FILE: libs/go/libp2p_node/dht/monitoring/file.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package monitoring import ( "fmt" "os" "sync" "time" ) type FileGauge struct { value float64 lock sync.RWMutex } func (fg *FileGauge) Set(value float64) { fg.lock.Lock() fg.value = value fg.lock.Unlock() } func (fg *FileGauge) Get() float64 { return fg.value } func (fg *FileGauge) Inc() { fg.Add(1.) } func (fg *FileGauge) Dec() { fg.Sub(1) } func (fg *FileGauge) Add(count float64) { fg.lock.Lock() fg.value += count fg.lock.Unlock() } func (fg *FileGauge) Sub(count float64) { fg.lock.Lock() fg.value -= count fg.lock.Unlock() } type FileCounter struct { value float64 lock sync.RWMutex } func (fc *FileCounter) Inc() { fc.Add(1.) } func (fc *FileCounter) Add(count float64) { fc.lock.Lock() fc.value += count fc.lock.Unlock() } func (fc *FileCounter) Get() float64 { return fc.value } type FileHistogram struct { buckets []float64 counts []uint64 lock sync.RWMutex } func (fh *FileHistogram) Observe(value float64) { fh.lock.Lock() var i int = 0 for i < len(fh.buckets) { if value <= fh.buckets[i] { fh.counts[i] += 1 } i++ } fh.counts[i] += 1 fh.lock.Unlock() } type FileMonitoring struct { Namespace string gaugeDict map[string]*FileGauge counterDict map[string]*FileCounter histoDict map[string]*FileHistogram timer *Timer path string write bool closing chan struct{} } func NewFileMonitoring(namespace string, write bool) *FileMonitoring { fm := &FileMonitoring{ Namespace: namespace, } fm.counterDict = map[string]*FileCounter{} fm.gaugeDict = map[string]*FileGauge{} fm.histoDict = map[string]*FileHistogram{} fm.timer = &Timer{ list: map[string]time.Time{}, lock: sync.RWMutex{}, } cwd, _ := os.Getwd() fm.path = cwd + "/" + fm.Namespace + ".stats" fm.write = write return fm } func (fm *FileMonitoring) NewCounter(name string, description string) (Counter, error) { counter := &FileCounter{} fm.counterDict[name] = counter return counter, nil } func (fm *FileMonitoring) GetCounter(name string) (Counter, bool) { counter, ok := fm.counterDict[name] return counter, ok } func (fm *FileMonitoring) NewGauge(name string, description string) (Gauge, error) { gauge := &FileGauge{} fm.gaugeDict[name] = gauge return gauge, nil } func (fm *FileMonitoring) GetGauge(name string) (Gauge, bool) { gauge, ok := fm.gaugeDict[name] return gauge, ok } func (fm *FileMonitoring) NewHistogram( name string, description string, buckets []float64, ) (Histogram, error) { histogram := &FileHistogram{ buckets: buckets, counts: make([]uint64, len(buckets)+1), } fm.histoDict[name] = histogram return histogram, nil } func (fm *FileMonitoring) GetHistogram(name string) (Histogram, bool) { histo, ok := fm.histoDict[name] return histo, ok } func (fm *FileMonitoring) Start() { if fm.closing != nil || !fm.write { return } fm.closing = make(chan struct{}) file, _ := os.OpenFile(fm.path, os.O_WRONLY|os.O_CREATE, 0666) L: for { select { case <-fm.closing: file.Close() break L default: ignore(file.Truncate(0)) _, err := file.Seek(0, 0) ignore(err) _, err = file.WriteString(fm.getStats()) ignore(err) time.Sleep(5 * time.Second) } } } func (fm *FileMonitoring) Stop() { close(fm.closing) } func (fm FileMonitoring) getStats() string { var stats string for name, value := range fm.gaugeDict { strValue := fmt.Sprintf("%e", value.Get()) stats += fm.Namespace + "_" + name + " " + strValue + "\n" } for name, value := range fm.counterDict { strValue := fmt.Sprintf("%e", value.Get()) stats += fm.Namespace + "_" + name + " " + strValue + "\n" } // TODO: report histograms return stats } func (fm *FileMonitoring) Info() string { return "FileMonitoring on " + fm.path } func (fm *FileMonitoring) Timer() *Timer { return fm.timer } ================================================ FILE: libs/go/libp2p_node/dht/monitoring/prometheus.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package monitoring import ( "net/http" "strconv" "sync" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" ) type PrometheusMonitoring struct { Namespace string Port uint16 running bool httpServer http.Server counterDict map[string]prometheus.Counter gaugeDict map[string]prometheus.Gauge histoDict map[string]prometheus.Histogram timer *Timer } func NewPrometheusMonitoring(namespace string, port uint16) *PrometheusMonitoring { pmts := &PrometheusMonitoring{ Namespace: namespace, Port: port, } pmts.counterDict = map[string]prometheus.Counter{} pmts.gaugeDict = map[string]prometheus.Gauge{} pmts.histoDict = map[string]prometheus.Histogram{} pmts.timer = &Timer{ list: map[string]time.Time{}, lock: sync.RWMutex{}, } return pmts } func (pmts *PrometheusMonitoring) NewCounter(name string, description string) (Counter, error) { counter := promauto.NewCounter(prometheus.CounterOpts{ Namespace: pmts.Namespace, Name: name, Help: description, }) pmts.counterDict[name] = counter return counter, nil } func (pmts *PrometheusMonitoring) GetCounter(name string) (Counter, bool) { counter, ok := pmts.counterDict[name] return counter, ok } func (pmts *PrometheusMonitoring) NewGauge(name string, description string) (Gauge, error) { gauge := promauto.NewGauge(prometheus.GaugeOpts{ Namespace: pmts.Namespace, Name: name, Help: description, }) pmts.gaugeDict[name] = gauge return gauge, nil } func (pmts *PrometheusMonitoring) GetGauge(name string) (Gauge, bool) { gauge, ok := pmts.gaugeDict[name] return gauge, ok } func (pmts *PrometheusMonitoring) NewHistogram( name string, description string, buckets []float64, ) (Histogram, error) { histogram := promauto.NewHistogram(prometheus.HistogramOpts{ Namespace: pmts.Namespace, Name: name, Help: description, Buckets: buckets, }) pmts.histoDict[name] = histogram return histogram, nil } func (pmts *PrometheusMonitoring) GetHistogram(name string) (Histogram, bool) { histogram, ok := pmts.histoDict[name] return histogram, ok } func (pmts *PrometheusMonitoring) Start() { if pmts.running { return } pmts.httpServer = http.Server{Addr: ":" + strconv.FormatInt(int64(pmts.Port), 10)} http.Handle("/metrics", promhttp.Handler()) pmts.running = true ignore(pmts.httpServer.ListenAndServe()) } func (pmts *PrometheusMonitoring) Stop() { pmts.httpServer.Close() } func (pmts *PrometheusMonitoring) Info() string { return "Prometheus at " + strconv.FormatInt(int64(pmts.Port), 10) } func (pmts *PrometheusMonitoring) Timer() *Timer { return pmts.timer } ================================================ FILE: libs/go/libp2p_node/dht/monitoring/service.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package monitoring import ( "errors" "log" "sync" "time" ) type Gauge interface { Set(value float64) Inc() Dec() Add(count float64) Sub(count float64) } type Counter interface { Inc() Add(count float64) } type Histogram interface { Observe(value float64) } type Summary interface { Observe(value float64) } type Timer struct { list map[string]time.Time lock sync.RWMutex } func (tm *Timer) NewTimer() time.Time { return time.Now() } func (tm *Timer) GetTimer(timer time.Time) time.Duration { end := time.Now() return end.Sub(timer) } func (tm *Timer) NewTimerNamed(name string) string { tm.lock.Lock() defer tm.lock.Unlock() tm.list[name] = time.Now() return name } func (tm *Timer) GetTimerNamed(timer string) (time.Duration, error) { end := time.Now() tm.lock.RLock() start, ok := tm.list[timer] tm.lock.RUnlock() if !ok { return time.Duration(0), errors.New("Unknown timer " + timer) } tm.lock.Lock() delete(tm.list, timer) tm.lock.Unlock() return end.Sub(start), nil } type MonitoringService interface { NewCounter(name string, description string) (Counter, error) GetCounter(name string) (Counter, bool) NewGauge(name string, description string) (Gauge, error) GetGauge(name string) (Gauge, bool) NewHistogram(name string, description string, buckets []float64) (Histogram, error) GetHistogram(name string) (Histogram, bool) //NewSummary(name string, description string, objectives map[float64]float64) (Summary, error) //GetSummary(name string) (Summary, bool) Start() Stop() Info() string Timer() *Timer } func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } ================================================ FILE: libs/go/libp2p_node/go.mod ================================================ module libp2p_node go 1.13 require ( bou.ke/monkey v1.0.2 github.com/btcsuite/btcd v0.20.1-beta github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d github.com/ethereum/go-ethereum v1.10.17 github.com/golang/mock v1.5.0 github.com/golang/protobuf v1.4.3 github.com/google/uuid v1.3.0 github.com/ipfs/go-cid v0.0.5 github.com/joho/godotenv v1.3.0 github.com/libp2p/go-libp2p v0.8.3 github.com/libp2p/go-libp2p-circuit v0.2.2 github.com/libp2p/go-libp2p-core v0.5.3 github.com/libp2p/go-libp2p-kad-dht v0.7.11 github.com/libp2p/go-libp2p-kbucket v0.4.1 github.com/multiformats/go-multiaddr v0.2.1 github.com/multiformats/go-multihash v0.0.13 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 github.com/rs/zerolog v1.21.0 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 google.golang.org/protobuf v1.25.0 honnef.co/go/tools v0.1.4 // indirect ) ================================================ FILE: libs/go/libp2p_node/go.sum ================================================ bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0 h1:MSskdM4/xJYcFzy0altH/C/xHopifpWzHUi1JeVI34Q= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204 h1:+EYBkW+dbi3F/atB+LSQZSWh7+HNrV3A/N0y6DSoy9k= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/ipfs/go-cid v0.0.5 h1:o0Ix8e/ql7Zb5UVUJEUfjsWCIY8t48++9lR8qi6oiJU= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4 h1:rjvQ9+muFaJ+QZ7dN5B1MSDNQ0JVZKkkES/rMZmA8X8= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-ipns v0.0.2 h1:oq4ErrV4hNQ2Eim257RTYRgfOSV/s8BDaf9iIl4NwFs= github.com/ipfs/go-ipns v0.0.2/go.mod h1:WChil4e0/m9cIINWLxZe1Jtf77oz5L05rO2ei/uKJ5U= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= github.com/ipfs/go-log v1.0.4 h1:6nLQdX4W8P9yZZFH7mO+X/PzjN8Laozm/lMJ6esdgzY= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5 h1:fL4YI+1g5V/b1Yxr1qAiXTMg1H8z9vx/VmJxBuQMHvU= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2 h1:vhC1OXXiT9R2pczegwz6moDvuRpggaroAXhPIseh57A= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-addr-util v0.0.1 h1:TpTQm9cXVRVSKsYbgQ7GKc3KbbHVTnbostgGaDEP+88= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0 h1:uNiDjS58vrvJTg9jO6bySd1rMKejieG7v45ekqHbZ1M= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-eventbus v0.1.0 h1:mlawomSAjjkk97QnYiEmHsLu7E136+2oCWSHRUvMfzQ= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.2/go.mod h1:NQDA/F/qArMHGe0J7sDScaKjW8Jh4y/ozQqBbYJ+BnA= github.com/libp2p/go-libp2p v0.8.3 h1:IFWeNzxkBaNO1N8stN9ayFGdC6RmVuSsKd5bou7qpK0= github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El8cTaefiM= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= github.com/libp2p/go-libp2p-autonat v0.2.2 h1:4dlgcEEugTFWSvdG2UIFxhnOMpX76QaZSRAtXmYB8n4= github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUADzkJacgZLvk= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.2.2 h1:87RLabJ9lrhoiSDDZyCJ80ZlI5TLJMwfyoGAaWXzWqA= github.com/libp2p/go-libp2p-circuit v0.2.2/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA= github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.2/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.3 h1:b9W3w7AZR2n/YJhG8d0qPFGhGhCWKIvPuJgp4hhc4MM= github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.4.0 h1:dK78UhopBk48mlHtRCzbdLm3q/81g77FahEBTjcqQT8= github.com/libp2p/go-libp2p-discovery v0.4.0/go.mod h1:bZ0aJSrFc/eX2llP0ryhb1kpgkPyTo23SJ5b7UQCMh4= github.com/libp2p/go-libp2p-kad-dht v0.7.11 h1:MP0DEuxO/Blg1AklIVV1P4R5xtYX+ZyXBCtEN7f60yQ= github.com/libp2p/go-libp2p-kad-dht v0.7.11/go.mod h1:1ht6+bG3Or+fNNERWPYmLacs6TN0CxBkFB5IKIWWwOI= github.com/libp2p/go-libp2p-kbucket v0.4.1 h1:6FyzbQuGLPzbMv3HiD232zqscIz5iB8ppJwb380+OGI= github.com/libp2p/go-libp2p-kbucket v0.4.1/go.mod h1:7sCeZx2GkNK1S6lQnGUW5JYZCFPnXzAZCCBBS70lytY= github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= github.com/libp2p/go-libp2p-mplex v0.2.3 h1:2zijwaJvpdesST2MXpI5w9wWFRgYtMcpRX7rrw0jmOo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6 h1:wMWis3kYynCbHoyKLPBEMu4YRLltbm8Mk08HGSfvTkU= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.3 h1:MofRq2l3c15vQpEygTetV+zRRrncz+ktiXW7H2EKoEQ= github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-record v0.1.2 h1:M50VKzWnmUrk/M5/Dz99qO9Xh4vs8ijsK+7HkJvRP+0= github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= github.com/libp2p/go-libp2p-secio v0.2.2 h1:rLLPvShPQAcY6eNurKNZq3eZjPWfU9kXF2eI9jIYdrg= github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-swarm v0.2.3 h1:uVkCb8Blfg7HQ/f30TyHn1g/uCwXsAET7pU0U59gx/A= github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.1 h1:U03z3HnGI7Ni8Xx6ONVZvUFOAzWYmolWf5W5jAOPNmU= github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-tls v0.1.3 h1:twKMhMu44jQO+HgQK9X8NHO5HkeJu2QbhLzLJpa8oNM= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0 h1:5EhPgQhXZNyfL22ERZTUoVp9UVVbNowWNVtELQaKCHk= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= github.com/libp2p/go-libp2p-yamux v0.2.7 h1:vzKu0NVtxvEIDGCv6mjKRcK0gipSgaXmJZ6jFv0d/dk= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.1.2 h1:qOg1s+WdGLlpkrczDqmhYzyk3vCfsQ8+RxRTQjOZWwI= github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4 h1:agEFehY3zWJFUHK6SEMR7UYmk2z6kC3oeCM7ybLhguA= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5 h1:qxnwkco8RLKqVh1NmjQ+tJ8p8khNLFxuElYG/TwqW4Q= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-netroute v0.1.2 h1:UHhB35chwgvcRI392znJA3RCBtZ3MpE3ahNCN5MR4Xg= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3 h1:zzOeXnTooCkRvoH+bSXEfXhn76+LAiwoneM0gnXjF2M= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-sockaddr v0.0.2 h1:tCuXfpA9rq7llM/v834RKc/Xvovy/AqM9kHvTV/jY/Q= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0 h1:TqnSHPJEIqDEO7h1wZZ0p3DXdvDSiLHQidKKUGZtiOY= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0 h1:YoThc549fzmNJIh7XjHVtMIFaEDRtIrtWciG5LyYAPo= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.3.1 h1:ZX5rWB8nhRRJVaPO6tmkGI/Xx8XNboYX20PW5hXIscw= github.com/libp2p/go-ws-transport v0.3.1/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.5 h1:ibuz4naPAully0pN6J/kmUARiqLpnDQIzI/8GCOrljg= github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.2.1 h1:SgG/cw5vqyB5QQe5FPe2TqggU9WtrA9X4nZw7LlVqOI= github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0 h1:YWJoIDwLePniH7OU5hBnDZV6SWuvJqJ0YtN6pLeH9zA= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.4 h1:g6gwydsfADqFvrHoMkS0n9Ok9CG6F7ytOH/bJDkhIOY= github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1 h1:JlAdpIFhBhGRLxe9W6Om0w++Gd6KMWoFPZL/dEnm9nI= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= ================================================ FILE: libs/go/libp2p_node/libp2p_node.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package main import ( "fmt" "os" "os/signal" "syscall" "time" "github.com/rs/zerolog" aea "libp2p_node/aea" "libp2p_node/dht/dhtclient" "libp2p_node/dht/dhtnode" "libp2p_node/dht/dhtpeer" "libp2p_node/utils" ) const ( libp2pNodePanicError = "LIBP2P_NODE_PANIC_ERROR" libp2pMultiaddrsListStart = "MULTIADDRS_LIST_START" libp2pMultiaddrsListEnd = "MULTIADDRS_LIST_END" ) var logger zerolog.Logger = utils.NewDefaultLogger() // panics if err is not nil func check(err error) { if err != nil { fmt.Println(libp2pNodePanicError, ":", err.Error()) panic(err) } } func main() { var err error // Initialize connection to aea agent := aea.AeaApi{} check(agent.Init()) logger.Info().Msg("successfully initialized API to AEA!") // Get node configuration // aea agent address aeaAddr := agent.AeaAddress() // node address (ip and port) nodeHost, nodePort := agent.Address() // node public address, if set nodeHostPublic, nodePortPublic := agent.PublicAddress() // node delegate service address, if set _, nodePortDelegate := agent.DelegateAddress() // node monitoring service address, if set _, nodePortMonitoring := agent.MonitoringAddress() // node private key key := agent.PrivateKey() // entry peers entryPeers := agent.EntryPeers() // agent proof of representation record := agent.AgentRecord() // add artificial delay for agent registration registrationDelay := agent.RegistrationDelayInSeconds() // persist agent records to file storagePath := agent.RecordStoragePath() // libp2p node var node dhtnode.DHTNode // Run as a peer or just as a client if nodePortPublic == 0 { // if no external address is provided, run as a client opts := []dhtclient.Option{ dhtclient.IdentityFromFetchAIKey(key), dhtclient.BootstrapFrom(entryPeers), } if record != nil { opts = append(opts, dhtclient.RegisterAgentAddress(record, agent.Connected)) } node, err = dhtclient.New(opts...) } else { opts := []dhtpeer.Option{ dhtpeer.LocalURI(nodeHost, nodePort), dhtpeer.PublicURI(nodeHostPublic, nodePortPublic), dhtpeer.IdentityFromFetchAIKey(key), dhtpeer.EnableRelayService(), dhtpeer.EnableDelegateService(nodePortDelegate), dhtpeer.BootstrapFrom(entryPeers), } if record != nil { opts = append(opts, dhtpeer.RegisterAgentAddress(record, agent.Connected)) } if nodePortMonitoring != 0 { opts = append(opts, dhtpeer.EnablePrometheusMonitoring(nodePortMonitoring)) } if registrationDelay != 0 { //lint:ignore ST1011 don't use unit-specific suffix "Seconds" durationSeconds := time.Duration(registrationDelay) opts = append(opts, dhtpeer.WithRegistrationDelay(durationSeconds*1000000*time.Microsecond)) } if storagePath != "" { opts = append(opts, dhtpeer.StoreRecordsTo(storagePath)) } if len(agent.MailboxUri()) > 0 { opts = append(opts, dhtpeer.EnableMailboxService(agent.MailboxUri())) } node, err = dhtpeer.New(opts...) } if err != nil { check(err) } defer node.Close() logger.Info().Msgf("Peer ID: %s", node.PeerID()) // Connect to the agent fmt.Println(libp2pMultiaddrsListStart) // keyword fmt.Println(node.MultiAddr()) fmt.Println(libp2pMultiaddrsListEnd) // keyword check(agent.Connect()) if aeaAddr != "" { logger.Info().Msg("successfully connected to AEA!") } // Receive envelopes from agent and forward to peer go func() { for envel := range agent.Queue() { envelope := envel logger.Info().Msgf("received envelope from agent: %s", envelope) err := node.RouteEnvelope(envelope) if err != nil { logger.Error().Msgf("Route envelope error: %s", err.Error()) } } }() // Deliver envelopes received fro DHT to agent node.ProcessEnvelope(func(envel *aea.Envelope) error { return agent.Put(envel) }) // Wait until Ctrl+C or a termination call is done. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) // SIGTERM for k8s graceful stop support signal.Notify(c, syscall.SIGTERM) //wait for termination <-c logger.Info().Msg("node stopped") } ================================================ FILE: libs/go/libp2p_node/link ================================================ #!/usr/bin/python2 import argparse import os import subprocess import sys # renamed 'link' file original_link_file = 'link_orig' # link first output=None code=0 try: output = subprocess.check_output([os.path.dirname(__file__) + '/' + original_link_file] + sys.argv[1:], cwd=os.getcwd()) except subprocess.CalledProcessError as e: output = e.output code = e.returncode if output: print output.replace(original_link_file, 'link') # change max_prot value to 0x7 parser = argparse.ArgumentParser() parser.add_argument('-o') args, _ = parser.parse_known_args() if args.o: binary_target = args.o # only for testing if binary_target.endswith('.test') or binary_target.endswith('_test_go'): with open(os.devnull, 'wb') as DEVNULL: try: subprocess.check_call(["printf '\x07' | dd of=%s bs=1 seek=160 count=1 conv=notrunc" % binary_target], shell=True, stdout=DEVNULL, stderr=DEVNULL) except subprocess.CalledProcessError as e: pass sys.exit(code) ================================================ FILE: libs/go/libp2p_node/mocks/mock_host.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/libp2p/go-libp2p-core/host (interfaces: Host) // Package mock_host is a generated GoMock package. package mocks import ( context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" connmgr "github.com/libp2p/go-libp2p-core/connmgr" event "github.com/libp2p/go-libp2p-core/event" network "github.com/libp2p/go-libp2p-core/network" peer "github.com/libp2p/go-libp2p-core/peer" peerstore "github.com/libp2p/go-libp2p-core/peerstore" protocol "github.com/libp2p/go-libp2p-core/protocol" multiaddr "github.com/multiformats/go-multiaddr" ) // MockHost is a mock of Host interface. type MockHost struct { ctrl *gomock.Controller recorder *MockHostMockRecorder } // MockHostMockRecorder is the mock recorder for MockHost. type MockHostMockRecorder struct { mock *MockHost } // NewMockHost creates a new mock instance. func NewMockHost(ctrl *gomock.Controller) *MockHost { mock := &MockHost{ctrl: ctrl} mock.recorder = &MockHostMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockHost) EXPECT() *MockHostMockRecorder { return m.recorder } // Addrs mocks base method. func (m *MockHost) Addrs() []multiaddr.Multiaddr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Addrs") ret0, _ := ret[0].([]multiaddr.Multiaddr) return ret0 } // Addrs indicates an expected call of Addrs. func (mr *MockHostMockRecorder) Addrs() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addrs", reflect.TypeOf((*MockHost)(nil).Addrs)) } // Close mocks base method. func (m *MockHost) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockHostMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockHost)(nil).Close)) } // ConnManager mocks base method. func (m *MockHost) ConnManager() connmgr.ConnManager { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ConnManager") ret0, _ := ret[0].(connmgr.ConnManager) return ret0 } // ConnManager indicates an expected call of ConnManager. func (mr *MockHostMockRecorder) ConnManager() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnManager", reflect.TypeOf((*MockHost)(nil).ConnManager)) } // Connect mocks base method. func (m *MockHost) Connect(arg0 context.Context, arg1 peer.AddrInfo) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Connect", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // Connect indicates an expected call of Connect. func (mr *MockHostMockRecorder) Connect(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockHost)(nil).Connect), arg0, arg1) } // EventBus mocks base method. func (m *MockHost) EventBus() event.Bus { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "EventBus") ret0, _ := ret[0].(event.Bus) return ret0 } // EventBus indicates an expected call of EventBus. func (mr *MockHostMockRecorder) EventBus() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventBus", reflect.TypeOf((*MockHost)(nil).EventBus)) } // ID mocks base method. func (m *MockHost) ID() peer.ID { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ID") ret0, _ := ret[0].(peer.ID) return ret0 } // ID indicates an expected call of ID. func (mr *MockHostMockRecorder) ID() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockHost)(nil).ID)) } // Mux mocks base method. func (m *MockHost) Mux() protocol.Switch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Mux") ret0, _ := ret[0].(protocol.Switch) return ret0 } // Mux indicates an expected call of Mux. func (mr *MockHostMockRecorder) Mux() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Mux", reflect.TypeOf((*MockHost)(nil).Mux)) } // Network mocks base method. func (m *MockHost) Network() network.Network { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") ret0, _ := ret[0].(network.Network) return ret0 } // Network indicates an expected call of Network. func (mr *MockHostMockRecorder) Network() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*MockHost)(nil).Network)) } // NewStream mocks base method. func (m *MockHost) NewStream(arg0 context.Context, arg1 peer.ID, arg2 ...protocol.ID) (network.Stream, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "NewStream", varargs...) ret0, _ := ret[0].(network.Stream) ret1, _ := ret[1].(error) return ret0, ret1 } // NewStream indicates an expected call of NewStream. func (mr *MockHostMockRecorder) NewStream(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewStream", reflect.TypeOf((*MockHost)(nil).NewStream), varargs...) } // Peerstore mocks base method. func (m *MockHost) Peerstore() peerstore.Peerstore { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Peerstore") ret0, _ := ret[0].(peerstore.Peerstore) return ret0 } // Peerstore indicates an expected call of Peerstore. func (mr *MockHostMockRecorder) Peerstore() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peerstore", reflect.TypeOf((*MockHost)(nil).Peerstore)) } // RemoveStreamHandler mocks base method. func (m *MockHost) RemoveStreamHandler(arg0 protocol.ID) { m.ctrl.T.Helper() m.ctrl.Call(m, "RemoveStreamHandler", arg0) } // RemoveStreamHandler indicates an expected call of RemoveStreamHandler. func (mr *MockHostMockRecorder) RemoveStreamHandler(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveStreamHandler", reflect.TypeOf((*MockHost)(nil).RemoveStreamHandler), arg0) } // SetStreamHandler mocks base method. func (m *MockHost) SetStreamHandler(arg0 protocol.ID, arg1 network.StreamHandler) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetStreamHandler", arg0, arg1) } // SetStreamHandler indicates an expected call of SetStreamHandler. func (mr *MockHostMockRecorder) SetStreamHandler(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStreamHandler", reflect.TypeOf((*MockHost)(nil).SetStreamHandler), arg0, arg1) } // SetStreamHandlerMatch mocks base method. func (m *MockHost) SetStreamHandlerMatch(arg0 protocol.ID, arg1 func(string) bool, arg2 network.StreamHandler) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetStreamHandlerMatch", arg0, arg1, arg2) } // SetStreamHandlerMatch indicates an expected call of SetStreamHandlerMatch. func (mr *MockHostMockRecorder) SetStreamHandlerMatch(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStreamHandlerMatch", reflect.TypeOf((*MockHost)(nil).SetStreamHandlerMatch), arg0, arg1, arg2) } ================================================ FILE: libs/go/libp2p_node/mocks/mock_net.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: net (interfaces: Conn) // Package mock_net is a generated GoMock package. package mocks import ( net "net" reflect "reflect" time "time" gomock "github.com/golang/mock/gomock" ) // MockConn is a mock of Conn interface. type MockConn struct { ctrl *gomock.Controller recorder *MockConnMockRecorder } // MockConnMockRecorder is the mock recorder for MockConn. type MockConnMockRecorder struct { mock *MockConn } // NewMockConn creates a new mock instance. func NewMockConn(ctrl *gomock.Controller) *MockConn { mock := &MockConn{ctrl: ctrl} mock.recorder = &MockConnMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockConn) EXPECT() *MockConnMockRecorder { return m.recorder } // Close mocks base method. func (m *MockConn) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockConnMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConn)(nil).Close)) } // LocalAddr mocks base method. func (m *MockConn) LocalAddr() net.Addr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LocalAddr") ret0, _ := ret[0].(net.Addr) return ret0 } // LocalAddr indicates an expected call of LocalAddr. func (mr *MockConnMockRecorder) LocalAddr() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockConn)(nil).LocalAddr)) } // Read mocks base method. func (m *MockConn) Read(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Read", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Read indicates an expected call of Read. func (mr *MockConnMockRecorder) Read(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockConn)(nil).Read), arg0) } // RemoteAddr mocks base method. func (m *MockConn) RemoteAddr() net.Addr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RemoteAddr") ret0, _ := ret[0].(net.Addr) return ret0 } // RemoteAddr indicates an expected call of RemoteAddr. func (mr *MockConnMockRecorder) RemoteAddr() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockConn)(nil).RemoteAddr)) } // SetDeadline mocks base method. func (m *MockConn) SetDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetDeadline indicates an expected call of SetDeadline. func (mr *MockConnMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockConn)(nil).SetDeadline), arg0) } // SetReadDeadline mocks base method. func (m *MockConn) SetReadDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetReadDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetReadDeadline indicates an expected call of SetReadDeadline. func (mr *MockConnMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockConn)(nil).SetReadDeadline), arg0) } // SetWriteDeadline mocks base method. func (m *MockConn) SetWriteDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetWriteDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetWriteDeadline indicates an expected call of SetWriteDeadline. func (mr *MockConnMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockConn)(nil).SetWriteDeadline), arg0) } // Write mocks base method. func (m *MockConn) Write(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Write", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Write indicates an expected call of Write. func (mr *MockConnMockRecorder) Write(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockConn)(nil).Write), arg0) } ================================================ FILE: libs/go/libp2p_node/mocks/mock_network.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/libp2p/go-libp2p-core/network (interfaces: Stream) // Package mock_network is a generated GoMock package. package mocks import ( reflect "reflect" time "time" gomock "github.com/golang/mock/gomock" network "github.com/libp2p/go-libp2p-core/network" protocol "github.com/libp2p/go-libp2p-core/protocol" ) // MockStream is a mock of Stream interface. type MockStream struct { ctrl *gomock.Controller recorder *MockStreamMockRecorder } // MockStreamMockRecorder is the mock recorder for MockStream. type MockStreamMockRecorder struct { mock *MockStream } // NewMockStream creates a new mock instance. func NewMockStream(ctrl *gomock.Controller) *MockStream { mock := &MockStream{ctrl: ctrl} mock.recorder = &MockStreamMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockStream) EXPECT() *MockStreamMockRecorder { return m.recorder } // Close mocks base method. func (m *MockStream) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockStreamMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStream)(nil).Close)) } // Conn mocks base method. func (m *MockStream) Conn() network.Conn { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Conn") ret0, _ := ret[0].(network.Conn) return ret0 } // Conn indicates an expected call of Conn. func (mr *MockStreamMockRecorder) Conn() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Conn", reflect.TypeOf((*MockStream)(nil).Conn)) } // Protocol mocks base method. func (m *MockStream) Protocol() protocol.ID { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Protocol") ret0, _ := ret[0].(protocol.ID) return ret0 } // Protocol indicates an expected call of Protocol. func (mr *MockStreamMockRecorder) Protocol() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protocol", reflect.TypeOf((*MockStream)(nil).Protocol)) } // Read mocks base method. func (m *MockStream) Read(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Read", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Read indicates an expected call of Read. func (mr *MockStreamMockRecorder) Read(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockStream)(nil).Read), arg0) } // Reset mocks base method. func (m *MockStream) Reset() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Reset") ret0, _ := ret[0].(error) return ret0 } // Reset indicates an expected call of Reset. func (mr *MockStreamMockRecorder) Reset() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockStream)(nil).Reset)) } // SetDeadline mocks base method. func (m *MockStream) SetDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetDeadline indicates an expected call of SetDeadline. func (mr *MockStreamMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockStream)(nil).SetDeadline), arg0) } // SetProtocol mocks base method. func (m *MockStream) SetProtocol(arg0 protocol.ID) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetProtocol", arg0) } // SetProtocol indicates an expected call of SetProtocol. func (mr *MockStreamMockRecorder) SetProtocol(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProtocol", reflect.TypeOf((*MockStream)(nil).SetProtocol), arg0) } // SetReadDeadline mocks base method. func (m *MockStream) SetReadDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetReadDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetReadDeadline indicates an expected call of SetReadDeadline. func (mr *MockStreamMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockStream)(nil).SetReadDeadline), arg0) } // SetWriteDeadline mocks base method. func (m *MockStream) SetWriteDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetWriteDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetWriteDeadline indicates an expected call of SetWriteDeadline. func (mr *MockStreamMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockStream)(nil).SetWriteDeadline), arg0) } // Stat mocks base method. func (m *MockStream) Stat() network.Stat { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Stat") ret0, _ := ret[0].(network.Stat) return ret0 } // Stat indicates an expected call of Stat. func (mr *MockStreamMockRecorder) Stat() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockStream)(nil).Stat)) } // Write mocks base method. func (m *MockStream) Write(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Write", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Write indicates an expected call of Write. func (mr *MockStreamMockRecorder) Write(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStream)(nil).Write), arg0) } ================================================ FILE: libs/go/libp2p_node/mocks/mock_peerstore.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/libp2p/go-libp2p-core/peerstore (interfaces: Peerstore) // Package mock_peerstore is a generated GoMock package. package mocks import ( context "context" reflect "reflect" time "time" gomock "github.com/golang/mock/gomock" crypto "github.com/libp2p/go-libp2p-core/crypto" peer "github.com/libp2p/go-libp2p-core/peer" multiaddr "github.com/multiformats/go-multiaddr" ) // MockPeerstore is a mock of Peerstore interface. type MockPeerstore struct { ctrl *gomock.Controller recorder *MockPeerstoreMockRecorder } // MockPeerstoreMockRecorder is the mock recorder for MockPeerstore. type MockPeerstoreMockRecorder struct { mock *MockPeerstore } // NewMockPeerstore creates a new mock instance. func NewMockPeerstore(ctrl *gomock.Controller) *MockPeerstore { mock := &MockPeerstore{ctrl: ctrl} mock.recorder = &MockPeerstoreMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockPeerstore) EXPECT() *MockPeerstoreMockRecorder { return m.recorder } // AddAddr mocks base method. func (m *MockPeerstore) AddAddr(arg0 peer.ID, arg1 multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "AddAddr", arg0, arg1, arg2) } // AddAddr indicates an expected call of AddAddr. func (mr *MockPeerstoreMockRecorder) AddAddr(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAddr", reflect.TypeOf((*MockPeerstore)(nil).AddAddr), arg0, arg1, arg2) } // AddAddrs mocks base method. func (m *MockPeerstore) AddAddrs(arg0 peer.ID, arg1 []multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "AddAddrs", arg0, arg1, arg2) } // AddAddrs indicates an expected call of AddAddrs. func (mr *MockPeerstoreMockRecorder) AddAddrs(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAddrs", reflect.TypeOf((*MockPeerstore)(nil).AddAddrs), arg0, arg1, arg2) } // AddPrivKey mocks base method. func (m *MockPeerstore) AddPrivKey(arg0 peer.ID, arg1 crypto.PrivKey) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddPrivKey", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // AddPrivKey indicates an expected call of AddPrivKey. func (mr *MockPeerstoreMockRecorder) AddPrivKey(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPrivKey", reflect.TypeOf((*MockPeerstore)(nil).AddPrivKey), arg0, arg1) } // AddProtocols mocks base method. func (m *MockPeerstore) AddProtocols(arg0 peer.ID, arg1 ...string) error { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "AddProtocols", varargs...) ret0, _ := ret[0].(error) return ret0 } // AddProtocols indicates an expected call of AddProtocols. func (mr *MockPeerstoreMockRecorder) AddProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddProtocols", reflect.TypeOf((*MockPeerstore)(nil).AddProtocols), varargs...) } // AddPubKey mocks base method. func (m *MockPeerstore) AddPubKey(arg0 peer.ID, arg1 crypto.PubKey) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddPubKey", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // AddPubKey indicates an expected call of AddPubKey. func (mr *MockPeerstoreMockRecorder) AddPubKey(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPubKey", reflect.TypeOf((*MockPeerstore)(nil).AddPubKey), arg0, arg1) } // AddrStream mocks base method. func (m *MockPeerstore) AddrStream(arg0 context.Context, arg1 peer.ID) <-chan multiaddr.Multiaddr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddrStream", arg0, arg1) ret0, _ := ret[0].(<-chan multiaddr.Multiaddr) return ret0 } // AddrStream indicates an expected call of AddrStream. func (mr *MockPeerstoreMockRecorder) AddrStream(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrStream", reflect.TypeOf((*MockPeerstore)(nil).AddrStream), arg0, arg1) } // Addrs mocks base method. func (m *MockPeerstore) Addrs(arg0 peer.ID) []multiaddr.Multiaddr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Addrs", arg0) ret0, _ := ret[0].([]multiaddr.Multiaddr) return ret0 } // Addrs indicates an expected call of Addrs. func (mr *MockPeerstoreMockRecorder) Addrs(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addrs", reflect.TypeOf((*MockPeerstore)(nil).Addrs), arg0) } // ClearAddrs mocks base method. func (m *MockPeerstore) ClearAddrs(arg0 peer.ID) { m.ctrl.T.Helper() m.ctrl.Call(m, "ClearAddrs", arg0) } // ClearAddrs indicates an expected call of ClearAddrs. func (mr *MockPeerstoreMockRecorder) ClearAddrs(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearAddrs", reflect.TypeOf((*MockPeerstore)(nil).ClearAddrs), arg0) } // Close mocks base method. func (m *MockPeerstore) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockPeerstoreMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockPeerstore)(nil).Close)) } // Get mocks base method. func (m *MockPeerstore) Get(arg0 peer.ID, arg1 string) (interface{}, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0, arg1) ret0, _ := ret[0].(interface{}) ret1, _ := ret[1].(error) return ret0, ret1 } // Get indicates an expected call of Get. func (mr *MockPeerstoreMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockPeerstore)(nil).Get), arg0, arg1) } // GetProtocols mocks base method. func (m *MockPeerstore) GetProtocols(arg0 peer.ID) ([]string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetProtocols", arg0) ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } // GetProtocols indicates an expected call of GetProtocols. func (mr *MockPeerstoreMockRecorder) GetProtocols(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProtocols", reflect.TypeOf((*MockPeerstore)(nil).GetProtocols), arg0) } // LatencyEWMA mocks base method. func (m *MockPeerstore) LatencyEWMA(arg0 peer.ID) time.Duration { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LatencyEWMA", arg0) ret0, _ := ret[0].(time.Duration) return ret0 } // LatencyEWMA indicates an expected call of LatencyEWMA. func (mr *MockPeerstoreMockRecorder) LatencyEWMA(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatencyEWMA", reflect.TypeOf((*MockPeerstore)(nil).LatencyEWMA), arg0) } // PeerInfo mocks base method. func (m *MockPeerstore) PeerInfo(arg0 peer.ID) peer.AddrInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PeerInfo", arg0) ret0, _ := ret[0].(peer.AddrInfo) return ret0 } // PeerInfo indicates an expected call of PeerInfo. func (mr *MockPeerstoreMockRecorder) PeerInfo(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerInfo", reflect.TypeOf((*MockPeerstore)(nil).PeerInfo), arg0) } // Peers mocks base method. func (m *MockPeerstore) Peers() peer.IDSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Peers") ret0, _ := ret[0].(peer.IDSlice) return ret0 } // Peers indicates an expected call of Peers. func (mr *MockPeerstoreMockRecorder) Peers() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peers", reflect.TypeOf((*MockPeerstore)(nil).Peers)) } // PeersWithAddrs mocks base method. func (m *MockPeerstore) PeersWithAddrs() peer.IDSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PeersWithAddrs") ret0, _ := ret[0].(peer.IDSlice) return ret0 } // PeersWithAddrs indicates an expected call of PeersWithAddrs. func (mr *MockPeerstoreMockRecorder) PeersWithAddrs() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeersWithAddrs", reflect.TypeOf((*MockPeerstore)(nil).PeersWithAddrs)) } // PeersWithKeys mocks base method. func (m *MockPeerstore) PeersWithKeys() peer.IDSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PeersWithKeys") ret0, _ := ret[0].(peer.IDSlice) return ret0 } // PeersWithKeys indicates an expected call of PeersWithKeys. func (mr *MockPeerstoreMockRecorder) PeersWithKeys() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeersWithKeys", reflect.TypeOf((*MockPeerstore)(nil).PeersWithKeys)) } // PrivKey mocks base method. func (m *MockPeerstore) PrivKey(arg0 peer.ID) crypto.PrivKey { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PrivKey", arg0) ret0, _ := ret[0].(crypto.PrivKey) return ret0 } // PrivKey indicates an expected call of PrivKey. func (mr *MockPeerstoreMockRecorder) PrivKey(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrivKey", reflect.TypeOf((*MockPeerstore)(nil).PrivKey), arg0) } // PubKey mocks base method. func (m *MockPeerstore) PubKey(arg0 peer.ID) crypto.PubKey { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PubKey", arg0) ret0, _ := ret[0].(crypto.PubKey) return ret0 } // PubKey indicates an expected call of PubKey. func (mr *MockPeerstoreMockRecorder) PubKey(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubKey", reflect.TypeOf((*MockPeerstore)(nil).PubKey), arg0) } // Put mocks base method. func (m *MockPeerstore) Put(arg0 peer.ID, arg1 string, arg2 interface{}) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Put", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } // Put indicates an expected call of Put. func (mr *MockPeerstoreMockRecorder) Put(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockPeerstore)(nil).Put), arg0, arg1, arg2) } // RecordLatency mocks base method. func (m *MockPeerstore) RecordLatency(arg0 peer.ID, arg1 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordLatency", arg0, arg1) } // RecordLatency indicates an expected call of RecordLatency. func (mr *MockPeerstoreMockRecorder) RecordLatency(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLatency", reflect.TypeOf((*MockPeerstore)(nil).RecordLatency), arg0, arg1) } // RemoveProtocols mocks base method. func (m *MockPeerstore) RemoveProtocols(arg0 peer.ID, arg1 ...string) error { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "RemoveProtocols", varargs...) ret0, _ := ret[0].(error) return ret0 } // RemoveProtocols indicates an expected call of RemoveProtocols. func (mr *MockPeerstoreMockRecorder) RemoveProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveProtocols", reflect.TypeOf((*MockPeerstore)(nil).RemoveProtocols), varargs...) } // SetAddr mocks base method. func (m *MockPeerstore) SetAddr(arg0 peer.ID, arg1 multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetAddr", arg0, arg1, arg2) } // SetAddr indicates an expected call of SetAddr. func (mr *MockPeerstoreMockRecorder) SetAddr(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAddr", reflect.TypeOf((*MockPeerstore)(nil).SetAddr), arg0, arg1, arg2) } // SetAddrs mocks base method. func (m *MockPeerstore) SetAddrs(arg0 peer.ID, arg1 []multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetAddrs", arg0, arg1, arg2) } // SetAddrs indicates an expected call of SetAddrs. func (mr *MockPeerstoreMockRecorder) SetAddrs(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAddrs", reflect.TypeOf((*MockPeerstore)(nil).SetAddrs), arg0, arg1, arg2) } // SetProtocols mocks base method. func (m *MockPeerstore) SetProtocols(arg0 peer.ID, arg1 ...string) error { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "SetProtocols", varargs...) ret0, _ := ret[0].(error) return ret0 } // SetProtocols indicates an expected call of SetProtocols. func (mr *MockPeerstoreMockRecorder) SetProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProtocols", reflect.TypeOf((*MockPeerstore)(nil).SetProtocols), varargs...) } // SupportsProtocols mocks base method. func (m *MockPeerstore) SupportsProtocols(arg0 peer.ID, arg1 ...string) ([]string, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "SupportsProtocols", varargs...) ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } // SupportsProtocols indicates an expected call of SupportsProtocols. func (mr *MockPeerstoreMockRecorder) SupportsProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportsProtocols", reflect.TypeOf((*MockPeerstore)(nil).SupportsProtocols), varargs...) } // UpdateAddrs mocks base method. func (m *MockPeerstore) UpdateAddrs(arg0 peer.ID, arg1, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "UpdateAddrs", arg0, arg1, arg2) } // UpdateAddrs indicates an expected call of UpdateAddrs. func (mr *MockPeerstoreMockRecorder) UpdateAddrs(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAddrs", reflect.TypeOf((*MockPeerstore)(nil).UpdateAddrs), arg0, arg1, arg2) } ================================================ FILE: libs/go/libp2p_node/protocols/acn/v1_0_0/acn.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.11.4 // source: acn.proto package aea_aea_acn_v1_0_0 import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type AcnMessage_StatusBody_StatusCodeEnum int32 const ( // common (0x) AcnMessage_StatusBody_SUCCESS AcnMessage_StatusBody_StatusCodeEnum = 0 AcnMessage_StatusBody_ERROR_UNSUPPORTED_VERSION AcnMessage_StatusBody_StatusCodeEnum = 1 AcnMessage_StatusBody_ERROR_UNEXPECTED_PAYLOAD AcnMessage_StatusBody_StatusCodeEnum = 2 AcnMessage_StatusBody_ERROR_GENERIC AcnMessage_StatusBody_StatusCodeEnum = 3 AcnMessage_StatusBody_ERROR_DECODE AcnMessage_StatusBody_StatusCodeEnum = 4 // register (1x) AcnMessage_StatusBody_ERROR_WRONG_AGENT_ADDRESS AcnMessage_StatusBody_StatusCodeEnum = 10 AcnMessage_StatusBody_ERROR_WRONG_PUBLIC_KEY AcnMessage_StatusBody_StatusCodeEnum = 11 AcnMessage_StatusBody_ERROR_INVALID_PROOF AcnMessage_StatusBody_StatusCodeEnum = 12 AcnMessage_StatusBody_ERROR_UNSUPPORTED_LEDGER AcnMessage_StatusBody_StatusCodeEnum = 13 // lookup & delivery (2x) AcnMessage_StatusBody_ERROR_UNKNOWN_AGENT_ADDRESS AcnMessage_StatusBody_StatusCodeEnum = 20 AcnMessage_StatusBody_ERROR_AGENT_NOT_READY AcnMessage_StatusBody_StatusCodeEnum = 21 ) // Enum value maps for AcnMessage_StatusBody_StatusCodeEnum. var ( AcnMessage_StatusBody_StatusCodeEnum_name = map[int32]string{ 0: "SUCCESS", 1: "ERROR_UNSUPPORTED_VERSION", 2: "ERROR_UNEXPECTED_PAYLOAD", 3: "ERROR_GENERIC", 4: "ERROR_DECODE", 10: "ERROR_WRONG_AGENT_ADDRESS", 11: "ERROR_WRONG_PUBLIC_KEY", 12: "ERROR_INVALID_PROOF", 13: "ERROR_UNSUPPORTED_LEDGER", 20: "ERROR_UNKNOWN_AGENT_ADDRESS", 21: "ERROR_AGENT_NOT_READY", } AcnMessage_StatusBody_StatusCodeEnum_value = map[string]int32{ "SUCCESS": 0, "ERROR_UNSUPPORTED_VERSION": 1, "ERROR_UNEXPECTED_PAYLOAD": 2, "ERROR_GENERIC": 3, "ERROR_DECODE": 4, "ERROR_WRONG_AGENT_ADDRESS": 10, "ERROR_WRONG_PUBLIC_KEY": 11, "ERROR_INVALID_PROOF": 12, "ERROR_UNSUPPORTED_LEDGER": 13, "ERROR_UNKNOWN_AGENT_ADDRESS": 20, "ERROR_AGENT_NOT_READY": 21, } ) func (x AcnMessage_StatusBody_StatusCodeEnum) Enum() *AcnMessage_StatusBody_StatusCodeEnum { p := new(AcnMessage_StatusBody_StatusCodeEnum) *p = x return p } func (x AcnMessage_StatusBody_StatusCodeEnum) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AcnMessage_StatusBody_StatusCodeEnum) Descriptor() protoreflect.EnumDescriptor { return file_acn_proto_enumTypes[0].Descriptor() } func (AcnMessage_StatusBody_StatusCodeEnum) Type() protoreflect.EnumType { return &file_acn_proto_enumTypes[0] } func (x AcnMessage_StatusBody_StatusCodeEnum) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AcnMessage_StatusBody_StatusCodeEnum.Descriptor instead. func (AcnMessage_StatusBody_StatusCodeEnum) EnumDescriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 1, 0} } type AcnMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Performative: // *AcnMessage_AeaEnvelope // *AcnMessage_LookupRequest // *AcnMessage_LookupResponse // *AcnMessage_Register // *AcnMessage_Status Performative isAcnMessage_Performative `protobuf_oneof:"performative"` } func (x *AcnMessage) Reset() { *x = AcnMessage{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage) ProtoMessage() {} func (x *AcnMessage) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage.ProtoReflect.Descriptor instead. func (*AcnMessage) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0} } func (m *AcnMessage) GetPerformative() isAcnMessage_Performative { if m != nil { return m.Performative } return nil } func (x *AcnMessage) GetAeaEnvelope() *AcnMessage_Aea_Envelope_Performative { if x, ok := x.GetPerformative().(*AcnMessage_AeaEnvelope); ok { return x.AeaEnvelope } return nil } func (x *AcnMessage) GetLookupRequest() *AcnMessage_Lookup_Request_Performative { if x, ok := x.GetPerformative().(*AcnMessage_LookupRequest); ok { return x.LookupRequest } return nil } func (x *AcnMessage) GetLookupResponse() *AcnMessage_Lookup_Response_Performative { if x, ok := x.GetPerformative().(*AcnMessage_LookupResponse); ok { return x.LookupResponse } return nil } func (x *AcnMessage) GetRegister() *AcnMessage_Register_Performative { if x, ok := x.GetPerformative().(*AcnMessage_Register); ok { return x.Register } return nil } func (x *AcnMessage) GetStatus() *AcnMessage_Status_Performative { if x, ok := x.GetPerformative().(*AcnMessage_Status); ok { return x.Status } return nil } type isAcnMessage_Performative interface { isAcnMessage_Performative() } type AcnMessage_AeaEnvelope struct { AeaEnvelope *AcnMessage_Aea_Envelope_Performative `protobuf:"bytes,5,opt,name=aea_envelope,json=aeaEnvelope,proto3,oneof"` } type AcnMessage_LookupRequest struct { LookupRequest *AcnMessage_Lookup_Request_Performative `protobuf:"bytes,6,opt,name=lookup_request,json=lookupRequest,proto3,oneof"` } type AcnMessage_LookupResponse struct { LookupResponse *AcnMessage_Lookup_Response_Performative `protobuf:"bytes,7,opt,name=lookup_response,json=lookupResponse,proto3,oneof"` } type AcnMessage_Register struct { Register *AcnMessage_Register_Performative `protobuf:"bytes,8,opt,name=register,proto3,oneof"` } type AcnMessage_Status struct { Status *AcnMessage_Status_Performative `protobuf:"bytes,9,opt,name=status,proto3,oneof"` } func (*AcnMessage_AeaEnvelope) isAcnMessage_Performative() {} func (*AcnMessage_LookupRequest) isAcnMessage_Performative() {} func (*AcnMessage_LookupResponse) isAcnMessage_Performative() {} func (*AcnMessage_Register) isAcnMessage_Performative() {} func (*AcnMessage_Status) isAcnMessage_Performative() {} // Custom Types type AcnMessage_AgentRecord struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ServiceId string `protobuf:"bytes,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` LedgerId string `protobuf:"bytes,2,opt,name=ledger_id,json=ledgerId,proto3" json:"ledger_id,omitempty"` Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` PublicKey string `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` PeerPublicKey string `protobuf:"bytes,5,opt,name=peer_public_key,json=peerPublicKey,proto3" json:"peer_public_key,omitempty"` Signature string `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` NotBefore string `protobuf:"bytes,7,opt,name=not_before,json=notBefore,proto3" json:"not_before,omitempty"` NotAfter string `protobuf:"bytes,8,opt,name=not_after,json=notAfter,proto3" json:"not_after,omitempty"` } func (x *AcnMessage_AgentRecord) Reset() { *x = AcnMessage_AgentRecord{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_AgentRecord) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_AgentRecord) ProtoMessage() {} func (x *AcnMessage_AgentRecord) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_AgentRecord.ProtoReflect.Descriptor instead. func (*AcnMessage_AgentRecord) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 0} } func (x *AcnMessage_AgentRecord) GetServiceId() string { if x != nil { return x.ServiceId } return "" } func (x *AcnMessage_AgentRecord) GetLedgerId() string { if x != nil { return x.LedgerId } return "" } func (x *AcnMessage_AgentRecord) GetAddress() string { if x != nil { return x.Address } return "" } func (x *AcnMessage_AgentRecord) GetPublicKey() string { if x != nil { return x.PublicKey } return "" } func (x *AcnMessage_AgentRecord) GetPeerPublicKey() string { if x != nil { return x.PeerPublicKey } return "" } func (x *AcnMessage_AgentRecord) GetSignature() string { if x != nil { return x.Signature } return "" } func (x *AcnMessage_AgentRecord) GetNotBefore() string { if x != nil { return x.NotBefore } return "" } func (x *AcnMessage_AgentRecord) GetNotAfter() string { if x != nil { return x.NotAfter } return "" } type AcnMessage_StatusBody struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Code AcnMessage_StatusBody_StatusCodeEnum `protobuf:"varint,1,opt,name=code,proto3,enum=aea.aea.acn.v1_0_0.AcnMessage_StatusBody_StatusCodeEnum" json:"code,omitempty"` Msgs []string `protobuf:"bytes,2,rep,name=msgs,proto3" json:"msgs,omitempty"` } func (x *AcnMessage_StatusBody) Reset() { *x = AcnMessage_StatusBody{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_StatusBody) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_StatusBody) ProtoMessage() {} func (x *AcnMessage_StatusBody) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_StatusBody.ProtoReflect.Descriptor instead. func (*AcnMessage_StatusBody) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 1} } func (x *AcnMessage_StatusBody) GetCode() AcnMessage_StatusBody_StatusCodeEnum { if x != nil { return x.Code } return AcnMessage_StatusBody_SUCCESS } func (x *AcnMessage_StatusBody) GetMsgs() []string { if x != nil { return x.Msgs } return nil } // Performatives and contents type AcnMessage_Register_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Record *AcnMessage_AgentRecord `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Register_Performative) Reset() { *x = AcnMessage_Register_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Register_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Register_Performative) ProtoMessage() {} func (x *AcnMessage_Register_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Register_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Register_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 2} } func (x *AcnMessage_Register_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Lookup_Request_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AgentAddress string `protobuf:"bytes,1,opt,name=agent_address,json=agentAddress,proto3" json:"agent_address,omitempty"` } func (x *AcnMessage_Lookup_Request_Performative) Reset() { *x = AcnMessage_Lookup_Request_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Lookup_Request_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Lookup_Request_Performative) ProtoMessage() {} func (x *AcnMessage_Lookup_Request_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Lookup_Request_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Lookup_Request_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 3} } func (x *AcnMessage_Lookup_Request_Performative) GetAgentAddress() string { if x != nil { return x.AgentAddress } return "" } type AcnMessage_Lookup_Response_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Record *AcnMessage_AgentRecord `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Lookup_Response_Performative) Reset() { *x = AcnMessage_Lookup_Response_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Lookup_Response_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Lookup_Response_Performative) ProtoMessage() {} func (x *AcnMessage_Lookup_Response_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Lookup_Response_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Lookup_Response_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 4} } func (x *AcnMessage_Lookup_Response_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Aea_Envelope_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Envelope []byte `protobuf:"bytes,1,opt,name=envelope,proto3" json:"envelope,omitempty"` Record *AcnMessage_AgentRecord `protobuf:"bytes,2,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Aea_Envelope_Performative) Reset() { *x = AcnMessage_Aea_Envelope_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Aea_Envelope_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Aea_Envelope_Performative) ProtoMessage() {} func (x *AcnMessage_Aea_Envelope_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Aea_Envelope_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Aea_Envelope_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 5} } func (x *AcnMessage_Aea_Envelope_Performative) GetEnvelope() []byte { if x != nil { return x.Envelope } return nil } func (x *AcnMessage_Aea_Envelope_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Status_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Body *AcnMessage_StatusBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` } func (x *AcnMessage_Status_Performative) Reset() { *x = AcnMessage_Status_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Status_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Status_Performative) ProtoMessage() {} func (x *AcnMessage_Status_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Status_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Status_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 6} } func (x *AcnMessage_Status_Performative) GetBody() *AcnMessage_StatusBody { if x != nil { return x.Body } return nil } var File_acn_proto protoreflect.FileDescriptor var file_acn_proto_rawDesc = []byte{ 0x0a, 0x09, 0x61, 0x63, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x22, 0xea, 0x0c, 0x0a, 0x0a, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x61, 0x65, 0x61, 0x5f, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x65, 0x61, 0x5f, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x65, 0x61, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x63, 0x0a, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x66, 0x0a, 0x0f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x84, 0x02, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x74, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x1a, 0x9e, 0x03, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x4c, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x22, 0xad, 0x02, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x49, 0x43, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x04, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0b, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x10, 0x0c, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x44, 0x47, 0x45, 0x52, 0x10, 0x0d, 0x12, 0x1f, 0x0a, 0x1b, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x14, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x15, 0x1a, 0x5b, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x42, 0x0a, 0x1b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0x62, 0x0a, 0x1c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x7b, 0x0a, 0x19, 0x41, 0x65, 0x61, 0x5f, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x54, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x0e, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_acn_proto_rawDescOnce sync.Once file_acn_proto_rawDescData = file_acn_proto_rawDesc ) func file_acn_proto_rawDescGZIP() []byte { file_acn_proto_rawDescOnce.Do(func() { file_acn_proto_rawDescData = protoimpl.X.CompressGZIP(file_acn_proto_rawDescData) }) return file_acn_proto_rawDescData } var file_acn_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_acn_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_acn_proto_goTypes = []interface{}{ (AcnMessage_StatusBody_StatusCodeEnum)(0), // 0: aea.aea.acn.v1_0_0.AcnMessage.StatusBody.StatusCodeEnum (*AcnMessage)(nil), // 1: aea.aea.acn.v1_0_0.AcnMessage (*AcnMessage_AgentRecord)(nil), // 2: aea.aea.acn.v1_0_0.AcnMessage.AgentRecord (*AcnMessage_StatusBody)(nil), // 3: aea.aea.acn.v1_0_0.AcnMessage.StatusBody (*AcnMessage_Register_Performative)(nil), // 4: aea.aea.acn.v1_0_0.AcnMessage.Register_Performative (*AcnMessage_Lookup_Request_Performative)(nil), // 5: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_Performative (*AcnMessage_Lookup_Response_Performative)(nil), // 6: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative (*AcnMessage_Aea_Envelope_Performative)(nil), // 7: aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative (*AcnMessage_Status_Performative)(nil), // 8: aea.aea.acn.v1_0_0.AcnMessage.Status_Performative } var file_acn_proto_depIdxs = []int32{ 7, // 0: aea.aea.acn.v1_0_0.AcnMessage.aea_envelope:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative 5, // 1: aea.aea.acn.v1_0_0.AcnMessage.lookup_request:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_Performative 6, // 2: aea.aea.acn.v1_0_0.AcnMessage.lookup_response:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative 4, // 3: aea.aea.acn.v1_0_0.AcnMessage.register:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Register_Performative 8, // 4: aea.aea.acn.v1_0_0.AcnMessage.status:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Status_Performative 0, // 5: aea.aea.acn.v1_0_0.AcnMessage.StatusBody.code:type_name -> aea.aea.acn.v1_0_0.AcnMessage.StatusBody.StatusCodeEnum 2, // 6: aea.aea.acn.v1_0_0.AcnMessage.Register_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 2, // 7: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 2, // 8: aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 3, // 9: aea.aea.acn.v1_0_0.AcnMessage.Status_Performative.body:type_name -> aea.aea.acn.v1_0_0.AcnMessage.StatusBody 10, // [10:10] is the sub-list for method output_type 10, // [10:10] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_acn_proto_init() } func file_acn_proto_init() { if File_acn_proto != nil { return } if !protoimpl.UnsafeEnabled { file_acn_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_AgentRecord); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_StatusBody); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Register_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Lookup_Request_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Lookup_Response_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Aea_Envelope_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Status_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_acn_proto_msgTypes[0].OneofWrappers = []interface{}{ (*AcnMessage_AeaEnvelope)(nil), (*AcnMessage_LookupRequest)(nil), (*AcnMessage_LookupResponse)(nil), (*AcnMessage_Register)(nil), (*AcnMessage_Status)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_acn_proto_rawDesc, NumEnums: 1, NumMessages: 8, NumExtensions: 0, NumServices: 0, }, GoTypes: file_acn_proto_goTypes, DependencyIndexes: file_acn_proto_depIdxs, EnumInfos: file_acn_proto_enumTypes, MessageInfos: file_acn_proto_msgTypes, }.Build() File_acn_proto = out.File file_acn_proto_rawDesc = nil file_acn_proto_goTypes = nil file_acn_proto_depIdxs = nil } ================================================ FILE: libs/go/libp2p_node/protocols/acn/v1_0_0/acn.proto ================================================ syntax = "proto3"; package aea.aea.acn.v1_0_0; option go_package = "libp2p_node/protocols/acn/v1_0_0"; message AcnMessage{ // Custom Types message AgentRecord{ string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; } message StatusBody{ enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; } // Performatives and contents message Register_Performative{ AgentRecord record = 1; } message Lookup_Request_Performative{ string agent_address = 1; } message Lookup_Response_Performative{ AgentRecord record = 1; } message Aea_Envelope_Performative{ bytes envelope = 1; AgentRecord record = 2; } message Status_Performative{ StatusBody body = 1; } oneof performative{ Aea_Envelope_Performative aea_envelope = 5; Lookup_Request_Performative lookup_request = 6; Lookup_Response_Performative lookup_response = 7; Register_Performative register = 8; Status_Performative status = 9; } } ================================================ FILE: libs/go/libp2p_node/protocols/acn/v1_0_0/acn.yaml ================================================ --- name: acn author: fetchai version: 1.0.0 description: The protocol used for envelope delivery on the ACN. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: aea/acn:1.0.0 speech_acts: register: record: ct:AgentRecord lookup_request: agent_address: pt:str lookup_response: record: ct:AgentRecord aea_envelope: envelope: pt:bytes record: ct:AgentRecord status: body: ct:StatusBody ... --- ct:AgentRecord: string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; ct:StatusBody: | enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; ... --- initiation: [register, lookup_request, aea_envelope] reply: register: [status] lookup_request: [lookup_response, status] aea_envelope: [status] status: [] lookup_response: [] termination: [status, lookup_response] roles: {node} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ================================================ FILE: libs/go/libp2p_node/utils/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package utils import ( "bufio" "context" "crypto/sha256" "encoding/base64" "encoding/binary" "encoding/hex" "errors" "fmt" "io" "math" "net" "os" "sync" "time" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multihash" "github.com/rs/zerolog" "golang.org/x/crypto/ripemd160" // nolint:staticcheck "golang.org/x/crypto/sha3" host "github.com/libp2p/go-libp2p-core/host" peerstore "github.com/libp2p/go-libp2p-core/peerstore" btcec "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/bech32" "github.com/ethereum/go-ethereum/common/hexutil" ethCrypto "github.com/ethereum/go-ethereum/crypto" proto "google.golang.org/protobuf/proto" "libp2p_node/aea" ) const ( maxMessageSizeDelegateConnection = 1024 * 1024 * 3 // 3Mb ) var ( addressFromPublicKeyTable = map[string]func(string) (string, error){ "fetchai": FetchAIAddressFromPublicKey, "cosmos": CosmosAddressFromPublicKey, "ethereum": EthereumAddressFromPublicKey, } verifyLedgerSignatureTable = map[string]func([]byte, string, string) (bool, error){ "fetchai": VerifyFetchAISignatureBTC, "cosmos": VerifyFetchAISignatureBTC, "ethereum": VerifyEthereumSignatureETH, } ) var loggerGlobalLevel zerolog.Level = zerolog.DebugLevel var logger zerolog.Logger = NewDefaultLogger() // SetLoggerLevel set utils logger level func SetLoggerLevel(lvl zerolog.Level) { logger = logger.Level(lvl) } func ignore(err error) { if err != nil { fmt.Println("IGNORED:", err) } } /* Logging */ func newConsoleLogger() zerolog.Logger { return zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339Nano, }) } // NewDefaultLogger basic zerolog console writer func NewDefaultLogger() zerolog.Logger { return newConsoleLogger(). With().Timestamp(). Logger().Level(loggerGlobalLevel) } // NewDefaultLoggerWithFields zerolog console writer func NewDefaultLoggerWithFields(fields map[string]string) zerolog.Logger { logger := newConsoleLogger(). With().Timestamp() for key, val := range fields { logger = logger.Str(key, val) } return logger.Logger().Level(loggerGlobalLevel) } /* Helpers */ // BootstrapConnect connect to `peers` at bootstrap // This code is borrowed from the go-ipfs bootstrap process func BootstrapConnect( ctx context.Context, ph host.Host, kaddht *dht.IpfsDHT, peers []peer.AddrInfo, ) error { if len(peers) < 1 { return errors.New("not enough bootstrap peers") } errs := make(chan error, len(peers)) var wg sync.WaitGroup for _, p := range peers { // performed asynchronously because when performed synchronously, if // one `Connect` call hangs, subsequent calls are more likely to // fail/abort due to an expiring context. // Also, performed asynchronously for dial speed. wg.Add(1) go func(p peer.AddrInfo) { defer wg.Done() //defer logger.Debug().Msgf("%s bootstrapDial %s %s", ctx, ph.ID(), p.ID) logger.Debug().Msgf("%s bootstrapping to %s", ph.ID(), p.ID) ph.Peerstore().AddAddrs(p.ID, p.Addrs, peerstore.PermanentAddrTTL) if err := ph.Connect(ctx, p); err != nil { //logger.Error(). // Str("err", err.Error()). // Msgf("failed to bootstrap with %v", p.ID) errs <- err return } logger.Debug().Msgf("bootstrapped with %v", p.ID) }(p) } wg.Wait() // our failure condition is when no connection attempt succeeded. // So drain the errs channel, counting the results. close(errs) count := 0 var err error for err = range errs { if err != nil { count++ } } if count == len(peers) { return errors.New("failed to bootstrap: " + err.Error()) } // workaround: to avoid getting `failed to find any peer in table` // when calling dht.Provide (happens occasionally) logger.Debug().Msg("waiting for bootstrap peers to be added to dht routing table...") for _, peer := range peers { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() for kaddht.RoutingTable().Find(peer.ID) == "" { select { case <-ctx.Done(): return errors.New( "timeout: entry peer haven't been added to DHT routing table " + peer.ID.Pretty(), ) case <-time.After(time.Millisecond * 5): } } } return nil } // ComputeCID compute content id for ipfsDHT func ComputeCID(addr string) (cid.Cid, error) { pref := cid.Prefix{ Version: 0, Codec: cid.Raw, MhType: multihash.SHA2_256, MhLength: -1, // default length } // And then feed it some data c, err := pref.Sum([]byte(addr)) if err != nil { return cid.Cid{}, err } return c, nil } // GetPeersAddrInfo Parse multiaddresses and convert them to peer.AddrInfo func GetPeersAddrInfo(peers []string) ([]peer.AddrInfo, error) { pinfos := make([]peer.AddrInfo, len(peers)) for i, addr := range peers { maddr := multiaddr.StringCast(addr) p, err := peer.AddrInfoFromP2pAddr(maddr) if err != nil { return pinfos, err } pinfos[i] = *p } return pinfos, nil } /* FetchAI Crypto Helpers */ // PubKeyFromFetchAIPublicKey create libp2p public key from fetchai hex encoded secp256k1 key func PubKeyFromFetchAIPublicKey(publicKey string) (crypto.PubKey, error) { hexBytes, _ := hex.DecodeString(publicKey) return crypto.UnmarshalSecp256k1PublicKey(hexBytes) } // FetchAIPublicKeyFromPubKey return FetchAI's format serialized public key func FetchAIPublicKeyFromPubKey(publicKey crypto.PubKey) (string, error) { raw, err := publicKey.Raw() if err != nil { return "", err } return hex.EncodeToString(raw), nil } // BTCPubKeyFromFetchAIPublicKey from public key string func BTCPubKeyFromFetchAIPublicKey(publicKey string) (*btcec.PublicKey, error) { pbkBytes, err := hex.DecodeString(publicKey) if err != nil { return nil, err } pbk, err := btcec.ParsePubKey(pbkBytes, btcec.S256()) return pbk, err } // BTCPubKeyFromEthereumPublicKey create libp2p public key from ethereum uncompressed // hex encoded secp256k1 key func BTCPubKeyFromEthereumPublicKey(publicKey string) (*btcec.PublicKey, error) { return BTCPubKeyFromUncompressedHex(publicKey[2:]) } // ConvertStrEncodedSignatureToDER to convert signature to DER format // References: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L258 // - https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L47 func ConvertStrEncodedSignatureToDER(signature []byte) []byte { rb := signature[:len(signature)/2] sb := signature[len(signature)/2:] length := 6 + len(rb) + len(sb) sigDER := make([]byte, length) sigDER[0] = 0x30 sigDER[1] = byte(length - 2) sigDER[2] = 0x02 sigDER[3] = byte(len(rb)) offset := copy(sigDER[4:], rb) + 4 sigDER[offset] = 0x02 sigDER[offset+1] = byte(len(sb)) copy(sigDER[offset+2:], sb) return sigDER } // ConvertDEREncodedSignatureToStr Convert signatue from der format to string // References: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L258 // - https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L47 func ConvertDEREncodedSignatureToStr(signature []byte) ([]byte, error) { sig, err := btcec.ParseDERSignature(signature, btcec.S256()) if err != nil { return []byte{}, err } return append(sig.R.Bytes(), sig.S.Bytes()...), nil } // ParseFetchAISignature create btcec Signature from base64 formated, string (not DER) encoded RFC6979 signature func ParseFetchAISignature(signature string) (*btcec.Signature, error) { // First convert the signature into a DER one sigBytes, err := base64.StdEncoding.DecodeString(signature) if err != nil { return nil, err } sigDER := ConvertStrEncodedSignatureToDER(sigBytes) // Parse sigBTC, err := btcec.ParseSignature(sigDER, btcec.S256()) return sigBTC, err } // VerifyLedgerSignature verify signature of message using public key for supported ledgers func VerifyLedgerSignature( ledgerID string, message []byte, signature string, pubKey string, ) (bool, error) { verifySignature, found := verifyLedgerSignatureTable[ledgerID] if found { return verifySignature(message, signature, pubKey) } return false, errors.New("unsupported ledger") } // VerifyFetchAISignatureBTC verify the RFC6967 string-encoded signature of message using FetchAI public key func VerifyFetchAISignatureBTC(message []byte, signature string, pubkey string) (bool, error) { // construct verifying key verifyKey, err := BTCPubKeyFromFetchAIPublicKey(pubkey) if err != nil { return false, err } // construct signature signatureBTC, err := ParseFetchAISignature(signature) if err != nil { return false, err } // verify signature messageHash := sha256.New() _, err = messageHash.Write([]byte(message)) if err != nil { return false, err } return signatureBTC.Verify(messageHash.Sum(nil), verifyKey), nil } // VerifyFetchAISignatureLibp2p verify RFC6967 string-encoded signature of message using FetchAI public key func VerifyFetchAISignatureLibp2p(message []byte, signature string, pubkey string) (bool, error) { // construct verifying key verifyKey, err := PubKeyFromFetchAIPublicKey(pubkey) if err != nil { return false, err } // Convert signature into DER encoding sigBytes, err := base64.StdEncoding.DecodeString(signature) if err != nil { return false, err } sigDER := ConvertStrEncodedSignatureToDER(sigBytes) // verify signature return verifyKey.Verify(message, sigDER) } // SignFetchAI signs message with private key func SignFetchAI(message []byte, privKey string) (string, error) { signingKey, _, err := KeyPairFromFetchAIKey(privKey) if err != nil { return "", err } signature, err := signingKey.Sign(message) if err != nil { return "", err } strSignature, err := ConvertDEREncodedSignatureToStr(signature) if err != nil { return "", err } encodedSignature := base64.StdEncoding.EncodeToString(strSignature) return encodedSignature, nil } func signHashETH(data []byte) []byte { msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) return ethCrypto.Keccak256([]byte(msg)) } // RecoverAddressFromEthereumSignature verify the signature and returns the address of the signer // references: // - https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 // - https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L404 func RecoverAddressFromEthereumSignature(message []byte, signature string) (string, error) { // prepare signature sigBytes, err := hexutil.Decode(signature) if err != nil { return "", err } if sigBytes[64] != 27 && sigBytes[64] != 28 { return "", errors.New("invalid Ethereum signature (V is not 27 or 28)") } sigBytes[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 // recover verify key recoveredPubKey, err := ethCrypto.SigToPub(signHashETH(message), sigBytes) if err != nil { return "", err } return ethCrypto.PubkeyToAddress(*recoveredPubKey).Hex(), nil } // VerifyEthereumSignatureETH verify ethereum signature using ethereum public key func VerifyEthereumSignatureETH(message []byte, signature string, pubkey string) (bool, error) { // get expected signer address expectedAddress, err := EthereumAddressFromPublicKey(pubkey) if err != nil { return false, err } // recover signer address recoveredAddress, err := RecoverAddressFromEthereumSignature(message, signature) if err != nil { return false, err } if recoveredAddress != expectedAddress { return false, errors.New("recovered and expected addresses don't match") } return true, nil } // KeyPairFromFetchAIKey key pair from hex encoded secp256k1 private key func KeyPairFromFetchAIKey(key string) (crypto.PrivKey, crypto.PubKey, error) { pkBytes, err := hex.DecodeString(key) if err != nil { return nil, nil, err } btcPrivateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes) prvKey, pubKey, err := crypto.KeyPairFromStdKey(btcPrivateKey) if err != nil { return nil, nil, err } return prvKey, pubKey, nil } // AgentAddressFromPublicKey get wallet address from public key associated with ledgerId // format from: https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L120 func AgentAddressFromPublicKey(ledgerID string, publicKey string) (string, error) { if addressFromPublicKey, found := addressFromPublicKeyTable[ledgerID]; found { return addressFromPublicKey(publicKey) } return "", errors.New("Unsupported ledger " + ledgerID) } // FetchAIAddressFromPublicKey get wallet address from hex encoded secp256k1 public key func FetchAIAddressFromPublicKey(publicKey string) (string, error) { return cosmosAddressFromPublicKeyWithPrefix("fetch", publicKey) } // CosmosAddressFromPublicKey get wallet address from hex encoded secp256k1 public key func CosmosAddressFromPublicKey(publicKey string) (string, error) { return cosmosAddressFromPublicKeyWithPrefix("cosmos", publicKey) } // cosmosAddressFromPublicKeyWithPrefix get wallet address from hex encoded secp256k1 public key // format from: https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L120 func cosmosAddressFromPublicKeyWithPrefix(prefix string, publicKey string) (string, error) { var addr string var err error hexBytes, err := hex.DecodeString(publicKey) if err != nil { return addr, err } hash := sha256.New() _, err = hash.Write(hexBytes) if err != nil { return addr, err } sha256Hash := hash.Sum(nil) hash = ripemd160.New() _, err = hash.Write(sha256Hash) if err != nil { return addr, err } ripemd160Hash := hash.Sum(nil) fiveBitsChar, err := bech32.ConvertBits(ripemd160Hash, 8, 5, true) if err != nil { return addr, err } addr, err = bech32.Encode(prefix, fiveBitsChar) return addr, err } // EthereumAddressFromPublicKey get wallet address from hex encoded secp256k1 public key // references: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/ethereum.py#L330 // - https://github.com/ethereum/go-ethereum/blob/master/crypto/crypto.go#L263 func EthereumAddressFromPublicKey(publicKey string) (string, error) { var addr string var err error hexBytes, err := hex.DecodeString(publicKey[2:]) if err != nil { return addr, err } hash := sha3.NewLegacyKeccak256() _, err = hash.Write(hexBytes) if err != nil { return addr, err } sha3KeccakHash := hash.Sum(nil) return encodeChecksumEIP55(sha3KeccakHash[12:]), nil } // encodeChecksumEIP55 EIP55-compliant hex string representation of the address // source: https://github.com/ethereum/go-ethereum/blob/master/common/types.go#L210 // reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md func encodeChecksumEIP55(address []byte) string { unchecksummed := hex.EncodeToString(address[:]) sha := sha3.NewLegacyKeccak256() _, err := sha.Write([]byte(unchecksummed)) ignore(err) hash := sha.Sum(nil) result := []byte(unchecksummed) for i := 0; i < len(result); i++ { hashByte := hash[i/2] if i%2 == 0 { hashByte = hashByte >> 4 } else { hashByte &= 0xf } if result[i] > '9' && hashByte > 7 { result[i] -= 32 } } return "0x" + string(result) } // IDFromFetchAIPublicKey Get PeeID (multihash) from fetchai public key func IDFromFetchAIPublicKey(publicKey string) (peer.ID, error) { b, err := hex.DecodeString(publicKey) if err != nil { return "", err } pubKey, err := btcec.ParsePubKey(b, btcec.S256()) if err != nil { return "", err } multihash, err := peer.IDFromPublicKey((*crypto.Secp256k1PublicKey)(pubKey)) if err != nil { return "", err } return multihash, nil } // BTCPubKeyFromUncompressedHex get public key from secp256k1 hex encoded uncompressed representation func BTCPubKeyFromUncompressedHex(publicKey string) (*btcec.PublicKey, error) { b, err := hex.DecodeString(publicKey) if err != nil { return nil, err } pubBytes := make([]byte, 0, btcec.PubKeyBytesLenUncompressed) pubBytes = append(pubBytes, 0x4) // btcec.pubkeyUncompressed pubBytes = append(pubBytes, b...) return btcec.ParsePubKey(pubBytes, btcec.S256()) } // IDFromFetchAIPublicKeyUncompressed Get PeeID (multihash) from fetchai public key func IDFromFetchAIPublicKeyUncompressed(publicKey string) (peer.ID, error) { pubKey, err := BTCPubKeyFromUncompressedHex(publicKey) if err != nil { return "", err } multihash, err := peer.IDFromPublicKey((*crypto.Secp256k1PublicKey)(pubKey)) if err != nil { return "", err } return multihash, nil } // FetchAIPublicKeyFromFetchAIPrivateKey get fetchai public key from fetchai private key func FetchAIPublicKeyFromFetchAIPrivateKey(privateKey string) (string, error) { pkBytes, err := hex.DecodeString(privateKey) if err != nil { return "", err } _, btcPublicKey := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes) return hex.EncodeToString(btcPublicKey.SerializeCompressed()), nil } /* Utils */ // WriteBytesConn send bytes to `conn` func WriteBytesConn(conn net.Conn, data []byte) error { if len(data) > math.MaxInt32 { logger.Error().Msg("data size too large") return errors.New("data size too large") } if len(data) == 0 { logger.Error().Msg("No data to write") return nil } size := uint32(len(data)) buf := make([]byte, 4, 4+size) binary.BigEndian.PutUint32(buf, size) buf = append(buf, data...) _, err := conn.Write(buf) return err } // ReadBytesConn receive bytes from `conn` func ReadBytesConn(conn net.Conn) ([]byte, error) { buf := make([]byte, 4) _, err := conn.Read(buf) if err != nil { return buf, err } size := binary.BigEndian.Uint32(buf) if size > maxMessageSizeDelegateConnection { return nil, errors.New("expected message size larger than maximum allowed") } buf = make([]byte, size) _, err = conn.Read(buf) return buf, err } // WriteEnvelopeConn send envelope to `conn` func WriteEnvelopeConn(conn net.Conn, envelope *aea.Envelope) error { data, err := proto.Marshal(envelope) if err != nil { return err } return WriteBytesConn(conn, data) } // ReadEnvelopeConn receive envelope from `conn` func ReadEnvelopeConn(conn net.Conn) (*aea.Envelope, error) { envelope := &aea.Envelope{} data, err := ReadBytesConn(conn) if err != nil { return envelope, err } err = proto.Unmarshal(data, envelope) return envelope, err } // ReadBytes from a network stream func ReadBytes(s network.Stream) ([]byte, error) { if s == nil { panic("CRITICAL can not write to nil stream") } rstream := bufio.NewReader(s) buf := make([]byte, 4) _, err := io.ReadFull(rstream, buf) if err != nil { logger.Error(). Str("err", err.Error()). Msg("while receiving size") return buf, err } size := binary.BigEndian.Uint32(buf) if size > maxMessageSizeDelegateConnection { return nil, errors.New("expected message size larger than maximum allowed") } //logger.Debug().Msgf("expecting %d", size) buf = make([]byte, size) _, err = io.ReadFull(rstream, buf) return buf, err } // WriteBytes to a network stream func WriteBytes(s network.Stream, data []byte) error { if len(data) > math.MaxInt32 { logger.Error().Msg("data size too large") return errors.New("data size too large") } if len(data) == 0 { logger.Error().Msg("No data to write") return nil } if s == nil { panic("CRITICAL, can not write to nil stream") } wstream := bufio.NewWriter(s) size := uint32(len(data)) buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, size) _, err := wstream.Write(buf) if err != nil { logger.Error(). Str("err", err.Error()). Msg("while sending size") return err } //logger.Debug().Msgf("writing %d", len(data)) _, err = wstream.Write(data) if err != nil { logger.Error(). Str("err", err.Error()). Msg("Error on data write") return err } if s == nil { panic("CRITICAL, can not flush nil stream") } err = wstream.Flush() return err } type ConnPipe struct { Conn net.Conn } func (conPipe ConnPipe) Connect() error { return nil } func (conPipe ConnPipe) Read() ([]byte, error) { return ReadBytesConn(conPipe.Conn) } func (conPipe ConnPipe) Write(data []byte) error { return WriteBytesConn(conPipe.Conn, data) } func (conPipe ConnPipe) Close() error { return nil } type StreamPipe struct { Stream network.Stream } func (streamPipe StreamPipe) Connect() error { return nil } func (streamPipe StreamPipe) Read() ([]byte, error) { return ReadBytes(streamPipe.Stream) } func (streamPipe StreamPipe) Write(data []byte) error { return WriteBytes(streamPipe.Stream, data) } func (streamPipe StreamPipe) Close() error { return nil } ================================================ FILE: libs/go/libp2p_node/utils/utils_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package utils import ( "bytes" "context" "encoding/json" "errors" "libp2p_node/aea" mocks "libp2p_node/mocks" "net" "reflect" "testing" "bou.ke/monkey" gomock "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p-core/peer" dht "github.com/libp2p/go-libp2p-kad-dht" kb "github.com/libp2p/go-libp2p-kbucket" ma "github.com/multiformats/go-multiaddr" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) // Crypto operations func TestEthereumCrypto(t *testing.T) { //privateKey := "0xb60fe8027fb82f1a1bd6b8e66d4400f858989a2c67428a4e7f589441700339b0" publicKey := "0xf753e5a9e2368e97f4db869a0d956d3ffb64672d6392670572906c786b5712ada13b6bff882951b3ba3dd65bdacc915c2b532efc3f183aa44657205c6c337225" address := "0xb8d8c62d4a1999b7aea0aebBD5020244a4a9bAD8" publicKeySignature := "0x304c2ba4ae7fa71295bfc2920b9c1268d574d65531f1f4d2117fc1439a45310c37ab75085a9df2a4169a4d47982b330a4387b1ded0c8881b030629db30bbaf3a1c" addFromPublicKey, err := EthereumAddressFromPublicKey(publicKey) if err != nil || addFromPublicKey != address { t.Error( "Error when computing address from public key or address and public key don't match", ) } _, err = BTCPubKeyFromEthereumPublicKey(publicKey) if err != nil { t.Errorf("While building BTC public key from string: %s", err.Error()) } /* ethSig, err := secp256k1.Sign(hashedPublicKey, hexutil.MustDecode(privateKey)) if err != nil { t.Error(err.Error()) } println(hexutil.Encode(ethSig)) hash := sha3.NewLegacyKeccak256() _, err = hash.Write([]byte(publicKey)) if err != nil { t.Error(err.Error()) } sha3KeccakHash := hash.Sum(nil) */ valid, err := VerifyEthereumSignatureETH([]byte(publicKey), publicKeySignature, publicKey) if err != nil { t.Error(err.Error()) } if !valid { t.Errorf("Signer address don't match %s", addFromPublicKey) } } func TestFetchAICrypto(t *testing.T) { publicKey := "02358e3e42a6ba15cf6b2ba6eb05f02b8893acf82b316d7dd9cda702b0892b8c71" address := "fetch19dq2mkcpp6x0aypxt9c9gz6n4fqvax0x9a7t5r" peerPublicKey := "027af21aff853b9d9589867ea142b0a60a9611fc8e1fae04c2f7144113fa4e938e" pySigStrCanonize := "N/GOa7/m3HU8/gpLJ88VCQ6vXsdrfiiYcqnNtF+c2N9VG9ZIiycykN4hdbpbOCGrChMYZQA3G1GpozsShrUBgg==" addressFromPublicKey, _ := FetchAIAddressFromPublicKey(publicKey) if address != addressFromPublicKey { t.Error("[ERR] Addresses don't match") } else { t.Log("[OK] Agent address matches its public key") } valid, err := VerifyFetchAISignatureBTC( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using BTC don't match %s", err.Error()) } valid, err = VerifyFetchAISignatureLibp2p( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using LPP don't match %s", err.Error()) } } func TestSetLoggerLevel(t *testing.T) { assert.Equal(t, logger.GetLevel(), zerolog.Level(0), "Initial log level is not 0") lvl := zerolog.InfoLevel SetLoggerLevel(lvl) assert.Equal( t, logger.GetLevel(), lvl, "Waited for logger level %d but got %d", lvl, logger.GetLevel(), ) } func Example_ignore() { ignore(errors.New("Test")) // Output: IGNORED: Test } func TestNewDefaultLoggerWithFields(t *testing.T) { fields := map[string]string{ "test_field": "test_value", } var logBuffer bytes.Buffer logger := NewDefaultLoggerWithFields(fields).Output(&logBuffer) logger.Info().Msg("test") var jsonResult map[string]interface{} err := json.Unmarshal(logBuffer.Bytes(), &jsonResult) assert.Equal(t, nil, err) assert.Equal(t, jsonResult["test_field"], "test_value") } func TestComputeCID(t *testing.T) { address := "fetch19dq2mkcpp6x0aypxt9c9gz6n4fqvax0x9a7t5r" cid, err := ComputeCID(address) assert.Equal(t, nil, err) assert.Equal(t, "QmZ6ryKyS9rSnesX8YnFLAmFwFuRMdHpE7pQ2V6SjXTbqM", cid.String()) } func TestWriteBytes(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockStream := mocks.NewMockStream(mockCtrl) mockStream.EXPECT().Write([]byte{0, 0, 0, 5, 104, 101, 108, 108, 111}).Return(9, nil).Times(1) err := WriteBytes(mockStream, []byte("hello")) assert.Equal(t, nil, err) mockStream.EXPECT(). Write([]byte{0, 0, 0, 4, 104, 101, 108, 108}). Return(8, errors.New("oops")). Times(1) err = WriteBytes(mockStream, []byte("hell")) assert.NotEqual(t, err, nil) } func TestReadBytesConn(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockConn := mocks.NewMockConn(mockCtrl) mockConn.EXPECT().Read(gomock.Any()).Return(4, nil).Times(2) buf, err := ReadBytesConn(mockConn) assert.Equal(t, nil, err) assert.Equal(t, "", string(buf)) } func TestWriteBytesConn(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockConn := mocks.NewMockConn(mockCtrl) mockConn.EXPECT().Write(gomock.Any()).Return(0, nil).Times(1) err := WriteBytesConn(mockConn, []byte("ABC")) assert.Equal(t, nil, err) } func TestReadWriteEnvelopeFromConnection(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() defer monkey.UnpatchAll() address := "0xb8d8c62d4a1999b7aea0aebBD5020244a4a9bAD8" buffer := bytes.NewBuffer([]byte{}) mockConn := mocks.NewMockConn(mockCtrl) t.Run("TestWriteEnvelope", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(mockConn), "Write", func(_ *mocks.MockConn, b []byte) (int, error) { buffer.Write(b) return 0, nil }, ) err := WriteEnvelopeConn(mockConn, &aea.Envelope{ To: address, Sender: address, }) assert.Equal(t, nil, err) assert.NotEqual(t, 0, buffer) }) t.Run("TestReadEnvelope", func(t *testing.T) { monkey.Patch(ReadBytesConn, func(conn net.Conn) ([]byte, error) { return buffer.Bytes()[4:], nil }) env, err := ReadEnvelopeConn(mockConn) assert.Equal(t, nil, err) assert.Equal(t, address, env.To) }) } func TestGetPeersAddrInfo(t *testing.T) { addrs, err := GetPeersAddrInfo( []string{ "/dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW", }, ) assert.Equal(t, nil, err) assert.Equal(t, 1, len(addrs)) } func TestFetchAIPublicKeyFromPubKey(t *testing.T) { //(publicKey crypto.PubKey) (string, error) { _, pubKey, err := KeyPairFromFetchAIKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) key, err := FetchAIPublicKeyFromPubKey(pubKey) assert.Equal(t, nil, err) assert.Equal(t, "03b7e977f498dce004e2614764ff576e17cc6691135497e7bcb5d3441e816ba9e1", key) } func TestIDFromFetchAIPublicKey(t *testing.T) { _, pubKey, err := KeyPairFromFetchAIKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) key, err := FetchAIPublicKeyFromPubKey(pubKey) assert.Equal(t, nil, err) peerID, err := IDFromFetchAIPublicKey(key) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(peerID)) } func TestAgentAddressFromPublicKey(t *testing.T) { address, err := AgentAddressFromPublicKey( "fetchai", "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(address)) } func TestCosmosAddressFromPublicKey(t *testing.T) { address, err := CosmosAddressFromPublicKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(address)) } func TestFetchAIPublicKeyFromFetchAIPrivateKey(t *testing.T) { key, err := FetchAIPublicKeyFromFetchAIPrivateKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) assert.Equal(t, "03b7e977f498dce004e2614764ff576e17cc6691135497e7bcb5d3441e816ba9e1", key) } func TestIDFromFetchAIPublicKeyUncompressed(t *testing.T) { //bad pub key _, err := IDFromFetchAIPublicKeyUncompressed("some") assert.NotEqual(t, nil, err) // good pub key id, err := IDFromFetchAIPublicKeyUncompressed( "50863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6", ) assert.Equal(t, nil, err) assert.Equal( t, peer.ID( "\x00%\b\x02\x12!\x02P\x86:\xd6J\x87\xae\x8a/\xe8<\x1a\xf1\xa8@<\xb5?S\xe4\x86\xd8Q\x1d\xad\x8a\x04\x88~[#R", ), id, ) } func TestSignFetchAI(t *testing.T) { privKey := "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc" message := []byte("somebytes") _, pubKey, err := KeyPairFromFetchAIKey(privKey) assert.Equal(t, nil, err) fetchPubKey, err := FetchAIPublicKeyFromPubKey(pubKey) assert.Equal(t, nil, err) signature, err := SignFetchAI(message, privKey) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(signature)) isValid, err := VerifyLedgerSignature("fetchai", message, signature, fetchPubKey) assert.Equal(t, nil, err) assert.Equal(t, true, isValid) } func TestBootstrapConnect(t *testing.T) { ctx := context.Background() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() defer monkey.UnpatchAll() var ipfsdht *dht.IpfsDHT var routingTable *kb.RoutingTable mockPeerstore := mocks.NewMockPeerstore(mockCtrl) peers := make([]peer.AddrInfo, 2) var addrs []ma.Multiaddr peers[0] = peer.AddrInfo{ID: peer.ID("peer1"), Addrs: addrs} peers[1] = peer.AddrInfo{ID: peer.ID("peer2"), Addrs: addrs} mockHost := mocks.NewMockHost(mockCtrl) mockHost.EXPECT().ID().Return(peer.ID("host_id")).Times(2) mockHost.EXPECT().Peerstore().Return(mockPeerstore).Times(2) mockHost.EXPECT().Connect(gomock.Any(), gomock.Any()).Return(nil).Times(2) mockPeerstore.EXPECT().AddAddrs(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(2) t.Run("TestOk", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(routingTable), "Find", func(_ *kb.RoutingTable, _ peer.ID) peer.ID { return peer.ID("som peer") }, ) monkey.PatchInstanceMethod( reflect.TypeOf(ipfsdht), "RoutingTable", func(_ *dht.IpfsDHT) *kb.RoutingTable { return routingTable }, ) err := BootstrapConnect(ctx, mockHost, ipfsdht, peers) assert.Equal(t, nil, err) }) mockHost = mocks.NewMockHost(mockCtrl) mockHost.EXPECT().ID().Return(peer.ID("host_id")).Times(2) mockHost.EXPECT().Peerstore().Return(mockPeerstore).Times(2) mockHost.EXPECT().Connect(gomock.Any(), gomock.Any()).Return(nil).Times(2) mockPeerstore.EXPECT().AddAddrs(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(2) t.Run("Test_PeersNotAdded", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(routingTable), "Find", func(_ *kb.RoutingTable, _ peer.ID) peer.ID { return peer.ID("") }, ) monkey.PatchInstanceMethod( reflect.TypeOf(ipfsdht), "RoutingTable", func(_ *dht.IpfsDHT) *kb.RoutingTable { return routingTable }, ) err := BootstrapConnect(ctx, mockHost, ipfsdht, peers) assert.NotEqual(t, nil, err) assert.Contains(t, err.Error(), "timeout: entry peer haven't been added to DHT") }) mockHost = mocks.NewMockHost(mockCtrl) mockHost.EXPECT().ID().Return(peer.ID("host_id")).Times(2) mockHost.EXPECT().Peerstore().Return(mockPeerstore).Times(2) mockHost.EXPECT().Connect(gomock.Any(), gomock.Any()).Return(errors.New("some error")).Times(2) mockPeerstore.EXPECT().AddAddrs(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(2) t.Run("Test_PeersNotConnected", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(routingTable), "Find", func(_ *kb.RoutingTable, _ peer.ID) peer.ID { return peer.ID("") }, ) monkey.PatchInstanceMethod( reflect.TypeOf(ipfsdht), "RoutingTable", func(_ *dht.IpfsDHT) *kb.RoutingTable { return routingTable }, ) err := BootstrapConnect(ctx, mockHost, ipfsdht, peers) assert.NotEqual(t, nil, err) assert.Equal(t, "failed to bootstrap: some error", err.Error()) }) } ================================================ FILE: mkdocs.yml ================================================ site_name: AEA Framework Documentation site_url: https://docs.fetch.ai/aea site_description: Everything you need to know about the Autonomous Economic Agents. repo_url: https://github.com/fetchai/agents-aea repo_name: fetchai/agents-aea edit_uri: "" site_author: developer@fetch.ai copyright: Copyright © 2018 - 2023 Fetch.ai # Copyright notice in footer strict: true nav: - AEA Overview: - Overview: 'index.md' - Autonomous Economic Agents: 'aeas.md' - Application areas: 'application.md' - Agent ecosystem: 'ecosystem.md' - Installation and setup: - Installation: 'install.md' - Setting up: 'setup.md' - Development setup: 'development-setup.md' - Upgrading: 'upgrading.md' - Build an AEA on a Raspberry Pi: 'raspberry-set-up.md' - Getting started: - Core components: 'core-components.md' - Ways to build an AEA: 'step-one.md' - Build an AEA with the CLI: 'build-aea-step-by-step.md' - Architecture deep dives: - Language Agnostic Definition: 'language-agnostic-definition.md' - Architectural diagram: 'diagram.md' - Core components - Part 1: 'core-components-1.md' - Core components - Part 2: 'core-components-2.md' - Comparisons: - AEA and web frameworks: 'aea-vs-mvc.md' - 12-Factor app and AEAs: '12-factor.md' - Design principles: 'design-principles.md' - Developing Agents: - Modes of running an AEA: 'modes.md' - Multi agent manager: 'multi-agent-manager.md' - Logging: 'logging.md' - Deployment: 'deployment.md' - Debugging: 'debug.md' - Profiling: 'runtime-cost.md' - Performance benchmark: 'performance-benchmark.md' - Security Considerations: 'security.md' - Developing Packages: - File structure: 'package-imports.md' - Scaffolding packages: 'scaffolding.md' - Configurations: 'config.md' - Skills: - Skills: 'skill.md' - AEA quick start: 'quickstart.md' - Build your first skill - search & discovery: 'skill-guide.md' - Trade between two AEAs: 'generic-skills-step-by-step.md' - Testing Skills: 'skill-testing.md' - Protocols: - Protocols: 'protocol.md' - Message routing: 'message-routing.md' - Generating protocols: 'protocol-generator.md' - Connections: - Connections: 'connection.md' - Contracts: - Contracts: 'contract.md' - Contract deploy and interact: 'erc1155-skills.md' - Decision Maker: - Decision Maker: 'decision-maker.md' - Ledger & Crypto APIs: - Ledger & Crypto APIs: 'ledger-integration.md' - Ecosystem: - Agent Communication: - Agent Communication Network: 'acn.md' - ACN Internals: 'acn-internals.md' - Proof of Representation: 'por.md' - P2P Connection: 'p2p-connection.md' - Search & Discovery: - Simple OEF: 'simple-oef.md' - Defining Data Models: 'defining-data-models.md' - The Query Language: 'query-language.md' - SOEF Connection: 'simple-oef-usage.md' - Integrations: - Front-end integration: 'connect-a-frontend.md' - ORM integration: 'orm-integration.md' - Generic Storage: 'generic-storage.md' - Prometheus monitoring: 'prometheus.md' - Generating wealth: 'wealth.md' - Guides: - Agent-oriented development: 'agent-oriented-development.md' - Interaction protocols: 'interaction-protocol.md' - Concepts: - Identity: 'identity.md' - Trust minimisation: 'trust.md' - Development - Advanced: - Build an AEA programmatically: 'build-aea-programmatically.md' - CLI vs programmatic AEAs: 'cli-vs-programmatic-aeas.md' - AEAs vs agents: 'agent-vs-aea.md' - Use multiplexer stand-alone: 'multiplexer-standalone.md' - Create stand-alone transaction: 'standalone-transaction.md' - Create decision-maker transaction: 'decision-maker-transaction.md' - Demos: - Demos: 'demos.md' - Generic skills: 'generic-skills.md' - HTTP Connection: 'http-connection-and-skill.md' - Aries Cloud Agents Demo: 'aries-cloud-agent-demo.md' - Car park skills: 'car-park-skills.md' - Gym example: 'gym-example.md' - Gym skill: 'gym-skill.md' - ML skills: 'ml-skills.md' - Oracle skills: 'oracle-demo.md' - Aggregation skill: 'aggregation-demo.md' - TAC skills: 'tac-skills.md' - TAC skills ledger-based: 'tac-skills-contract.md' - Thermometer skills: 'thermometer-skills.md' - Weather skills: 'weather-skills.md' - Reference: - Command Line Interface: 'cli-commands.md' - API: - AbstractAgent: 'api/abstract_agent.md' - AEA: 'api/aea.md' - AEA Builder: 'api/aea_builder.md' - Agent: 'api/agent.md' - Agent Loop: 'api/agent_loop.md' - Common: 'api/common.md' - Exceptions: 'api/exceptions.md' - Launcher: 'api/launcher.md' - Multiplexer: 'api/multiplexer.md' - Runner: 'api/runner.md' - Runtime: 'api/runtime.md' - Components: - Base: 'api/components/base.md' - Loader: 'api/components/loader.md' - Utils: 'api/components/utils.md' - Configurations: - Base: 'api/configurations/base.md' - Constants: 'api/configurations/constants.md' - Data Types: 'api/configurations/data_types.md' - Loader: 'api/configurations/loader.md' - Manager: 'api/configurations/manager.md' - Pypi: 'api/configurations/pypi.md' - Utils: 'api/configurations/utils.md' - Validation: 'api/configurations/validation.md' - Connections: - Base: 'api/connections/base.md' - Context: 'api/context/base.md' - Contracts: - Base: 'api/contracts/base.md' - Crypto: - Base: 'api/crypto/base.md' - Helpers: 'api/crypto/helpers.md' - LedgerApis: 'api/crypto/ledger_apis.md' - Plugin: 'api/crypto/plugin.md' - Wallet: 'api/crypto/wallet.md' - Registries: - Base: 'api/crypto/registries/base.md' - Decision Maker: - Base: 'api/decision_maker/base.md' - Default: 'api/decision_maker/default.md' - GOP: 'api/decision_maker/gop.md' - Error Handler: - Base: 'api/error_handler/base.md' - Default: 'api/error_handler/default.md' - Helpers: - ACN: - Agent Record: 'api/helpers/acn/agent_record.md' - URI: 'api/helpers/acn/uri.md' - Async Friendly Queue: 'api/helpers/async_friendly_queue.md' - Async Utils: 'api/helpers/async_utils.md' - Base: 'api/helpers/base.md' - Constants: 'api/helpers/constants.md' - Env Vars: 'api/helpers/env_vars.md' - Exception Policy: 'api/helpers/exception_policy.md' - Exec Timeout: 'api/helpers/exec_timeout.md' - File IO: 'api/helpers/file_io.md' - File Lock: 'api/helpers/file_lock.md' - HttpRequests: 'api/helpers/http_requests.md' - Install Dependency: 'api/helpers/install_dependency.md' - IO: 'api/helpers/io.md' - IPFS: - Base: 'api/helpers/ipfs/base.md' - Utils: 'api/helpers/ipfs/utils.md' - Logging: 'api/helpers/logging.md' - MultiAddress: - Base: 'api/helpers/multiaddr/base.md' - MultipleExecutor: 'api/helpers/multiple_executor.md' - Pipe: 'api/helpers/pipe.md' - Preferences: - Base: 'api/helpers/preference_representations/base.md' - Profiling: 'api/helpers/profiling.md' - Search: - Generic: 'api/helpers/search/generic.md' - Models: 'api/helpers/search/models.md' - Serializers: 'api/helpers/serializers.md' - Storage: - GenericStorage: 'api/helpers/storage/generic_storage.md' - Backends: - Base: 'api/helpers/storage/backends/base.md' - Sqlite: 'api/helpers/storage/backends/sqlite.md' - Sym Link: 'api/helpers/sym_link.md' - Transaction: - Base: 'api/helpers/transaction/base.md' - Win32: 'api/helpers/win32.md' - YamlUtils: 'api/helpers/yaml_utils.md' - Identity: 'api/identity/base.md' - Mail: 'api/mail/base.md' - Manager: - Manager: 'api/manager/manager.md' - Project: 'api/manager/project.md' - Utils: 'api/manager/utils.md' - Protocols: - Base: 'api/protocols/base.md' - Dialogue: - Base: 'api/protocols/dialogue/base.md' - Generator: - Base: 'api/protocols/generator/base.md' - Common: 'api/protocols/generator/common.md' - Extract Specification: 'api/protocols/generator/extract_specification.md' - Validate: 'api/protocols/generator/validate.md' - Default Protocol: - Custom Types: 'api/protocols/default/custom_types.md' - Dialogues: 'api/protocols/default/dialogues.md' - Message: 'api/protocols/default/message.md' - Serialization: 'api/protocols/default/serialization.md' - Signing Protocol: - Custom Types: 'api/protocols/signing/custom_types.md' - Dialogues: 'api/protocols/signing/dialogues.md' - Message: 'api/protocols/signing/message.md' - Serialization: 'api/protocols/signing/serialization.md' - State Update Protocol: - Dialogues: 'api/protocols/state_update/dialogues.md' - Message: 'api/protocols/state_update/message.md' - Serialization: 'api/protocols/state_update/serialization.md' - Registries: - Base: 'api/registries/base.md' - Filter: 'api/registries/filter.md' - Resources: 'api/registries/resources.md' - Skills: - Base: 'api/skills/base.md' - Behaviors: 'api/skills/behaviours.md' - Task: 'api/skills/tasks.md' - Test Tools: - Constants: 'api/test_tools/constants.md' - Exceptions: 'api/test_tools/exceptions.md' - Generic: 'api/test_tools/generic.md' - Test Cases: 'api/test_tools/test_cases.md' - Test Contract: 'api/test_tools/test_contract.md' - Test Skill: 'api/test_tools/test_skill.md' - Plugins: - CLI: - IPFS: - API: 'api/plugins/aea_cli_ipfs/core.md' - Utils: 'api/plugins/aea_cli_ipfs/ipfs_utils.md' - Ledger: - Cosmos: - API: 'api/plugins/aea_ledger_cosmos/cosmos.md' - Ethereum: - API: 'api/plugins/aea_ledger_ethereum/ethereum.md' - Fetchai: - API: 'api/plugins/aea_ledger_fetchai/fetchai.md' - Helper: 'api/plugins/aea_ledger_fetchai/_cosmos.md' - Limitations of v1: 'limits.md' - Known limitations: 'known-limits.md' - Glossary: 'glossary.md' - Q&A: 'questions-and-answers.md' - Archives: - TAC external app: 'tac.md' theme: name: material language: en palette: # Set light/dark theme button next to the search bar - media: "(prefers-color-scheme: light)" scheme: default toggle: icon: material/weather-night name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate toggle: icon: material/weather-sunny name: Switch to light mode logo: assets/images/logo.png # Set Fetch Logo top left favicon: assets/images/favicon.ico # Set Fetch favicon icon: repo: fontawesome/brands/github features: - navigation.instant # Fast page loading - navigation.tracking # URL automatically updated with the currently active anchor # - navigation.tabs # - navigation.tabs.sticky # - navigation.sections # - navigation.expand # - navigation.indexes - navigation.top # Back-to-top button - navigation.footer # Add links to the previous and next page of the current page - search.suggest # Completion for the searched word (can be accepted with ->). - search.highlight # Highlight all occurrences after following a search result link - search.share # Show share button for copying deep link to the current search query and result - toc.follow # Follow the table of content - content.action.view # Shows button to "view the source of this page" - content.code.copy # Shows a button next to code blocks to copy the code into clipboard # custom_dir: docs/overrides # Uncomment to enable announcements bar at the top extra_css: - css/my-styles.css - css/admonitions.css markdown_extensions: - admonition # Required by admonitions - pymdownx.superfences: # Required by admonitions, annotations, tabs. Enables arbitrary nesting of code and content blocks custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.highlight: # Required by code blocks anchor_linenums: true - pymdownx.inlinehilite # Required by code blocks - pymdownx.snippets # Required by code blocks - pymdownx.superfences # Required by admonitions, code blocks - pymdownx.details # Required by admonitions, code blocks - attr_list # Required by annotations - md_in_html # Required by annotations - pymdownx.tabbed: # Required by tabs alternate_style: true - tables # # Required by tables - toc: permalink: true - def_list plugins: - search # Enables search extra: social: - icon: fontawesome/brands/twitter link: https://bit.ly/3oDuI3f name: fetch.ai on twitter - icon: fontawesome/brands/telegram link: https://t.me/fetch_ai name: fetch.ai on telegram - icon: fontawesome/brands/discord link: https://bit.ly/3ra5uMI name: fetch.ai on discord - icon: fontawesome/brands/github link: https://bit.ly/3AFCWxl name: fetch.ai on github - icon: fontawesome/brands/reddit link: https://bit.ly/30zS1Tg name: fetch.ai on reddit - icon: fontawesome/brands/youtube link: https://bit.ly/3DxFazs name: fetch.ai on youtube - icon: fontawesome/brands/linkedin link: https://bit.ly/3kNO70p name: fetch.ai on linkedin ================================================ FILE: packages/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Package registry for the AEA framework.""" ================================================ FILE: packages/fetchai/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Packages authored by 'fetchai'.""" ================================================ FILE: packages/fetchai/agents/aries_alice/README.md ================================================ # Aries Alice This agent represents the Alice actor in this demo. ## Description This agent is part of the Fetch.ai Aries demo. It simulates the Alice actor of the demo linked above. It uses its primary skill, the `fetchai/aries_alice` skill, to do the following: - Registers Alice on the `SOEF` service. - On receiving an invitation details from Faber AEA (see `fetchai/aries_faber` agent), it connects with an underlying Aries Cloud Agent (ACA) instance and executes an `accept-invitation` command. ## Links - AEA Aries Demo - Hyperledger Demo ================================================ FILE: packages/fetchai/agents/aries_alice/aea-config.yaml ================================================ agent_name: aries_alice author: fetchai version: 0.32.5 description: An AEA representing Alice in the Aries demo. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 - fetchai/webhook:0.20.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/aries_alice:0.26.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/aries_faber/README.md ================================================ # Aries Faber This agent represents the Faber actor in this demo. ## Description This agent is part of the Fetch.ai Aries demo. It simulates the Faber actor of the demo linked above. It uses its primary skill, the `fetchai/aries_faber` skill, to do the following: - Register a decentralised ID on a ledger. - Connects with an underlying Aries Cloud Agent (ACA) instance, and forwards the following instructions: - Register schema definition - Register credential definition - Create an invitation It then sends the invitation detail to the Alice agent (see `fetchai/aries_alice` agent) it finds via the `SOEF` service. ## Links - AEA Aries Demo - Hyperledger Demo ================================================ FILE: packages/fetchai/agents/aries_faber/aea-config.yaml ================================================ agent_name: aries_faber author: fetchai version: 0.32.5 description: An AEA representing Faber in the Aries demo. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 - fetchai/webhook:0.20.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/aries_faber:0.24.5 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/car_data_buyer/README.md ================================================ # Car Park Client This agent purchases information on available car parking spaces in a vicinity. ## Description This agent is part of the Fetch.ai car park demo. It uses its primary skill, the `fetchai/carpark_client` skill, to find an agent on the `SOEF` service that sells car park availability data in a vicinity. Once found, it requests this data, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. ## Links - Car Park Demo ================================================ FILE: packages/fetchai/agents/car_data_buyer/aea-config.yaml ================================================ agent_name: car_data_buyer author: fetchai version: 0.33.5 description: An agent which searches for an instance of a `car_detector` agent and attempts to purchase car park data from it. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/carpark_client:0.27.6 - fetchai/generic_buyer:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/car_detector/README.md ================================================ # Car Park Detector This agent sells information on the number of car parking spaces available in a given vicinity. ## Description This agent is part of the Fetch.ai car park demo. It uses its primary skill, the `fetchai/carpark_detection` skill, to register its car park availability data selling service on the `SOEF`. It can then be contacted by another agent (for example the `fetchai/carpark_client` agent) to provide its data. Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the data after receiving payment. ## Links - Car Park Demo ================================================ FILE: packages/fetchai/agents/car_detector/aea-config.yaml ================================================ agent_name: car_detector author: fetchai version: 0.32.5 description: An agent which sells car park data to instances of `car_data_buyer` agents. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/carpark_detection:0.27.6 - fetchai/generic_seller:0.28.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/coin_price_feed/README.md ================================================ # Coin Price Feed AEA An agent that fetches a coin price from an API and makes it available by http request. ## Description This agent uses the `fetchai/advanced_data_request` skill to fetch a coin price from an API, which is then logged and then made available by http request using the `fetchai/http_server` connection. The agent also exposes the number of price quote retrievals and incoming http requests to a local prometheus server. ================================================ FILE: packages/fetchai/agents/coin_price_feed/aea-config.yaml ================================================ agent_name: coin_price_feed author: fetchai version: 0.15.5 license: Apache-2.0 description: An AEA providing a coin price feed. aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/http_server:0.23.6 - fetchai/prometheus:0.9.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/prometheus:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/advanced_data_request:0.7.6 default_connection: fetchai/http_server:0.23.6 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/prometheus:1.1.7: fetchai/prometheus:0.9.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/http_server:0.23.6 type: connection config: api_spec_path: vendor/fetchai/skills/advanced_data_request/api_spec.yaml target_skill_id: fetchai/advanced_data_request:0.7.6 --- public_id: fetchai/advanced_data_request:0.7.6 type: skill models: advanced_data_request_model: args: url: https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd outputs: - name: price json_path: fetch-ai.usd use_http_server: true ================================================ FILE: packages/fetchai/agents/coin_price_oracle/README.md ================================================ # Coin Price Oracle AEA An agent that fetches a coin price from an API and makes it available by request to an oracle smart contract. ## Description This agent uses the `fetchai/simple_oracle` skill to deploy an oracle smart contract to a ledger and updates this contract with the latest value of a coin price fetched using the `fetchai/advanced_data_request` skill. ================================================ FILE: packages/fetchai/agents/coin_price_oracle/aea-config.yaml ================================================ agent_name: coin_price_oracle author: fetchai version: 0.17.6 license: Apache-2.0 description: An AEA providing a coin price oracle service. aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/ledger:0.21.5 - fetchai/prometheus:0.9.6 contracts: - fetchai/oracle:0.12.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/prometheus:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/advanced_data_request:0.7.6 - fetchai/simple_oracle:0.16.5 default_connection: fetchai/ledger:0.21.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/prometheus:1.1.7: fetchai/prometheus:0.9.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/advanced_data_request:0.7.6 type: skill models: advanced_data_request_model: args: url: https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd outputs: - name: price json_path: fetch-ai.usd --- public_id: fetchai/simple_oracle:0.16.5 type: skill models: strategy: args: ledger_id: fetchai oracle_value_name: price update_function: update_oracle_value ================================================ FILE: packages/fetchai/agents/coin_price_oracle_client/README.md ================================================ # Coin Price Oracle Client AEA An agent that deploys an oracle client contract that can request a coin price from an oracle contract. ## Description This agent uses the `fetchai/simple_oracle_client` skill to deploy an oracle client smart contract to a ledger and periodically calls the contract function that requests the latest value of a coin price from a deployed oracle smart contract. ================================================ FILE: packages/fetchai/agents/coin_price_oracle_client/aea-config.yaml ================================================ agent_name: coin_price_oracle_client author: fetchai version: 0.12.6 license: Apache-2.0 description: An AEA providing a coin price oracle client service. aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/ledger:0.21.5 contracts: - fetchai/fet_erc20:0.9.2 - fetchai/oracle_client:0.11.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/simple_oracle_client:0.13.5 default_connection: fetchai/ledger:0.21.5 default_ledger: fetchai required_ledgers: - fetchai - ethereum default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/simple_oracle_client:0.13.5 type: skill models: strategy: args: ledger_id: fetchai query_function: query_oracle_value ================================================ FILE: packages/fetchai/agents/confirmation_aea_aw1/README.md ================================================ # Confirmation AEA AW1 An agent that manages confirmation of registration for Agent World 1. ## Description This agent uses the `fetchai/simple_service_registration` skill to register itself on the sOEF. The `fetchai/confirmation_aw1` skill, is used to handle incoming registration requests, verifying the data contained in them and responding with an outcome (success/error). For successful registrations it transfers test-net wealth to the sender of the request. ## Links ================================================ FILE: packages/fetchai/agents/confirmation_aea_aw1/aea-config.yaml ================================================ agent_name: confirmation_aea_aw1 author: fetchai version: 0.20.5 description: This agent manages confirmation of registration for Agent World 1 license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: - fetchai/staking_erc20:0.10.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/register:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/confirmation_aw1:0.15.6 - fetchai/simple_service_registration:0.23.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: NOTSET filename: all.log logfile_error: class: logging.FileHandler formatter: extra level: ERROR filename: error.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - logfile_error - console level: DEBUG propagate: false dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised token_storage_path: /data/soef_key.txt --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: ethereum: address: null fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 --- public_id: fetchai/simple_service_registration:0.23.6 type: skill models: strategy: args: location: latitude: 51.5194 longitude: 0.127 service_data: key: registration_service value: aw1-registration --- public_id: fetchai/confirmation_aw1:0.15.6 type: skill models: registration_db: args: custom_path: /data/registration.db strategy: args: token_dispense_amount: 100000000000000000000 awx_aeas: [] ================================================ FILE: packages/fetchai/agents/confirmation_aea_aw2/README.md ================================================ # Confirmation AEA AW2 An agent that purchases information from other agents as specified in its configuration. It acts as the confirmation AEA in Agent World 2. ## Description This agent uses the `fetchai/confirmation_aw2` skill to purchase data from valid Agent World 2 participants. ## Links ================================================ FILE: packages/fetchai/agents/confirmation_aea_aw2/aea-config.yaml ================================================ agent_name: confirmation_aea_aw2 author: fetchai version: 0.18.5 description: This agent purchases information from other agents as specified in its configuration. It acts as the confirmation AEA in Agent World 2. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/confirmation_aw2:0.13.6 - fetchai/generic_buyer:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: NOTSET filename: all.log logfile_error: class: logging.FileHandler formatter: extra level: ERROR filename: error.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - logfile_error - console level: DEBUG propagate: false dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised token_storage_path: /data/soef_key.txt --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: ethereum: address: null fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 --- public_id: fetchai/confirmation_aw2:0.13.6 type: skill behaviours: search: args: search_interval: 30 models: registration_db: args: custom_path: /data/registration.db strategy: args: aw1_aea: null location: latitude: 51.5194 longitude: 0.127 max_tx_fee: 3550000000000000 max_unit_price: 20 mininum_hours_between_txs: 4 search_query: constraint_type: == search_key: seller_service search_value: weather_data search_radius: 50.0 service_id: weather_data ================================================ FILE: packages/fetchai/agents/confirmation_aea_aw3/README.md ================================================ # Confirmation AEA AW3 An agent that purchases information from other agents as specified in its configuration. It acts as the confirmation AEA in Agent World 3. ## Description This agent uses the `fetchai/confirmation_aw3` skill to purchase data from valid Agent World 3 participants. ## Links ================================================ FILE: packages/fetchai/agents/confirmation_aea_aw3/aea-config.yaml ================================================ agent_name: confirmation_aea_aw3 author: fetchai version: 0.16.5 description: This agent purchases information from other agents as specified in its configuration. It acts as the confirmation AEA in Agent World 3. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/http:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/confirmation_aw3:0.12.6 - fetchai/generic_buyer:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: NOTSET filename: all.log logfile_error: class: logging.FileHandler formatter: extra level: ERROR filename: error.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - logfile_error - console level: DEBUG propagate: false dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised token_storage_path: /data/soef_key.txt --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: ethereum: address: null fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 --- public_id: fetchai/confirmation_aw3:0.12.6 type: skill behaviours: search: args: search_interval: 900 models: registration_db: args: custom_path: /data/registration.db strategy: args: aw1_aea: null leaderboard_token: null leaderboard_url: null locations: berlin: latitude: 52.52 longitude: 13.405 london: latitude: 51.5074 longitude: -0.1278 san_francisco: latitude: 37.7749 longitude: -122.4194 shanghai: latitude: 31.2304 longitude: 121.4737 rome: latitude: 41.9028 longitude: 12.4964 rio_de_janeiro: latitude: -22.9068 longitude: -43.1729 sydney: latitude: -33.8688 longitude: 151.2093 delhi: latitude: 28.7041 longitude: 77.1025 tokyo: latitude: 35.6762 longitude: 139.6503 mexico_city: latitude: 19.4326 longitude: -99.1332 cairo: latitude: 30.0444 longitude: 31.2357 kinshasa: latitude: -4.4419 longitude: 15.2663 search_queries: weather: constraint_type: == search_key: seller_service search_value: weather_data mobility: constraint_type: == search_key: seller_service search_value: mobility_data search_radius: 50.0 ================================================ FILE: packages/fetchai/agents/confirmation_aea_aw5/aea-config.yaml ================================================ agent_name: confirmation_aea_aw5 author: fetchai version: 0.5.5 license: Apache-2.0 description: This agent manages confirmation of registration for Agent World 5 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: - fetchai/staking_erc20:0.10.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/register:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/confirmation_aw1:0.15.6 - fetchai/simple_service_registration:0.23.6 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: NOTSET filename: all.log logfile_error: class: logging.FileHandler formatter: extra level: ERROR filename: error.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - logfile_error - console level: DEBUG propagate: false dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 default_connection: fetchai/p2p_libp2p:0.27.5 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised token_storage_path: soef_key.txt --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: ethereum: address: null fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 --- public_id: fetchai/simple_service_registration:0.23.6 type: skill models: strategy: args: location: latitude: 51.5074 longitude: -0.1278 service_data: key: registration_service value: aw5-registration --- public_id: fetchai/confirmation_aw1:0.15.6 type: skill models: registration_db: args: custom_path: registration.db strategy: args: developer_handle_only: true token_dispense_amount: 100000000000000000000 awx_aeas: [] ================================================ FILE: packages/fetchai/agents/erc1155_client/README.md ================================================ # ERC1155 Client An agent that purchases data via a smart contract. ## Description This agent uses its primary skill, the `fetchai/erc1155_client` skill, to find an agent selling data on the `SOEF` service. Once found, it requests specific data, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount via a deployed smart contract and receives the data. ## Links - Contract Deployment Guide ================================================ FILE: packages/fetchai/agents/erc1155_client/aea-config.yaml ================================================ agent_name: erc1155_client author: fetchai version: 0.34.5 description: An AEA to interact with the ERC1155 deployer AEA license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/erc1155_client:0.29.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: ethereum required_ledgers: - fetchai - ethereum logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-cosmos: version: <2.0.0,>=1.0.0 aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: ethereum --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection cert_requests: - identifier: acn ledger_id: ethereum not_after: '2023-01-01' not_before: '2022-01-01' public_key: fetchai message_format: '{public_key}' save_path: .certs/conn_cert.txt ================================================ FILE: packages/fetchai/agents/erc1155_deployer/README.md ================================================ # ERC1155 Deployer An agent that deploys a smart contract sells data using it. ## Description This agent uses its primary skill, the `fetchai/erc1155_deploy` skill, to deploy a smart contract, create and mint tokens, and register its 'data-selling' service on the `SOEF`. It can then be contacted by another agent (for example the `fetchai/erc1155_client` agent) to provide specific data. Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the data after receiving payment via the deployed smart contract. ## Links - Contract Deployment Guide ================================================ FILE: packages/fetchai/agents/erc1155_deployer/aea-config.yaml ================================================ agent_name: erc1155_deployer author: fetchai version: 0.34.5 description: An AEA to deploy and interact with an ERC1155 license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/erc1155_deploy:0.31.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: ethereum required_ledgers: - fetchai - ethereum logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-cosmos: version: <2.0.0,>=1.0.0 aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: ethereum --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection cert_requests: - identifier: acn ledger_id: ethereum not_after: '2023-01-01' not_before: '2022-01-01' public_key: fetchai message_format: '{public_key}' save_path: .certs/conn_cert.txt ================================================ FILE: packages/fetchai/agents/error_test/README.md ================================================ # Error Test Agent to raise an error in special skill. ## Description For tests purposes only! ## Links ================================================ FILE: packages/fetchai/agents/error_test/aea-config.yaml ================================================ agent_name: error_test author: fetchai version: 0.1.1 description: A simple agent to test error raised in skill. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/stub:0.21.3 contracts: [] protocols: [] skills: - fetchai/error_test_skill:0.1.2 default_connection: fetchai/stub:0.21.3 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: {} connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/fipa_dummy_buyer/README.md ================================================ # FIPA Dummy buyer A sample agent for FIPA protocol interaction. ## Description Dummy agent that tries to communicate with FIPA seller agent over libp2p connection. ================================================ FILE: packages/fetchai/agents/fipa_dummy_buyer/aea-config.yaml ================================================ agent_name: fipa_dummy_buyer author: fetchai version: 0.5.5 description: Sample agent for FIPA interaction license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/p2p_libp2p:0.27.5 protocols: - fetchai/fipa:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/fipa_dummy_buyer:0.3.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai contracts: [] required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: {} connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW public_uri: null local_uri: 127.0.0.1:9000 ================================================ FILE: packages/fetchai/agents/generic_buyer/README.md ================================================ # Generic Buyer A generic agent for buying data. ## Description This agent uses its primary skill, the `fetchai/generic_buyer` skill, to find an agent selling data on the `SOEF` service. Once found, it requests specific data, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. ## Links - Generic Skills - Generic Skill Step by Step Guide ================================================ FILE: packages/fetchai/agents/generic_buyer/aea-config.yaml ================================================ agent_name: generic_buyer author: fetchai version: 0.30.5 description: The buyer AEA purchases the services offered by the seller AEA. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_buyer:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/generic_seller/README.md ================================================ # Generic Seller A generic agent for selling data. ## Description This agent uses its primary skill, the `fetchai/generic_seller` skill, to register its 'data-selling' service on the `SOEF`. It can then be contacted by another agent (for example the `fetchai/generic_buyer` agent) to provide specific data. Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the data after receiving payment. ## Links - Generic Skills - Generic Skill Step by Step Guide ================================================ FILE: packages/fetchai/agents/generic_seller/aea-config.yaml ================================================ agent_name: generic_seller author: fetchai version: 0.29.5 description: The seller AEA sells the services specified in the `skill.yaml` file and delivers them upon payment to the buyer. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_seller:0.28.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/gym_aea/README.md ================================================ # Gym This agent trains an RL algorithm using OpenAI Gym. ## Description This agent is part of the Fetch.ai Gym demo. It uses its primary skill, the `fetchai/gym` skill, to train a reinforcement learning (RL) algorithm using OpenAI Gym. It demonstrate how an RL agent can be wrapped inside a skill by decoupling the RL agent from the gym environment, allowing them to run in separate execution environments. ## Links - Gym Demo - Gym Example - OpenAI Gym ================================================ FILE: packages/fetchai/agents/gym_aea/aea-config.yaml ================================================ agent_name: gym_aea author: fetchai version: 0.26.5 description: The gym aea demos the interaction between a skill containing a RL agent and a gym connection. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/gym:0.20.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/gym:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/gym:0.21.6 default_connection: fetchai/gym:0.20.6 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: {} connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/hello_world/README.md ================================================ # Hello World Agent This is a "Hello World" agent! ## Description This agent uses its primary `fetchai/hello_world` skill to print `Hello World!` on the screen. The message to be printed can be configured to be any string. ================================================ FILE: packages/fetchai/agents/hello_world/aea-config.yaml ================================================ agent_name: hello_world author: fetchai version: 0.1.7 license: Apache-2.0 description: A hello world agent. An agent that prints a message on the screen. aea_version: '>=1.2.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/signing:1.1.7 skills: - fetchai/hello_world:0.1.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: {} connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 dependencies: aea-ledger-fetchai: {} default_connection: null --- public_id: fetchai/hello_world:0.1.5 type: skill behaviours: hello_world: args: message: Hello World! ================================================ FILE: packages/fetchai/agents/latest_block_feed/README.md ================================================ # Random Beacon Feed AEA An agent that fetches the latest block from the Fetch ledger. ## Description This agent uses the `fetchai/fetch_block` skill to get the latest block data from the fetch ledger. ================================================ FILE: packages/fetchai/agents/latest_block_feed/aea-config.yaml ================================================ agent_name: latest_block_feed author: fetchai version: 0.11.5 license: Apache-2.0 description: An agent that retrieves the latest block data from the Fetch ledger. aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/fetch_block:0.12.6 default_connection: fetchai/ledger:0.21.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: {} connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: fetchai: address: https://rest-dorado.fetch.ai:443 denom: atestfet chain_id: dorado-1 ================================================ FILE: packages/fetchai/agents/ml_data_provider/README.md ================================================ # ML Data Provider This agent sells ML data for training. ## Description This agent is part of the Fetch.ai ML skill demo. It uses its primary skill, the `fetchai/ml_data_provider` skill, to register its 'ML-data-selling' service on the `SOEF`. It can be contacted by another agent (for example the `fetchai/ml_train` agent) to provide specific data samples. Once such a request is made and the terms of trade are agreed by both agents, it delivers the data after receiving payment. ## Links - ML Demo ================================================ FILE: packages/fetchai/agents/ml_data_provider/aea-config.yaml ================================================ agent_name: ml_data_provider author: fetchai version: 0.32.5 description: An agent that sells data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/ml_trade:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_seller:0.28.6 - fetchai/ml_data_provider:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/ml_model_trainer/README.md ================================================ # ML Train This agent buys ML data for training. ## Description This skill is part of the Fetch.ai ML skill demo. It uses its primary skill, the `fetchai/ml_train` skill, to find an agent selling ML data on the `SOEF` service (for example a `fetchai/ml_data_provider` agent). Once found, it requests specific data samples. If both parties agree with the terms of trade, it pays the proposed amount and trains an ML model using the data bought. ## Links - ML Demo ================================================ FILE: packages/fetchai/agents/ml_model_trainer/aea-config.yaml ================================================ agent_name: ml_model_trainer author: fetchai version: 0.33.5 description: An agent buying data and training a model from it. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/ml_trade:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_buyer:0.27.6 - fetchai/ml_train:0.29.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/my_first_aea/README.md ================================================ # My First Agent This is the "Hello World" agent! ## Description This agent uses its primary skill, the `fetchai/echo` skill, to sends back the contents of any message it receives. ## Links - Quick Start - Programmatically Build an AEA ================================================ FILE: packages/fetchai/agents/my_first_aea/aea-config.yaml ================================================ agent_name: my_first_aea author: fetchai version: 0.28.5 description: A simple agent to demo the echo skill. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/stub:0.21.3 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/echo:0.20.6 default_connection: fetchai/stub:0.21.3 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: {} connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/registration_aea_aw1/README.md ================================================ # Registration AEA AW1 This is an agent to register for Agent World 1. ## Description This agent uses the `fetchai/simple_service_registration` skill to register information about itself on the sOEF. It uses the `fetchai/simple_service_search` skill to find the agent handling confirmation of registration in AW1. It uses the `fetchai/registration_aw1` skill to deal with the registration flow, including signing of messages. ## Links ================================================ FILE: packages/fetchai/agents/registration_aea_aw1/aea-config.yaml ================================================ agent_name: registration_aea_aw1 author: fetchai version: 0.18.5 description: This is an agent to register for Agent World 1. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/register:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/registration_aw1:0.13.6 - fetchai/simple_service_registration:0.23.6 - fetchai/simple_service_search:0.11.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: DEBUG filename: all.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - console level: DEBUG propagate: false dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/registration_aw1:0.13.6 type: skill models: strategy: args: ethereum_address: PUT_YOUR_ETHEREUM_ADDRESS_HERE developer_handle: PUT_YOUR_DEVELOPER_HANDLE_HERE signature_of_fetchai_address: PUT_YOUR_SIGNATURE_HERE tweet: PUT_THE_LINK_TO_YOUR_TWEET_HERE whitelist: - PUT_WHITELIST_ADDRESSES_HERE --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 --- public_id: fetchai/simple_service_registration:0.23.6 type: skill models: strategy: args: location: latitude: 51.5194 longitude: 0.127 service_data: key: agentworld-1 value: PUT_YOUR_DEVELOPER_HANDLE_HERE --- public_id: fetchai/simple_service_search:0.11.6 type: skill models: strategy: args: search_location: latitude: 51.5194 longitude: 0.127 search_query: constraint_type: == search_key: registration_service search_value: aw1-registration ================================================ FILE: packages/fetchai/agents/simple_aggregator/README.md ================================================ # Oracle Aggregator AEA An agent that aggregates individual observations for an oracle network ## Description This agent uses the `fetchai/oracle_aggregation` skill to find peers and aggregate values. ================================================ FILE: packages/fetchai/agents/simple_aggregator/aea-config.yaml ================================================ agent_name: simple_aggregator author: fetchai version: 0.5.5 license: Apache-2.0 description: AEA that aggregates observations aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/http_server:0.23.6 - fetchai/p2p_libp2p:0.27.5 - fetchai/prometheus:0.9.6 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/aggregation:0.2.7 - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/prometheus:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/advanced_data_request:0.7.6 - fetchai/simple_aggregation:0.3.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 default_routing: {} connection_private_key_paths: {} private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/advanced_data_request:0.7.6 type: skill models: advanced_data_request_model: args: decimals: 0 use_http_server: false --- public_id: fetchai/http_server:0.23.6 type: connection config: target_skill_id: fetchai/advanced_data_request:0.7.6 --- public_id: fetchai/simple_aggregation:0.3.6 type: skill models: strategy: args: aggregation_function: mean quantity_name: price search_query: search_value: generic_aggregation_service service_id: generic_aggregation_service ================================================ FILE: packages/fetchai/agents/simple_buyer_aw2/README.md ================================================ # Simple Buyer AW2 This is an agent that buys data in Agent World 2. ## Description This agent uses the `fetchai/buyer_aw2` skill to purchase data from other agents in Agent World 2. ================================================ FILE: packages/fetchai/agents/simple_buyer_aw2/aea-config.yaml ================================================ agent_name: simple_buyer_aw2 author: fetchai version: 0.18.5 license: Apache-2.0 description: This AEA buys data from a simple seller in Agent World 2. aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_buyer:0.27.6 - fetchai/simple_buyer:0.13.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: DEBUG filename: all.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - console level: DEBUG propagate: false dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/simple_buyer:0.13.6 type: skill models: strategy: args: location: latitude: 51.5194 longitude: 0.127 max_negotiations: 2 max_unit_price: 20 search_query: constraint_type: == search_key: seller_service search_value: weather_data search_radius: 50.0 service_id: weather_data --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 ================================================ FILE: packages/fetchai/agents/simple_buyer_aw5/aea-config.yaml ================================================ agent_name: simple_buyer_aw5 author: fetchai version: 0.5.5 license: Apache-2.0 description: This agent purchases information from other agents as specified in its configuration. It acts as the confirmation AEA in Agent World 5. aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/http:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/confirmation_aw3:0.12.6 - fetchai/generic_buyer:0.27.6 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: NOTSET filename: all.log logfile_error: class: logging.FileHandler formatter: extra level: ERROR filename: error.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - logfile_error - console level: DEBUG propagate: false dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 default_connection: fetchai/p2p_libp2p:0.27.5 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised token_storage_path: soef_key.txt --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: ethereum: address: null fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 --- public_id: fetchai/confirmation_aw3:0.12.6 type: skill behaviours: search: args: search_interval: 900 models: registration_db: args: custom_path: registration.db strategy: args: aw1_aea: null leaderboard_token: null leaderboard_url: null locations: berlin: latitude: 52.52 longitude: 13.405 london: latitude: 51.5074 longitude: -0.1278 san_francisco: latitude: 37.7749 longitude: -122.4194 shanghai: latitude: 31.2304 longitude: 121.4737 rome: latitude: 41.9028 longitude: 12.4964 rio_de_janeiro: latitude: -22.9068 longitude: -43.1729 sydney: latitude: -33.8688 longitude: 151.2093 delhi: latitude: 28.7041 longitude: 77.1025 tokyo: latitude: 35.6762 longitude: 139.6503 mexico_city: latitude: 19.4326 longitude: -99.1332 cairo: latitude: 30.0444 longitude: 31.2357 kinshasa: latitude: -4.4419 longitude: 15.2663 search_queries: weather: constraint_type: == search_key: seller_service search_value: weather_data mobility: constraint_type: == search_key: seller_service search_value: mobility_data search_radius: 50.0 ================================================ FILE: packages/fetchai/agents/simple_seller_aw2/README.md ================================================ # Simple Seller AW2 This is an agent that sells data in Agent World 2. ## Description This agent uses the `fetchai/simple_seller` skill to sell data to other agents in Agent World 2. ================================================ FILE: packages/fetchai/agents/simple_seller_aw2/aea-config.yaml ================================================ agent_name: simple_seller_aw2 author: fetchai version: 0.20.5 description: This AEA sells data to a simple buyer in Agent World 2. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/http:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_seller:0.28.6 - fetchai/simple_data_request:0.14.6 - fetchai/simple_seller:0.14.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: DEBUG filename: all.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - console level: DEBUG propagate: false private_key_paths: {} default_routing: fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/simple_seller:0.14.6 type: skill models: strategy: args: location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: weather_data service_id: weather_data shared_state_key: my_data unit_price: 10 --- public_id: fetchai/simple_data_request:0.14.6 type: skill behaviours: http_request: args: request_interval: 20 body: '' method: null url: null handlers: http: args: shared_state_key: my_data --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 ================================================ FILE: packages/fetchai/agents/simple_seller_aw5/aea-config.yaml ================================================ agent_name: simple_seller_aw5 author: fetchai version: 0.5.5 license: Apache-2.0 description: An agent that participates in Agent World 5 as a simple seller. aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/http:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/register:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_seller:0.28.6 - fetchai/registration_aw1:0.13.6 - fetchai/simple_data_request:0.14.6 - fetchai/simple_seller:0.14.6 - fetchai/simple_service_registration:0.23.6 - fetchai/simple_service_search:0.11.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai default_routing: fetchai/http:1.1.7: fetchai/http_client:0.24.6 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} private_key_paths: {} logging_config: disable_existing_loggers: false version: 1 formatters: standard: format: '[%(levelname)s]: %(message)s' extra: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: extra level: DEBUG filename: all.log console: class: logging.StreamHandler formatter: standard level: INFO loggers: aea: handlers: - logfile - console level: DEBUG propagate: false dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/registration_aw1:0.13.6 type: skill models: strategy: args: announce_termination_key: aw-registration developer_handle: PUT_YOUR_DEVELOPER_HANDLE_HERE developer_handle_only: true whitelist: - PUT_WHITELIST_ADDRESSES_HERE --- public_id: fetchai/simple_service_registration:0.23.6 type: skill models: strategy: args: location: latitude: 51.5194 longitude: 0.127 service_data: key: agentworld-5 value: PUT_YOUR_DEVELOPER_HANDLE_HERE --- public_id: fetchai/simple_service_search:0.11.6 type: skill models: strategy: args: search_location: latitude: 51.5074 longitude: -0.1278 search_query: constraint_type: == search_key: registration_service search_value: aw5-registration --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null public_uri: null entry_peers: - /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_testnet_incentivised --- public_id: fetchai/ledger:0.21.5 type: connection config: ledger_apis: fetchai: address: https://rest-dorado.fetch.ai:443 chain_id: dorado-1 --- public_id: fetchai/simple_seller:0.14.6 type: skill models: strategy: args: location: latitude: 51.5074 longitude: -0.1278 service_data: key: seller_service value: weather_data service_id: weather_data shared_state_key: my_data unit_price: 10 --- public_id: fetchai/simple_data_request:0.14.6 type: skill behaviours: http_request: args: request_interval: 20 body: '' method: null url: null lookup_termination_key: aw-registration handlers: http: args: shared_state_key: my_data ================================================ FILE: packages/fetchai/agents/simple_service_registration/README.md ================================================ # Simple Service Registration This agent registers and unregisters its service on the SOEF. ## Description This agent is used in the "Guide on Writing a Skill" section of the documentation. On start, it registers its service on the `SOEF`, and on termination it unregisters it. ## Links - Guide on Building a Skill ================================================ FILE: packages/fetchai/agents/simple_service_registration/aea-config.yaml ================================================ agent_name: simple_service_registration author: fetchai version: 0.32.5 description: A simple example of service registration. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/simple_service_registration:0.23.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/simple_service_search/README.md ================================================ # Simple Service Search This agent searches for a service on the SOEF. ## Description This agent is used in the "Guide on Writing a Skill" section of the documentation. On start, it starts repeatedly searching on the `SOEF`. ## Links - Guide on Building a Skill ================================================ FILE: packages/fetchai/agents/simple_service_search/aea-config.yaml ================================================ agent_name: simple_service_search author: fetchai version: 0.16.5 description: A simple example of service search. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/simple_service_search:0.11.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/tac_controller/README.md ================================================ # TAC Controller This is a controller agent for a Trading Agent Competition (TAC). ## Description This agent is part of the Fetch.ai TAC demo. It uses its `fetchai/tac_control` skill to manage the progression of a competition across various stages. ## Links - TAC Demo ================================================ FILE: packages/fetchai/agents/tac_controller/aea-config.yaml ================================================ agent_name: tac_controller author: fetchai version: 0.30.5 description: An AEA to manage an instance of the TAC (trading agent competition) license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 - fetchai/tac:1.1.7 skills: - fetchai/tac_control:0.25.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/tac_controller_contract/README.md ================================================ # TAC Controller Contract This is a controller agent for a Trading Agent Competition (TAC) using smart contracts. ## Description This agent is part of the Fetch.ai TAC demo. It uses its `fetchai/tac_control` skill to manage the progression of a competition across various stages, and uses its `fetchai/tac_control_contract` skill to create and maintain a smart contract (for example, deploying the contract, creating and minting tokens, etc). ## Links - TAC Demo ================================================ FILE: packages/fetchai/agents/tac_controller_contract/aea-config.yaml ================================================ agent_name: tac_controller_contract author: fetchai version: 0.32.5 description: An AEA to manage an instance of the TAC (trading agent competition) using an ERC1155 smart contract. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 - fetchai/tac:1.1.7 skills: - fetchai/tac_control:0.25.6 - fetchai/tac_control_contract:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai - ethereum logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection cert_requests: - identifier: acn ledger_id: fetchai not_after: '2023-01-01' not_before: '2022-01-01' public_key: fetchai message_format: '{public_key}' save_path: .certs/conn_cert.txt --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_misc --- public_id: fetchai/tac_control:0.25.6 type: skill is_abstract: true ================================================ FILE: packages/fetchai/agents/tac_participant/README.md ================================================ # TAC Participant This is an agent that participates in a Trading Agent Competition (TAC). ## Description This agent is part of the Fetch.ai TAC demo. It uses its `fetchai/tac_participation` skill to find and register with a TAC by communicating with a controller agent. It then uses its `fetchai/tac_negotiation` skill to find trading partners, negotiate deals and exchange goods with them. ## Links - TAC Demo ================================================ FILE: packages/fetchai/agents/tac_participant/aea-config.yaml ================================================ agent_name: tac_participant author: fetchai version: 0.32.5 description: An AEA to participate in the TAC (trading agent competition) license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 - fetchai/tac:1.1.7 skills: - fetchai/tac_negotiation:0.29.6 - fetchai/tac_participation:0.25.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} decision_maker_handler: dotted_path: aea.decision_maker.gop:DecisionMakerHandler file_path: null config: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/tac_participant_contract/README.md ================================================ # TAC Participant Contract This is an agent that participates in a Trading Agent Competition (TAC) using a smart contract. ## Description This agent is part of the Fetch.ai TAC demo. It uses its `fetchai/tac_participation` skill to find and register with a TAC by communicating with a controller agent. It then uses its `fetchai/tac_negotiation` skill to find trading partners, negotiate deals and exchange goods with them using a smart contract. ## Links - TAC Demo ================================================ FILE: packages/fetchai/agents/tac_participant_contract/aea-config.yaml ================================================ agent_name: tac_participant_contract author: fetchai version: 0.22.5 description: An AEA to participate in the TAC (trading agent competition) using an ERC1155 smart contract. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 - fetchai/tac:1.1.7 skills: - fetchai/tac_negotiation:0.29.6 - fetchai/tac_participation:0.25.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai - ethereum logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} decision_maker_handler: dotted_path: aea.decision_maker.gop:DecisionMakerHandler file_path: null config: {} dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection cert_requests: - identifier: acn ledger_id: fetchai not_after: '2023-01-01' not_before: '2022-01-01' public_key: fetchai message_format: '{public_key}' save_path: .certs/conn_cert.txt --- public_id: fetchai/soef:0.27.6 type: connection config: chain_identifier: fetchai_v2_misc --- public_id: fetchai/tac_participation:0.25.6 type: skill models: game: args: is_using_contract: true --- public_id: fetchai/tac_negotiation:0.29.6 type: skill models: strategy: args: is_contract_tx: true ================================================ FILE: packages/fetchai/agents/thermometer_aea/README.md ================================================ # Thermometer This agent sells thermometer data. ## Description This agent is part of the Fetch.ai thermometer demo. It uses its primary skill, the `fetchai/thermometer` skill, to register its 'thermometer-data-selling' service on the `SOEF`. It can be contacted by another agent (for example the `fetchai/thermometer_client` agent) to provide data from a thermometer reading. Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it reads data from a (real or fake) thermometer, and delivers it after receiving payment. ## Links - Thermometer Demo ================================================ FILE: packages/fetchai/agents/thermometer_aea/aea-config.yaml ================================================ agent_name: thermometer_aea author: fetchai version: 0.30.5 description: An AEA to represent a thermometer and sell temperature data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_seller:0.28.6 - fetchai/thermometer:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/thermometer_client/README.md ================================================ # Thermometer Client This agent buys thermometer data. ## Description This agent is part of the Fetch.ai thermometer demo. It uses its primary skill, the `fetchai/thermometer_client` skill, to find an agent selling thermometer data on the `SOEF` service. Once found, it requests data from a thermometer reading, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. ## Links - Thermometer Demo ================================================ FILE: packages/fetchai/agents/thermometer_client/aea-config.yaml ================================================ agent_name: thermometer_client author: fetchai version: 0.32.5 description: An AEA that purchases thermometer data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_buyer:0.27.6 - fetchai/thermometer_client:0.26.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/weather_client/README.md ================================================ # Weather Client This agent buys dummy weather data. ## Description This agent is part of the Fetch.ai weather demo. It uses its primary skill, the `fetchai/weather_client` skill, to find an agent selling weather data on the `SOEF` service. Once found, it requests weather data for specific dates, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. ## Links - Weather Demo ================================================ FILE: packages/fetchai/agents/weather_client/aea-config.yaml ================================================ agent_name: weather_client author: fetchai version: 0.33.5 description: This AEA purchases weather data from the weather station. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_buyer:0.27.6 - fetchai/weather_client:0.26.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/agents/weather_station/README.md ================================================ # Weather Station This agent sells dummy weather data. ## Description This agent is part of the Fetch.ai weather demo. It uses its primary skill, the `fetchai/weather_station` skill, to registers its 'weather-data-selling' service on the `SOEF`. This data comes from a database that is populated with dummy data from a weather station. It can be contacted by another agent (for example the `fetchai/weather_client` agent) to provide weather data for specific dates.. Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the weather data after receiving payment. ## Links - Weather Demo ================================================ FILE: packages/fetchai/agents/weather_station/aea-config.yaml ================================================ agent_name: weather_station author: fetchai version: 0.32.5 description: This AEA represents a weather station selling weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 - fetchai/p2p_libp2p:0.27.5 - fetchai/soef:0.27.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - fetchai/generic_seller:0.28.6 - fetchai/weather_station:0.27.6 default_connection: fetchai/p2p_libp2p:0.27.5 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} default_routing: fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 connection_private_key_paths: {} dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/connections/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the connection packages authored by Fetch.ai""" ================================================ FILE: packages/fetchai/connections/gym/README.md ================================================ # Gym connection Connection providing access to the gym interface () for training reinforcement learning systems. The connection wraps a gym and allows the AEA to interact with the gym interface via the `gym` protocol. ## Usage First, add the connection to your AEA project (`aea add connection fetchai/gym:0.20.6`). Then, update the `config` in `connection.yaml` by providing a dotted path to the gym module in the `env` field. ================================================ FILE: packages/fetchai/connections/gym/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the Gym connection.""" ================================================ FILE: packages/fetchai/connections/gym/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Gym connector and gym channel.""" import asyncio import logging from asyncio import CancelledError from asyncio.events import AbstractEventLoop from concurrent.futures.thread import ThreadPoolExecutor from typing import Any, Callable, Dict, Optional, Tuple, Union, cast import gym from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.exceptions import enforce from aea.helpers.base import locate from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.gym.dialogues import GymDialogue from packages.fetchai.protocols.gym.dialogues import GymDialogues as BaseGymDialogues from packages.fetchai.protocols.gym.message import GymMessage _default_logger = logging.getLogger("aea.packages.fetchai.connections.gym") PUBLIC_ID = PublicId.from_str("fetchai/gym:0.20.6") class GymDialogues(BaseGymDialogues): """The dialogues class keeps track of all gym dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The gym connection maintains the dialogue on behalf of the environment return GymDialogue.Role.ENVIRONMENT BaseGymDialogues.__init__( self, self_address=str(PUBLIC_ID), role_from_first_message=role_from_first_message, **kwargs, ) class GymChannel: """A wrapper of the gym environment.""" THREAD_POOL_SIZE = 3 def __init__(self, address: Address, gym_env: gym.Env): """Initialize a gym channel.""" self.address = address self.gym_env = gym_env self._loop: Optional[AbstractEventLoop] = None self._queue: Optional[asyncio.Queue] = None self._threaded_pool: ThreadPoolExecutor = ThreadPoolExecutor( self.THREAD_POOL_SIZE ) self.logger: Union[logging.Logger, logging.LoggerAdapter] = _default_logger self._dialogues = GymDialogues() def _get_message_and_dialogue( self, envelope: Envelope ) -> Tuple[GymMessage, Optional[GymDialogue]]: """ Get a message copy and dialogue related to this message. :param envelope: incoming envelope :return: Tuple[Message, Optional[Dialogue]] """ message = cast(GymMessage, envelope.message) dialogue = cast(GymDialogue, self._dialogues.update(message)) return message, dialogue @property def queue(self) -> asyncio.Queue: """Check queue is set and return queue.""" if self._queue is None: # pragma: nocover raise ValueError("Channel is not connected") return self._queue async def connect(self) -> None: """ Connect an address to the gym. :return: an asynchronous queue, that constitutes the communication channel. """ if self._queue: # pragma: nocover return None self._loop = asyncio.get_event_loop() self._queue = asyncio.Queue() async def send(self, envelope: Envelope) -> None: """ Process the envelopes to the gym. :param envelope: the envelope """ sender = envelope.sender self.logger.debug("Processing message from {}: {}".format(sender, envelope)) if envelope.protocol_specification_id != GymMessage.protocol_specification_id: raise ValueError("This protocol is not valid for gym.") await self.handle_gym_message(envelope) async def _run_in_executor( self, fn: Callable, *args: Any ) -> Tuple[Any, float, bool, Dict]: if self._loop is None: # pragma: nocover raise ValueError("Loop not set!") return await self._loop.run_in_executor(self._threaded_pool, fn, *args) async def handle_gym_message(self, envelope: Envelope) -> None: """ Forward a message to gym. :param envelope: the envelope """ enforce( isinstance(envelope.message, GymMessage), "Message not of type GymMessage" ) gym_message, dialogue = self._get_message_and_dialogue(envelope) if dialogue is None: self.logger.warning( "Could not create dialogue from message={}".format(gym_message) ) return if gym_message.performative == GymMessage.Performative.ACT: action = gym_message.action.any step_id = gym_message.step_id observation, reward, done, info = await self._run_in_executor( self.gym_env.step, action ) msg = dialogue.reply( performative=GymMessage.Performative.PERCEPT, target_message=gym_message, observation=GymMessage.AnyObject(observation), reward=reward, done=done, info=GymMessage.AnyObject(info), step_id=step_id, ) elif gym_message.performative == GymMessage.Performative.RESET: await self._run_in_executor(self.gym_env.reset) msg = dialogue.reply( performative=GymMessage.Performative.STATUS, target_message=gym_message, content={"reset": "success"}, ) elif gym_message.performative == GymMessage.Performative.CLOSE: await self._run_in_executor(self.gym_env.close) return envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self._send(envelope) async def _send(self, envelope: Envelope) -> None: """Send a message. :param envelope: the envelope """ await self.queue.put(envelope) async def disconnect(self) -> None: """Disconnect.""" if self._queue is not None: await self._queue.put(None) self._queue = None async def get(self) -> Optional[Envelope]: """Get incoming envelope.""" return await self.queue.get() class GymConnection(Connection): """Proxy to the functionality of the gym.""" connection_id = PUBLIC_ID def __init__(self, gym_env: Optional[gym.Env] = None, **kwargs: Any) -> None: """ Initialize a connection to a local gym environment. :param gym_env: the gym environment (this cannot be loaded by AEA loader). :param kwargs: the keyword arguments of the parent class. """ super().__init__(**kwargs) if gym_env is None: gym_env_package = cast(str, self.configuration.config.get("env")) if gym_env_package is None: # pragma: nocover raise ValueError("`env` must be set in configuration!") gym_env_class = locate(gym_env_package) gym_env = gym_env_class() self.channel = GymChannel(self.address, gym_env) self._connection = None # type: Optional[asyncio.Queue] async def connect(self) -> None: """Connect to the gym.""" if self.is_connected: # pragma: nocover return with self._connect_context(): self.channel.logger = self.logger await self.channel.connect() async def disconnect(self) -> None: """Disconnect from the gym.""" if self.is_disconnected: # pragma: nocover return self.state = ConnectionStates.disconnecting await self.channel.disconnect() self.state = ConnectionStates.disconnected async def send(self, envelope: Envelope) -> None: """ Send an envelope. :param envelope: the envelop """ self._ensure_connected() await self.channel.send(envelope) async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """Receive an envelope.""" self._ensure_connected() try: envelope = await self.channel.get() return envelope except CancelledError: # pragma: no cover return None ================================================ FILE: packages/fetchai/connections/gym/connection.yaml ================================================ name: gym author: fetchai version: 0.20.6 type: connection description: The gym connection wraps an OpenAI gym. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmbQw2EjS2WJYrmf1H7AJkdJfewXAWyYkfGqL1k9DBe1hs __init__.py: QmaxS1pbCJtyT7zjAamvEnwLyR1LdrHK6VDcfN45jFgwQH connection.py: QmP316GNMBHiuFuPhcgLHdwYsoH7ejwxCtGJSNodnDhyWa fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/gym:1.1.7 class_name: GymConnection config: env: '' excluded_protocols: [] restricted_to_protocols: - fetchai/gym:1.1.7 dependencies: gym: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/http_client/README.md ================================================ # HTTP client connection This connection wraps an HTTP client. It consumes messages from the AEA, translates them into HTTP requests, then sends the HTTP response as a message back to the AEA. ## Usage First, add the connection to your AEA project (`aea add connection fetchai/http_client:0.24.6`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. ================================================ FILE: packages/fetchai/connections/http_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the HTTP_client connection and channel.""" ================================================ FILE: packages/fetchai/connections/http_client/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """HTTP client connection and channel.""" import asyncio import email import logging import ssl from asyncio import CancelledError from asyncio.events import AbstractEventLoop from asyncio.tasks import Task from traceback import format_exc from typing import Any, Optional, Set, Tuple, cast import aiohttp import certifi # pylint: disable=wrong-import-order from aiohttp.client_reqrep import ClientResponse from multidict import CIMultiDict, CIMultiDictProxy from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.exceptions import enforce from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage SUCCESS = 200 NOT_FOUND = 404 REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.24.6") _default_logger = logging.getLogger("aea.packages.fetchai.connections.http_client") RequestId = str ssl_context = ssl.create_default_context(cafile=certifi.where()) def headers_to_string(headers: CIMultiDictProxy) -> str: """ Convert headers to string. :param headers: dict :return: str """ msg = email.message.Message() for name, value in headers.items(): msg.add_header(name, value) return msg.as_string() HttpDialogue = BaseHttpDialogue class HttpDialogues(BaseHttpDialogues): """The dialogues class keeps track of all http dialogues.""" def __init__(self) -> None: """Initialize dialogues.""" def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The client connection maintains the dialogue on behalf of the server return HttpDialogue.Role.SERVER BaseHttpDialogues.__init__( self, self_address=str(HTTPClientConnection.connection_id), role_from_first_message=role_from_first_message, dialogue_class=HttpDialogue, ) class HTTPClientAsyncChannel: """A wrapper for a HTTPClient.""" DEFAULT_TIMEOUT = 300 # default timeout in seconds DEFAULT_EXCEPTION_CODE = ( 600 # custom code to indicate there was exception during request ) def __init__( self, agent_address: Address, address: str, port: int, connection_id: PublicId, ): """ Initialize an http client channel. :param agent_address: the address of the agent. :param address: server hostname / IP address :param port: server port number :param connection_id: the id of the connection """ self.agent_address = agent_address self.address = address self.port = port self.connection_id = connection_id self._dialogues = HttpDialogues() self._in_queue = None # type: Optional[asyncio.Queue] # pragma: no cover self._loop = ( None ) # type: Optional[asyncio.AbstractEventLoop] # pragma: no cover self.is_stopped = True self._tasks: Set[Task] = set() self.logger = _default_logger self.logger.debug("Initialised the HTTP client channel") async def connect(self, loop: AbstractEventLoop) -> None: """ Connect channel using loop. :param loop: asyncio event loop to use """ self._loop = loop self._in_queue = asyncio.Queue() self.is_stopped = False def _get_message_and_dialogue( self, envelope: Envelope ) -> Tuple[HttpMessage, Optional[HttpDialogue]]: """ Get a message copy and dialogue related to this message. :param envelope: incoming envelope :return: Tuple[MEssage, Optional[Dialogue]] """ message = cast(HttpMessage, envelope.message) dialogue = cast(Optional[HttpDialogue], self._dialogues.update(message)) return message, dialogue async def _http_request_task(self, request_envelope: Envelope) -> None: """ Perform http request and send back response. :param request_envelope: request envelope. """ if not self._loop: # pragma: nocover raise ValueError("Channel is not connected") request_http_message, dialogue = self._get_message_and_dialogue( request_envelope ) if not dialogue: self.logger.warning( "Could not create dialogue for message={}".format(request_http_message) ) return try: resp = await asyncio.wait_for( self._perform_http_request(request_http_message), timeout=self.DEFAULT_TIMEOUT, ) envelope = self.to_envelope( request_http_message, status_code=resp.status, headers=resp.headers, status_text=resp.reason, body=resp._body # pylint: disable=protected-access if resp._body is not None # pylint: disable=protected-access else b"", dialogue=dialogue, ) except Exception: # pylint: disable=broad-except envelope = self.to_envelope( request_http_message, status_code=self.DEFAULT_EXCEPTION_CODE, headers=CIMultiDictProxy(CIMultiDict()), status_text="HTTPConnection request error.", body=format_exc().encode("utf-8"), dialogue=dialogue, ) if self._in_queue is not None: await self._in_queue.put(envelope) async def _perform_http_request( self, request_http_message: HttpMessage ) -> ClientResponse: """ Perform http request and return response. :param request_http_message: HttpMessage with http request constructed. :return: aiohttp.ClientResponse """ try: if request_http_message.is_set("headers") and request_http_message.headers: headers: Optional[dict] = dict( email.message_from_string(request_http_message.headers).items() ) else: headers = None async with aiohttp.ClientSession() as session: async with session.request( method=request_http_message.method, url=request_http_message.url, headers=headers, data=request_http_message.body, ssl=ssl_context, ) as resp: await resp.read() return resp except Exception: # pragma: nocover # pylint: disable=broad-except self.logger.exception( f"Exception raised during http call: {request_http_message.method} {request_http_message.url}" ) raise def send(self, request_envelope: Envelope) -> None: """ Send an envelope with http request data to request. Convert an http envelope into an http request. Send the http request Wait for and receive its response Translate the response into a response envelop. Send the response envelope to the in-queue. :param request_envelope: the envelope containing an http request """ if self._loop is None or self.is_stopped: raise ValueError("Can not send a message! Channel is not started!") if request_envelope is None: return enforce( isinstance(request_envelope.message, HttpMessage), "Message not of type HttpMessage", ) request_http_message = cast(HttpMessage, request_envelope.message) if ( request_http_message.performative != HttpMessage.Performative.REQUEST ): # pragma: nocover self.logger.warning( "The HTTPMessage performative must be a REQUEST. Envelop dropped." ) return task = self._loop.create_task(self._http_request_task(request_envelope)) task.add_done_callback(self._task_done_callback) self._tasks.add(task) def _task_done_callback(self, task: Task) -> None: """ Handle http request task completed. Removes tasks from _tasks. :param task: Task completed. """ self._tasks.remove(task) self.logger.debug(f"Task completed: {task}") async def get_message(self) -> Optional["Envelope"]: """ Get http response from in-queue. :return: None or envelope with http response. """ if self._in_queue is None: raise ValueError("Looks like channel is not connected!") try: return await self._in_queue.get() except CancelledError: # pragma: nocover return None @staticmethod def to_envelope( http_request_message: HttpMessage, status_code: int, headers: CIMultiDictProxy, status_text: Optional[Any], body: bytes, dialogue: HttpDialogue, ) -> Envelope: """ Convert an HTTP response object (from the 'requests' library) into an Envelope containing an HttpMessage (from the 'http' Protocol). :param http_request_message: the message of the http request envelop :param status_code: the http status code, int :param headers: dict of http response headers :param status_text: the http status_text, str :param body: bytes of http response content :param dialogue: the http dialogue :return: Envelope with http response data. """ http_message = dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_request_message, status_code=status_code, headers=headers_to_string(headers), status_text=status_text, body=body, version="", ) envelope = Envelope( to=http_message.to, sender=http_message.sender, message=http_message, ) return envelope async def _cancel_tasks(self) -> None: """Cancel all requests tasks pending.""" for task in list(self._tasks): if task.done(): # pragma: nocover continue task.cancel() for task in list(self._tasks): try: await task except KeyboardInterrupt: # pragma: nocover raise except BaseException: # pragma: nocover # pylint: disable=broad-except pass # nosec async def disconnect(self) -> None: """Disconnect.""" if not self.is_stopped: self.logger.info("HTTP Client has shutdown on port: {}.".format(self.port)) self.is_stopped = True await self._cancel_tasks() class HTTPClientConnection(Connection): """Proxy to the functionality of the web client.""" connection_id = PUBLIC_ID def __init__(self, **kwargs: Any) -> None: """ Initialize a HTTP client connection. :param kwargs: keyword arguments """ super().__init__(**kwargs) host = cast(str, self.configuration.config.get("host")) port = cast(int, self.configuration.config.get("port")) if host is None or port is None: # pragma: nocover raise ValueError("host and port must be set!") self.channel = HTTPClientAsyncChannel( self.address, host, port, connection_id=self.connection_id, ) async def connect(self) -> None: """Connect to a HTTP server.""" if self.is_connected: # pragma: nocover return with self._connect_context(): self.channel.logger = self.logger await self.channel.connect(self.loop) async def disconnect(self) -> None: """Disconnect from a HTTP server.""" if self.is_disconnected: return # pragma: nocover self.state = ConnectionStates.disconnecting await self.channel.disconnect() self.state = ConnectionStates.disconnected async def send(self, envelope: "Envelope") -> None: """ Send an envelope. :param envelope: the envelop """ self._ensure_connected() self.channel.send(envelope) async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ self._ensure_connected() try: return await self.channel.get_message() except Exception: # pragma: nocover # pylint: disable=broad-except self.logger.exception("Exception on receive") return None ================================================ FILE: packages/fetchai/connections/http_client/connection.yaml ================================================ name: http_client author: fetchai version: 0.24.6 type: connection description: The HTTP_client connection that wraps a web-based client connecting to a RESTful API specification. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmaAq5Ed75yQRZaSPWMksAC6JWTzvWfo1ustNjuMQBJ1WX __init__.py: QmNsJsfE93PYUGgj7GqndT4aRoZ39ruL9zF3MMrbFcuE9a connection.py: QmRPbv2Rhw7akb9BsNsZWFDfk9wChkEi1xxnWfqhFoTfsP fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/http:1.1.7 class_name: HTTPClientConnection config: host: 127.0.0.1 port: 8000 excluded_protocols: [] restricted_to_protocols: - fetchai/http:1.1.7 dependencies: aiohttp: version: <3.8,>=3.7.4 is_abstract: false ================================================ FILE: packages/fetchai/connections/http_server/README.md ================================================ # HTTP server connection This connection wraps an HTTP server. It consumes requests from clients, translates them into messages for the AEA, waits for a response message from the AEA, then serves the response to the client. ## Usage First, add the connection to your AEA project (`aea add connection fetchai/http_server:0.23.6`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. Optionally, provide a path to an [OpenAPI specification](https://swagger.io/docs/specification/about/) for request validation. ================================================ FILE: packages/fetchai/connections/http_server/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the HTTP connection and channel.""" ================================================ FILE: packages/fetchai/connections/http_server/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """HTTP server connection, channel, server, and handler.""" import asyncio import email import logging import ssl from abc import ABC, abstractmethod from asyncio import CancelledError from asyncio.events import AbstractEventLoop from asyncio.futures import Future from concurrent.futures._base import CancelledError as FuturesCancelledError from traceback import format_exc from typing import Any, Dict, Optional, cast from urllib.parse import parse_qs, urlparse from aiohttp import web from aiohttp.web_request import BaseRequest from openapi_core import create_spec from openapi_core.validation.request.datatypes import OpenAPIRequest, RequestParameters from openapi_core.validation.request.shortcuts import validate_request from openapi_core.validation.request.validators import RequestValidator from openapi_spec_validator.exceptions import ( # pylint: disable=wrong-import-order OpenAPIValidationError, ) from openapi_spec_validator.schemas import ( # pylint: disable=wrong-import-order read_yaml_file, ) from werkzeug.datastructures import ( # pylint: disable=wrong-import-order ImmutableMultiDict, ) from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel from packages.fetchai.protocols.http.dialogues import HttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage SUCCESS = 200 NOT_FOUND = 404 REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 _default_logger = logging.getLogger("aea.packages.fetchai.connections.http_server") RequestId = DialogueLabel PUBLIC_ID = PublicId.from_str("fetchai/http_server:0.23.6") class HttpDialogues(BaseHttpDialogues): """The dialogues class keeps track of all http dialogues.""" def __init__(self, self_address: Address, **kwargs: Any) -> None: """ Initialize dialogues. :param self_address: address of the dialogues maintainer. :param kwargs: keyword arguments. """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The server connection maintains the dialogue on behalf of the client return HttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, **kwargs, ) def headers_to_string(headers: Dict) -> str: """ Convert headers to string. :param headers: dict :return: str """ msg = email.message.Message() for name, value in headers.items(): msg.add_header(name, value) return msg.as_string() class Request(OpenAPIRequest): """Generic request object.""" @property def is_id_set(self) -> bool: """Check if id is set.""" return self._id is not None @property def id(self) -> RequestId: """Get the request id.""" return self._id @id.setter def id(self, request_id: RequestId) -> None: """Set the request id.""" self._id = request_id @classmethod async def create(cls, http_request: BaseRequest) -> "Request": """ Create a request. :param http_request: http_request :return: a request """ method = http_request.method.lower() parsed_path = urlparse(http_request.path_qs) url = http_request.url body = await http_request.read() mimetype = http_request.content_type query_params = parse_qs(parsed_path.query, keep_blank_values=True) parameters = RequestParameters( query=ImmutableMultiDict(query_params), # type: ignore header=headers_to_string(dict(http_request.headers)), path={}, ) request = Request( full_url_pattern=str(url), method=method, parameters=parameters, body=body, mimetype=mimetype, ) return request def to_envelope_and_set_id( self, dialogues: HttpDialogues, target_skill_id: PublicId, ) -> Envelope: """ Process incoming API request by packaging into Envelope and sending it in-queue. :param dialogues: the http dialogues :param target_skill_id: the target skill id :return: envelope """ url = self.full_url_pattern http_message, http_dialogue = dialogues.create( counterparty=str(target_skill_id), performative=HttpMessage.Performative.REQUEST, method=self.method, url=url, headers=self.parameters.header, body=self.body if self.body is not None else b"", version="", ) dialogue = cast(HttpDialogue, http_dialogue) self.id = dialogue.incomplete_dialogue_label envelope = Envelope( to=http_message.to, sender=http_message.sender, message=http_message, ) return envelope class Response(web.Response): """Generic response object.""" @classmethod def from_message(cls, http_message: HttpMessage) -> "Response": """ Turn an envelope into a response. :param http_message: the http_message :return: the response """ if http_message.performative == HttpMessage.Performative.RESPONSE: if http_message.is_set("headers") and http_message.headers: headers: Optional[dict] = dict( email.message_from_string(http_message.headers).items() ) else: headers = None # if content length header provided, it should correspond to actuyal body length if headers and "Content-Length" in headers: headers["Content-Length"] = str(len(http_message.body or "")) response = cls( status=http_message.status_code, reason=http_message.status_text, body=http_message.body, headers=headers, ) else: # pragma: nocover response = cls(status=SERVER_ERROR, text="Server error") return response class APISpec: """API Spec class to verify a request against an OpenAPI/Swagger spec.""" def __init__( self, api_spec_path: Optional[str] = None, server: Optional[str] = None, logger: logging.Logger = _default_logger, ): """ Initialize the API spec. :param api_spec_path: Directory API path and filename of the API spec YAML source file. :param server: the server url :param logger: the logger """ self._validator = None # type: Optional[RequestValidator] self.logger = logger if api_spec_path is not None: try: api_spec_dict = read_yaml_file(api_spec_path) if server is not None: api_spec_dict["servers"] = [{"url": server}] api_spec = create_spec(api_spec_dict) self._validator = RequestValidator(api_spec) except OpenAPIValidationError as e: # pragma: nocover self.logger.error( f"API specification YAML source file not correctly formatted: {str(e)}" ) except Exception: self.logger.exception( "API specification YAML source file not correctly formatted." ) raise def verify(self, request: Request) -> bool: """ Verify a http_method, url and param against the provided API spec. :param request: the request object :return: whether or not the request conforms with the API spec """ if self._validator is None: self.logger.debug("Skipping API verification!") return True try: validate_request(self._validator, request) except Exception: # pragma: nocover # pylint: disable=broad-except self.logger.exception("APISpec verify error") return False return True class BaseAsyncChannel(ABC): """Base asynchronous channel class.""" def __init__(self, address: Address, connection_id: PublicId) -> None: """ Initialize a channel. :param address: the address of the agent. :param connection_id: public id of connection using this channel. """ self._in_queue = None # type: Optional[asyncio.Queue] self._loop = None # type: Optional[asyncio.AbstractEventLoop] self.is_stopped = True self.address = address self.connection_id = connection_id @abstractmethod async def connect(self, loop: AbstractEventLoop) -> None: """ Connect. Upon HTTP Channel connection, start the HTTP Server in its own thread. :param loop: asyncio event loop """ self._loop = loop self._in_queue = asyncio.Queue() self.is_stopped = False async def get_message(self) -> Optional["Envelope"]: """ Get http response from in-queue. :return: None or envelope with http response. """ if self._in_queue is None: raise ValueError("Looks like channel is not connected!") try: return await self._in_queue.get() except CancelledError: # pragma: nocover return None @abstractmethod def send(self, envelope: Envelope) -> None: """ Send the envelope in_queue. :param envelope: the envelope """ @abstractmethod async def disconnect(self) -> None: """ Disconnect. Shut-off the HTTP Server. """ class HTTPChannel(BaseAsyncChannel): """A wrapper for an RESTful API with an internal HTTPServer.""" RESPONSE_TIMEOUT = 5.0 def __init__( self, address: Address, host: str, port: int, target_skill_id: PublicId, api_spec_path: Optional[str], connection_id: PublicId, timeout_window: float = RESPONSE_TIMEOUT, logger: logging.Logger = _default_logger, ssl_cert_path: Optional[str] = None, ssl_key_path: Optional[str] = None, ): """ Initialize a channel and process the initial API specification from the file path (if given). :param address: the address of the agent. :param host: RESTful API hostname / IP address :param port: RESTful API port number :param target_skill_id: the skill id which handles the requests :param api_spec_path: Directory API path and filename of the API spec YAML source file. :param connection_id: public id of connection using this channel. :param timeout_window: the timeout (in seconds) for a request to be handled. :param logger: the logger :param ssl_cert_path: optional path to ssl certificate :param ssl_key_path: optional path to ssl key """ super().__init__(address=address, connection_id=connection_id) self.host = host self.port = port self.ssl_cert_path = ssl_cert_path self.ssl_key_path = ssl_key_path self.target_skill_id = target_skill_id if self.ssl_cert_path and self.ssl_key_path: self.server_address = "https://{}:{}".format(self.host, self.port) else: self.server_address = "http://{}:{}".format(self.host, self.port) self._api_spec = APISpec(api_spec_path, self.server_address, logger) self.timeout_window = timeout_window self.http_server: Optional[web.TCPSite] = None self.pending_requests: Dict[RequestId, Future] = {} self._dialogues = HttpDialogues(str(HTTPServerConnection.connection_id)) self.logger = logger @property def api_spec(self) -> APISpec: """Get the api spec.""" return self._api_spec async def connect(self, loop: AbstractEventLoop) -> None: """ Connect. Upon HTTP Channel connection, start the HTTP Server in its own thread. :param loop: asyncio event loop """ if self.is_stopped: await super().connect(loop) try: await self._start_http_server() self.logger.info( "HTTP Server has connected to port: {}.".format(self.port) ) except Exception: # pragma: nocover # pylint: disable=broad-except self.is_stopped = True self._in_queue = None self.logger.exception( "Failed to start server on {}:{}.".format(self.host, self.port) ) async def _http_handler(self, http_request: BaseRequest) -> Response: """ Verify the request then send the request to Agent as an envelope. :param http_request: the request object :return: a tuple of response code and response description """ request = await Request.create(http_request) if self._in_queue is None: # pragma: nocover raise ValueError("Channel not connected!") is_valid_request = self.api_spec.verify(request) if not is_valid_request: self.logger.warning(f"request is not valid: {request}") return Response(status=NOT_FOUND, reason="Request Not Found") try: # turn request into envelope envelope = request.to_envelope_and_set_id( self._dialogues, self.target_skill_id ) self.pending_requests[request.id] = Future() # send the envelope to the agent's inbox (via self.in_queue) await self._in_queue.put(envelope) # wait for response envelope within given timeout window (self.timeout_window) to appear in dispatch_ready_envelopes response_message = await asyncio.wait_for( self.pending_requests[request.id], timeout=self.timeout_window, ) return Response.from_message(response_message) except asyncio.TimeoutError: self.logger.warning( f"Request timed out! Request={request} not handled as a result. Ensure requests (protocol_id={HttpMessage.protocol_id}) are handled by a skill!" ) return Response(status=REQUEST_TIMEOUT, reason="Request Timeout") except FuturesCancelledError: return Response( # pragma: nocover status=SERVER_ERROR, reason="Server terminated unexpectedly." ) except BaseException: # pragma: nocover # pylint: disable=broad-except self.logger.exception("Error during handling incoming request") return Response( status=SERVER_ERROR, reason="Server Error", text=format_exc() ) finally: if request.is_id_set: self.pending_requests.pop(request.id, None) async def _start_http_server(self) -> None: """Start http server.""" server = web.Server(self._http_handler) runner = web.ServerRunner(server) await runner.setup() ssl_context = None if self.ssl_cert_path and self.ssl_key_path: ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_context.load_cert_chain(self.ssl_cert_path, self.ssl_key_path) self.http_server = web.TCPSite( runner, self.host, self.port, ssl_context=ssl_context ) await self.http_server.start() def send(self, envelope: Envelope) -> None: """ Send the envelope in_queue. :param envelope: the envelope """ if self.http_server is None: # pragma: nocover raise ValueError("Server not connected, call connect first!") message = cast(HttpMessage, envelope.message) dialogue = self._dialogues.update(message) if dialogue is None: self.logger.warning( "Could not create dialogue for message={}".format(message) ) return future = self.pending_requests.pop(dialogue.incomplete_dialogue_label, None) if not future: self.logger.warning( "Dropping message={} for incomplete_dialogue_label={} which has timed out.".format( message, dialogue.incomplete_dialogue_label ) ) return if not future.done(): future.set_result(message) async def disconnect(self) -> None: """ Disconnect. Shut-off the HTTP Server. """ if self.http_server is None: # pragma: nocover raise ValueError("Server not connected, call connect first!") if not self.is_stopped: await self.http_server.stop() self.logger.info("HTTP Server has shutdown on port: {}.".format(self.port)) self.is_stopped = True self._in_queue = None class HTTPServerConnection(Connection): """Proxy to the functionality of the http server implementing a RESTful API specification.""" connection_id = PUBLIC_ID def __init__(self, **kwargs: Any) -> None: """Initialize a HTTP server connection.""" super().__init__(**kwargs) host = cast(Optional[str], self.configuration.config.get("host")) port = cast(Optional[int], self.configuration.config.get("port")) target_skill_id_ = cast( Optional[str], self.configuration.config.get("target_skill_id") ) if host is None or port is None or target_skill_id_ is None: # pragma: nocover raise ValueError("host and port and target_skill_id must be set!") target_skill_id = PublicId.try_from_str(target_skill_id_) if target_skill_id is None: # pragma: nocover raise ValueError("Provided target_skill_id is not a valid public id.") api_spec_path = cast( Optional[str], self.configuration.config.get("api_spec_path") ) ssl_cert_path = cast(Optional[str], self.configuration.config.get("ssl_cert")) ssl_key_path = cast(Optional[str], self.configuration.config.get("ssl_key")) if bool(ssl_cert_path) != bool(ssl_key_path): # pragma: nocover raise ValueError("Please specify both ssl_cert and ssl_key or neither.") self.channel = HTTPChannel( self.address, host, port, target_skill_id, api_spec_path, connection_id=self.connection_id, logger=self.logger, ssl_cert_path=ssl_cert_path, ssl_key_path=ssl_key_path, ) async def connect(self) -> None: """Connect to the http channel.""" if self.is_connected: return self.state = ConnectionStates.connecting self.channel.logger = self.logger await self.channel.connect(loop=self.loop) if self.channel.is_stopped: self.state = ConnectionStates.disconnected else: self.state = ConnectionStates.connected async def disconnect(self) -> None: """Disconnect from HTTP channel.""" if self.is_disconnected: return self.state = ConnectionStates.disconnecting await self.channel.disconnect() self.state = ConnectionStates.disconnected async def send(self, envelope: "Envelope") -> None: """ Send an envelope. :param envelope: the envelop """ self._ensure_connected() self.channel.send(envelope) async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ self._ensure_connected() try: return await self.channel.get_message() except CancelledError: # pragma: no cover return None ================================================ FILE: packages/fetchai/connections/http_server/connection.yaml ================================================ name: http_server author: fetchai version: 0.23.6 type: connection description: The HTTP server connection that wraps http server implementing a RESTful API specification. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmQKVp9nw9xvQa36AbBQzwrwvvLeuSEE1poy1GNw5egBXG __init__.py: QmPQbWNzuoYoaWQ35b4XUzGUZqwm2YYNg9XpngS4xqCdiZ connection.py: QmaQj91PXE5X9BZBBtei7JAKRWuxjHo8X8oewX5rFKU7VR fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/http:1.1.7 class_name: HTTPServerConnection config: api_spec_path: null host: 127.0.0.1 port: 8000 ssl_cert: null ssl_key: null target_skill_id: null excluded_protocols: [] restricted_to_protocols: - fetchai/http:1.1.7 dependencies: aiohttp: version: <3.8,>=3.7.4 openapi-core: version: ==0.13.2 openapi-spec-validator: version: ==0.2.8 is_abstract: false ================================================ FILE: packages/fetchai/connections/ledger/README.md ================================================ # Ledger connection The ledger connection wraps the APIs needed to interact with multiple ledgers, including smart contracts deployed on those ledgers. The AEA communicates with the ledger connection via the `fetchai/ledger_api:1.0.0` and `fetchai/contract_api:1.0.0` protocols. The connection uses the ledger APIs registered in the ledger API registry. ## Usage First, add the connection to your AEA project (`aea add connection fetchai/ledger:0.21.5`). Optionally, update the `ledger_apis` in `config` of `connection.yaml`. ================================================ FILE: packages/fetchai/connections/ledger/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Scaffold of a connection.""" ================================================ FILE: packages/fetchai/connections/ledger/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains base classes for the ledger API connection.""" import asyncio from abc import ABC, abstractmethod from asyncio import Task from concurrent.futures._base import Executor from logging import Logger from typing import Any, Callable, Dict, Optional, Union from aea.configurations.base import PublicId from aea.crypto.base import LedgerApi from aea.crypto.registries import Registry, ledger_apis_registry from aea.helpers.async_utils import AsyncState from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, Dialogues CONNECTION_ID = PublicId.from_str("fetchai/ledger:0.21.5") class RequestDispatcher(ABC): """Base class for a request dispatcher.""" TIMEOUT = 3 MAX_ATTEMPTS = 120 def __init__( self, logger: Logger, connection_state: AsyncState, loop: Optional[asyncio.AbstractEventLoop] = None, executor: Optional[Executor] = None, api_configs: Optional[Dict[str, Dict[str, str]]] = None, ): """ Initialize the request dispatcher. :param logger: the logger. :param connection_state: the connection state. :param loop: the asyncio loop. :param executor: an executor. :param api_configs: the configurations of the api. """ self.connection_state = connection_state self.loop = loop if loop is not None else asyncio.get_event_loop() self.executor = executor self._api_configs = api_configs self.logger = logger def api_config(self, ledger_id: str) -> Dict[str, str]: """Get api config.""" config = {} # type: Dict[str, str] if self._api_configs is not None and ledger_id in self._api_configs: config = self._api_configs[ledger_id] return config async def run_async( self, func: Callable[[Any], Task], api: LedgerApi, message: Message, dialogue: Dialogue, ) -> Union[Message, Task]: """ Run a function in executor. :param func: the function to execute. :param api: the ledger api. :param message: the message. :param dialogue: the dialogue. :return: the return value of the function. """ try: response = await self.loop.run_in_executor( self.executor, func, api, message, dialogue ) return response except Exception as e: # pylint: disable=broad-except return self.get_error_message(e, api, message, dialogue) def dispatch(self, envelope: Envelope) -> Task: """ Dispatch the request to the right sender handler. :param envelope: the envelope. :return: an awaitable. """ if not isinstance(envelope.message, Message): # pragma: nocover raise ValueError("Ledger connection expects non-serialized messages.") message = envelope.message ledger_id = self.get_ledger_id(message) api = self.ledger_api_registry.make(ledger_id, **self.api_config(ledger_id)) dialogue = self.dialogues.update(message) if dialogue is None: raise ValueError( # pragma: nocover "No dialogue created. Message={} not valid.".format(message) ) performative = message.performative handler = self.get_handler(performative) return self.loop.create_task(self.run_async(handler, api, message, dialogue)) def get_handler(self, performative: Any) -> Callable[[Any], Task]: """ Get the handler method, given the message performative. :param performative: the message performative. :return: the method that will send the request. """ handler = getattr(self, performative.value, None) if handler is None: raise Exception("Performative not recognized.") return handler @abstractmethod def get_error_message( self, e: Exception, api: LedgerApi, message: Message, dialogue: Dialogue, ) -> Message: """ Build an error message. :param e: the exception :param api: the ledger api :param message: the received message. :param dialogue: the dialogue. :return: an error message response. """ @property @abstractmethod def dialogues(self) -> Dialogues: """Get the dialogues.""" @property def ledger_api_registry(self) -> Registry: """Get the registry.""" return ledger_apis_registry @abstractmethod def get_ledger_id(self, message: Message) -> str: """Extract the ledger id from the message.""" ================================================ FILE: packages/fetchai/connections/ledger/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Scaffold connection and channel.""" import asyncio from asyncio import Task from collections import deque from typing import Any, Deque, Dict, List, Optional, cast from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope from aea.protocols.base import Message from packages.fetchai.connections.ledger.base import CONNECTION_ID, RequestDispatcher from packages.fetchai.connections.ledger.contract_dispatcher import ( ContractApiRequestDispatcher, ) from packages.fetchai.connections.ledger.ledger_dispatcher import ( LedgerApiRequestDispatcher, ) from packages.fetchai.protocols.contract_api import ContractApiMessage from packages.fetchai.protocols.ledger_api import LedgerApiMessage class LedgerConnection(Connection): """Proxy to the functionality of the SDK or API.""" connection_id = CONNECTION_ID def __init__(self, **kwargs: Any): """Initialize a connection to interact with a ledger APIs.""" super().__init__(**kwargs) self._ledger_dispatcher: Optional[LedgerApiRequestDispatcher] = None self._contract_dispatcher: Optional[ContractApiRequestDispatcher] = None self._event_new_receiving_task: Optional[asyncio.Event] = None self.receiving_tasks: List[asyncio.Future] = [] self.task_to_request: Dict[asyncio.Future, Envelope] = {} self.done_tasks: Deque[asyncio.Future] = deque() self.api_configs = self.configuration.config.get( "ledger_apis", {} ) # type: Dict[str, Dict[str, str]] @property def event_new_receiving_task(self) -> asyncio.Event: """Get the event to notify the 'receive' method of new receiving tasks.""" return cast(asyncio.Event, self._event_new_receiving_task) async def connect(self) -> None: """Set up the connection.""" if self.is_connected: # pragma: nocover return self.state = ConnectionStates.connecting self._ledger_dispatcher = LedgerApiRequestDispatcher( self._state, loop=self.loop, api_configs=self.api_configs, logger=self.logger, ) self._contract_dispatcher = ContractApiRequestDispatcher( self._state, loop=self.loop, api_configs=self.api_configs, logger=self.logger, ) self._event_new_receiving_task = asyncio.Event() self.state = ConnectionStates.connected async def disconnect(self) -> None: """Tear down the connection.""" if self.is_disconnected: # pragma: nocover return self.state = ConnectionStates.disconnecting for task in self.receiving_tasks: if not task.cancelled(): # pragma: nocover task.cancel() self._ledger_dispatcher = None self._contract_dispatcher = None self._event_new_receiving_task = None self.state = ConnectionStates.disconnected async def send(self, envelope: "Envelope") -> None: """ Send an envelope. :param envelope: the envelope to send. """ task = self._schedule_request(envelope) self.receiving_tasks.append(task) self.task_to_request[task] = envelope self.event_new_receiving_task.set() def _schedule_request(self, envelope: Envelope) -> Task: """ Schedule a ledger API request. :param envelope: the message. :return: task """ dispatcher: RequestDispatcher if ( envelope.protocol_specification_id == LedgerApiMessage.protocol_specification_id ): if self._ledger_dispatcher is None: # pragma: nocover raise ValueError("No ledger dispatcher set.") dispatcher = self._ledger_dispatcher elif ( envelope.protocol_specification_id == ContractApiMessage.protocol_specification_id ): if self._contract_dispatcher is None: # pragma: nocover raise ValueError("No contract dispatcher set.") dispatcher = self._contract_dispatcher else: raise ValueError("Protocol not supported") task = dispatcher.dispatch(envelope) return task async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. Blocking. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ # if there are done tasks, return the result if len(self.done_tasks) > 0: # pragma: nocover done_task = self.done_tasks.pop() return self._handle_done_task(done_task) if len(self.receiving_tasks) == 0: self.event_new_receiving_task.clear() await self.event_new_receiving_task.wait() # wait for completion of at least one receiving task done, _ = await asyncio.wait( self.receiving_tasks, return_when=asyncio.FIRST_COMPLETED ) # pick one done task done_task = done.pop() # update done tasks self.done_tasks.extend([*done]) return self._handle_done_task(done_task) def _handle_done_task(self, task: asyncio.Future) -> Optional[Envelope]: """ Process a done receiving task. :param task: the done task. :return: the response envelope. """ request = self.task_to_request.pop(task) self.receiving_tasks.remove(task) response_message: Optional[Message] = task.result() response_envelope = None if response_message is not None: response_envelope = Envelope( to=request.sender, sender=request.to, message=response_message, context=request.context, ) return response_envelope ================================================ FILE: packages/fetchai/connections/ledger/connection.yaml ================================================ name: ledger author: fetchai version: 0.21.5 type: connection description: A connection to interact with any ledger API and contract API. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmSFv3KfHuN5dhUU5bo6Eem5Df6Ph77GgSP8ZPgKQrRQHN __init__.py: QmaA7o9G1hT3fHtPDq6UYUyS5KY51uDkwMUGUc96odzSCX base.py: QmexgSRbx7AbPP1dfRuqLiU5BYwanYzmLMkQ9ebxHctGqG connection.py: Qmcu2SPDRdoT6jPAfuTAeEfugBppyEM4Ge5FM9hRNKNgzB contract_dispatcher.py: QmaQjpMMUNZXGXUavhofVnaXCQAte7hj4zCmvhWzPPkc5V ledger_dispatcher.py: QmQXRSCdQiYqdb7vX7S95Bhpr5V6sX15fb4f2gzQzvW148 fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/contract_api:1.1.7 - fetchai/ledger_api:1.1.7 class_name: LedgerConnection config: ledger_apis: ethereum: address: http://127.0.0.1:8545 gas_price_api_key: null fetchai: address: https://rest-dorado.fetch.ai:443 denom: atestfet chain_id: dorado-1 excluded_protocols: [] restricted_to_protocols: - fetchai/contract_api:1.1.7 - fetchai/ledger_api:1.1.7 dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/ledger/contract_dispatcher.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the contract API request dispatcher.""" import inspect import logging from collections.abc import Mapping from typing import Any, Callable, Optional, Union, cast from aea.common import JSONLike from aea.contracts import Contract, contract_registry from aea.crypto.base import LedgerApi from aea.crypto.registries import Registry from aea.exceptions import AEAException from aea.helpers.transaction.base import RawMessage, RawTransaction, State from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import Dialogues as BaseDialogues from packages.fetchai.connections.ledger.base import CONNECTION_ID, RequestDispatcher from packages.fetchai.protocols.contract_api import ContractApiMessage from packages.fetchai.protocols.contract_api.dialogues import ContractApiDialogue from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) _default_logger = logging.getLogger( "aea.packages.fetchai.connections.ledger.contract_dispatcher" ) class ContractApiDialogues(BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The ledger connection maintains the dialogue on behalf of the ledger return ContractApiDialogue.Role.LEDGER BaseContractApiDialogues.__init__( self, self_address=str(CONNECTION_ID), role_from_first_message=role_from_first_message, **kwargs, ) class ContractApiRequestDispatcher(RequestDispatcher): """Implement the contract API request dispatcher.""" def __init__(self, *args: Any, **kwargs: Any) -> None: """Initialize the dispatcher.""" logger = kwargs.pop("logger", None) logger = logger if logger is not None else _default_logger super().__init__(logger, *args, **kwargs) self._contract_api_dialogues = ContractApiDialogues() @property def dialogues(self) -> BaseDialogues: """Get the dialogues.""" return self._contract_api_dialogues @property def contract_registry(self) -> Registry[Contract]: """Get the contract registry.""" return contract_registry def get_ledger_id(self, message: Message) -> str: """Get the ledger id.""" if not isinstance(message, ContractApiMessage): # pragma: nocover raise ValueError("argument is not a ContractApiMessage instance.") message = cast(ContractApiMessage, message) return message.ledger_id def get_error_message( self, e: Exception, api: LedgerApi, message: Message, dialogue: BaseDialogue, ) -> ContractApiMessage: """ Build an error message. :param e: the exception. :param api: the Ledger API. :param message: the request message. :param dialogue: the dialogue :return: an error message response. """ response = cast( ContractApiMessage, dialogue.reply( performative=ContractApiMessage.Performative.ERROR, target_message=message, code=500, message=str(e), data=b"", ), ) return response def dispatch_request( self, ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, response_builder: Callable[ [Union[bytes, JSONLike], ContractApiDialogue], ContractApiMessage ], ) -> ContractApiMessage: """ Dispatch a request to a user-defined contract method. :param ledger_api: the ledger apis. :param message: the contract API request message. :param dialogue: the contract API dialogue. :param response_builder: callable that from bytes builds a contract API message. :return: the response message. """ contract = self.contract_registry.make(message.contract_id) try: data = self._get_data(ledger_api, message, contract) response = response_builder(data, dialogue) except AEAException as e: self.logger.error(f"Exception during contract request: {str(e)}") response = self.get_error_message(e, ledger_api, message, dialogue) except Exception as e: # pylint: disable=broad-except # pragma: nocover self.logger.error( f"An error occurred while processing the contract api request: '{str(e)}'." ) response = self.get_error_message(e, ledger_api, message, dialogue) return response def get_state( self, ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_state'. :param ledger_api: the API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: the contract api message """ def build_response( data: Union[bytes, JSONLike], dialogue: ContractApiDialogue ) -> ContractApiMessage: if not isinstance(data, Mapping): raise ValueError( f"Invalid state type, got={type(data)}, expected={JSONLike}." ) return cast( ContractApiMessage, dialogue.reply( performative=ContractApiMessage.Performative.STATE, state=State(message.ledger_id, data), ), ) return self.dispatch_request(ledger_api, message, dialogue, build_response) def get_deploy_transaction( self, ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_raw_transaction'. :param ledger_api: the API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: the contract api message """ def build_response( tx: Union[bytes, JSONLike], dialogue: ContractApiDialogue ) -> ContractApiMessage: if not isinstance(tx, Mapping): raise ValueError( f"Invalid transaction type, got={type(tx)}, expected={JSONLike}." ) return cast( ContractApiMessage, dialogue.reply( performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=RawTransaction(message.ledger_id, tx), ), ) return self.dispatch_request(ledger_api, message, dialogue, build_response) def get_raw_transaction( self, ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_raw_transaction'. :param ledger_api: the API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: the contract api message """ def build_response( tx: Union[bytes, JSONLike], dialogue: ContractApiDialogue ) -> ContractApiMessage: if isinstance(tx, bytes): raise ValueError( f"Invalid transaction type, got={type(tx)}, expected={JSONLike}." ) return cast( ContractApiMessage, dialogue.reply( performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=RawTransaction(message.ledger_id, tx), ), ) return self.dispatch_request(ledger_api, message, dialogue, build_response) def get_raw_message( self, ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_raw_message'. :param ledger_api: the ledger API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: the contract api message """ def build_response( rm: Union[bytes, JSONLike], dialogue: ContractApiDialogue ) -> ContractApiMessage: if not isinstance(rm, bytes): raise ValueError( f"Invalid message type, got={type(rm)}, expected=bytes." ) return cast( ContractApiMessage, dialogue.reply( performative=ContractApiMessage.Performative.RAW_MESSAGE, raw_message=RawMessage(message.ledger_id, rm), ), ) return self.dispatch_request(ledger_api, message, dialogue, build_response) def _get_data( self, api: LedgerApi, message: ContractApiMessage, contract: Contract, ) -> Union[bytes, JSONLike]: """Get the data from the contract method, either from the stub or from the callable specified by the message.""" # first, check if the custom handler for this type of request has been implemented. data = self._call_stub(api, message, contract) if data is not None: return data # then, check if there is the handler for the provided callable. data = self._validate_and_call_callable(api, message, contract) return data @staticmethod def _call_stub( ledger_api: LedgerApi, message: ContractApiMessage, contract: Contract ) -> Optional[Union[bytes, JSONLike]]: """Try to call stub methods associated to the contract API request performative.""" try: method: Callable = getattr(contract, message.performative.value) if message.performative in [ ContractApiMessage.Performative.GET_STATE, ContractApiMessage.Performative.GET_RAW_MESSAGE, ContractApiMessage.Performative.GET_RAW_TRANSACTION, ]: args, kwargs = ( [ledger_api, message.contract_address], message.kwargs.body, ) elif message.performative in [ # pragma: nocover ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ]: args, kwargs = [ledger_api], message.kwargs.body else: # pragma: nocover raise AEAException(f"Unexpected performative: {message.performative}") data = method(*args, **kwargs) return data except (AttributeError, NotImplementedError): return None @staticmethod def _validate_and_call_callable( api: LedgerApi, message: ContractApiMessage, contract: Contract ) -> Union[bytes, JSONLike]: """ Validate a Contract callable, given the performative. In particular: - if the performative is either 'get_state' or 'get_raw_transaction', the signature must accept ledger api as first argument and contract address as second argument, plus keyword arguments. - if the performative is either 'get_deploy_transaction' or 'get_raw_message', the signature must accept ledger api as first argument, plus keyword arguments. :param api: the ledger api object. :param message: the contract api request. :param contract: the contract instance. :return: the data generated by the method. """ try: method_to_call = getattr(contract, message.callable) except AttributeError: raise AEAException( f"Cannot find {message.callable} in contract {type(contract)}" ) full_args_spec = inspect.getfullargspec(method_to_call) if message.performative in [ ContractApiMessage.Performative.GET_STATE, ContractApiMessage.Performative.GET_RAW_MESSAGE, ContractApiMessage.Performative.GET_RAW_TRANSACTION, ]: if len(full_args_spec.args) < 2: raise AEAException( f"Expected two or more positional arguments, got {len(full_args_spec.args)}" ) return method_to_call(api, message.contract_address, **message.kwargs.body) if message.performative in [ ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ]: if len(full_args_spec.args) < 1: raise AEAException( f"Expected one or more positional arguments, got {len(full_args_spec.args)}" ) return method_to_call(api, **message.kwargs.body) raise AEAException( # pragma: nocover f"Unexpected performative: {message.performative}" ) ================================================ FILE: packages/fetchai/connections/ledger/ledger_dispatcher.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the ledger API request dispatcher.""" import logging import time from typing import Any, cast from aea.connections.base import ConnectionStates from aea.crypto.base import LedgerApi from aea.helpers.transaction.base import RawTransaction, State, TransactionDigest from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import Dialogues as BaseDialogues from packages.fetchai.connections.ledger.base import CONNECTION_ID, RequestDispatcher from packages.fetchai.protocols.ledger_api.custom_types import TransactionReceipt from packages.fetchai.protocols.ledger_api.dialogues import LedgerApiDialogue from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage _default_logger = logging.getLogger( "aea.packages.fetchai.connections.ledger.ledger_dispatcher" ) class LedgerApiDialogues(BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The ledger connection maintains the dialogue on behalf of the ledger return LedgerApiDialogue.Role.LEDGER BaseLedgerApiDialogues.__init__( self, self_address=str(CONNECTION_ID), role_from_first_message=role_from_first_message, **kwargs, ) class LedgerApiRequestDispatcher(RequestDispatcher): """Implement ledger API request dispatcher.""" def __init__(self, *args: Any, **kwargs: Any) -> None: """Initialize the dispatcher.""" logger = kwargs.pop("logger", None) logger = logger if logger is not None else _default_logger super().__init__(logger, *args, **kwargs) self._ledger_api_dialogues = LedgerApiDialogues() def get_ledger_id(self, message: Message) -> str: """Get the ledger id from message.""" if not isinstance(message, LedgerApiMessage): # pragma: nocover raise ValueError("argument is not a LedgerApiMessage instance.") message = cast(LedgerApiMessage, message) if message.performative is LedgerApiMessage.Performative.GET_RAW_TRANSACTION: ledger_id = message.terms.ledger_id elif ( message.performative is LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION ): ledger_id = message.signed_transaction.ledger_id elif ( message.performative is LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT ): ledger_id = message.transaction_digest.ledger_id else: ledger_id = message.ledger_id return ledger_id @property def dialogues(self) -> BaseDialogues: """Get the dialogues.""" return self._ledger_api_dialogues def get_balance( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_balance'. :param api: the API object. :param message: the Ledger API message :param dialogue: the dialogue :return: the ledger api message """ balance = api.get_balance(message.address) if balance is None: response = self.get_error_message( ValueError("No balance returned"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.BALANCE, target_message=message, balance=balance, ledger_id=message.ledger_id, ), ) return response def get_state( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_state'. :param api: the API object. :param message: the Ledger API message :param dialogue: the dialogue :return: the ledger api message """ result = api.get_state(message.callable, *message.args, **message.kwargs.body) if result is None: # pragma: nocover response = self.get_error_message( ValueError("Failed to get state"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.STATE, target_message=message, state=State(message.ledger_id, result), ledger_id=message.ledger_id, ), ) return response def get_raw_transaction( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_raw_transaction'. :param api: the API object. :param message: the Ledger API message :param dialogue: the dialogue :return: the ledger api message """ raw_transaction = api.get_transfer_transaction( sender_address=message.terms.sender_address, destination_address=message.terms.counterparty_address, amount=message.terms.sender_payable_amount, tx_fee=message.terms.fee, tx_nonce=message.terms.nonce, **message.terms.kwargs, ) if raw_transaction is None: response = self.get_error_message( ValueError("No raw transaction returned"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.RAW_TRANSACTION, target_message=message, raw_transaction=RawTransaction( message.terms.ledger_id, raw_transaction ), ), ) return response def get_transaction_receipt( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_transaction_receipt'. :param api: the API object. :param message: the Ledger API message :param dialogue: the dialogue :return: the ledger api message """ is_settled = False attempts = 0 while ( not is_settled and attempts < self.MAX_ATTEMPTS and self.connection_state.get() == ConnectionStates.connected ): time.sleep(self.TIMEOUT) transaction_receipt = api.get_transaction_receipt( message.transaction_digest.body ) if transaction_receipt is not None: is_settled = api.is_transaction_settled(transaction_receipt) attempts += 1 attempts = 0 transaction = api.get_transaction(message.transaction_digest.body) while ( transaction is None and attempts < self.MAX_ATTEMPTS and self.connection_state.get() == ConnectionStates.connected ): time.sleep(self.TIMEOUT) transaction = api.get_transaction(message.transaction_digest.body) attempts += 1 if not is_settled: # pragma: nocover response = self.get_error_message( ValueError("Transaction not settled within timeout"), api, message, dialogue, ) elif transaction_receipt is None: # pragma: nocover response = self.get_error_message( ValueError("No transaction_receipt returned"), api, message, dialogue ) elif transaction is None: # pragma: nocover response = self.get_error_message( ValueError("No transaction returned"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, target_message=message, transaction_receipt=TransactionReceipt( message.transaction_digest.ledger_id, transaction_receipt, transaction, ), ), ) return response def send_signed_transaction( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'send_signed_tx'. :param api: the API object. :param message: the Ledger API message :param dialogue: the dialogue :return: the ledger api message """ transaction_digest = api.send_signed_transaction( message.signed_transaction.body ) if transaction_digest is None: # pragma: nocover response = self.get_error_message( ValueError("No transaction_digest returned"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, target_message=message, transaction_digest=TransactionDigest( message.signed_transaction.ledger_id, transaction_digest ), ), ) return response def get_error_message( self, e: Exception, api: LedgerApi, message: Message, dialogue: BaseDialogue, ) -> LedgerApiMessage: """ Build an error message. :param e: the exception. :param api: the Ledger API. :param message: the request message. :param dialogue: the dialogue :return: an error message response. """ message = cast(LedgerApiMessage, message) dialogue = cast(LedgerApiDialogue, dialogue) response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.ERROR, target_message=message, code=500, message=str(e), data=b"", ), ) return response ================================================ FILE: packages/fetchai/connections/local/README.md ================================================ # Local connection OEF compatible local connection for testing purposes only. ## Usage OEF compatible connection to be used for testing, does not interact with external nodes. Does not preserve state on restart. ================================================ FILE: packages/fetchai/connections/local/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the Local OEF connection.""" ================================================ FILE: packages/fetchai/connections/local/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Extension to the Local Node.""" import asyncio import logging import threading from asyncio import AbstractEventLoop, Queue from collections import defaultdict from concurrent.futures import Future from threading import Thread from typing import Any, Dict, List, Optional, Tuple, cast from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.helpers.search.models import Description from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage _default_logger = logging.getLogger("aea.packages.fetchai.connections.local") TARGET = 0 MESSAGE_ID = 1 RESPONSE_TARGET = MESSAGE_ID RESPONSE_MESSAGE_ID = MESSAGE_ID + 1 STUB_DIALOGUE_ID = 0 PUBLIC_ID = PublicId.from_str("fetchai/local:0.21.6") OefSearchDialogue = BaseOefSearchDialogue OEF_LOCAL_NODE_SEARCH_ADDRESS = "oef_local_node_search" OEF_LOCAL_NODE_ADDRESS = "oef_local_node" class OefSearchDialogues(BaseOefSearchDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self) -> None: """Initialize dialogues.""" def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The local connection maintains the dialogue on behalf of the node return OefSearchDialogue.Role.OEF_NODE BaseOefSearchDialogues.__init__( self, self_address=OEF_LOCAL_NODE_SEARCH_ADDRESS, role_from_first_message=role_from_first_message, dialogue_class=OefSearchDialogue, ) class LocalNode: """A light-weight local implementation of a OEF Node.""" def __init__( self, loop: AbstractEventLoop = None, logger: logging.Logger = _default_logger ): """ Initialize a local (i.e. non-networked) implementation of an OEF Node. :param loop: the event loop. If None, a new event loop is instantiated. :param logger: the logger. """ self._lock = threading.Lock() self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] self._loop = loop if loop is not None else asyncio.new_event_loop() self._thread = Thread(target=self._run_loop, daemon=True) self._in_queue = None # type: Optional[asyncio.Queue] self._out_queues = {} # type: Dict[str, asyncio.Queue] self._receiving_loop_task = None # type: Optional[Future] self.address: Optional[Address] = None self._dialogues: Optional[OefSearchDialogues] = None self.logger = logger self.started_event = threading.Event() def __enter__(self) -> "LocalNode": """Start the local node.""" self.start() return self def __exit__(self, exc_type: str, exc_val: str, exc_tb: str) -> None: """Stop the local node.""" self.stop() def _run_loop(self) -> None: """ Run the asyncio loop. This method is supposed to be run only in the Multiplexer thread. """ self.logger.debug("Starting threaded asyncio loop...") asyncio.set_event_loop(self._loop) self._loop.run_forever() self.logger.debug("Asyncio loop has been stopped.") async def connect( self, address: Address, writer: asyncio.Queue ) -> Optional[asyncio.Queue]: """ Connect an address to the node. :param address: the address of the agent. :param writer: the queue where the client is listening. :return: an asynchronous queue, that constitutes the communication channel. """ if address in self._out_queues.keys(): return None if self._in_queue is None: # pragma: nocover raise ValueError("In queue not set.") q = self._in_queue # type: asyncio.Queue self._out_queues[address] = writer self.address = address self._dialogues = OefSearchDialogues() return q def start(self) -> None: """Start the node.""" if not self._loop.is_running() and not self._thread.is_alive(): self._thread.start() self._receiving_loop_task = asyncio.run_coroutine_threadsafe( self.receiving_loop(), loop=self._loop ) self.started_event.wait() self.logger.debug("Local node has been started.") def stop(self) -> None: """Stop the node.""" if self._receiving_loop_task is None or self._in_queue is None: raise ValueError("Connection not started!") asyncio.run_coroutine_threadsafe(self._in_queue.put(None), self._loop).result() self._receiving_loop_task.result() if self._loop.is_running(): self._loop.call_soon_threadsafe(self._loop.stop) if self._thread.is_alive(): self._thread.join() async def receiving_loop(self) -> None: """Process incoming messages.""" self._in_queue = asyncio.Queue() self.started_event.set() while True: envelope = await self._in_queue.get() if envelope is None: self.logger.debug("Receiving loop terminated.") return self.logger.debug("Handling envelope: {}".format(envelope)) await self._handle_envelope(envelope) async def _handle_envelope(self, envelope: Envelope) -> None: """Handle an envelope. :param envelope: the envelope """ if ( envelope.protocol_specification_id == OefSearchMessage.protocol_specification_id ): await self._handle_oef_message(envelope) else: OEFLocalConnection._ensure_valid_envelope_for_external_comms( # pylint: disable=protected-access envelope ) await self._handle_agent_message(envelope) async def _handle_oef_message(self, envelope: Envelope) -> None: """Handle oef messages. :param envelope: the envelope """ if not isinstance(envelope.message, OefSearchMessage): # pragma: nocover raise ValueError("Message not of type OefSearchMessage.") oef_message, dialogue = self._get_message_and_dialogue(envelope) if dialogue is None: self.logger.warning( "Could not create dialogue for message={}".format(oef_message) ) return if oef_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE: await self._register_service( envelope.sender, oef_message.service_description ) elif ( oef_message.performative == OefSearchMessage.Performative.UNREGISTER_SERVICE ): await self._unregister_service(oef_message, dialogue) elif oef_message.performative == OefSearchMessage.Performative.SEARCH_SERVICES: await self._search_services(oef_message, dialogue) else: # request not recognized pass async def _handle_agent_message(self, envelope: Envelope) -> None: """ Forward an envelope to the right agent. :param envelope: the envelope """ destination = envelope.to if destination not in self._out_queues.keys(): msg = DefaultMessage( performative=DefaultMessage.Performative.ERROR, dialogue_reference=("", ""), target=TARGET, message_id=MESSAGE_ID, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Destination not available", error_data={}, ) error_envelope = Envelope( to=envelope.sender, sender=OEF_LOCAL_NODE_ADDRESS, message=msg, ) await self._send(error_envelope) return await self._send(envelope) async def _register_service( self, address: Address, service_description: Description ) -> None: """ Register a service agent in the service directory of the node. :param address: the address of the service agent to be registered. :param service_description: the description of the service agent to be registered. """ with self._lock: self.services[address].append(service_description) async def _unregister_service( self, oef_search_msg: OefSearchMessage, dialogue: OefSearchDialogue, ) -> None: """ Unregister a service agent. :param oef_search_msg: the incoming message. :param dialogue: the dialogue. """ service_description = oef_search_msg.service_description address = oef_search_msg.sender with self._lock: if address not in self.services: msg = dialogue.reply( performative=OefSearchMessage.Performative.OEF_ERROR, target_message=oef_search_msg, oef_error_operation=OefSearchMessage.OefErrorOperation.UNREGISTER_SERVICE, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self._send(envelope) else: self.services[address].remove(service_description) if len(self.services[address]) == 0: self.services.pop(address) async def _search_services( self, oef_search_msg: OefSearchMessage, dialogue: OefSearchDialogue, ) -> None: """ Search the agents in the local Service Directory, and send back the result. This is actually a dummy search, it will return all the registered agents with the specified data model. If the data model is not specified, it will return all the agents. :param oef_search_msg: the message. :param dialogue: the dialogue. """ with self._lock: query = oef_search_msg.query result = [] # type: List[str] if query.model is None: result = list(set(self.services.keys())) else: for agent_address, descriptions in self.services.items(): for description in descriptions: if description.data_model == query.model: result.append(agent_address) msg = dialogue.reply( performative=OefSearchMessage.Performative.SEARCH_RESULT, target_message=oef_search_msg, agents=tuple(sorted(set(result))), ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self._send(envelope) def _get_message_and_dialogue( self, envelope: Envelope ) -> Tuple[OefSearchMessage, Optional[OefSearchDialogue]]: """ Get a message copy and dialogue related to this message. :param envelope: incoming envelope :return: Tuple[Message, Optional[Dialogue]] """ if self._dialogues is None: # pragma: nocover raise ValueError("Call connect before!") message = cast(OefSearchMessage, envelope.message) dialogue = cast(Optional[OefSearchDialogue], self._dialogues.update(message)) return message, dialogue async def _send(self, envelope: Envelope) -> None: """Send a message.""" destination = envelope.to destination_queue = self._out_queues[destination] destination_queue._loop.call_soon_threadsafe(destination_queue.put_nowait, envelope) # type: ignore # pylint: disable=protected-access self.logger.debug("Send envelope {}".format(envelope)) async def disconnect(self, address: Address) -> None: """ Disconnect. :param address: the address of the agent """ with self._lock: self._out_queues.pop(address, None) self.services.pop(address, None) class OEFLocalConnection(Connection): """ Proxy to the functionality of the OEF. It allows the interaction between agents, but not the search functionality. It is useful for local testing. """ connection_id = PUBLIC_ID def __init__(self, local_node: Optional[LocalNode] = None, **kwargs: Any) -> None: """ Load the connection configuration. Initialize a OEF proxy for a local OEF Node :param local_node: the Local OEF Node object. This reference must be the same across the agents of interest. (Note, AEA loader will not accept this argument.) :param kwargs: keyword arguments. """ super().__init__(**kwargs) self._local_node = local_node self._reader = None # type: Optional[Queue] self._writer = None # type: Optional[Queue] async def connect(self) -> None: """Connect to the local OEF Node.""" if self._local_node is None: # pragma: nocover raise ValueError("No local node set!") if self.is_connected: # pragma: nocover return with self._connect_context(): self._reader = Queue() self._writer = await self._local_node.connect(self.address, self._reader) async def disconnect(self) -> None: """Disconnect from the local OEF Node.""" if self._local_node is None: raise ValueError("No local node set!") # pragma: nocover if self.is_disconnected: return # pragma: nocover self.state = ConnectionStates.disconnecting if self._reader is None: raise ValueError("No reader set!") # pragma: nocover await self._local_node.disconnect(self.address) await self._reader.put(None) self._reader, self._writer = None, None self.state = ConnectionStates.disconnected async def send(self, envelope: Envelope) -> None: """ Send a message. :param envelope: the envelope. """ self._ensure_connected() self._writer._loop.call_soon_threadsafe(self._writer.put_nowait, envelope) # type: ignore # pylint: disable=protected-access async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. Blocking. :param args: positional arguments. :param kwargs: keyword arguments. :return: the envelope received, or None. """ self._ensure_connected() try: if self._reader is None: raise ValueError("No reader set!") # pragma: nocover envelope = await self._reader.get() if envelope is None: # pragma: no cover self.logger.debug("Receiving task terminated.") return None self.logger.debug("Received envelope {}".format(envelope)) return envelope except Exception: # pragma: nocover # pylint: disable=broad-except return None ================================================ FILE: packages/fetchai/connections/local/connection.yaml ================================================ name: local author: fetchai version: 0.21.6 type: connection description: The local connection provides a stub for an OEF node. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmbK7MtyAVqh2LmSh9TY6yBZqfWaAXURP4rQGATyP2hTKC __init__.py: QmUZgacY7XBWHCum6DrUkoy4r3xM3hkzKpqC49XFmKuYRQ connection.py: QmWiVqkzLKTJNgXNKRKJSiR3CLZkJWvU9QYN691gTuCTZG fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/oef_search:1.1.7 class_name: OEFLocalConnection config: {} excluded_protocols: [] restricted_to_protocols: [] dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/oef/README.md ================================================ # OEF connection Connection to interact with an OEF node (). ## Usage Register/unregister services, perform searches using `fetchai/oef_search:1.1.7` protocol and send messages of any protocol to other agents connected to the same node. ================================================ FILE: packages/fetchai/connections/oef/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the OEF connection.""" ================================================ FILE: packages/fetchai/connections/oef/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Extension to the OEF Python SDK.""" import asyncio import logging from asyncio import AbstractEventLoop, CancelledError from concurrent.futures.thread import ThreadPoolExecutor from itertools import cycle from logging import Logger from typing import Any, Callable, Dict, List, Optional, cast import oef from oef.agents import OEFAgent from oef.core import AsyncioCore from oef.messages import CFP_TYPES, PROPOSE_TYPES from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.exceptions import enforce from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.oef.object_translator import OEFObjectTranslator from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage _default_logger = logging.getLogger("aea.packages.fetchai.connections.oef") TARGET = 0 MESSAGE_ID = 1 RESPONSE_TARGET = MESSAGE_ID RESPONSE_MESSAGE_ID = MESSAGE_ID + 1 STUB_MESSAGE_ID = 0 STUB_DIALOGUE_ID = 0 DEFAULT_OEF = "oef" PUBLIC_ID = PublicId.from_str("fetchai/oef:0.22.6") OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(BaseOefSearchDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self) -> None: """Initialize dialogues.""" def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The oef connection maintains the dialogue on behalf of the node return OefSearchDialogue.Role.OEF_NODE BaseOefSearchDialogues.__init__( self, self_address=str(OEFConnection.connection_id), role_from_first_message=role_from_first_message, dialogue_class=OefSearchDialogue, ) class OEFChannel(OEFAgent): """The OEFChannel connects the OEF Agent with the connection.""" THREAD_POOL_SIZE = 3 CONNECT_RETRY_DELAY = 5.0 CONNECT_TIMEOUT = 2 CONNECT_ATTEMPTS_LIMIT = 0 def __init__( self, address: Address, oef_addr: str, oef_port: int, logger: Logger = _default_logger, ): """ Initialize. :param address: the address of the agent. :param oef_addr: the OEF IP address. :param oef_port: the OEF port. :param logger: the logger. """ super().__init__( address, oef_addr=oef_addr, oef_port=oef_port, core=AsyncioCore(logger=logger), logger=lambda *x: None, logger_debug=lambda *x: None, ) self.address = address self._in_queue = None # type: Optional[asyncio.Queue] self._loop = None # type: Optional[AbstractEventLoop] self.oef_search_dialogues = OefSearchDialogues() self.oef_msg_id = 0 self.oef_msg_id_to_dialogue = {} # type: Dict[int, OefSearchDialogue] self._threaded_pool = ThreadPoolExecutor(self.THREAD_POOL_SIZE) self.aea_logger = logger async def _run_in_executor(self, fn: Callable, *args: Any) -> Any: if not self._loop: # pragma: nocover raise ValueError("Channel not connected!") return await self._loop.run_in_executor(self._threaded_pool, fn, *args) @property def in_queue(self) -> asyncio.Queue: """Get input messages queue.""" if not self._in_queue: # pragma: nocover raise ValueError("Channel not connected!") return self._in_queue @property def loop(self) -> AbstractEventLoop: """Get event loop.""" if not self._loop: # pragma: nocover raise ValueError("Channel not connected!") return self._loop def on_message( # pylint: disable=unused-argument self, msg_id: int, dialogue_id: int, origin: Address, content: bytes ) -> None: """ On message event handler. :param msg_id: the message id. :param dialogue_id: the dialogue id. :param origin: the address of the sender. :param content: the bytes content. """ # We are not using the 'msg_id', 'dialogue_id' and 'origin' parameters because 'content' contains a # serialized instance of 'Envelope', hence it already contains this information. self._check_loop_and_queue() envelope = Envelope.decode(content) asyncio.run_coroutine_threadsafe(self.in_queue.put(envelope), self.loop) def on_cfp( self, msg_id: int, dialogue_id: int, origin: Address, target: int, query: CFP_TYPES, ) -> None: """ On cfp event handler. :param msg_id: the message id. :param dialogue_id: the dialogue id. :param origin: the address of the sender. :param target: the message target. :param query: the query. """ self._check_loop_and_queue() self.aea_logger.warning( "Dropping incompatible on_cfp: msg_id={}, dialogue_id={}, origin={}, target={}, query={}".format( msg_id, dialogue_id, origin, target, query ) ) def on_propose( # pylint: disable=unused-argument self, msg_id: int, dialogue_id: int, origin: Address, target: int, proposals: PROPOSE_TYPES, ) -> None: """ On propose event handler. :param msg_id: the message id. :param dialogue_id: the dialogue id. :param origin: the address of the sender. :param target: the message target. :param proposals: the proposals. """ self._check_loop_and_queue() self.aea_logger.warning( "Dropping incompatible on_propose: msg_id={}, dialogue_id={}, origin={}, target={}".format( msg_id, dialogue_id, origin, target ) ) def on_accept( self, msg_id: int, dialogue_id: int, origin: Address, target: int ) -> None: """ On accept event handler. :param msg_id: the message id. :param dialogue_id: the dialogue id. :param origin: the address of the sender. :param target: the message target. """ self._check_loop_and_queue() self.aea_logger.warning( "Dropping incompatible on_accept: msg_id={}, dialogue_id={}, origin={}, target={}".format( msg_id, dialogue_id, origin, target ) ) def on_decline( self, msg_id: int, dialogue_id: int, origin: Address, target: int ) -> None: """ On decline event handler. :param msg_id: the message id. :param dialogue_id: the dialogue id. :param origin: the address of the sender. :param target: the message target. """ self._check_loop_and_queue() self.aea_logger.warning( "Dropping incompatible on_decline: msg_id={}, dialogue_id={}, origin={}, target={}".format( msg_id, dialogue_id, origin, target ) ) def on_search_result(self, search_id: int, agents: List[Address]) -> None: """ On accept event handler. :param search_id: the search id. :param agents: the list of agents. """ self._check_loop_and_queue() oef_search_dialogue = self.oef_msg_id_to_dialogue.pop(search_id, None) if oef_search_dialogue is None: self.aea_logger.warning( "Could not find dialogue for search_id={}".format(search_id) ) # pragma: nocover return # pragma: nocover last_msg = oef_search_dialogue.last_incoming_message if last_msg is None: self.aea_logger.warning("Could not find last message.") # pragma: nocover return # pragma: nocover msg = oef_search_dialogue.reply( performative=OefSearchMessage.Performative.SEARCH_RESULT, target_message=last_msg, agents=tuple(agents), ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) asyncio.run_coroutine_threadsafe(self.in_queue.put(envelope), self.loop) def on_oef_error( self, answer_id: int, operation: oef.messages.OEFErrorOperation ) -> None: """ On oef error event handler. :param answer_id: the answer id. :param operation: the error operation. """ self._check_loop_and_queue() try: operation = OefSearchMessage.OefErrorOperation(operation) except ValueError: operation = OefSearchMessage.OefErrorOperation.OTHER oef_search_dialogue = self.oef_msg_id_to_dialogue.pop(answer_id, None) if oef_search_dialogue is None: self.aea_logger.warning( "Could not find dialogue for answer_id={}".format(answer_id) ) # pragma: nocover return # pragma: nocover last_msg = oef_search_dialogue.last_incoming_message if last_msg is None: self.aea_logger.warning("Could not find last message.") # pragma: nocover return # pragma: nocover msg = oef_search_dialogue.reply( performative=OefSearchMessage.Performative.OEF_ERROR, target_message=last_msg, oef_error_operation=operation, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) asyncio.run_coroutine_threadsafe(self.in_queue.put(envelope), self.loop) def on_dialogue_error( # pylint: disable=unused-argument self, answer_id: int, dialogue_id: int, origin: Address ) -> None: """ On dialogue error event handler. :param answer_id: the answer id. :param dialogue_id: the dialogue id. :param origin: the message sender. """ self._check_loop_and_queue() msg = DefaultMessage( performative=DefaultMessage.Performative.ERROR, dialogue_reference=(str(answer_id), ""), target=TARGET, message_id=MESSAGE_ID, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Destination not available", error_data={}, ) envelope = Envelope( to=self.address, sender=DEFAULT_OEF, message=msg, ) asyncio.run_coroutine_threadsafe(self.in_queue.put(envelope), self.loop) def send(self, envelope: Envelope) -> None: """ Send message handler. :param envelope: the message. """ if ( envelope.protocol_specification_id == OefSearchMessage.protocol_specification_id ): self.send_oef_message(envelope) else: self.send_default_message(envelope) def send_default_message(self, envelope: Envelope) -> None: """Send a 'default' message.""" self.send_message( STUB_MESSAGE_ID, STUB_DIALOGUE_ID, envelope.to, envelope.encode() ) def send_oef_message(self, envelope: Envelope) -> None: """ Send oef message handler. :param envelope: the message. """ enforce( isinstance(envelope.message, OefSearchMessage), "Message not of type OefSearchMessage", ) oef_message = cast(OefSearchMessage, envelope.message) oef_search_dialogue = cast( OefSearchDialogue, self.oef_search_dialogues.update(oef_message) ) if oef_search_dialogue is None: self.aea_logger.warning( "Could not create dialogue for message={}".format(oef_message) ) # pragma: nocover return # pragma: nocover self.oef_msg_id += 1 self.oef_msg_id_to_dialogue[self.oef_msg_id] = oef_search_dialogue if oef_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE: service_description = oef_message.service_description oef_service_description = OEFObjectTranslator.to_oef_description( service_description ) self.register_service(self.oef_msg_id, oef_service_description) elif ( oef_message.performative == OefSearchMessage.Performative.UNREGISTER_SERVICE ): service_description = oef_message.service_description oef_service_description = OEFObjectTranslator.to_oef_description( service_description ) self.unregister_service(self.oef_msg_id, oef_service_description) elif oef_message.performative == OefSearchMessage.Performative.SEARCH_SERVICES: query = oef_message.query oef_query = OEFObjectTranslator.to_oef_query(query) self.search_services(self.oef_msg_id, oef_query) else: raise ValueError("OEF request not recognized.") # pragma: nocover def handle_failure( # pylint: disable=unused-argument self, exception: Exception, conn: Any ) -> None: """Handle failure.""" self.aea_logger.exception(exception) # pragma: nocover async def _set_loop_and_queue(self) -> None: self._loop = asyncio.get_event_loop() self._in_queue = asyncio.Queue() async def _unset_loop_and_queue(self) -> None: self._loop = None self._in_queue = None def _check_loop_and_queue(self) -> None: enforce(self.in_queue is not None, "In queue is not set!") enforce(self.loop is not None, "Loop is not set!") async def connect( # pylint: disable=invalid-overridden-method,arguments-differ self, ) -> None: """Connect channel.""" await self._set_loop_and_queue() self.core = AsyncioCore( # pylint: disable=attribute-defined-outside-init loop=self._loop, logger=_default_logger ) if self.CONNECT_ATTEMPTS_LIMIT != 0: # pragma: nocover gen = range(self.CONNECT_ATTEMPTS_LIMIT) else: gen = cycle(range(1)) # type: ignore try: for _ in gen: is_connected = await self._run_in_executor( self._oef_agent_connect, self.CONNECT_TIMEOUT ) if is_connected: return self.aea_logger.warning( "Cannot connect to OEFChannel. Retrying in 5 seconds..." ) await asyncio.sleep(self.CONNECT_RETRY_DELAY) raise ValueError("Connect attempts limit!") # pragma: nocover except Exception: # pragma: nocover await self._unset_loop_and_queue() raise def _oef_agent_connect(self, timeout: float = 2) -> bool: """ Connect OEF agent. :param timeout: timeout to wait on connection :return: bool, connected or not """ return super().connect(timeout) async def disconnect(self) -> None: # pylint: disable=invalid-overridden-method """Disconnect channel.""" if self._in_queue is None and self._loop is None: # pragma: nocover return # not connected so nothing to do await self.in_queue.put(None) await self._run_in_executor(super().disconnect) await self._unset_loop_and_queue() async def get(self) -> Optional[Envelope]: """Get incoming envelope.""" return await self.in_queue.get() class OEFConnection(Connection): """The OEFConnection connects the to the mailbox.""" connection_id = PUBLIC_ID def __init__(self, **kwargs: Any) -> None: """ Initialize. :param kwargs: the keyword arguments (check the parent constructor) """ super().__init__(**kwargs) addr = cast(str, self.configuration.config.get("addr")) port = cast(int, self.configuration.config.get("port")) if addr is None or port is None: raise ValueError("addr and port must be set!") # pragma: nocover self.oef_addr = addr self.oef_port = port self.channel = OEFChannel(self.address, self.oef_addr, self.oef_port, logger=self.logger) # type: ignore self._connection_check_task = None # type: Optional[asyncio.Future] async def connect(self) -> None: """ Connect to the channel. :raises Exception if the connection to the OEF fails. """ if self.is_connected: return with self._connect_context(): self.channel.aea_logger = self.logger await self.channel.connect() self._connection_check_task = self.loop.create_task( self._connection_check() ) async def _connection_check(self) -> None: """ Check for connection to the channel. Try to reconnect if connection is dropped. """ while self.is_connected: await asyncio.sleep(2.0) if not self.channel.get_state() == "connected": # pragma: no cover self.state = ConnectionStates.connecting self.logger.warning( "Lost connection to OEFChannel. Retrying to connect soon ..." ) await self.channel.connect() self.state = ConnectionStates.connected self.logger.warning( "Successfully re-established connection to OEFChannel." ) async def disconnect(self) -> None: """Disconnect from the channel.""" if self.is_disconnected: return self.state = ConnectionStates.disconnecting if self._connection_check_task is not None: self._connection_check_task.cancel() self._connection_check_task = None await self.channel.disconnect() self.state = ConnectionStates.disconnected async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. Blocking. :param args: the positional arguments :param kwargs: the keyword arguments :return: the envelope received, or None. """ try: envelope = await self.channel.get() if envelope is None: # pragma: no cover self.logger.debug("Received None.") return None self.logger.debug("Received envelope: {}".format(envelope)) return envelope except CancelledError: self.logger.debug("Receive cancelled.") return None except Exception as e: # pragma: nocover # pylint: disable=broad-except self.logger.exception(e) return None async def send(self, envelope: "Envelope") -> None: """ Send an envelope. :param envelope: the envelope to send. """ if self.is_connected: self.channel.send(envelope) ================================================ FILE: packages/fetchai/connections/oef/connection.yaml ================================================ name: oef author: fetchai version: 0.22.6 type: connection description: The oef connection provides a wrapper around the OEF SDK for connection with the OEF search and communication node. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmWXtFDtByTYoLy5UKYbYQ9hfDUnDhLJzSy1svoAnHyKUp __init__.py: QmPyDUmHgQdAtuq46GSQ75kpkeH3jg5LQoZm2xQi9j9PMk connection.py: QmTKTZvUxTfL9qZ9on9h6WmiempZBbPsQ6yMiCvwt2jm3v object_translator.py: QmTcyE7yVMzx72zr7JJoZDftL42aP5s3jQQBgMCGqgY6PW fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/default:1.1.7 - fetchai/oef_search:1.1.7 class_name: OEFConnection config: addr: 127.0.0.1 port: '10000' excluded_protocols: [] restricted_to_protocols: [] dependencies: colorlog: {} oef: version: ==0.8.1 is_abstract: false ================================================ FILE: packages/fetchai/connections/oef/object_translator.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Extension to the OEF Python SDK.""" import logging from typing import Tuple from oef.query import And as OEFAnd from oef.query import Constraint as OEFConstraint from oef.query import ConstraintExpr as OEFConstraintExpr from oef.query import ConstraintType as OEFConstraintType from oef.query import Distance, Eq, Gt, GtEq, In from oef.query import Location as OEFLocation from oef.query import Lt, LtEq from oef.query import Not as OEFNot from oef.query import NotEq, NotIn from oef.query import Or as OEFOr from oef.query import Query as OEFQuery from oef.query import Range from oef.schema import AttributeSchema as OEFAttribute from oef.schema import DataModel as OEFDataModel from oef.schema import Description as OEFDescription from aea.helpers.search.models import ( And, Attribute, Constraint, ConstraintExpr, ConstraintType, ConstraintTypes, DataModel, Description, Location, Not, Or, Query, ) _default_logger = logging.getLogger("aea.packages.fetchai.connections.oef") class OEFObjectTranslator: """Translate our OEF object to object of OEF SDK classes.""" @classmethod def to_oef_description(cls, desc: Description) -> OEFDescription: """From our description to OEF description.""" oef_data_model = ( cls.to_oef_data_model(desc.data_model) if desc.data_model is not None else None ) new_values = {} location_keys = set() loggers_by_key = {} for key, value in desc.values.items(): if isinstance(value, Location): oef_location = OEFLocation( latitude=value.latitude, longitude=value.longitude ) location_keys.add(key) new_values[key] = oef_location else: new_values[key] = value # this is a workaround to make OEFLocation objects deep-copyable. # Indeed, there is a problem in deep-copying such objects # because of the logger object they have attached. # Steps: # 1) we remove the loggers attached to each Location obj, # 2) then we instantiate the description (it runs deepcopy on the values), # 3) and then we reattach the loggers. for key in location_keys: loggers_by_key[key] = new_values[key].log # in this way we remove the logger new_values[key].log = None description = OEFDescription(new_values, oef_data_model) for key in location_keys: new_values[key].log = loggers_by_key[key] return description @classmethod def to_oef_data_model(cls, data_model: DataModel) -> OEFDataModel: """From our data model to OEF data model.""" oef_attributes = [ cls.to_oef_attribute(attribute) for attribute in data_model.attributes ] return OEFDataModel(data_model.name, oef_attributes, data_model.description) @classmethod def to_oef_attribute(cls, attribute: Attribute) -> OEFAttribute: """From our attribute to OEF attribute.""" # in case the attribute type is Location, replace with the `oef` class. attribute_type = OEFLocation if attribute.type == Location else attribute.type return OEFAttribute( attribute.name, attribute_type, attribute.is_required, attribute.description ) @classmethod def to_oef_query(cls, query: Query) -> OEFQuery: """From our query to OEF query.""" oef_data_model = ( cls.to_oef_data_model(query.model) if query.model is not None else None ) constraints = [cls.to_oef_constraint_expr(c) for c in query.constraints] return OEFQuery(constraints, oef_data_model) @classmethod def to_oef_location(cls, location: Location) -> OEFLocation: """From our location to OEF location.""" return OEFLocation(latitude=location.latitude, longitude=location.longitude) # type: ignore @classmethod def to_oef_constraint_expr( cls, constraint_expr: ConstraintExpr ) -> OEFConstraintExpr: """From our constraint expression to the OEF constraint expression.""" if isinstance(constraint_expr, And): return OEFAnd( [cls.to_oef_constraint_expr(c) for c in constraint_expr.constraints] ) if isinstance(constraint_expr, Or): return OEFOr( [cls.to_oef_constraint_expr(c) for c in constraint_expr.constraints] ) if isinstance(constraint_expr, Not): return OEFNot(cls.to_oef_constraint_expr(constraint_expr.constraint)) if isinstance(constraint_expr, Constraint): oef_constraint_type = cls.to_oef_constraint_type( constraint_expr.constraint_type ) return OEFConstraint(constraint_expr.attribute_name, oef_constraint_type) raise ValueError("Constraint expression not supported.") @classmethod def to_oef_constraint_type( cls, constraint_type: ConstraintType ) -> OEFConstraintType: """From our constraint type to OEF constraint type.""" value = constraint_type.value def distance(value: Tuple) -> Distance: location = cls.to_oef_location(location=value[0]) return Distance(center=location, distance=value[1]) CONSTRAINT_MAP = { ConstraintTypes.EQUAL: Eq, ConstraintTypes.NOT_EQUAL: NotEq, ConstraintTypes.LESS_THAN: Lt, ConstraintTypes.LESS_THAN_EQ: LtEq, ConstraintTypes.GREATER_THAN: Gt, ConstraintTypes.GREATER_THAN_EQ: GtEq, ConstraintTypes.WITHIN: Range, ConstraintTypes.IN: In, ConstraintTypes.NOT_IN: NotIn, ConstraintTypes.DISTANCE: distance, } if constraint_type.type not in CONSTRAINT_MAP: raise ValueError("Constraint type not recognized.") return CONSTRAINT_MAP[constraint_type.type](value) @classmethod def from_oef_description(cls, oef_desc: OEFDescription) -> Description: """From an OEF description to our description.""" data_model = ( cls.from_oef_data_model(oef_desc.data_model) if oef_desc.data_model is not None else None ) new_values = {} for key, value in oef_desc.values.items(): if isinstance(value, OEFLocation): new_values[key] = Location( latitude=value.latitude, longitude=value.longitude ) else: new_values[key] = value return Description(new_values, data_model=data_model) @classmethod def from_oef_data_model(cls, oef_data_model: OEFDataModel) -> DataModel: """From an OEF data model to our data model.""" attributes = [ cls.from_oef_attribute(oef_attribute) for oef_attribute in oef_data_model.attribute_schemas ] return DataModel(oef_data_model.name, attributes, oef_data_model.description) @classmethod def from_oef_attribute(cls, oef_attribute: OEFAttribute) -> Attribute: """From an OEF attribute to our attribute.""" oef_attribute_type = ( Location if oef_attribute.type == OEFLocation else oef_attribute.type ) return Attribute( oef_attribute.name, oef_attribute_type, oef_attribute.required, oef_attribute.description, ) @classmethod def from_oef_query(cls, oef_query: OEFQuery) -> Query: """From our query to OrOEF query.""" data_model = ( cls.from_oef_data_model(oef_query.model) if oef_query.model is not None else None ) constraints = [cls.from_oef_constraint_expr(c) for c in oef_query.constraints] return Query(constraints, data_model) @classmethod def from_oef_location(cls, oef_location: OEFLocation) -> Location: """From oef location to our location.""" return Location( latitude=oef_location.latitude, longitude=oef_location.longitude ) @classmethod def from_oef_constraint_expr( cls, oef_constraint_expr: OEFConstraintExpr ) -> ConstraintExpr: """From our query to OEF query.""" if isinstance(oef_constraint_expr, OEFAnd): return And( [ cls.from_oef_constraint_expr(c) for c in oef_constraint_expr.constraints ] ) if isinstance(oef_constraint_expr, OEFOr): return Or( [ cls.from_oef_constraint_expr(c) for c in oef_constraint_expr.constraints ] ) if isinstance(oef_constraint_expr, OEFNot): return Not(cls.from_oef_constraint_expr(oef_constraint_expr.constraint)) if isinstance(oef_constraint_expr, OEFConstraint): constraint_type = cls.from_oef_constraint_type( oef_constraint_expr.constraint ) return Constraint(oef_constraint_expr.attribute_name, constraint_type) raise ValueError("OEF Constraint not supported.") @classmethod def from_oef_constraint_type( cls, constraint_type: OEFConstraintType ) -> ConstraintType: """From OEF constraint type to our constraint type.""" if isinstance(constraint_type, Eq): return ConstraintType(ConstraintTypes.EQUAL, constraint_type.value) if isinstance(constraint_type, NotEq): return ConstraintType(ConstraintTypes.NOT_EQUAL, constraint_type.value) if isinstance(constraint_type, Lt): return ConstraintType(ConstraintTypes.LESS_THAN, constraint_type.value) if isinstance(constraint_type, LtEq): return ConstraintType(ConstraintTypes.LESS_THAN_EQ, constraint_type.value) if isinstance(constraint_type, Gt): return ConstraintType(ConstraintTypes.GREATER_THAN, constraint_type.value) if isinstance(constraint_type, GtEq): return ConstraintType( ConstraintTypes.GREATER_THAN_EQ, constraint_type.value ) if isinstance(constraint_type, Range): return ConstraintType(ConstraintTypes.WITHIN, constraint_type.values) if isinstance(constraint_type, In): return ConstraintType(ConstraintTypes.IN, constraint_type.values) if isinstance(constraint_type, NotIn): return ConstraintType(ConstraintTypes.NOT_IN, constraint_type.values) if isinstance(constraint_type, Distance): location = cls.from_oef_location(constraint_type.center) return ConstraintType( ConstraintTypes.DISTANCE, (location, constraint_type.distance) ) raise ValueError("Constraint type not recognized.") ================================================ FILE: packages/fetchai/connections/p2p_libp2p/README.md ================================================ # P2P Libp2p Connection This connection enables point-to-point secure end-to-end encrypted communication between agents in a fully decentralized way. The connection deploys a node that collectively maintains a distributed hash table (DHT) along with other nodes in the same network. The DHT provides proper messages delivery by mapping agents addresses to their locations. ## Usage First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p:0.27.5`. Next, ensure that the connection is properly configured by setting: - `local_uri` to the local IP address and port number that the node should use, in format `${ip}:${port}` - `public_uri` to the external IP address and port number allocated for the node, can be the same as `local_uri` if running locally - `entry_peers` to a list of multiaddresses of already deployed nodes to join their network, should be empty for genesis node - `delegate_uri` to the IP address and port number for the delegate service, leave empty to disable the service If the delegate service is enabled, then other AEAs can connect to the peer node using the `fetchai/p2p_libp2p_client:0.20.6` connection. ================================================ FILE: packages/fetchai/connections/p2p_libp2p/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the p2p libp2p connection.""" ================================================ FILE: packages/fetchai/connections/p2p_libp2p/check_dependencies.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Check that the dependencies 'gcc' and 'go' are installed in the system.""" import asyncio import os import platform import re import shutil import subprocess # nosec import sys import tempfile from itertools import islice from shutil import copytree as copy_tree from subprocess import Popen, TimeoutExpired # nosec from typing import Iterable, List, Optional, Pattern, Tuple from aea.exceptions import AEAException from aea.helpers.base import ensure_dir try: # flake8: noqa # pylint: disable=unused-import,ungrouped-imports from .consts import ( # type: ignore LIBP2P_NODE_DEPS_DOWNLOAD_TIMEOUT, LIBP2P_NODE_MODULE, LIBP2P_NODE_MODULE_NAME, ) except ImportError: # pragma: nocover # flake8: noqa # pylint: disable=unused-import,ungrouped-imports from consts import ( # type: ignore LIBP2P_NODE_DEPS_DOWNLOAD_TIMEOUT, LIBP2P_NODE_MODULE, LIBP2P_NODE_MODULE_NAME, ) ERROR_MESSAGE_TEMPLATE_BINARY_NOT_FOUND = "'{command}' is required by the libp2p connection, but it is not installed, or it is not accessible from the system path." ERROR_MESSAGE_TEMPLATE_VERSION_TOO_LOW = "The installed version of '{command}' is too low: expected at least {lower_bound}; found {actual_version}." # for the purposes of this script, # a version is a tuple of integers: (major, minor, patch) VERSION = Tuple[int, int, int] MINIMUM_GO_VERSION: VERSION = (1, 13, 0) MINIMUM_GCC_VERSION: VERSION = (7, 5, 0) def nth(iterable: Iterable, n: int, default: int = 0) -> int: """Returns the nth item or a default value""" return next(islice(iterable, n, None), default) def get_version(*args: int) -> VERSION: """ Get the version from a list of arguments. Set to '0' if there are not enough arguments. :param args: positional arguments :return: the version """ major = nth(args, 0, 0) minor = nth(args, 1, 0) patch = nth(args, 2, 0) return major, minor, patch def version_to_string(version: VERSION) -> str: """ Transform version to string. :param version: the version. :return: the string representation. """ return ".".join(map(str, version)) def print_ok_message( binary_name: str, actual_version: VERSION, version_lower_bound: VERSION ) -> None: """ Print OK message. :param binary_name: the binary binary_name. :param actual_version: the actual version. :param version_lower_bound: the version lower bound. """ print( f"check '{binary_name}'>={version_to_string(version_lower_bound)}, found {version_to_string(actual_version)}" ) def check_binary( binary_name: str, args: List[str], version_regex: Pattern, version_lower_bound: VERSION, ) -> None: """ Check a binary is accessible from the terminal. It breaks down in: 1) check if the binary is reachable from the system path; 2) check that the version number is higher or equal than the minimum required version. :param binary_name: the name of the binary. :param args: the arguments to provide to the binary to retrieve the version. :param version_regex: the regex used to extract the version from the output. :param version_lower_bound: the minimum required version. """ path = shutil.which(binary_name) if not path: raise AEAException( ERROR_MESSAGE_TEMPLATE_BINARY_NOT_FOUND.format(command=binary_name) ) version_getter_command = [binary_name, *args] stdout = subprocess.check_output(version_getter_command).decode("utf-8") # nosec version_match = version_regex.search(stdout) if version_match is None: print( f"Warning: cannot parse '{binary_name}' version from command: {version_getter_command}. stdout: {stdout}" ) return actual_version: VERSION = get_version(*map(int, version_match.groups(default="0"))) if actual_version < version_lower_bound: raise AEAException( ERROR_MESSAGE_TEMPLATE_VERSION_TOO_LOW.format( command=binary_name, lower_bound=version_to_string(version_lower_bound), actual_version=version_to_string(actual_version), ) ) print_ok_message(binary_name, actual_version, version_lower_bound) def check_versions() -> None: """Check versions.""" check_binary( "go", ["version"], re.compile(r"go version go([0-9]+)\.([0-9]+)"), MINIMUM_GO_VERSION, ) if platform.system() == "Darwin": check_binary( # pragma: nocover "gcc", ["--version"], re.compile(r"clang version.* ([0-9]+)\.([0-9]+)\.([0-9]+) "), MINIMUM_GCC_VERSION, ) else: check_binary( "gcc", ["--version"], re.compile(r"gcc.* ([0-9]+)\.([0-9]+)\.([0-9]+)"), MINIMUM_GCC_VERSION, ) def main() -> None: # pragma: nocover """The main entrypoint of the script.""" if len(sys.argv) < 2: raise ValueError("Please provide build directory path as an argument!") build_dir = sys.argv[1] check_versions() build_node(build_dir) def _golang_module_build( path: str, timeout: float = LIBP2P_NODE_DEPS_DOWNLOAD_TIMEOUT, ) -> Optional[str]: """ Builds go module located at `path`, downloads necessary dependencies :param path: the path to the node code :param timeout: the build timeout :return: str with logs or error description if happens """ with Popen( # nosec ["go", "build"], stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, cwd=path, env=os.environ, ) as proc: try: stdout, _ = proc.communicate(timeout=timeout) # type: ignore except TimeoutExpired: # pragma: nocover proc.terminate() proc.wait(timeout=timeout) return "terminated by timeout" if proc.returncode != 0: # pragma: nocover return stdout.decode() # type: ignore return None def build_node(build_dir: str) -> None: """Build node placed inside build_dir.""" with tempfile.TemporaryDirectory() as dirname: copy_tree(LIBP2P_NODE_MODULE, dirname, dirs_exist_ok=True) # type: ignore err_str = _golang_module_build(dirname) if err_str: # pragma: nocover raise Exception(f"Node build failed: {err_str}") ensure_dir(build_dir) shutil.copy( os.path.join(dirname, LIBP2P_NODE_MODULE_NAME), os.path.join(build_dir, LIBP2P_NODE_MODULE_NAME), ) print(f"{LIBP2P_NODE_MODULE_NAME} built successfully!") if __name__ == "__main__": main() # pragma: nocover ================================================ FILE: packages/fetchai/connections/p2p_libp2p/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the p2p libp2p connection.""" import asyncio import logging import os import platform import subprocess # nosec import sys from asyncio import AbstractEventLoop, CancelledError, events from ipaddress import ip_address from pathlib import Path from socket import gethostbyname from typing import Any, IO, List, Optional, Sequence, cast from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER from aea.connections.base import Connection, ConnectionStates from aea.crypto.base import Crypto from aea.exceptions import enforce from aea.helpers.acn.agent_record import AgentRecord from aea.helpers.acn.uri import Uri from aea.helpers.multiaddr.base import MultiAddr from aea.helpers.pipe import IPCChannel, TCPSocketChannel from aea.mail.base import Envelope from packages.fetchai.connections.p2p_libp2p.consts import LIBP2P_NODE_MODULE_NAME from packages.fetchai.protocols.acn import acn_pb2 from packages.fetchai.protocols.acn.message import AcnMessage _default_logger = logging.getLogger("aea.packages.fetchai.connections.p2p_libp2p") ACN_CURRENT_VERSION = "0.1.0" LIBP2P_NODE_LOG_FILE = "libp2p_node.log" LIBP2P_NODE_ENV_FILE = ".env.libp2p" LIBP2P_NODE_CLARGS = [] # type: List[str] PIPE_CONN_TIMEOUT = 10.0 PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p:0.27.5") SUPPORTED_LEDGER_IDS = ["fetchai", "cosmos", "ethereum"] LIBP2P_SUCCESS_MESSAGE = "Peer running in " POR_DEFAULT_SERVICE_ID = "acn" def _ip_all_private_or_all_public(addrs: List[str]) -> bool: if len(addrs) == 0: return True is_private = ip_address(gethostbyname(addrs[0])).is_private is_loopback = ip_address(gethostbyname(addrs[0])).is_loopback for addr in addrs: if ip_address(gethostbyname(addr)).is_private != is_private: return False # pragma: nocover if ip_address(gethostbyname(addr)).is_loopback != is_loopback: return False return True def _golang_module_run( path: str, name: str, args: Sequence[str], log_file_desc: IO[str], logger: logging.Logger = _default_logger, ) -> subprocess.Popen: """ Runs a built module located at `path`. :param path: the path to the go module. :param name: the name of the module. :param args: the args :param log_file_desc: the file descriptor of the log file. :param logger: the logger :return: subprocess """ cmd = [os.path.join(path, name)] cmd.extend(args) env = os.environ try: logger.debug(cmd) proc = subprocess.Popen( # nosec # pylint: disable=consider-using-with cmd, cwd=path, env=env, stdout=log_file_desc, stderr=log_file_desc, shell=False, ) except Exception as e: logger.error( "While executing go run . {} at {} : {}".format(path, args, str(e)) ) raise e return proc class NodeClient: """Client to communicate with node using ipc channel(pipe).""" ACN_ACK_TIMEOUT = 5 def __init__(self, pipe: IPCChannel, agent_record: AgentRecord) -> None: """Set node client with pipe.""" self.pipe = pipe self.agent_record = agent_record self._wait_status: Optional[asyncio.Future] = None async def connect(self) -> bool: """Connect to node with pipe.""" return await self.pipe.connect() async def send_envelope(self, envelope: Envelope) -> None: """Send envelope to node.""" self._wait_status = asyncio.Future() buf = self.make_acn_envelope_message(envelope) await self._write(buf) status = await self.wait_for_status() if status.code != int(AcnMessage.StatusBody.StatusCode.SUCCESS): # type: ignore # pylint: disable=no-member raise ValueError( # pragma: nocover f"failed to send envelope. got error confirmation: {status.code}" ) async def wait_for_status(self) -> Any: """Get status.""" if self._wait_status is None: # pragma: nocover raise ValueError("value failed!") try: status = await asyncio.wait_for( self._wait_status, timeout=self.ACN_ACK_TIMEOUT ) return status except asyncio.TimeoutError: # pragma: nocover if not self._wait_status.done(): # pragma: nocover self._wait_status.set_exception(Exception("Timeout")) await asyncio.sleep(0) raise ValueError("acn status await timeout!") finally: self._wait_status = None @staticmethod def make_acn_envelope_message(envelope: Envelope) -> bytes: """Make acn message with envelope in.""" acn_msg = acn_pb2.AcnMessage() performative = acn_pb2.AcnMessage.Aea_Envelope_Performative() # type: ignore performative.envelope = envelope.encode() acn_msg.aea_envelope.CopyFrom(performative) # pylint: disable=no-member buf = acn_msg.SerializeToString() return buf async def read_envelope(self) -> Optional[Envelope]: """Read envelope from the node.""" while True: buf = await self._read() if not buf: return None try: acn_msg = acn_pb2.AcnMessage() acn_msg.ParseFromString(buf) except Exception as e: await self.write_acn_status_error( f"Failed to parse acn message {e}", status_code=AcnMessage.StatusBody.StatusCode.ERROR_DECODE, ) raise ValueError(f"Error parsing acn message: {e}") from e performative = acn_msg.WhichOneof("performative") if performative == "aea_envelope": # pragma: nocover aea_envelope = acn_msg.aea_envelope # pylint: disable=no-member try: envelope = Envelope.decode(aea_envelope.envelope) await self.write_acn_status_ok() return envelope except Exception as e: await self.write_acn_status_error( f"Failed to decode envelope: {e}", status_code=AcnMessage.StatusBody.StatusCode.ERROR_DECODE, ) raise elif performative == "status": if self._wait_status is not None: self._wait_status.set_result( acn_msg.status.body # pylint: disable=no-member ) else: # pragma: nocover await self.write_acn_status_error( f"Bad acn message {performative}", status_code=AcnMessage.StatusBody.StatusCode.ERROR_UNEXPECTED_PAYLOAD, ) async def write_acn_status_ok(self) -> None: """Send acn status ok.""" acn_msg = acn_pb2.AcnMessage() performative = acn_pb2.AcnMessage.Status_Performative() # type: ignore status = AcnMessage.StatusBody( status_code=AcnMessage.StatusBody.StatusCode.SUCCESS, msgs=[] ) AcnMessage.StatusBody.encode( performative.body, status # pylint: disable=no-member ) acn_msg.status.CopyFrom(performative) # pylint: disable=no-member buf = acn_msg.SerializeToString() await self._write(buf) async def write_acn_status_error( self, msg: str, status_code: AcnMessage.StatusBody.StatusCode = AcnMessage.StatusBody.StatusCode.ERROR_GENERIC, # type: ignore ) -> None: """Send acn status error generic.""" acn_msg = acn_pb2.AcnMessage() performative = acn_pb2.AcnMessage.Status_Performative() # type: ignore status = AcnMessage.StatusBody(status_code=status_code, msgs=[msg]) AcnMessage.StatusBody.encode( performative.body, status # pylint: disable=no-member ) acn_msg.status.CopyFrom(performative) # pylint: disable=no-member buf = acn_msg.SerializeToString() await self._write(buf) async def _write(self, data: bytes) -> None: """ Write to the writer stream. :param data: data to write to stream """ await self.pipe.write(data) async def _read(self) -> Optional[bytes]: """ Read from the reader stream. :return: bytes """ return await self.pipe.read() class Libp2pNode: """Libp2p p2p node as a subprocess with named pipes interface.""" def __init__( self, agent_record: AgentRecord, key: Crypto, module_path: str, data_dir: str, clargs: Optional[List[str]] = None, uri: Optional[Uri] = None, public_uri: Optional[Uri] = None, delegate_uri: Optional[Uri] = None, monitoring_uri: Optional[Uri] = None, entry_peers: Optional[Sequence[MultiAddr]] = None, log_file: Optional[str] = None, env_file: Optional[str] = None, logger: logging.Logger = _default_logger, peer_registration_delay: Optional[float] = None, records_storage_path: Optional[str] = None, connection_timeout: Optional[float] = None, max_restarts: int = 5, mailbox_uri: str = "127.0.0.1:8888", ): """ Initialize a p2p libp2p node. :param agent_record: the agent proof-of-representation for peer. :param key: secp256k1 curve private key. :param module_path: the module path. :param data_dir: the data directory. :param clargs: the command line arguments for the libp2p node :param uri: libp2p node ip address and port number in format ipaddress:port. :param public_uri: libp2p node public ip address and port number in format ipaddress:port. :param delegate_uri: libp2p node delegate service ip address and port number in format ipaddress:port. :param monitoring_uri: libp2 node monitoring ip address and port in format ipaddress:port :param entry_peers: libp2p entry peers multiaddresses. :param log_file: the logfile path for the libp2p node :param env_file: the env file path for the exchange of environment variables :param logger: the logger. :param peer_registration_delay: add artificial delay to agent registration in seconds :param records_storage_path: the path where to store the agent records. :param connection_timeout: the connection timeout of the node. :param max_restarts: amount of node restarts during operation. :param mailbox_uri: libp2p mailbox_uri ip address and port number in format ipaddress:port. """ self.record = agent_record self.address = self.record.address # node id in the p2p network self.key = key.private_key self.pub = key.public_key # node uri self.uri = uri if uri is not None else Uri() # node public uri, optional self.public_uri = public_uri # node delegate uri, optional self.delegate_uri = delegate_uri # node monitoring uri, optional self.monitoring_uri = monitoring_uri # entry peer self.entry_peers = entry_peers if entry_peers is not None else [] self.mailbox_uri = mailbox_uri # peer configuration self.peer_registration_delay = peer_registration_delay self.records_storage_path = records_storage_path if ( self.records_storage_path is not None and not Path(self.records_storage_path).is_absolute() ): self.records_storage_path = os.path.join( # pragma: nocover data_dir, self.records_storage_path ) # node startup self.source = os.path.abspath(module_path) self.clargs = clargs if clargs is not None else [] # node libp2p multiaddress self.multiaddrs = [] # type: Sequence[MultiAddr] # log file self.log_file = log_file if log_file is not None else LIBP2P_NODE_LOG_FILE if not Path(self.log_file).is_absolute(): self.log_file = os.path.join(data_dir, self.log_file) # pragma: nocover # env file self.env_file = env_file if env_file is not None else LIBP2P_NODE_ENV_FILE if not Path(self.env_file).is_absolute(): self.env_file = os.path.join(data_dir, self.env_file) # named pipes (fifos) self.pipe = None # type: Optional[IPCChannel] self._loop = None # type: Optional[AbstractEventLoop] self.proc = None # type: Optional[subprocess.Popen] self._log_file_desc = None # type: Optional[IO[str]] self.logger = logger self._connection_timeout = ( connection_timeout if connection_timeout is not None else PIPE_CONN_TIMEOUT ) self._max_restarts = max_restarts self._restart_counter: int = 0 self._is_on_stop: bool = False def _make_env_file(self, pipe_in_path: str, pipe_out_path: str) -> str: # setup config if os.path.exists(self.env_file): os.remove(self.env_file) # pragma: nocover config = "" config += "AEA_AGENT_ADDR={}\n".format(self.address) config += "AEA_P2P_ID={}\n".format(self.key) config += "AEA_P2P_URI={}\n".format(str(self.uri)) config += "AEA_P2P_ENTRY_URIS={}\n".format( ",".join( [ str(maddr) for maddr in self.entry_peers if str(maddr) != str(self.uri) # TOFIX(LR) won't exclude self ] ) ) config += "NODE_TO_AEA={}\n".format(pipe_in_path) config += "AEA_TO_NODE={}\n".format(pipe_out_path) config += "AEA_P2P_URI_PUBLIC={}\n".format( str(self.public_uri) if self.public_uri is not None else "" ) config += "AEA_P2P_DELEGATE_URI={}\n".format( str(self.delegate_uri) if self.delegate_uri is not None else "" ) config += "AEA_P2P_URI_MONITORING={}\n".format( str(self.monitoring_uri) if self.monitoring_uri is not None else "" ) config += "AEA_P2P_POR_ADDRESS={}\n".format(self.record.address) config += "AEA_P2P_POR_PUBKEY={}\n".format(self.record.public_key) config += "AEA_P2P_POR_PEER_PUBKEY={}\n".format( self.record.representative_public_key ) config += "AEA_P2P_POR_SIGNATURE={}\n".format(self.record.signature) config += "AEA_P2P_POR_SERVICE_ID={}\n".format(POR_DEFAULT_SERVICE_ID) config += "AEA_P2P_POR_LEDGER_ID={}\n".format(self.record.ledger_id) config += "AEA_P2P_CFG_REGISTRATION_DELAY={}\n".format( str(self.peer_registration_delay) if self.peer_registration_delay is not None else str(0.0) ) config += "AEA_P2P_CFG_STORAGE_PATH={}\n".format( self.records_storage_path if self.records_storage_path is not None else "" ) config += "AEA_P2P_MAILBOX_URI={}\n".format(self.mailbox_uri) with open( self.env_file, "w", encoding="utf-8" ) as env_file: # overwrite if exists env_file.write(config) return config async def _set_connection_to_node(self) -> bool: if self.pipe is None: raise Exception("pipe was not set") # pragma: nocover return await self.pipe.connect(timeout=self._connection_timeout) def get_client(self) -> NodeClient: """Get client instance to communicate to node.""" if self.pipe is None: raise Exception("pipe was not set") # pragma: nocover return NodeClient(self.pipe, self.record) def _child_watcher_callback(self, *_) -> None: # type: ignore # pragma: nocover """Log if process was terminated before stop was called.""" if self._is_on_stop: return if self.proc is None: return self.proc.poll() returncode = self.proc.returncode self.logger.error( f"Node process with pid {self.proc.pid} was terminated with returncode {returncode}" ) def is_proccess_running(self) -> bool: """Check process is running.""" if not self.proc: return False self.proc.poll() return self.proc.returncode is None async def start(self) -> None: """Start the node.""" self._is_on_stop = False if self._loop is None: self._loop = asyncio.get_event_loop() self._log_file_desc = open( # pylint: disable=consider-using-with self.log_file, "a", 1, encoding="utf-8" ) self._log_file_desc.write("test") self._log_file_desc.flush() # tcp socket on every platform self.pipe = TCPSocketChannel(logger=self.logger) env_file_data = self._make_env_file( pipe_in_path=self.pipe.in_path, pipe_out_path=self.pipe.out_path ) # run node self.logger.info("Starting libp2p node...") self.proc = _golang_module_run( self.source, LIBP2P_NODE_MODULE_NAME, [self.env_file], self._log_file_desc ) if ( platform.system() != "Windows" and sys.version_info.major == 3 and sys.version_info.minor >= 8 ): # pragma: nocover with events.get_child_watcher() as watcher: if watcher: watcher.add_child_handler( self.proc.pid, self._child_watcher_callback ) self.logger.info("Connecting to libp2p node...") try: connected = await self._set_connection_to_node() if not connected: raise Exception("Couldn't connect to libp2p process within timeout") except Exception as e: err_msg = self.get_libp2p_node_error() self.logger.error("Couldn't connect to libp2p process: {}".format(err_msg)) self.logger.error( "Libp2p process configuration:\n{}".format(env_file_data.strip()) ) if err_msg == "": with open(self.log_file, "r", encoding="utf-8") as f: self.logger.error( "Libp2p process log file {}:\n{}".format( self.log_file, f.read() ) ) else: # pragma: nocover self.logger.error( "Please check log file {} for more details.".format(self.log_file) ) await self.stop() raise e self.logger.info("Successfully connected to libp2p node!") self.multiaddrs = self.get_libp2p_node_multiaddrs() self.describe_configuration() async def restart(self) -> None: """Perform node restart.""" if self._restart_counter >= self._max_restarts: raise ValueError(f"Max restarts attempts reached: {self._max_restarts}") await self.stop() await self.start() self._restart_counter += 1 def describe_configuration(self) -> None: """Print a message describing the libp2p node configuration""" msg = LIBP2P_SUCCESS_MESSAGE if self.public_uri is not None: msg += "full DHT mode with " if self.delegate_uri is not None: # pragma: nocover msg += "delegate service reachable at '{}:{}' and relay service enabled. ".format( self.public_uri.host, self.delegate_uri.port ) else: msg += "relay service enabled. " msg += "To join its network use multiaddr '{}'.".format(self.multiaddrs[0]) else: msg += "relayed mode and cannot be used as entry peer." self.logger.info(msg) def get_libp2p_node_multiaddrs(self) -> Sequence[MultiAddr]: """ Get the node's multiaddresses. :return: a list of multiaddresses """ LIST_START = "MULTIADDRS_LIST_START" LIST_END = "MULTIADDRS_LIST_END" multiaddrs = [] # type: List[MultiAddr] with open(self.log_file, "r", encoding="utf-8") as f: lines = f.readlines() found = False for line in lines: if LIST_START in line: found = True multiaddrs = [] continue if found: elem = line.strip() if elem != LIST_END and len(elem) != 0: multiaddrs.append(MultiAddr.from_string(elem)) else: found = False return multiaddrs def get_libp2p_node_error(self) -> str: """ Parses libp2p node logs for critical errors :return: error message if any, empty string otherwise """ CRITICAL_ERROR = "LIBP2P_NODE_PANIC_ERROR" PANIC_ERROR = "panic:" error_msg = "" panic_msg = "" with open(self.log_file, "r", encoding="utf-8") as f: lines = f.readlines() for line in lines: # pragma: nocover if CRITICAL_ERROR in line: parts = line.split(":", 1) error_msg = parts[1].strip() if PANIC_ERROR in line: parts = line.split(":", 1) panic_msg = parts[1].strip() return error_msg if error_msg != "" else panic_msg async def stop(self) -> None: """Stop the node.""" if self.proc is not None: self.logger.debug("Terminating node process {}...".format(self.proc.pid)) self._is_on_stop = True self.proc.poll() self.proc.terminate() self.logger.debug( "Waiting for node process {} to terminate...".format(self.proc.pid) ) self.proc.wait() if self._log_file_desc is None: raise ValueError("log file descriptor is not set.") # pragma: nocover self._log_file_desc.close() else: self.logger.debug("Called stop when process not set!") # pragma: no cover if self.pipe is not None: try: await self.pipe.close() except Exception as e: # pragma: nocover pylint: disable=broad-except self.logger.exception((f"Failure during pipe closing. Exception: {e}")) self.pipe = None else: self.logger.debug("Called stop when pipe not set!") # pragma: no cover if os.path.exists(self.env_file): os.remove(self.env_file) class P2PLibp2pConnection(Connection): """A libp2p p2p node connection.""" connection_id = PUBLIC_ID DEFAULT_MAX_RESTARTS = 5 def __init__(self, **kwargs: Any) -> None: """Initialize a p2p libp2p connection.""" super().__init__(**kwargs) ledger_id = self.configuration.config.get("ledger_id", DEFAULT_LEDGER) if ledger_id not in SUPPORTED_LEDGER_IDS: raise ValueError( # pragma: nocover "Ledger id '{}' is not supported. Supported ids: '{}'".format( ledger_id, SUPPORTED_LEDGER_IDS ) ) libp2p_local_uri: Optional[str] = self.configuration.config.get("local_uri") libp2p_public_uri: Optional[str] = self.configuration.config.get("public_uri") libp2p_delegate_uri: Optional[str] = self.configuration.config.get( "delegate_uri" ) libp2p_monitoring_uri: Optional[str] = self.configuration.config.get( "monitoring_uri" ) libp2p_entry_peers = self.configuration.config.get("entry_peers") if libp2p_entry_peers is None: libp2p_entry_peers = [] libp2p_entry_peers = list(cast(List, libp2p_entry_peers)) log_file: Optional[str] = self.configuration.config.get("log_file") env_file: Optional[str] = self.configuration.config.get("env_file") peer_registration_delay: Optional[str] = self.configuration.config.get( "peer_registration_delay" ) records_storage_path: Optional[str] = self.configuration.config.get( "storage_path" ) node_connection_timeout: Optional[float] = self.configuration.config.get( "node_connection_timeout", PIPE_CONN_TIMEOUT ) if ( self.has_crypto_store and self.crypto_store.crypto_objects.get(ledger_id, None) is not None ): # pragma: no cover key = self.crypto_store.crypto_objects[ledger_id] else: raise ValueError( f"Couldn't find connection key for {str(ledger_id)} in connections keys. " "Please ensure agent private key is added with `aea add-key`." ) uri = None if libp2p_local_uri is not None: uri = Uri(libp2p_local_uri) public_uri = None if libp2p_public_uri is not None: public_uri = Uri(libp2p_public_uri) delegate_uri = None if libp2p_delegate_uri is not None: # pragma: nocover delegate_uri = Uri(libp2p_delegate_uri) monitoring_uri = None if libp2p_monitoring_uri is not None: monitoring_uri = Uri(libp2p_monitoring_uri) # pragma: nocover entry_peers = [ MultiAddr.from_string(str(maddr)) for maddr in libp2p_entry_peers ] delay = None if peer_registration_delay is not None: try: delay = float(peer_registration_delay) except ValueError: raise ValueError( f"peer_registration_delay {peer_registration_delay} must be a float number in seconds" ) if public_uri is None: # node will be run as a ClientDHT # requires entry peers to use as relay if entry_peers is None or len(entry_peers) == 0: raise ValueError( # pragma: no cover "At least one Entry Peer should be provided when node is run in relayed mode" ) if delegate_uri is not None: # pragma: no cover self.logger.warning( "Ignoring Delegate Uri configuration as node is run in relayed mode" ) else: # node will be run as a full NodeDHT if uri is None: raise ValueError( # pragma: no cover "Local Uri must be set when Public Uri is provided. " "Hint: they are the same for local host/network deployment" ) # check if node's public host and entry peers hosts are either # both private or both public if not _ip_all_private_or_all_public( [public_uri.host] + [maddr.host for maddr in entry_peers] ): raise ValueError( # pragma: nocover "Node's public ip and entry peers ip addresses are not in the same ip address space (private/public)" ) cert_requests = self.configuration.cert_requests if cert_requests is None or len(cert_requests) != 1: raise ValueError( # pragma: no cover "cert_requests field must be set and contain exactly one entry!" ) cert_request = cert_requests[0] agent_record = AgentRecord.from_cert_request( cert_request, self.address, key.public_key, Path(self.data_dir) ) # libp2p local node self.logger.debug("Public key used by libp2p node: {}".format(key.public_key)) if self.configuration.config.get("mailbox_uri"): mailbox_uri = str(self.configuration.config.get("mailbox_uri")) else: mailbox_uri = "" module_dir = self._check_node_built() self.node = Libp2pNode( agent_record, key, module_dir, self.data_dir, LIBP2P_NODE_CLARGS, uri, public_uri, delegate_uri, monitoring_uri, entry_peers, log_file, env_file, self.logger, delay, records_storage_path, node_connection_timeout, max_restarts=self.configuration.config.get( "max_node_restarts", self.DEFAULT_MAX_RESTARTS ), mailbox_uri=mailbox_uri, ) self._in_queue = None # type: Optional[asyncio.Queue] self._receive_from_node_task = None # type: Optional[asyncio.Future] self._node_client: Optional[NodeClient] = None self._send_queue: Optional[asyncio.Queue] = None self._send_task: Optional[asyncio.Task] = None def _check_node_built(self) -> str: """Check node built.""" if self.configuration.build_directory is None: raise ValueError("Connection Configuration build directory is not set!") libp2p_node_module_path = os.path.join( self.configuration.build_directory, LIBP2P_NODE_MODULE_NAME ) enforce( os.path.exists(libp2p_node_module_path), f"Module {LIBP2P_NODE_MODULE_NAME} is not present in {self.configuration.build_directory}, please call the `aea build` command first!", ) return self.configuration.build_directory async def connect(self) -> None: """Set up the connection.""" if self.is_connected: return # pragma: nocover # start libp2p node with self._connect_context(): self.node.logger = self.logger await self._start_node() # starting receiving msgs self._in_queue = asyncio.Queue() self._send_queue = asyncio.Queue() self._receive_from_node_task = asyncio.ensure_future( self._receive_from_node(), loop=self.loop ) self._send_task = self.loop.create_task(self._send_loop()) async def _start_node(self) -> None: """Start node and set node client instance.""" await self.node.start() self._node_client = self.node.get_client() async def _restart_node(self) -> None: """Stop and start node again.""" await self.node.stop() await self._start_node() async def disconnect(self) -> None: """Disconnect from the channel.""" if self.is_disconnected: return # pragma: nocover self.state = ConnectionStates.disconnecting try: if self._receive_from_node_task is not None: self._receive_from_node_task.cancel() self._receive_from_node_task = None if self._send_task is not None: self._send_task.cancel() self._send_task = None await self.node.stop() if self._in_queue is not None: self._in_queue.put_nowait(None) else: self.logger.debug( # pragma: nocover "Called disconnect when input queue not initialized." ) finally: self.state = ConnectionStates.disconnected async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. Blocking. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ try: if self._in_queue is None: raise ValueError("Input queue not initialized.") # pragma: nocover envelope = await self._in_queue.get() if envelope is None: # pragma: nocover self.logger.debug("Received None.") return None return envelope except CancelledError: # pragma: no cover self.logger.debug("Receive cancelled.") return None except Exception as e: # pragma: nocover # pylint: disable=broad-except self.logger.exception(e) return None async def _send_envelope_with_node_client(self, envelope: Envelope) -> None: if not self._node_client: # pragma: nocover raise ValueError(f"Node client not set! Can not send envelope: {envelope}") if not self.node.pipe: # pragma: nocover raise ValueError("Node is not connected") try: await self._node_client.send_envelope(envelope) return except asyncio.CancelledError: # pylint: disable=try-except-raise raise # pragma: nocover except Exception as e: # pylint: disable=broad-except self.logger.exception( f"Failed to send. Exception: {e}. Try recover connection to node and send again." ) try: if self.node.is_proccess_running(): await self.node.pipe.connect() await self._node_client.send_envelope(envelope) self.logger.debug("Envelope sent after reconnect to node") return except asyncio.CancelledError: # pylint: disable=try-except-raise raise # pragma: nocover except Exception as e: # pylint: disable=broad-except self.logger.exception( f"Failed to send after pipe reconnect. Exception: {e}. Try recover connection to node and send again." ) try: await self._restart_node() await self._node_client.send_envelope(envelope) except asyncio.CancelledError: # pylint: disable=try-except-raise raise # pragma: nocover except Exception as e: # pylint: disable=broad-except self.logger.exception( f"Failed to send after node restart. Exception: {e}. Try recover connection to node and send again." ) raise async def _send_loop(self) -> None: """Handle message in the send queue.""" if not self._send_queue or not self._node_client: # pragma: nocover self.logger.error("Send loop not started cause not connected properly.") return try: while self.is_connected: envelope = await self._send_queue.get() await self._send_envelope_with_node_client(envelope) except asyncio.CancelledError: # pylint: disable=try-except-raise raise # pragma: nocover except Exception: # pylint: disable=broad-except # pragma: nocover self.logger.exception( f"Failed to send an envelope {envelope}. Stop connection." ) await asyncio.shield(self.disconnect()) async def send(self, envelope: Envelope) -> None: """ Send messages. :param envelope: the envelope """ if not self._node_client or not self._send_queue: raise ValueError("Node is not connected!") # pragma: nocover self._ensure_valid_envelope_for_external_comms(envelope) await self._send_queue.put(envelope) async def _read_envelope_from_node(self) -> Optional[Envelope]: if not self._node_client: raise ValueError("Node is not connected!") # pragma: nocover try: return await self._node_client.read_envelope() except asyncio.CancelledError: # pylint: disable=try-except-raise raise # pragma: nocover except Exception as e: # pylint: disable=broad-except self.logger.exception( f"Failed to read. Exception: {e}. Try reconnect to node and read again." ) await self._restart_node() return await self._node_client.read_envelope() async def _receive_from_node(self) -> None: """Receive data from node.""" while True: if self._in_queue is None: raise ValueError("Input queue not initialized.") # pragma: nocover if not self._node_client: raise ValueError("Node is not connected!") # pragma: nocover envelope = await self._read_envelope_from_node() if envelope is None: break self._in_queue.put_nowait(envelope) ================================================ FILE: packages/fetchai/connections/p2p_libp2p/connection.yaml ================================================ name: p2p_libp2p author: fetchai version: 0.27.5 type: connection description: The p2p libp2p connection implements an interface to standalone golang go-libp2p node that can exchange aea envelopes with other agents connected to the same DHT. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qmf8Gh38S4rtZVh2yPUTdHrAEpovt1ULg4bccaFNZfYxCt __init__.py: Qmcwvb5isp1zoF57phgDHEPTNLuPa5zCkEC4JSsDUUHE78 check_dependencies.py: QmXCg3mGhHzGxxbaTtHKY1HfumubPKryiaBeBMbJEZLQTE connection.py: QmZxZagVYj9NVzAYtM9UyMPsiXjz6uPuF8Ywb2WnJnTDiZ consts.py: QmXi6edKonz6SuAnRnMURRRU62GNZa9TRhqiDxmnwLB4Sp libp2p_node/.dockerignore: QmVwyNjya468nRTxSjFP73dSzQdSffp74osz5dGEAHHweA libp2p_node/Dockerfile: QmeZ6KJf4cpgL7DY6qdWetVfTPPijUvHxoCUbYgSS3SsWM libp2p_node/Makefile: Qmezxsp96mW85mJhjGAarLmwWJG9JvGJowcY5h56oPB9bt libp2p_node/README.md: QmR1Lk6utZWxN1rEBmTodenwfyjTucv5akx9GGcFzxmPjb libp2p_node/acn/utils.go: Qmb39aDGu7N5a8JPdHeRsjSzJ9fyKp2jT3EkULocvGGYKV libp2p_node/aea/api.go: QmW7mHHShumfDHg9Ds1nxHZJN4EAHGLrr15jLX7kKYkcV9 libp2p_node/aea/envelope.pb.go: QmRfUNGpCeVJfsW3H1MzCN4pwDWgumfyWufVFp6xvUjjug libp2p_node/aea/envelope.proto: QmVuvesmfgzj5aKnbFoCocoGEv3T9MR7u6KWn7CT5yfjGi libp2p_node/aea/pipe.go: QmdY77nQhJt5ojfcpJn2quaxdseGxS8GymQbzNvizkXNoz libp2p_node/aea/utils.go: QmbPhDuPZzzA8hrf7U5uLxsVQqY31BHJbv9fiGYEPDg1Go libp2p_node/common/common.go: QmeGr6xMzqkBVJMnWzCTcXJVQj2A8RK64mhTSewZJKpiJa libp2p_node/dht/common/handlers.go: Qmb5bvheRaddLXmcjwLNPyKq3SX5dqWUwc6aop5YjE9WMx libp2p_node/dht/dhtclient/dhtclient.go: QmP9Jzr7s38iJPuWUuMuFJ8TiepHT2AwaYzzKZ7hgWAUWg libp2p_node/dht/dhtclient/dhtclient_test.go: QmY5YQVYPHYdHc3GXPgbMQBtMstEpYiik1KtZH9ZchJVNh libp2p_node/dht/dhtclient/options.go: QmbjHT3ZXFo5GaMDTFbpZVRNiq3KPeoQWuGQuDaz7QJfaU libp2p_node/dht/dhtnode/dhtnode.go: QmfYx33eCLKLz7cVGrj3wTRF9oLjeS8yXR7kSKmQSfHPP8 libp2p_node/dht/dhtnode/streams.go: Qmc2JcyiU4wHsgDj6aUunMAp4c5yMzo2ixeqRZHSW5PVwo libp2p_node/dht/dhtnode/utils.go: QmUabTTyRXixEEfksTzSNiHsqF9GcQ9qXpfwVY3VNsXBYK libp2p_node/dht/dhtpeer/benchmarks_test.go: QmeXZbWBxwGY33oRRogFPv61qJu28ufmoprGkmZVrQ9kEV libp2p_node/dht/dhtpeer/dhtpeer.go: Qme7jvbR5JLnxvnRxaPxZ76Pq2Pece1C95ZkJxoY9a4YwE libp2p_node/dht/dhtpeer/dhtpeer_test.go: QmegkzZpi9vpFHTDxrSUKFDWQhBviL63AYMRNgcHpe5wcu libp2p_node/dht/dhtpeer/mailbox.go: QmSgWxkVVQQSrMux8WNvvLbWRfZCb7ZChr6PjumyoW5vdT libp2p_node/dht/dhtpeer/notifee.go: Qmes2KPbWecKZu6Bh3mThEsPs74W3LwD6y3Mzrai1fV7zi libp2p_node/dht/dhtpeer/options.go: QmXiQ1iKHWCGZLKu2YTRkkLkJ7opR7LzsWxiwktKYHc3Va libp2p_node/dht/dhtpeer/utils.go: QmPWx5716sBX43gkCqHHXMmQ8hcg5KBbXqCsRGAnqJcSZw libp2p_node/dht/dhttests/dhttests.go: QmSe8XsEhMJAWuokeWtPLXP4m9G5rDEAXrrQWBcma8t71c libp2p_node/dht/monitoring/file.go: QmZwH59RcraRfgYjugVafVojakv1oEcgBUKuMdFAoeZBhS libp2p_node/dht/monitoring/prometheus.go: QmQvXjEozVPMvRjda5WGRAU5b7cfUcRZUACQkTESG7Aewu libp2p_node/dht/monitoring/service.go: QmT47y2LHZECYcoE2uJ9QCGh3Kq8ePhYedo8dQE7X7v6YV libp2p_node/go.mod: QmWJwSZP3BzuyZjSUUADTuKD7Y3LhJE8NSd8CVke7noFeD libp2p_node/go.sum: QmSPKTBqNZJaTSacn5cPN2sC3MqwzeAN7L2RnCwRhpGR1Q libp2p_node/libp2p_node.go: Qmcp82gLaEpyJU7Q8NnsB6Wp7wF5FMV5ZRv9Y3XdNgPJ2L libp2p_node/link: QmXoSqhnHAFDZiZYT3F1txkjrsjtxDAkPVg9oG9Kvpv2dx libp2p_node/mocks/mock_host.go: QmSJ7g6S2PGhC5j8xBQyrjs56hXJGEewkgFgiivyZDthbW libp2p_node/mocks/mock_net.go: QmVKnkDdH8XCJ9jriEkZui4NoB36GBYF2DtfX8uCqAthMw libp2p_node/mocks/mock_network.go: QmbVVvd3wrY6PnVs1rn9T9m6FD5kbmSVJLwhSxUgSLAiM5 libp2p_node/mocks/mock_peerstore.go: QmaPCBrwsTeWCHZoAKDzaxN6uhY3bez1karzeGeovWYwkB libp2p_node/protocols/acn/v1_0_0/acn.pb.go: QmWDoniHr3wa5freLHDESv7SgC9ggYenrLyibw4jFkTc4r libp2p_node/protocols/acn/v1_0_0/acn.proto: QmcguMcKttJfsuPYrUVWFJ1Trr1VxrSSfan94Z3yNAxNEW libp2p_node/protocols/acn/v1_0_0/acn.yaml: QmeqaYRWL5bVRE8FKgbiAspgFkoi5HKtkSncT7fCf1JAex libp2p_node/utils/utils.go: QmRYpCrW8fia1qtmraGcGRhFz5KgAJ8zwGBFP2DJkpFnm8 libp2p_node/utils/utils_test.go: QmaUTrtqhPYcCLTHnJic4EN7R1pZY8ra7QbeRrPuedgAuc fingerprint_ignore_patterns: [] build_entrypoint: check_dependencies.py connections: [] protocols: - fetchai/acn:1.1.7 class_name: P2PLibp2pConnection config: delegate_uri: 127.0.0.1:11000 entry_peers: [] ledger_id: fetchai local_uri: 127.0.0.1:9000 log_file: libp2p_node.log max_node_restarts: 5 monitoring_uri: null node_connection_timeout: 10 public_uri: 127.0.0.1:9000 storage_path: null cert_requests: - identifier: acn ledger_id: fetchai message_format: '{public_key}' not_after: '2023-01-01' not_before: '2022-01-01' public_key: fetchai save_path: .certs/conn_cert.txt excluded_protocols: [] restricted_to_protocols: [] dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/p2p_libp2p/consts.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains some constants for the p2p libp2p connection.""" import os import platform LIBP2P_NODE_MODULE_NAME = "libp2p_node" LIBP2P_NODE_MODULE = str( os.path.join(os.path.abspath(os.path.dirname(__file__)), LIBP2P_NODE_MODULE_NAME) ) if platform.system() == "Windows": # pragma: nocover LIBP2P_NODE_MODULE_NAME += ".exe" LIBP2P_NODE_DEPS_DOWNLOAD_TIMEOUT = 660 # time to download ~66Mb ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/.dockerignore ================================================ libp2p_node ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/Dockerfile ================================================ FROM golang:1.17.5-buster as builder USER root WORKDIR /build COPY ./ ./ RUN go build FROM scratch AS export-stage COPY --from=builder /build/libp2p_node libp2p_node ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/Makefile ================================================ test: go test -gcflags=-l -p 1 -timeout 0 -count 1 -covermode=atomic -coverprofile=coverage.txt -v ./... go tool cover -func=coverage.txt lint: golines . -w golangci-lint run build: go build install: go get -v -t -d ./... race_test: go test -gcflags=-l -p 1 -timeout 0 -count 1 -race -v ./... build_in_docker: DOCKER_BUILDKIT=1 docker build --output . . ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/README.md ================================================ # Libp2p Node The `libp2p_node` is an integral part of the ACN. ## ACN - Agent Communication Network The agent communication network (ACN) provides a system for agents to find each other and communicate, solely based on their wallet addresses. It addresses the message delivery problem. For more details check out the [docs](https://github.com/fetchai/agents-aea/blob/main/docs/acn.md). ## Development To run all tests run: ``` bash go test -p 1 -timeout 0 -count 1 -v ./... ``` To lint: ``` bash golines . -w golangci-lint run staticcheck ./... ``` For mocks generation: check ## Messaging patterns Interaction protocol ___ ACN ___ TCP/UDP/... ___ ### Messaging patterns inwards ACN Connection (`p2p_libp2p_client`) > Delegate Client > Relay Peer > Peer (Discouraged!) Connection (`p2p_libp2p_client`) > Delegate Client > Peer Connection (`p2p_libp2p`) > Relay Peer > Peer Connection (`p2p_libp2p`) > Peer ### Messaging patterns outwards ACN Peer > Relay Peer > Delegate Client > Connection (`p2p_libp2p_client`) (Discouraged!) Peer > Relay Peer > Connection (`p2p_libp2p`) Peer > Delegate Client > Connection (`p2p_libp2p_client`) Peer > Connection (`p2p_libp2p`) In total 4*4 = 16 patterns (practically: 3*3 = 9 patterns) ## Guarantees ACN should guarantee total ordering of messages for all agent pairs, independent of the type of connection and ACN messaging pattern used. ## Advanced feature (post `v1`) Furthermore, there is the agent mobility. An agent can move between entry-points (Relay Peer/Peer/Delegate Client). The ACN must ensure that all messaging patterns maintain total ordering of messages for agent pairs during the move. ## ACN protocols The ACN has the following protocols: - register - lookup - unregister (dealt with by DHT defaults) - DHT default protocols in libp2p - message delivery protocol ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/acn/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package acn import ( "errors" "fmt" "os" "strings" "time" acn_protocol "libp2p_node/protocols/acn/v1_0_0" "github.com/rs/zerolog" proto "google.golang.org/protobuf/proto" ) type StatusBody = acn_protocol.AcnMessage_StatusBody type AgentRecord = acn_protocol.AcnMessage_AgentRecord type AcnMessage = acn_protocol.AcnMessage type LookupRequest = acn_protocol.AcnMessage_LookupRequest type LookupResponse = acn_protocol.AcnMessage_LookupResponse type Status = acn_protocol.AcnMessage_Status type LookupRequestPerformative = acn_protocol.AcnMessage_Lookup_Request_Performative type LookupResponsePerformative = acn_protocol.AcnMessage_Lookup_Response_Performative type StatusPerformative = acn_protocol.AcnMessage_Status_Performative type RegisterPerformative = acn_protocol.AcnMessage_Register_Performative type Register = acn_protocol.AcnMessage_Register type AeaEnvelope = acn_protocol.AcnMessage_AeaEnvelope type AeaEnvelopePerformative = acn_protocol.AcnMessage_Aea_Envelope_Performative const ERROR_DECODE = acn_protocol.AcnMessage_StatusBody_ERROR_DECODE const SUCCESS = acn_protocol.AcnMessage_StatusBody_SUCCESS const ERROR_UNEXPECTED_PAYLOAD = acn_protocol.AcnMessage_StatusBody_ERROR_UNEXPECTED_PAYLOAD const ERROR_AGENT_NOT_READY = acn_protocol.AcnMessage_StatusBody_ERROR_AGENT_NOT_READY const ERROR_UNKNOWN_AGENT_ADDRESS = acn_protocol.AcnMessage_StatusBody_ERROR_UNKNOWN_AGENT_ADDRESS const ERROR_GENERIC = acn_protocol.AcnMessage_StatusBody_ERROR_GENERIC const ERROR_WRONG_AGENT_ADDRESS = acn_protocol.AcnMessage_StatusBody_ERROR_WRONG_AGENT_ADDRESS const ERROR_UNSUPPORTED_LEDGER = acn_protocol.AcnMessage_StatusBody_ERROR_UNSUPPORTED_LEDGER const ERROR_WRONG_PUBLIC_KEY = acn_protocol.AcnMessage_StatusBody_ERROR_WRONG_PUBLIC_KEY const ERROR_INVALID_PROOF = acn_protocol.AcnMessage_StatusBody_ERROR_INVALID_PROOF type Status_ErrCode = acn_protocol.AcnMessage_StatusBody_StatusCodeEnum var logger zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: "15:04:05.000", }). With().Timestamp(). Str("package", "AeaApiACN"). Logger() const CurrentVersion = "0.1.0" func ignore(err error) { if err != nil { logger.Error().Str("err", err.Error()).Msgf("IGNORED: %s", err.Error()) } } type ACNError struct { ErrorCode Status_ErrCode Err error } func (err *ACNError) Error() string { return err.Err.Error() } func DecodeAcnMessage(buf []byte) (string, *AeaEnvelopePerformative, *StatusBody, *ACNError) { response := &AcnMessage{} err := proto.Unmarshal(buf, response) msg_type := "" if err != nil { logger.Error().Str("err", err.Error()).Msgf("while decoding acn message") return msg_type, nil, nil, &ACNError{ErrorCode: ERROR_DECODE, Err: err} } // response is either a LookupResponse or Status var aeaEnvelope *AeaEnvelopePerformative = nil var status *StatusBody = nil switch pl := response.Performative.(type) { case *AeaEnvelope: aeaEnvelope = pl.AeaEnvelope msg_type = "aea_envelope" case *Status: status = pl.Status.Body msg_type = "status" default: err = fmt.Errorf("unexpected ACN Message: %s", response) logger.Error().Msg(err.Error()) return msg_type, nil, nil, &ACNError{ErrorCode: ERROR_UNEXPECTED_PAYLOAD, Err: err} } return msg_type, aeaEnvelope, status, nil } func WaitForStatus(ch chan *StatusBody, timeout time.Duration) (*StatusBody, error) { select { case m := <-ch: return m, nil case <-time.After(timeout): err := errors.New("ACN send acknowledge timeout") logger.Error().Msg(err.Error()) return nil, err } } func SendAcnSuccess(pipe Pipe) error { status := &StatusBody{Code: SUCCESS} performative := &StatusPerformative{Body: status} msg := &AcnMessage{ Performative: &Status{Status: performative}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on encoding acn status message") return err } err = pipe.Write(buf) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on sending acn status message") } return err } func SendAcnError(pipe Pipe, error_msg string, err_codes ...Status_ErrCode) error { var err_code Status_ErrCode if len(err_codes) == 0 { err_code = ERROR_GENERIC } else { err_code = err_codes[0] } status := &StatusBody{Code: err_code, Msgs: []string{error_msg}} msg := &AcnMessage{ Performative: &Status{Status: &StatusPerformative{Body: status}}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on encoding acn status message") return err } err = pipe.Write(buf) if err != nil { logger.Error().Str("err", err.Error()).Msgf("error on sending acn status message") } return err } func EncodeAcnEnvelope(envelope_bytes []byte, record *AgentRecord) ([]byte, error) { var performative *AeaEnvelopePerformative if record != nil { performative = &AeaEnvelopePerformative{Envelope: envelope_bytes, Record: record} } else { performative = &AeaEnvelopePerformative{Envelope: envelope_bytes} } msg := &AcnMessage{ Performative: &AeaEnvelope{AeaEnvelope: performative}, } buf, err := proto.Marshal(msg) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("while serializing envelope bytes: %s", envelope_bytes) } return buf, err } type Pipe interface { Connect() error Read() ([]byte, error) Write(data []byte) error Close() error } type StatusQueue interface { AddAcnStatusMessage(status *StatusBody, counterpartyID string) } func ReadAgentRegistrationMessage(pipe Pipe) (*RegisterPerformative, error) { var register *RegisterPerformative buf, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msgf("while receiving agent's registration request") return nil, err } msg := &AcnMessage{} err = proto.Unmarshal(buf, msg) if err != nil { logger.Error().Str("err", err.Error()).Msgf("couldn't deserialize acn registration message") // TOFIX(LR) setting Msgs to err.Error is potentially a security vulnerability acn_send_error := SendAcnError(pipe, err.Error(), ERROR_DECODE) ignore(acn_send_error) return nil, err } switch pl := msg.Performative.(type) { case *Register: register = pl.Register default: err = errors.New("Unexpected payload") acn_send_error := SendAcnError(pipe, err.Error(), ERROR_UNEXPECTED_PAYLOAD) ignore(acn_send_error) return nil, err } return register, nil } func SendEnvelopeMessageAndWaitForStatus( pipe Pipe, envelope_bytes []byte, acn_status_chan chan *StatusBody, acnStatusTimeout time.Duration, ) error { err := SendEnvelopeMessage(pipe, envelope_bytes, nil) if err != nil { return err } status, err := WaitForStatus(acn_status_chan, acnStatusTimeout) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on envelope sent status wait") return err } if status.Code != SUCCESS { logger.Error(). Str("op", "send_envelope"). Msgf("acn confirmation status is not Status Success: %d.", status.Code) return fmt.Errorf( "send envelope: acn confirmation status is not Status Success: %d", status.Code, ) } return err } func ReadLookupRequest(pipe Pipe) (string, error) { buf, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msgf("while reading message from stream") return "", err } msg := &AcnMessage{} err = proto.Unmarshal(buf, msg) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("couldn't deserialize acn lookup request message") // TOFIX(LR) setting Msgs to err.Error is potentially a security vulnerability acn_send_error := SendAcnError(pipe, err.Error(), ERROR_DECODE) ignore(acn_send_error) return "", err } // Get LookupRequest message var lookupRequest *LookupRequestPerformative switch pl := msg.Performative.(type) { case *LookupRequest: lookupRequest = pl.LookupRequest default: err = errors.New("Unexpected payload") acn_send_error := SendAcnError(pipe, err.Error(), ERROR_UNEXPECTED_PAYLOAD) ignore(acn_send_error) return "", err } reqAddress := lookupRequest.AgentAddress return reqAddress, nil } func SendLookupRequest(pipe Pipe, address string) error { lookupRequest := &LookupRequestPerformative{AgentAddress: address} msg := &AcnMessage{ Performative: &LookupRequest{LookupRequest: lookupRequest}, } buf, err := proto.Marshal(msg) if err != nil { return err } err = pipe.Write([]byte(buf)) return err } func ReadLookupResponse(pipe Pipe) (*AgentRecord, error) { buf, err := pipe.Read() if err != nil { return nil, err } response := &AcnMessage{} err = proto.Unmarshal(buf, response) if err != nil { return nil, err } var lookupResponse *LookupResponsePerformative = nil var status *StatusPerformative = nil switch pl := response.Performative.(type) { case *LookupResponse: lookupResponse = pl.LookupResponse case *Status: status = pl.Status default: err = errors.New("Unexpected Acn Message") logger.Error().Str("err", err.Error()).Msgf("couldn't deserialize acn lookup response message") return nil, err } if status != nil { err = errors.New( "Failed agent lookup response " + status.Body.Code.String() + " : " + strings.Join( status.Body.Msgs, ":", ), ) return nil, err } return lookupResponse.Record, nil } func SendLookupResponse(pipe Pipe, record *AgentRecord) error { lookupResponse := &LookupResponsePerformative{Record: record} response := &AcnMessage{ Performative: &LookupResponse{LookupResponse: lookupResponse}, } buf, err := proto.Marshal(response) if err != nil { return err } err = pipe.Write(buf) return err } func SendEnvelopeMessage(pipe Pipe, envelope_bytes []byte, record *AgentRecord) error { acnMsgBytes, err := EncodeAcnEnvelope(envelope_bytes, record) if err != nil { return err } err = pipe.Write(acnMsgBytes) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on pipe write") return err } return nil } func SendAgentRegisterMessage(pipe Pipe, agentRecord *AgentRecord) error { registration := &RegisterPerformative{Record: agentRecord} msg := &AcnMessage{ Performative: &Register{Register: registration}, } buf, err := proto.Marshal(msg) if err != nil { return err } err = pipe.Write(buf) if err != nil { return err } status, err := ReadAcnStatus(pipe) if err != nil { return err } if status.Code != SUCCESS { return errors.New("Registration failed: " + strings.Join(status.Msgs, ":")) } return nil } func ReadAcnStatus(pipe Pipe) (*StatusBody, error) { buf, err := pipe.Read() if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on pipe read") return nil, err } response := &AcnMessage{} err = proto.Unmarshal(buf, response) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on acn decode") return nil, err } // response is expected to be a Status var status *StatusPerformative switch pl := response.Performative.(type) { case *Status: status = pl.Status default: err = errors.New("Unexpected Acn Message") return nil, err } return status.Body, nil } func ReadEnvelopeMessage(pipe Pipe) (*AeaEnvelopePerformative, error) { buf, err := pipe.Read() if err != nil { return nil, err } messageType, envelope, _, acnErr := DecodeAcnMessage(buf) if acnErr != nil { err = SendAcnError( pipe, acnErr.Error(), acnErr.ErrorCode, ) ignore(err) return nil, acnErr.Err } if messageType != "aea_envelope" { return nil, errors.New("unexpected payload for acn message") } return envelope, nil } func PerformAddressLookup(pipe Pipe, address string) (*AgentRecord, error) { err := SendLookupRequest(pipe, address) if err != nil { return nil, err } return ReadLookupResponse(pipe) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/aea/api.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aea import ( "errors" "log" "net" "os" "strconv" "strings" "time" "github.com/joho/godotenv" "github.com/rs/zerolog" proto "google.golang.org/protobuf/proto" acn "libp2p_node/acn" common "libp2p_node/common" ) const AcnStatusTimeout = 15.0 * time.Second const SendQueueSize = 100 const OutQueueSize = 100 // code redandency to avoid import cycle var logger zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: "15:04:05.000", }). With().Timestamp(). Str("package", "AeaApi"). Logger() /* AeaApi type */ type AeaApi struct { msgin_path string msgout_path string agent_addr string agent_record *acn.AgentRecord id string entry_peers []string host string port uint16 host_public string port_public uint16 host_delegate string port_delegate uint16 host_monitoring string port_monitoring uint16 mailbox_uri string registrationDelay float64 recordsStoragePath string pipe common.Pipe out_queue chan *Envelope send_queue chan *Envelope closing bool connected bool sandbox bool standalone bool acn_status_chan chan *acn.StatusBody } func (aea AeaApi) MailboxUri() string { return aea.mailbox_uri } func (aea AeaApi) AeaAddress() string { return aea.agent_addr } func (aea AeaApi) PrivateKey() string { return aea.id } func (aea AeaApi) Address() (string, uint16) { return aea.host, aea.port } func (aea AeaApi) PublicAddress() (string, uint16) { return aea.host_public, aea.port_public } func (aea AeaApi) DelegateAddress() (string, uint16) { return aea.host_delegate, aea.port_delegate } func (aea AeaApi) MonitoringAddress() (string, uint16) { return aea.host_monitoring, aea.port_monitoring } func (aea AeaApi) EntryPeers() []string { return aea.entry_peers } func (aea AeaApi) AgentRecord() *acn.AgentRecord { return aea.agent_record } func (aea AeaApi) RegistrationDelayInSeconds() float64 { return aea.registrationDelay } func (aea AeaApi) RecordStoragePath() string { return aea.recordsStoragePath } func (aea AeaApi) Put(envelope *Envelope) error { if aea.standalone { errorMsg := "node running in standalone mode" logger.Warn().Msgf(errorMsg) return errors.New(errorMsg) } aea.send_queue <- envelope return nil } func (aea *AeaApi) Get() *Envelope { if aea.standalone { errorMsg := "node running in standalone mode" logger.Warn().Msgf(errorMsg) return nil } return <-aea.out_queue } func (aea *AeaApi) Queue() <-chan *Envelope { return aea.out_queue } func (aea *AeaApi) Connected() bool { return aea.connected || aea.standalone } func (aea *AeaApi) Stop() { aea.send_queue <- nil aea.closing = true aea.stop() close(aea.out_queue) close(aea.send_queue) } func (aea *AeaApi) Init() error { zerolog.TimeFieldFormat = time.RFC3339Nano if aea.sandbox { return nil } if aea.connected { return nil } aea.connected = false env_file := os.Args[1] logger.Debug().Msgf("env_file: %s", env_file) // get config err := godotenv.Overload(env_file) if err != nil { log.Fatal("Error loading env file") } aea.msgin_path = os.Getenv("AEA_TO_NODE") aea.msgout_path = os.Getenv("NODE_TO_AEA") aea.agent_addr = os.Getenv("AEA_AGENT_ADDR") aea.id = os.Getenv("AEA_P2P_ID") entry_peers := os.Getenv("AEA_P2P_ENTRY_URIS") uri := os.Getenv("AEA_P2P_URI") uri_public := os.Getenv("AEA_P2P_URI_PUBLIC") uri_delegate := os.Getenv("AEA_P2P_DELEGATE_URI") uri_monitoring := os.Getenv("AEA_P2P_URI_MONITORING") por_address := os.Getenv("AEA_P2P_POR_ADDRESS") if por_address != "" { record := &acn.AgentRecord{Address: por_address} record.PublicKey = os.Getenv("AEA_P2P_POR_PUBKEY") record.PeerPublicKey = os.Getenv("AEA_P2P_POR_PEER_PUBKEY") record.Signature = os.Getenv("AEA_P2P_POR_SIGNATURE") record.ServiceId = os.Getenv("AEA_P2P_POR_SERVICE_ID") record.LedgerId = os.Getenv("AEA_P2P_POR_LEDGER_ID") aea.agent_record = record } aea.mailbox_uri = os.Getenv("AEA_P2P_MAILBOX_URI") registrationDelay := os.Getenv("AEA_P2P_CFG_REGISTRATION_DELAY") aea.recordsStoragePath = os.Getenv("AEA_P2P_CFG_STORAGE_PATH") logger.Debug().Msgf("msgin_path: %s", aea.msgin_path) logger.Debug().Msgf("msgout_path: %s", aea.msgout_path) logger.Debug().Msgf("id: %s", aea.id) logger.Debug().Msgf("addr: %s", aea.agent_addr) logger.Debug().Msgf("entry_peers: %s", entry_peers) logger.Debug().Msgf("uri: %s", uri) logger.Debug().Msgf("uri public: %s", uri_public) logger.Debug().Msgf("uri delegate service: %s", uri_delegate) if aea.id == "" || uri == "" { err := errors.New("couldn't get AEA configuration: key and uri are required") logger.Error().Str("err", err.Error()).Msg("") return err } if aea.msgin_path == "" && aea.msgout_path == "" && aea.agent_addr == "" { aea.standalone = true } else if aea.msgin_path == "" || aea.msgout_path == "" || aea.agent_addr == "" { err := errors.New("couldn't get AEA configuration: pipes paths are required when agent address is provided") logger.Error().Str("err", err.Error()).Msg("") return err } // parse uri parts := strings.SplitN(uri, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host = parts[0] port, _ := strconv.ParseUint(parts[1], 10, 16) aea.port = uint16(port) // hack: test if port is taken addr, err := net.ResolveTCPAddr("tcp", uri) if err != nil { return err } listener, err := net.ListenTCP("tcp", addr) if err != nil { logger.Error().Str("err", err.Error()).Msgf("Uri already taken %s", uri) return err } listener.Close() // parse public uri if uri_public != "" { parts = strings.SplitN(uri_public, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri_public) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host_public = parts[0] port, _ = strconv.ParseUint(parts[1], 10, 16) aea.port_public = uint16(port) } else { aea.host_public = "" aea.port_public = 0 } // parse delegate uri if uri_delegate != "" { parts = strings.SplitN(uri_delegate, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri_delegate) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host_delegate = parts[0] port, _ = strconv.ParseUint(parts[1], 10, 16) aea.port_delegate = uint16(port) } else { aea.host_delegate = "" aea.port_delegate = 0 } // parse monitoring uri if uri_monitoring != "" { parts = strings.SplitN(uri_monitoring, ":", -1) if len(parts) < 2 { err := errors.New("malformed Uri " + uri_monitoring) logger.Error().Str("err", err.Error()).Msg("") return err } aea.host_monitoring = parts[0] port, _ = strconv.ParseUint(parts[1], 10, 16) aea.port_monitoring = uint16(port) } else { aea.host_monitoring = "" aea.port_monitoring = 0 } // parse entry peers multiaddress if len(entry_peers) > 0 { aea.entry_peers = strings.SplitN(entry_peers, ",", -1) } // parse registration delay if registrationDelay == "" { aea.registrationDelay = 0.0 } else { delay, err := strconv.ParseFloat(registrationDelay, 32) if err != nil { logger.Error().Str("err", err.Error()).Msgf("malformed RegistrationDelay value") return err } aea.registrationDelay = delay } // setup pipe if !aea.standalone { aea.pipe = NewPipe(aea.msgin_path, aea.msgout_path) } aea.acn_status_chan = make(chan *acn.StatusBody, 1000) return nil } func (aea *AeaApi) Connect() error { if aea.standalone { logger.Info().Msg("Successfully running in standalone mode") return nil } // open pipes err := aea.pipe.Connect() if err != nil { logger.Error().Str("err", err.Error()). Msg("while connecting to pipe") return err } aea.closing = false //TOFIX(LR) trade-offs between bufferd vs unbuffered channel aea.out_queue = make(chan *Envelope, OutQueueSize) aea.send_queue = make(chan *Envelope, SendQueueSize) go aea.listenForEnvelopes() go aea.envelopeSendLoop() logger.Info().Msg("connected to agent") aea.connected = true return nil } func (aea *AeaApi) listenForEnvelopes() { //TOFIX(LR) add an exit strategy for { envel, err := HandleAcnMessageFromPipe(aea.pipe, aea, aea.AeaAddress()) var e *common.PipeError if errors.As(err, &e) { logger.Error(). Str("err", err.Error()). Msg("pipe error while receiving envelope. disconnect") logger.Info().Msg("disconnecting") if !aea.closing { aea.Stop() } return } if err != nil { logger.Error().Str("err", err.Error()).Msg("while receiving envelope. skip") continue } if envel == nil { // ACN STATUS MSG continue } if envel.Sender != aea.agent_record.Address { logger.Error(). Str("err", "Sender ("+envel.Sender+") must match registered address"). Msg("while processing envelope") // TODO send error back to agent continue } logger.Debug().Msgf("received envelope from agent") aea.out_queue <- envel if aea.closing { return } } } func (aea *AeaApi) envelopeSendLoop() { logger.Debug().Msg("send loop started") var err error for { envelope := <-aea.send_queue if envelope == nil { logger.Info().Msg("envelope is nil. exit send loop") return } err = aea.SendEnvelope(envelope) if err != nil { logger.Error().Str("err", err.Error()).Msg("while sending envelope") } else { logger.Debug().Msg("envelope sent") } if aea.closing { return } } } func (aea *AeaApi) stop() { err := aea.pipe.Close() if err != nil { logger.Error().Str("err", err.Error()).Msgf("on pipe close during aeaapi stop") } } /* Pipes helpers */ const CurrentVersion = "0.1.0" func MakeAcnMessageFromEnvelope(envelope *Envelope) ([]byte, error) { envelope_bytes, err := proto.Marshal(envelope) if err != nil { return envelope_bytes, err } return acn.EncodeAcnEnvelope(envelope_bytes, nil) } func (aea AeaApi) SendEnvelope(envelope *Envelope) error { return SendEnvelope(aea.pipe, aea.acn_status_chan, envelope, AcnStatusTimeout) } func SendEnvelope( pipe acn.Pipe, acn_status_chan chan *acn.StatusBody, envelope *Envelope, acnStatusTimeout time.Duration, ) error { envelope_bytes, err := proto.Marshal(envelope) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("while serializing envelope: %s", envelope.String()) return err } err = acn.SendEnvelopeMessageAndWaitForStatus( pipe, envelope_bytes, acn_status_chan, acnStatusTimeout, ) if err != nil { logger.Error(). Str("err", err.Error()). Msgf("on send envelope: %s", envelope.String()) return err } return nil } func (aea AeaApi) AddAcnStatusMessage(status *acn.StatusBody, counterpartyID string) { aea.acn_status_chan <- status logger.Info().Msgf("chan len is %d", len(aea.acn_status_chan)) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/aea/envelope.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc (unknown) // source: envelope.proto package aea import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Envelope struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields To string `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` ProtocolId string `protobuf:"bytes,3,opt,name=protocol_id,json=protocolId,proto3" json:"protocol_id,omitempty"` Message []byte `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` Uri string `protobuf:"bytes,5,opt,name=uri,proto3" json:"uri,omitempty"` } func (x *Envelope) Reset() { *x = Envelope{} if protoimpl.UnsafeEnabled { mi := &file_envelope_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Envelope) String() string { return protoimpl.X.MessageStringOf(x) } func (*Envelope) ProtoMessage() {} func (x *Envelope) ProtoReflect() protoreflect.Message { mi := &file_envelope_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Envelope.ProtoReflect.Descriptor instead. func (*Envelope) Descriptor() ([]byte, []int) { return file_envelope_proto_rawDescGZIP(), []int{0} } func (x *Envelope) GetTo() string { if x != nil { return x.To } return "" } func (x *Envelope) GetSender() string { if x != nil { return x.Sender } return "" } func (x *Envelope) GetProtocolId() string { if x != nil { return x.ProtocolId } return "" } func (x *Envelope) GetMessage() []byte { if x != nil { return x.Message } return nil } func (x *Envelope) GetUri() string { if x != nil { return x.Uri } return "" } var File_envelope_proto protoreflect.FileDescriptor var file_envelope_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x65, 0x61, 0x22, 0x7f, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_envelope_proto_rawDescOnce sync.Once file_envelope_proto_rawDescData = file_envelope_proto_rawDesc ) func file_envelope_proto_rawDescGZIP() []byte { file_envelope_proto_rawDescOnce.Do(func() { file_envelope_proto_rawDescData = protoimpl.X.CompressGZIP(file_envelope_proto_rawDescData) }) return file_envelope_proto_rawDescData } var file_envelope_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_envelope_proto_goTypes = []interface{}{ (*Envelope)(nil), // 0: aea.Envelope } var file_envelope_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_envelope_proto_init() } func file_envelope_proto_init() { if File_envelope_proto != nil { return } if !protoimpl.UnsafeEnabled { file_envelope_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Envelope); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_envelope_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_envelope_proto_goTypes, DependencyIndexes: file_envelope_proto_depIdxs, MessageInfos: file_envelope_proto_msgTypes, }.Build() File_envelope_proto = out.File file_envelope_proto_rawDesc = nil file_envelope_proto_goTypes = nil file_envelope_proto_depIdxs = nil } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/aea/envelope.proto ================================================ syntax = "proto3"; //package libp2p_node.aea; package aea; message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; bytes message = 4; string uri = 5; } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/aea/pipe.go ================================================ // +build windows linux darwin /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aea import ( "encoding/binary" "errors" common "libp2p_node/common" "math" "net" "strconv" ) type TCPSocketChannel struct { port uint16 conn net.Conn } func (sock *TCPSocketChannel) Connect() error { // open tcp connection var err error sock.conn, err = net.Dial("tcp", "127.0.0.1:"+strconv.FormatInt(int64(sock.port), 10)) if err != nil { return err } return nil } func (sock *TCPSocketChannel) Read() ([]byte, error) { // TOFIX(LR) duplicated code to avoid circular dep // utils.ReadBytesConn(sock.conn) buf := make([]byte, 4) _, err := sock.conn.Read(buf) if err != nil { return buf, err } size := binary.BigEndian.Uint32(buf) buf = make([]byte, size) _, err = sock.conn.Read(buf) return buf, err } func (sock *TCPSocketChannel) Write(data []byte) error { // TOFIX(LR) duplicated code to avoid circular dep // utils.WriteBytesConn(sock.conn, data) if len(data) > math.MaxInt32 { return errors.New("value too large") } size := uint32(len(data)) buf := make([]byte, 4, 4+size) binary.BigEndian.PutUint32(buf, size) buf = append(buf, data...) _, err := sock.conn.Write(buf) logger.Debug().Msgf("wrote data to pipe: %d bytes", size) return err } func (sock *TCPSocketChannel) Close() error { return sock.conn.Close() } func NewPipe(msgin_path string, msgout_path string) common.Pipe { port, _ := strconv.ParseUint(msgin_path, 10, 16) return &TCPSocketChannel{port: uint16(port)} } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/aea/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package aea import ( "errors" acn "libp2p_node/acn" common "libp2p_node/common" proto "google.golang.org/protobuf/proto" ) func HandleAcnMessageFromPipe( pipe common.Pipe, statusQueue acn.StatusQueue, counterpartyID string, ) (*Envelope, error) { envelope := &Envelope{} var acn_err error data, err := pipe.Read() if err != nil { logger.Error().Str("err", err.Error()).Msg("while receiving data") return nil, &common.PipeError{Err: err, Msg: "Pipe error during envelope read"} } msg_type, acn_envelope, status, acnErr := acn.DecodeAcnMessage(data) if acnErr != nil { logger.Error().Str("err", acnErr.Error()).Msg("while handling acn message") acn_err = acn.SendAcnError( pipe, acnErr.Error(), acnErr.ErrorCode, ) if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return envelope, acnErr } switch msg_type { case "aea_envelope": { err = proto.Unmarshal(acn_envelope.Envelope, envelope) if err != nil { logger.Error().Str("err", err.Error()).Msg("while decoding envelope") acn_err = acn.SendAcnError( pipe, "error on decoding envelope", acn.ERROR_DECODE, ) if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return envelope, err } err = acn.SendAcnSuccess(pipe) return envelope, err } case "status": { logger.Debug().Msgf("got acn status %d", status.Code) statusQueue.AddAcnStatusMessage(status, counterpartyID) return nil, nil } default: { acn_err = acn.SendAcnError(pipe, "Unsupported ACN message") if acn_err != nil { logger.Error().Str("err", acn_err.Error()).Msg("on acn send error") } return nil, errors.New("unsupported ACN message") } } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/common/common.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package common type PipeError struct { Err error Msg string } func (err *PipeError) Error() string { return err.Msg } func (err *PipeError) Unwrap() error { return err.Err } type Pipe interface { Connect() error Read() ([]byte, error) Write(data []byte) error Close() error } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/common/handlers.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtpeer provides an implementation of an Agent Communication Network node // using libp2p. It participates in data storage and routing for the network. // It offers RelayService for dhtclient and DelegateService for tcp clients. package common import ( "strings" "log" "github.com/libp2p/go-libp2p-core/network" "github.com/pkg/errors" "github.com/rs/zerolog" "google.golang.org/protobuf/proto" acn "libp2p_node/acn" aea "libp2p_node/aea" "libp2p_node/dht/dhtnode" utils "libp2p_node/utils" ) func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } //Abstract DHTHandler that provides logging function and handle for incoming envelopes and ACN address requests type DHTHandler interface { GetLoggers() (func(error) *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event) HandleAeaEnvelope(envel *aea.Envelope) *acn.ACNError HandleAeaAddressRequest(reqAddress string) (*acn.AgentRecord, *acn.ACNError) } //read ACN message, decode envelope, check PoR func receiveEnvelopeFromPeer(dhtHandler DHTHandler, stream network.Stream) (*aea.Envelope, error) { lerror, _, _, _ := dhtHandler.GetLoggers() streamPipe := utils.StreamPipe{Stream: stream} aeaEnvelope, err := acn.ReadEnvelopeMessage(streamPipe) if err != nil { lerror(err).Msg("while handling acn envelope message") return nil, err } envel := &aea.Envelope{} err = proto.Unmarshal(aeaEnvelope.Envelope, envel) if err != nil { lerror(err).Msg("while deserializing acn aea envelope message") ignore(acn.SendAcnError( streamPipe, "while deserializing acn aea envelope message", acn.ERROR_DECODE, )) return nil, err } remotePubkey, err := utils.FetchAIPublicKeyFromPubKey(stream.Conn().RemotePublicKey()) ignore(err) status, err := dhtnode.IsValidProofOfRepresentation( aeaEnvelope.Record, aeaEnvelope.Record.Address, remotePubkey, ) if err != nil || status.Code != acn.SUCCESS { if err == nil { err = errors.New(status.Code.String() + ":" + strings.Join(status.Msgs, ":")) } lerror(err).Msg("incoming envelope PoR is not valid") ignore(acn.SendAcnError(streamPipe, "incoming envelope PoR is not valid", status.Code)) return nil, err } return envel, nil } // handle envelope stream, handle acn protocol and call dhtHandler.HandleAeaEnvelope for incoming envelopes func HandleAeaEnvelopeStream(dhtHandler DHTHandler, stream network.Stream) { lerror, _, _, ldebug := dhtHandler.GetLoggers() //ldebug().Msgf("Got a new aea envelope stream") envel, err := receiveEnvelopeFromPeer(dhtHandler, stream) if err != nil { lerror(err).Msgf("while reading envelope from peer") stream.Close() return } ldebug().Msgf("Received envelope from peer %s", envel.String()) streamPipe := utils.StreamPipe{Stream: stream} acnError := dhtHandler.HandleAeaEnvelope(envel) if acnError != nil { err = acn.SendAcnError(streamPipe, acnError.Err.Error(), acnError.ErrorCode) ignore(err) err = stream.Close() ignore(err) return } err = acn.SendAcnSuccess(streamPipe) ignore(err) err = stream.Close() ignore(err) } // handle address request stream, handle acn protocol and call dhtHandler.HandleAeaAddressRequest for incoming requests func HandleAeaAddressStream(dhtHandler DHTHandler, stream network.Stream) { lerror, _, _, ldebug := dhtHandler.GetLoggers() //ldebug().Msg("Got a new aea address stream") // get LookupRequest streamPipe := utils.StreamPipe{Stream: stream} reqAddress, err := acn.ReadLookupRequest(streamPipe) if err != nil { lerror(err).Str("op", "resolve"). Msg("while reading message from stream") err = stream.Reset() ignore(err) return } ldebug(). Str("op", "resolve"). Str("target", reqAddress). Msg("Received query for addr") record, acnError := dhtHandler.HandleAeaAddressRequest(reqAddress) if acnError != nil { lerror(acnError.Err). Str("op", "resolve"). Str("target", reqAddress). Msgf("request address error") err = acn.SendAcnError(streamPipe, acnError.Err.Error(), acnError.ErrorCode) ignore(err) err = stream.Close() ignore(err) return } if record == nil { lerror(acnError.Err). Str("op", "resolve"). Str("target", reqAddress). Msgf("unexpected error. agent record is nil!") return } err = acn.SendLookupResponse(streamPipe, record) if err != nil { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msg("while sending agent record to peer") err = stream.Reset() ignore(err) } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtclient/dhtclient.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtclient provides implementation of a lightweight Agent Communication Network // node. It doesn't participate in network maintenance. It doesn't require a public IP // address either, as it relies on a DHTPeer (relay peer) to communicate with other peers. package dhtclient import ( "context" "log" "math/rand" "strings" "time" "github.com/pkg/errors" "github.com/rs/zerolog" "google.golang.org/protobuf/proto" libp2p "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" "github.com/multiformats/go-multiaddr" peerstore "github.com/libp2p/go-libp2p-core/peerstore" kaddht "github.com/libp2p/go-libp2p-kad-dht" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" acn "libp2p_node/acn" aea "libp2p_node/aea" common "libp2p_node/dht/common" "libp2p_node/dht/dhtnode" utils "libp2p_node/utils" ) func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } const ( newStreamTimeoutRelayPeer = 5 * 60 * time.Second // includes peer restart newStreamTimeout = 1 * 60 * time.Second // doesn't include peer restart bootstrapTimeout = 1 * 60 * time.Second // doesn't include peer restart sleepTimeDefaultDuration = 100 * time.Millisecond sleepTimeIncreaseMFactor = 2 // multiplicative increase reconnectTimeout = 5 * time.Second ) // Notifee Handle DHTClient network events type Notifee struct { myRelayPeer peer.AddrInfo myHost host.Host logger zerolog.Logger closing chan struct{} } // Listen called when network starts listening on an addr func (notifee *Notifee) Listen(network.Network, multiaddr.Multiaddr) {} // ListenClose called when network stops listening on an addr func (notifee *Notifee) ListenClose(network.Network, multiaddr.Multiaddr) {} // Connected called when a connection opened func (notifee *Notifee) Connected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Connected to peer %s", conn.RemotePeer().Pretty(), ) } // Disconnected called when a connection closed // Reconnects if connection is to relay peer and not currenctly closing connection. func (notifee *Notifee) Disconnected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Disconnected from peer %s", conn.RemotePeer().Pretty(), ) pinfo := notifee.myRelayPeer if conn.RemotePeer().Pretty() != pinfo.ID.Pretty() { return } notifee.myHost.Peerstore().AddAddrs(pinfo.ID, pinfo.Addrs, peerstore.PermanentAddrTTL) for { var err error select { case _, open := <-notifee.closing: if !open { return } default: notifee.logger.Warn().Msgf( "Lost connection to relay peer %s, reconnecting...", pinfo.ID.Pretty(), ) ctx, cancel := context.WithTimeout(context.Background(), reconnectTimeout) defer cancel() if err = notifee.myHost.Connect(ctx, pinfo); err == nil { break } time.Sleep(1 * time.Second) } if err == nil { break } } notifee.logger.Info().Msgf("Connection to relay peer %s reestablished", pinfo.ID.Pretty()) } // OpenedStream called when a stream opened func (notifee *Notifee) OpenedStream(network.Network, network.Stream) {} // ClosedStream called when a stream closed func (notifee *Notifee) ClosedStream(network.Network, network.Stream) {} // DHTClient A restricted libp2p node for the Agents Communication Network // It use a `DHTPeer` to communicate with other peers. type DHTClient struct { bootstrapPeers []peer.AddrInfo relayPeer peer.ID key crypto.PrivKey publicKey crypto.PubKey dht *kaddht.IpfsDHT routedHost *routedhost.RoutedHost myAgentAddress string myAgentRecord *acn.AgentRecord myAgentReady func() bool processEnvelope func(*aea.Envelope) error closing chan struct{} logger zerolog.Logger } // New creates a new DHTClient func New(opts ...Option) (*DHTClient, error) { var err error dhtClient := &DHTClient{} for _, opt := range opts { if err := opt(dhtClient); err != nil { return nil, err } } dhtClient.closing = make(chan struct{}) /* check correct configuration */ // private key if dhtClient.key == nil { return nil, errors.New("private key must be provided") } // agent address is mandatory if dhtClient.myAgentAddress == "" { return nil, errors.New("missing agent address") } // agent record is mandatory if dhtClient.myAgentRecord == nil { return nil, errors.New("missing agent record") } // check if the PoR is delivered for my public key myPublicKey, err := utils.FetchAIPublicKeyFromPubKey(dhtClient.publicKey) status, errPoR := dhtnode.IsValidProofOfRepresentation( dhtClient.myAgentRecord, dhtClient.myAgentRecord.Address, myPublicKey, ) if err != nil || errPoR != nil || status.Code != acn.SUCCESS { msg := "Invalid AgentRecord" if err != nil { msg += " - " + err.Error() } if errPoR != nil { msg += " - " + errPoR.Error() } return nil, errors.New(msg) } // bootstrap peers if len(dhtClient.bootstrapPeers) < 1 { return nil, errors.New("at least one boostrap peer should be provided") } // select a relay node randomly from entry peers rand.Seed(time.Now().Unix()) index := rand.Intn(len(dhtClient.bootstrapPeers)) dhtClient.relayPeer = dhtClient.bootstrapPeers[index].ID dhtClient.setupLogger() _, _, linfo, ldebug := dhtClient.GetLoggers() linfo().Msg("INFO Using as relay") /* setup libp2p node */ ctx := context.Background() // libp2p options libp2pOpts := []libp2p.Option{ libp2p.ListenAddrs(), libp2p.Identity(dhtClient.key), libp2p.DefaultTransports, libp2p.DefaultMuxers, libp2p.DefaultSecurity, libp2p.NATPortMap(), libp2p.EnableNATService(), libp2p.EnableRelay(), } // create a basic host basicHost, err := libp2p.New(ctx, libp2pOpts...) if err != nil { return nil, err } // create the dht dhtClient.dht, err = kaddht.New(ctx, basicHost, kaddht.Mode(kaddht.ModeClient)) if err != nil { return nil, err } // make the routed host dhtClient.routedHost = routedhost.Wrap(basicHost, dhtClient.dht) dhtClient.setupLogger() // connect to the booststrap nodes err = dhtClient.bootstrapLoopUntilTimeout() if err != nil { dhtClient.Close() return nil, err } // bootstrap the host err = dhtClient.dht.Bootstrap(ctx) if err != nil { dhtClient.Close() return nil, err } // register my address to relay peer err = dhtClient.registerAgentAddress() if err != nil { dhtClient.Close() return nil, err } dhtClient.routedHost.Network().Notify(&Notifee{ myRelayPeer: dhtClient.bootstrapPeers[index], myHost: dhtClient.routedHost, logger: dhtClient.logger, closing: dhtClient.closing, }) /* setup DHTClient message handlers */ // aea address lookup ldebug().Msg("DEBUG Setting /aea-address/0.1.0 stream...") dhtClient.routedHost.SetStreamHandler(dhtnode.AeaAddressStream, dhtClient.handleAeaAddressStream) // incoming envelopes stream ldebug().Msg("DEBUG Setting /aea/0.1.0 stream...") dhtClient.routedHost.SetStreamHandler(dhtnode.AeaEnvelopeStream, dhtClient.handleAeaEnvelopeStream) return dhtClient, nil } // bootstrapLoopUntilTimeout loops until connection to bootstrap peers established or timeout reached func (dhtClient *DHTClient) bootstrapLoopUntilTimeout() error { lerror, _, _, _ := dhtClient.GetLoggers() ctx, cancel := context.WithTimeout(context.Background(), bootstrapTimeout) defer cancel() err := utils.BootstrapConnect( ctx, dhtClient.routedHost, dhtClient.dht, dhtClient.bootstrapPeers, ) sleepTime := sleepTimeDefaultDuration for err != nil { lerror(err). Str("op", "bootstrap"). Msgf("couldn't open stream to bootstrap peer, retrying in %s", sleepTime) select { default: time.Sleep(sleepTime) sleepTime = sleepTime * sleepTimeIncreaseMFactor err = utils.BootstrapConnect( ctx, dhtClient.routedHost, dhtClient.dht, dhtClient.bootstrapPeers, ) case <-ctx.Done(): err = errors.New("bootstrap connect timeout reached") } } return err } // newStreamLoopUntilTimeout loops until stream to peer established or timeout reached func (dhtClient *DHTClient) newStreamLoopUntilTimeout( peerID peer.ID, streamType protocol.ID, timeout time.Duration, ) (network.Stream, error) { lerror, _, _, _ := dhtClient.GetLoggers() ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() stream, err := dhtClient.routedHost.NewStream(ctx, peerID, streamType) sleepTime := sleepTimeDefaultDuration disconnected := false for err != nil { disconnected = true lerror(err). Str("op", "route"). Msgf("couldn't open stream to peer %s, retrying in %s", peerID.Pretty(), sleepTime) select { default: time.Sleep(sleepTime) sleepTime = sleepTime * sleepTimeIncreaseMFactor stream, err = dhtClient.routedHost.NewStream(ctx, peerID, streamType) case <-ctx.Done(): err = errors.New("new stream loop timeout reached") } } if stream == nil && err == nil { return nil, errors.New("stream nil and err nil") } // register again in case of disconnection if disconnected { err = dhtClient.registerAgentAddress() } return stream, err } // setupLogger sets up a logger for the DHTClient func (dhtClient *DHTClient) setupLogger() { fields := map[string]string{ "package": "DHTClient", "relayid": dhtClient.relayPeer.Pretty(), } if dhtClient.routedHost != nil { fields["peerid"] = dhtClient.routedHost.ID().Pretty() } dhtClient.logger = utils.NewDefaultLoggerWithFields(fields) } // GetLoggers gets the various logger levels of the DHTClient func (dhtClient *DHTClient) GetLoggers() (func(error) *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event) { ldebug := dhtClient.logger.Debug linfo := dhtClient.logger.Info lwarn := dhtClient.logger.Warn lerror := func(err error) *zerolog.Event { if err == nil { return dhtClient.logger.Error().Str("err", "nil") } return dhtClient.logger.Error().Str("err", err.Error()) } return lerror, lwarn, linfo, ldebug } // Close stops the DHTClient // Closes the DHT and routedHost of the DHTClient func (dhtClient *DHTClient) Close() []error { var err error var status []error _, _, linfo, _ := dhtClient.GetLoggers() linfo().Msg("Stopping DHTClient...") close(dhtClient.closing) errappend := func(err error) { if err != nil { status = append(status, err) } } err = dhtClient.dht.Close() errappend(err) err = dhtClient.routedHost.Close() errappend(err) return status } // MultiAddr always return empty string func (dhtClient *DHTClient) MultiAddr() string { return "" } func (dhtClient *DHTClient) PeerID() string { return dhtClient.routedHost.ID().Pretty() } // RouteEnvelope routes the provided envelope to its destination contact peer func (dhtClient *DHTClient) RouteEnvelope(envel *aea.Envelope) error { lerror, lwarn, _, ldebug := dhtClient.GetLoggers() // only send envelopes from own agent if envel.Sender != dhtClient.myAgentAddress { err := errors.New("Sender (" + envel.Sender + ") must match registered address") lerror(err).Str("addr", dhtClient.myAgentAddress). Msgf("while routing envelope") return err } target := envel.To // TODO(LR) check if the record is valid if target == dhtClient.myAgentAddress { ldebug(). Str("op", "route"). Str("target", target). Msg("envelope destinated to my local agent...") for !dhtClient.myAgentReady() { ldebug(). Str("op", "route"). Str("target", target). Msg("agent not ready yet, sleeping for some time ...") time.Sleep(time.Duration(100) * time.Millisecond) } if dhtClient.processEnvelope != nil { err := dhtClient.processEnvelope(envel) if err != nil { return err } } else { lwarn(). Str("op", "route"). Str("target", target). Msgf("ProcessEnvelope not set, ignoring envelope %s", envel.String()) return nil } } // client can get addresses only through bootstrap peer stream, err := dhtClient.newStreamLoopUntilTimeout( dhtClient.relayPeer, dhtnode.AeaAddressStream, newStreamTimeoutRelayPeer, ) if err != nil { return err } ldebug(). Str("op", "route"). Str("target", target). Msg("requesting agent record from relay...") streamPipe := utils.StreamPipe{Stream: stream} record, err := acn.PerformAddressLookup(streamPipe, target) if err != nil { lerror(err).Str("op", "route").Str("target", target). Msgf("failed agent lookup") return err } valid, err := dhtnode.IsValidProofOfRepresentation(record, target, record.PeerPublicKey) if err != nil || valid.Code != acn.SUCCESS { errMsg := valid.Code.String() + " : " + strings.Join( valid.Msgs, ":", ) if err == nil { err = errors.New(errMsg) } else { err = errors.Wrap(err, valid.Code.String()+" : "+strings.Join(valid.Msgs, ":")) } lerror(err).Str("op", "route").Str("target", target). Msgf("invalid agent record") return err } stream.Close() // retrieve peerID peerID, err := utils.IDFromFetchAIPublicKey(record.PeerPublicKey) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("CRITICAL couldn't get peer ID from message") return errors.New("CRITICAL route - couldn't get peer ID from record peerID:" + err.Error()) } ldebug(). Str("op", "route"). Str("target", target). Msgf("got peer ID %s for agent Address", peerID.Pretty()) // TODO(LR): test if representative peer is relay peer, and skip the Connect if it is the case // TODO(DM): extract below multi-address creation for reuse and consistency multiAddr := "/p2p/" + dhtClient.relayPeer.Pretty() + "/p2p-circuit/p2p/" + peerID.Pretty() relayMultiaddr, err := multiaddr.NewMultiaddr(multiAddr) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msgf("while creating relay multiaddress %s", multiAddr) return err } peerRelayInfo := peer.AddrInfo{ ID: peerID, Addrs: []multiaddr.Multiaddr{relayMultiaddr}, } ldebug(). Str("op", "route"). Str("target", target). Msgf("connecting to target through relay %s", relayMultiaddr) if err = dhtClient.routedHost.Connect(context.Background(), peerRelayInfo); err != nil { lerror(err). Str("op", "route"). Str("target", target). Msgf("couldn't connect to target %s", peerID) return err } ldebug(). Str("op", "route"). Str("target", target). Msgf("opening stream to target %s", peerID) stream, err = dhtClient.newStreamLoopUntilTimeout( peerID, dhtnode.AeaEnvelopeStream, newStreamTimeout, ) if err != nil { return err } streamPipe = utils.StreamPipe{Stream: stream} envelBytes, err := proto.Marshal(envel) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("couldn't serialize envelope") errReset := stream.Reset() ignore(errReset) return err } err = acn.SendEnvelopeMessage(streamPipe, envelBytes, dhtClient.myAgentRecord) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("couldn't send envelope") errReset := stream.Reset() lerror(errReset). Msg("stream.Reset error") ignore(errReset) return err } // wait for response status, err := acn.ReadAcnStatus(streamPipe) if err != nil { lerror(err). Str("op", "route"). Str("target", target). Msg("while getting confirmation") errReset := stream.Reset() ignore(errReset) return err } stream.Close() if status.Code != acn.SUCCESS { err = errors.New( status.Code.String() + " : " + strings.Join( status.Msgs, ":", ), ) lerror(err). Str("op", "route"). Str("target", target). Msg("failed to deliver envelope") return err } // TODO(DM) check how we handle case when envelope not routable. return err } // handleAeaEnvelopeStream deals with incoming envelopes on the AeaEnvelopeStream // envelopes arrive from other peers (full or client) and are processed // by HandleAeaEnvelope func (dhtClient *DHTClient) handleAeaEnvelopeStream(stream network.Stream) { common.HandleAeaEnvelopeStream(dhtClient, stream) } // Callback to handle and route aea envelope comes from the aea envelope stream // return ACNError if message routing failed, otherwise nil. func (dhtClient *DHTClient) HandleAeaEnvelope(envel *aea.Envelope) *acn.ACNError { lerror, lwarn, _, _ := dhtClient.GetLoggers() var err error if envel.To == dhtClient.myAgentAddress && dhtClient.processEnvelope != nil { err = dhtClient.processEnvelope(envel) if err != nil { lerror(err).Msgf("while processing envelope by agent") return &acn.ACNError{ Err: errors.New("agent is not ready"), ErrorCode: acn.ERROR_AGENT_NOT_READY, } } } else { lwarn().Msgf("ignored envelope from unknown agent %s", envel.String()) return &acn.ACNError{Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS} } return nil } // handleAeaAddressStream deals with incoming envelopes on the AeaAddressStream // agent record lookup requests arrive from other peers (full or client) and are // served with the agent record (if applicable) func (dhtClient *DHTClient) handleAeaAddressStream(stream network.Stream) { common.HandleAeaAddressStream(dhtClient, stream) } func (dhtClient *DHTClient) HandleAeaAddressRequest( reqAddress string, ) (*acn.AgentRecord, *acn.ACNError) { lerror, _, _, ldebug := dhtClient.GetLoggers() ldebug(). Str("op", "resolve"). Str("target", reqAddress). Msg("Received query for addr") if reqAddress != dhtClient.myAgentAddress { lerror(errors.New("unknown agent address")). Str("op", "resolve"). Str("target", reqAddress). Msgf("requested address different from advertised one %s", dhtClient.myAgentAddress) return nil, &acn.ACNError{ Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS, } } else { return dhtClient.myAgentRecord, nil } } // registerAgentAddress registers agent address to relay peer func (dhtClient *DHTClient) registerAgentAddress() error { lerror, _, _, ldebug := dhtClient.GetLoggers() ldebug(). Str("op", "register"). Str("addr", dhtClient.myAgentAddress). Msg("opening stream aea-register to relay peer...") ctx, cancel := context.WithTimeout(context.Background(), newStreamTimeoutRelayPeer) defer cancel() stream, err := dhtClient.routedHost.NewStream( ctx, dhtClient.relayPeer, dhtnode.AeaRegisterRelayStream, ) if err != nil { lerror(err). Str("op", "register"). Str("addr", dhtClient.myAgentAddress). Msg("timeout, couldn't open stream to relay peer") return err } ldebug(). Str("op", "register"). Str("addr", dhtClient.myAgentAddress). Msgf("registering addr and peerID to relay peer") streamPipe := utils.StreamPipe{Stream: stream} err = acn.SendAgentRegisterMessage(streamPipe, dhtClient.myAgentRecord) if err != nil { errReset := stream.Close() ignore(errReset) return err } stream.Close() return nil } // ProcessEnvelope register a callback function for processing of envelopes // the function processes envelopes received in handleAeaEnvelopeStream func (dhtClient *DHTClient) ProcessEnvelope(fn func(*aea.Envelope) error) { dhtClient.processEnvelope = fn } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtclient/dhtclient_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtclient import ( "testing" "time" "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/dht/dhtnode" "libp2p_node/dht/dhttests" utils "libp2p_node/utils" ) // const ( DefaultFetchAIKey = "04ab8ac134ec727917cf4f9e47685e84622151bc8b12838bc54c8ffe5d44d04a" DefaultFetchAIPublicKey = "03b07ef4513e3f0372245b3d6d474d871ba58eacaf3a2a07c487af6d82006b86b4" DefaultAgentKey = "f76137a61c1ad3ee8a0a9a185bc8e6fa51be1a2528f86042c11f9cc00880024a" DefaultAgentPublicKey = "021820ce23b5f3a6ef01988149e724af854f89d37b9cabc3b1702cc5287f617b92" DefaultAgentAddress = "fetch1ver6u7xdvkjy4dq8xxrkc6ualu98k7ykumv08q" EnvelopeDeliveryTimeout = 10 * time.Second DefaultLedger = dhtnode.DefaultLedger ) // TestNew dht client peer func TestNew(t *testing.T) { rxEnvelopesPeer := make(chan *aea.Envelope, 2) dhtPeer, cleanup, err := dhttests.NewDHTPeerWithDefaults(rxEnvelopesPeer) if err != nil { t.Fatal("Failed to create DHTPeer (required for DHTClient):", err) } defer cleanup() signature, err := utils.SignFetchAI([]byte(DefaultFetchAIPublicKey), DefaultAgentKey) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = DefaultAgentAddress record.PublicKey = DefaultAgentPublicKey record.PeerPublicKey = DefaultFetchAIPublicKey record.Signature = signature opts := []Option{ IdentityFromFetchAIKey(DefaultFetchAIKey), RegisterAgentAddress(record, func() bool { return true }), BootstrapFrom([]string{dhtPeer.MultiAddr()}), } t.Log(dhtPeer.MultiAddr()) dhtClient, err := New(opts...) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClient.Close() rxEnvelopesClient := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxEnvelopesClient <- envel return nil }) } // TestRouteEnvelopeToPeerAgent send envelope from DHTClient agent to DHTPeer agent func TestRouteEnvelopeToPeerAgent(t *testing.T) { rxEnvelopesPeer := make(chan *aea.Envelope, 2) dhtPeer, cleanup, err := dhttests.NewDHTPeerWithDefaults(rxEnvelopesPeer) if err != nil { t.Fatal("Failed to create DHTPeer (required for DHTClient):", err) } defer cleanup() signature, err := utils.SignFetchAI([]byte(DefaultFetchAIPublicKey), DefaultAgentKey) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = DefaultAgentAddress record.PublicKey = DefaultAgentPublicKey record.PeerPublicKey = DefaultFetchAIPublicKey record.Signature = signature opts := []Option{ IdentityFromFetchAIKey(DefaultFetchAIKey), RegisterAgentAddress(record, func() bool { return true }), BootstrapFrom([]string{dhtPeer.MultiAddr()}), } t.Log(dhtPeer.MultiAddr()) dhtClient, err := New(opts...) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClient.Close() rxEnvelopesClient := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxEnvelopesClient <- envel return nil }) if len(rxEnvelopesPeer) != 0 { t.Error("DHTPeer agent inbox should be empty") } err = dhtClient.RouteEnvelope(&aea.Envelope{ To: dhttests.DHTPeerDefaultAgentAddress, Sender: DefaultAgentAddress, }) if err != nil { t.Error("Failed to Route envelope to DHTPeer agent:", err) } timeout := time.After(EnvelopeDeliveryTimeout) select { case envel := <-rxEnvelopesPeer: t.Log("DHT received envelope", envel) case <-timeout: t.Error("Failed to Route envelope to DHTPeer agent") } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtclient/options.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtclient import ( acn "libp2p_node/acn" "libp2p_node/utils" ) // Option for dhtclient.New type Option func(*DHTClient) error // IdentityFromFetchAIKey for dhtclient.New func IdentityFromFetchAIKey(key string) Option { return func(dhtClient *DHTClient) error { var err error dhtClient.key, dhtClient.publicKey, err = utils.KeyPairFromFetchAIKey(key) if err != nil { return err } return nil } } // RegisterAgentAddress for dhtclient.New func RegisterAgentAddress(record *acn.AgentRecord, isReady func() bool) Option { return func(dhtClient *DHTClient) error { pbRecord := &acn.AgentRecord{} pbRecord.Address = record.Address pbRecord.PublicKey = record.PublicKey pbRecord.PeerPublicKey = record.PeerPublicKey pbRecord.Signature = record.Signature pbRecord.ServiceId = record.ServiceId pbRecord.LedgerId = record.LedgerId dhtClient.myAgentAddress = record.Address dhtClient.myAgentRecord = pbRecord dhtClient.myAgentReady = isReady return nil } } // BootstrapFrom for dhtclient.New func BootstrapFrom(entryPeers []string) Option { return func(dhtClient *DHTClient) error { var err error dhtClient.bootstrapPeers, err = utils.GetPeersAddrInfo(entryPeers) if err != nil { return err } return nil } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtnode/dhtnode.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtnode (in progress) contains the common interface between dhtpeer and dhtclient package dhtnode import "libp2p_node/aea" // DHTNode libp2p node interface type DHTNode interface { RouteEnvelope(*aea.Envelope) error ProcessEnvelope(func(*aea.Envelope) error) MultiAddr() string PeerID() string Close() []error } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtnode/streams.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtnode const ( AeaNotifStream = "/aea-notif/0.1.0" AeaAddressStream = "/aea-address/0.1.0" AeaEnvelopeStream = "/aea/0.1.0" AeaRegisterRelayStream = "/aea-register/0.1.0" ) ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtnode/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtnode contains the common interface between dhtpeer and dhtclient // TODO: extraction of shared functionality is work in progress package dhtnode import ( "errors" "strings" acn "libp2p_node/acn" utils "libp2p_node/utils" ) const ( DefaultLedger = "fetchai" CurrentVersion = "0.1.0" ) var supportedLedgers = []string{"fetchai", "cosmos", "ethereum"} func IsValidProofOfRepresentation( record *acn.AgentRecord, agentAddress string, representativePeerPubKey string, ) (*acn.StatusBody, error) { // check agent address matches if record.Address != agentAddress { err := errors.New("Wrong agent address, expected " + agentAddress) response := &acn.StatusBody{ Code: acn.ERROR_WRONG_AGENT_ADDRESS, Msgs: []string{err.Error()}, } return response, err } // check if ledger is supported var found = false for _, supported := range supportedLedgers { if record.LedgerId == supported { found = true break } } if !found { err := errors.New( "Unsupported ledger " + record.LedgerId + ", expected " + strings.Join( supportedLedgers, ",", ), ) response := &acn.StatusBody{Code: acn.ERROR_UNSUPPORTED_LEDGER, Msgs: []string{err.Error()}} return response, err } // check public key matches if record.PeerPublicKey != representativePeerPubKey { err := errors.New("Wrong peer public key, expected " + representativePeerPubKey) response := &acn.StatusBody{Code: acn.ERROR_WRONG_PUBLIC_KEY, Msgs: []string{err.Error()}} return response, err } // check that agent address and public key match addrFromPubKey, err := utils.AgentAddressFromPublicKey(record.LedgerId, record.PublicKey) if err != nil || addrFromPubKey != record.Address { if err == nil { err = errors.New("agent address and public key don't match") } response := &acn.StatusBody{Code: acn.ERROR_WRONG_AGENT_ADDRESS} return response, err } // check that signature is valid ok, err := utils.VerifyLedgerSignature( record.LedgerId, []byte(record.PeerPublicKey), record.Signature, record.PublicKey, ) if !ok || err != nil { if err == nil { err = errors.New("signature is not valid") } response := &acn.StatusBody{Code: acn.ERROR_INVALID_PROOF} return response, err } // PoR is valid response := &acn.StatusBody{Code: acn.SUCCESS} return response, nil } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtpeer/benchmarks_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( "bufio" "context" "flag" "net" "os" "testing" "time" "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/utils" "github.com/rs/zerolog" ) /* ********** * How to run * *********** $ go test -p 1 -count 20 libp2p_node/dht/dhtpeer/ -run=XXX -bench . -benchtime=20x -peers-keys-file=/path/to/file/benchmark_peers_keys.txt -agents-keys-file=/path/to/file/benchmark_agents_keys.txt */ var peersKeysFilePath string var agentsKeysFilePath string var tcpUri = "localhost:12345" func init() { flag.StringVar(&peersKeysFilePath, "peers-keys-file", "", "File with list of EC private keys") flag.StringVar( &agentsKeysFilePath, "agents-keys-file", "", "File with list of agents EC private keys", ) } /* ********************************** * baseline TCP connection benchmark * ********************************** */ // helpers func acceptAndEcho(server net.Listener) { for { conn, err := server.Accept() if err != nil { return } go func() { for { buf, err := utils.ReadBytesConn(conn) if err != nil { return } err = utils.WriteBytesConn(conn, buf) if err != nil { return } } }() } } func connect(uri string, b *testing.B) net.Conn { conn, err := net.Dial("tcp", uri) if err != nil { b.Fatal(err.Error()) } return conn } func sendAndReceive(conn net.Conn, buf []byte, b *testing.B) { err := utils.WriteBytesConn(conn, buf) if err != nil { b.Fatal(err.Error()) } _, err = utils.ReadBytesConn(conn) if err != nil { b.Fatal(err.Error()) } } func connectAndSend(buf []byte, b *testing.B) { conn := connect(tcpUri, b) sendAndReceive(conn, buf, b) conn.Close() } // benchs func BenchmarkBaselineTCPEcho(b *testing.B) { tcpServer, err := net.Listen("tcp", tcpUri) if err != nil { b.Fatal(err.Error()) } go acceptAndEcho(tcpServer) buf := make([]byte, 200) conn := connect(tcpUri, b) for i := 0; i < b.N; i++ { sendAndReceive(conn, buf, b) } b.StopTimer() tcpServer.Close() b.StartTimer() } func BenchmarkBaselineTCPConnectAndEcho(b *testing.B) { tcpServer, err := net.Listen("tcp", tcpUri) if err != nil { b.Fatal(err.Error()) } go acceptAndEcho(tcpServer) buf := make([]byte, 200) for i := 0; i < b.N; i++ { //var elapsed time.Duration //start := time.Now() b.ResetTimer() connectAndSend(buf, b) b.StopTimer() //elapsed = time.Since(start) //fmt.Println("Elapsed ", elapsed.String()) b.StartTimer() } b.StopTimer() tcpServer.Close() b.StartTimer() } /* ********************************** * Peer DHT operations benchmark * ********************************** */ // helpers func getKeysAndAddrs(b *testing.B) (peers []string, agents []string) { peersKeysFile, err := os.Open(peersKeysFilePath) if err != nil { b.Fatal(err) } defer peersKeysFile.Close() agentsKeysFile, err := os.Open(agentsKeysFilePath) if err != nil { b.Fatal(err) } defer agentsKeysFile.Close() ksc := bufio.NewScanner(peersKeysFile) asc := bufio.NewScanner(agentsKeysFile) peers = []string{} agents = []string{} for ksc.Scan() && asc.Scan() { peers = append(peers, ksc.Text()) agents = append(agents, asc.Text()) } return peers, agents } func setupLocalDHTPeerForBench( key string, agentKey string, dhtPort uint16, delegatePort uint16, entry []string, ) (*DHTPeer, func(), error) { /* peer, peerCleanup, err := SetupLocalDHTPeer(key, addr, dhtPort, delegatePort, entry) if err == nil { peer.SetLogLevel(zerolog.Disabled) utils.SetLoggerLevel(zerolog.Disabled) } return peer, peerCleanup, err */ opts := []Option{ LocalURI(DefaultLocalHost, dhtPort), PublicURI(DefaultLocalHost, dhtPort), IdentityFromFetchAIKey(key), EnableRelayService(), BootstrapFrom(entry), } if agentKey != "" { agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(agentKey) if err != nil { return nil, nil, err } agentAddress, err := utils.FetchAIAddressFromPublicKey(agentPubKey) if err != nil { return nil, nil, err } peerPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } signature, err := utils.SignFetchAI([]byte(peerPubKey), agentKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{} record.Address = agentAddress record.PublicKey = agentPubKey record.PeerPublicKey = peerPubKey record.Signature = signature opts = append(opts, RegisterAgentAddress(record, func() bool { return true })) } if delegatePort != 0 { opts = append(opts, EnableDelegateService(delegatePort)) } dhtPeer, err := New(opts...) if err != nil { return nil, nil, err } utils.SetLoggerLevel(zerolog.Disabled) return dhtPeer, func() { dhtPeer.Close() }, nil } func deployPeers(number uint16, b *testing.B) ([]*DHTPeer, []string) { peerKeys, agentsKeys := getKeysAndAddrs(b) peers := make([]*DHTPeer, 0, number) for i := uint16(0); i < number; i++ { entry := []string{} if i > 0 { entry = append(entry, peers[i-1].MultiAddr()) } peer, _, err := setupLocalDHTPeerForBench( peerKeys[i], agentsKeys[i], DefaultLocalPort+i, 0, entry, ) if err != nil { b.Fatal("Failed to initialize DHTPeer:", err) } peers = append(peers, peer) } return peers, agentsKeys } func closePeers(peers ...*DHTPeer) { for _, peer := range peers { peer.Close() } } func setupEchoServicePeers(peers ...*DHTPeer) { for _, peer := range peers { peer.ProcessEnvelope(func(envel *aea.Envelope) error { err := peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) } } // benchs func benchmarkAgentRegistration(npeers uint16, b *testing.B) { peers, addrs := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], "", DefaultLocalPort+npeers+1, 0, []string{peers[0].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } defer peerCleanup() for i := 0; i < b.N; i++ { b.ResetTimer() err = peer.RegisterAgentAddress(addrs[len(addrs)-1-i%len(addrs)]) if err != nil { b.Fail() } } } func benchmarkAgentLookup(npeers uint16, b *testing.B) { peers, addrs := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+npeers+1, 0, []string{peers[len(peers)-1].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } defer peerCleanup() ensureAddressAnnounced(peer) ctx, cancel_lookup := context.WithTimeout(context.Background(), 30*time.Second) defer cancel_lookup() for i := 0; i < b.N; i++ { b.ResetTimer() _, _, err = peer.lookupAddressDHT(ctx, addrs[len(peers)-1-i%len(peers)]) if err != nil { b.Fail() } } } func benchmarkPeerJoin(npeers uint16, b *testing.B) { peers, _ := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) for i := 0; i < b.N; i++ { b.ResetTimer() peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+npeers+1, 0, []string{peers[i%len(peers)].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } ensureAddressAnnounced(peer) b.StopTimer() peerCleanup() b.StartTimer() } } func benchmarkPeerEcho(npeers uint16, b *testing.B) { peers, addrs := deployPeers(npeers, b) ensureAddressAnnounced(peers...) defer closePeers(peers...) setupEchoServicePeers(peers...) peer, peerCleanup, err := setupLocalDHTPeerForBench( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+npeers+1, 0, []string{peers[len(peers)-1].MultiAddr()}, ) if err != nil { b.Fatal(err.Error()) } defer peerCleanup() ensureAddressAnnounced(peer) rxPeer := make(chan *aea.Envelope, 10) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return nil }) for i := 0; i < b.N; i++ { envel := &aea.Envelope{ To: addrs[len(peers)-1-i%len(peers)], Sender: AgentsTestAddresses[1], Message: make([]byte, 101), } b.ResetTimer() err = peer.RouteEnvelope(envel) if err != nil { b.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } <-rxPeer } } func BenchmarkAgentRegistration2(b *testing.B) { benchmarkAgentRegistration(2, b) } func BenchmarkAgentRegistration8(b *testing.B) { benchmarkAgentRegistration(8, b) } func BenchmarkAgentRegistration32(b *testing.B) { benchmarkAgentRegistration(32, b) } func BenchmarkAgentRegistration128(b *testing.B) { benchmarkAgentRegistration(128, b) } func BenchmarkAgentRegistration256(b *testing.B) { benchmarkAgentRegistration(256, b) } func BenchmarkAgentLookup2(b *testing.B) { benchmarkAgentLookup(2, b) } func BenchmarkAgentLookup8(b *testing.B) { benchmarkAgentLookup(8, b) } func BenchmarkAgentLookup32(b *testing.B) { benchmarkAgentLookup(32, b) } func BenchmarkAgentLookup128(b *testing.B) { benchmarkAgentLookup(128, b) } func BenchmarkAgentLookup256(b *testing.B) { benchmarkAgentLookup(256, b) } func BenchmarkPeerJoin2(b *testing.B) { benchmarkPeerJoin(2, b) } func BenchmarkPeerJoin8(b *testing.B) { benchmarkPeerJoin(8, b) } func BenchmarkPeerJoin32(b *testing.B) { benchmarkPeerJoin(32, b) } func BenchmarkPeerJoin128(b *testing.B) { benchmarkPeerJoin(128, b) } func BenchmarkPeerJoin256(b *testing.B) { benchmarkPeerJoin(256, b) } func BenchmarkPeerEcho2(b *testing.B) { benchmarkPeerEcho(2, b) } func BenchmarkPeerEcho8(b *testing.B) { benchmarkPeerEcho(8, b) } func BenchmarkPeerEcho32(b *testing.B) { benchmarkPeerEcho(32, b) } func BenchmarkPeerEcho128(b *testing.B) { benchmarkPeerEcho(128, b) } func BenchmarkPeerEcho256(b *testing.B) { benchmarkPeerEcho(256, b) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtpeer/dhtpeer.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtpeer provides an implementation of an Agent Communication Network node // using libp2p. It participates in data storage and routing for the network. // It offers RelayService for dhtclient and DelegateService for tcp clients. package dhtpeer import ( "bufio" "bytes" "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/binary" "encoding/pem" "fmt" "io" "log" "math/big" "net" "os" "strconv" "strings" "sync" "time" "github.com/btcsuite/btcd/btcec" "github.com/pkg/errors" "github.com/rs/zerolog" "google.golang.org/protobuf/proto" libp2p "github.com/libp2p/go-libp2p" cryptop2p "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" "github.com/multiformats/go-multiaddr" circuit "github.com/libp2p/go-libp2p-circuit" kaddht "github.com/libp2p/go-libp2p-kad-dht" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" acn "libp2p_node/acn" aea "libp2p_node/aea" common "libp2p_node/dht/common" "libp2p_node/dht/dhtnode" monitoring "libp2p_node/dht/monitoring" utils "libp2p_node/utils" ) const AcnStatusTimeout = 5.0 * time.Second const AcnStatusesQueueSize = 1000 const SlowQueueSize = 100 // panics if err is not nil func check(err error) { if err != nil { panic(err) } } func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } const ( addressLookupTimeout = 20 * time.Second timeoutBeforeMovingToSlowQueue = 3 * time.Second routingTableConnectionUpdateTimeout = 5 * time.Second newStreamTimeout = 10 * time.Second addressRegisterTimeout = 3 * time.Second addressRegistrationDelay = 0 * time.Second monitoringNamespace = "acn" metricDHTOpLatencyStore = "dht_op_latency_store" metricDHTOpLatencyLookup = "dht_op_latency_lookup" metricOpLatencyRegister = "op_latency_register" metricOpLatencyRoute = "op_latency_route" metricOpRouteCount = "op_route_count" metricOpRouteCountAll = "op_route_count_all" metricOpRouteCountSuccess = "op_route_count_success" metricServiceDelegateClientsCount = "service_delegate_clients_count" metricServiceDelegateClientsCountAll = "service_delegate_clients_count_all" metricServiceRelayClientsCount = "service_relay_clients_count" metricServiceRelayClientsCountAll = "service_relay_clients_count_all" defaultPersistentStoragePath = "./agent_records_store" ) var ( //latencyBucketsMilliSeconds = []float64{1., 10., 20., 50., 100., 200., 500., 1000.} latencyBucketsMicroSeconds = []float64{100., 500., 1e3, 1e4, 1e5, 5e5, 1e6} ) // DHTPeer A full libp2p node for the Agents Communication Network. // It is required to have a local address and a public one // and can acts as a relay for `DHTClient`. // Optionally, it provides delegate service for tcp clients. type DHTPeer struct { host string port uint16 publicHost string publicPort uint16 delegatePort uint16 monitoringPort uint16 enableRelay bool mailboxHostPort string mailboxServer *MailboxServer registrationDelay time.Duration key cryptop2p.PrivKey publicKey cryptop2p.PubKey localMultiaddr multiaddr.Multiaddr publicMultiaddr multiaddr.Multiaddr bootstrapPeers []peer.AddrInfo dht *kaddht.IpfsDHT routedHost *routedhost.RoutedHost tcpListener net.Listener cert *tls.Certificate sslSignature []byte addressAnnouncedMap map[string]bool addressAnnouncedMapLock sync.RWMutex // flag to announce addresses over network if peer connected to other peers enableAddressAnnouncement bool enableAddressAnnouncementWg *sync.WaitGroup enableAddressAnnouncementLock sync.RWMutex myAgentAddress string myAgentRecord *acn.AgentRecord myAgentReady func() bool dhtAddresses map[string]string acnStatuses map[string]chan *acn.StatusBody tcpAddresses map[string]net.Conn agentRecords map[string]*acn.AgentRecord acnStatusesLock sync.RWMutex dhtAddressesLock sync.RWMutex tcpAddressesLock sync.RWMutex agentRecordsLock sync.RWMutex // TOFIX(LR): maps and locks need refactoring for better abstraction processEnvelope func(*aea.Envelope) error persistentStoragePath string storage *os.File monitor monitoring.MonitoringService closing chan struct{} isClosing bool goroutines *sync.WaitGroup logger zerolog.Logger // syncMessages map[string]*sync.WaitGroup syncMessagesLock sync.RWMutex syncMessages map[string](chan *aea.Envelope) slow_queue chan *aea.Envelope } // New creates a new DHTPeer func New(opts ...Option) (*DHTPeer, error) { var err error dhtPeer := &DHTPeer{registrationDelay: addressRegistrationDelay, isClosing: false} dhtPeer.dhtAddresses = map[string]string{} dhtPeer.tcpAddresses = map[string]net.Conn{} dhtPeer.agentRecords = map[string]*acn.AgentRecord{} dhtPeer.acnStatuses = map[string]chan *acn.StatusBody{} dhtPeer.dhtAddressesLock = sync.RWMutex{} dhtPeer.tcpAddressesLock = sync.RWMutex{} dhtPeer.agentRecordsLock = sync.RWMutex{} dhtPeer.persistentStoragePath = defaultPersistentStoragePath dhtPeer.syncMessages = make(map[string](chan *aea.Envelope)) dhtPeer.addressAnnouncedMap = map[string]bool{} dhtPeer.addressAnnouncedMapLock = sync.RWMutex{} dhtPeer.enableAddressAnnouncementWg = &sync.WaitGroup{} dhtPeer.enableAddressAnnouncementLock = sync.RWMutex{} dhtPeer.mailboxHostPort = "" dhtPeer.slow_queue = make(chan *aea.Envelope, SlowQueueSize) for _, opt := range opts { if err := opt(dhtPeer); err != nil { return nil, err } } dhtPeer.closing = make(chan struct{}) dhtPeer.goroutines = &sync.WaitGroup{} /* check correct configuration */ // private key if dhtPeer.key == nil { return nil, errors.New("private key must be provided") } // local uri if dhtPeer.localMultiaddr == nil { return nil, errors.New("local host and port must be set") } // public uri if dhtPeer.publicMultiaddr == nil { return nil, errors.New("public host and port must be set") } // check if the PoR is delivered for my public key if dhtPeer.myAgentRecord != nil { myPublicKey, err := utils.FetchAIPublicKeyFromPubKey(dhtPeer.publicKey) status, errPoR := dhtnode.IsValidProofOfRepresentation( dhtPeer.myAgentRecord, dhtPeer.myAgentRecord.Address, myPublicKey, ) if err != nil || errPoR != nil || status.Code != acn.SUCCESS { errMsg := "Invalid AgentRecord" if err == nil { err = errors.New(errMsg) } else { err = errors.Wrap(err, errMsg) } if errPoR != nil { err = errors.Wrap(err, errPoR.Error()) } return nil, err } } /* setup libp2p node */ ctx := context.Background() // setup public uri as external address addressFactory := func(addrs []multiaddr.Multiaddr) []multiaddr.Multiaddr { return []multiaddr.Multiaddr{dhtPeer.publicMultiaddr} } // libp2p options libp2pOpts := []libp2p.Option{ libp2p.ListenAddrs(dhtPeer.localMultiaddr), libp2p.AddrsFactory(addressFactory), libp2p.Identity(dhtPeer.key), libp2p.DefaultTransports, libp2p.DefaultMuxers, libp2p.DefaultSecurity, libp2p.NATPortMap(), libp2p.EnableNATService(), libp2p.EnableRelay(circuit.OptHop), } // create a basic host basicHost, err := libp2p.New(ctx, libp2pOpts...) if err != nil { return nil, err } // create the dht dhtPeer.dht, err = kaddht.New(ctx, basicHost, kaddht.Mode(kaddht.ModeServer)) if err != nil { return nil, err } err = dhtPeer.makeSSLCertifiateAndSignature() if err != nil { return nil, err } // make the routed host dhtPeer.routedHost = routedhost.Wrap(basicHost, dhtPeer.dht) dhtPeer.setupLogger() lerror, _, linfo, ldebug := dhtPeer.GetLoggers() basicHost.Network().Notify(&Notifee{ logger: dhtPeer.logger, }) // connect to the booststrap nodes if len(dhtPeer.bootstrapPeers) > 0 { linfo().Msgf("Bootstrapping from %s", dhtPeer.bootstrapPeers) err = utils.BootstrapConnect(ctx, dhtPeer.routedHost, dhtPeer.dht, dhtPeer.bootstrapPeers) if err != nil { dhtPeer.Close() return nil, err } } // bootstrap the dht err = dhtPeer.dht.Bootstrap(ctx) if err != nil { dhtPeer.Close() return nil, err } linfo().Msgf("My Peer ID is %s", dhtPeer.PeerID()) linfo().Msg("successfully created libp2p node!") /* setup DHTPeer message handlers and services */ // setup monitoring dhtPeer.setupMonitoring() // relay service if dhtPeer.enableRelay { // Allow clients to register their agents addresses ldebug().Msg("Setting /aea-register/0.1.0 stream...") dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaRegisterRelayStream, dhtPeer.handleAeaRegisterStream) } // new peers connection notification, so that this peer can register its addresses dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaNotifStream, dhtPeer.handleAeaNotifStream) // Notify bootstrap peers if any for _, bPeer := range dhtPeer.bootstrapPeers { ctx := context.Background() s, err := dhtPeer.routedHost.NewStream(ctx, bPeer.ID, dhtnode.AeaNotifStream) if err != nil { lerror(err).Msgf("failed to open stream to notify bootstrap peer %s", bPeer.ID) dhtPeer.Close() return nil, err } _, err = s.Write([]byte(dhtnode.AeaNotifStream)) if err != nil { lerror(err).Msgf("failed to notify bootstrap peer %s", bPeer.ID) dhtPeer.Close() return nil, err } s.Close() } // initialize agents records persistent storage if dhtPeer.persistentStoragePath == defaultPersistentStoragePath { myPeerID, err := peer.IDFromPublicKey(dhtPeer.publicKey) ignore(err) dhtPeer.persistentStoragePath += "_" + myPeerID.Pretty() } nbr, err := dhtPeer.initAgentRecordPersistentStorage() if err != nil { return nil, errors.Wrap(err, "while initializing agent record storage") } if len(dhtPeer.bootstrapPeers) > 0 { for addr := range dhtPeer.dhtAddresses { err := dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Str("addr", addr). Msg("while announcing stored client address") } } } linfo().Msgf("successfully loaded %d agents", nbr) // if peer is joining an existing network, announce my agent address if set if len(dhtPeer.bootstrapPeers) > 0 { // there are some bootstrap peers so we can announce addresses to them dhtPeer.enableAddressAnnouncement = true if dhtPeer.myAgentAddress != "" && !dhtPeer.IsAddressAnnounced(dhtPeer.myAgentAddress) { ldebug().Msg("Address was announced on bootstrap peers") opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() err := dhtPeer.RegisterAgentAddress(dhtPeer.myAgentAddress) if err != nil { dhtPeer.Close() return nil, err } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) } } // aea addresses lookup ldebug().Msg("Setting /aea-address/0.1.0 stream...") dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaAddressStream, dhtPeer.handleAeaAddressStream) // incoming envelopes stream ldebug().Msg("Setting /aea/0.1.0 stream...") dhtPeer.routedHost.SetStreamHandler(dhtnode.AeaEnvelopeStream, dhtPeer.handleAeaEnvelopeStream) // setup delegate service if dhtPeer.delegatePort != 0 { dhtPeer.launchDelegateService() ready := &sync.WaitGroup{} dhtPeer.goroutines.Add(1) ready.Add(1) go dhtPeer.handleDelegateService(ready) ready.Wait() } // check mailbox uri is set if len(dhtPeer.mailboxHostPort) != 0 { dhtPeer.launchMailboxService() } // start monitoring ready := &sync.WaitGroup{} ready.Add(1) go dhtPeer.startMonitoring(ready) ready.Wait() go dhtPeer.slowEnvelopeSendLoop() return dhtPeer, nil } // saveAgentRecordToPersistentStorage saves the agent record to persistent storage func (dhtPeer *DHTPeer) saveAgentRecordToPersistentStorage(record *acn.AgentRecord) error { msg := formatPersistentStorageLine(record) if len(msg) == 0 { return errors.New("while formating record " + record.String()) } size := uint32(len(msg)) buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, size) buf = append(buf, msg...) _, err := dhtPeer.storage.Write(buf) if err != nil { return errors.Wrap(err, "while writing record to persistent storage") } return nil } func parsePersistentStorageLine(line []byte) (*acn.AgentRecord, error) { record := &acn.AgentRecord{} err := proto.Unmarshal(line, record) return record, err } func formatPersistentStorageLine(record *acn.AgentRecord) []byte { msg, err := proto.Marshal(record) ignore(err) return msg } // initAgentRecordPersistentStorage loads agent records from persistent storage func (dhtPeer *DHTPeer) initAgentRecordPersistentStorage() (int, error) { var err error _, _, linfo, _ := dhtPeer.GetLoggers() linfo().Msg("Load records from store " + dhtPeer.persistentStoragePath) dhtPeer.storage, err = os.OpenFile( dhtPeer.persistentStoragePath, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0600, ) if err != nil { return 0, err } reader := bufio.NewReader(dhtPeer.storage) var counter int = 0 for { buf := make([]byte, 4) _, err = io.ReadFull(reader, buf) if err == io.EOF { break } if err != nil { return 0, errors.Wrap(err, "while loading agent records") } size := binary.BigEndian.Uint32(buf) line := make([]byte, size) _, err = io.ReadFull(reader, line) if err != nil { return 0, errors.Wrap(err, "while loading agent records") } record, err := parsePersistentStorageLine(line) if err != nil { return 0, errors.Wrap(err, "while loading agent records") } dhtPeer.agentRecords[record.Address] = record relayPeerID, err := utils.IDFromFetchAIPublicKey(record.PeerPublicKey) if err != nil { return 0, errors.Wrap(err, "While loading agent records") } dhtPeer.dhtAddresses[record.Address] = relayPeerID.Pretty() counter++ } return counter, nil } func (dhtPeer *DHTPeer) closeAgentRecordPersistentStorage() error { dhtPeer.agentRecordsLock.Lock() err := dhtPeer.storage.Close() dhtPeer.agentRecordsLock.Unlock() return err } func (dhtPeer *DHTPeer) setupMonitoring() { if dhtPeer.monitoringPort != 0 { dhtPeer.monitor = monitoring.NewPrometheusMonitoring( monitoringNamespace, dhtPeer.monitoringPort, ) } else { dhtPeer.monitor = monitoring.NewFileMonitoring(monitoringNamespace, false) } dhtPeer.addMonitoringMetrics() } func (dhtPeer *DHTPeer) startMonitoring(ready *sync.WaitGroup) { _, _, linfo, _ := dhtPeer.GetLoggers() linfo().Msg("Starting monitoring service: " + dhtPeer.monitor.Info()) go dhtPeer.monitor.Start() ready.Done() } func (dhtPeer *DHTPeer) addMonitoringMetrics() { buckets := latencyBucketsMicroSeconds var err error // acn primitives _, err = dhtPeer.monitor.NewHistogram(metricDHTOpLatencyStore, "Histogram for time to store a key in the DHT", buckets) ignore(err) _, err = dhtPeer.monitor.NewHistogram(metricDHTOpLatencyLookup, "Histogram for time to find a key in the DHT", buckets) ignore(err) // acn main service _, err = dhtPeer.monitor.NewHistogram(metricOpLatencyRegister, "Histogram for end-to-end time to register an agent in the acn", buckets) ignore(err) _, err = dhtPeer.monitor.NewHistogram( metricOpLatencyRoute, "Histogram for end-to-end time to route an envelope to its destination, excluding time to send envelope itself", buckets, ) ignore(err) _, err = dhtPeer.monitor.NewGauge(metricOpRouteCount, "Number of ongoing envelope routing requests") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricOpRouteCountAll, "Total number envelope routing requests, successful or not") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricOpRouteCountSuccess, "Total number envelope routed successfully") ignore(err) // acn delegate service _, err = dhtPeer.monitor.NewGauge(metricServiceDelegateClientsCount, "Number of active delagate connections") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricServiceDelegateClientsCountAll, "Number of all delagate clients, connected or disconnected") ignore(err) // acn relay service _, err = dhtPeer.monitor.NewGauge(metricServiceRelayClientsCount, "Number of active relay clients") ignore(err) _, err = dhtPeer.monitor.NewCounter(metricServiceRelayClientsCountAll, "Total number of all relayed clients, connected or disconnected") ignore(err) } func (dhtPeer *DHTPeer) setupLogger() { fields := map[string]string{ "package": "DHTPeer", } if dhtPeer.routedHost != nil { fields["peerid"] = dhtPeer.routedHost.ID().Pretty() } dhtPeer.logger = utils.NewDefaultLoggerWithFields(fields) } func (dhtPeer *DHTPeer) PeerID() string { return dhtPeer.routedHost.ID().Pretty() } func (dhtPeer *DHTPeer) GetLoggers() (func(error) *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event, func() *zerolog.Event) { ldebug := dhtPeer.logger.Debug linfo := dhtPeer.logger.Info lwarn := dhtPeer.logger.Warn lerror := func(err error) *zerolog.Event { if err == nil { return dhtPeer.logger.Error().Str("err", "nil") } return dhtPeer.logger.Error().Str("err", err.Error()) } return lerror, lwarn, linfo, ldebug } // SetLogLevel set utils logger level func (dhtPeer *DHTPeer) SetLogLevel(lvl zerolog.Level) { dhtPeer.logger = dhtPeer.logger.Level(lvl) } // Close stops the DHTPeer func (dhtPeer *DHTPeer) Close() []error { var err error var status []error _, _, linfo, _ := dhtPeer.GetLoggers() linfo().Msg("Stopping DHTPeer...") close(dhtPeer.closing) dhtPeer.isClosing = true //return status errappend := func(err error) { if err != nil { status = append(status, err) } } if dhtPeer.tcpListener != nil { err = dhtPeer.tcpListener.Close() errappend(err) dhtPeer.tcpAddressesLock.Lock() for _, conn := range dhtPeer.tcpAddresses { err = conn.Close() errappend(err) } dhtPeer.tcpAddressesLock.Unlock() } if dhtPeer.mailboxServer != nil { dhtPeer.mailboxServer.stop() } err = dhtPeer.dht.Close() errappend(err) err = dhtPeer.routedHost.Close() errappend(err) err = dhtPeer.closeAgentRecordPersistentStorage() errappend(err) //linfo().Msg("Stopping DHTPeer: waiting for goroutines to cancel...") // dhtPeer.goroutines.Wait() dhtPeer.syncMessagesLock.Lock() for _, channel := range dhtPeer.syncMessages { close(channel) } dhtPeer.syncMessagesLock.Unlock() close(dhtPeer.slow_queue) return status } // Generate selfsigned c509 certificate with temprorary key to be used with TLS server // We can not use peer private key cause it does not supported by golang TLS implementation // So we generate a new one and send session public key signature made with peer private key // snd client can validate it with peer public key/address func generate_x509_cert() (*tls.Certificate, error) { privBtcKey, err := btcec.NewPrivateKey(elliptic.P256()) if err != nil { return nil, errors.Wrap(err, "while creating new private key") } privKey := privBtcKey.ToECDSA() pubKey := &privKey.PublicKey ca := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ Organization: []string{"Acn Node"}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(1, 0, 0), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, }, BasicConstraintsValid: true, } ca.IsCA = true ca.KeyUsage |= x509.KeyUsageCertSign certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, pubKey, privKey) if err != nil { return nil, errors.Wrap(err, "while creating ca") } certPEM := new(bytes.Buffer) err = pem.Encode(certPEM, &pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) if err != nil { return nil, errors.Wrap(err, "while encoding cert pem") } privPEM := new(bytes.Buffer) b, err := x509.MarshalECPrivateKey(privKey) if err != nil { return nil, errors.Wrap(err, "while marshaling ec private key") } err = pem.Encode(privPEM, &pem.Block{ Type: "EC PRIVATE KEY", Bytes: b, }) if err != nil { return nil, errors.Wrap(err, "while encoding prive pem") } cert, err := tls.X509KeyPair(certPEM.Bytes(), privPEM.Bytes()) return &cert, err } // launchDelegateService launches the delegate service on the configured uri func (dhtPeer *DHTPeer) launchDelegateService() { var err error lerror, _, _, _ := dhtPeer.GetLoggers() config := &tls.Config{Certificates: []tls.Certificate{*dhtPeer.cert}} uri := dhtPeer.host + ":" + strconv.FormatInt(int64(dhtPeer.delegatePort), 10) listener, err := tls.Listen("tcp", uri, config) if err != nil { lerror(err).Msgf("while setting up listening tcp socket %s", uri) check(err) } if err != nil { lerror(err).Msgf("while generating tls signature") check(err) } dhtPeer.tcpListener = TLSListener{Listener: listener, Signature: dhtPeer.sslSignature} } // launchDelegateService launches the delegate service on the configured uri func (dhtPeer *DHTPeer) makeSSLCertifiateAndSignature() error { var err error lerror, _, _, _ := dhtPeer.GetLoggers() dhtPeer.cert, err = generate_x509_cert() if err != nil { lerror(err).Msgf("while generating tls certificate") return err } dhtPeer.sslSignature, err = makeSessionKeySignature(dhtPeer.cert, dhtPeer.key) if err != nil { lerror(err).Msgf("while generating tls certificate server signature") return err } return nil } // launchMailboxService launches the mailbox http service on the configured uri func (dhtPeer *DHTPeer) launchMailboxService() { _, _, linfo, _ := dhtPeer.GetLoggers() if len(dhtPeer.mailboxHostPort) == 0 { return } dhtPeer.mailboxServer = &MailboxServer{ addr: dhtPeer.mailboxHostPort, dhtPeer: dhtPeer, } linfo().Msgf("Starting mailbox service on %s", dhtPeer.mailboxHostPort) go dhtPeer.mailboxServer.start() } // Make signature for session public key using peer private key func makeSessionKeySignature(cert *tls.Certificate, privateKey cryptop2p.PrivKey) ([]byte, error) { cert_pub_key := cert.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey) cert_pub_key_bytes := elliptic.Marshal(cert_pub_key.Curve, cert_pub_key.X, cert_pub_key.Y) signature, err := privateKey.Sign(cert_pub_key_bytes) return signature, err } // handleDelegateService listens for new connections to delegate service and handles them func (dhtPeer *DHTPeer) handleDelegateService(ready *sync.WaitGroup) { defer dhtPeer.goroutines.Done() defer dhtPeer.tcpListener.Close() lerror, _, linfo, _ := dhtPeer.GetLoggers() done := false L: for { select { default: linfo().Msg("DelegateService listening for new connections...") if !done { done = true ready.Done() } conn, err := dhtPeer.tcpListener.Accept() if err != nil { if strings.Contains(err.Error(), "use of closed network connection") { // About using string comparison to get the type of err, // check https://github.com/golang/go/issues/4373 linfo().Msg("DelegateService Stopped.") } else { lerror(err).Msgf("while accepting a new connection") } } else { dhtPeer.goroutines.Add(1) go dhtPeer.handleNewDelegationConnection(conn) } case <-dhtPeer.closing: break L } } } func (dhtPeer *DHTPeer) CheckPOR(record *acn.AgentRecord) (*acn.StatusBody, error) { addr := record.Address myPubKey, err := utils.FetchAIPublicKeyFromPubKey(dhtPeer.publicKey) ignore(err) status, err := dhtnode.IsValidProofOfRepresentation(record, addr, myPubKey) return status, err } // handleNewDelegationConnection handles a new delegate connection // verifies agent record and registers agent in DHT, handles incoming envelopes // and forwards them for processing func (dhtPeer *DHTPeer) handleNewDelegationConnection(conn net.Conn) { defer dhtPeer.goroutines.Done() defer conn.Close() // to limit spamming time.Sleep(dhtPeer.registrationDelay) nbrConns, _ := dhtPeer.monitor.GetGauge(metricServiceDelegateClientsCount) nbrClients, _ := dhtPeer.monitor.GetCounter(metricServiceDelegateClientsCountAll) opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() lerror, _, linfo, _ := dhtPeer.GetLoggers() //linfo().Msgf("received a new connection from %s", conn.RemoteAddr().String()) // read agent registration message conPipe := utils.ConnPipe{Conn: conn} var register *acn.RegisterPerformative var err error register, err = acn.ReadAgentRegistrationMessage(conPipe) if err != nil { lerror(err).Msg("while receiving agent's registration request") nbrConns.Dec() return } linfo().Msgf("Received registration request %s", register) // Get Register message record := register.Record addr := record.Address linfo().Msgf("connection from %s established for Address %s", conn.RemoteAddr().String(), addr) // check if the PoR is valid status, err := dhtPeer.CheckPOR(record) if err != nil || status.Code != acn.SUCCESS { lerror(err).Msg("PoR is not valid") acn_send_error := acn.SendAcnError(conPipe, err.Error(), status.Code) ignore(acn_send_error) nbrConns.Dec() return } // TOFIX(LR) post-pone answer until address successfully registered err = acn.SendAcnSuccess(conPipe) if err != nil { nbrConns.Dec() return } // Add connection to map dhtPeer.agentRecordsLock.Lock() dhtPeer.agentRecords[addr] = record dhtPeer.agentRecordsLock.Unlock() dhtPeer.tcpAddressesLock.Lock() dhtPeer.tcpAddresses[addr] = conn dhtPeer.tcpAddressesLock.Unlock() dhtPeer.acnStatusesLock.Lock() dhtPeer.acnStatuses[addr] = make(chan *acn.StatusBody, AcnStatusesQueueSize) dhtPeer.acnStatusesLock.Unlock() //linfo().Msgf("announcing tcp client address %s...", addr) // TOFIX(LR) disconnect client? if dhtPeer.IsAddressAnnouncementEnabled() { err = dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Msgf("while announcing tcp client address %s to the dht", addr) return } } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) nbrConns.Inc() nbrClients.Inc() connPipe := utils.ConnPipe{Conn: conn} for { // read envelopes envel, err := aea.HandleAcnMessageFromPipe(connPipe, dhtPeer, addr) if err != nil { if err == io.EOF { linfo().Str( "addr", addr, ).Msgf( "connection closed by client: %s, stopping...", err.Error(), ) } else { lerror(err).Str("addr", addr).Msg("while reading envelope from client connection, aborting...") } nbrConns.Dec() break } if envel == nil { // ACN status continue } linfo().Str( "addr", addr, ).Msgf( "got envelope from delegate connection", ) if envel.Sender != addr { err = errors.New("Sender (" + envel.Sender + ") must match registered address") lerror(err).Str("addr", addr). Msg("while routing delegate client envelope") continue } // initializing pairwise channel queues and one go routine per pair pair := envel.To + envel.Sender dhtPeer.syncMessagesLock.Lock() _, ok := dhtPeer.syncMessages[pair] dhtPeer.syncMessagesLock.Unlock() if !ok { dhtPeer.syncMessagesLock.Lock() dhtPeer.syncMessages[pair] = make(chan *aea.Envelope, 1000) dhtPeer.syncMessagesLock.Unlock() // route envelope dhtPeer.goroutines.Add(1) go func() { defer dhtPeer.goroutines.Done() dhtPeer.syncMessagesLock.Lock() pair_range := dhtPeer.syncMessages[pair] dhtPeer.syncMessagesLock.Unlock() for e := range pair_range { err := dhtPeer.RouteEnvelope(e) if err != nil { lerror(err).Str("addr", addr). Msg("while routing delegate client envelope") // TODO() send error back } } }() } // add to queue (nonblocking - buffered queue) dhtPeer.syncMessagesLock.Lock() select { case dhtPeer.syncMessages[pair] <- envel: default: // send back! error fmt.Println("CHANNEL FULL, DISCARDING <<<-------- ", string(envel.Message)) } dhtPeer.syncMessagesLock.Unlock() } linfo().Str("addr", addr). Msg("delegate client disconnected") // Remove connection from map dhtPeer.tcpAddressesLock.Lock() delete(dhtPeer.tcpAddresses, addr) dhtPeer.tcpAddressesLock.Unlock() // TOFIX(LR) currently I am keeping the agent record dhtPeer.acnStatusesLock.Lock() delete(dhtPeer.acnStatuses, addr) dhtPeer.acnStatusesLock.Unlock() } // ProcessEnvelope register callback function func (dhtPeer *DHTPeer) ProcessEnvelope(fn func(*aea.Envelope) error) { dhtPeer.processEnvelope = fn } // MultiAddr libp2p multiaddr of the peer func (dhtPeer *DHTPeer) MultiAddr() string { multiAddr, _ := multiaddr.NewMultiaddr( fmt.Sprintf("/p2p/%s", dhtPeer.routedHost.ID().Pretty())) addrs := dhtPeer.routedHost.Addrs() if len(addrs) == 0 { return "" } return addrs[0].Encapsulate(multiAddr).String() } func (dhtPeer *DHTPeer) GetCertAndSignature() (*tls.Certificate, []byte) { return dhtPeer.cert, dhtPeer.sslSignature } // RouteEnvelope to its destination func (dhtPeer *DHTPeer) getEnvelopeAcnRecord(envel *aea.Envelope) (*acn.AgentRecord, error) { sender := envel.Sender dhtPeer.agentRecordsLock.RLock() localRec, existsLocal := dhtPeer.agentRecords[sender] dhtPeer.agentRecordsLock.RUnlock() var envelRec *acn.AgentRecord if sender == dhtPeer.myAgentAddress { envelRec = dhtPeer.myAgentRecord } else if dhtPeer.mailboxServer != nil && dhtPeer.mailboxServer.IsAddrRegistered(sender) { envelRec = dhtPeer.mailboxServer.GetAgentRecord(sender) } else if existsLocal { // TOFIX(LR) should acquire RLock envelRec = localRec } else { err := errors.New("Envelope sender is not registered locally " + sender) return nil, err } return envelRec, nil } func (dhtPeer *DHTPeer) RouteEnvelope(envel *aea.Envelope) error { lerror, lwarn, linfo, ldebug := dhtPeer.GetLoggers() routeCount, _ := dhtPeer.monitor.GetGauge(metricOpRouteCount) routeCountAll, _ := dhtPeer.monitor.GetCounter(metricOpRouteCountAll) routeCountSuccess, _ := dhtPeer.monitor.GetCounter(metricOpRouteCountSuccess) opLatencyRoute, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRoute) timer := dhtPeer.monitor.Timer() routeCount.Inc() routeCountAll.Inc() start := timer.NewTimer() ldebug().Str("addr", envel.To).Msgf("-> Routing envelope: %s", envel.String()) // get sender agent envelRec // TODO can change function signature to force the caller to provide the envelRec target := envel.To var err error envelRec, err := dhtPeer.getEnvelopeAcnRecord(envel) if err != nil { lerror(err).Str("op", "route").Str("addr", envel.To). Msg("") return err } dhtPeer.tcpAddressesLock.RLock() connDelegate, existsDelegate := dhtPeer.tcpAddresses[target] dhtPeer.tcpAddressesLock.RUnlock() if dhtPeer.mailboxServer != nil { if dhtPeer.mailboxServer.RouteEnvelope(envel) { linfo().Str("op", "route").Str("addr", target). Msg("route envelope destinated to mailbox registered agent...") return nil } } if target == dhtPeer.myAgentAddress { linfo().Str("op", "route").Str("addr", target). Msg("route envelope destinated to my local agent...") // TOFIX(LR) risk of infinite loop for dhtPeer.myAgentReady != nil && !dhtPeer.myAgentReady() { lwarn().Str("op", "route").Str("addr", target). Msg("agent not ready yet, sleeping for some time ...") time.Sleep(time.Duration(100) * time.Millisecond) } if dhtPeer.processEnvelope != nil { duration := timer.GetTimer(start) opLatencyRoute.Observe(float64(duration.Microseconds())) err := dhtPeer.processEnvelope(envel) routeCount.Dec() if err != nil { return err } routeCountSuccess.Inc() } else { lwarn().Str("op", "route").Str("addr", target). Msgf("ProcessEnvelope not set, ignoring envelope %s", envel.String()) return errors.New("Agent not ready") } } else if existsDelegate { linfo().Str("op", "route").Str("addr", target). Msgf("destination is a delegate client %s", connDelegate.RemoteAddr().String()) routeCount.Dec() routeCountSuccess.Inc() duration := timer.GetTimer(start) opLatencyRoute.Observe(float64(duration.Microseconds())) data, err := aea.MakeAcnMessageFromEnvelope(envel) if err != nil { lerror(err).Msgf("while serializing envelope: %s", envel) return err } err = utils.WriteBytesConn(connDelegate, data) if err != nil { lerror(err).Msgf("while writing envelope: %s", envel) return err } linfo().Str("addr", target).Msg("wait for acn ack") err = dhtPeer.AwaitAcnStatus(target) linfo().Str("addr", target).Msg("got acn ack") if err != nil { lerror(err).Msgf("while waiting acn ack for envelope: %s", envel) return err } } else { var peerID peer.ID var err error dhtPeer.dhtAddressesLock.RLock() sPeerID, exists := dhtPeer.dhtAddresses[target] dhtPeer.dhtAddressesLock.RUnlock() if exists { linfo().Str("op", "route").Str("addr", target). Msgf("destination is a relay client %s", sPeerID) peerID, err = peer.Decode(sPeerID) linfo().Str("op", "route").Str("addr", target). Msgf("relay client peer is %s", peerID) if err != nil { lerror(err).Str("op", "route").Str("addr", target). Msgf("CRITICAL couldn't parse peer id from relay client id") routeCount.Dec() return err } return dhtPeer._routeEnvelopeWithNewStream(envel, peerID, envelRec) } // not exists err = dhtPeer._routeEnvelopeDHTLookup(envel, timeoutBeforeMovingToSlowQueue) if err != nil { routeCount.Dec() if !dhtPeer.isClosing { lerror(err).Str("op", "route").Str("addr", target). Msg("while rooting and looking up address on the DHT. moving it to she slow queue") dhtPeer.slow_queue <- envel } else { lerror(err).Str("op", "route").Str("addr", target). Msg("while rooting and looking up address on the DHT. dht peer is closing. message dropped") } return nil } } return nil } func (dhtPeer *DHTPeer) slowEnvelopeSendLoop() { lerror, _, _, ldebug := dhtPeer.GetLoggers() var err error for { envel, ok := <-dhtPeer.slow_queue if !ok || dhtPeer.isClosing { ldebug().Msg("slow envelope send loop closed") return } if envel == nil { ldebug().Msg("got nil envelope. exit slow envelope send loop") return } ldebug().Str("addr", envel.To).Msgf("-> Routing slow envelope: %s", envel.String()) err = dhtPeer._routeEnvelopeDHTLookup(envel, addressLookupTimeout) if err != nil { lerror(err).Msgf("while sending slow envelope: %s", envel.String()) } else { ldebug().Str("addr", envel.To).Msgf("sent slow envelope: %s", envel.String()) } } } func (dhtPeer *DHTPeer) _routeEnvelopeDHTLookup( envel *aea.Envelope, lookup_timeout time.Duration, ) error { target := envel.To lerror, _, linfo, _ := dhtPeer.GetLoggers() envelRec, err := dhtPeer.getEnvelopeAcnRecord(envel) if err != nil { return err } linfo().Str("op", "route").Str("addr", target). Msg("did NOT find destination address locally, looking for it in the DHT...") ctx, cancel_lookup := context.WithTimeout(context.Background(), lookup_timeout) defer cancel_lookup() peerID, _, err := dhtPeer.lookupAddressDHT(ctx, target) // guarantees peerID has a valid PoR if err != nil { lerror(err).Str("op", "route").Str("addr", target). Msg("while looking up address on the DHT") return err } err = dhtPeer._routeEnvelopeWithNewStream(envel, peerID, envelRec) return err } func (dhtPeer *DHTPeer) _routeEnvelopeWithNewStream( envel *aea.Envelope, peerID peer.ID, envelRec *acn.AgentRecord, ) error { //linfo().Str("op", "route").Str("addr", target). // Msgf("got peer id '%s' for agent address", peerID.Pretty()) //linfo().Str("op", "route").Str("addr", target). // Msgf("opening stream to target %s...", peerID.Pretty()) lerror, _, linfo, _ := dhtPeer.GetLoggers() routeCount, _ := dhtPeer.monitor.GetGauge(metricOpRouteCount) opLatencyRoute, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRoute) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() var err error target := envel.To ctx, cancel := context.WithTimeout(context.Background(), newStreamTimeout) defer cancel() stream, err := dhtPeer.routedHost.NewStream(ctx, peerID, dhtnode.AeaEnvelopeStream) if err != nil { lerror(err).Str("op", "route").Str("addr", target). Msgf("timeout, couldn't open stream to target %s", peerID.Pretty()) routeCount.Dec() return err } duration := timer.GetTimer(start) opLatencyRoute.Observe(float64(duration.Microseconds())) linfo().Str("op", "route").Str("addr", target). Msgf("sending envelope to target peer %s...", peerID.Pretty()) envelBytes, err := proto.Marshal(envel) if err != nil { lerror(err). Str("op", "route"). Str("addr", target). Msg("couldn't serialize envelope") errReset := stream.Reset() ignore(errReset) routeCount.Dec() return err } streamPipe := utils.StreamPipe{Stream: stream} err = acn.SendEnvelopeMessage(streamPipe, envelBytes, envelRec) if err != nil { lerror(err). Str("op", "route"). Str("addr", target). Msg("couldn't send envelope") errReset := stream.Reset() ignore(errReset) routeCount.Dec() return err } // wait for response linfo().Str("op", "route").Str("addr", target). Msgf("waiting fro envelope delivery confirmation from target peer %s...", peerID.Pretty()) statusBody, err := acn.ReadAcnStatus(streamPipe) if err != nil { lerror(err). Str("op", "route"). Str("addr", target). Msg("failed to decode acn status") routeCount.Dec() return err } if statusBody.Code != acn.SUCCESS { err = errors.New(statusBody.Code.String() + " : " + strings.Join(statusBody.Msgs, ":")) lerror(err). Str("op", "route"). Str("addr", target). Msg("failed to deliver envelope") routeCount.Dec() return err } routeCount.Dec() return nil } /// TOFIX(LR) should return (*dhtnode) func (dhtPeer *DHTPeer) lookupAddressDHT( ctx context.Context, address string, ) (peer.ID, *acn.AgentRecord, error) { lerror, lwarn, linfo, _ := dhtPeer.GetLoggers() var err error dhtLookupLatency, _ := dhtPeer.monitor.GetHistogram(metricDHTOpLatencyLookup) timer := dhtPeer.monitor.Timer() addressCID, err := utils.ComputeCID(address) if err != nil { return "", nil, err } linfo().Str("op", "lookup").Str("addr", address). Msgf("Querying for providers for cid %s...", addressCID.String()) ctx, cancel := context.WithTimeout(ctx, addressLookupTimeout) defer cancel() //var elapsed time.Duration var provider peer.AddrInfo var stream network.Stream start := timer.NewTimer() noProvider := false for { providers := dhtPeer.dht.FindProvidersAsync(ctx, addressCID, 0) for provider = range providers { duration := timer.GetTimer(start) dhtLookupLatency.Observe(float64(duration.Microseconds())) //linfo().Str("op", "lookup").Str("addr", address). // Msgf("found provider %s after %s", provider, elapsed.String()) // Add peer to host PeerStore - the provider should be the holder of the address dhtPeer.routedHost.Peerstore().AddAddrs( provider.ID, provider.Addrs, peerstore.PermanentAddrTTL, ) //linfo().Str("op", "lookup").Str("addr", address). // Msgf("opening stream to the address provider %s...", provider) ctxConnect := context.Background() stream, err = dhtPeer.routedHost.NewStream( ctxConnect, provider.ID, dhtnode.AeaAddressStream, ) if err != nil { lwarn().Str("op", "lookup").Str("addr", address). Msgf("couldn't open stream to address provider %s: %s, looking up other providers...", provider, err.Error()) dhtPeer.routedHost.Peerstore().ClearAddrs(provider.ID) continue } streamPipe := utils.StreamPipe{Stream: stream} linfo().Str("op", "lookup").Str("addr", address). Msgf("getting agent record from provider %s...", provider) // perfrormaddress lookup record, err := acn.PerformAddressLookup(streamPipe, address) // Response is either a LookupResponse or Status ignore(stream.Reset()) stream.Close() if err != nil { lwarn().Str("op", "lookup").Str("addr", address). Msgf("Failed agent lookup from provider %s (%s), looking up other providers...", provider, err.Error()) continue } // lookupResponse must be set valid, err := dhtnode.IsValidProofOfRepresentation( record, address, record.PeerPublicKey, ) if err != nil || valid.Code != acn.SUCCESS { errMsg := valid.Code.String() + " : " + strings.Join(valid.Msgs, ":") if err == nil { err = errors.New(errMsg) } else { err = errors.Wrap(err, valid.Code.String()+" : "+strings.Join(valid.Msgs, ":")) } lwarn().Str("op", "lookup").Str("addr", address). Msgf("invalid agent record from provider %s (%s), looking up other providers...", provider, err.Error()) continue } peerid, err := utils.IDFromFetchAIPublicKey(record.PeerPublicKey) if err != nil { return "", nil, errors.New( "CRITICAL couldn't get peer ID from message:" + err.Error(), ) } return peerid, record, nil } if provider.ID == "" { msg := "didn't find any provider for address" if !noProvider { noProvider = true lwarn().Str("op", "lookup").Str("addr", address).Msgf("%s, retrying...", msg) } select { default: time.Sleep(200 * time.Millisecond) case <-ctx.Done(): err = errors.New(msg + " " + address + " within timeout") lerror(err).Str("op", "lookup").Str("addr", address).Msg("") return "", nil, err } } else { return "", nil, err } } } // handleAeaEnvelopeStream deals with incoming envelopes on the AeaEnvelopeStream // envelopes arrive from other peers (full or client) and are processed // by HandleAeaEnvelope func (dhtPeer *DHTPeer) handleAeaEnvelopeStream(stream network.Stream) { common.HandleAeaEnvelopeStream(dhtPeer, stream) } // Callback to handle and route aea envelope comes from the aea envelope stream // return ACNError if message routing failed, otherwise nil. func (dhtPeer *DHTPeer) HandleAeaEnvelope(envel *aea.Envelope) *acn.ACNError { var err error lerror, lwarn, linfo, _ := dhtPeer.GetLoggers() dhtPeer.tcpAddressesLock.RLock() connDelegate, existsDelegate := dhtPeer.tcpAddresses[envel.To] dhtPeer.tcpAddressesLock.RUnlock() if dhtPeer.mailboxServer != nil { if dhtPeer.mailboxServer.RouteEnvelope(envel) { linfo().Str("op", "route").Str("addr", envel.To). Msg("route envelope destinated to mailbox registered agent...") return nil } } if existsDelegate { linfo().Msgf( "Sending envelope to tcp delegate client %s...", connDelegate.RemoteAddr().String(), ) data, err := aea.MakeAcnMessageFromEnvelope(envel) if err != nil { lerror(err).Msgf("while serializing envelope: %s", envel) return &acn.ACNError{ Err: errors.New("serializing envelope error"), ErrorCode: acn.ERROR_DECODE, } } err = utils.WriteBytesConn(connDelegate, data) if err != nil { lerror( err, ).Msgf( "while sending envelope to tcp client %s", connDelegate.RemoteAddr().String(), ) return &acn.ACNError{ Err: errors.New("agent is not ready"), ErrorCode: acn.ERROR_AGENT_NOT_READY, } } err = dhtPeer.AwaitAcnStatus(envel.To) if err != nil { lerror( err, ).Msgf( "while awating acn ack on sending envelope to tcp client %s", connDelegate.RemoteAddr().String(), ) return &acn.ACNError{ Err: errors.New("while awating acn ack on sending envelope to tcp clien"), ErrorCode: acn.ERROR_AGENT_NOT_READY, } } } else if envel.To == dhtPeer.myAgentAddress { if dhtPeer.processEnvelope == nil { lerror(err).Msgf("while processing envelope by agent") return &acn.ACNError{Err: errors.New("agent is not ready"), ErrorCode: acn.ERROR_AGENT_NOT_READY} } linfo().Msg("Processing envelope by local agent...") err = dhtPeer.processEnvelope(envel) ignore(err) } else { lwarn().Msgf("ignored envelope %s", envel.String()) return &acn.ACNError{Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS} } // all good return nil } func (dhtPeer *DHTPeer) handleAeaAddressStream(stream network.Stream) { common.HandleAeaAddressStream(dhtPeer, stream) } func (dhtPeer *DHTPeer) HandleAeaAddressRequest( reqAddress string, ) (*acn.AgentRecord, *acn.ACNError) { lerror, _, linfo, _ := dhtPeer.GetLoggers() // Msg("Received query for addr") var sPeerID string var sRecord *acn.AgentRecord = nil dhtPeer.dhtAddressesLock.RLock() idRelay, existsRelay := dhtPeer.dhtAddresses[reqAddress] dhtPeer.dhtAddressesLock.RUnlock() dhtPeer.tcpAddressesLock.RLock() _, existsDelegate := dhtPeer.tcpAddresses[reqAddress] dhtPeer.tcpAddressesLock.RUnlock() dhtPeer.agentRecordsLock.RLock() localRec := dhtPeer.agentRecords[reqAddress] dhtPeer.agentRecordsLock.RUnlock() if reqAddress == dhtPeer.myAgentAddress { peerID, err := peer.IDFromPublicKey(dhtPeer.publicKey) if err != nil { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msgf("CRITICAL could not get peer ID from public key %s", dhtPeer.publicKey) } else { sPeerID = peerID.Pretty() sRecord = dhtPeer.myAgentRecord } } else if existsRelay { linfo().Str("op", "resolve").Str("addr", reqAddress). Msg("found address in my relay clients map") sPeerID = idRelay sRecord = localRec } else if dhtPeer.mailboxServer != nil && dhtPeer.mailboxServer.IsAddrRegistered(reqAddress) { sPeerID = idRelay sRecord = dhtPeer.mailboxServer.GetAgentRecord(reqAddress) } else if existsDelegate { linfo().Str("op", "resolve").Str("addr", reqAddress). Msgf("found address in my delegate clients map") peerID, err := peer.IDFromPublicKey(dhtPeer.publicKey) if err != nil { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msgf("CRITICAL could not get peer ID from public key %s", dhtPeer.publicKey) } else { sPeerID = peerID.Pretty() sRecord = localRec } } else { // needed when a relay client queries for a peer ID //linfo().Str("op", "resolve").Str("addr", reqAddress). // Msg("did NOT found the address locally, looking for it in the DHT...") peerID, peerRecord, err := dhtPeer.lookupAddressDHT(context.Background(), reqAddress) if err == nil { linfo().Str("op", "resolve").Str("addr", reqAddress). Msg("found address on the DHT") sPeerID = peerID.Pretty() sRecord = peerRecord } else { lerror(err).Str("op", "resolve").Str("addr", reqAddress). Msgf("did NOT find address locally or on the DHT.") return nil, &acn.ACNError{ Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS, } } } if sRecord == nil { return nil, &acn.ACNError{ Err: errors.New("unknown agent address"), ErrorCode: acn.ERROR_UNKNOWN_AGENT_ADDRESS, } } // record found, send linfo().Str("op", "resolve").Str("addr", reqAddress). Msgf("sending agent record (%s) %s", sPeerID, sRecord) return sRecord, nil } func (dhtPeer *DHTPeer) handleAeaNotifStream(stream network.Stream) { lerror, _, _, ldebug := dhtPeer.GetLoggers() ldebug().Str("op", "notif"). Msgf("Got a new notif stream. peerid: %s", stream.Conn().RemotePeer().Pretty()) opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() // workaround: to avoid getting `failed to find any peer in table` // when calling dht.Provide (happens occasionally) ldebug().Msg("waiting for notifying peer to be added to dht routing table...") ctx, cancel := context.WithTimeout( context.Background(), routingTableConnectionUpdateTimeout, ) defer cancel() defer dhtPeer.enableAddressAnnouncementWg.Done() dhtPeer.enableAddressAnnouncementWg.Add(1) for dhtPeer.dht.RoutingTable().Find(stream.Conn().RemotePeer()) == "" { select { case <-ctx.Done(): lerror(nil). Msgf("timeout: notifying peer %s haven't been added to DHT routing table", stream.Conn().RemotePeer().Pretty()) return case <-time.After(time.Millisecond * 5): } } if dhtPeer.myAgentAddress != "" && !dhtPeer.IsAddressAnnounced(dhtPeer.myAgentAddress) { err := dhtPeer.RegisterAgentAddress(dhtPeer.myAgentAddress) if err != nil { lerror(err).Str("op", "notif"). Str("addr", dhtPeer.myAgentAddress). Msgf("while announcing my agent address") return } } if dhtPeer.enableRelay { dhtPeer.dhtAddressesLock.RLock() for addr := range dhtPeer.dhtAddresses { err := dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Str("op", "notif"). Str("addr", addr). Msg("while announcing relay client address") } } dhtPeer.dhtAddressesLock.RUnlock() } if dhtPeer.delegatePort != 0 { dhtPeer.tcpAddressesLock.RLock() for addr := range dhtPeer.tcpAddresses { err := dhtPeer.RegisterAgentAddress(addr) if err != nil { lerror(err).Str("op", "notif"). Str("addr", addr). Msg("while announcing delegate client address") } } dhtPeer.tcpAddressesLock.RUnlock() } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) ldebug().Msgf("Address was announced: peerid: %s", stream.Conn().RemotePeer().Pretty()) // got a connection to a peer, so now we can allow address announcements dhtPeer.enableAddressAnnouncementLock.Lock() dhtPeer.enableAddressAnnouncement = true dhtPeer.enableAddressAnnouncementLock.Unlock() } func (dhtPeer *DHTPeer) IsAddressAnnouncementEnabled() bool { // wait if new peers connection establish in process dhtPeer.enableAddressAnnouncementWg.Wait() dhtPeer.enableAddressAnnouncementLock.Lock() isEnabled := dhtPeer.enableAddressAnnouncement dhtPeer.enableAddressAnnouncementLock.Unlock() return isEnabled } func (dhtPeer *DHTPeer) handleAeaRegisterStream(stream network.Stream) { lerror, _, linfo, _ := dhtPeer.GetLoggers() // to limit spamming time.Sleep(dhtPeer.registrationDelay) nbrClients, _ := dhtPeer.monitor.GetCounter(metricServiceRelayClientsCountAll) opLatencyRegister, _ := dhtPeer.monitor.GetHistogram(metricOpLatencyRegister) timer := dhtPeer.monitor.Timer() start := timer.NewTimer() //linfo().Str("op", "register"). // Msg("Got a new aea register stream") streamPipe := utils.StreamPipe{Stream: stream} // Get Register message register, err := acn.ReadAgentRegistrationMessage(streamPipe) if err != nil { lerror(err).Str("op", "register"). Msg("while reading relay client registration request from stream") err = stream.Reset() ignore(err) return } record := register.Record clientAddr := record.Address //linfo().Msgf("connection from %s established for Address %s", // stream.Conn().RemotePeer().Pretty(), clientAddr) // check if the PoR is valid clientPubKey, err := utils.FetchAIPublicKeyFromPubKey(stream.Conn().RemotePublicKey()) ignore(err) status, err := dhtnode.IsValidProofOfRepresentation(record, record.Address, clientPubKey) if err != nil || status.Code != acn.SUCCESS { lerror(err).Msg("PoR is not valid") acn_send_error := acn.SendAcnError(streamPipe, err.Error(), status.Code) ignore(acn_send_error) return } // TOFIX(LR) post-pone answer until address successfully registered err = acn.SendAcnSuccess(streamPipe) if err != nil { err = stream.Reset() ignore(err) return } stream.Close() nbrClients.Inc() //linfo().Str("op", "register"). // Str("addr", string(clientAddr)). // Msgf("Received address registration request for peer id %s", string(clientPeerID)) clientPeerID := stream.Conn().RemotePeer().Pretty() dhtPeer.agentRecordsLock.Lock() dhtPeer.agentRecords[clientAddr] = record dhtPeer.agentRecordsLock.Unlock() dhtPeer.dhtAddressesLock.Lock() dhtPeer.dhtAddresses[clientAddr] = clientPeerID err = dhtPeer.saveAgentRecordToPersistentStorage(record) if err != nil { lerror(err).Str("op", "register"). Str("addr", clientAddr). Msg("while saving agent record to persistent storage") } dhtPeer.dhtAddressesLock.Unlock() linfo().Str("op", "register"). Str("addr", clientAddr). Msgf("peer added: %s", clientPeerID) if dhtPeer.IsAddressAnnouncementEnabled() && !dhtPeer.IsAddressAnnounced(string(clientAddr)) { linfo().Str("op", "register"). Str("addr", clientAddr). Msgf("Announcing client address on behalf of %s...", clientPeerID) err = dhtPeer.RegisterAgentAddress(string(clientAddr)) if err != nil { //TOFIX(LR) remove agent from map, or don't add it unless announcement done lerror(err).Str("op", "register"). Str("addr", clientAddr). Msg("while announcing client address to the dht") return } } duration := timer.GetTimer(start) opLatencyRegister.Observe(float64(duration.Microseconds())) } func (dhtPeer *DHTPeer) RegisterAgentAddress(addr string) error { _, _, linfo, _ := dhtPeer.GetLoggers() if dhtPeer.IsAddressAnnounced(addr) { // already announced return nil } dhtPeer.setAddressAnnounced(addr) dhtStoreLatency, _ := dhtPeer.monitor.GetHistogram(metricDHTOpLatencyStore) timer := dhtPeer.monitor.Timer() addressCID, err := utils.ComputeCID(addr) if err != nil { return err } // TOFIX(LR) tune timeout ctx, cancel := context.WithTimeout(context.Background(), addressRegisterTimeout) defer cancel() linfo().Str("op", "register"). Str("addr", addr). Msgf("Announcing address to the dht with cid key %s", addressCID.String()) start := timer.NewTimer() err = dhtPeer.dht.Provide(ctx, addressCID, true) if err != context.DeadlineExceeded { duration := timer.GetTimer(start) dhtStoreLatency.Observe(float64(duration.Microseconds())) return err } return nil } func (dhtPeer *DHTPeer) IsAddressAnnounced(address string) bool { dhtPeer.addressAnnouncedMapLock.Lock() _, present := dhtPeer.addressAnnouncedMap[address] dhtPeer.addressAnnouncedMapLock.Unlock() return present } func (dhtPeer *DHTPeer) setAddressAnnounced(address string) { dhtPeer.addressAnnouncedMapLock.Lock() dhtPeer.addressAnnouncedMap[address] = true dhtPeer.addressAnnouncedMapLock.Unlock() } func (dhtPeer *DHTPeer) AddAcnStatusMessage(status *acn.StatusBody, counterpartyID string) { dhtPeer.acnStatusesLock.Lock() queue := dhtPeer.acnStatuses[counterpartyID] dhtPeer.acnStatusesLock.Unlock() queue <- status } func (dhtPeer *DHTPeer) AwaitAcnStatus(counterpartyID string) error { lerror, _, _, _ := dhtPeer.GetLoggers() dhtPeer.acnStatusesLock.Lock() counterParty := dhtPeer.acnStatuses[counterpartyID] dhtPeer.acnStatusesLock.Unlock() status, err := acn.WaitForStatus(counterParty, AcnStatusTimeout) if err != nil { lerror(err).Msg("timeout on status wait") return err } if status.Code != acn.SUCCESS { lerror(err).Msgf("bad status: %d", status.Code) return errors.New("bad status!") } return err } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtpeer/dhtpeer_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/tls" "log" "math/rand" "net" "os" "path" "strconv" "testing" "time" "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/dht/dhtclient" "libp2p_node/dht/dhtnode" "libp2p_node/utils" "github.com/pkg/errors" "google.golang.org/protobuf/proto" ) /* DHTPeer and DHT network routing tests */ const ( DefaultLocalHost = "127.0.0.1" DefaultLocalPort = 2000 DefaultDelegatePort = 3000 EnvelopeDeliveryTimeout = 1 * time.Second DHTPeerSetupTimeout = 5 * time.Second DefaultLedger = dhtnode.DefaultLedger ) var ( FetchAITestKeys = []string{ "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", "92c36941ae78c1b93e5f4bebcf2b40be0af37573aa263ebb70b769ea235b88b6", "b6a8ff857c49b81895f18dd6dbd309e270906b75e2c290a721da48c5de4cba70", "91a90b5be4817c46e06f0e792dd9d9ef3ceb2dbb5ff5c45125153d289d515ce1", "5ee086c5c3df6f641e36e083769d6a03f918b33e4505b1102d2be7a75bb2ae0f", "6768d7918659c1699a379691381c19e55c3c13c49d30086e74a86524123659fb", "d31485403d0cce93b0c48a2fad2acae61a68396e93a602acfcd08dadd7ba12ae", "db533c3e74963a0571e962a4022a4ebce14ab5f240299b5350c83dd18549c1fd", "95aaa63bceeb0946c877c414e1f17119b8a975417924d83db8e281abd71820b2", "9427c1472b66f6abd94a6c246eee495e3709ec45882ae0badcbc71ad2cd8f8b2", } FetchAITestPublicKeys = []string{ "03b7e977f498dce004e2614764ff576e17cc6691135497e7bcb5d3441e816ba9e1", "02344c3f0e79f56aef8e167a6fea912745f1f770b66b4c5096040c0e8c9e3c68b3", "023d6021c001c7b562af8b6e54ace4f98b1b14170d7db4749ecab2b1f0e4252794", "02a0eb20ae23f2f78650b42dfafa6bf4e4752657905da8598b2c0806478e0bfa0d", "023db373d1fc21212f2f03fec1ddd49f193f54f71545e72f37c8a70ca20ef1622b", "03290b4e5dabcca2a994a8d63057f5c83f60d999ede181a8d9b42084e3bee256c2", "03510651fbb9d2ce5b7ae00968339055fbc552e565c54cce8c69f5a52209d3d6a7", "02c11df29b5873e0c37d1427c488ba84e5ccc57405d39299757cee06893ab8595d", "031545edc0fe81a17c77a391a343f95547745b28703bbe664e12c523e3272b637e", "02dd78522785e4175e7db9794b03adcdcfaf707153f307caa3368da5a30594d369", } AgentsTestKeys = []string{ "730c22474709a6d17cf11599a80413a84ddb691a3c7b11a6d8d47a2c024b7b56", "a085c5eeb39636a21c85a9bc667bae18bf3e327a220ecb3998e317b62ab20ec6", "0b7af750e7e96ceb9fe5582bdf9bdafae726427d34447f7245a084b6cf0aa5e5", "dffaa5a9779931a2c1194794e6e9a89787557d6cd708d84c74de20ec5e03a7bf", "509c4019dd96a337a36149031869e6de5db014ab9ae5d8097ac997ca8f10422a", "a385fa48b4f40a2f4ea66de88c0021532299865fe6137d765788f9f856e79453", "ff212371e454f8292fd3b13020a3910fc91002a7ab5eb3f297b71df6b7ff9bc1", "04289e97041fc025c103141909d2cce649944153822f032b646214a850363618", "116294510fba759d19af7a65b915467384258d997695ed7018d8c19d38c29412", "dc2f0238e65c0291bedae58cb1c013bd03e0f41f78e1779744ac401952ec2b51", } AgentsTestAddresses = []string{ "fetch1y39e4tec9fll66x2k7wed5qn7zhaneayjm55kk", "fetch1ufjmhth6dnhrckxrvk05lmt8s2vture23xvwjl", "fetch1dja5uazc9n7jpjm94rhmkkmcyv5nj3kt8aexgf", "fetch18v5lz9psp53akm26ztk3exytqfdvpnfdsyx232", "fetch10u6ra4qmukhf57xadv64jt9jhr9gdrg707x6l9", "fetch1hys3k2anw5mxe0y2vksccpe58jyk5gksrsjd60", "fetch1t07jnjjtlqa07mstg4gw9twjs2ddtqs3sgtx7c", "fetch18sxxgat6uaxqxvd7mgt99y7avyy3c24av36u2l", "fetch1sx2rmtndc5t97pn00x76sksrzgc9s2watpgw64", "fetch1mwd8n27t68svv4w5urztgw7e3kjh7nqkqz0j94", } ) /* DHT Network: DHTPeer-to-DHTPeer */ // TestRoutingDHTPeerToSelf dht peer with agent attached func TestRoutingDHTPeerToSelf(t *testing.T) { opts := []Option{ LocalURI(DefaultLocalHost, DefaultLocalPort), PublicURI(DefaultLocalHost, DefaultLocalPort), IdentityFromFetchAIKey(FetchAITestKeys[0]), EnableRelayService(), EnableDelegateService(DefaultDelegatePort), StoreRecordsTo(path.Join(os.TempDir(), "agents_records_"+randSeq(5))), } agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(AgentsTestKeys[0]) if err != nil { t.Fatal("Failed at DHTPeer initialization:", err) } signature, err := utils.SignFetchAI([]byte(FetchAITestPublicKeys[0]), AgentsTestKeys[0]) if err != nil { t.Fatal("Failed at DHTPeer initialization:", err) } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = AgentsTestAddresses[0] record.PublicKey = agentPubKey record.PeerPublicKey = FetchAITestPublicKeys[0] record.Signature = signature opts = append(opts, RegisterAgentAddress(record, func() bool { return true })) dhtPeer, err := New(opts...) if err != nil { t.Fatal("Failed at DHTPeer initialization:", err) } defer dhtPeer.Close() var rxEnvelopes []*aea.Envelope dhtPeer.ProcessEnvelope(func(envel *aea.Envelope) error { rxEnvelopes = append(rxEnvelopes, envel) return nil }) err = dhtPeer.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[0], }) if err != nil { t.Error("Failed to Route envelope to local Agent") } if len(rxEnvelopes) == 0 { t.Error("Failed to Route & Process envelope to local Agent") } } // TestRoutingDHTPeerToDHTPeerDirect from a dht peer to its bootstrap peer func TestRoutingDHTPeerToDHTPeerDirect(t *testing.T) { dhtPeer1, cleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup1() dhtPeer2, cleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{dhtPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup2() rxPeer1 := make(chan *aea.Envelope, 2) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) rxPeer2 := make(chan *aea.Envelope, 2) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(dhtPeer1, dhtPeer2) err = dhtPeer2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } expectEnvelope(t, rxPeer1) expectEnvelope(t, rxPeer2) } // TestRoutingDHTPeerToDHTPeerIndirect two dht peers connected to the same peer func TestRoutingDHTPeerToDHTPeerIndirect(t *testing.T) { entryPeer, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup() dhtPeer1, cleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup1() dhtPeer2, cleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup2() rxPeer1 := make(chan *aea.Envelope, 2) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) rxPeer2 := make(chan *aea.Envelope, 2) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(dhtPeer1, dhtPeer2) err = dhtPeer2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } expectEnvelope(t, rxPeer1) expectEnvelope(t, rxPeer2) } // TestRoutingDHTPeerToDHTPeerIndirectTwoHops two dht peers connected to different peers func TestRoutingDHTPeerToDHTPeerIndirectTwoHops(t *testing.T) { entryPeer1, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup() entryPeer2, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup() time.Sleep(1 * time.Second) dhtPeer1, cleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{entryPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup1() dhtPeer2, cleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[3], AgentsTestKeys[3], DefaultLocalPort+3, DefaultDelegatePort+3, []string{entryPeer2.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer cleanup2() rxPeer1 := make(chan *aea.Envelope, 2) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) return err }) rxPeer2 := make(chan *aea.Envelope, 2) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(dhtPeer1, dhtPeer2) err = dhtPeer2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to RouteEnvelope from peer 2 to peer 1:", err) } expectEnvelope(t, rxPeer1) expectEnvelope(t, rxPeer2) } // TestRoutingDHTPeerToDHTPeerFullConnectivity fully connected dht peers network func TestRoutingDHTPeerToDHTPeerFullConnectivity(t *testing.T) { peers := []*DHTPeer{} rxs := []chan *aea.Envelope{} for i := range FetchAITestKeys { peer, cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[i], AgentsTestKeys[i], DefaultLocalPort+uint16(i), DefaultDelegatePort+uint16(i), func() []string { multiaddrs := []string{} for _, entryPeer := range peers { multiaddrs = append(multiaddrs, entryPeer.MultiAddr()) } return multiaddrs }(), ) if err != nil { t.Fatal("Failed to initialize DHTPeer", i, ":", err) } rx := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rx <- envel if string(envel.Message) == "ping" { err := peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) peers = append(peers, peer) rxs = append(rxs, rx) defer cleanup() } ensureAddressAnnounced(peers...) for i := range peers { for j := range peers { from := len(peers) - 1 - i target := j // Should be able to route to self though if from == target { continue } err := peers[from].RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[target], Sender: AgentsTestAddresses[from], Message: []byte("ping"), }) if err != nil { t.Error("Failed to RouteEnvelope from ", from, "to", target) } expectEnvelope(t, rxs[target]) expectEnvelope(t, rxs[from]) } } } /* DHT network: DHTClient */ // TestRoutingDHTClientToDHTPeer dht client to its bootstrap peer func TestRoutingDHTClientToDHTPeerX(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client, clientCleanup, err := SetupDHTClient( FetchAITestKeys[1], AgentsTestKeys[1], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup() rxPeer := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient := make(chan *aea.Envelope, 2) client.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient <- envel return nil }) time.Sleep(1 * time.Second) err = client.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTPeer:", err) } expectEnvelope(t, rxPeer) expectEnvelope(t, rxClient) } // TestRoutingDHTClientToDHTPeerIndirect dht client to dht peer different than its bootstrap one func TestRoutingDHTClientToDHTPeerIndirect(t *testing.T) { entryPeer, entryPeerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer entryPeerCleanup() peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() time.Sleep(1 * time.Second) client, clientCleanup, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup() rxPeer := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return peer.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient := make(chan *aea.Envelope, 2) client.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient <- envel return nil }) ensureAddressAnnounced(entryPeer, peer) time.Sleep(1 * time.Second) err = client.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTPeer:", err) } expectEnvelope(t, rxPeer) expectEnvelope(t, rxClient) } // TestRoutingDHTClientToDHTClient dht client to dht client connected to the same peer func TestRoutingDHTClientToDHTClient(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client1, clientCleanup1, err := SetupDHTClient( FetchAITestKeys[1], AgentsTestKeys[1], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup2() rxClient1 := make(chan *aea.Envelope, 2) client1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient1 <- envel return client1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient2 := make(chan *aea.Envelope, 2) client2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient2 <- envel return nil }) time.Sleep(1 * time.Second) err = client2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTClient:", err) } expectEnvelope(t, rxClient1) expectEnvelope(t, rxClient2) } // TestRoutingDHTClientToDHTClientIndirect dht client to dht client connected to a different peer func TestRoutingDHTClientToDHTClientIndirect(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() client1, clientCleanup1, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDHTClient( FetchAITestKeys[3], AgentsTestKeys[3], []string{peer2.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer clientCleanup2() rxClient1 := make(chan *aea.Envelope, 2) client1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient1 <- envel return client1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) rxClient2 := make(chan *aea.Envelope, 2) client2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient2 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) time.Sleep(1 * time.Second) err = client2.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to RouteEnvelope from DHTClient to DHTClient:", err) } expectEnvelope(t, rxClient1) expectEnvelope(t, rxClient2) } /* DHT network: DelegateClient */ // TestRoutingDelegateClientToDHTPeer func TestRoutingDelegateClientToDHTPeerX(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[1], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer := make(chan *aea.Envelope, 2) peer.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer <- envel return nil }) err = client.Send(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } expectEnvelope(t, rxPeer) err = peer.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[0], }) if err != nil { t.Error("Failed to RouteEnvelope from peer to delegate client:", err) } expectEnvelope(t, client.Rx) } // TestRoutingDelegateClientToDHTPeerIndirect func TestRoutingDelegateClientToDHTPeerIndirect(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(3 * time.Second) client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer1 := make(chan *aea.Envelope, 20) peer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) err = client.Send(&aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } expectEnvelope(t, rxPeer1) err = peer1.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[0], }) if err != nil { t.Error("Failed to RouteEnvelope from peer to delegate client:", err) } expectEnvelope(t, client.Rx) } // TestMessageOrderingWithDelegateClient func TestMessageOrderingWithDelegateClient(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(1 * time.Second) client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer1 := make(chan *aea.Envelope, 20) peer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel return nil }) rxPeer2 := make(chan *aea.Envelope, 20) peer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer2 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) max := 10 i := 0 for x := 0; x < max; x++ { envelope := &aea.Envelope{ To: AgentsTestAddresses[0], Sender: AgentsTestAddresses[2], Message: []byte(strconv.Itoa(i)), } err = client.Send(envelope) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope) // time.Sleep(100 * time.Millisecond) envelope1 := &aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], Message: []byte(strconv.Itoa(i)), } err = client.Send(envelope1) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope1) // time.Sleep(100 * time.Millisecond) } // go func() { ii := 0 for x := 0; x < max; x++ { expectEnvelopeOrdered(t, rxPeer1, ii) ii++ ii++ } // }() // go func() { iii := 0 for x := 0; x < max; x++ { iii++ expectEnvelopeOrdered(t, rxPeer2, iii) iii++ } // }() print("STOP OF THE TEST") } // TestMessageOrderingWithDelegateClientTwoHops func TestMessageOrderingWithDelegateClientTwoHops(t *testing.T) { peer1Index := 0 peer2Index := 1 client1Index := 2 client2Index := 3 peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[peer1Index], AgentsTestKeys[peer1Index], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[peer2Index], AgentsTestKeys[peer2Index], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(1 * time.Second) client1, clientCleanup1, err := SetupDelegateClient( AgentsTestKeys[client1Index], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[peer1Index], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDelegateClient( AgentsTestKeys[client2Index], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[peer2Index], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup2() rxClient1 := make(chan *aea.Envelope, 20) client1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient1 <- envel return nil }) rxClient2 := make(chan *aea.Envelope, 20) client2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClient2 <- envel return nil }) ensureAddressAnnounced(peer1, peer2) max := 100 i := 0 for x := 0; x < max; x++ { envelope := &aea.Envelope{ To: AgentsTestAddresses[client2Index], Sender: AgentsTestAddresses[client1Index], Message: []byte(strconv.Itoa(i)), } err = client1.Send(envelope) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope) // time.Sleep(100 * time.Millisecond) envelope1 := &aea.Envelope{ To: AgentsTestAddresses[client1Index], Sender: AgentsTestAddresses[client2Index], Message: []byte(strconv.Itoa(i)), } err = client2.Send(envelope1) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } i++ t.Log("Sending Envelope : ", envelope1) // time.Sleep(100 * time.Millisecond) } // go func() { ii := 0 for x := 0; x < max; x++ { expectEnvelopeOrdered(t, rxClient2, ii) ii++ ii++ } // }() // go func() { iii := 0 for x := 0; x < max; x++ { iii++ expectEnvelopeOrdered(t, rxClient1, iii) iii++ } // }() print("STOP OF THE TEST") } // TestRoutingDelegateClientToDHTPeerIndirectTwoHops func TestRoutingDelegateClientToDHTPeerIndirectTwoHops(t *testing.T) { entryPeer, entryPeerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer entryPeerCleanup() peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{entryPeer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() time.Sleep(1 * time.Second) client, clientCleanup, err := SetupDelegateClient( AgentsTestKeys[3], DefaultLocalHost, DefaultDelegatePort+2, FetchAITestPublicKeys[2], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup() rxPeer1 := make(chan *aea.Envelope, 2) peer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeer1 <- envel return nil }) ensureAddressAnnounced(entryPeer, peer1, peer2) err = client.Send(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTPeer:", err) } expectEnvelope(t, rxPeer1) err = peer1.RouteEnvelope(&aea.Envelope{ To: AgentsTestAddresses[3], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to RouteEnvelope from peer to delegate client:", err) } expectEnvelope(t, client.Rx) } // TestRoutingDelegateClientToDelegateClient func TestRoutingDelegateClientToDelegateClient(t *testing.T) { _, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() client1, clientCleanup1, err := SetupDelegateClient( AgentsTestKeys[1], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup2() time.Sleep(1 * time.Second) err = client1.Send(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[1], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client2.Rx) err = client2.Send(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client1.Rx) } // TestRoutingDelegateClientToDelegateClientIndirect func TestRoutingDelegateClientToDelegateClientIndirect(t *testing.T) { peer1, peer1Cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peer1Cleanup() peer2, peer2Cleanup, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peer2Cleanup() client1, clientCleanup1, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup1() client2, clientCleanup2, err := SetupDelegateClient( AgentsTestKeys[3], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer clientCleanup2() ensureAddressAnnounced(peer1, peer2) err = client1.Send(&aea.Envelope{ To: AgentsTestAddresses[3], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client2.Rx) err = client2.Send(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DelegateClient:", err) } expectEnvelope(t, client1.Rx) } // TestRoutingDelegateClientToDHTClientDirect func TestRoutingDelegateClientToDHTClientDirect(t *testing.T) { peer, peerCleanup, err := SetupLocalDHTPeer( FetchAITestKeys[0], "", DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup() dhtClient, dhtClientCleanup, err := SetupDHTClient( FetchAITestKeys[1], AgentsTestKeys[1], []string{peer.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup() delegateClient, delegateClientCleanup, err := SetupDelegateClient( AgentsTestKeys[2], DefaultLocalHost, DefaultDelegatePort, FetchAITestPublicKeys[0], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup() rxClientDHT := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT <- envel return dhtClient.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) time.Sleep(1 * time.Second) err = delegateClient.Send(&aea.Envelope{ To: AgentsTestAddresses[1], Sender: AgentsTestAddresses[2], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTClient:", err) } expectEnvelope(t, rxClientDHT) expectEnvelope(t, delegateClient.Rx) } // TestRoutingDelegateClientToDHTClientIndirect func TestRoutingDelegateClientToDHTClientIndirect(t *testing.T) { peer1, peerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup1() peer2, peerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer peerCleanup2() dhtClient, dhtClientCleanup, err := SetupDHTClient( FetchAITestKeys[2], AgentsTestKeys[2], []string{peer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup() delegateClient, delegateClientCleanup, err := SetupDelegateClient( AgentsTestKeys[3], DefaultLocalHost, DefaultDelegatePort+1, FetchAITestPublicKeys[1], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup() rxClientDHT := make(chan *aea.Envelope, 2) dhtClient.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT <- envel return dhtClient.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, }) }) ensureAddressAnnounced(peer1, peer2) time.Sleep(3 * time.Second) err = delegateClient.Send(&aea.Envelope{ To: AgentsTestAddresses[2], Sender: AgentsTestAddresses[3], }) if err != nil { t.Error("Failed to Send envelope from DelegateClient to DHTClient:", err) } expectEnvelope(t, rxClientDHT) expectEnvelope(t, delegateClient.Rx) } /* DHT network: all-to-all */ /* Network topology DHTClient ------- -- DelegateClient | | DHTClient ------- -- DelegateClient | | |-- DHTPeer --- DHTPeeer -- DHTPeer --- DHTPeer --| | | DelegateClient -- ------- DHTClient */ // TestRoutingAlltoAll func TestRoutingAllToAll(t *testing.T) { rxs := []chan *aea.Envelope{} send := []func(*aea.Envelope) error{} // setup DHTPeers dhtPeer1, dhtPeerCleanup1, err := SetupLocalDHTPeer( FetchAITestKeys[0], AgentsTestKeys[0], DefaultLocalPort, DefaultDelegatePort, []string{}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup1() rxPeerDHT1 := make(chan *aea.Envelope, 100) dhtPeer1.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT1 <- envel if string(envel.Message) == "ping" { err := dhtPeer1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT1) send = append(send, func(envel *aea.Envelope) error { return dhtPeer1.RouteEnvelope(envel) }) dhtPeer2, dhtPeerCleanup2, err := SetupLocalDHTPeer( FetchAITestKeys[1], AgentsTestKeys[1], DefaultLocalPort+1, DefaultDelegatePort+1, []string{dhtPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup2() rxPeerDHT2 := make(chan *aea.Envelope, 100) dhtPeer2.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT2 <- envel if string(envel.Message) == "ping" { err := dhtPeer2.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT2) send = append(send, func(envel *aea.Envelope) error { return dhtPeer2.RouteEnvelope(envel) }) dhtPeer3, dhtPeerCleanup3, err := SetupLocalDHTPeer( FetchAITestKeys[2], AgentsTestKeys[2], DefaultLocalPort+2, DefaultDelegatePort+2, []string{dhtPeer1.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup3() rxPeerDHT3 := make(chan *aea.Envelope, 100) dhtPeer3.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT3 <- envel if string(envel.Message) == "ping" { err := dhtPeer3.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT3) send = append(send, func(envel *aea.Envelope) error { return dhtPeer3.RouteEnvelope(envel) }) dhtPeer4, dhtPeerCleanup4, err := SetupLocalDHTPeer( FetchAITestKeys[3], AgentsTestKeys[3], DefaultLocalPort+3, DefaultDelegatePort+3, []string{dhtPeer2.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTPeer:", err) } defer dhtPeerCleanup4() rxPeerDHT4 := make(chan *aea.Envelope, 100) dhtPeer4.ProcessEnvelope(func(envel *aea.Envelope) error { rxPeerDHT4 <- envel if string(envel.Message) == "ping" { err := dhtPeer4.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxPeerDHT4) send = append(send, func(envel *aea.Envelope) error { return dhtPeer4.RouteEnvelope(envel) }) // setup DHTClients dhtClient1, dhtClientCleanup1, err := SetupDHTClient( FetchAITestKeys[4], AgentsTestKeys[4], []string{dhtPeer3.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup1() rxClientDHT1 := make(chan *aea.Envelope, 100) dhtClient1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT1 <- envel if string(envel.Message) == "ping" { err := dhtClient1.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDHT1) send = append(send, func(envel *aea.Envelope) error { return dhtClient1.RouteEnvelope(envel) }) dhtClient2, dhtClientCleanup2, err := SetupDHTClient( FetchAITestKeys[5], AgentsTestKeys[5], []string{dhtPeer3.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup2() rxClientDHT2 := make(chan *aea.Envelope, 100) dhtClient2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT2 <- envel if string(envel.Message) == "ping" { err := dhtClient2.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDHT2) send = append(send, func(envel *aea.Envelope) error { return dhtClient2.RouteEnvelope(envel) }) dhtClient3, dhtClientCleanup3, err := SetupDHTClient( FetchAITestKeys[6], AgentsTestKeys[6], []string{dhtPeer4.MultiAddr()}, ) if err != nil { t.Fatal("Failed to initialize DHTClient:", err) } defer dhtClientCleanup3() rxClientDHT3 := make(chan *aea.Envelope, 100) dhtClient3.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDHT3 <- envel if string(envel.Message) == "ping" { err := dhtClient3.RouteEnvelope(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDHT3) send = append(send, func(envel *aea.Envelope) error { return dhtClient3.RouteEnvelope(envel) }) // setup DelegateClients delegateClient1, delegateClientCleanup1, err := SetupDelegateClient( AgentsTestKeys[7], DefaultLocalHost, DefaultDelegatePort+2, FetchAITestPublicKeys[2], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup1() rxClientDelegate1 := make(chan *aea.Envelope, 100) delegateClient1.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDelegate1 <- envel if string(envel.Message) == "ping" { err := delegateClient1.Send(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDelegate1) send = append(send, func(envel *aea.Envelope) error { return delegateClient1.Send(envel) }) delegateClient2, delegateClientCleanup2, err := SetupDelegateClient( AgentsTestKeys[8], DefaultLocalHost, DefaultDelegatePort+3, FetchAITestPublicKeys[3], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup2() rxClientDelegate2 := make(chan *aea.Envelope, 100) delegateClient2.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDelegate2 <- envel if string(envel.Message) == "ping" { err := delegateClient2.Send(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDelegate2) send = append(send, func(envel *aea.Envelope) error { return delegateClient2.Send(envel) }) delegateClient3, delegateClientCleanup3, err := SetupDelegateClient( AgentsTestKeys[9], DefaultLocalHost, DefaultDelegatePort+3, FetchAITestPublicKeys[3], ) if err != nil { t.Fatal("Failed to initialize DelegateClient:", err) } defer delegateClientCleanup3() rxClientDelegate3 := make(chan *aea.Envelope, 100) delegateClient3.ProcessEnvelope(func(envel *aea.Envelope) error { rxClientDelegate3 <- envel if string(envel.Message) == "ping" { err := delegateClient3.Send(&aea.Envelope{ To: envel.Sender, Sender: envel.To, Message: []byte("ack"), }) return err } return nil }) rxs = append(rxs, rxClientDelegate3) send = append(send, func(envel *aea.Envelope) error { return delegateClient3.Send(envel) }) // Send envelope from everyone to everyone else and expect an echo back ensureAddressAnnounced(dhtPeer1, dhtPeer2, dhtPeer3, dhtPeer4) for i := range AgentsTestAddresses { for j := range AgentsTestAddresses { from := len(AgentsTestAddresses) - 1 - i target := j // Should be able to route to self though if from == target { continue } err := send[from](&aea.Envelope{ To: AgentsTestAddresses[target], Sender: AgentsTestAddresses[from], Message: []byte("ping"), }) if err != nil { t.Error("Failed to RouteEnvelope from ", from, "to", target) } } } for i := range AgentsTestAddresses { for j := range AgentsTestAddresses { from := len(AgentsTestAddresses) - 1 - i target := j if from == target { continue } expectEnvelope(t, rxs[target]) expectEnvelope(t, rxs[from]) } } } /* Helpers TOFIX(LR) how to share test helpers between packages tests without having circular dependencies */ func randSeq(n int) string { rand.Seed(time.Now().UnixNano()) var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func SetupLocalDHTPeer( key string, agentKey string, dhtPort uint16, delegatePort uint16, entry []string, ) (*DHTPeer, func(), error) { opts := []Option{ LocalURI(DefaultLocalHost, dhtPort), PublicURI(DefaultLocalHost, dhtPort), IdentityFromFetchAIKey(key), EnableRelayService(), BootstrapFrom(entry), StoreRecordsTo(path.Join(os.TempDir(), "agents_records_"+randSeq(5))), } if agentKey != "" { agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(agentKey) if err != nil { return nil, nil, err } agentAddress, err := utils.FetchAIAddressFromPublicKey(agentPubKey) if err != nil { return nil, nil, err } peerPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } signature, err := utils.SignFetchAI([]byte(peerPubKey), agentKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = agentAddress record.PublicKey = agentPubKey record.PeerPublicKey = peerPubKey record.Signature = signature opts = append(opts, RegisterAgentAddress(record, func() bool { return true })) } if delegatePort != 0 { opts = append(opts, EnableDelegateService(delegatePort)) } dhtPeer, err := New(opts...) if err != nil { return nil, nil, err } return dhtPeer, func() { println("dhtpeer going to be closed!"); dhtPeer.Close() }, nil } // DHTClient func SetupDHTClient( key string, agentKey string, entry []string, ) (*dhtclient.DHTClient, func(), error) { agentPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(agentKey) if err != nil { return nil, nil, err } agentAddress, err := utils.FetchAIAddressFromPublicKey(agentPubKey) if err != nil { return nil, nil, err } peerPubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } signature, err := utils.SignFetchAI([]byte(peerPubKey), agentKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = agentAddress record.PublicKey = agentPubKey record.PeerPublicKey = peerPubKey record.Signature = signature opts := []dhtclient.Option{ dhtclient.IdentityFromFetchAIKey(key), dhtclient.RegisterAgentAddress(record, func() bool { return true }), dhtclient.BootstrapFrom(entry), } dhtClient, err := dhtclient.New(opts...) if err != nil { println("dhtclient.New:", err.Error()) return nil, nil, err } return dhtClient, func() { println("dhtclient going to be closed!"); dhtClient.Close() }, nil } // Delegate tcp client for tests only type DelegateClient struct { AgentAddress string AgentKey string AgentPubKey string PeerPubKey string PoR string Rx chan *aea.Envelope Conn net.Conn processEnvelope func(*aea.Envelope) error acn_status_chan chan *acn.StatusBody } func (client *DelegateClient) AddAcnStatusMessage(status *acn.StatusBody, counterpartyID string) { //client.acn_status_chan <- status } func (client *DelegateClient) Close() error { return client.Conn.Close() } func (client *DelegateClient) Send(envelope *aea.Envelope) error { data, err := aea.MakeAcnMessageFromEnvelope(envelope) if err != nil { println("while serializing envelope:", err.Error()) return err } err = utils.WriteBytesConn(client.Conn, data) if err != nil { println("while sending envelope:", err.Error()) return err } return err } func (client *DelegateClient) ProcessEnvelope(fn func(*aea.Envelope) error) { client.processEnvelope = fn } func ValidateTLSSignature( tlsSignature []byte, sessionPubKey *ecdsa.PublicKey, peerPubKey string, ) error { sessionPubKeyBytes := elliptic.Marshal(sessionPubKey.Curve, sessionPubKey.X, sessionPubKey.Y) verifyKey, err := utils.PubKeyFromFetchAIPublicKey(peerPubKey) if err != nil { return err } ok, err := verifyKey.Verify(sessionPubKeyBytes, tlsSignature) if err != nil { return err } if !ok { return errors.New("error on signature validation for tls start") } return nil } func SetupDelegateClient( key string, host string, port uint16, peerPubKey string, ) (*DelegateClient, func(), error) { var err error client := &DelegateClient{} client.AgentKey = key client.acn_status_chan = make(chan *acn.StatusBody, 10000) pubKey, err := utils.FetchAIPublicKeyFromFetchAIPrivateKey(key) if err != nil { return nil, nil, err } client.AgentPubKey = pubKey address, err := utils.FetchAIAddressFromPublicKey(pubKey) if err != nil { return nil, nil, err } client.AgentAddress = address signature, err := utils.SignFetchAI([]byte(peerPubKey), key) if err != nil { return nil, nil, err } client.PoR = signature client.Rx = make(chan *aea.Envelope, 2) conf := &tls.Config{ InsecureSkipVerify: true, } client.Conn, err = tls.Dial("tcp", host+":"+strconv.FormatInt(int64(port), 10), conf) if err != nil { return nil, nil, err } certPubKey := client.Conn.(*tls.Conn).ConnectionState().PeerCertificates[0].PublicKey.(*ecdsa.PublicKey) tlsSignature, _ := utils.ReadBytesConn(client.Conn) err = ValidateTLSSignature(tlsSignature, certPubKey, peerPubKey) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: DefaultLedger} record.Address = address record.PublicKey = pubKey record.PeerPublicKey = peerPubKey record.Signature = signature registration := &acn.RegisterPerformative{Record: record} msg := &acn.AcnMessage{ Performative: &acn.Register{Register: registration}, } data, err := proto.Marshal(msg) ignore(err) err = utils.WriteBytesConn(client.Conn, data) ignore(err) data, err = utils.ReadBytesConn(client.Conn) if err != nil { return nil, nil, err } response := &acn.AcnMessage{} err = proto.Unmarshal(data, response) if err != nil { return nil, nil, err } // Get Status message var status *acn.StatusPerformative switch pl := response.Performative.(type) { case *acn.Status: status = pl.Status default: return nil, nil, err } if status.Body.Code != acn.SUCCESS { println("Registration error:", status.Body.String()) return nil, nil, errors.New(status.Body.String()) } pipe := utils.ConnPipe{Conn: client.Conn} go func() { for { envel, err := aea.HandleAcnMessageFromPipe(pipe, client, "") if err != nil { break } if envel == nil { continue } _ = acn.SendAcnSuccess(pipe) if client.processEnvelope != nil { err = client.processEnvelope(envel) ignore(err) } else { client.Rx <- envel } } }() return client, func() { client.Close() }, nil } func expectEnvelope(t *testing.T, rx chan *aea.Envelope) { timeout := time.After(EnvelopeDeliveryTimeout) select { case envel := <-rx: t.Log("Received envelope", envel) case <-timeout: t.Error("Failed to receive envelope before timeout") } } func expectEnvelopeOrdered(t *testing.T, rx chan *aea.Envelope, counter int) { timeout := time.After(EnvelopeDeliveryTimeout) select { case envel := <-rx: t.Log("Received envelope", envel) if envel == nil { t.Log("Empty envelope. exit") return } message, _ := strconv.Atoi(string(envel.Message)) if message != counter { log.Fatalf("Expected counter %d received counter %d", counter, message) } case <-timeout: t.Error("Failed to receive envelope before timeout") } } func ensureAddressAnnounced(peers ...*DHTPeer) { for _, peer := range peers { ctx, cancel := context.WithTimeout(context.Background(), DHTPeerSetupTimeout) defer cancel() L: for !peer.IsAddressAnnounced(peer.myAgentAddress) { select { case <-ctx.Done(): break L case <-time.After(5 * time.Millisecond): } } } } func TestFetchAICrypto(t *testing.T) { publicKey := "02358e3e42a6ba15cf6b2ba6eb05f02b8893acf82b316d7dd9cda702b0892b8c71" address := "fetch19dq2mkcpp6x0aypxt9c9gz6n4fqvax0x9a7t5r" peerPublicKey := "027af21aff853b9d9589867ea142b0a60a9611fc8e1fae04c2f7144113fa4e938e" pySigStrCanonize := "N/GOa7/m3HU8/gpLJ88VCQ6vXsdrfiiYcqnNtF+c2N9VG9ZIiycykN4hdbpbOCGrChMYZQA3G1GpozsShrUBgg==" addressFromPublicKey, _ := utils.FetchAIAddressFromPublicKey(publicKey) if address != addressFromPublicKey { t.Error("[ERR] Addresses don't match") } else { t.Log("[OK] Agent address matches its public key") } valid, err := utils.VerifyFetchAISignatureBTC( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using BTC don't match %s", err.Error()) } valid, err = utils.VerifyFetchAISignatureLibp2p( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using LPP don't match %s", err.Error()) } } func TestEthereumCrypto(t *testing.T) { //privateKey := "0xb60fe8027fb82f1a1bd6b8e66d4400f858989a2c67428a4e7f589441700339b0" publicKey := "0xf753e5a9e2368e97f4db869a0d956d3ffb64672d6392670572906c786b5712ada13b6bff882951b3ba3dd65bdacc915c2b532efc3f183aa44657205c6c337225" address := "0xb8d8c62d4a1999b7aea0aebBD5020244a4a9bAD8" publicKeySignature := "0x304c2ba4ae7fa71295bfc2920b9c1268d574d65531f1f4d2117fc1439a45310c37ab75085a9df2a4169a4d47982b330a4387b1ded0c8881b030629db30bbaf3a1c" addFromPublicKey, err := utils.EthereumAddressFromPublicKey(publicKey) if err != nil || addFromPublicKey != address { t.Error( "Error when computing address from public key or address and public key don't match", ) } _, err = utils.BTCPubKeyFromEthereumPublicKey(publicKey) if err != nil { t.Errorf("While building BTC public key from string: %s", err.Error()) } /* ethSig, err := secp256k1.Sign(hashedPublicKey, hexutil.MustDecode(privateKey)) if err != nil { t.Error(err.Error()) } println(hexutil.Encode(ethSig)) hash := sha3.NewLegacyKeccak256() _, err = hash.Write([]byte(publicKey)) if err != nil { t.Error(err.Error()) } sha3KeccakHash := hash.Sum(nil) */ valid, err := utils.VerifyEthereumSignatureETH([]byte(publicKey), publicKeySignature, publicKey) if err != nil { t.Error(err.Error()) } if !valid { t.Errorf("Signer address don't match %s", addFromPublicKey) } } // Perform tests for tls signature generation and checking func TestTLSSignatureValidation(t *testing.T) { key1, pubKey, _ := utils.KeyPairFromFetchAIKey(FetchAITestKeys[0]) key2, _, _ := utils.KeyPairFromFetchAIKey(FetchAITestKeys[1]) peerPubKey, _ := utils.FetchAIPublicKeyFromPubKey(pubKey) cert, err := generate_x509_cert() sessionPubKey := cert.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey) if err != nil { t.Fatal("Failed to generate certificate") } // valid tlsSignature, err := makeSessionKeySignature(cert, key1) if err != nil { t.Fatal("Failed to make signature") } err = ValidateTLSSignature(tlsSignature, sessionPubKey, peerPubKey) if err != nil { t.Fatal("Signature is invalid, but expected to be ok") } //invalid tlsSignature, err = makeSessionKeySignature(cert, key2) if err != nil { t.Fatal("Failed to make signature") } err = ValidateTLSSignature(tlsSignature, sessionPubKey, peerPubKey) if err == nil { t.Fatal("Signature is valid, but shoud not") } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtpeer/mailbox.go ================================================ package dhtpeer import ( "context" "crypto/tls" "io/ioutil" acn "libp2p_node/acn" aea "libp2p_node/aea" "net/http" "strings" "sync" "time" "github.com/google/uuid" "google.golang.org/protobuf/proto" ) func (mailboxServer *MailboxServer) apiRegister(res http.ResponseWriter, req *http.Request) { var data []byte var body []byte var err error if req.Method != "POST" { data = []byte("invalid method") res.WriteHeader(400) _, err = res.Write(data) ignore(err) return } body, err = ioutil.ReadAll(req.Body) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) return } //get por if len(body) == 0 { res.WriteHeader(400) _, err = res.Write([]byte("Empty body")) ignore(err) return } record := &acn.AgentRecord{} err = proto.Unmarshal(body, record) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte("Error on agent record deserialize")) ignore(err) return } addr := record.Address // TODO: double register fix!!! //check por status, err := mailboxServer.dhtPeer.CheckPOR(record) if err != nil || status.Code != acn.SUCCESS { res.WriteHeader(400) _, err = res.Write([]byte("Invalid PoR")) ignore(err) return } uuid := strings.ReplaceAll(uuid.NewString(), "-", "") if mailboxServer.dhtPeer.IsAddressAnnouncementEnabled() { err = mailboxServer.dhtPeer.RegisterAgentAddress(addr) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte("failed to register address over dht")) ignore(err) return } } mailboxServer.lock.Lock() mailboxServer.agentRecords[addr] = record mailboxServer.sessions[uuid] = addr mailboxServer.envelopes[addr] = make([]*aea.Envelope, 0) mailboxServer.lock.Unlock() res.WriteHeader(200) _, err = res.Write([]byte(uuid)) ignore(err) } func (mailboxServer *MailboxServer) apiUnregister(res http.ResponseWriter, req *http.Request) { var data []byte var err error var addr string var sessionId string if req.Method != "GET" { data = []byte("invalid method") res.WriteHeader(400) _, err = res.Write(data) ignore(err) return } session_header, exists := req.Header["Session-Id"] if exists { sessionId = session_header[0] mailboxServer.lock.Lock() addr, exists = mailboxServer.sessions[sessionId] mailboxServer.lock.Unlock() } if !exists { res.WriteHeader(400) _, err = res.Write([]byte("invalid session_id header")) ignore(err) return } mailboxServer.lock.Lock() delete(mailboxServer.agentRecords, addr) delete(mailboxServer.sessions, sessionId) delete(mailboxServer.envelopes, addr) mailboxServer.lock.Unlock() } func (mailboxServer *MailboxServer) apiGetSignature(res http.ResponseWriter, req *http.Request) { var err error if req.Method != "GET" { res.WriteHeader(400) _, err = res.Write([]byte("invalid method")) ignore(err) return } res.WriteHeader(200) _, err = res.Write(mailboxServer.signature) ignore(err) } func (mailboxServer *MailboxServer) apiSendEnvelope(res http.ResponseWriter, req *http.Request) { var err error if req.Method != "POST" { res.WriteHeader(400) _, err = res.Write([]byte("invalid method")) ignore(err) return } session_header, exists := req.Header["Session-Id"] if exists { sessionId := session_header[0] mailboxServer.lock.Lock() _, exists = mailboxServer.sessions[sessionId] mailboxServer.lock.Unlock() } if !exists { res.WriteHeader(400) _, err = res.Write([]byte("invalid session_id header")) ignore(err) return } body, err := ioutil.ReadAll(req.Body) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) return } //getenvelope if len(body) == 0 { res.WriteHeader(400) _, err = res.Write([]byte("Empty body")) ignore(err) return } envelope := &aea.Envelope{} err = proto.Unmarshal(body, envelope) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) return } err = mailboxServer.dhtPeer.RouteEnvelope(envelope) if err != nil { res.WriteHeader(400) _, err = res.Write([]byte(err.Error())) ignore(err) } } func (mailboxServer *MailboxServer) apiGetEnvelope(res http.ResponseWriter, req *http.Request) { var buf []byte var envelopesList []*aea.Envelope var err error var addr string if req.Method != "GET" { res.WriteHeader(400) _, err = res.Write([]byte("invalid method")) ignore(err) return } session_header, exists := req.Header["Session-Id"] mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() if exists { sessionId := session_header[0] addr, exists = mailboxServer.sessions[sessionId] } if !exists { res.WriteHeader(400) _, err = res.Write([]byte("invalid session_id header")) ignore(err) return } envelopesList = mailboxServer.envelopes[addr] if len(envelopesList) == 0 { res.WriteHeader(200) return } envelope := envelopesList[0] buf, err = proto.Marshal(envelope) if err != nil { //log error return } res.WriteHeader(200) _, err = res.Write(buf) if err == nil { // all ok, remove the first envelope from slice mailboxServer.envelopes[addr] = mailboxServer.envelopes[addr][1:] } } type MailboxServer struct { addr string dhtPeer *DHTPeer httpServer *http.Server sessions map[string]string agentRecords map[string]*acn.AgentRecord envelopes map[string]([]*aea.Envelope) lock sync.RWMutex envelopesLimit int cert *tls.Certificate signature []byte } func (mailboxServer *MailboxServer) start() { var err error lerror, _, _, _ := mailboxServer.dhtPeer.GetLoggers() mailboxServer.envelopes = map[string][]*aea.Envelope{} mailboxServer.agentRecords = map[string]*acn.AgentRecord{} mailboxServer.sessions = map[string]string{} mailboxServer.lock = sync.RWMutex{} mailboxServer.envelopesLimit = 1000 mailboxServer.cert, mailboxServer.signature = mailboxServer.dhtPeer.GetCertAndSignature() mux := http.NewServeMux() mux.HandleFunc("/register", mailboxServer.apiRegister) mux.HandleFunc("/unregister", mailboxServer.apiUnregister) mux.HandleFunc("/get_envelope", mailboxServer.apiGetEnvelope) mux.HandleFunc("/send_envelope", mailboxServer.apiSendEnvelope) mux.HandleFunc("/ssl_signature", mailboxServer.apiGetSignature) tlsConfig := &tls.Config{Certificates: []tls.Certificate{*mailboxServer.cert}} mailboxServer.httpServer = &http.Server{ Addr: mailboxServer.addr, Handler: mux, TLSConfig: tlsConfig, } listener, err := tls.Listen("tcp", mailboxServer.addr, tlsConfig) if err != nil { lerror(err).Msgf("while setting mailbox tls") } err = mailboxServer.httpServer.Serve(listener) if err != nil { lerror(err).Msgf("while running mailbox http server") } } func (mailboxServer *MailboxServer) stop() { var err error lerror, _, _, _ := mailboxServer.dhtPeer.GetLoggers() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err = mailboxServer.httpServer.Shutdown(ctx) if err != nil { lerror(err).Msg("Error on mailbox http server shutdown") } } func (mailboxServer *MailboxServer) RouteEnvelope(envelope *aea.Envelope) bool { target := envelope.To _, _, linfo, _ := mailboxServer.dhtPeer.GetLoggers() linfo().Msgf("route to %s", target) mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() envelopesList, listExist := mailboxServer.envelopes[target] if !listExist { linfo().Msgf("route to %s. no target", target) return false } // check chan is full if mailboxServer.envelopesLimit != 0 && len(mailboxServer.envelopes[target]) >= mailboxServer.envelopesLimit { linfo().Msgf("Envelopes queue for %s is full. (%d envelopes)", target, mailboxServer.envelopesLimit) return false } mailboxServer.envelopes[target] = append(envelopesList, envelope) linfo().Msgf("route to %s. added to queue!", target) return true } func (mailboxServer *MailboxServer) IsAddrRegistered(addr string) bool { mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() _, listExist := mailboxServer.envelopes[addr] return listExist } func (mailboxServer *MailboxServer) GetAgentRecord(addr string) *acn.AgentRecord { if !mailboxServer.IsAddrRegistered(addr) { return nil } mailboxServer.lock.Lock() defer mailboxServer.lock.Unlock() return mailboxServer.agentRecords[addr] } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtpeer/notifee.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2022 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhtpeer provides an implementation of an Agent Communication Network node // using libp2p. It participates in data storage and routing for the network. // It offers RelayService for dhtclient and DelegateService for tcp clients. package dhtpeer import ( "github.com/libp2p/go-libp2p-core/network" "github.com/multiformats/go-multiaddr" "github.com/rs/zerolog" ) // Notifee Handle DHTClient network events type Notifee struct { logger zerolog.Logger } // Listen called when network starts listening on an addr func (notifee *Notifee) Listen(network.Network, multiaddr.Multiaddr) {} // ListenClose called when network stops listening on an addr func (notifee *Notifee) ListenClose(network.Network, multiaddr.Multiaddr) {} // Connected called when a connection opened func (notifee *Notifee) Connected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Connected to peer %s", conn.RemotePeer().Pretty(), ) } // Disconnected called when a connection closed // Reconnects if connection is to relay peer and not currenctly closing connection. func (notifee *Notifee) Disconnected(net network.Network, conn network.Conn) { notifee.logger.Info().Msgf( "Disconnected from peer %s", conn.RemotePeer().Pretty(), ) } // OpenedStream called when a stream opened func (notifee *Notifee) OpenedStream(network.Network, network.Stream) {} // ClosedStream called when a stream closed func (notifee *Notifee) ClosedStream(network.Network, network.Stream) {} ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtpeer/options.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( "fmt" "time" "github.com/multiformats/go-multiaddr" "github.com/rs/zerolog" acn "libp2p_node/acn" utils "libp2p_node/utils" ) // Option for dhtpeer.New type Option func(*DHTPeer) error // IdentityFromFetchAIKey for dhtpeer.New func IdentityFromFetchAIKey(key string) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.key, dhtPeer.publicKey, err = utils.KeyPairFromFetchAIKey(key) if err != nil { return err } return nil } } // RegisterAgentAddress for dhtpeer.New func RegisterAgentAddress(record *acn.AgentRecord, isReady func() bool) Option { return func(dhtPeer *DHTPeer) error { pbRecord := &acn.AgentRecord{} pbRecord.Address = record.Address pbRecord.PublicKey = record.PublicKey pbRecord.PeerPublicKey = record.PeerPublicKey pbRecord.Signature = record.Signature pbRecord.ServiceId = record.ServiceId pbRecord.LedgerId = record.LedgerId dhtPeer.myAgentAddress = record.Address dhtPeer.myAgentRecord = pbRecord dhtPeer.myAgentReady = isReady return nil } } // BootstrapFrom for dhtpeer.New func BootstrapFrom(entryPeers []string) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.bootstrapPeers, err = utils.GetPeersAddrInfo(entryPeers) if err != nil { return err } return nil } } // LocalURI for dhtpeer.New func LocalURI(host string, port uint16) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.localMultiaddr, err = multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", host, port)) if err != nil { return err } dhtPeer.host = host dhtPeer.port = port return nil } } // PublicURI for dhtpeer.New func PublicURI(host string, port uint16) Option { return func(dhtPeer *DHTPeer) error { var err error dhtPeer.publicMultiaddr, err = multiaddr.NewMultiaddr(fmt.Sprintf("/dns4/%s/tcp/%d", host, port)) if err != nil { return err } dhtPeer.publicHost = host dhtPeer.publicPort = port return nil } } // EnableDelegateService for dhtpeer.New func EnableDelegateService(port uint16) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.delegatePort = port return nil } } // EnableMailboxService for dhtpeer.New func EnableMailboxService(hostport string) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.mailboxHostPort = hostport return nil } } // EnableRelayService for dhtpeer.New func EnableRelayService() Option { return func(dhtPeer *DHTPeer) error { dhtPeer.enableRelay = true return nil } } // LoggingLevel for dhtpeer.New func LoggingLevel(lvl zerolog.Level) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.logger = dhtPeer.logger.Level(lvl) return nil } } // EnablePrometheusMonitoring for dhtpeer.New func EnablePrometheusMonitoring(port uint16) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.monitoringPort = port return nil } } // WithRegistrationDelay for dhtpeer.New func WithRegistrationDelay(delay time.Duration) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.registrationDelay = delay return nil } } // StoreRecordsTo for dhtpeer.New func StoreRecordsTo(path string) Option { return func(dhtPeer *DHTPeer) error { dhtPeer.persistentStoragePath = path return nil } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhtpeer/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package dhtpeer import ( utils "libp2p_node/utils" "net" ) type TLSListener struct { Listener net.Listener Signature []byte } func (listener TLSListener) Accept() (net.Conn, error) { con, err := listener.Listener.Accept() if err != nil { return con, err } err = utils.WriteBytesConn(con, listener.Signature) return con, err } func (listener TLSListener) Close() error { return listener.Listener.Close() } func (listener TLSListener) Addr() net.Addr { return listener.Listener.Addr() } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/dhttests/dhttests.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ // Package dhttests offers utilities to facilitate tests of dhtpeer, dhtclient, and dhtnetwork packages package dhttests import ( "libp2p_node/acn" "libp2p_node/aea" "libp2p_node/dht/dhtnode" "libp2p_node/dht/dhtpeer" "libp2p_node/utils" "log" "math/rand" "os" "path" ) // const ( DHTPeerDefaultLocalHost = "127.0.0.1" DHTPeerDefaultLocalPort = 2000 DHTPeerDefaultDelegatePort = 3000 DHTPeerDefaultFetchAIKey = "34604436e55b0eb99b5e62508433e172dd3ee133cf7a2fecb705e69611147605" DHTPeerDefaultFetchAIPublicKey = "039e883de988eededb9afaa4d3a6baec9ba74dd1cc237028e810569780b319940a" DHTPeerDefaultAgentKey = "719133dc740d76ff6d1d325e193f7cd63af4c8f3491bfe3010e58b0b58c77795" DHTPeerDefaultAgentPublicKey = "039623e63ba1617404b2abbe7bd94d24eb788335f870fac1ae4519e9bc359b7833" DHTPeerDefaultAgentAddress = "fetch134rg4n3wgmwctxsrm7gp6l65uwv6hxtxyfdwgw" ) func randSeq(n int) string { var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } // NewDHTPeerWithDefaults for testing func NewDHTPeerWithDefaults(inbox chan<- *aea.Envelope) (*dhtpeer.DHTPeer, func(), error) { opts := []dhtpeer.Option{ dhtpeer.LocalURI(DHTPeerDefaultLocalHost, DHTPeerDefaultLocalPort), dhtpeer.PublicURI(DHTPeerDefaultLocalHost, DHTPeerDefaultLocalPort), dhtpeer.IdentityFromFetchAIKey(DHTPeerDefaultFetchAIKey), dhtpeer.EnableRelayService(), dhtpeer.EnableDelegateService(DHTPeerDefaultDelegatePort), dhtpeer.StoreRecordsTo(path.Join(os.TempDir(), "agents_records_"+randSeq(5))), } signature, err := utils.SignFetchAI( []byte(DHTPeerDefaultFetchAIPublicKey), DHTPeerDefaultAgentKey, ) if err != nil { return nil, nil, err } record := &acn.AgentRecord{LedgerId: dhtnode.DefaultLedger} record.Address = DHTPeerDefaultAgentAddress record.PublicKey = DHTPeerDefaultAgentPublicKey record.PeerPublicKey = DHTPeerDefaultFetchAIPublicKey record.Signature = signature opts = append(opts, dhtpeer.RegisterAgentAddress(record, func() bool { return true })) dhtPeer, err := dhtpeer.New(opts...) if err != nil { return nil, nil, err } cleanup := func() { errs := dhtPeer.Close() if len(errs) > 0 { log.Println("ERROR while stopping DHTPeer:", errs) } } dhtPeer.ProcessEnvelope(func(envel *aea.Envelope) error { inbox <- envel return nil }) return dhtPeer, cleanup, nil } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/monitoring/file.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package monitoring import ( "fmt" "os" "sync" "time" ) type FileGauge struct { value float64 lock sync.RWMutex } func (fg *FileGauge) Set(value float64) { fg.lock.Lock() fg.value = value fg.lock.Unlock() } func (fg *FileGauge) Get() float64 { return fg.value } func (fg *FileGauge) Inc() { fg.Add(1.) } func (fg *FileGauge) Dec() { fg.Sub(1) } func (fg *FileGauge) Add(count float64) { fg.lock.Lock() fg.value += count fg.lock.Unlock() } func (fg *FileGauge) Sub(count float64) { fg.lock.Lock() fg.value -= count fg.lock.Unlock() } type FileCounter struct { value float64 lock sync.RWMutex } func (fc *FileCounter) Inc() { fc.Add(1.) } func (fc *FileCounter) Add(count float64) { fc.lock.Lock() fc.value += count fc.lock.Unlock() } func (fc *FileCounter) Get() float64 { return fc.value } type FileHistogram struct { buckets []float64 counts []uint64 lock sync.RWMutex } func (fh *FileHistogram) Observe(value float64) { fh.lock.Lock() var i int = 0 for i < len(fh.buckets) { if value <= fh.buckets[i] { fh.counts[i] += 1 } i++ } fh.counts[i] += 1 fh.lock.Unlock() } type FileMonitoring struct { Namespace string gaugeDict map[string]*FileGauge counterDict map[string]*FileCounter histoDict map[string]*FileHistogram timer *Timer path string write bool closing chan struct{} } func NewFileMonitoring(namespace string, write bool) *FileMonitoring { fm := &FileMonitoring{ Namespace: namespace, } fm.counterDict = map[string]*FileCounter{} fm.gaugeDict = map[string]*FileGauge{} fm.histoDict = map[string]*FileHistogram{} fm.timer = &Timer{ list: map[string]time.Time{}, lock: sync.RWMutex{}, } cwd, _ := os.Getwd() fm.path = cwd + "/" + fm.Namespace + ".stats" fm.write = write return fm } func (fm *FileMonitoring) NewCounter(name string, description string) (Counter, error) { counter := &FileCounter{} fm.counterDict[name] = counter return counter, nil } func (fm *FileMonitoring) GetCounter(name string) (Counter, bool) { counter, ok := fm.counterDict[name] return counter, ok } func (fm *FileMonitoring) NewGauge(name string, description string) (Gauge, error) { gauge := &FileGauge{} fm.gaugeDict[name] = gauge return gauge, nil } func (fm *FileMonitoring) GetGauge(name string) (Gauge, bool) { gauge, ok := fm.gaugeDict[name] return gauge, ok } func (fm *FileMonitoring) NewHistogram( name string, description string, buckets []float64, ) (Histogram, error) { histogram := &FileHistogram{ buckets: buckets, counts: make([]uint64, len(buckets)+1), } fm.histoDict[name] = histogram return histogram, nil } func (fm *FileMonitoring) GetHistogram(name string) (Histogram, bool) { histo, ok := fm.histoDict[name] return histo, ok } func (fm *FileMonitoring) Start() { if fm.closing != nil || !fm.write { return } fm.closing = make(chan struct{}) file, _ := os.OpenFile(fm.path, os.O_WRONLY|os.O_CREATE, 0666) L: for { select { case <-fm.closing: file.Close() break L default: ignore(file.Truncate(0)) _, err := file.Seek(0, 0) ignore(err) _, err = file.WriteString(fm.getStats()) ignore(err) time.Sleep(5 * time.Second) } } } func (fm *FileMonitoring) Stop() { close(fm.closing) } func (fm FileMonitoring) getStats() string { var stats string for name, value := range fm.gaugeDict { strValue := fmt.Sprintf("%e", value.Get()) stats += fm.Namespace + "_" + name + " " + strValue + "\n" } for name, value := range fm.counterDict { strValue := fmt.Sprintf("%e", value.Get()) stats += fm.Namespace + "_" + name + " " + strValue + "\n" } // TODO: report histograms return stats } func (fm *FileMonitoring) Info() string { return "FileMonitoring on " + fm.path } func (fm *FileMonitoring) Timer() *Timer { return fm.timer } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/monitoring/prometheus.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package monitoring import ( "net/http" "strconv" "sync" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" ) type PrometheusMonitoring struct { Namespace string Port uint16 running bool httpServer http.Server counterDict map[string]prometheus.Counter gaugeDict map[string]prometheus.Gauge histoDict map[string]prometheus.Histogram timer *Timer } func NewPrometheusMonitoring(namespace string, port uint16) *PrometheusMonitoring { pmts := &PrometheusMonitoring{ Namespace: namespace, Port: port, } pmts.counterDict = map[string]prometheus.Counter{} pmts.gaugeDict = map[string]prometheus.Gauge{} pmts.histoDict = map[string]prometheus.Histogram{} pmts.timer = &Timer{ list: map[string]time.Time{}, lock: sync.RWMutex{}, } return pmts } func (pmts *PrometheusMonitoring) NewCounter(name string, description string) (Counter, error) { counter := promauto.NewCounter(prometheus.CounterOpts{ Namespace: pmts.Namespace, Name: name, Help: description, }) pmts.counterDict[name] = counter return counter, nil } func (pmts *PrometheusMonitoring) GetCounter(name string) (Counter, bool) { counter, ok := pmts.counterDict[name] return counter, ok } func (pmts *PrometheusMonitoring) NewGauge(name string, description string) (Gauge, error) { gauge := promauto.NewGauge(prometheus.GaugeOpts{ Namespace: pmts.Namespace, Name: name, Help: description, }) pmts.gaugeDict[name] = gauge return gauge, nil } func (pmts *PrometheusMonitoring) GetGauge(name string) (Gauge, bool) { gauge, ok := pmts.gaugeDict[name] return gauge, ok } func (pmts *PrometheusMonitoring) NewHistogram( name string, description string, buckets []float64, ) (Histogram, error) { histogram := promauto.NewHistogram(prometheus.HistogramOpts{ Namespace: pmts.Namespace, Name: name, Help: description, Buckets: buckets, }) pmts.histoDict[name] = histogram return histogram, nil } func (pmts *PrometheusMonitoring) GetHistogram(name string) (Histogram, bool) { histogram, ok := pmts.histoDict[name] return histogram, ok } func (pmts *PrometheusMonitoring) Start() { if pmts.running { return } pmts.httpServer = http.Server{Addr: ":" + strconv.FormatInt(int64(pmts.Port), 10)} http.Handle("/metrics", promhttp.Handler()) pmts.running = true ignore(pmts.httpServer.ListenAndServe()) } func (pmts *PrometheusMonitoring) Stop() { pmts.httpServer.Close() } func (pmts *PrometheusMonitoring) Info() string { return "Prometheus at " + strconv.FormatInt(int64(pmts.Port), 10) } func (pmts *PrometheusMonitoring) Timer() *Timer { return pmts.timer } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/dht/monitoring/service.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package monitoring import ( "errors" "log" "sync" "time" ) type Gauge interface { Set(value float64) Inc() Dec() Add(count float64) Sub(count float64) } type Counter interface { Inc() Add(count float64) } type Histogram interface { Observe(value float64) } type Summary interface { Observe(value float64) } type Timer struct { list map[string]time.Time lock sync.RWMutex } func (tm *Timer) NewTimer() time.Time { return time.Now() } func (tm *Timer) GetTimer(timer time.Time) time.Duration { end := time.Now() return end.Sub(timer) } func (tm *Timer) NewTimerNamed(name string) string { tm.lock.Lock() defer tm.lock.Unlock() tm.list[name] = time.Now() return name } func (tm *Timer) GetTimerNamed(timer string) (time.Duration, error) { end := time.Now() tm.lock.RLock() start, ok := tm.list[timer] tm.lock.RUnlock() if !ok { return time.Duration(0), errors.New("Unknown timer " + timer) } tm.lock.Lock() delete(tm.list, timer) tm.lock.Unlock() return end.Sub(start), nil } type MonitoringService interface { NewCounter(name string, description string) (Counter, error) GetCounter(name string) (Counter, bool) NewGauge(name string, description string) (Gauge, error) GetGauge(name string) (Gauge, bool) NewHistogram(name string, description string, buckets []float64) (Histogram, error) GetHistogram(name string) (Histogram, bool) //NewSummary(name string, description string, objectives map[float64]float64) (Summary, error) //GetSummary(name string) (Summary, bool) Start() Stop() Info() string Timer() *Timer } func ignore(err error) { if err != nil { log.Println("IGNORED", err) } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/go.mod ================================================ module libp2p_node go 1.13 require ( bou.ke/monkey v1.0.2 github.com/btcsuite/btcd v0.20.1-beta github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d github.com/ethereum/go-ethereum v1.10.17 github.com/golang/mock v1.5.0 github.com/golang/protobuf v1.4.3 github.com/google/uuid v1.3.0 github.com/ipfs/go-cid v0.0.5 github.com/joho/godotenv v1.3.0 github.com/libp2p/go-libp2p v0.8.3 github.com/libp2p/go-libp2p-circuit v0.2.2 github.com/libp2p/go-libp2p-core v0.5.3 github.com/libp2p/go-libp2p-kad-dht v0.7.11 github.com/libp2p/go-libp2p-kbucket v0.4.1 github.com/multiformats/go-multiaddr v0.2.1 github.com/multiformats/go-multihash v0.0.13 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 github.com/rs/zerolog v1.21.0 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 google.golang.org/protobuf v1.25.0 honnef.co/go/tools v0.1.4 // indirect ) ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/go.sum ================================================ bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0 h1:MSskdM4/xJYcFzy0altH/C/xHopifpWzHUi1JeVI34Q= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204 h1:+EYBkW+dbi3F/atB+LSQZSWh7+HNrV3A/N0y6DSoy9k= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/ipfs/go-cid v0.0.5 h1:o0Ix8e/ql7Zb5UVUJEUfjsWCIY8t48++9lR8qi6oiJU= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4 h1:rjvQ9+muFaJ+QZ7dN5B1MSDNQ0JVZKkkES/rMZmA8X8= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-ipns v0.0.2 h1:oq4ErrV4hNQ2Eim257RTYRgfOSV/s8BDaf9iIl4NwFs= github.com/ipfs/go-ipns v0.0.2/go.mod h1:WChil4e0/m9cIINWLxZe1Jtf77oz5L05rO2ei/uKJ5U= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= github.com/ipfs/go-log v1.0.4 h1:6nLQdX4W8P9yZZFH7mO+X/PzjN8Laozm/lMJ6esdgzY= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5 h1:fL4YI+1g5V/b1Yxr1qAiXTMg1H8z9vx/VmJxBuQMHvU= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2 h1:vhC1OXXiT9R2pczegwz6moDvuRpggaroAXhPIseh57A= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-addr-util v0.0.1 h1:TpTQm9cXVRVSKsYbgQ7GKc3KbbHVTnbostgGaDEP+88= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0 h1:uNiDjS58vrvJTg9jO6bySd1rMKejieG7v45ekqHbZ1M= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-eventbus v0.1.0 h1:mlawomSAjjkk97QnYiEmHsLu7E136+2oCWSHRUvMfzQ= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.2/go.mod h1:NQDA/F/qArMHGe0J7sDScaKjW8Jh4y/ozQqBbYJ+BnA= github.com/libp2p/go-libp2p v0.8.3 h1:IFWeNzxkBaNO1N8stN9ayFGdC6RmVuSsKd5bou7qpK0= github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El8cTaefiM= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= github.com/libp2p/go-libp2p-autonat v0.2.2 h1:4dlgcEEugTFWSvdG2UIFxhnOMpX76QaZSRAtXmYB8n4= github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUADzkJacgZLvk= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.2.2 h1:87RLabJ9lrhoiSDDZyCJ80ZlI5TLJMwfyoGAaWXzWqA= github.com/libp2p/go-libp2p-circuit v0.2.2/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA= github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.2/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.3 h1:b9W3w7AZR2n/YJhG8d0qPFGhGhCWKIvPuJgp4hhc4MM= github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.4.0 h1:dK78UhopBk48mlHtRCzbdLm3q/81g77FahEBTjcqQT8= github.com/libp2p/go-libp2p-discovery v0.4.0/go.mod h1:bZ0aJSrFc/eX2llP0ryhb1kpgkPyTo23SJ5b7UQCMh4= github.com/libp2p/go-libp2p-kad-dht v0.7.11 h1:MP0DEuxO/Blg1AklIVV1P4R5xtYX+ZyXBCtEN7f60yQ= github.com/libp2p/go-libp2p-kad-dht v0.7.11/go.mod h1:1ht6+bG3Or+fNNERWPYmLacs6TN0CxBkFB5IKIWWwOI= github.com/libp2p/go-libp2p-kbucket v0.4.1 h1:6FyzbQuGLPzbMv3HiD232zqscIz5iB8ppJwb380+OGI= github.com/libp2p/go-libp2p-kbucket v0.4.1/go.mod h1:7sCeZx2GkNK1S6lQnGUW5JYZCFPnXzAZCCBBS70lytY= github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= github.com/libp2p/go-libp2p-mplex v0.2.3 h1:2zijwaJvpdesST2MXpI5w9wWFRgYtMcpRX7rrw0jmOo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6 h1:wMWis3kYynCbHoyKLPBEMu4YRLltbm8Mk08HGSfvTkU= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.3 h1:MofRq2l3c15vQpEygTetV+zRRrncz+ktiXW7H2EKoEQ= github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-record v0.1.2 h1:M50VKzWnmUrk/M5/Dz99qO9Xh4vs8ijsK+7HkJvRP+0= github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= github.com/libp2p/go-libp2p-secio v0.2.2 h1:rLLPvShPQAcY6eNurKNZq3eZjPWfU9kXF2eI9jIYdrg= github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-swarm v0.2.3 h1:uVkCb8Blfg7HQ/f30TyHn1g/uCwXsAET7pU0U59gx/A= github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.1 h1:U03z3HnGI7Ni8Xx6ONVZvUFOAzWYmolWf5W5jAOPNmU= github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-tls v0.1.3 h1:twKMhMu44jQO+HgQK9X8NHO5HkeJu2QbhLzLJpa8oNM= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0 h1:5EhPgQhXZNyfL22ERZTUoVp9UVVbNowWNVtELQaKCHk= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= github.com/libp2p/go-libp2p-yamux v0.2.7 h1:vzKu0NVtxvEIDGCv6mjKRcK0gipSgaXmJZ6jFv0d/dk= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.1.2 h1:qOg1s+WdGLlpkrczDqmhYzyk3vCfsQ8+RxRTQjOZWwI= github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4 h1:agEFehY3zWJFUHK6SEMR7UYmk2z6kC3oeCM7ybLhguA= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5 h1:qxnwkco8RLKqVh1NmjQ+tJ8p8khNLFxuElYG/TwqW4Q= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-netroute v0.1.2 h1:UHhB35chwgvcRI392znJA3RCBtZ3MpE3ahNCN5MR4Xg= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3 h1:zzOeXnTooCkRvoH+bSXEfXhn76+LAiwoneM0gnXjF2M= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-sockaddr v0.0.2 h1:tCuXfpA9rq7llM/v834RKc/Xvovy/AqM9kHvTV/jY/Q= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0 h1:TqnSHPJEIqDEO7h1wZZ0p3DXdvDSiLHQidKKUGZtiOY= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0 h1:YoThc549fzmNJIh7XjHVtMIFaEDRtIrtWciG5LyYAPo= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.3.1 h1:ZX5rWB8nhRRJVaPO6tmkGI/Xx8XNboYX20PW5hXIscw= github.com/libp2p/go-ws-transport v0.3.1/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.5 h1:ibuz4naPAully0pN6J/kmUARiqLpnDQIzI/8GCOrljg= github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.2.1 h1:SgG/cw5vqyB5QQe5FPe2TqggU9WtrA9X4nZw7LlVqOI= github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0 h1:YWJoIDwLePniH7OU5hBnDZV6SWuvJqJ0YtN6pLeH9zA= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.4 h1:g6gwydsfADqFvrHoMkS0n9Ok9CG6F7ytOH/bJDkhIOY= github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1 h1:JlAdpIFhBhGRLxe9W6Om0w++Gd6KMWoFPZL/dEnm9nI= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/libp2p_node.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package main import ( "fmt" "os" "os/signal" "syscall" "time" "github.com/rs/zerolog" aea "libp2p_node/aea" "libp2p_node/dht/dhtclient" "libp2p_node/dht/dhtnode" "libp2p_node/dht/dhtpeer" "libp2p_node/utils" ) const ( libp2pNodePanicError = "LIBP2P_NODE_PANIC_ERROR" libp2pMultiaddrsListStart = "MULTIADDRS_LIST_START" libp2pMultiaddrsListEnd = "MULTIADDRS_LIST_END" ) var logger zerolog.Logger = utils.NewDefaultLogger() // panics if err is not nil func check(err error) { if err != nil { fmt.Println(libp2pNodePanicError, ":", err.Error()) panic(err) } } func main() { var err error // Initialize connection to aea agent := aea.AeaApi{} check(agent.Init()) logger.Info().Msg("successfully initialized API to AEA!") // Get node configuration // aea agent address aeaAddr := agent.AeaAddress() // node address (ip and port) nodeHost, nodePort := agent.Address() // node public address, if set nodeHostPublic, nodePortPublic := agent.PublicAddress() // node delegate service address, if set _, nodePortDelegate := agent.DelegateAddress() // node monitoring service address, if set _, nodePortMonitoring := agent.MonitoringAddress() // node private key key := agent.PrivateKey() // entry peers entryPeers := agent.EntryPeers() // agent proof of representation record := agent.AgentRecord() // add artificial delay for agent registration registrationDelay := agent.RegistrationDelayInSeconds() // persist agent records to file storagePath := agent.RecordStoragePath() // libp2p node var node dhtnode.DHTNode // Run as a peer or just as a client if nodePortPublic == 0 { // if no external address is provided, run as a client opts := []dhtclient.Option{ dhtclient.IdentityFromFetchAIKey(key), dhtclient.BootstrapFrom(entryPeers), } if record != nil { opts = append(opts, dhtclient.RegisterAgentAddress(record, agent.Connected)) } node, err = dhtclient.New(opts...) } else { opts := []dhtpeer.Option{ dhtpeer.LocalURI(nodeHost, nodePort), dhtpeer.PublicURI(nodeHostPublic, nodePortPublic), dhtpeer.IdentityFromFetchAIKey(key), dhtpeer.EnableRelayService(), dhtpeer.EnableDelegateService(nodePortDelegate), dhtpeer.BootstrapFrom(entryPeers), } if record != nil { opts = append(opts, dhtpeer.RegisterAgentAddress(record, agent.Connected)) } if nodePortMonitoring != 0 { opts = append(opts, dhtpeer.EnablePrometheusMonitoring(nodePortMonitoring)) } if registrationDelay != 0 { //lint:ignore ST1011 don't use unit-specific suffix "Seconds" durationSeconds := time.Duration(registrationDelay) opts = append(opts, dhtpeer.WithRegistrationDelay(durationSeconds*1000000*time.Microsecond)) } if storagePath != "" { opts = append(opts, dhtpeer.StoreRecordsTo(storagePath)) } if len(agent.MailboxUri()) > 0 { opts = append(opts, dhtpeer.EnableMailboxService(agent.MailboxUri())) } node, err = dhtpeer.New(opts...) } if err != nil { check(err) } defer node.Close() logger.Info().Msgf("Peer ID: %s", node.PeerID()) // Connect to the agent fmt.Println(libp2pMultiaddrsListStart) // keyword fmt.Println(node.MultiAddr()) fmt.Println(libp2pMultiaddrsListEnd) // keyword check(agent.Connect()) if aeaAddr != "" { logger.Info().Msg("successfully connected to AEA!") } // Receive envelopes from agent and forward to peer go func() { for envel := range agent.Queue() { envelope := envel logger.Info().Msgf("received envelope from agent: %s", envelope) err := node.RouteEnvelope(envelope) if err != nil { logger.Error().Msgf("Route envelope error: %s", err.Error()) } } }() // Deliver envelopes received fro DHT to agent node.ProcessEnvelope(func(envel *aea.Envelope) error { return agent.Put(envel) }) // Wait until Ctrl+C or a termination call is done. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) // SIGTERM for k8s graceful stop support signal.Notify(c, syscall.SIGTERM) //wait for termination <-c logger.Info().Msg("node stopped") } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/link ================================================ #!/usr/bin/python2 import argparse import os import subprocess import sys # renamed 'link' file original_link_file = 'link_orig' # link first output=None code=0 try: output = subprocess.check_output([os.path.dirname(__file__) + '/' + original_link_file] + sys.argv[1:], cwd=os.getcwd()) except subprocess.CalledProcessError as e: output = e.output code = e.returncode if output: print output.replace(original_link_file, 'link') # change max_prot value to 0x7 parser = argparse.ArgumentParser() parser.add_argument('-o') args, _ = parser.parse_known_args() if args.o: binary_target = args.o # only for testing if binary_target.endswith('.test') or binary_target.endswith('_test_go'): with open(os.devnull, 'wb') as DEVNULL: try: subprocess.check_call(["printf '\x07' | dd of=%s bs=1 seek=160 count=1 conv=notrunc" % binary_target], shell=True, stdout=DEVNULL, stderr=DEVNULL) except subprocess.CalledProcessError as e: pass sys.exit(code) ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/mocks/mock_host.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/libp2p/go-libp2p-core/host (interfaces: Host) // Package mock_host is a generated GoMock package. package mocks import ( context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" connmgr "github.com/libp2p/go-libp2p-core/connmgr" event "github.com/libp2p/go-libp2p-core/event" network "github.com/libp2p/go-libp2p-core/network" peer "github.com/libp2p/go-libp2p-core/peer" peerstore "github.com/libp2p/go-libp2p-core/peerstore" protocol "github.com/libp2p/go-libp2p-core/protocol" multiaddr "github.com/multiformats/go-multiaddr" ) // MockHost is a mock of Host interface. type MockHost struct { ctrl *gomock.Controller recorder *MockHostMockRecorder } // MockHostMockRecorder is the mock recorder for MockHost. type MockHostMockRecorder struct { mock *MockHost } // NewMockHost creates a new mock instance. func NewMockHost(ctrl *gomock.Controller) *MockHost { mock := &MockHost{ctrl: ctrl} mock.recorder = &MockHostMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockHost) EXPECT() *MockHostMockRecorder { return m.recorder } // Addrs mocks base method. func (m *MockHost) Addrs() []multiaddr.Multiaddr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Addrs") ret0, _ := ret[0].([]multiaddr.Multiaddr) return ret0 } // Addrs indicates an expected call of Addrs. func (mr *MockHostMockRecorder) Addrs() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addrs", reflect.TypeOf((*MockHost)(nil).Addrs)) } // Close mocks base method. func (m *MockHost) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockHostMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockHost)(nil).Close)) } // ConnManager mocks base method. func (m *MockHost) ConnManager() connmgr.ConnManager { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ConnManager") ret0, _ := ret[0].(connmgr.ConnManager) return ret0 } // ConnManager indicates an expected call of ConnManager. func (mr *MockHostMockRecorder) ConnManager() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnManager", reflect.TypeOf((*MockHost)(nil).ConnManager)) } // Connect mocks base method. func (m *MockHost) Connect(arg0 context.Context, arg1 peer.AddrInfo) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Connect", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // Connect indicates an expected call of Connect. func (mr *MockHostMockRecorder) Connect(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockHost)(nil).Connect), arg0, arg1) } // EventBus mocks base method. func (m *MockHost) EventBus() event.Bus { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "EventBus") ret0, _ := ret[0].(event.Bus) return ret0 } // EventBus indicates an expected call of EventBus. func (mr *MockHostMockRecorder) EventBus() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventBus", reflect.TypeOf((*MockHost)(nil).EventBus)) } // ID mocks base method. func (m *MockHost) ID() peer.ID { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ID") ret0, _ := ret[0].(peer.ID) return ret0 } // ID indicates an expected call of ID. func (mr *MockHostMockRecorder) ID() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockHost)(nil).ID)) } // Mux mocks base method. func (m *MockHost) Mux() protocol.Switch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Mux") ret0, _ := ret[0].(protocol.Switch) return ret0 } // Mux indicates an expected call of Mux. func (mr *MockHostMockRecorder) Mux() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Mux", reflect.TypeOf((*MockHost)(nil).Mux)) } // Network mocks base method. func (m *MockHost) Network() network.Network { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") ret0, _ := ret[0].(network.Network) return ret0 } // Network indicates an expected call of Network. func (mr *MockHostMockRecorder) Network() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*MockHost)(nil).Network)) } // NewStream mocks base method. func (m *MockHost) NewStream(arg0 context.Context, arg1 peer.ID, arg2 ...protocol.ID) (network.Stream, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "NewStream", varargs...) ret0, _ := ret[0].(network.Stream) ret1, _ := ret[1].(error) return ret0, ret1 } // NewStream indicates an expected call of NewStream. func (mr *MockHostMockRecorder) NewStream(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewStream", reflect.TypeOf((*MockHost)(nil).NewStream), varargs...) } // Peerstore mocks base method. func (m *MockHost) Peerstore() peerstore.Peerstore { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Peerstore") ret0, _ := ret[0].(peerstore.Peerstore) return ret0 } // Peerstore indicates an expected call of Peerstore. func (mr *MockHostMockRecorder) Peerstore() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peerstore", reflect.TypeOf((*MockHost)(nil).Peerstore)) } // RemoveStreamHandler mocks base method. func (m *MockHost) RemoveStreamHandler(arg0 protocol.ID) { m.ctrl.T.Helper() m.ctrl.Call(m, "RemoveStreamHandler", arg0) } // RemoveStreamHandler indicates an expected call of RemoveStreamHandler. func (mr *MockHostMockRecorder) RemoveStreamHandler(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveStreamHandler", reflect.TypeOf((*MockHost)(nil).RemoveStreamHandler), arg0) } // SetStreamHandler mocks base method. func (m *MockHost) SetStreamHandler(arg0 protocol.ID, arg1 network.StreamHandler) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetStreamHandler", arg0, arg1) } // SetStreamHandler indicates an expected call of SetStreamHandler. func (mr *MockHostMockRecorder) SetStreamHandler(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStreamHandler", reflect.TypeOf((*MockHost)(nil).SetStreamHandler), arg0, arg1) } // SetStreamHandlerMatch mocks base method. func (m *MockHost) SetStreamHandlerMatch(arg0 protocol.ID, arg1 func(string) bool, arg2 network.StreamHandler) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetStreamHandlerMatch", arg0, arg1, arg2) } // SetStreamHandlerMatch indicates an expected call of SetStreamHandlerMatch. func (mr *MockHostMockRecorder) SetStreamHandlerMatch(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStreamHandlerMatch", reflect.TypeOf((*MockHost)(nil).SetStreamHandlerMatch), arg0, arg1, arg2) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/mocks/mock_net.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: net (interfaces: Conn) // Package mock_net is a generated GoMock package. package mocks import ( net "net" reflect "reflect" time "time" gomock "github.com/golang/mock/gomock" ) // MockConn is a mock of Conn interface. type MockConn struct { ctrl *gomock.Controller recorder *MockConnMockRecorder } // MockConnMockRecorder is the mock recorder for MockConn. type MockConnMockRecorder struct { mock *MockConn } // NewMockConn creates a new mock instance. func NewMockConn(ctrl *gomock.Controller) *MockConn { mock := &MockConn{ctrl: ctrl} mock.recorder = &MockConnMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockConn) EXPECT() *MockConnMockRecorder { return m.recorder } // Close mocks base method. func (m *MockConn) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockConnMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConn)(nil).Close)) } // LocalAddr mocks base method. func (m *MockConn) LocalAddr() net.Addr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LocalAddr") ret0, _ := ret[0].(net.Addr) return ret0 } // LocalAddr indicates an expected call of LocalAddr. func (mr *MockConnMockRecorder) LocalAddr() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockConn)(nil).LocalAddr)) } // Read mocks base method. func (m *MockConn) Read(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Read", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Read indicates an expected call of Read. func (mr *MockConnMockRecorder) Read(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockConn)(nil).Read), arg0) } // RemoteAddr mocks base method. func (m *MockConn) RemoteAddr() net.Addr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RemoteAddr") ret0, _ := ret[0].(net.Addr) return ret0 } // RemoteAddr indicates an expected call of RemoteAddr. func (mr *MockConnMockRecorder) RemoteAddr() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockConn)(nil).RemoteAddr)) } // SetDeadline mocks base method. func (m *MockConn) SetDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetDeadline indicates an expected call of SetDeadline. func (mr *MockConnMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockConn)(nil).SetDeadline), arg0) } // SetReadDeadline mocks base method. func (m *MockConn) SetReadDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetReadDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetReadDeadline indicates an expected call of SetReadDeadline. func (mr *MockConnMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockConn)(nil).SetReadDeadline), arg0) } // SetWriteDeadline mocks base method. func (m *MockConn) SetWriteDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetWriteDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetWriteDeadline indicates an expected call of SetWriteDeadline. func (mr *MockConnMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockConn)(nil).SetWriteDeadline), arg0) } // Write mocks base method. func (m *MockConn) Write(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Write", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Write indicates an expected call of Write. func (mr *MockConnMockRecorder) Write(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockConn)(nil).Write), arg0) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/mocks/mock_network.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/libp2p/go-libp2p-core/network (interfaces: Stream) // Package mock_network is a generated GoMock package. package mocks import ( reflect "reflect" time "time" gomock "github.com/golang/mock/gomock" network "github.com/libp2p/go-libp2p-core/network" protocol "github.com/libp2p/go-libp2p-core/protocol" ) // MockStream is a mock of Stream interface. type MockStream struct { ctrl *gomock.Controller recorder *MockStreamMockRecorder } // MockStreamMockRecorder is the mock recorder for MockStream. type MockStreamMockRecorder struct { mock *MockStream } // NewMockStream creates a new mock instance. func NewMockStream(ctrl *gomock.Controller) *MockStream { mock := &MockStream{ctrl: ctrl} mock.recorder = &MockStreamMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockStream) EXPECT() *MockStreamMockRecorder { return m.recorder } // Close mocks base method. func (m *MockStream) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockStreamMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStream)(nil).Close)) } // Conn mocks base method. func (m *MockStream) Conn() network.Conn { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Conn") ret0, _ := ret[0].(network.Conn) return ret0 } // Conn indicates an expected call of Conn. func (mr *MockStreamMockRecorder) Conn() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Conn", reflect.TypeOf((*MockStream)(nil).Conn)) } // Protocol mocks base method. func (m *MockStream) Protocol() protocol.ID { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Protocol") ret0, _ := ret[0].(protocol.ID) return ret0 } // Protocol indicates an expected call of Protocol. func (mr *MockStreamMockRecorder) Protocol() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protocol", reflect.TypeOf((*MockStream)(nil).Protocol)) } // Read mocks base method. func (m *MockStream) Read(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Read", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Read indicates an expected call of Read. func (mr *MockStreamMockRecorder) Read(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockStream)(nil).Read), arg0) } // Reset mocks base method. func (m *MockStream) Reset() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Reset") ret0, _ := ret[0].(error) return ret0 } // Reset indicates an expected call of Reset. func (mr *MockStreamMockRecorder) Reset() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockStream)(nil).Reset)) } // SetDeadline mocks base method. func (m *MockStream) SetDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetDeadline indicates an expected call of SetDeadline. func (mr *MockStreamMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockStream)(nil).SetDeadline), arg0) } // SetProtocol mocks base method. func (m *MockStream) SetProtocol(arg0 protocol.ID) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetProtocol", arg0) } // SetProtocol indicates an expected call of SetProtocol. func (mr *MockStreamMockRecorder) SetProtocol(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProtocol", reflect.TypeOf((*MockStream)(nil).SetProtocol), arg0) } // SetReadDeadline mocks base method. func (m *MockStream) SetReadDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetReadDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetReadDeadline indicates an expected call of SetReadDeadline. func (mr *MockStreamMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockStream)(nil).SetReadDeadline), arg0) } // SetWriteDeadline mocks base method. func (m *MockStream) SetWriteDeadline(arg0 time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetWriteDeadline", arg0) ret0, _ := ret[0].(error) return ret0 } // SetWriteDeadline indicates an expected call of SetWriteDeadline. func (mr *MockStreamMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockStream)(nil).SetWriteDeadline), arg0) } // Stat mocks base method. func (m *MockStream) Stat() network.Stat { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Stat") ret0, _ := ret[0].(network.Stat) return ret0 } // Stat indicates an expected call of Stat. func (mr *MockStreamMockRecorder) Stat() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockStream)(nil).Stat)) } // Write mocks base method. func (m *MockStream) Write(arg0 []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Write", arg0) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Write indicates an expected call of Write. func (mr *MockStreamMockRecorder) Write(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStream)(nil).Write), arg0) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/mocks/mock_peerstore.go ================================================ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/libp2p/go-libp2p-core/peerstore (interfaces: Peerstore) // Package mock_peerstore is a generated GoMock package. package mocks import ( context "context" reflect "reflect" time "time" gomock "github.com/golang/mock/gomock" crypto "github.com/libp2p/go-libp2p-core/crypto" peer "github.com/libp2p/go-libp2p-core/peer" multiaddr "github.com/multiformats/go-multiaddr" ) // MockPeerstore is a mock of Peerstore interface. type MockPeerstore struct { ctrl *gomock.Controller recorder *MockPeerstoreMockRecorder } // MockPeerstoreMockRecorder is the mock recorder for MockPeerstore. type MockPeerstoreMockRecorder struct { mock *MockPeerstore } // NewMockPeerstore creates a new mock instance. func NewMockPeerstore(ctrl *gomock.Controller) *MockPeerstore { mock := &MockPeerstore{ctrl: ctrl} mock.recorder = &MockPeerstoreMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockPeerstore) EXPECT() *MockPeerstoreMockRecorder { return m.recorder } // AddAddr mocks base method. func (m *MockPeerstore) AddAddr(arg0 peer.ID, arg1 multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "AddAddr", arg0, arg1, arg2) } // AddAddr indicates an expected call of AddAddr. func (mr *MockPeerstoreMockRecorder) AddAddr(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAddr", reflect.TypeOf((*MockPeerstore)(nil).AddAddr), arg0, arg1, arg2) } // AddAddrs mocks base method. func (m *MockPeerstore) AddAddrs(arg0 peer.ID, arg1 []multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "AddAddrs", arg0, arg1, arg2) } // AddAddrs indicates an expected call of AddAddrs. func (mr *MockPeerstoreMockRecorder) AddAddrs(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAddrs", reflect.TypeOf((*MockPeerstore)(nil).AddAddrs), arg0, arg1, arg2) } // AddPrivKey mocks base method. func (m *MockPeerstore) AddPrivKey(arg0 peer.ID, arg1 crypto.PrivKey) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddPrivKey", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // AddPrivKey indicates an expected call of AddPrivKey. func (mr *MockPeerstoreMockRecorder) AddPrivKey(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPrivKey", reflect.TypeOf((*MockPeerstore)(nil).AddPrivKey), arg0, arg1) } // AddProtocols mocks base method. func (m *MockPeerstore) AddProtocols(arg0 peer.ID, arg1 ...string) error { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "AddProtocols", varargs...) ret0, _ := ret[0].(error) return ret0 } // AddProtocols indicates an expected call of AddProtocols. func (mr *MockPeerstoreMockRecorder) AddProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddProtocols", reflect.TypeOf((*MockPeerstore)(nil).AddProtocols), varargs...) } // AddPubKey mocks base method. func (m *MockPeerstore) AddPubKey(arg0 peer.ID, arg1 crypto.PubKey) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddPubKey", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // AddPubKey indicates an expected call of AddPubKey. func (mr *MockPeerstoreMockRecorder) AddPubKey(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPubKey", reflect.TypeOf((*MockPeerstore)(nil).AddPubKey), arg0, arg1) } // AddrStream mocks base method. func (m *MockPeerstore) AddrStream(arg0 context.Context, arg1 peer.ID) <-chan multiaddr.Multiaddr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddrStream", arg0, arg1) ret0, _ := ret[0].(<-chan multiaddr.Multiaddr) return ret0 } // AddrStream indicates an expected call of AddrStream. func (mr *MockPeerstoreMockRecorder) AddrStream(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrStream", reflect.TypeOf((*MockPeerstore)(nil).AddrStream), arg0, arg1) } // Addrs mocks base method. func (m *MockPeerstore) Addrs(arg0 peer.ID) []multiaddr.Multiaddr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Addrs", arg0) ret0, _ := ret[0].([]multiaddr.Multiaddr) return ret0 } // Addrs indicates an expected call of Addrs. func (mr *MockPeerstoreMockRecorder) Addrs(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addrs", reflect.TypeOf((*MockPeerstore)(nil).Addrs), arg0) } // ClearAddrs mocks base method. func (m *MockPeerstore) ClearAddrs(arg0 peer.ID) { m.ctrl.T.Helper() m.ctrl.Call(m, "ClearAddrs", arg0) } // ClearAddrs indicates an expected call of ClearAddrs. func (mr *MockPeerstoreMockRecorder) ClearAddrs(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearAddrs", reflect.TypeOf((*MockPeerstore)(nil).ClearAddrs), arg0) } // Close mocks base method. func (m *MockPeerstore) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) return ret0 } // Close indicates an expected call of Close. func (mr *MockPeerstoreMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockPeerstore)(nil).Close)) } // Get mocks base method. func (m *MockPeerstore) Get(arg0 peer.ID, arg1 string) (interface{}, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0, arg1) ret0, _ := ret[0].(interface{}) ret1, _ := ret[1].(error) return ret0, ret1 } // Get indicates an expected call of Get. func (mr *MockPeerstoreMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockPeerstore)(nil).Get), arg0, arg1) } // GetProtocols mocks base method. func (m *MockPeerstore) GetProtocols(arg0 peer.ID) ([]string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetProtocols", arg0) ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } // GetProtocols indicates an expected call of GetProtocols. func (mr *MockPeerstoreMockRecorder) GetProtocols(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProtocols", reflect.TypeOf((*MockPeerstore)(nil).GetProtocols), arg0) } // LatencyEWMA mocks base method. func (m *MockPeerstore) LatencyEWMA(arg0 peer.ID) time.Duration { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LatencyEWMA", arg0) ret0, _ := ret[0].(time.Duration) return ret0 } // LatencyEWMA indicates an expected call of LatencyEWMA. func (mr *MockPeerstoreMockRecorder) LatencyEWMA(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatencyEWMA", reflect.TypeOf((*MockPeerstore)(nil).LatencyEWMA), arg0) } // PeerInfo mocks base method. func (m *MockPeerstore) PeerInfo(arg0 peer.ID) peer.AddrInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PeerInfo", arg0) ret0, _ := ret[0].(peer.AddrInfo) return ret0 } // PeerInfo indicates an expected call of PeerInfo. func (mr *MockPeerstoreMockRecorder) PeerInfo(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerInfo", reflect.TypeOf((*MockPeerstore)(nil).PeerInfo), arg0) } // Peers mocks base method. func (m *MockPeerstore) Peers() peer.IDSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Peers") ret0, _ := ret[0].(peer.IDSlice) return ret0 } // Peers indicates an expected call of Peers. func (mr *MockPeerstoreMockRecorder) Peers() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peers", reflect.TypeOf((*MockPeerstore)(nil).Peers)) } // PeersWithAddrs mocks base method. func (m *MockPeerstore) PeersWithAddrs() peer.IDSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PeersWithAddrs") ret0, _ := ret[0].(peer.IDSlice) return ret0 } // PeersWithAddrs indicates an expected call of PeersWithAddrs. func (mr *MockPeerstoreMockRecorder) PeersWithAddrs() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeersWithAddrs", reflect.TypeOf((*MockPeerstore)(nil).PeersWithAddrs)) } // PeersWithKeys mocks base method. func (m *MockPeerstore) PeersWithKeys() peer.IDSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PeersWithKeys") ret0, _ := ret[0].(peer.IDSlice) return ret0 } // PeersWithKeys indicates an expected call of PeersWithKeys. func (mr *MockPeerstoreMockRecorder) PeersWithKeys() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeersWithKeys", reflect.TypeOf((*MockPeerstore)(nil).PeersWithKeys)) } // PrivKey mocks base method. func (m *MockPeerstore) PrivKey(arg0 peer.ID) crypto.PrivKey { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PrivKey", arg0) ret0, _ := ret[0].(crypto.PrivKey) return ret0 } // PrivKey indicates an expected call of PrivKey. func (mr *MockPeerstoreMockRecorder) PrivKey(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrivKey", reflect.TypeOf((*MockPeerstore)(nil).PrivKey), arg0) } // PubKey mocks base method. func (m *MockPeerstore) PubKey(arg0 peer.ID) crypto.PubKey { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PubKey", arg0) ret0, _ := ret[0].(crypto.PubKey) return ret0 } // PubKey indicates an expected call of PubKey. func (mr *MockPeerstoreMockRecorder) PubKey(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubKey", reflect.TypeOf((*MockPeerstore)(nil).PubKey), arg0) } // Put mocks base method. func (m *MockPeerstore) Put(arg0 peer.ID, arg1 string, arg2 interface{}) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Put", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } // Put indicates an expected call of Put. func (mr *MockPeerstoreMockRecorder) Put(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockPeerstore)(nil).Put), arg0, arg1, arg2) } // RecordLatency mocks base method. func (m *MockPeerstore) RecordLatency(arg0 peer.ID, arg1 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordLatency", arg0, arg1) } // RecordLatency indicates an expected call of RecordLatency. func (mr *MockPeerstoreMockRecorder) RecordLatency(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLatency", reflect.TypeOf((*MockPeerstore)(nil).RecordLatency), arg0, arg1) } // RemoveProtocols mocks base method. func (m *MockPeerstore) RemoveProtocols(arg0 peer.ID, arg1 ...string) error { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "RemoveProtocols", varargs...) ret0, _ := ret[0].(error) return ret0 } // RemoveProtocols indicates an expected call of RemoveProtocols. func (mr *MockPeerstoreMockRecorder) RemoveProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveProtocols", reflect.TypeOf((*MockPeerstore)(nil).RemoveProtocols), varargs...) } // SetAddr mocks base method. func (m *MockPeerstore) SetAddr(arg0 peer.ID, arg1 multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetAddr", arg0, arg1, arg2) } // SetAddr indicates an expected call of SetAddr. func (mr *MockPeerstoreMockRecorder) SetAddr(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAddr", reflect.TypeOf((*MockPeerstore)(nil).SetAddr), arg0, arg1, arg2) } // SetAddrs mocks base method. func (m *MockPeerstore) SetAddrs(arg0 peer.ID, arg1 []multiaddr.Multiaddr, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetAddrs", arg0, arg1, arg2) } // SetAddrs indicates an expected call of SetAddrs. func (mr *MockPeerstoreMockRecorder) SetAddrs(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAddrs", reflect.TypeOf((*MockPeerstore)(nil).SetAddrs), arg0, arg1, arg2) } // SetProtocols mocks base method. func (m *MockPeerstore) SetProtocols(arg0 peer.ID, arg1 ...string) error { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "SetProtocols", varargs...) ret0, _ := ret[0].(error) return ret0 } // SetProtocols indicates an expected call of SetProtocols. func (mr *MockPeerstoreMockRecorder) SetProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProtocols", reflect.TypeOf((*MockPeerstore)(nil).SetProtocols), varargs...) } // SupportsProtocols mocks base method. func (m *MockPeerstore) SupportsProtocols(arg0 peer.ID, arg1 ...string) ([]string, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "SupportsProtocols", varargs...) ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } // SupportsProtocols indicates an expected call of SupportsProtocols. func (mr *MockPeerstoreMockRecorder) SupportsProtocols(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportsProtocols", reflect.TypeOf((*MockPeerstore)(nil).SupportsProtocols), varargs...) } // UpdateAddrs mocks base method. func (m *MockPeerstore) UpdateAddrs(arg0 peer.ID, arg1, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "UpdateAddrs", arg0, arg1, arg2) } // UpdateAddrs indicates an expected call of UpdateAddrs. func (mr *MockPeerstoreMockRecorder) UpdateAddrs(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAddrs", reflect.TypeOf((*MockPeerstore)(nil).UpdateAddrs), arg0, arg1, arg2) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/protocols/acn/v1_0_0/acn.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 // protoc v3.11.4 // source: acn.proto package aea_aea_acn_v1_0_0 import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type AcnMessage_StatusBody_StatusCodeEnum int32 const ( // common (0x) AcnMessage_StatusBody_SUCCESS AcnMessage_StatusBody_StatusCodeEnum = 0 AcnMessage_StatusBody_ERROR_UNSUPPORTED_VERSION AcnMessage_StatusBody_StatusCodeEnum = 1 AcnMessage_StatusBody_ERROR_UNEXPECTED_PAYLOAD AcnMessage_StatusBody_StatusCodeEnum = 2 AcnMessage_StatusBody_ERROR_GENERIC AcnMessage_StatusBody_StatusCodeEnum = 3 AcnMessage_StatusBody_ERROR_DECODE AcnMessage_StatusBody_StatusCodeEnum = 4 // register (1x) AcnMessage_StatusBody_ERROR_WRONG_AGENT_ADDRESS AcnMessage_StatusBody_StatusCodeEnum = 10 AcnMessage_StatusBody_ERROR_WRONG_PUBLIC_KEY AcnMessage_StatusBody_StatusCodeEnum = 11 AcnMessage_StatusBody_ERROR_INVALID_PROOF AcnMessage_StatusBody_StatusCodeEnum = 12 AcnMessage_StatusBody_ERROR_UNSUPPORTED_LEDGER AcnMessage_StatusBody_StatusCodeEnum = 13 // lookup & delivery (2x) AcnMessage_StatusBody_ERROR_UNKNOWN_AGENT_ADDRESS AcnMessage_StatusBody_StatusCodeEnum = 20 AcnMessage_StatusBody_ERROR_AGENT_NOT_READY AcnMessage_StatusBody_StatusCodeEnum = 21 ) // Enum value maps for AcnMessage_StatusBody_StatusCodeEnum. var ( AcnMessage_StatusBody_StatusCodeEnum_name = map[int32]string{ 0: "SUCCESS", 1: "ERROR_UNSUPPORTED_VERSION", 2: "ERROR_UNEXPECTED_PAYLOAD", 3: "ERROR_GENERIC", 4: "ERROR_DECODE", 10: "ERROR_WRONG_AGENT_ADDRESS", 11: "ERROR_WRONG_PUBLIC_KEY", 12: "ERROR_INVALID_PROOF", 13: "ERROR_UNSUPPORTED_LEDGER", 20: "ERROR_UNKNOWN_AGENT_ADDRESS", 21: "ERROR_AGENT_NOT_READY", } AcnMessage_StatusBody_StatusCodeEnum_value = map[string]int32{ "SUCCESS": 0, "ERROR_UNSUPPORTED_VERSION": 1, "ERROR_UNEXPECTED_PAYLOAD": 2, "ERROR_GENERIC": 3, "ERROR_DECODE": 4, "ERROR_WRONG_AGENT_ADDRESS": 10, "ERROR_WRONG_PUBLIC_KEY": 11, "ERROR_INVALID_PROOF": 12, "ERROR_UNSUPPORTED_LEDGER": 13, "ERROR_UNKNOWN_AGENT_ADDRESS": 20, "ERROR_AGENT_NOT_READY": 21, } ) func (x AcnMessage_StatusBody_StatusCodeEnum) Enum() *AcnMessage_StatusBody_StatusCodeEnum { p := new(AcnMessage_StatusBody_StatusCodeEnum) *p = x return p } func (x AcnMessage_StatusBody_StatusCodeEnum) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (AcnMessage_StatusBody_StatusCodeEnum) Descriptor() protoreflect.EnumDescriptor { return file_acn_proto_enumTypes[0].Descriptor() } func (AcnMessage_StatusBody_StatusCodeEnum) Type() protoreflect.EnumType { return &file_acn_proto_enumTypes[0] } func (x AcnMessage_StatusBody_StatusCodeEnum) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use AcnMessage_StatusBody_StatusCodeEnum.Descriptor instead. func (AcnMessage_StatusBody_StatusCodeEnum) EnumDescriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 1, 0} } type AcnMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Performative: // *AcnMessage_AeaEnvelope // *AcnMessage_LookupRequest // *AcnMessage_LookupResponse // *AcnMessage_Register // *AcnMessage_Status Performative isAcnMessage_Performative `protobuf_oneof:"performative"` } func (x *AcnMessage) Reset() { *x = AcnMessage{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage) ProtoMessage() {} func (x *AcnMessage) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage.ProtoReflect.Descriptor instead. func (*AcnMessage) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0} } func (m *AcnMessage) GetPerformative() isAcnMessage_Performative { if m != nil { return m.Performative } return nil } func (x *AcnMessage) GetAeaEnvelope() *AcnMessage_Aea_Envelope_Performative { if x, ok := x.GetPerformative().(*AcnMessage_AeaEnvelope); ok { return x.AeaEnvelope } return nil } func (x *AcnMessage) GetLookupRequest() *AcnMessage_Lookup_Request_Performative { if x, ok := x.GetPerformative().(*AcnMessage_LookupRequest); ok { return x.LookupRequest } return nil } func (x *AcnMessage) GetLookupResponse() *AcnMessage_Lookup_Response_Performative { if x, ok := x.GetPerformative().(*AcnMessage_LookupResponse); ok { return x.LookupResponse } return nil } func (x *AcnMessage) GetRegister() *AcnMessage_Register_Performative { if x, ok := x.GetPerformative().(*AcnMessage_Register); ok { return x.Register } return nil } func (x *AcnMessage) GetStatus() *AcnMessage_Status_Performative { if x, ok := x.GetPerformative().(*AcnMessage_Status); ok { return x.Status } return nil } type isAcnMessage_Performative interface { isAcnMessage_Performative() } type AcnMessage_AeaEnvelope struct { AeaEnvelope *AcnMessage_Aea_Envelope_Performative `protobuf:"bytes,5,opt,name=aea_envelope,json=aeaEnvelope,proto3,oneof"` } type AcnMessage_LookupRequest struct { LookupRequest *AcnMessage_Lookup_Request_Performative `protobuf:"bytes,6,opt,name=lookup_request,json=lookupRequest,proto3,oneof"` } type AcnMessage_LookupResponse struct { LookupResponse *AcnMessage_Lookup_Response_Performative `protobuf:"bytes,7,opt,name=lookup_response,json=lookupResponse,proto3,oneof"` } type AcnMessage_Register struct { Register *AcnMessage_Register_Performative `protobuf:"bytes,8,opt,name=register,proto3,oneof"` } type AcnMessage_Status struct { Status *AcnMessage_Status_Performative `protobuf:"bytes,9,opt,name=status,proto3,oneof"` } func (*AcnMessage_AeaEnvelope) isAcnMessage_Performative() {} func (*AcnMessage_LookupRequest) isAcnMessage_Performative() {} func (*AcnMessage_LookupResponse) isAcnMessage_Performative() {} func (*AcnMessage_Register) isAcnMessage_Performative() {} func (*AcnMessage_Status) isAcnMessage_Performative() {} // Custom Types type AcnMessage_AgentRecord struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ServiceId string `protobuf:"bytes,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` LedgerId string `protobuf:"bytes,2,opt,name=ledger_id,json=ledgerId,proto3" json:"ledger_id,omitempty"` Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` PublicKey string `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` PeerPublicKey string `protobuf:"bytes,5,opt,name=peer_public_key,json=peerPublicKey,proto3" json:"peer_public_key,omitempty"` Signature string `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` NotBefore string `protobuf:"bytes,7,opt,name=not_before,json=notBefore,proto3" json:"not_before,omitempty"` NotAfter string `protobuf:"bytes,8,opt,name=not_after,json=notAfter,proto3" json:"not_after,omitempty"` } func (x *AcnMessage_AgentRecord) Reset() { *x = AcnMessage_AgentRecord{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_AgentRecord) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_AgentRecord) ProtoMessage() {} func (x *AcnMessage_AgentRecord) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_AgentRecord.ProtoReflect.Descriptor instead. func (*AcnMessage_AgentRecord) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 0} } func (x *AcnMessage_AgentRecord) GetServiceId() string { if x != nil { return x.ServiceId } return "" } func (x *AcnMessage_AgentRecord) GetLedgerId() string { if x != nil { return x.LedgerId } return "" } func (x *AcnMessage_AgentRecord) GetAddress() string { if x != nil { return x.Address } return "" } func (x *AcnMessage_AgentRecord) GetPublicKey() string { if x != nil { return x.PublicKey } return "" } func (x *AcnMessage_AgentRecord) GetPeerPublicKey() string { if x != nil { return x.PeerPublicKey } return "" } func (x *AcnMessage_AgentRecord) GetSignature() string { if x != nil { return x.Signature } return "" } func (x *AcnMessage_AgentRecord) GetNotBefore() string { if x != nil { return x.NotBefore } return "" } func (x *AcnMessage_AgentRecord) GetNotAfter() string { if x != nil { return x.NotAfter } return "" } type AcnMessage_StatusBody struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Code AcnMessage_StatusBody_StatusCodeEnum `protobuf:"varint,1,opt,name=code,proto3,enum=aea.aea.acn.v1_0_0.AcnMessage_StatusBody_StatusCodeEnum" json:"code,omitempty"` Msgs []string `protobuf:"bytes,2,rep,name=msgs,proto3" json:"msgs,omitempty"` } func (x *AcnMessage_StatusBody) Reset() { *x = AcnMessage_StatusBody{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_StatusBody) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_StatusBody) ProtoMessage() {} func (x *AcnMessage_StatusBody) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_StatusBody.ProtoReflect.Descriptor instead. func (*AcnMessage_StatusBody) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 1} } func (x *AcnMessage_StatusBody) GetCode() AcnMessage_StatusBody_StatusCodeEnum { if x != nil { return x.Code } return AcnMessage_StatusBody_SUCCESS } func (x *AcnMessage_StatusBody) GetMsgs() []string { if x != nil { return x.Msgs } return nil } // Performatives and contents type AcnMessage_Register_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Record *AcnMessage_AgentRecord `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Register_Performative) Reset() { *x = AcnMessage_Register_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Register_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Register_Performative) ProtoMessage() {} func (x *AcnMessage_Register_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Register_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Register_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 2} } func (x *AcnMessage_Register_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Lookup_Request_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AgentAddress string `protobuf:"bytes,1,opt,name=agent_address,json=agentAddress,proto3" json:"agent_address,omitempty"` } func (x *AcnMessage_Lookup_Request_Performative) Reset() { *x = AcnMessage_Lookup_Request_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Lookup_Request_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Lookup_Request_Performative) ProtoMessage() {} func (x *AcnMessage_Lookup_Request_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Lookup_Request_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Lookup_Request_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 3} } func (x *AcnMessage_Lookup_Request_Performative) GetAgentAddress() string { if x != nil { return x.AgentAddress } return "" } type AcnMessage_Lookup_Response_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Record *AcnMessage_AgentRecord `protobuf:"bytes,1,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Lookup_Response_Performative) Reset() { *x = AcnMessage_Lookup_Response_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Lookup_Response_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Lookup_Response_Performative) ProtoMessage() {} func (x *AcnMessage_Lookup_Response_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Lookup_Response_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Lookup_Response_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 4} } func (x *AcnMessage_Lookup_Response_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Aea_Envelope_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Envelope []byte `protobuf:"bytes,1,opt,name=envelope,proto3" json:"envelope,omitempty"` Record *AcnMessage_AgentRecord `protobuf:"bytes,2,opt,name=record,proto3" json:"record,omitempty"` } func (x *AcnMessage_Aea_Envelope_Performative) Reset() { *x = AcnMessage_Aea_Envelope_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Aea_Envelope_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Aea_Envelope_Performative) ProtoMessage() {} func (x *AcnMessage_Aea_Envelope_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Aea_Envelope_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Aea_Envelope_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 5} } func (x *AcnMessage_Aea_Envelope_Performative) GetEnvelope() []byte { if x != nil { return x.Envelope } return nil } func (x *AcnMessage_Aea_Envelope_Performative) GetRecord() *AcnMessage_AgentRecord { if x != nil { return x.Record } return nil } type AcnMessage_Status_Performative struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Body *AcnMessage_StatusBody `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` } func (x *AcnMessage_Status_Performative) Reset() { *x = AcnMessage_Status_Performative{} if protoimpl.UnsafeEnabled { mi := &file_acn_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcnMessage_Status_Performative) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcnMessage_Status_Performative) ProtoMessage() {} func (x *AcnMessage_Status_Performative) ProtoReflect() protoreflect.Message { mi := &file_acn_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcnMessage_Status_Performative.ProtoReflect.Descriptor instead. func (*AcnMessage_Status_Performative) Descriptor() ([]byte, []int) { return file_acn_proto_rawDescGZIP(), []int{0, 6} } func (x *AcnMessage_Status_Performative) GetBody() *AcnMessage_StatusBody { if x != nil { return x.Body } return nil } var File_acn_proto protoreflect.FileDescriptor var file_acn_proto_rawDesc = []byte{ 0x0a, 0x09, 0x61, 0x63, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x22, 0xea, 0x0c, 0x0a, 0x0a, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x61, 0x65, 0x61, 0x5f, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x65, 0x61, 0x5f, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x65, 0x61, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x63, 0x0a, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x66, 0x0a, 0x0f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x84, 0x02, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x74, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x1a, 0x9e, 0x03, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x4c, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x22, 0xad, 0x02, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x49, 0x43, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x04, 0x12, 0x1d, 0x0a, 0x19, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0b, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x4f, 0x46, 0x10, 0x0c, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x44, 0x47, 0x45, 0x52, 0x10, 0x0d, 0x12, 0x1f, 0x0a, 0x1b, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x14, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, 0x15, 0x1a, 0x5b, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x42, 0x0a, 0x1b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0x62, 0x0a, 0x1c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x7b, 0x0a, 0x19, 0x41, 0x65, 0x61, 0x5f, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x06, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x1a, 0x54, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x65, 0x61, 0x2e, 0x61, 0x63, 0x6e, 0x2e, 0x76, 0x31, 0x5f, 0x30, 0x5f, 0x30, 0x2e, 0x41, 0x63, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x0e, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x76, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_acn_proto_rawDescOnce sync.Once file_acn_proto_rawDescData = file_acn_proto_rawDesc ) func file_acn_proto_rawDescGZIP() []byte { file_acn_proto_rawDescOnce.Do(func() { file_acn_proto_rawDescData = protoimpl.X.CompressGZIP(file_acn_proto_rawDescData) }) return file_acn_proto_rawDescData } var file_acn_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_acn_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_acn_proto_goTypes = []interface{}{ (AcnMessage_StatusBody_StatusCodeEnum)(0), // 0: aea.aea.acn.v1_0_0.AcnMessage.StatusBody.StatusCodeEnum (*AcnMessage)(nil), // 1: aea.aea.acn.v1_0_0.AcnMessage (*AcnMessage_AgentRecord)(nil), // 2: aea.aea.acn.v1_0_0.AcnMessage.AgentRecord (*AcnMessage_StatusBody)(nil), // 3: aea.aea.acn.v1_0_0.AcnMessage.StatusBody (*AcnMessage_Register_Performative)(nil), // 4: aea.aea.acn.v1_0_0.AcnMessage.Register_Performative (*AcnMessage_Lookup_Request_Performative)(nil), // 5: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_Performative (*AcnMessage_Lookup_Response_Performative)(nil), // 6: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative (*AcnMessage_Aea_Envelope_Performative)(nil), // 7: aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative (*AcnMessage_Status_Performative)(nil), // 8: aea.aea.acn.v1_0_0.AcnMessage.Status_Performative } var file_acn_proto_depIdxs = []int32{ 7, // 0: aea.aea.acn.v1_0_0.AcnMessage.aea_envelope:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative 5, // 1: aea.aea.acn.v1_0_0.AcnMessage.lookup_request:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_Performative 6, // 2: aea.aea.acn.v1_0_0.AcnMessage.lookup_response:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative 4, // 3: aea.aea.acn.v1_0_0.AcnMessage.register:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Register_Performative 8, // 4: aea.aea.acn.v1_0_0.AcnMessage.status:type_name -> aea.aea.acn.v1_0_0.AcnMessage.Status_Performative 0, // 5: aea.aea.acn.v1_0_0.AcnMessage.StatusBody.code:type_name -> aea.aea.acn.v1_0_0.AcnMessage.StatusBody.StatusCodeEnum 2, // 6: aea.aea.acn.v1_0_0.AcnMessage.Register_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 2, // 7: aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 2, // 8: aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative.record:type_name -> aea.aea.acn.v1_0_0.AcnMessage.AgentRecord 3, // 9: aea.aea.acn.v1_0_0.AcnMessage.Status_Performative.body:type_name -> aea.aea.acn.v1_0_0.AcnMessage.StatusBody 10, // [10:10] is the sub-list for method output_type 10, // [10:10] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_acn_proto_init() } func file_acn_proto_init() { if File_acn_proto != nil { return } if !protoimpl.UnsafeEnabled { file_acn_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_AgentRecord); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_StatusBody); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Register_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Lookup_Request_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Lookup_Response_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Aea_Envelope_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_acn_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcnMessage_Status_Performative); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_acn_proto_msgTypes[0].OneofWrappers = []interface{}{ (*AcnMessage_AeaEnvelope)(nil), (*AcnMessage_LookupRequest)(nil), (*AcnMessage_LookupResponse)(nil), (*AcnMessage_Register)(nil), (*AcnMessage_Status)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_acn_proto_rawDesc, NumEnums: 1, NumMessages: 8, NumExtensions: 0, NumServices: 0, }, GoTypes: file_acn_proto_goTypes, DependencyIndexes: file_acn_proto_depIdxs, EnumInfos: file_acn_proto_enumTypes, MessageInfos: file_acn_proto_msgTypes, }.Build() File_acn_proto = out.File file_acn_proto_rawDesc = nil file_acn_proto_goTypes = nil file_acn_proto_depIdxs = nil } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/protocols/acn/v1_0_0/acn.proto ================================================ syntax = "proto3"; package aea.aea.acn.v1_0_0; option go_package = "libp2p_node/protocols/acn/v1_0_0"; message AcnMessage{ // Custom Types message AgentRecord{ string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; } message StatusBody{ enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; } // Performatives and contents message Register_Performative{ AgentRecord record = 1; } message Lookup_Request_Performative{ string agent_address = 1; } message Lookup_Response_Performative{ AgentRecord record = 1; } message Aea_Envelope_Performative{ bytes envelope = 1; AgentRecord record = 2; } message Status_Performative{ StatusBody body = 1; } oneof performative{ Aea_Envelope_Performative aea_envelope = 5; Lookup_Request_Performative lookup_request = 6; Lookup_Response_Performative lookup_response = 7; Register_Performative register = 8; Status_Performative status = 9; } } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/protocols/acn/v1_0_0/acn.yaml ================================================ --- name: acn author: fetchai version: 1.0.0 description: The protocol used for envelope delivery on the ACN. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: aea/acn:1.0.0 speech_acts: register: record: ct:AgentRecord lookup_request: agent_address: pt:str lookup_response: record: ct:AgentRecord aea_envelope: envelope: pt:bytes record: ct:AgentRecord status: body: ct:StatusBody ... --- ct:AgentRecord: string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; ct:StatusBody: | enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; ... --- initiation: [register, lookup_request, aea_envelope] reply: register: [status] lookup_request: [lookup_response, status] aea_envelope: [status] status: [] lookup_response: [] termination: [status, lookup_response] roles: {node} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/utils/utils.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2019 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package utils import ( "bufio" "context" "crypto/sha256" "encoding/base64" "encoding/binary" "encoding/hex" "errors" "fmt" "io" "math" "net" "os" "sync" "time" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multihash" "github.com/rs/zerolog" "golang.org/x/crypto/ripemd160" // nolint:staticcheck "golang.org/x/crypto/sha3" host "github.com/libp2p/go-libp2p-core/host" peerstore "github.com/libp2p/go-libp2p-core/peerstore" btcec "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil/bech32" "github.com/ethereum/go-ethereum/common/hexutil" ethCrypto "github.com/ethereum/go-ethereum/crypto" proto "google.golang.org/protobuf/proto" "libp2p_node/aea" ) const ( maxMessageSizeDelegateConnection = 1024 * 1024 * 3 // 3Mb ) var ( addressFromPublicKeyTable = map[string]func(string) (string, error){ "fetchai": FetchAIAddressFromPublicKey, "cosmos": CosmosAddressFromPublicKey, "ethereum": EthereumAddressFromPublicKey, } verifyLedgerSignatureTable = map[string]func([]byte, string, string) (bool, error){ "fetchai": VerifyFetchAISignatureBTC, "cosmos": VerifyFetchAISignatureBTC, "ethereum": VerifyEthereumSignatureETH, } ) var loggerGlobalLevel zerolog.Level = zerolog.DebugLevel var logger zerolog.Logger = NewDefaultLogger() // SetLoggerLevel set utils logger level func SetLoggerLevel(lvl zerolog.Level) { logger = logger.Level(lvl) } func ignore(err error) { if err != nil { fmt.Println("IGNORED:", err) } } /* Logging */ func newConsoleLogger() zerolog.Logger { return zerolog.New(zerolog.ConsoleWriter{ Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339Nano, }) } // NewDefaultLogger basic zerolog console writer func NewDefaultLogger() zerolog.Logger { return newConsoleLogger(). With().Timestamp(). Logger().Level(loggerGlobalLevel) } // NewDefaultLoggerWithFields zerolog console writer func NewDefaultLoggerWithFields(fields map[string]string) zerolog.Logger { logger := newConsoleLogger(). With().Timestamp() for key, val := range fields { logger = logger.Str(key, val) } return logger.Logger().Level(loggerGlobalLevel) } /* Helpers */ // BootstrapConnect connect to `peers` at bootstrap // This code is borrowed from the go-ipfs bootstrap process func BootstrapConnect( ctx context.Context, ph host.Host, kaddht *dht.IpfsDHT, peers []peer.AddrInfo, ) error { if len(peers) < 1 { return errors.New("not enough bootstrap peers") } errs := make(chan error, len(peers)) var wg sync.WaitGroup for _, p := range peers { // performed asynchronously because when performed synchronously, if // one `Connect` call hangs, subsequent calls are more likely to // fail/abort due to an expiring context. // Also, performed asynchronously for dial speed. wg.Add(1) go func(p peer.AddrInfo) { defer wg.Done() //defer logger.Debug().Msgf("%s bootstrapDial %s %s", ctx, ph.ID(), p.ID) logger.Debug().Msgf("%s bootstrapping to %s", ph.ID(), p.ID) ph.Peerstore().AddAddrs(p.ID, p.Addrs, peerstore.PermanentAddrTTL) if err := ph.Connect(ctx, p); err != nil { //logger.Error(). // Str("err", err.Error()). // Msgf("failed to bootstrap with %v", p.ID) errs <- err return } logger.Debug().Msgf("bootstrapped with %v", p.ID) }(p) } wg.Wait() // our failure condition is when no connection attempt succeeded. // So drain the errs channel, counting the results. close(errs) count := 0 var err error for err = range errs { if err != nil { count++ } } if count == len(peers) { return errors.New("failed to bootstrap: " + err.Error()) } // workaround: to avoid getting `failed to find any peer in table` // when calling dht.Provide (happens occasionally) logger.Debug().Msg("waiting for bootstrap peers to be added to dht routing table...") for _, peer := range peers { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() for kaddht.RoutingTable().Find(peer.ID) == "" { select { case <-ctx.Done(): return errors.New( "timeout: entry peer haven't been added to DHT routing table " + peer.ID.Pretty(), ) case <-time.After(time.Millisecond * 5): } } } return nil } // ComputeCID compute content id for ipfsDHT func ComputeCID(addr string) (cid.Cid, error) { pref := cid.Prefix{ Version: 0, Codec: cid.Raw, MhType: multihash.SHA2_256, MhLength: -1, // default length } // And then feed it some data c, err := pref.Sum([]byte(addr)) if err != nil { return cid.Cid{}, err } return c, nil } // GetPeersAddrInfo Parse multiaddresses and convert them to peer.AddrInfo func GetPeersAddrInfo(peers []string) ([]peer.AddrInfo, error) { pinfos := make([]peer.AddrInfo, len(peers)) for i, addr := range peers { maddr := multiaddr.StringCast(addr) p, err := peer.AddrInfoFromP2pAddr(maddr) if err != nil { return pinfos, err } pinfos[i] = *p } return pinfos, nil } /* FetchAI Crypto Helpers */ // PubKeyFromFetchAIPublicKey create libp2p public key from fetchai hex encoded secp256k1 key func PubKeyFromFetchAIPublicKey(publicKey string) (crypto.PubKey, error) { hexBytes, _ := hex.DecodeString(publicKey) return crypto.UnmarshalSecp256k1PublicKey(hexBytes) } // FetchAIPublicKeyFromPubKey return FetchAI's format serialized public key func FetchAIPublicKeyFromPubKey(publicKey crypto.PubKey) (string, error) { raw, err := publicKey.Raw() if err != nil { return "", err } return hex.EncodeToString(raw), nil } // BTCPubKeyFromFetchAIPublicKey from public key string func BTCPubKeyFromFetchAIPublicKey(publicKey string) (*btcec.PublicKey, error) { pbkBytes, err := hex.DecodeString(publicKey) if err != nil { return nil, err } pbk, err := btcec.ParsePubKey(pbkBytes, btcec.S256()) return pbk, err } // BTCPubKeyFromEthereumPublicKey create libp2p public key from ethereum uncompressed // hex encoded secp256k1 key func BTCPubKeyFromEthereumPublicKey(publicKey string) (*btcec.PublicKey, error) { return BTCPubKeyFromUncompressedHex(publicKey[2:]) } // ConvertStrEncodedSignatureToDER to convert signature to DER format // References: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L258 // - https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L47 func ConvertStrEncodedSignatureToDER(signature []byte) []byte { rb := signature[:len(signature)/2] sb := signature[len(signature)/2:] length := 6 + len(rb) + len(sb) sigDER := make([]byte, length) sigDER[0] = 0x30 sigDER[1] = byte(length - 2) sigDER[2] = 0x02 sigDER[3] = byte(len(rb)) offset := copy(sigDER[4:], rb) + 4 sigDER[offset] = 0x02 sigDER[offset+1] = byte(len(sb)) copy(sigDER[offset+2:], sb) return sigDER } // ConvertDEREncodedSignatureToStr Convert signatue from der format to string // References: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L258 // - https://github.com/btcsuite/btcd/blob/master/btcec/signature.go#L47 func ConvertDEREncodedSignatureToStr(signature []byte) ([]byte, error) { sig, err := btcec.ParseDERSignature(signature, btcec.S256()) if err != nil { return []byte{}, err } return append(sig.R.Bytes(), sig.S.Bytes()...), nil } // ParseFetchAISignature create btcec Signature from base64 formated, string (not DER) encoded RFC6979 signature func ParseFetchAISignature(signature string) (*btcec.Signature, error) { // First convert the signature into a DER one sigBytes, err := base64.StdEncoding.DecodeString(signature) if err != nil { return nil, err } sigDER := ConvertStrEncodedSignatureToDER(sigBytes) // Parse sigBTC, err := btcec.ParseSignature(sigDER, btcec.S256()) return sigBTC, err } // VerifyLedgerSignature verify signature of message using public key for supported ledgers func VerifyLedgerSignature( ledgerID string, message []byte, signature string, pubKey string, ) (bool, error) { verifySignature, found := verifyLedgerSignatureTable[ledgerID] if found { return verifySignature(message, signature, pubKey) } return false, errors.New("unsupported ledger") } // VerifyFetchAISignatureBTC verify the RFC6967 string-encoded signature of message using FetchAI public key func VerifyFetchAISignatureBTC(message []byte, signature string, pubkey string) (bool, error) { // construct verifying key verifyKey, err := BTCPubKeyFromFetchAIPublicKey(pubkey) if err != nil { return false, err } // construct signature signatureBTC, err := ParseFetchAISignature(signature) if err != nil { return false, err } // verify signature messageHash := sha256.New() _, err = messageHash.Write([]byte(message)) if err != nil { return false, err } return signatureBTC.Verify(messageHash.Sum(nil), verifyKey), nil } // VerifyFetchAISignatureLibp2p verify RFC6967 string-encoded signature of message using FetchAI public key func VerifyFetchAISignatureLibp2p(message []byte, signature string, pubkey string) (bool, error) { // construct verifying key verifyKey, err := PubKeyFromFetchAIPublicKey(pubkey) if err != nil { return false, err } // Convert signature into DER encoding sigBytes, err := base64.StdEncoding.DecodeString(signature) if err != nil { return false, err } sigDER := ConvertStrEncodedSignatureToDER(sigBytes) // verify signature return verifyKey.Verify(message, sigDER) } // SignFetchAI signs message with private key func SignFetchAI(message []byte, privKey string) (string, error) { signingKey, _, err := KeyPairFromFetchAIKey(privKey) if err != nil { return "", err } signature, err := signingKey.Sign(message) if err != nil { return "", err } strSignature, err := ConvertDEREncodedSignatureToStr(signature) if err != nil { return "", err } encodedSignature := base64.StdEncoding.EncodeToString(strSignature) return encodedSignature, nil } func signHashETH(data []byte) []byte { msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) return ethCrypto.Keccak256([]byte(msg)) } // RecoverAddressFromEthereumSignature verify the signature and returns the address of the signer // references: // - https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 // - https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L404 func RecoverAddressFromEthereumSignature(message []byte, signature string) (string, error) { // prepare signature sigBytes, err := hexutil.Decode(signature) if err != nil { return "", err } if sigBytes[64] != 27 && sigBytes[64] != 28 { return "", errors.New("invalid Ethereum signature (V is not 27 or 28)") } sigBytes[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 // recover verify key recoveredPubKey, err := ethCrypto.SigToPub(signHashETH(message), sigBytes) if err != nil { return "", err } return ethCrypto.PubkeyToAddress(*recoveredPubKey).Hex(), nil } // VerifyEthereumSignatureETH verify ethereum signature using ethereum public key func VerifyEthereumSignatureETH(message []byte, signature string, pubkey string) (bool, error) { // get expected signer address expectedAddress, err := EthereumAddressFromPublicKey(pubkey) if err != nil { return false, err } // recover signer address recoveredAddress, err := RecoverAddressFromEthereumSignature(message, signature) if err != nil { return false, err } if recoveredAddress != expectedAddress { return false, errors.New("recovered and expected addresses don't match") } return true, nil } // KeyPairFromFetchAIKey key pair from hex encoded secp256k1 private key func KeyPairFromFetchAIKey(key string) (crypto.PrivKey, crypto.PubKey, error) { pkBytes, err := hex.DecodeString(key) if err != nil { return nil, nil, err } btcPrivateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes) prvKey, pubKey, err := crypto.KeyPairFromStdKey(btcPrivateKey) if err != nil { return nil, nil, err } return prvKey, pubKey, nil } // AgentAddressFromPublicKey get wallet address from public key associated with ledgerId // format from: https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L120 func AgentAddressFromPublicKey(ledgerID string, publicKey string) (string, error) { if addressFromPublicKey, found := addressFromPublicKeyTable[ledgerID]; found { return addressFromPublicKey(publicKey) } return "", errors.New("Unsupported ledger " + ledgerID) } // FetchAIAddressFromPublicKey get wallet address from hex encoded secp256k1 public key func FetchAIAddressFromPublicKey(publicKey string) (string, error) { return cosmosAddressFromPublicKeyWithPrefix("fetch", publicKey) } // CosmosAddressFromPublicKey get wallet address from hex encoded secp256k1 public key func CosmosAddressFromPublicKey(publicKey string) (string, error) { return cosmosAddressFromPublicKeyWithPrefix("cosmos", publicKey) } // cosmosAddressFromPublicKeyWithPrefix get wallet address from hex encoded secp256k1 public key // format from: https://github.com/fetchai/agents-aea/blob/main/aea/crypto/cosmos.py#L120 func cosmosAddressFromPublicKeyWithPrefix(prefix string, publicKey string) (string, error) { var addr string var err error hexBytes, err := hex.DecodeString(publicKey) if err != nil { return addr, err } hash := sha256.New() _, err = hash.Write(hexBytes) if err != nil { return addr, err } sha256Hash := hash.Sum(nil) hash = ripemd160.New() _, err = hash.Write(sha256Hash) if err != nil { return addr, err } ripemd160Hash := hash.Sum(nil) fiveBitsChar, err := bech32.ConvertBits(ripemd160Hash, 8, 5, true) if err != nil { return addr, err } addr, err = bech32.Encode(prefix, fiveBitsChar) return addr, err } // EthereumAddressFromPublicKey get wallet address from hex encoded secp256k1 public key // references: // - https://github.com/fetchai/agents-aea/blob/main/aea/crypto/ethereum.py#L330 // - https://github.com/ethereum/go-ethereum/blob/master/crypto/crypto.go#L263 func EthereumAddressFromPublicKey(publicKey string) (string, error) { var addr string var err error hexBytes, err := hex.DecodeString(publicKey[2:]) if err != nil { return addr, err } hash := sha3.NewLegacyKeccak256() _, err = hash.Write(hexBytes) if err != nil { return addr, err } sha3KeccakHash := hash.Sum(nil) return encodeChecksumEIP55(sha3KeccakHash[12:]), nil } // encodeChecksumEIP55 EIP55-compliant hex string representation of the address // source: https://github.com/ethereum/go-ethereum/blob/master/common/types.go#L210 // reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md func encodeChecksumEIP55(address []byte) string { unchecksummed := hex.EncodeToString(address[:]) sha := sha3.NewLegacyKeccak256() _, err := sha.Write([]byte(unchecksummed)) ignore(err) hash := sha.Sum(nil) result := []byte(unchecksummed) for i := 0; i < len(result); i++ { hashByte := hash[i/2] if i%2 == 0 { hashByte = hashByte >> 4 } else { hashByte &= 0xf } if result[i] > '9' && hashByte > 7 { result[i] -= 32 } } return "0x" + string(result) } // IDFromFetchAIPublicKey Get PeeID (multihash) from fetchai public key func IDFromFetchAIPublicKey(publicKey string) (peer.ID, error) { b, err := hex.DecodeString(publicKey) if err != nil { return "", err } pubKey, err := btcec.ParsePubKey(b, btcec.S256()) if err != nil { return "", err } multihash, err := peer.IDFromPublicKey((*crypto.Secp256k1PublicKey)(pubKey)) if err != nil { return "", err } return multihash, nil } // BTCPubKeyFromUncompressedHex get public key from secp256k1 hex encoded uncompressed representation func BTCPubKeyFromUncompressedHex(publicKey string) (*btcec.PublicKey, error) { b, err := hex.DecodeString(publicKey) if err != nil { return nil, err } pubBytes := make([]byte, 0, btcec.PubKeyBytesLenUncompressed) pubBytes = append(pubBytes, 0x4) // btcec.pubkeyUncompressed pubBytes = append(pubBytes, b...) return btcec.ParsePubKey(pubBytes, btcec.S256()) } // IDFromFetchAIPublicKeyUncompressed Get PeeID (multihash) from fetchai public key func IDFromFetchAIPublicKeyUncompressed(publicKey string) (peer.ID, error) { pubKey, err := BTCPubKeyFromUncompressedHex(publicKey) if err != nil { return "", err } multihash, err := peer.IDFromPublicKey((*crypto.Secp256k1PublicKey)(pubKey)) if err != nil { return "", err } return multihash, nil } // FetchAIPublicKeyFromFetchAIPrivateKey get fetchai public key from fetchai private key func FetchAIPublicKeyFromFetchAIPrivateKey(privateKey string) (string, error) { pkBytes, err := hex.DecodeString(privateKey) if err != nil { return "", err } _, btcPublicKey := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes) return hex.EncodeToString(btcPublicKey.SerializeCompressed()), nil } /* Utils */ // WriteBytesConn send bytes to `conn` func WriteBytesConn(conn net.Conn, data []byte) error { if len(data) > math.MaxInt32 { logger.Error().Msg("data size too large") return errors.New("data size too large") } if len(data) == 0 { logger.Error().Msg("No data to write") return nil } size := uint32(len(data)) buf := make([]byte, 4, 4+size) binary.BigEndian.PutUint32(buf, size) buf = append(buf, data...) _, err := conn.Write(buf) return err } // ReadBytesConn receive bytes from `conn` func ReadBytesConn(conn net.Conn) ([]byte, error) { buf := make([]byte, 4) _, err := conn.Read(buf) if err != nil { return buf, err } size := binary.BigEndian.Uint32(buf) if size > maxMessageSizeDelegateConnection { return nil, errors.New("expected message size larger than maximum allowed") } buf = make([]byte, size) _, err = conn.Read(buf) return buf, err } // WriteEnvelopeConn send envelope to `conn` func WriteEnvelopeConn(conn net.Conn, envelope *aea.Envelope) error { data, err := proto.Marshal(envelope) if err != nil { return err } return WriteBytesConn(conn, data) } // ReadEnvelopeConn receive envelope from `conn` func ReadEnvelopeConn(conn net.Conn) (*aea.Envelope, error) { envelope := &aea.Envelope{} data, err := ReadBytesConn(conn) if err != nil { return envelope, err } err = proto.Unmarshal(data, envelope) return envelope, err } // ReadBytes from a network stream func ReadBytes(s network.Stream) ([]byte, error) { if s == nil { panic("CRITICAL can not write to nil stream") } rstream := bufio.NewReader(s) buf := make([]byte, 4) _, err := io.ReadFull(rstream, buf) if err != nil { logger.Error(). Str("err", err.Error()). Msg("while receiving size") return buf, err } size := binary.BigEndian.Uint32(buf) if size > maxMessageSizeDelegateConnection { return nil, errors.New("expected message size larger than maximum allowed") } //logger.Debug().Msgf("expecting %d", size) buf = make([]byte, size) _, err = io.ReadFull(rstream, buf) return buf, err } // WriteBytes to a network stream func WriteBytes(s network.Stream, data []byte) error { if len(data) > math.MaxInt32 { logger.Error().Msg("data size too large") return errors.New("data size too large") } if len(data) == 0 { logger.Error().Msg("No data to write") return nil } if s == nil { panic("CRITICAL, can not write to nil stream") } wstream := bufio.NewWriter(s) size := uint32(len(data)) buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, size) _, err := wstream.Write(buf) if err != nil { logger.Error(). Str("err", err.Error()). Msg("while sending size") return err } //logger.Debug().Msgf("writing %d", len(data)) _, err = wstream.Write(data) if err != nil { logger.Error(). Str("err", err.Error()). Msg("Error on data write") return err } if s == nil { panic("CRITICAL, can not flush nil stream") } err = wstream.Flush() return err } type ConnPipe struct { Conn net.Conn } func (conPipe ConnPipe) Connect() error { return nil } func (conPipe ConnPipe) Read() ([]byte, error) { return ReadBytesConn(conPipe.Conn) } func (conPipe ConnPipe) Write(data []byte) error { return WriteBytesConn(conPipe.Conn, data) } func (conPipe ConnPipe) Close() error { return nil } type StreamPipe struct { Stream network.Stream } func (streamPipe StreamPipe) Connect() error { return nil } func (streamPipe StreamPipe) Read() ([]byte, error) { return ReadBytes(streamPipe.Stream) } func (streamPipe StreamPipe) Write(data []byte) error { return WriteBytes(streamPipe.Stream, data) } func (streamPipe StreamPipe) Close() error { return nil } ================================================ FILE: packages/fetchai/connections/p2p_libp2p/libp2p_node/utils/utils_test.go ================================================ /* -*- coding: utf-8 -*- * ------------------------------------------------------------------------------ * * Copyright 2018-2021 Fetch.AI Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------------------ */ package utils import ( "bytes" "context" "encoding/json" "errors" "libp2p_node/aea" mocks "libp2p_node/mocks" "net" "reflect" "testing" "bou.ke/monkey" gomock "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p-core/peer" dht "github.com/libp2p/go-libp2p-kad-dht" kb "github.com/libp2p/go-libp2p-kbucket" ma "github.com/multiformats/go-multiaddr" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) // Crypto operations func TestEthereumCrypto(t *testing.T) { //privateKey := "0xb60fe8027fb82f1a1bd6b8e66d4400f858989a2c67428a4e7f589441700339b0" publicKey := "0xf753e5a9e2368e97f4db869a0d956d3ffb64672d6392670572906c786b5712ada13b6bff882951b3ba3dd65bdacc915c2b532efc3f183aa44657205c6c337225" address := "0xb8d8c62d4a1999b7aea0aebBD5020244a4a9bAD8" publicKeySignature := "0x304c2ba4ae7fa71295bfc2920b9c1268d574d65531f1f4d2117fc1439a45310c37ab75085a9df2a4169a4d47982b330a4387b1ded0c8881b030629db30bbaf3a1c" addFromPublicKey, err := EthereumAddressFromPublicKey(publicKey) if err != nil || addFromPublicKey != address { t.Error( "Error when computing address from public key or address and public key don't match", ) } _, err = BTCPubKeyFromEthereumPublicKey(publicKey) if err != nil { t.Errorf("While building BTC public key from string: %s", err.Error()) } /* ethSig, err := secp256k1.Sign(hashedPublicKey, hexutil.MustDecode(privateKey)) if err != nil { t.Error(err.Error()) } println(hexutil.Encode(ethSig)) hash := sha3.NewLegacyKeccak256() _, err = hash.Write([]byte(publicKey)) if err != nil { t.Error(err.Error()) } sha3KeccakHash := hash.Sum(nil) */ valid, err := VerifyEthereumSignatureETH([]byte(publicKey), publicKeySignature, publicKey) if err != nil { t.Error(err.Error()) } if !valid { t.Errorf("Signer address don't match %s", addFromPublicKey) } } func TestFetchAICrypto(t *testing.T) { publicKey := "02358e3e42a6ba15cf6b2ba6eb05f02b8893acf82b316d7dd9cda702b0892b8c71" address := "fetch19dq2mkcpp6x0aypxt9c9gz6n4fqvax0x9a7t5r" peerPublicKey := "027af21aff853b9d9589867ea142b0a60a9611fc8e1fae04c2f7144113fa4e938e" pySigStrCanonize := "N/GOa7/m3HU8/gpLJ88VCQ6vXsdrfiiYcqnNtF+c2N9VG9ZIiycykN4hdbpbOCGrChMYZQA3G1GpozsShrUBgg==" addressFromPublicKey, _ := FetchAIAddressFromPublicKey(publicKey) if address != addressFromPublicKey { t.Error("[ERR] Addresses don't match") } else { t.Log("[OK] Agent address matches its public key") } valid, err := VerifyFetchAISignatureBTC( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using BTC don't match %s", err.Error()) } valid, err = VerifyFetchAISignatureLibp2p( []byte(peerPublicKey), pySigStrCanonize, publicKey, ) if !valid { t.Errorf("Signature using LPP don't match %s", err.Error()) } } func TestSetLoggerLevel(t *testing.T) { assert.Equal(t, logger.GetLevel(), zerolog.Level(0), "Initial log level is not 0") lvl := zerolog.InfoLevel SetLoggerLevel(lvl) assert.Equal( t, logger.GetLevel(), lvl, "Waited for logger level %d but got %d", lvl, logger.GetLevel(), ) } func Example_ignore() { ignore(errors.New("Test")) // Output: IGNORED: Test } func TestNewDefaultLoggerWithFields(t *testing.T) { fields := map[string]string{ "test_field": "test_value", } var logBuffer bytes.Buffer logger := NewDefaultLoggerWithFields(fields).Output(&logBuffer) logger.Info().Msg("test") var jsonResult map[string]interface{} err := json.Unmarshal(logBuffer.Bytes(), &jsonResult) assert.Equal(t, nil, err) assert.Equal(t, jsonResult["test_field"], "test_value") } func TestComputeCID(t *testing.T) { address := "fetch19dq2mkcpp6x0aypxt9c9gz6n4fqvax0x9a7t5r" cid, err := ComputeCID(address) assert.Equal(t, nil, err) assert.Equal(t, "QmZ6ryKyS9rSnesX8YnFLAmFwFuRMdHpE7pQ2V6SjXTbqM", cid.String()) } func TestWriteBytes(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockStream := mocks.NewMockStream(mockCtrl) mockStream.EXPECT().Write([]byte{0, 0, 0, 5, 104, 101, 108, 108, 111}).Return(9, nil).Times(1) err := WriteBytes(mockStream, []byte("hello")) assert.Equal(t, nil, err) mockStream.EXPECT(). Write([]byte{0, 0, 0, 4, 104, 101, 108, 108}). Return(8, errors.New("oops")). Times(1) err = WriteBytes(mockStream, []byte("hell")) assert.NotEqual(t, err, nil) } func TestReadBytesConn(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockConn := mocks.NewMockConn(mockCtrl) mockConn.EXPECT().Read(gomock.Any()).Return(4, nil).Times(2) buf, err := ReadBytesConn(mockConn) assert.Equal(t, nil, err) assert.Equal(t, "", string(buf)) } func TestWriteBytesConn(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockConn := mocks.NewMockConn(mockCtrl) mockConn.EXPECT().Write(gomock.Any()).Return(0, nil).Times(1) err := WriteBytesConn(mockConn, []byte("ABC")) assert.Equal(t, nil, err) } func TestReadWriteEnvelopeFromConnection(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() defer monkey.UnpatchAll() address := "0xb8d8c62d4a1999b7aea0aebBD5020244a4a9bAD8" buffer := bytes.NewBuffer([]byte{}) mockConn := mocks.NewMockConn(mockCtrl) t.Run("TestWriteEnvelope", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(mockConn), "Write", func(_ *mocks.MockConn, b []byte) (int, error) { buffer.Write(b) return 0, nil }, ) err := WriteEnvelopeConn(mockConn, &aea.Envelope{ To: address, Sender: address, }) assert.Equal(t, nil, err) assert.NotEqual(t, 0, buffer) }) t.Run("TestReadEnvelope", func(t *testing.T) { monkey.Patch(ReadBytesConn, func(conn net.Conn) ([]byte, error) { return buffer.Bytes()[4:], nil }) env, err := ReadEnvelopeConn(mockConn) assert.Equal(t, nil, err) assert.Equal(t, address, env.To) }) } func TestGetPeersAddrInfo(t *testing.T) { addrs, err := GetPeersAddrInfo( []string{ "/dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW", }, ) assert.Equal(t, nil, err) assert.Equal(t, 1, len(addrs)) } func TestFetchAIPublicKeyFromPubKey(t *testing.T) { //(publicKey crypto.PubKey) (string, error) { _, pubKey, err := KeyPairFromFetchAIKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) key, err := FetchAIPublicKeyFromPubKey(pubKey) assert.Equal(t, nil, err) assert.Equal(t, "03b7e977f498dce004e2614764ff576e17cc6691135497e7bcb5d3441e816ba9e1", key) } func TestIDFromFetchAIPublicKey(t *testing.T) { _, pubKey, err := KeyPairFromFetchAIKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) key, err := FetchAIPublicKeyFromPubKey(pubKey) assert.Equal(t, nil, err) peerID, err := IDFromFetchAIPublicKey(key) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(peerID)) } func TestAgentAddressFromPublicKey(t *testing.T) { address, err := AgentAddressFromPublicKey( "fetchai", "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(address)) } func TestCosmosAddressFromPublicKey(t *testing.T) { address, err := CosmosAddressFromPublicKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(address)) } func TestFetchAIPublicKeyFromFetchAIPrivateKey(t *testing.T) { key, err := FetchAIPublicKeyFromFetchAIPrivateKey( "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc", ) assert.Equal(t, nil, err) assert.Equal(t, "03b7e977f498dce004e2614764ff576e17cc6691135497e7bcb5d3441e816ba9e1", key) } func TestIDFromFetchAIPublicKeyUncompressed(t *testing.T) { //bad pub key _, err := IDFromFetchAIPublicKeyUncompressed("some") assert.NotEqual(t, nil, err) // good pub key id, err := IDFromFetchAIPublicKeyUncompressed( "50863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6", ) assert.Equal(t, nil, err) assert.Equal( t, peer.ID( "\x00%\b\x02\x12!\x02P\x86:\xd6J\x87\xae\x8a/\xe8<\x1a\xf1\xa8@<\xb5?S\xe4\x86\xd8Q\x1d\xad\x8a\x04\x88~[#R", ), id, ) } func TestSignFetchAI(t *testing.T) { privKey := "3e7a1f43b2d8a4b9f63a2ffeb1d597f971a8db7ffd95453173268b453106cadc" message := []byte("somebytes") _, pubKey, err := KeyPairFromFetchAIKey(privKey) assert.Equal(t, nil, err) fetchPubKey, err := FetchAIPublicKeyFromPubKey(pubKey) assert.Equal(t, nil, err) signature, err := SignFetchAI(message, privKey) assert.Equal(t, nil, err) assert.NotEqual(t, 0, len(signature)) isValid, err := VerifyLedgerSignature("fetchai", message, signature, fetchPubKey) assert.Equal(t, nil, err) assert.Equal(t, true, isValid) } func TestBootstrapConnect(t *testing.T) { ctx := context.Background() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() defer monkey.UnpatchAll() var ipfsdht *dht.IpfsDHT var routingTable *kb.RoutingTable mockPeerstore := mocks.NewMockPeerstore(mockCtrl) peers := make([]peer.AddrInfo, 2) var addrs []ma.Multiaddr peers[0] = peer.AddrInfo{ID: peer.ID("peer1"), Addrs: addrs} peers[1] = peer.AddrInfo{ID: peer.ID("peer2"), Addrs: addrs} mockHost := mocks.NewMockHost(mockCtrl) mockHost.EXPECT().ID().Return(peer.ID("host_id")).Times(2) mockHost.EXPECT().Peerstore().Return(mockPeerstore).Times(2) mockHost.EXPECT().Connect(gomock.Any(), gomock.Any()).Return(nil).Times(2) mockPeerstore.EXPECT().AddAddrs(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(2) t.Run("TestOk", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(routingTable), "Find", func(_ *kb.RoutingTable, _ peer.ID) peer.ID { return peer.ID("som peer") }, ) monkey.PatchInstanceMethod( reflect.TypeOf(ipfsdht), "RoutingTable", func(_ *dht.IpfsDHT) *kb.RoutingTable { return routingTable }, ) err := BootstrapConnect(ctx, mockHost, ipfsdht, peers) assert.Equal(t, nil, err) }) mockHost = mocks.NewMockHost(mockCtrl) mockHost.EXPECT().ID().Return(peer.ID("host_id")).Times(2) mockHost.EXPECT().Peerstore().Return(mockPeerstore).Times(2) mockHost.EXPECT().Connect(gomock.Any(), gomock.Any()).Return(nil).Times(2) mockPeerstore.EXPECT().AddAddrs(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(2) t.Run("Test_PeersNotAdded", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(routingTable), "Find", func(_ *kb.RoutingTable, _ peer.ID) peer.ID { return peer.ID("") }, ) monkey.PatchInstanceMethod( reflect.TypeOf(ipfsdht), "RoutingTable", func(_ *dht.IpfsDHT) *kb.RoutingTable { return routingTable }, ) err := BootstrapConnect(ctx, mockHost, ipfsdht, peers) assert.NotEqual(t, nil, err) assert.Contains(t, err.Error(), "timeout: entry peer haven't been added to DHT") }) mockHost = mocks.NewMockHost(mockCtrl) mockHost.EXPECT().ID().Return(peer.ID("host_id")).Times(2) mockHost.EXPECT().Peerstore().Return(mockPeerstore).Times(2) mockHost.EXPECT().Connect(gomock.Any(), gomock.Any()).Return(errors.New("some error")).Times(2) mockPeerstore.EXPECT().AddAddrs(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(2) t.Run("Test_PeersNotConnected", func(t *testing.T) { monkey.PatchInstanceMethod( reflect.TypeOf(routingTable), "Find", func(_ *kb.RoutingTable, _ peer.ID) peer.ID { return peer.ID("") }, ) monkey.PatchInstanceMethod( reflect.TypeOf(ipfsdht), "RoutingTable", func(_ *dht.IpfsDHT) *kb.RoutingTable { return routingTable }, ) err := BootstrapConnect(ctx, mockHost, ipfsdht, peers) assert.NotEqual(t, nil, err) assert.Equal(t, "failed to bootstrap: some error", err.Error()) }) } ================================================ FILE: packages/fetchai/connections/p2p_libp2p_client/README.md ================================================ # P2P Libp2p Client Connection A lightweight TCP connection to a libp2p DHT node. It allows for using the DHT without having to deploy a node by delegating its communication traffic to an already running DHT node with delegate service enabled. ## Usage First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p_client:0.20.6`. Next, ensure that the connection is properly configured by setting: - `nodes` to a list of `uri`s, connection will choose the delegate randomly - `uri` to the public IP address and port number of the delegate service of a running DHT node, in format `${ip|dns}:${port}` ================================================ FILE: packages/fetchai/connections/p2p_libp2p_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the libp2p client connection.""" ================================================ FILE: packages/fetchai/connections/p2p_libp2p_client/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the libp2p client connection.""" import asyncio import contextlib import hashlib import logging import random import ssl from asyncio import CancelledError from asyncio.events import AbstractEventLoop from asyncio.streams import StreamWriter from pathlib import Path from typing import Any, Dict, List, Optional from asn1crypto import x509 # type: ignore from ecdsa.curves import SECP256k1 from ecdsa.keys import BadSignatureError, VerifyingKey from ecdsa.util import sigdecode_der from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER from aea.connections.base import Connection, ConnectionStates from aea.crypto.registries import make_crypto from aea.exceptions import enforce from aea.helpers.acn.agent_record import AgentRecord from aea.helpers.acn.uri import Uri from aea.helpers.pipe import IPCChannelClient, TCPSocketChannelClient, TCPSocketProtocol from aea.mail.base import Envelope from packages.fetchai.protocols.acn import acn_pb2 from packages.fetchai.protocols.acn.message import AcnMessage try: from asyncio.streams import IncompleteReadError # pylint: disable=ungrouped-imports except ImportError: # pragma: nocover from asyncio import IncompleteReadError # pylint: disable=ungrouped-imports _default_logger = logging.getLogger( "aea.packages.fetchai.connections.p2p_libp2p_client" ) PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p_client:0.20.6") SUPPORTED_LEDGER_IDS = ["fetchai", "cosmos", "ethereum"] POR_DEFAULT_SERVICE_ID = "acn" ACN_CURRENT_VERSION = "0.1.0" class NodeClient: """Client to communicate with node using ipc channel(pipe).""" ACN_ACK_TIMEOUT = 5.0 def __init__(self, pipe: IPCChannelClient, node_por: AgentRecord) -> None: """Set node client with pipe.""" self.pipe = pipe self._wait_status: Optional[asyncio.Future] = None self.agent_record = node_por async def wait_for_status(self) -> Any: """Get status.""" if self._wait_status is None: # pragma: nocover raise ValueError("waiter for status not set!") return await asyncio.wait_for(self._wait_status, timeout=self.ACN_ACK_TIMEOUT) @staticmethod def make_acn_envelope_message(envelope: Envelope) -> bytes: """Make acn message with envelope in.""" acn_msg = acn_pb2.AcnMessage() performative = acn_pb2.AcnMessage.Aea_Envelope_Performative() # type: ignore performative.envelope = envelope.encode() acn_msg.aea_envelope.CopyFrom(performative) # pylint: disable=no-member buf = acn_msg.SerializeToString() return buf async def write_acn_status_ok(self) -> None: """Send acn status ok.""" acn_msg = acn_pb2.AcnMessage() performative = acn_pb2.AcnMessage.Status_Performative() # type: ignore status = AcnMessage.StatusBody( status_code=AcnMessage.StatusBody.StatusCode.SUCCESS, msgs=[] ) AcnMessage.StatusBody.encode( performative.body, status # pylint: disable=no-member ) acn_msg.status.CopyFrom(performative) # pylint: disable=no-member buf = acn_msg.SerializeToString() await self._write(buf) async def write_acn_status_error( self, msg: str, status_code: AcnMessage.StatusBody.StatusCode = AcnMessage.StatusBody.StatusCode.ERROR_GENERIC, # type: ignore ) -> None: """Send acn status error generic.""" acn_msg = acn_pb2.AcnMessage() performative = acn_pb2.AcnMessage.Status_Performative() # type: ignore status = AcnMessage.StatusBody(status_code=status_code, msgs=[msg]) AcnMessage.StatusBody.encode( performative.body, status # pylint: disable=no-member ) acn_msg.status.CopyFrom(performative) # pylint: disable=no-member buf = acn_msg.SerializeToString() await self._write(buf) async def connect(self) -> bool: """Connect to node with pipe.""" return await self.pipe.connect() async def send_envelope(self, envelope: Envelope) -> None: """Send envelope to node.""" self._wait_status = asyncio.Future() buf = self.make_acn_envelope_message(envelope) await self._write(buf) try: status = await self.wait_for_status() if status.code != int(AcnMessage.StatusBody.StatusCode.SUCCESS): # type: ignore # pylint: disable=no-member raise ValueError( # pragma: nocover f"failed to send envelope. got error confirmation: {status}" ) except asyncio.TimeoutError: # pragma: nocover if not self._wait_status.done(): # pragma: nocover self._wait_status.set_exception(Exception("Timeout")) await asyncio.sleep(0) raise ValueError("acn status await timeout!") finally: self._wait_status = None def make_agent_record(self) -> AcnMessage.AgentRecord: # type: ignore """Make acn agent record.""" agent_record = AcnMessage.AgentRecord( address=self.agent_record.address, public_key=self.agent_record.public_key, peer_public_key=self.agent_record.representative_public_key, signature=self.agent_record.signature, service_id=POR_DEFAULT_SERVICE_ID, ledger_id=self.agent_record.ledger_id, ) return agent_record async def read_envelope(self) -> Optional[Envelope]: """Read envelope from the node.""" while True: buf = await self._read() if not buf: return None try: acn_msg = acn_pb2.AcnMessage() acn_msg.ParseFromString(buf) except Exception as e: # pragma: nocover await self.write_acn_status_error( f"Failed to parse acn message {e}", status_code=AcnMessage.StatusBody.StatusCode.ERROR_DECODE, ) raise ValueError(f"Error parsing acn message: {e}") from e performative = acn_msg.WhichOneof("performative") if performative == "aea_envelope": # pragma: nocover aea_envelope = acn_msg.aea_envelope # pylint: disable=no-member try: envelope = Envelope.decode(aea_envelope.envelope) await self.write_acn_status_ok() return envelope except Exception as e: await self.write_acn_status_error( f"Failed to decode envelope: {e}", status_code=AcnMessage.StatusBody.StatusCode.ERROR_DECODE, ) raise elif performative == "status": if self._wait_status is not None: self._wait_status.set_result( acn_msg.status.body # pylint: disable=no-member ) else: # pragma: nocover await self.write_acn_status_error( f"Bad acn message {performative}", status_code=AcnMessage.StatusBody.StatusCode.ERROR_UNEXPECTED_PAYLOAD, ) async def _write(self, data: bytes) -> None: """ Write to the writer stream. :param data: data to write to stream """ await self.pipe.write(data) async def _read(self) -> Optional[bytes]: """ Read from the reader stream. :return: bytes """ return await self.pipe.read() async def register( self, ) -> None: """Register agent on the remote node.""" agent_record = self.make_agent_record() acn_msg = acn_pb2.AcnMessage() performative = acn_pb2.AcnMessage.Register_Performative() # type: ignore AcnMessage.AgentRecord.encode( performative.record, agent_record # pylint: disable=no-member ) acn_msg.register.CopyFrom(performative) # pylint: disable=no-member buf = acn_msg.SerializeToString() await self._write(buf) try: buf = await asyncio.wait_for(self._read(), timeout=self.ACN_ACK_TIMEOUT) except ConnectionError as e: # pragma: nocover raise e except IncompleteReadError as e: # pragma: no cover raise e if buf is None: # pragma: nocover raise ConnectionError( "Error on connection setup. Incoming buffer is empty!" ) acn_msg = acn_pb2.AcnMessage() acn_msg.ParseFromString(buf) performative = acn_msg.WhichOneof("performative") if performative != "status": # pragma: nocover raise Exception(f"Wrong response message from peer: {performative}") response = acn_msg.status # pylint: disable=no-member if response.body.code != int(AcnMessage.StatusBody.StatusCode.SUCCESS): # type: ignore # pylint: disable=no-member raise Exception( # pragma: nocover "Registration to peer failed: {}".format( AcnMessage.StatusBody.StatusCode(response.body.code) # type: ignore # pylint: disable=no-member ) ) async def close(self) -> None: """Close client and pipe.""" await self.pipe.close() class P2PLibp2pClientConnection(Connection): """ A libp2p client connection. Send and receive envelopes to and from agents on the p2p network without deploying a libp2p node. Connect to the libp2p node using traffic delegation service. """ connection_id = PUBLIC_ID DEFAULT_CONNECT_RETRIES = 3 DEFAULT_TLS_CONNECTION_SIGNATURE_TIMEOUT = 5.0 def __init__(self, **kwargs: Any) -> None: """Initialize a libp2p client connection.""" super().__init__(**kwargs) self.tls_connection_signature_timeout = self.configuration.config.get( "tls_connection_signature_timeout", self.DEFAULT_TLS_CONNECTION_SIGNATURE_TIMEOUT, ) self.connect_retries = self.configuration.config.get( "connect_retries", self.DEFAULT_CONNECT_RETRIES ) ledger_id = self.configuration.config.get("ledger_id", DEFAULT_LEDGER) if ledger_id not in SUPPORTED_LEDGER_IDS: raise ValueError( # pragma: nocover "Ledger id '{}' is not supported. Supported ids: '{}'".format( ledger_id, SUPPORTED_LEDGER_IDS ) ) key_file: Optional[str] = self.configuration.config.get("tcp_key_file") nodes: Optional[List[Dict[str, Any]]] = self.configuration.config.get("nodes") if nodes is None: raise ValueError("At least one node should be provided") nodes = list(nodes) nodes_uris = [node.get("uri", None) for node in nodes] enforce( len(nodes_uris) == len(nodes) and None not in nodes_uris, "Delegate 'uri' should be provided for each node", ) nodes_public_keys = [node.get("public_key", None) for node in nodes] enforce( len(nodes_public_keys) == len(nodes) and None not in nodes_public_keys, "Delegate 'public_key' should be provided for each node", ) cert_requests = self.configuration.cert_requests if cert_requests is None or len(cert_requests) != len(nodes): raise ValueError( # pragma: nocover "cert_requests field must be set and contain exactly as many entries as 'nodes'!" ) for cert_request in cert_requests: save_path = cert_request.get_absolute_save_path(Path(self.data_dir)) if not save_path.is_file(): raise Exception( # pragma: nocover "cert_request 'save_path' field is not a file. " "Please ensure that 'issue-certificates' command is called beforehand" ) # we cannot use the key from the connection's crypto store as # the key will be used for TLS tcp connection, whereas the # connection's crypto store key is used for PoR if key_file is not None: key = make_crypto(ledger_id, private_key_path=key_file) else: key = make_crypto(ledger_id) # client connection id self.key = key self.logger.debug("Public key used for TCP: {}".format(key.public_key)) # delegate uris self.delegate_uris = [Uri(node_uri) for node_uri in nodes_uris] # delegates PoRs self.delegate_pors: List[AgentRecord] = [] for i, cert_request in enumerate(cert_requests): agent_record = AgentRecord.from_cert_request( cert_request, self.address, nodes_public_keys[i], self.data_dir ) self.delegate_pors.append(agent_record) # select a delegate index = random.randint(0, len(self.delegate_uris) - 1) # nosec self.node_uri = self.delegate_uris[index] self.node_por = self.delegate_pors[index] self.logger.debug("Node to use as delegate: {}".format(self.node_uri)) self._in_queue = None # type: Optional[asyncio.Queue] self._process_messages_task = None # type: Optional[asyncio.Future] self._node_client: Optional[NodeClient] = None self._send_queue: Optional[asyncio.Queue] = None self._send_task: Optional[asyncio.Task] = None async def _send_loop(self) -> None: """Handle message in the send queue.""" if not self._send_queue or not self._node_client: # pragma: nocover self.logger.error("Send loop not started cause not connected properly.") return try: while self.is_connected: envelope = await self._send_queue.get() await self._send_envelope_with_node_client(envelope) except asyncio.CancelledError: # pylint: disable=try-except-raise raise # pragma: nocover except Exception: # pylint: disable=broad-except # pragma: nocover self.logger.exception( f"Failed to send an envelope {envelope}. Stop connection." ) await asyncio.shield(self.disconnect()) async def _send_envelope_with_node_client(self, envelope: Envelope) -> None: """Send envelope with node client, reconnect and retry on fail.""" if not self._node_client: # pragma: nocover raise ValueError("Connection not connected to node!") self._ensure_valid_envelope_for_external_comms(envelope) try: await self._node_client.send_envelope(envelope) except Exception: # pylint: disable=broad-except self.logger.exception( "Exception raised on message send. Try reconnect and send again." ) await self._perform_connection_to_node() await self._node_client.send_envelope(envelope) async def connect(self) -> None: """Set up the connection.""" if self.is_connected: # pragma: nocover return with self._connect_context(): # connect libp2p client await self._perform_connection_to_node() # start receiving msgs self._in_queue = asyncio.Queue() self._process_messages_task = asyncio.ensure_future( self._process_messages(), loop=self.loop ) self._send_queue = asyncio.Queue() self._send_task = self.loop.create_task(self._send_loop()) async def _perform_connection_to_node(self) -> None: """Connect to node with retries.""" for attempt in range(self.connect_retries): if self.state not in [ ConnectionStates.connecting, ConnectionStates.connected, ]: # do nothing if disconnected, or disconnecting return # pragma: nocover try: self.logger.info( "Connecting to libp2p node {}. Attempt {}".format( str(self.node_uri), attempt + 1 ) ) pipe = TCPSocketChannelClientTLS( f"{self.node_uri.host}:{self.node_uri._port}", # pylint: disable=protected-access "", server_pub_key=self.node_por.representative_public_key, verification_signature_wait_timeout=self.tls_connection_signature_timeout, ) if not await pipe.connect(): raise ValueError( f"Pipe connection error: {pipe.last_exception or ''}" ) self._node_client = NodeClient(pipe, self.node_por) await self._setup_connection() self.logger.info( "Successfully connected to libp2p node {}".format( str(self.node_uri) ) ) return except Exception as e: # pylint: disable=broad-except if attempt == self.connect_retries - 1: self.logger.error( "Connection to libp2p node {} failed: error: {}. It was the last attempt, exception will be raised".format( str(self.node_uri), str(e) ) ) self.state = ConnectionStates.disconnected raise sleep_time = attempt * 2 + 1 self.logger.error( "Connection to libp2p node {} failed: error: {}. Another attempt will be performed in {} seconds".format( str(self.node_uri), str(e), sleep_time ) ) await asyncio.sleep(sleep_time) async def _setup_connection(self) -> None: """Set up connection to node over tcp connection.""" if not self._node_client: # pragma: nocover raise ValueError("Connection was not connected!") await self._node_client.register() async def disconnect(self) -> None: """Disconnect from the channel.""" if self.is_disconnected: # pragma: nocover return self.state = ConnectionStates.disconnecting self.logger.debug("disconnecting libp2p client connection...") if self._process_messages_task is not None: if not self._process_messages_task.done(): self._process_messages_task.cancel() self._process_messages_task = None if self._send_task is not None: if not self._send_task.done(): self._send_task.cancel() self._send_task = None try: self.logger.debug("disconnecting libp2p node client connection...") if self._node_client is not None: await self._node_client.close() except Exception: # pragma: nocover # pylint:disable=broad-except self.logger.exception("exception on node client close") raise finally: # set disconnected state anyway if self._in_queue is not None: self._in_queue.put_nowait(None) self.state = ConnectionStates.disconnected self.logger.debug("libp2p client connection disconnected.") async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. Blocking. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ try: if self._in_queue is None: raise ValueError("Input queue not initialized.") # pragma: nocover envelope = await self._in_queue.get() if envelope is None: # pragma: no cover self.logger.debug("Received None.") return None self.logger.debug("Received envelope: {}".format(envelope)) return envelope except CancelledError: # pragma: no cover self.logger.debug("Receive cancelled.") return None except Exception as e: # pragma: no cover # pylint: disable=broad-except self.logger.exception(e) return None async def send(self, envelope: Envelope) -> None: """ Send messages. :param envelope: the envelope """ if not self._node_client or not self._send_queue: raise ValueError("Node is not connected!") # pragma: nocover self._ensure_valid_envelope_for_external_comms(envelope) await self._send_queue.put(envelope) async def _read_envelope_from_node(self) -> Optional[Envelope]: """Read envelope from node, reconnec on error.""" if not self._node_client: # pragma: nocover raise ValueError("Connection not connected to node!") try: self.logger.debug("Waiting for messages...") envelope = await self._node_client.read_envelope() return envelope except ConnectionError as e: # pragma: nocover self.logger.error(f"Connection error: {e}. Try to reconnect and read again") except IncompleteReadError as e: # pragma: no cover self.logger.error( "Connection disconnected while reading from node ({}/{})".format( len(e.partial), e.expected ) ) except Exception as e: # pylint: disable=broad-except # pragma: nocover self.logger.exception(f"On envelope read: {e}") try: self.logger.debug("Read envelope retry! Reconnect first!") await self._perform_connection_to_node() envelope = await self._node_client.read_envelope() return envelope except Exception: # pragma: no cover # pylint: disable=broad-except self.logger.exception("Failed to read with reconnect!") return None async def _process_messages(self) -> None: """Receive data from node.""" if not self._node_client: # pragma: nocover raise ValueError("Connection not connected to node!") while True: envelope = await self._read_envelope_from_node() if self._in_queue is None: raise ValueError("Input queue not initialized.") # pragma: nocover self._in_queue.put_nowait(envelope) if envelope is None: break # pragma: no cover class TCPSocketChannelClientTLS(TCPSocketChannelClient): """Interprocess communication channel client using tcp sockets with TLS.""" DEFAULT_VERIFICATION_SIGNATURE_WAIT_TIMEOUT = 5.0 def __init__( self, in_path: str, out_path: str, server_pub_key: str, logger: logging.Logger = _default_logger, loop: Optional[AbstractEventLoop] = None, verification_signature_wait_timeout: Optional[float] = None, ) -> None: """ Initialize a tcp socket communication channel client. :param in_path: rendezvous point for incoming data :param out_path: rendezvous point for outgoing data :param server_pub_key: str, server public key to verify identity :param logger: the logger :param loop: the event loop :param verification_signature_wait_timeout: optional float, if not provided, default value will be used """ super().__init__(in_path, out_path, logger, loop) self.verification_signature_wait_timeout = ( self.DEFAULT_VERIFICATION_SIGNATURE_WAIT_TIMEOUT if verification_signature_wait_timeout is None else verification_signature_wait_timeout ) self.server_pub_key = server_pub_key @staticmethod def _get_session_pub_key(writer: StreamWriter) -> bytes: # pragma: nocover """Get session public key from tls stream writer.""" cert_data = writer.get_extra_info("ssl_object").getpeercert(binary_form=True) cert = x509.Certificate.load(cert_data) session_pub_key = VerifyingKey.from_der(cert.public_key.dump()).to_string( "uncompressed" ) return session_pub_key async def _open_connection(self) -> TCPSocketProtocol: """Open a connection with TLS support and verify peer.""" sock = await self._open_tls_connection() session_pub_key = self._get_session_pub_key(sock.writer) try: signature = await asyncio.wait_for( sock.read(), timeout=self.verification_signature_wait_timeout ) except asyncio.TimeoutError: # pragma: nocover raise ValueError( f"Failed to get peer verification record in timeout: {self.verification_signature_wait_timeout}" ) if not signature: # pragma: nocover raise ValueError("Unexpected socket read data!") try: self._verify_session_key_signature(signature, session_pub_key) except BadSignatureError as e: # pragma: nocover with contextlib.suppress(Exception): await sock.close() raise ValueError(f"Invalid TLS session key signature: {e}") return sock async def _open_tls_connection(self) -> TCPSocketProtocol: """Open a connection with TLS support.""" cadata = await asyncio.get_event_loop().run_in_executor( None, lambda: ssl.get_server_certificate((self._host, self._port)) ) ssl_ctx = ssl.create_default_context(cadata=cadata) ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_REQUIRED reader, writer = await asyncio.open_connection( self._host, self._port, ssl=ssl_ctx, ) return TCPSocketProtocol(reader, writer, logger=self.logger, loop=self._loop) def _verify_session_key_signature( self, signature: bytes, session_pub_key: bytes ) -> None: """ Validate signature of session public key. :param signature: bytes, signature of session public key made with server private key :param session_pub_key: session public key to check signature for. """ vk = VerifyingKey.from_string(bytes.fromhex(self.server_pub_key), SECP256k1) vk.verify( signature, session_pub_key, hashfunc=hashlib.sha256, sigdecode=sigdecode_der ) ================================================ FILE: packages/fetchai/connections/p2p_libp2p_client/connection.yaml ================================================ name: p2p_libp2p_client author: fetchai version: 0.20.6 type: connection description: The libp2p client connection implements a tcp connection to a running libp2p node as a traffic delegate to send/receive envelopes to/from agents in the DHT. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmSbRjhLF6vhoVugcGKJtT3CD59sSgCPG3NF3rBrS3CG8t __init__.py: QmXwtBAZxhrLXVTU5FYytTxnoh7vScRQBRjtMvFerXH31e connection.py: Qmb2RBaDjdEpM1i8YHYcZQpuPfFcZGPiZ4SdSunejxvfho fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/acn:1.1.7 class_name: P2PLibp2pClientConnection config: connect_retries: 3 ledger_id: fetchai nodes: - uri: acn.fetch.ai:11000 public_key: 0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7 - uri: acn.fetch.ai:11001 public_key: 03fa7cfae1037cba5218f0f5743802eced8de3247c55ecebaae46c7d3679e3f91d tls_connection_signature_timeout: 5.0 cert_requests: - identifier: acn ledger_id: fetchai message_format: '{public_key}' not_after: '2023-01-01' not_before: '2022-01-01' public_key: 0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7 save_path: .certs/acn_fetch_ai_11000.txt - identifier: acn ledger_id: fetchai message_format: '{public_key}' not_after: '2023-01-01' not_before: '2022-01-01' public_key: 03fa7cfae1037cba5218f0f5743802eced8de3247c55ecebaae46c7d3679e3f91d save_path: .certs/acn_fetch_ai_11001.txt excluded_protocols: [] restricted_to_protocols: [] dependencies: asn1crypto: version: <1.5.0,>=1.4.0 is_abstract: false ================================================ FILE: packages/fetchai/connections/p2p_libp2p_mailbox/README.md ================================================ # P2P Libp2p Client Connection A lightweight TCP connection to a libp2p DHT node. It allows for using the DHT without having to deploy a node by delegating its communication traffic to an already running DHT node with delegate service enabled. ## Usage First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p_client:0.20.6`. Next, ensure that the connection is properly configured by setting: - `nodes` to a list of `uri`s, connection will choose the delegate randomly - `uri` to the public IP address and port number of the delegate service of a running DHT node, in format `${ip|dns}:${port}` ================================================ FILE: packages/fetchai/connections/p2p_libp2p_mailbox/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the libp2p client connection.""" ================================================ FILE: packages/fetchai/connections/p2p_libp2p_mailbox/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the libp2p client connection.""" import asyncio import hashlib import logging import random import re import ssl from asyncio import CancelledError from asyncio.streams import StreamWriter from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, cast from urllib.parse import urlparse import aiohttp from aiohttp.client_reqrep import ClientResponse from asn1crypto import x509 # type: ignore from ecdsa.curves import SECP256k1 from ecdsa.keys import VerifyingKey from ecdsa.util import sigdecode_der from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER from aea.connections.base import Connection, ConnectionStates from aea.crypto.registries import make_crypto from aea.exceptions import enforce from aea.helpers.acn.agent_record import AgentRecord from aea.helpers.acn.uri import Uri from aea.mail.base import Envelope from packages.fetchai.protocols.acn import acn_pb2 from packages.fetchai.protocols.acn.message import AcnMessage try: from asyncio.streams import IncompleteReadError # pylint: disable=ungrouped-imports except ImportError: # pragma: nocover from asyncio import IncompleteReadError # pylint: disable=ungrouped-imports _default_logger = logging.getLogger( "aea.packages.fetchai.connections.p2p_libp2p_client" ) PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p_mailbox:0.2.6") SUPPORTED_LEDGER_IDS = ["fetchai", "cosmos", "ethereum"] POR_DEFAULT_SERVICE_ID = "acn" ACN_CURRENT_VERSION = "0.1.0" class NodeClient: """Client to communicate with node using ipc channel(pipe).""" NO_ENVELOPES_SLEEP_TIME: float = 2.0 def __init__(self, node_uri: Uri, node_por: AgentRecord) -> None: """Set node client with pipe.""" self.node_uri = node_uri self.agent_record = node_por self._session_token: Optional[str] = None self.ssl_ctx = Optional[ssl.SSLContext] async def connect(self) -> bool: """Connect to node with pipe.""" url = f"https://{self.node_uri}/ssl_signature" self.ssl_ctx = await SSLValidator( url, self.agent_record.representative_public_key ).check() await self.register() return True async def send_envelope(self, envelope: Envelope) -> None: """Send envelope to node.""" if not self._session_token: # pragma: nocover raise ValueError("not connected!") envelope_data = envelope.encode() # send here response, _ = await self._perform_http_request( method="POST", url="/send_envelope", data=envelope_data, headers={"Session-Id": self._session_token}, ) if response.status != 200: # pragma: nocover text = await response.text() raise ValueError(f"Bad response code: {response.status} {text}") async def _perform_http_request( self, method: str, url: str, **kwargs: Any ) -> Tuple[ClientResponse, bytes]: async with aiohttp.ClientSession() as session: async with session.request( method, url=f"https://{self.node_uri}{url}", ssl=self.ssl_ctx, **kwargs ) as response: data = await response.read() return response, data def make_agent_record(self) -> AcnMessage.AgentRecord: # type: ignore """Make acn agent record.""" agent_record = AcnMessage.AgentRecord( address=self.agent_record.address, public_key=self.agent_record.public_key, peer_public_key=self.agent_record.representative_public_key, signature=self.agent_record.signature, service_id=POR_DEFAULT_SERVICE_ID, ledger_id=self.agent_record.ledger_id, ) return agent_record async def read_envelope(self) -> Optional[Envelope]: """Read envelope from the mailbox node.""" while True: if not self._session_token: # pragma: nocover raise ValueError("Client not registered!") response, data = await self._perform_http_request( "GET", "/get_envelope", headers={"Session-Id": self._session_token} ) if response.status != 200: # pragma: nocover raise ValueError(f"Bad response code: {response.status}") if not data: await asyncio.sleep(self.NO_ENVELOPES_SLEEP_TIME) continue envelope = Envelope.decode(data) return envelope async def register(self) -> None: """Register agent on the remote node.""" agent_record = self.make_agent_record() performative = acn_pb2.AcnMessage.Register_Performative() # type: ignore AcnMessage.AgentRecord.encode( performative.record, agent_record # pylint: disable=no-member ) data = performative.record.SerializeToString() # pylint: disable=no-member response, _ = await self._perform_http_request( "POST", url="/register", data=data ) if response.status != 200: # pragma: nocover raise ValueError(f"Bad response code: {response.status}") token = await response.text() if not re.match("[0-9a-f]{32}", token, re.I): # pragma: nocover raise ValueError(f"invalid response: {token}") self._session_token = token async def close(self) -> None: """Close node connection.""" if not self._session_token: # pragma: nocover raise ValueError("not connected!") response, _ = await self._perform_http_request( "GET", "/unregister", headers={"Session-Id": self._session_token} ) if response.status != 200: # pragma: nocover raise ValueError(f"Bad response code: {response.status}") self._session_token = None class P2PLibp2pMailboxConnection(Connection): """ A libp2p client connection. Send and receive envelopes to and from agents on the p2p network without deploying a libp2p node. Connect to the libp2p node using traffic delegation service. """ connection_id = PUBLIC_ID DEFAULT_CONNECT_RETRIES = 3 DEFAULT_TLS_CONNECTION_SIGNATURE_TIMEOUT = 5.0 def __init__(self, **kwargs: Any) -> None: """Initialize a libp2p client connection.""" super().__init__(**kwargs) self.tls_connection_signature_timeout = self.configuration.config.get( "tls_connection_signature_timeout", self.DEFAULT_TLS_CONNECTION_SIGNATURE_TIMEOUT, ) self.connect_retries = self.configuration.config.get( "connect_retries", self.DEFAULT_CONNECT_RETRIES ) ledger_id = self.configuration.config.get("ledger_id", DEFAULT_LEDGER) if ledger_id not in SUPPORTED_LEDGER_IDS: raise ValueError( # pragma: nocover "Ledger id '{}' is not supported. Supported ids: '{}'".format( ledger_id, SUPPORTED_LEDGER_IDS ) ) key_file: Optional[str] = self.configuration.config.get("tcp_key_file") nodes: Optional[List[Dict[str, Any]]] = self.configuration.config.get("nodes") if nodes is None: raise ValueError("At least one node should be provided") nodes = list(nodes) nodes_uris = [node.get("uri", None) for node in nodes] enforce( len(nodes_uris) == len(nodes) and None not in nodes_uris, "Delegate 'uri' should be provided for each node", ) nodes_public_keys = [node.get("public_key", None) for node in nodes] enforce( len(nodes_public_keys) == len(nodes) and None not in nodes_public_keys, "Delegate 'public_key' should be provided for each node", ) cert_requests = self.configuration.cert_requests if cert_requests is None or len(cert_requests) != len(nodes): raise ValueError( # pragma: nocover "cert_requests field must be set and contain exactly as many entries as 'nodes'!" ) for cert_request in cert_requests: save_path = cert_request.get_absolute_save_path(Path(self.data_dir)) if not save_path.is_file(): raise Exception( # pragma: nocover "cert_request 'save_path' field is not a file. " "Please ensure that 'issue-certificates' command is called beforehand" ) # we cannot use the key from the connection's crypto store as # the key will be used for TLS tcp connection, whereas the # connection's crypto store key is used for PoR if key_file is not None: key = make_crypto(ledger_id, private_key_path=key_file) else: key = make_crypto(ledger_id) # client connection id self.key = key self.logger.debug("Public key used for TCP: {}".format(key.public_key)) # delegate uris self.delegate_uris = [Uri(node_uri) for node_uri in nodes_uris] # delegates PoRs self.delegate_pors: List[AgentRecord] = [] for i, cert_request in enumerate(cert_requests): agent_record = AgentRecord.from_cert_request( cert_request, self.address, nodes_public_keys[i], self.data_dir ) self.delegate_pors.append(agent_record) # select a delegate index = random.randint(0, len(self.delegate_uris) - 1) # nosec self.node_uri = self.delegate_uris[index] self.node_por = self.delegate_pors[index] self.logger.debug("Node to use as delegate: {}".format(self.node_uri)) self._in_queue = None # type: Optional[asyncio.Queue] self._process_messages_task = None # type: Optional[asyncio.Future] self._node_client: Optional[NodeClient] = None self._send_queue: Optional[asyncio.Queue] = None self._send_task: Optional[asyncio.Task] = None async def _send_loop(self) -> None: """Handle message in the send queue.""" if not self._send_queue or not self._node_client: # pragma: nocover self.logger.error("Send loop not started cause not connected properly.") return try: while self.is_connected: envelope = await self._send_queue.get() await self._send_envelope_with_node_client(envelope) except asyncio.CancelledError: # pylint: disable=try-except-raise raise # pragma: nocover except Exception: # pylint: disable=broad-except # pragma: nocover self.logger.exception( f"Failed to send an envelope {envelope}. Stop connection." ) await asyncio.shield(self.disconnect()) async def _send_envelope_with_node_client(self, envelope: Envelope) -> None: """Send envelope with node client, reconnect and retry on fail.""" if not self._node_client: # pragma: nocover raise ValueError("Connection not connected to node!") self._ensure_valid_envelope_for_external_comms(envelope) try: await self._node_client.send_envelope(envelope) except Exception: # pylint: disable=broad-except self.logger.exception( "Exception raised on message send. Try reconnect and send again." ) await self._perform_connection_to_node() await self._node_client.send_envelope(envelope) async def connect(self) -> None: """Set up the connection.""" if self.is_connected: # pragma: nocover return with self._connect_context(): # connect libp2p client await self._perform_connection_to_node() # start receiving msgs self._in_queue = asyncio.Queue() self._process_messages_task = asyncio.ensure_future( self._process_messages(), loop=self.loop ) self._send_queue = asyncio.Queue() self._send_task = self.loop.create_task(self._send_loop()) async def _perform_connection_to_node(self) -> None: """Connect to node with retries.""" for attempt in range(self.connect_retries): if self.state not in [ ConnectionStates.connecting, ConnectionStates.connected, ]: # do nothing if disconnected, or disconnecting return # pragma: nocover try: self.logger.info( "Connecting to libp2p node {}. Attempt {}".format( str(self.node_uri), attempt + 1 ) ) self._node_client = NodeClient(self.node_uri, self.node_por) await self._setup_connection() self.logger.info( "Successfully connected to libp2p node {}".format( str(self.node_uri) ) ) return except Exception as e: # pylint: disable=broad-except if attempt == self.connect_retries - 1: self.logger.error( "Connection to libp2p node {} failed: error: {}. It was the last attempt, exception will be raised".format( str(self.node_uri), str(e) ) ) self.state = ConnectionStates.disconnected raise sleep_time = attempt * 2 + 1 self.logger.error( "Connection to libp2p node {} failed: error: {}. Another attempt will be performed in {} seconds".format( str(self.node_uri), str(e), sleep_time ) ) await asyncio.sleep(sleep_time) async def _setup_connection(self) -> None: """Set up connection to node over tcp connection.""" if not self._node_client: # pragma: nocover raise ValueError("Connection was not connected!") await self._node_client.connect() async def disconnect(self) -> None: """Disconnect from the channel.""" if self.is_disconnected: # pragma: nocover return self.state = ConnectionStates.disconnecting self.logger.debug("disconnecting libp2p client connection...") if self._process_messages_task is not None: if not self._process_messages_task.done(): self._process_messages_task.cancel() self._process_messages_task = None if self._send_task is not None: if not self._send_task.done(): self._send_task.cancel() self._send_task = None try: self.logger.debug("disconnecting libp2p node client connection...") if self._node_client is not None: await self._node_client.close() except Exception: # pragma: nocover # pylint:disable=broad-except self.logger.exception("exception on node client close") raise finally: # set disconnected state anyway if self._in_queue is not None: self._in_queue.put_nowait(None) self.state = ConnectionStates.disconnected self.logger.debug("libp2p client connection disconnected.") async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. Blocking. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ try: if self._in_queue is None: raise ValueError("Input queue not initialized.") # pragma: nocover envelope = await self._in_queue.get() if envelope is None: # pragma: no cover self.logger.debug("Received None.") return None self.logger.debug("Received envelope: {}".format(envelope)) return envelope except CancelledError: # pragma: no cover self.logger.debug("Receive cancelled.") return None except Exception as e: # pragma: no cover # pylint: disable=broad-except self.logger.exception(e) return None async def send(self, envelope: Envelope) -> None: """ Send messages. :param envelope: the envelope """ if not self._node_client or not self._send_queue: raise ValueError("Node is not connected!") # pragma: nocover self._ensure_valid_envelope_for_external_comms(envelope) await self._send_queue.put(envelope) async def _read_envelope_from_node(self) -> Optional[Envelope]: """Read envelope from node, reconnec on error.""" if not self._node_client: # pragma: nocover raise ValueError("Connection not connected to node!") try: self.logger.debug("Waiting for messages...") envelope = await self._node_client.read_envelope() return envelope except ConnectionError as e: # pragma: nocover self.logger.error(f"Connection error: {e}. Try to reconnect and read again") except IncompleteReadError as e: # pragma: no cover self.logger.error( "Connection disconnected while reading from node ({}/{})".format( len(e.partial), e.expected ) ) except Exception as e: # pylint: disable=broad-except # pragma: nocover self.logger.exception(f"On envelope read: {e}") try: self.logger.debug("Read envelope retry! Reconnect first!") await self._perform_connection_to_node() envelope = await self._node_client.read_envelope() return envelope # pragma: no cover except Exception: # pragma: no cover # pylint: disable=broad-except self.logger.exception("Failed to read with reconnect!") return None async def _process_messages(self) -> None: """Receive data from node.""" if not self._node_client: # pragma: nocover raise ValueError("Connection not connected to node!") while True: envelope = await self._read_envelope_from_node() if self._in_queue is None: raise ValueError("Input queue not initialized.") # pragma: nocover self._in_queue.put_nowait(envelope) if envelope is None: break # pragma: no cover class SSLValidator: """Interprocess communication channel client using tcp sockets with TLS.""" def __init__( self, url: str, server_pub_key: str, logger: logging.Logger = _default_logger, ) -> None: """ Check ssl certificate with server pub key. :param url: url to get signature :param server_pub_key: str, server public key to verify identity :param logger: the logger """ self.server_pub_key = server_pub_key o = urlparse(url) self.url = url self.logger = logger self.host: str = cast(str, o.hostname) self.port: int = cast(int, o.port) async def check(self) -> ssl.SSLContext: """Check ssl/pubkey for mailbox service and return ssl context.""" ssl_ctx, session_pub_key = await self.get_ssl_ctx_and_session_pub_key( self.host, self.port ) signature = await self.get_signature(ssl_ctx) self._verify_session_key_signature( self.server_pub_key, signature, session_pub_key ) return ssl_ctx async def get_signature(self, ssl_ctx: ssl.SSLContext) -> bytes: """ Get signature for mailbox service (ssl pubkey signed with node private key). :param ssl_ctx: ssl context. :return: signature in bytes """ async with aiohttp.ClientSession() as client: async with client.get(self.url, ssl=ssl_ctx) as resp: if resp.status != 200: # pragma: nocover raise ValueError("Bad server response") signature = await resp.read() return signature @staticmethod def _get_session_pub_key(writer: StreamWriter) -> bytes: # pragma: nocover """Get session public key from tls stream writer.""" cert_data = writer.get_extra_info("ssl_object").getpeercert(binary_form=True) cert = x509.Certificate.load(cert_data) session_pub_key = VerifyingKey.from_der(cert.public_key.dump()).to_string( "uncompressed" ) return session_pub_key async def get_ssl_ctx_and_session_pub_key( self, host: str, port: int ) -> Tuple[ssl.SSLContext, bytes]: """Open a connection with TLS support.""" cadata = await asyncio.get_event_loop().run_in_executor( None, lambda: ssl.get_server_certificate((host, port)) ) ssl_ctx = ssl.create_default_context(cadata=cadata) ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_REQUIRED _, writer = await asyncio.open_connection( self.host, self.port, ssl=ssl_ctx, ) session_pub_key = self._get_session_pub_key(writer) writer.close() return ssl_ctx, session_pub_key @staticmethod def _verify_session_key_signature( server_pub_key: str, signature: bytes, session_pub_key: bytes ) -> None: """ Validate signature of session public key. :param server_pub_key: node pub key/addr. :param signature: bytes, signature of session public key made with server private key :param session_pub_key: session public key to check signature for. """ vk = VerifyingKey.from_string(bytes.fromhex(server_pub_key), SECP256k1) vk.verify( signature, session_pub_key, hashfunc=hashlib.sha256, sigdecode=sigdecode_der ) ================================================ FILE: packages/fetchai/connections/p2p_libp2p_mailbox/connection.yaml ================================================ name: p2p_libp2p_mailbox author: fetchai version: 0.2.6 type: connection description: The libp2p client connection implements a tcp connection to a running libp2p node as a traffic delegate to send/receive envelopes to/from agents in the DHT. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmSbRjhLF6vhoVugcGKJtT3CD59sSgCPG3NF3rBrS3CG8t __init__.py: QmXwtBAZxhrLXVTU5FYytTxnoh7vScRQBRjtMvFerXH31e connection.py: QmStAHydBBs2De2oAnjspoNi56cDuKMAdyFCsjsK6PBHfa fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/acn:1.1.7 class_name: P2PLibp2pMailboxConnection config: connect_retries: 3 ledger_id: fetchai nodes: - uri: acn.fetch.ai:8888 public_key: 0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7 tls_connection_signature_timeout: 5.0 cert_requests: - identifier: acn ledger_id: fetchai message_format: '{public_key}' not_after: '2023-01-01' not_before: '2022-01-01' public_key: 0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7 save_path: .certs/acn_fetch_ai_11000.txt - identifier: acn ledger_id: fetchai message_format: '{public_key}' not_after: '2023-01-01' not_before: '2022-01-01' public_key: 03fa7cfae1037cba5218f0f5743802eced8de3247c55ecebaae46c7d3679e3f91d save_path: .certs/acn_fetch_ai_11001.txt excluded_protocols: [] restricted_to_protocols: [] dependencies: aiohttp: version: <3.8,>=3.7.4 asn1crypto: version: <1.5.0,>=1.4.0 is_abstract: false ================================================ FILE: packages/fetchai/connections/p2p_stub/README.md ================================================ # P2P stub connection Simple file based connection to perform interaction between multiple local agents. ## Usage First, add the connection to your AEA project: `aea add connection fetchai/p2p_stub:0.18.3`. Optionally, in the `connection.yaml` file under `config` set the `namespace_dir` to the desired file path. The `p2p_stub` connection reads encoded envelopes from its input file and writes encoded envelopes to its output file. Multiple agents can be pointed to the same `namespace_dir` and are then able to exchange envelopes via the file system. ================================================ FILE: packages/fetchai/connections/p2p_stub/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the P2P stub connection.""" ================================================ FILE: packages/fetchai/connections/p2p_stub/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the p2p stub connection.""" import os import tempfile from pathlib import Path from typing import Any, Union, cast from aea.configurations.base import ConnectionConfig, PublicId from aea.identity.base import Identity from aea.mail.base import Envelope from packages.fetchai.connections.stub.connection import StubConnection, write_envelope PUBLIC_ID = PublicId.from_str("fetchai/p2p_stub:0.18.3") class P2PStubConnection(StubConnection): r"""A p2p stub connection. This connection uses an existing directory as a Rendez-Vous point for agents to communicate locally. Each connected agent will create a file named after its address/identity where it can receive messages. The connection detects new messages by watchdogging the input file looking for new lines. """ connection_id = PUBLIC_ID def __init__( self, configuration: ConnectionConfig, identity: Identity, **kwargs: Any ) -> None: """ Initialize a p2p stub connection. :param configuration: the connection configuration :param identity: the identity :param kwargs: positional arguments """ namespace_dir_path = cast( Union[str, Path], configuration.config.get("namespace_dir", tempfile.mkdtemp()), ) if namespace_dir_path is None: raise ValueError("namespace_dir_path must be set!") # pragma: nocover self.namespace = os.path.abspath(namespace_dir_path) input_file_path = os.path.join(self.namespace, "{}.in".format(identity.address)) output_file_path = os.path.join( self.namespace, "{}.out".format(identity.address) ) configuration.config["input_file"] = input_file_path configuration.config["output_file"] = output_file_path super().__init__(configuration=configuration, identity=identity, **kwargs) async def send(self, envelope: Envelope) -> None: """ Send messages. :param envelope: the envelope """ if self.loop is None: raise ValueError("Loop not initialized.") # pragma: nocover self._ensure_valid_envelope_for_external_comms(envelope) target_file = Path(os.path.join(self.namespace, "{}.in".format(envelope.to))) with open(target_file, "ab") as file: await self.loop.run_in_executor( self._write_pool, write_envelope, envelope, file ) async def disconnect(self) -> None: """Disconnect the connection.""" if self.loop is None: raise ValueError("Loop not initialized.") # pragma: nocover await self.loop.run_in_executor(self._write_pool, self._cleanup) await super().disconnect() def _cleanup(self) -> None: try: os.unlink(self.configuration.config["input_file"]) except OSError: pass try: os.unlink(self.configuration.config["output_file"]) except OSError: pass try: os.rmdir(self.namespace) except OSError: pass ================================================ FILE: packages/fetchai/connections/p2p_stub/connection.yaml ================================================ name: p2p_stub author: fetchai version: 0.18.3 type: connection description: The stub p2p connection implements a local p2p connection allowing agents to communicate with each other through files created in the namespace directory. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmRsMvDbqtD1Hwdjr6H6ypRLuWmyUwT7ZJxj6Taq7VYM28 __init__.py: QmTEHNj5MHXKyboUYrPr2FEpFH2rDgFPasLGViZJg6GuS3 connection.py: QmfHu2JX3ksGkFvcwQiXfASvrp7RZ8bzoKDdYNYU11XfuV fingerprint_ignore_patterns: [] connections: - fetchai/stub:0.21.3 protocols: [] class_name: P2PStubConnection config: namespace_dir: /tmp/ excluded_protocols: [] restricted_to_protocols: [] dependencies: watchdog: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/prometheus/README.md ================================================ # Prometheus connection AEAs can create and update prometheus metrics for remote monitoring by sending messages to the prometheus connection. ## Usage First, add the connection to your AEA project (`aea add connection fetchai/prometheus:0.9.6`). Then, add the protocol (`aea add protocol fetchai/prometheus:1.1.7`) to your project. The default port (`9090`) to expose metrics can be changed to `PORT` by updating the `config` at the agent level (`aea config set --type=int vendor.fetchai.connections.prometheus.config.port PORT`). ================================================ FILE: packages/fetchai/connections/prometheus/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the Prometheus connection.""" ================================================ FILE: packages/fetchai/connections/prometheus/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Prometheus connection and channel.""" import asyncio import logging from typing import Any, Dict, Optional, Tuple, Union, cast import aioprometheus # type: ignore from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.exceptions import enforce from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.prometheus.dialogues import PrometheusDialogue from packages.fetchai.protocols.prometheus.dialogues import ( PrometheusDialogues as BasePrometheusDialogues, ) from packages.fetchai.protocols.prometheus.message import PrometheusMessage PUBLIC_ID = PublicId.from_str("fetchai/prometheus:0.9.6") DEFAULT_HOST = "127.0.0.1" DEFAULT_PORT = 9090 VALID_UPDATE_FUNCS = {"inc", "dec", "add", "sub", "set", "observe"} VALID_METRIC_TYPES = {"Counter", "Gauge", "Histogram", "Summary"} class PrometheusDialogues(BasePrometheusDialogues): """The dialogues class keeps track of all prometheus dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The server connection maintains the dialogue on behalf of the agent return PrometheusDialogue.Role.SERVER BasePrometheusDialogues.__init__( self, self_address=str(PUBLIC_ID), role_from_first_message=role_from_first_message, **kwargs, ) class PrometheusChannel: """A wrapper for interacting with a prometheus server.""" def __init__( self, address: Address, host: str, port: int, logger: Union[logging.Logger, logging.LoggerAdapter], ): """ Initialize a prometheus channel. :param address: The address of the connection. :param host: The host at which to expose the metrics. :param port: The port at which to expose the metrics. :param logger: The logger. """ self.address = address self.metrics = {} # type: Dict[str, aioprometheus.Collector] self.logger = logger self._loop: Optional[asyncio.AbstractEventLoop] = None self._queue: Optional[asyncio.Queue] = None self._dialogues = PrometheusDialogues() self._host = host self._port = port self._service = aioprometheus.Service() def _get_message_and_dialogue( self, envelope: Envelope ) -> Tuple[PrometheusMessage, Optional[PrometheusDialogue]]: """ Get a message copy and dialogue related to this message. :param envelope: incoming envelope :return: Tuple[Message, Optional[Dialogue]] """ message = cast(PrometheusMessage, envelope.message) dialogue = cast(Optional[PrometheusDialogue], self._dialogues.update(message)) return message, dialogue @property def queue(self) -> asyncio.Queue: """Check queue is set and return queue.""" if self._queue is None: # pragma: nocover raise ValueError("Channel is not connected") return self._queue async def connect(self) -> None: """Start prometheus http server.""" if self._queue: # pragma: nocover return None self._loop = asyncio.get_event_loop() self._queue = asyncio.Queue() await self._service.start(addr=self._host, port=self._port) async def send(self, envelope: Envelope) -> None: """ Process the envelopes to prometheus. :param envelope: envelope """ sender = envelope.sender self.logger.debug("Processing message from {}: {}".format(sender, envelope)) if ( envelope.protocol_specification_id != PrometheusMessage.protocol_specification_id ): raise ValueError( f"Protocol {envelope.protocol_specification_id} is not valid for prometheus." ) await self._handle_prometheus_message(envelope) async def _handle_prometheus_message(self, envelope: Envelope) -> None: """ Handle messages to prometheus. :param envelope: the envelope """ enforce( isinstance(envelope.message, PrometheusMessage), "Message not of type PrometheusMessage", ) message, dialogue = self._get_message_and_dialogue(envelope) if dialogue is None: self.logger.warning( "Could not create dialogue from message={}".format(message) ) return if message.performative == PrometheusMessage.Performative.ADD_METRIC: response = await self._handle_add_metric(message) elif message.performative == PrometheusMessage.Performative.UPDATE_METRIC: response = await self._handle_update_metric(message) else: # pragma: nocover self.logger.warning("Unrecognized performative for PrometheusMessage") return response_code, response_msg = cast(Tuple[int, str], response) msg = dialogue.reply( performative=PrometheusMessage.Performative.RESPONSE, target_message=message, code=response_code, message=response_msg, ) envelope = Envelope(to=msg.to, sender=msg.sender, message=msg) await self._send(envelope) async def _handle_add_metric(self, message: PrometheusMessage) -> Tuple[int, str]: """Handle add metric message. :param message: the message to handle. :return: the response code and response message. """ if message.title in self.metrics: response_code = 409 response_msg = "Metric already exists." else: metric_type = getattr(aioprometheus, message.type, None) if metric_type is None or message.type not in VALID_METRIC_TYPES: response_code = 404 response_msg = f"{message.type} is not a recognized prometheus metric." else: self.metrics[message.title] = metric_type( message.title, message.description, message.labels ) self._service.register(self.metrics[message.title]) response_code = 200 response_msg = ( f"New {message.type} successfully added: {message.title}." ) return response_code, response_msg async def _handle_update_metric( self, message: PrometheusMessage ) -> Tuple[int, str]: """Handle update metric message. :param message: the message to handle. :return: the response code and response message. """ metric = message.title if metric not in self.metrics: response_code = 404 response_msg = f"Metric {metric} not found." else: update_func = getattr(self.metrics[metric], message.callable, None) if update_func is None: response_code = 400 response_msg = ( f"Update function {message.callable} not found for metric {metric}." ) else: if message.callable in VALID_UPDATE_FUNCS: # Update the metric ("inc" and "dec" do not take "value" argument) if message.callable in {"inc", "dec"}: update_func(message.labels) else: update_func(message.labels, message.value) response_code = 200 response_msg = f"Metric {metric} successfully updated." else: response_code = 400 response_msg = f"Failed to update metric {metric}: {message.callable} is not a valid update function." return response_code, response_msg async def _send(self, envelope: Envelope) -> None: """Send a message. :param envelope: the envelope """ await self.queue.put(envelope) async def disconnect(self) -> None: """Disconnect.""" if self._queue is not None: await self._queue.put(None) self._queue = None await self._service.stop() async def get(self) -> Optional[Envelope]: """Get incoming envelope.""" return await self.queue.get() class PrometheusConnection(Connection): """Proxy to the functionality of prometheus.""" connection_id = PUBLIC_ID def __init__(self, **kwargs: Any) -> None: """ Initialize a connection to a local prometheus server. :param kwargs: the keyword arguments of the parent class. """ super().__init__(**kwargs) self.host = cast(str, self.configuration.config.get("host", DEFAULT_HOST)) self.port = cast(int, self.configuration.config.get("port", DEFAULT_PORT)) self.channel = PrometheusChannel( self.address, self.host, self.port, self.logger ) async def connect(self) -> None: """Connect to prometheus server via prometheus channel.""" if self.is_connected: # pragma: nocover return with self._connect_context(): self.channel.logger = self.logger self.state = ConnectionStates.connecting await self.channel.connect() self.state = ConnectionStates.connected async def disconnect(self) -> None: """Disconnect from prometheus server.""" if self.is_disconnected: # pragma: nocover return self.state = ConnectionStates.disconnecting await self.channel.disconnect() self.state = ConnectionStates.disconnected async def send(self, envelope: Envelope) -> None: """ Send an envelope. :param envelope: the envelop """ self._ensure_connected() await self.channel.send(envelope) async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. :param args: positional arguments :param kwargs: keyword arguments :return: The received envelope or None """ self._ensure_connected() try: envelope = await self.channel.get() return envelope except asyncio.CancelledError: # pragma: no cover return None ================================================ FILE: packages/fetchai/connections/prometheus/connection.yaml ================================================ name: prometheus author: fetchai version: 0.9.6 type: connection description: Connection for exposing agent metrics to prometheus server license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmRRMZobBhviy9KjqzJR5NqaL4YaKtE9atwK6Y5mdgZYSM __init__.py: QmW4f9cnBi7hiCqyiNk7Xx4bS1y6yt5B1b64FErHCm8BSA connection.py: QmSXqP7kK42HCoavWUaZHgk7ke3ioaTKQgVFLtCcNpFsPL fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/prometheus:1.1.7 class_name: PrometheusConnection config: host: 127.0.0.1 port: 9090 excluded_protocols: [] restricted_to_protocols: - fetchai/prometheus:1.1.7 dependencies: aioprometheus: version: <21.0.0,>=20.0.0 is_abstract: false ================================================ FILE: packages/fetchai/connections/soef/README.md ================================================ # SOEF connection The SOEF connection is used to connect to an SOEF node. The SOEF provides OEF services of register/unregister and search. ## Usage First, add the connection to your AEA project: `aea add connection fetchai/soef:0.27.6`. Then ensure the `config` in `connection.yaml` matches your need. In particular, make sure `chain_identifier` matches your `default_ledger`. To register/unregister services and perform searches use the `fetchai/oef_search:1.1.7` protocol ================================================ FILE: packages/fetchai/connections/soef/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the Simple OEF connection.""" ================================================ FILE: packages/fetchai/connections/soef/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Extension to the Simple OEF and OEF Python SDK.""" import asyncio import copy import logging import os import re import urllib from asyncio import CancelledError from concurrent.futures._base import CancelledError as ConcurrentCancelledError from concurrent.futures.thread import ThreadPoolExecutor from contextlib import suppress from enum import Enum from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Union, cast from urllib import parse from uuid import uuid4 from defusedxml import ElementTree # pylint: disable=wrong-import-order from aea.common import Address, JSONLike from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.exceptions import enforce from aea.helpers import http_requests as requests from aea.helpers.search.models import ( Constraint, ConstraintTypes, Description, Location, Query, ) from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.oef_search.custom_types import ( AgentsInfo, OefErrorOperation, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage _default_logger = logging.getLogger("aea.packages.fetchai.connections.soef") PUBLIC_ID = PublicId.from_str("fetchai/soef:0.27.6") NOT_SPECIFIED = object() PERSONALITY_PIECES_KEYS = [ "genus", "classification", "architecture", "dynamics.moving", "dynamics.heading", "dynamics.position", "action.buyer", "action.seller", ] class ModelNames(Enum): """Enum of supported data models.""" LOCATION_AGENT = "location_agent" SET_SERVICE_KEY = "set_service_key" REMOVE_SERVICE_KEY = "remove_service_key" PERSONALITY_AGENT = "personality_agent" SEARCH_MODEL = "search_model" PING = "ping" GENERIC_COMMAND = "generic_command" class SOEFException(Exception): """SOEF channel expected exception.""" @classmethod def warning( cls, msg: str, logger: logging.Logger = _default_logger ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.warning(msg) return cls(msg) @classmethod def debug( cls, msg: str, logger: logging.Logger = _default_logger ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.debug(msg) return cls(msg) @classmethod def error( cls, msg: str, logger: logging.Logger = _default_logger ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.error(msg) return cls(msg) @classmethod def exception( cls, msg: str, logger: logging.Logger = _default_logger ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.exception(msg) return cls(msg) OefSearchDialogue = BaseOefSearchDialogue class BaseHandledException(Exception): """Base Exception class.""" MSG: str def __init__(self, exc: Union[Exception, str]) -> None: """Init exception with message or exception instance.""" super().__init__() self.exc = exc def __repr__(self) -> str: """Get exception representation.""" return self.MSG.format(str(self.exc)) def __str__(self) -> str: """Get exception str representation.""" return self.__repr__() class SOEFNetworkConnectionError(BaseHandledException): """Exception class for network connection errors.""" MSG = "" class SOEFServerBadResponseError(BaseHandledException): """Exception class for bad server responses.""" MSG = "" class OefSearchDialogues(BaseOefSearchDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self) -> None: """Initialize dialogues.""" def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The soef connection maintains the dialogue on behalf of the node return OefSearchDialogue.Role.OEF_NODE BaseOefSearchDialogues.__init__( self, self_address=str(SOEFConnection.connection_id.to_any()), role_from_first_message=role_from_first_message, dialogue_class=OefSearchDialogue, ) class SOEFChannel: """The OEFChannel connects the OEF Agent with the connection.""" DEFAULT_CHAIN_IDENTIFIER = "fetchai_v2_testnet_stable" SUPPORTED_CHAIN_IDENTIFIERS = [ re.compile("ethereum"), re.compile("^fetchai(_[a-z0-9_]*)?$"), ] DEFAULT_PERSONALITY_PIECES = ["architecture,agentframework"] NONE_UNIQUE_PAGE_ADDRESS = "" PING_PERIOD = 30 * 60 # 30 minutes FIND_AROUND_ME_REQUEST_DELAY = 2 # seconds def __init__( self, address: Address, api_key: str, is_https: bool, soef_addr: str, soef_port: int, data_dir: str, chain_identifier: Optional[str] = None, token_storage_path: Optional[str] = None, logger: logging.Logger = _default_logger, connection_check_timeout: float = 15.0, connection_check_max_retries: int = 3, ): """ Initialize. :param address: the address of the agent. :param api_key: the SOEF API key. :param is_https: whether htts or http is used. :param soef_addr: the SOEF IP address. :param soef_port: the SOEF port. :param data_dir: the data directory. :param chain_identifier: supported chain id. :param token_storage_path: storage path for the SOEF token. :param logger: the logger. :param connection_check_timeout: timeout to check network connection on connect. :param connection_check_max_retries: maximum retries when performing connection check. """ if chain_identifier is not None and not any( regex.match(chain_identifier) for regex in self.SUPPORTED_CHAIN_IDENTIFIERS ): raise ValueError( f"Unsupported chain_identifier. Valid identifier regular expressions are {', '.join([reg.pattern for reg in self.SUPPORTED_CHAIN_IDENTIFIERS])}" ) self.address = address self.api_key = api_key self.is_https = is_https self.soef_addr = soef_addr self.soef_port = soef_port self.base_url = ( f"https://{soef_addr}:{soef_port}" if self.is_https else f"http://{soef_addr}:{soef_port}" ) self.oef_search_dialogues = OefSearchDialogues() self.connection_check_timeout = connection_check_timeout self.connection_check_max_retries = connection_check_max_retries self._token_storage_path = token_storage_path if self._token_storage_path is not None: if not Path(self._token_storage_path).is_absolute(): self._token_storage_path = os.path.join( data_dir, self._token_storage_path ) Path(self._token_storage_path).touch() self.declared_name = uuid4().hex self._unique_page_address = None # type: Optional[str] self.agent_location = None # type: Optional[Location] self.in_queue = None # type: Optional[asyncio.Queue] self._executor_pool: Optional[ThreadPoolExecutor] = None self.chain_identifier: str = chain_identifier or self.DEFAULT_CHAIN_IDENTIFIER self._loop = None # type: Optional[asyncio.AbstractEventLoop] self._ping_periodic_task: Optional[asyncio.Task] = None self._find_around_me_queue: Optional[asyncio.Queue] = None self._find_around_me_processor_task: Optional[asyncio.Task] = None self.logger = logger self._unregister_lock: Optional[asyncio.Lock] = None @property def unique_page_address(self) -> Optional[str]: """Get unique page address.""" if self._unique_page_address is None: # check if we have it in storage self._unique_page_address = self._get_unique_page_address_from_storage() return self._unique_page_address @unique_page_address.setter def unique_page_address(self, unique_page_address: Optional[str]) -> None: """Set the unique page address.""" self._unique_page_address = unique_page_address self._set_unique_page_address_to_storage(unique_page_address) def _get_unique_page_address_from_storage(self) -> Optional[str]: """Get the unique page address from storage.""" if self._token_storage_path is None: return None with open(self._token_storage_path, "r", encoding="utf-8") as f: result = f.read().strip() unique_page_address = ( result if result != self.NONE_UNIQUE_PAGE_ADDRESS else None ) return unique_page_address def _set_unique_page_address_to_storage( self, unique_page_address: Optional[str] ) -> None: """Set the unique page address to storage.""" if self._token_storage_path is None: return if unique_page_address is None: unique_page_address = self.NONE_UNIQUE_PAGE_ADDRESS with open(self._token_storage_path, "w", encoding="utf-8") as f: f.write(unique_page_address) async def _find_around_me_processor(self) -> None: """Process find me around requests in background task.""" while self._find_around_me_queue is not None: try: task = await self._find_around_me_queue.get() oef_message, oef_search_dialogue, radius, params = task except ( asyncio.CancelledError, CancelledError, GeneratorExit, ): # pylint: disable=try-except-raise # pragma: nocover return except Exception: # pragma: nocover self.logger.exception( "Error on reading messages queue for find around me!" ) raise try: await self._find_around_me_handle_request( oef_message, oef_search_dialogue, radius, params ) await asyncio.sleep(self.FIND_AROUND_ME_REQUEST_DELAY) except ( asyncio.CancelledError, CancelledError, GeneratorExit, ): # pylint: disable=try-except-raise return except SOEFException: # pragma: nocover await self._send_error_response( oef_message, oef_search_dialogue, oef_error_operation=OefSearchMessage.OefErrorOperation.OTHER, ) except Exception as e: # pylint: disable=broad-except # pragma: nocover self.logger.exception( f"Exception occurred in _find_around_me_processor: {e}" ) await self._send_error_response( oef_message, oef_search_dialogue, oef_error_operation=OefSearchMessage.OefErrorOperation.OTHER, ) finally: self.logger.debug("_find_around_me_processor exited") @property def loop(self) -> asyncio.AbstractEventLoop: """Get event loop.""" if self._loop is None: raise ValueError("Loop not set!") # pragma: nocover return self._loop @staticmethod def _is_compatible_query(query: Query) -> bool: """ Check if a query is compatible with the soef. Each query must contain a distance constraint type. :param query: search query to check :return: bool """ constraints = [c for c in query.constraints if isinstance(c, Constraint)] if len(constraints) == 0: # pragma: nocover return False if ConstraintTypes.DISTANCE not in [ c.constraint_type.type for c in constraints ]: # pragma: nocover return False return True def _construct_personality_filter_params( self, equality_constraints: List[Constraint], ) -> Dict[str, List[str]]: """ Construct a dictionary of personality filters. :param equality_constraints: list of equality constraints :return: bool """ filters = copy.copy(self.DEFAULT_PERSONALITY_PIECES) for constraint in equality_constraints: if constraint.attribute_name not in PERSONALITY_PIECES_KEYS: continue filters.append( constraint.attribute_name + "," + constraint.constraint_type.value ) if not filters: # pragma: nocover return {} return {"ppfilter": filters} @staticmethod def _construct_service_key_filter_params( equality_constraints: List[Constraint], ) -> Dict[str, List[str]]: """ Construct a dictionary of service keys filters. We assume each equality constraint which is not a personality piece relates to a service key! :param equality_constraints: list of equality constraints :return: bool """ filters = [] for constraint in equality_constraints: if constraint.attribute_name in PERSONALITY_PIECES_KEYS: continue filters.append( constraint.attribute_name + "," + constraint.constraint_type.value ) if not filters: # pragma: nocover return {} return {"skfilter": filters} async def send(self, envelope: Envelope) -> None: """ Send message handler. :param envelope: the envelope. """ await self.process_envelope(envelope) async def _request_text(self, *args: Any, **kwargs: Any) -> str: """Perform and http request and return text of response.""" def _do_request() -> requests.Response: return requests.request(*args, **kwargs) try: response = await self.loop.run_in_executor(self._executor_pool, _do_request) except requests.ConnectionError as e: raise SOEFNetworkConnectionError(e) from e if response.status_code < 200 or response.status_code >= 300: raise SOEFServerBadResponseError( f"Bad server response: code {response.status_code} when 2XX expected. Request data: ({args}, {kwargs}) Response content: `{response.text}`" ) if not response.text: raise SOEFServerBadResponseError( f"Bad server response: empty response. Request data: ({args}, {kwargs})" ) return response.text async def process_envelope(self, envelope: Envelope) -> None: """ Process envelope. :param envelope: the envelope. """ enforce( isinstance(envelope.message, OefSearchMessage), "Message not of type OefSearchMessage", ) oef_message = cast(OefSearchMessage, envelope.message) oef_search_dialogue = cast( OefSearchDialogue, self.oef_search_dialogues.update(oef_message) ) if oef_search_dialogue is None: # pragma: nocover raise ValueError( "Could not create dialogue for message={}".format(oef_message) ) err_ops = OefSearchMessage.OefErrorOperation oef_error_operation = err_ops.OTHER try: if self.unique_page_address is None: # pragma: nocover # first time registration or we previously unregistered await self._register_agent() handlers_and_errors = { OefSearchMessage.Performative.REGISTER_SERVICE: ( self.register_service, err_ops.REGISTER_SERVICE, ), OefSearchMessage.Performative.UNREGISTER_SERVICE: ( self.unregister_service, err_ops.UNREGISTER_SERVICE, ), OefSearchMessage.Performative.SEARCH_SERVICES: ( self.search_services, err_ops.SEARCH_SERVICES, ), } if oef_message.performative not in handlers_and_errors: raise ValueError("OEF request not recognized.") # pragma: nocover handler, oef_error_operation = handlers_and_errors[oef_message.performative] await handler(oef_message, oef_search_dialogue) except SOEFException: await self._send_error_response( oef_message, oef_search_dialogue, oef_error_operation=oef_error_operation, ) except (asyncio.CancelledError, ConcurrentCancelledError): # pragma: nocover pass except Exception as e: # pylint: disable=broad-except # pragma: nocover if "Forbiddenalready in lobby" in str(e): raise ValueError( "Could not register with SOEF. Agent address already registered from elsewhere." ) self.logger.exception(f"Exception during envelope processing: {e}") await self._send_error_response( oef_message, oef_search_dialogue, oef_error_operation=oef_error_operation, ) async def register_service( self, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Register a service on the SOEF. :param oef_message: OefSearchMessage :param oef_search_dialogue: OefSearchDialogue """ service_description = oef_message.service_description data_model_handlers = { "location_agent": self._register_location_handler, "personality_agent": self._set_personality_piece_handler, "set_service_key": self._set_service_key_handler, "ping": self._ping_handler, "generic_command": self._generic_command_handler, } # type: Dict[str, Callable] data_model_name = service_description.data_model.name if data_model_name not in data_model_handlers: raise SOEFException.error( f'Data model name: {data_model_name} is not supported for `register`. Valid models for performative {oef_message.performative} are: {", ".join(data_model_handlers.keys())}' ) handler = data_model_handlers[data_model_name] await handler(service_description, oef_message, oef_search_dialogue) async def _ping_handler( self, service_description: Description, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Perform ping command. :param service_description: Service description :param oef_message: the oef message. :param oef_search_dialogue: the oef search dialogue """ self._check_data_model(service_description, ModelNames.PING.value) await self._ping_command() await self._send_success_response(oef_message, oef_search_dialogue) async def _generic_command_handler( self, service_description: Description, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Perform ping command. :param service_description: Service description :param oef_message: the oef message. :param oef_search_dialogue: the oef search dialogue """ if not self.in_queue: # pragma: no cover """not connected.""" return self._check_data_model(service_description, ModelNames.GENERIC_COMMAND.value) command = service_description.values.get("command", None) params = service_description.values.get("parameters", {}) if params: params = urllib.parse.parse_qs(params) parsed_text = await self._generic_oef_command(command, params) content = ElementTree.tostring(parsed_text) agents_info: JSONLike = { "response": {"content": content}, "command": service_description.values, } await self._send_success_response(oef_message, oef_search_dialogue, agents_info) async def _ping_command(self) -> None: """Perform ping on registered agent.""" await self._generic_oef_command("ping", {}, check_success=False) async def _ping_periodic(self, period: float = 30 * 60) -> None: """ Send ping command every `period`. :param period: period of ping in seconds """ with suppress(asyncio.CancelledError): while self.unique_page_address: try: await self._ping_command() except ( asyncio.CancelledError, ConcurrentCancelledError, ): # pragma: nocover # pylint: disable=try-except-raise raise except Exception: # pylint: disable=broad-except # pragma: nocover self.logger.exception("Error on periodic ping command!") await asyncio.sleep(period) async def _set_service_key_handler( self, service_description: Description, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Set service key from service description. :param service_description: Service description :param oef_message: the oef message. :param oef_search_dialogue: the oef search dialogue """ self._check_data_model(service_description, ModelNames.SET_SERVICE_KEY.value) key = service_description.values.get("key", None) value = service_description.values.get("value", NOT_SPECIFIED) if key is None or value is NOT_SPECIFIED: # pragma: nocover raise SOEFException.error("Bad values provided!") await self._set_service_key(key, value) await self._send_success_response(oef_message, oef_search_dialogue) @staticmethod def _parse_soef_response( response_text: str, check_success: bool = True ) -> ElementTree: try: root = ElementTree.fromstring(response_text) except ElementTree.ParseError as e: # pragma: nocover raise SOEFServerBadResponseError( f"Failed to parse xml from the response: Error {e}. Text: {response_text}" ) from e if root.tag != "response": raise SOEFServerBadResponseError( "Not a valid response. Expected `root.tag = response`, received `root.tag = {root.tag}`" ) if check_success: el = root.find("./success") if el is None: raise SOEFServerBadResponseError( "Bad response, no success value present. Found = {response_text}." ) if str(el.text).strip() != "1": raise SOEFServerBadResponseError( # pragma: nocover "Request was not successful. Found = {response_text}" ) return root async def _generic_oef_command( self, command: str, params: Optional[Dict[str, Union[str, List[str]]]] = None, unique_page_address: Optional[str] = None, check_success: bool = True, ) -> ElementTree: """ Set service key from service description. :param command: the command :param params: the parameters of the command :param unique_page_address: the unique page address :param check_success: whether or not to check for success :return: parsed xml ElementTree """ params = params or {} self.logger.debug(f"Perform `{command}` with {params}") url = parse.urljoin( self.base_url, unique_page_address or self.unique_page_address ) response_text = "" try: response_text = await self._request_text( "get", url=url, params={"command": command, **params} ) parsed_text = self._parse_soef_response(response_text, check_success) self.logger.debug(f"`{command}` SUCCESS!") return parsed_text except ( asyncio.CancelledError, ConcurrentCancelledError, ): # pragma: nocover # pylint: disable=try-except-raise raise except Exception as e: raise SOEFException.error( f"Command: `{command}` Params: `{params}` Response: `{response_text}` Exception: {[e]}" ) from e async def _set_service_key(self, key: str, value: Union[str, int, float]) -> None: """ Perform set service key command. :param key: key to set :param value: value to set """ await self._generic_oef_command( "set_service_key", {"key": key, "value": str(value)} ) async def _remove_service_key_handler( self, service_description: Description, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Remove service key from service description. :param service_description: Service description :param oef_message: the oef message. :param oef_search_dialogue: the oef search dialogue """ self._check_data_model(service_description, ModelNames.REMOVE_SERVICE_KEY.value) key = service_description.values.get("key", None) if key is None: # pragma: nocover raise SOEFException.error("Bad values provided!") await self._remove_service_key(key) await self._send_success_response(oef_message, oef_search_dialogue) async def _remove_service_key(self, key: str) -> None: """ Perform remove service key command. :param key: key to remove """ await self._generic_oef_command("remove_service_key", {"key": key}) async def _register_location_handler( self, service_description: Description, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Register service with location. :param service_description: Service description :param oef_message: the oef message. :param oef_search_dialogue: the oef search dialogue """ self._check_data_model(service_description, ModelNames.LOCATION_AGENT.value) agent_location = service_description.values.get("location", None) if agent_location is None or not isinstance( agent_location, Location ): # pragma: nocover raise SOEFException.debug("Bad location provided.") disclosure_accuracy = service_description.values.get( "disclosure_accuracy", None ) if disclosure_accuracy not in [ None, "none", "low", "medium", "high", "maximum", ]: raise SOEFException.debug("Bad disclosure_accuracy.") # pragma: nocover await self._set_location(agent_location, disclosure_accuracy) await self._send_success_response(oef_message, oef_search_dialogue) async def _send_success_response( self, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, agents_info: Optional[JSONLike] = None, ) -> None: """ Send success message in response to the given dialogue and message. :param oef_search_dialogue: the oef search dialogue :param oef_message: the oef message :param agents_info: the agents info json """ if self.in_queue is None: raise ValueError("Inqueue not set!") # pragma: nocover if agents_info is None: agents_info = {} message = oef_search_dialogue.reply( performative=OefSearchMessage.Performative.SUCCESS, target_message=oef_message, agents_info=AgentsInfo(cast(Dict[str, Any], agents_info)), ) envelope = Envelope(to=message.to, sender=message.sender, message=message) await self.in_queue.put(envelope) @staticmethod def _check_data_model( service_description: Description, data_model_name: str ) -> None: """ Check data model corresponds. Raise exception if not. :param service_description: Service description :param data_model_name: data model name expected. """ if service_description.data_model.name != data_model_name: # pragma: nocover raise SOEFException.error( f"Bad service description! expected {data_model_name} but go {service_description.data_model.name}" ) async def _set_location( self, agent_location: Location, disclosure_accuracy: Optional[str] = None ) -> None: """ Set the location. :param agent_location: the agent location :param disclosure_accuracy: the accuracy of the agent location disclosure """ latitude = agent_location.latitude longitude = agent_location.longitude params: Dict[str, Union[str, List[str]]] = { "longitude": str(longitude), "latitude": str(latitude), } await self._generic_oef_command("set_position", params) if disclosure_accuracy: params = {"accuracy": disclosure_accuracy} await self._generic_oef_command( "set_find_position_disclosure_accuracy", params, ) self.agent_location = agent_location async def _set_personality_piece_handler( self, service_description: Description, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Set the personality piece. :param service_description: the service description. :param oef_message: the oef message. :param oef_search_dialogue: the oef search dialogue """ self._check_data_model(service_description, ModelNames.PERSONALITY_AGENT.value) piece = service_description.values.get("piece", None) value = service_description.values.get("value", None) if not (isinstance(piece, str) and isinstance(value, str)): # pragma: nocover raise SOEFException.debug("Personality piece bad values provided.") await self._set_personality_piece(piece, value) await self._send_success_response(oef_message, oef_search_dialogue) async def _set_personality_piece(self, piece: str, value: str) -> None: """ Set the personality piece. :param piece: the piece to be set :param value: the value to be set """ params: Dict[str, Union[str, List[str]]] = { "piece": piece, "value": value, } await self._generic_oef_command("set_personality_piece", params) async def _register_agent(self) -> None: """ Register an agent on the SOEF. Includes the following steps: - apply to lobby to receive unique page address and token - acknowledge registration - set default personality piece for agent framework - initiate ping task """ self.logger.debug("Applying to SOEF lobby with address={}".format(self.address)) url = parse.urljoin(self.base_url, "register") params: Dict[str, Union[str, List[str]]] = { "api_key": self.api_key, "chain_identifier": self.chain_identifier, "address": self.address, "declared_name": self.declared_name, } response_text = await self._request_text("get", url=url, params=params) root = self._parse_soef_response(response_text, check_success=False) self.logger.debug("Root tag: {}".format(root.tag)) unique_page_address = "" unique_token = "" # nosec for child in root: self.logger.debug( "Child tag={}, child attrib={}, child text={}".format( child.tag, child.attrib, child.text ) ) if child.tag == "page_address" and child.text is not None: unique_page_address = child.text if child.tag == "token" and child.text is not None: unique_token = child.text if not (len(unique_page_address) > 0 and len(unique_token) > 0): raise SOEFException.error( f"Agent registration error - page address or token not received. Response text: {response_text}" ) self.logger.debug("Registering agent") params = {"token": unique_token} await self._generic_oef_command( "acknowledge", params, unique_page_address=unique_page_address ) self.unique_page_address = unique_page_address await self._set_personality_piece("architecture", "agentframework") self.start_periodic_ping_task() def start_periodic_ping_task(self) -> None: """Start the periodic ping task.""" if self._loop and not self._ping_periodic_task: self._ping_periodic_task = self._loop.create_task( self._ping_periodic(self.PING_PERIOD) ) async def _send_error_response( self, oef_search_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, oef_error_operation: OefErrorOperation = OefSearchMessage.OefErrorOperation.OTHER, ) -> None: """ Send an error response back. :param oef_search_message: the oef search message :param oef_search_dialogue: the oef search dialogue :param oef_error_operation: the error code to send back """ if self.in_queue is None: raise ValueError("Inqueue not set!") # pragma: nocover message = oef_search_dialogue.reply( performative=OefSearchMessage.Performative.OEF_ERROR, target_message=oef_search_message, oef_error_operation=oef_error_operation, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) await self.in_queue.put(envelope) async def unregister_service( self, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Unregister a service on the SOEF. :param oef_message: OefSearchMessage :param oef_search_dialogue: OefSearchDialogue """ service_description = oef_message.service_description data_model_handlers = { "location_agent": self._unregister_agent, "remove_service_key": self._remove_service_key_handler, } # type: Dict[str, Callable] data_model_name = service_description.data_model.name if data_model_name not in data_model_handlers: # pragma: nocover raise SOEFException.error( f'Data model name: {data_model_name} is not supported for `unregister`. Valid models for performative {oef_message.performative} are: {", ".join(data_model_handlers.keys())}' ) handler = data_model_handlers[data_model_name] if data_model_name == "location_agent": await handler(oef_message, oef_search_dialogue) else: await handler(service_description, oef_message, oef_search_dialogue) async def _unregister_agent( self, oef_message: Optional[OefSearchMessage] = None, oef_search_dialogue: Optional[OefSearchDialogue] = None, ) -> None: """ Unregister a location agent from the SOEF. :param oef_message: OefSearchMessage :param oef_search_dialogue: OefSearchDialogue """ if not self._unregister_lock: raise ValueError( # pragma: nocover "unregistered lock is not set, please call connect!" ) async with self._unregister_lock: if self.unique_page_address is None: # pragma: nocover self.logger.debug( "The service is not registered to the simple OEF. Cannot unregister." ) return task = asyncio.ensure_future( self._generic_oef_command("unregister", check_success=False) ) try: response = await asyncio.shield(task) finally: response = await task if ( "Goodbye!" not in response ): # pragma: nocover self.logger.debug(f"No Goodbye response. Response={response}") self.unique_page_address = None await self._stop_periodic_ping_task() if oef_message is not None and oef_search_dialogue is not None: await self._send_success_response(oef_message, oef_search_dialogue) async def _stop_periodic_ping_task(self) -> None: """Cancel periodic ping task.""" if self._ping_periodic_task and not self._ping_periodic_task.done(): self._ping_periodic_task.cancel() self._ping_periodic_task.cancel() with suppress(asyncio.CancelledError): await self._ping_periodic_task self._ping_periodic_task = None async def _check_server_reachable(self) -> None: """Check network connection is ok.""" try: await asyncio.wait_for( self._request_text( "get", self.base_url, timeout=self.connection_check_timeout ), timeout=self.connection_check_timeout, ) except asyncio.TimeoutError: raise SOEFNetworkConnectionError( f"Server can not be reached within timeout={self.connection_check_timeout}!" ) async def connect(self) -> None: """Connect channel set queues and executor pool.""" self._loop = asyncio.get_event_loop() reachable_check_count = 0 while reachable_check_count < self.connection_check_max_retries: reachable_check_count += 1 try: await self._check_server_reachable() reachable_check_count = self.connection_check_max_retries except Exception as e: # pylint: disable=broad-except # pragma: nocover if reachable_check_count == self.connection_check_max_retries: raise e self.logger.debug(f"Exception during SOEF reachability check: {e}.") self.in_queue = asyncio.Queue() self._find_around_me_queue = asyncio.Queue() self._unregister_lock = asyncio.Lock() self._executor_pool = ThreadPoolExecutor(max_workers=10) self._find_around_me_processor_task = self._loop.create_task( self._find_around_me_processor() ) # make sure we first unregister, in case of improper previous termination try: await self._unregister_agent() except Exception as e: # pylint: disable=broad-except # pragma: nocover if "Bad Requestagent lookup failed" not in str(e): # i.e. we're not trying to unregister and already unregistered agent raise e self.logger.debug("Unregister on SOEF failed. Agent not registered.") self.unique_page_address = None async def disconnect(self) -> None: """Disconnect unregisters any potential services still registered.""" await self._stop_periodic_ping_task() if self.in_queue is None: raise ValueError("Queue is not set, use connect first!") # pragma: nocover if self._find_around_me_processor_task: if not self._find_around_me_processor_task.done(): self._find_around_me_processor_task.cancel() await self._find_around_me_processor_task try: await self._unregister_agent() except Exception as e: # pylint: disable=broad-except # pragma: nocover self.logger.exception(str(e)) await self.in_queue.put(None) self._find_around_me_queue = None async def search_services( self, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Search services on the SOEF. :param oef_message: OefSearchMessage :param oef_search_dialogue: OefSearchDialogue """ query = oef_message.query if not self._is_compatible_query(query): raise SOEFException.warning( "Service query incompatible with SOEF: constraints={}".format( query.constraints ) ) constraints = [cast(Constraint, c) for c in query.constraints] constraint_distance = [ c for c in constraints if c.constraint_type.type == ConstraintTypes.DISTANCE ][0] service_location, radius = constraint_distance.constraint_type.value equality_constraints = [ c for c in constraints if c.constraint_type.type == ConstraintTypes.EQUAL ] params = {} params.update(self._construct_personality_filter_params(equality_constraints)) params.update(self._construct_service_key_filter_params(equality_constraints)) if self.agent_location is None or self.agent_location != service_location: # we update the location to match the query. await self._set_location(service_location) # pragma: nocover await self._find_around_me(oef_message, oef_search_dialogue, radius, params) async def _find_around_me( self, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, radius: float, params: Dict[str, List[str]], ) -> None: """ Add find agent task to queue to process in dedicated loop respectful to timeouts. :param oef_message: OefSearchMessage :param oef_search_dialogue: OefSearchDialogue :param radius: the radius in which to search :param params: the parameters for the query """ if not self._find_around_me_queue: raise ValueError("SOEFChannel not started.") # pragma: nocover await self._find_around_me_queue.put( (oef_message, oef_search_dialogue, radius, params) ) async def _find_around_me_handle_request( self, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, radius: float, params: Dict[str, List[str]], ) -> None: """ Find agents around me. :param oef_message: OefSearchMessage :param oef_search_dialogue: OefSearchDialogue :param radius: the radius in which to search :param params: the parameters for the query """ if self.in_queue is None: raise ValueError("Inqueue not set!") # pragma: nocover self.logger.debug("Searching in radius={} of myself".format(radius)) root = await self._generic_oef_command( "find_around_me", {"range_in_km": [str(radius)], **params} ) agents = {} # type: Dict[str, Dict[str, Union[str, Dict[str, str]]]] for agent in root.findall(path=".//agent"): chain_identifier = "" for identities in agent.findall("identities"): for identity in identities.findall("identity"): for ( chain_identifier_key, chain_identifier_name, ) in identity.items(): if chain_identifier_key == "chain_identifier": chain_identifier = chain_identifier_name agent_address = identity.text range_in_km = agent.find("range_in_km").text agents[agent_address] = { "chain_identifier": chain_identifier, "address": agent_address, "range_in_km": range_in_km, **agent.attrib, } location = agent.find("./location") if location: agents[agent_address]["location"] = { **location.attrib, "longitude": location.find("longitude").text, "latitude": location.find("latitude").text, } message = oef_search_dialogue.reply( performative=OefSearchMessage.Performative.SEARCH_RESULT, target_message=oef_message, agents=tuple(agents.keys()), agents_info=AgentsInfo(agents), ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) await self.in_queue.put(envelope) class SOEFConnection(Connection): """The SOEFConnection connects the Simple OEF to the mailbox.""" connection_id = PUBLIC_ID DEFAULT_CONNECTION_CHECK_TIMEOUT: float = 15.0 DEFAULT_CONNECTION_CHECK_MAX_RETRIES: int = 3 def __init__(self, **kwargs: Any) -> None: """Initialize.""" if kwargs.get("configuration") is None: # pragma: nocover kwargs["excluded_protocols"] = kwargs.get("excluded_protocols") or [] kwargs["restricted_to_protocols"] = kwargs.get("excluded_protocols") or [ OefSearchMessage.protocol_id ] super().__init__(**kwargs) api_key = cast(str, self.configuration.config.get("api_key")) connection_check_timeout = cast( float, self.configuration.config.get( "connection_check_timeout", self.DEFAULT_CONNECTION_CHECK_TIMEOUT ), ) connection_check_max_retries = cast( int, self.configuration.config.get( "connection_check_max_retries", self.DEFAULT_CONNECTION_CHECK_MAX_RETRIES, ), ) is_https = cast(bool, self.configuration.config.get("is_https", True)) soef_addr = cast(str, self.configuration.config.get("soef_addr")) soef_port = cast(int, self.configuration.config.get("soef_port")) chain_identifier = cast(str, self.configuration.config.get("chain_identifier")) token_storage_path = cast( Optional[str], self.configuration.config.get("token_storage_path") ) not_none_params = { "api_key": api_key, "soef_addr": soef_addr, "soef_port": soef_port, } for param_name, param_value in not_none_params.items(): # pragma: nocover if param_value is None: raise ValueError(f"{param_name} must be set!") self.api_key = api_key self.is_https = is_https self.soef_addr = soef_addr self.soef_port = soef_port self.channel = SOEFChannel( self.address, self.api_key, self.is_https, self.soef_addr, self.soef_port, data_dir=self.data_dir, chain_identifier=chain_identifier, token_storage_path=token_storage_path, connection_check_timeout=connection_check_timeout, connection_check_max_retries=connection_check_max_retries, ) async def connect(self) -> None: """ Connect to the channel. :raises Exception if the connection to the OEF fails. """ if self.is_connected: # pragma: nocover return with self._connect_context(): await self.channel.connect() @property def in_queue(self) -> Optional[asyncio.Queue]: """Return in_queue of the channel.""" return self.channel.in_queue async def disconnect(self) -> None: """Disconnect from the channel.""" if self.is_disconnected: # pragma: nocover return if self.in_queue is None: raise ValueError("In queue not set.") # pragma: nocover self.state = ConnectionStates.disconnecting await self.channel.disconnect() self.state = ConnectionStates.disconnected async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. Blocking. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ try: if self.in_queue is None: raise ValueError("In queue not set.") # pragma: nocover envelope = await self.in_queue.get() if envelope is None: # pragma: nocover self.logger.debug("Received None.") return None self.logger.debug("Received envelope: {}".format(envelope)) return envelope except CancelledError: self.logger.debug("Receive cancelled.") return None except Exception as e: # pragma: nocover # pylint: disable=broad-except self.logger.exception(e) return None async def send(self, envelope: "Envelope") -> None: """ Send an envelope. :param envelope: the envelope to send. """ if self.is_connected: await self.channel.send(envelope) ================================================ FILE: packages/fetchai/connections/soef/connection.yaml ================================================ name: soef author: fetchai version: 0.27.6 type: connection description: The soef connection provides a connection api to the simple OEF. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qma5vKjAp6cPoDKM7fxTDhTbzKbemEWQecfk7kosHymptF __init__.py: QmTCY2JASjfXJdt9ywBE5pejcXKvbrtSNCzJ9uiiEoHKFm connection.py: QmZjpVuG4x9m9nFtAitdZ5MbJdBXFgrYHtkVrxUq7QL8c5 fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/oef_search:1.1.7 class_name: SOEFConnection config: api_key: TwiCIriSl0mLahw17pyqoA chain_identifier: fetchai_v2_testnet_stable connection_check_max_retries: 3 connection_check_timeout: 15.0 is_https: true soef_addr: s-oef.fetch.ai soef_port: 443 token_storage_path: soef_token.txt excluded_protocols: [] restricted_to_protocols: - fetchai/oef_search:1.1.7 dependencies: defusedxml: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/stub/README.md ================================================ # Stub connection A simple connection for communication with an AEA, using the file system as a point of data exchange. ## Usage First, add the connection to your AEA project: `aea add connection fetchai/stub:0.21.3`. (If you have created your AEA project with `aea create` then the connection will already be available by default.) Optionally, in the `connection.yaml` file under `config` set the `input_file` and `output_file` to the desired file path. The `stub` connection reads encoded envelopes from the `input_file` and writes encoded envelopes to the `output_file`. ================================================ FILE: packages/fetchai/connections/stub/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the stub connection.""" ================================================ FILE: packages/fetchai/connections/stub/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the stub connection.""" import asyncio import logging import os import re import typing from asyncio import CancelledError from asyncio.tasks import Task from concurrent.futures.thread import ThreadPoolExecutor from pathlib import Path from typing import Any, AsyncIterable, List, Optional from aea.configurations.base import PublicId from aea.configurations.constants import ( DEFAULT_INPUT_FILE_NAME, DEFAULT_OUTPUT_FILE_NAME, ) from aea.connections.base import Connection, ConnectionStates from aea.helpers.file_io import envelope_from_bytes, lock_file, write_envelope from aea.mail.base import Envelope _default_logger = logging.getLogger("aea.packages.fetchai.connections.stub") INPUT_FILE_KEY = "input_file" OUTPUT_FILE_KEY = "output_file" SEPARATOR = b"," PUBLIC_ID = PublicId.from_str("fetchai/stub:0.21.3") class StubConnection(Connection): r"""A stub connection. This connection uses two files to communicate: one for the incoming messages and the other for the outgoing messages. Each line contains an encoded envelope. The format of each line is the following: TO,SENDER,PROTOCOL_ID,ENCODED_MESSAGE e.g.: recipient_agent,sender_agent,default,{"type": "bytes", "content": "aGVsbG8="} The connection detects new messages by watchdogging the input file looking for new lines. To post a message on the input file, you can use e.g. echo "..." >> input_file or: #>>> fp = open(DEFAULT_INPUT_FILE_NAME, "ab+") #>>> fp.write(b"...\n") It is discouraged adding a message with a text editor since the outcome depends on the actual text editor used. """ connection_id = PUBLIC_ID message_regex = re.compile( (b"[^" + SEPARATOR + b"]*" + SEPARATOR) * 3 + b".*,[\n]?", re.DOTALL ) read_delay = 0.001 def __init__(self, **kwargs: Any): """Initialize a stub connection.""" super().__init__(**kwargs) input_file: str = self.configuration.config.get( INPUT_FILE_KEY, DEFAULT_INPUT_FILE_NAME ) output_file: str = self.configuration.config.get( OUTPUT_FILE_KEY, DEFAULT_OUTPUT_FILE_NAME ) if not Path(input_file).is_absolute(): input_file = os.path.join(self.data_dir, input_file) if not Path(output_file).is_absolute(): output_file = os.path.join(self.data_dir, output_file) input_file_path = Path(input_file) output_file_path = Path(output_file) if not input_file_path.exists(): input_file_path.touch() self.input_file_path = input_file_path self.output_file_path = output_file_path self.input_file: Optional[typing.IO] = None self.output_file: Optional[typing.IO] = None self.in_queue = None # type: Optional[asyncio.Queue] self._read_envelopes_task: Optional[Task] = None self._write_pool = ThreadPoolExecutor( max_workers=1, thread_name_prefix="stub_connection_writer_" ) # sequential write only! but threaded! def _open_files(self) -> None: """Open file to read and write.""" self.input_file = open( # pylint: disable=consider-using-with self.input_file_path, "rb+" ) self.output_file = open( # pylint: disable=consider-using-with self.output_file_path, "wb+" ) def _close_files(self) -> None: """Close opened files.""" if self.input_file: self.input_file.close() if self.output_file: self.output_file.close() async def _file_read_and_trunc(self, delay: float = 0.001) -> AsyncIterable[bytes]: """ Generate input file read chunks and truncate data already read. :param delay: float, delay on empty read. :yield: async generator return file read bytes. """ if not self.input_file: # pragma: nocover raise ValueError("Input file not opened! Call Connection.connect first.") while True: if self.input_file.closed: # pragma: nocover return with lock_file(self.input_file): data = self.input_file.read() if data: self.input_file.truncate(0) self.input_file.seek(0) if data: yield data else: await asyncio.sleep(delay) async def read_envelopes(self) -> None: """Read envelopes from input file, decode and put into in_queue.""" self._ensure_connected() if self.in_queue is None: # pragma: nocover raise ValueError("Input queue not initialized.") self.logger.debug("Read messages!") async for data in self._file_read_and_trunc(delay=self.read_delay): lines = self._split_messages(data) for line in lines: envelope = envelope_from_bytes(line, SEPARATOR, self.logger) if envelope is None: continue self.logger.debug(f"Add envelope {envelope}") await self.in_queue.put(envelope) @classmethod def _split_messages(cls, data: bytes) -> List[bytes]: """ Split binary data on messages. :param data: bytes :return: list of bytes """ return [m.group(0) for m in cls.message_regex.finditer(data)] async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """Receive an envelope.""" self._ensure_connected() if self.in_queue is None: # pragma: nocover self.logger.error("Input queue not initialized.") return None try: envelope = await self.in_queue.get() return envelope except (CancelledError, asyncio.TimeoutError): # pragma: no cover self.logger.debug("Receive cancelled.") raise except Exception: # pylint: disable=broad-except self.logger.exception("Stub connection receive error:") return None async def connect(self) -> None: """Set up the connection.""" if self.is_connected: return with self._connect_context(): self.in_queue = asyncio.Queue() self._open_files() self._read_envelopes_task = self.loop.create_task(self.read_envelopes()) async def _stop_read_envelopes(self) -> None: """ Stop read envelopes task. Cancel task and wait for completed. """ if not self._read_envelopes_task: return # pragma: nocover if not self._read_envelopes_task.done(): self._read_envelopes_task.cancel() try: await self._read_envelopes_task except CancelledError: pass # task was cancelled, that was expected except BaseException: # pragma: nocover # pylint: disable=broad-except self.logger.exception( "during envelop read" ) # do not raise exception cause it's on task stop async def disconnect(self) -> None: """ Disconnect from the channel. In this type of connection there's no channel to disconnect. """ if self.is_disconnected: return if self.in_queue is None: # pragma: nocover raise ValueError("Input queue not initialized.") self.state = ConnectionStates.disconnecting await self._stop_read_envelopes() self._write_pool.shutdown(wait=True) # wait write operation to complete self.in_queue.put_nowait(None) self._close_files() self.state = ConnectionStates.disconnected async def send(self, envelope: Envelope) -> None: """ Send messages. :param envelope: the envelope """ self._ensure_connected() self._ensure_valid_envelope_for_external_comms(envelope) if not self.output_file: # pragma: nocover raise ValueError( "output_file file not opened! Call Connection.connect first." ) await self.loop.run_in_executor( self._write_pool, write_envelope, envelope, self.output_file, SEPARATOR, self.logger, ) ================================================ FILE: packages/fetchai/connections/stub/connection.yaml ================================================ name: stub author: fetchai version: 0.21.3 type: connection description: The stub connection implements a connection stub which reads/writes messages from/to file. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmVtTqPDAvnMjj2W97E1u84NDLREZZ1Qcp8csVGRi8TjHL __init__.py: QmU3CUkFsuMuBuFtcrCkUpt7ydGXRfM5PtBmamTk3yP2uP connection.py: Qmb379EQWBCrqXw9PiTpsbeRtzPyYXiwDVXKJLWwYR2wXN fingerprint_ignore_patterns: [] connections: [] protocols: [] class_name: StubConnection config: input_file: ./input_file output_file: ./output_file excluded_protocols: [] restricted_to_protocols: [] dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/tcp/README.md ================================================ # TCP client connection A simple TCP client/server connection to use the TCP protocol for sending and receiving envelopes. ## Usage Add the connection to your AEA project: `aea add connection fetchai/tcp:0.17.3`. ================================================ FILE: packages/fetchai/connections/tcp/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of a TCP connection.""" ================================================ FILE: packages/fetchai/connections/tcp/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Base classes for TCP communication.""" import logging import struct from abc import ABC, abstractmethod from asyncio import CancelledError, StreamReader, StreamWriter from typing import Any, Optional from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope _default_logger = logging.getLogger("aea.packages.fetchai.connections.tcp") PUBLIC_ID = PublicId.from_str("fetchai/tcp:0.17.3") class TCPConnection(Connection, ABC): """Abstract TCP connection.""" connection_id = PUBLIC_ID def __init__(self, host: str, port: int, **kwargs: Any) -> None: """ Initialize a TCP connection. :param host: the socket bind address. :param port: the socket bind port. :param kwargs: keyword arguments. """ super().__init__(**kwargs) # for the server, the listening address/port # for the client, the server address/port self.host = host self.port = port @abstractmethod async def setup(self) -> None: """Set the TCP connection up.""" @abstractmethod async def teardown(self) -> None: """Tear the TCP connection down.""" @abstractmethod def select_writer_from_envelope(self, envelope: Envelope) -> Optional[StreamWriter]: """ Select the destination, given the envelope. :param envelope: the envelope to be sent. :return: the stream writer to communicate with the recipient. None if it cannot be determined. """ async def connect(self) -> None: """Set up the connection.""" if self.is_connected: # pragma: nocover self.logger.warning("Connection already set up.") return self.state = ConnectionStates.connecting try: await self.setup() self.state = ConnectionStates.connected except Exception as e: # pragma: nocover # pylint: disable=broad-except self.logger.error(str(e)) self.state = ConnectionStates.disconnected async def disconnect(self) -> None: """Tear down the connection.""" if self.is_disconnected: # pragma: nocover self.logger.warning("Connection already disconnected.") return self.state = ConnectionStates.disconnecting await self.teardown() self.state = ConnectionStates.disconnected async def _recv(self, reader: StreamReader) -> Optional[bytes]: """Receive bytes.""" data = await reader.read(len(struct.pack("I", 0))) if not self.is_connected: return None nbytes = struct.unpack("I", data)[0] nbytes_read = 0 data = b"" while nbytes_read < nbytes: data += await reader.read(nbytes - nbytes_read) nbytes_read = len(data) return data async def _send(self, writer: StreamWriter, data: bytes) -> None: self.logger.debug("[{}] Send a message".format(self.address)) nbytes = struct.pack("I", len(data)) self.logger.debug("#bytes: {!r}".format(nbytes)) try: writer.write(nbytes) writer.write(data) await writer.drain() except CancelledError: return None async def send(self, envelope: Envelope) -> None: """ Send an envelope. :param envelope: the envelope to send. """ self._ensure_valid_envelope_for_external_comms(envelope) writer = self.select_writer_from_envelope(envelope) if writer is not None: data = envelope.encode() await self._send(writer, data) else: self.logger.error( "[{}]: Cannot send envelope {}".format(self.address, envelope) ) ================================================ FILE: packages/fetchai/connections/tcp/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ # pragma: nocover # noqa: E800 """Base classes for TCP communication.""" from .tcp_client import ( # noqa: F401 # pylint: disable=unused-import TCPClientConnection, ) from .tcp_server import ( # noqa: F401 # pylint: disable=unused-import TCPServerConnection, ) ================================================ FILE: packages/fetchai/connections/tcp/connection.yaml ================================================ name: tcp author: fetchai version: 0.17.3 type: connection description: The tcp connection implements a tcp server and client. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qmc2px6Bbjnf44wPB56Y2gYroNE1gfjEszTntnTWkwsUzX __init__.py: Qmb3vSwEJwhNEaV899VUrwEkUatVJrxXqbegc1oiXEmAtJ base.py: QmYLota5rE8UjaJmEY1eUmjakEsv2uUcYLGYGToAwCtX5F connection.py: QmNcPrHd1Qoe59eTLWLH4Xbg31AZZ6SdCpekJnmhhxG1o8 tcp_client.py: QmauAiCbvMtp8e5V2s1qZWpeXqPRfXxSEfo11jLhPMFxBc tcp_server.py: QmSJDqUHCFA9erUjyQVcUUTnHr7FP2CEPQbYijdcUARjdv fingerprint_ignore_patterns: [] connections: [] protocols: [] class_name: TCPClientConnection config: address: 127.0.0.1 port: 8082 excluded_protocols: [] restricted_to_protocols: [] dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/connections/tcp/tcp_client.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the TCP client.""" import asyncio import logging import struct from asyncio import ( # pylint: disable=unused-import CancelledError, StreamReader, StreamWriter, ) from typing import Any, Optional, cast from aea.configurations.base import ConnectionConfig from aea.mail.base import Envelope from packages.fetchai.connections.tcp.base import TCPConnection _default_logger = logging.getLogger("aea.packages.fetchai.connections.tcp.tcp_client") STUB_DIALOGUE_ID = 0 class TCPClientConnection(TCPConnection): """This class implements a TCP client.""" def __init__(self, configuration: ConnectionConfig, **kwargs: Any) -> None: """ Initialize a TCP client connection. :param configuration: the configuration object. :param kwargs: keyword arguments. """ address = cast(str, configuration.config.get("address")) port = cast(int, configuration.config.get("port")) if address is None or port is None: raise ValueError("address and port must be set!") # pragma: nocover super().__init__(address, port, configuration=configuration, **kwargs) self._reader, self._writer = ( None, None, ) # type: Optional[StreamReader], Optional[StreamWriter] async def setup(self) -> None: """Set the connection up.""" self._reader, self._writer = await asyncio.open_connection(self.host, self.port) address_bytes = self.address.encode("utf-8") await self._send(self._writer, address_bytes) async def teardown(self) -> None: """Tear the connection down.""" if self._reader is not None: self._reader.feed_eof() if self._writer is None: # pragma: nocover return if self._writer.can_write_eof(): self._writer.write_eof() await self._writer.drain() self._writer.close() # it should be 'await self._writer.wait_closed()' # however, it is not backward compatible with Python 3.6. # this turned out to work in the tests await asyncio.sleep(0.0) async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. :param args: positional arguments :param kwargs: keyword arguments :return: the received envelope, or None if an error occurred. """ try: if self._reader is None: raise ValueError("Reader not set.") # pragma: nocover data = await self._recv(self._reader) if data is None: # pragma: nocover self.logger.debug("[{}] No data received.".format(self.address)) return None self.logger.debug("[{}] Message received: {!r}".format(self.address, data)) envelope = Envelope.decode(data) self.logger.debug( "[{}] Decoded envelope: {}".format(self.address, envelope) ) return envelope except CancelledError: self.logger.debug("[{}] Read cancelled.".format(self.address)) return None except struct.error as e: self.logger.debug("Struct error: {}".format(str(e))) return None except Exception as e: self.logger.exception(e) raise def select_writer_from_envelope(self, envelope: Envelope) -> Optional[StreamWriter]: """Select the destination, given the envelope.""" return self._writer ================================================ FILE: packages/fetchai/connections/tcp/tcp_server.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the TCP server.""" import asyncio import logging from asyncio import AbstractServer, Future, StreamReader, StreamWriter from typing import Any, Dict, Optional, Tuple, cast from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.mail.base import Envelope from packages.fetchai.connections.tcp.base import TCPConnection _default_logger = logging.getLogger("aea.packages.fetchai.connections.tcp.tcp_server") STUB_DIALOGUE_ID = 0 class TCPServerConnection(TCPConnection): """This class implements a TCP server.""" def __init__(self, configuration: ConnectionConfig, **kwargs: Any) -> None: """ Initialize a TCP server connection. :param configuration: the configuration object. :param kwargs: keyword arguments. """ address = cast(str, configuration.config.get("address")) port = cast(int, configuration.config.get("port")) if address is None or port is None: raise ValueError("address and port must be set!") # pragma: nocover super().__init__(address, port, configuration=configuration, **kwargs) self._server = None # type: Optional[AbstractServer] self.connections = {} # type: Dict[str, Tuple[StreamReader, StreamWriter]] self._read_tasks_to_address = {} # type: Dict[Future, Address] async def handle(self, reader: StreamReader, writer: StreamWriter) -> None: """ Handle new connections. :param reader: the stream reader. :param writer: the stream writer. """ self.logger.debug("Waiting for client address...") address_bytes = await self._recv(reader) if address_bytes is not None: address_bytes = cast(bytes, address_bytes) address = address_bytes.decode("utf-8") self.logger.debug("Public key of the client: {}".format(address)) self.connections[address] = (reader, writer) read_task = asyncio.ensure_future(self._recv(reader), loop=self.loop) self._read_tasks_to_address[read_task] = address async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. :param args: positional arguments :param kwargs: keyword arguments :return: the received envelope, or None if an error occurred. """ if len(self._read_tasks_to_address) == 0: self.logger.warning( "Tried to read from the TCP server. However, there is no open connection to read from." ) return None try: self.logger.debug("Waiting for incoming messages...") done, _ = await asyncio.wait(self._read_tasks_to_address.keys(), return_when=asyncio.FIRST_COMPLETED) # type: ignore # take the first task = next(iter(done)) envelope_bytes = task.result() if envelope_bytes is None: # pragma: no cover self.logger.debug("[{}]: No data received.") return None envelope = Envelope.decode(envelope_bytes) address = self._read_tasks_to_address.pop(task) reader = self.connections[address][0] new_task = asyncio.ensure_future(self._recv(reader), loop=self.loop) self._read_tasks_to_address[new_task] = address return envelope except asyncio.CancelledError: self.logger.debug("Receiving loop cancelled.") return None except Exception as e: # pragma: nocover # pylint: disable=broad-except self.logger.error("Error in the receiving loop: {}".format(str(e))) return None async def setup(self) -> None: """Set the connection up.""" self._server = await asyncio.start_server( self.handle, host=self.host, port=self.port ) self.logger.debug("Start listening on {}:{}".format(self.host, self.port)) async def teardown(self) -> None: """Tear the connection down.""" for (reader, _) in self.connections.values(): reader.feed_eof() for t in self._read_tasks_to_address: t.cancel() if self._server is None: # pragma: nocover raise ValueError("Server not set!") self._server.close() await self._server.wait_closed() def select_writer_from_envelope(self, envelope: Envelope) -> Optional[StreamWriter]: """Select the destination, given the envelope.""" to = envelope.to if to not in self.connections: return None _, writer = self.connections[to] return writer ================================================ FILE: packages/fetchai/connections/webhook/README.md ================================================ # Webhook connection An HTTP webhook connection which registers a webhook and waits for incoming requests. It generates messages based on webhook requests received and forwards them to the agent. ## Usage First, add the connection to your AEA project: `aea add connection fetchai/webhook:0.20.6`. Then ensure the `config` in `connection.yaml` matches your need. In particular, set `webhook_address`, `webhook_port` and `webhook_url_path` appropriately. ================================================ FILE: packages/fetchai/connections/webhook/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Implementation of the webhook connection and channel.""" ================================================ FILE: packages/fetchai/connections/webhook/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Webhook connection and channel.""" import asyncio import json import logging from asyncio import CancelledError from typing import Any, Optional, cast from aiohttp import web # type: ignore from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage SUCCESS = 200 NOT_FOUND = 404 REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 PUBLIC_ID = PublicId.from_str("fetchai/webhook:0.20.6") _default_logger = logging.getLogger("aea.packages.fetchai.connections.webhook") RequestId = str class HttpDialogues(BaseHttpDialogues): """The dialogues class keeps track of all http dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ # The webhook connection maintains the dialogue on behalf of the server return HttpDialogue.Role.SERVER BaseHttpDialogues.__init__( self, self_address=str(WebhookConnection.connection_id), role_from_first_message=role_from_first_message, **kwargs, ) class WebhookChannel: """A wrapper for a Webhook.""" def __init__( self, agent_address: Address, webhook_address: Address, webhook_port: int, webhook_url_path: str, connection_id: PublicId, target_skill_id: PublicId, logger: logging.Logger = _default_logger, ): """ Initialize a webhook channel. :param agent_address: the address of the agent :param webhook_address: webhook hostname / IP address :param webhook_port: webhook port number :param webhook_url_path: the url path to receive webhooks from :param connection_id: the connection id :param target_skill_id: the skill id which should receive the http messages :param logger: the logger """ self.agent_address = agent_address self.webhook_address = webhook_address self.webhook_port = webhook_port self.webhook_url_path = webhook_url_path self.target_skill_id = target_skill_id self.webhook_site = None # type: Optional[web.TCPSite] self.runner = None # type: Optional[web.AppRunner] self.app = None # type: Optional[web.Application] self.is_stopped = True self.connection_id = connection_id self.in_queue = None # type: Optional[asyncio.Queue] # pragma: no cover self.logger = logger self.logger.info("Initialised a webhook channel") self._dialogues = HttpDialogues() async def connect(self) -> None: """ Connect the webhook. Connects the webhook via the webhook_address and webhook_port parameters """ if self.is_stopped: self.app = web.Application() self.app.add_routes( [web.post(self.webhook_url_path, self._receive_webhook)] ) self.runner = web.AppRunner(self.app) await self.runner.setup() self.webhook_site = web.TCPSite( self.runner, self.webhook_address, self.webhook_port ) await self.webhook_site.start() self.is_stopped = False async def disconnect(self) -> None: """ Disconnect. Shut-off and cleanup the webhook site, the runner and the web app, then stop the channel. """ if self.webhook_site is None or self.runner is None or self.app is None: raise ValueError( "Application not connected, call connect first!" ) # pragma: nocover if not self.is_stopped: await self.webhook_site.stop() await self.runner.shutdown() await self.runner.cleanup() await self.app.shutdown() await self.app.cleanup() self.logger.info("Webhook app is shutdown.") self.is_stopped = True async def _receive_webhook(self, request: web.Request) -> web.Response: """ Receive a webhook request. Get webhook request, turn it to envelop and send it to the agent to be picked up. :param request: the webhook request :return: Http response with a 200 code """ webhook_envelop = await self.to_envelope(request) self.in_queue.put_nowait(webhook_envelop) # type: ignore return web.Response(status=200) async def send(self, envelope: Envelope) -> None: """ Send an envelope. Sending envelopes via the webhook is not possible! :param envelope: the envelope """ self.logger.warning( "Dropping envelope={} as sending via the webhook is not possible!".format( envelope ) ) async def to_envelope(self, request: web.Request) -> Envelope: """ Convert a webhook request object into an Envelope containing an HttpMessage `from the 'http' Protocol`. :param request: the webhook request :return: The envelop representing the webhook request """ payload_bytes = await request.read() version = str(request.version[0]) + "." + str(request.version[1]) http_message, _ = self._dialogues.create( counterparty=str(self.target_skill_id), performative=HttpMessage.Performative.REQUEST, method=request.method, url=str(request.url), version=version, headers=json.dumps(dict(request.headers)), body=payload_bytes if payload_bytes is not None else b"", ) envelope = Envelope( to=http_message.to, sender=http_message.sender, message=http_message, ) return envelope class WebhookConnection(Connection): """Proxy to the functionality of a webhook.""" connection_id = PUBLIC_ID def __init__(self, **kwargs: Any) -> None: """Initialize a web hook connection.""" super().__init__(**kwargs) webhook_address = cast(str, self.configuration.config.get("webhook_address")) webhook_port = cast(int, self.configuration.config.get("webhook_port")) webhook_url_path = cast(str, self.configuration.config.get("webhook_url_path")) target_skill_id_ = cast( Optional[str], self.configuration.config.get("target_skill_id") ) if ( webhook_address is None or webhook_port is None or webhook_url_path is None or target_skill_id_ is None ): raise ValueError( # pragma: nocover "webhook_address, webhook_port, webhook_url_path and target_skill_id must be set!" ) target_skill_id = PublicId.try_from_str(target_skill_id_) if target_skill_id is None: # pragma: nocover raise ValueError("Provided target_skill_id is not a valid public id.") self.channel = WebhookChannel( agent_address=self.address, webhook_address=webhook_address, webhook_port=webhook_port, webhook_url_path=webhook_url_path, connection_id=self.connection_id, target_skill_id=target_skill_id, logger=self.logger, ) async def connect(self) -> None: """Connect to a HTTP server.""" if self.is_connected: # pragma: nocover return with self._connect_context(): self.channel.logger = self.logger self.channel.in_queue = asyncio.Queue() await self.channel.connect() async def disconnect(self) -> None: """Disconnect from a HTTP server.""" if self.is_disconnected: # pragma: nocover return self.state = ConnectionStates.disconnecting await self.channel.disconnect() self.state = ConnectionStates.disconnected async def send(self, envelope: "Envelope") -> None: """ Send does nothing. Webhooks only receive. :param envelope: the envelop """ self._ensure_connected() if self.channel.in_queue is None: raise ValueError("Channel in queue not set.") # pragma: nocover await self.channel.send(envelope) async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]: """ Receive an envelope. :param args: positional arguments :param kwargs: keyword arguments :return: the envelope received, or None. """ self._ensure_connected() if self.channel.in_queue is None: raise ValueError("Channel in queue not set.") # pragma: nocover try: envelope = await self.channel.in_queue.get() if envelope is None: return None # pragma: no cover return envelope except CancelledError: # pragma: no cover return None ================================================ FILE: packages/fetchai/connections/webhook/connection.yaml ================================================ name: webhook author: fetchai version: 0.20.6 type: connection description: The webhook connection that wraps a webhook functionality. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmdSsHTfZkHbdjFTM9mJRiNEobjVzDy4YfMxGUQdSPiBtF __init__.py: QmesEv4nZwUWcqYRFs1BkHp7RW9uuHG8did8FF9Axc7mD8 connection.py: QmRFthb8nkh9RPA39o9tWUQNDB7YVjKrCd7Z1q3sMQfwMY fingerprint_ignore_patterns: [] connections: [] protocols: - fetchai/http:1.1.7 class_name: WebhookConnection config: target_skill_id: null webhook_address: 127.0.0.1 webhook_port: 8000 webhook_url_path: /some/url/path excluded_protocols: [] restricted_to_protocols: - fetchai/http:1.1.7 dependencies: aiohttp: version: <3.8,>=3.7.4 is_abstract: false ================================================ FILE: packages/fetchai/contracts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the contract packages authored by Fetch.ai""" ================================================ FILE: packages/fetchai/contracts/erc1155/README.md ================================================ # Fetch ERC1155 Contract ## Description This contract package is used to interface with an ERC1155 smart contract. ## Functions - `generate_token_ids(token_type, nb_tokens, starting_index)`: Generate token ids. - `get_create_batch_transaction(token_ids)`: Get the transaction to create a batch of `token_ids` tokens. - `get_create_single_transaction()`: Get the transaction to create a single `token_id` token. - `get_mint_batch_transaction(token_ids, mint_quantities)`: Get the transaction to mint `mint_quantities` number of `token_ids` tokens. - `validate_mint_quantities(token_ids, mint_quantities)`: Validate the mint quantities - `get_mint_single_transaction(token_id, mint_quantity)`: Get the transaction to mint `mint_quantity` number of a single `token_id` token. - `get_balance(token_id)`: Get the balance for a specific `token_id`. - `get_atomic_swap_single_transaction(token_id)`: Get the transaction for a trustless trade between two agents for a single `token_id` token. - `get_balances(token_ids)`: Get the balances for a batch of specific `token_ids`. - `get_atomic_swap_batch_transaction(token_ids)`: Get the transaction for a trustless trade between two agents for a batch of `token_ids` tokens. - `get_hash_single(token_id)`: Get the hash for a trustless trade between two agents for a single `token_id` token. - `get_hash_batch(token_ids)`: Get the hash for a trustless trade between two agents for a batch of `token_ids` token. - `generate_trade_nonce()`: Generate a valid trade nonce. ## Links - ERC1155 Standard ================================================ FILE: packages/fetchai/contracts/erc1155/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the support resources for the erc1155 contract.""" ================================================ FILE: packages/fetchai/contracts/erc1155/build/Migrations.json ================================================ { "contractName": "Migrations", "abi": [ { "constant": true, "inputs": [], "name": "last_completed_migration", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "constant": false, "inputs": [ { "name": "completed", "type": "uint256" } ], "name": "setCompleted", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "new_address", "type": "address" } ], "name": "upgrade", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ], "metadata": "{\"compiler\":{\"version\":\"0.5.2+commit.1df8f40c\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":false,\"inputs\":[{\"name\":\"new_address\",\"type\":\"address\"}],\"name\":\"upgrade\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"last_completed_migration\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"completed\",\"type\":\"uint256\"}],\"name\":\"setCompleted\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol\":\"Migrations\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol\":{\"keccak256\":\"0xfdb731592344e2a2890faf03baec7b4bee7057ffba18ba6dbb6eec8db85f8f4c\",\"urls\":[\"bzzr://ddc8801d0a2a7220c2c9bf3881b4921817e72fdd96827ec8be4428fa009ace07\"]}},\"version\":1}", "bytecode": "0x608060405234801561001057600080fd5b5060008054600160a060020a03191633179055610230806100326000396000f3fe608060405234801561001057600080fd5b5060043610610068577c010000000000000000000000000000000000000000000000000000000060003504630900f010811461006d578063445df0ac146100a25780638da5cb5b146100bc578063fdacd576146100ed575b600080fd5b6100a06004803603602081101561008357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661010a565b005b6100aa6101bd565b60408051918252519081900360200190f35b6100c46101c3565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b6100a06004803603602081101561010357600080fd5b50356101df565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760008190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101a057600080fd5b505af11580156101b4573d6000803e3d6000fd5b50505050505b50565b60015481565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760015556fea165627a7a7230582065546060ae2694c0faa5ae3a86876c1f4f60c32218234b846e28adec7370506c0029", "deployedBytecode": "0x608060405234801561001057600080fd5b5060043610610068577c010000000000000000000000000000000000000000000000000000000060003504630900f010811461006d578063445df0ac146100a25780638da5cb5b146100bc578063fdacd576146100ed575b600080fd5b6100a06004803603602081101561008357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661010a565b005b6100aa6101bd565b60408051918252519081900360200190f35b6100c46101c3565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b6100a06004803603602081101561010357600080fd5b50356101df565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760008190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101a057600080fd5b505af11580156101b4573d6000803e3d6000fd5b50505050505b50565b60015481565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760015556fea165627a7a7230582065546060ae2694c0faa5ae3a86876c1f4f60c32218234b846e28adec7370506c0029", "sourceMap": "34:480:0:-;;;123:50;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;150:5:0;:18;;-1:-1:-1;;;;;;150:18:0;158:10;150:18;;;34:480;;;;;;", "deployedSourceMap": "34:480:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;34:480:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;347:165;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;347:165:0;;;;:::i;:::-;;82:36;;;:::i;:::-;;;;;;;;;;;;;;;;58:20;;;:::i;:::-;;;;;;;;;;;;;;;;;;;240:103;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;240:103:0;;:::i;347:165::-;223:5;;;;209:10;:19;205:26;;;409:19;442:11;409:45;;460:8;:21;;;482:24;;460:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;460:47:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;460:47:0;;;;230:1;205:26;347:165;:::o;82:36::-;;;;:::o;58:20::-;;;;;;:::o;240:103::-;223:5;;;;209:10;:19;205:26;;;302:24;:36;240:103::o", "source": "pragma solidity >=0.4.21 <0.6.0;\n\ncontract Migrations {\n address public owner;\n uint public last_completed_migration;\n\n constructor() public {\n owner = msg.sender;\n }\n\n modifier restricted() {\n if (msg.sender == owner) _;\n }\n\n function setCompleted(uint completed) public restricted {\n last_completed_migration = completed;\n }\n\n function upgrade(address new_address) public restricted {\n Migrations upgraded = Migrations(new_address);\n upgraded.setCompleted(last_completed_migration);\n }\n}\n", "sourcePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol", "ast": { "absolutePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol", "exportedSymbols": { "Migrations": [ 56 ] }, "id": 57, "nodeType": "SourceUnit", "nodes": [ { "id": 1, "literals": [ "solidity", ">=", "0.4", ".21", "<", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "0:32:0" }, { "baseContracts": [], "contractDependencies": [], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 56, "linearizedBaseContracts": [ 56 ], "name": "Migrations", "nodeType": "ContractDefinition", "nodes": [ { "constant": false, "id": 3, "name": "owner", "nodeType": "VariableDeclaration", "scope": 56, "src": "58:20:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 2, "name": "address", "nodeType": "ElementaryTypeName", "src": "58:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "public" }, { "constant": false, "id": 5, "name": "last_completed_migration", "nodeType": "VariableDeclaration", "scope": 56, "src": "82:36:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4, "name": "uint", "nodeType": "ElementaryTypeName", "src": "82:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "body": { "id": 13, "nodeType": "Block", "src": "144:29:0", "statements": [ { "expression": { "argumentTypes": null, "id": 11, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 8, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "150:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 9, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "158:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 10, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "158:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "150:18:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "id": 12, "nodeType": "ExpressionStatement", "src": "150:18:0" } ] }, "documentation": null, "id": 14, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "parameters": { "id": 6, "nodeType": "ParameterList", "parameters": [], "src": "134:2:0" }, "returnParameters": { "id": 7, "nodeType": "ParameterList", "parameters": [], "src": "144:0:0" }, "scope": 56, "src": "123:50:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 22, "nodeType": "Block", "src": "199:37:0", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address", "typeString": "address" }, "id": 19, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 16, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "209:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 17, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "209:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "id": 18, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "223:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "src": "209:19:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 21, "nodeType": "IfStatement", "src": "205:26:0", "trueBody": { "id": 20, "nodeType": "PlaceholderStatement", "src": "230:1:0" } } ] }, "documentation": null, "id": 23, "name": "restricted", "nodeType": "ModifierDefinition", "parameters": { "id": 15, "nodeType": "ParameterList", "parameters": [], "src": "196:2:0" }, "src": "177:59:0", "visibility": "internal" }, { "body": { "id": 34, "nodeType": "Block", "src": "296:47:0", "statements": [ { "expression": { "argumentTypes": null, "id": 32, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 30, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "302:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 31, "name": "completed", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 25, "src": "329:9:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "302:36:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 33, "nodeType": "ExpressionStatement", "src": "302:36:0" } ] }, "documentation": null, "id": 35, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 28, "modifierName": { "argumentTypes": null, "id": 27, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "285:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "285:10:0" } ], "name": "setCompleted", "nodeType": "FunctionDefinition", "parameters": { "id": 26, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 25, "name": "completed", "nodeType": "VariableDeclaration", "scope": 35, "src": "262:14:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 24, "name": "uint", "nodeType": "ElementaryTypeName", "src": "262:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "261:16:0" }, "returnParameters": { "id": 29, "nodeType": "ParameterList", "parameters": [], "src": "296:0:0" }, "scope": 56, "src": "240:103:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 54, "nodeType": "Block", "src": "403:109:0", "statements": [ { "assignments": [ 43 ], "declarations": [ { "constant": false, "id": 43, "name": "upgraded", "nodeType": "VariableDeclaration", "scope": 54, "src": "409:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" }, "typeName": { "contractScope": null, "id": 42, "name": "Migrations", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 56, "src": "409:10:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "value": null, "visibility": "internal" } ], "id": 47, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 45, "name": "new_address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 37, "src": "442:11:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 44, "name": "Migrations", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 56, "src": "431:10:0", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_Migrations_$56_$", "typeString": "type(contract Migrations)" } }, "id": 46, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "431:23:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "nodeType": "VariableDeclarationStatement", "src": "409:45:0" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 51, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "482:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 48, "name": "upgraded", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 43, "src": "460:8:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "id": 50, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "setCompleted", "nodeType": "MemberAccess", "referencedDeclaration": 35, "src": "460:21:0", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256) external" } }, "id": 52, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "460:47:0", "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 53, "nodeType": "ExpressionStatement", "src": "460:47:0" } ] }, "documentation": null, "id": 55, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 40, "modifierName": { "argumentTypes": null, "id": 39, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "392:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "392:10:0" } ], "name": "upgrade", "nodeType": "FunctionDefinition", "parameters": { "id": 38, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 37, "name": "new_address", "nodeType": "VariableDeclaration", "scope": 55, "src": "364:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 36, "name": "address", "nodeType": "ElementaryTypeName", "src": "364:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "363:21:0" }, "returnParameters": { "id": 41, "nodeType": "ParameterList", "parameters": [], "src": "403:0:0" }, "scope": 56, "src": "347:165:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" } ], "scope": 57, "src": "34:480:0" } ], "src": "0:515:0" }, "legacyAST": { "absolutePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol", "exportedSymbols": { "Migrations": [ 56 ] }, "id": 57, "nodeType": "SourceUnit", "nodes": [ { "id": 1, "literals": [ "solidity", ">=", "0.4", ".21", "<", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "0:32:0" }, { "baseContracts": [], "contractDependencies": [], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 56, "linearizedBaseContracts": [ 56 ], "name": "Migrations", "nodeType": "ContractDefinition", "nodes": [ { "constant": false, "id": 3, "name": "owner", "nodeType": "VariableDeclaration", "scope": 56, "src": "58:20:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 2, "name": "address", "nodeType": "ElementaryTypeName", "src": "58:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "public" }, { "constant": false, "id": 5, "name": "last_completed_migration", "nodeType": "VariableDeclaration", "scope": 56, "src": "82:36:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4, "name": "uint", "nodeType": "ElementaryTypeName", "src": "82:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "body": { "id": 13, "nodeType": "Block", "src": "144:29:0", "statements": [ { "expression": { "argumentTypes": null, "id": 11, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 8, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "150:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 9, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "158:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 10, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "158:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "150:18:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "id": 12, "nodeType": "ExpressionStatement", "src": "150:18:0" } ] }, "documentation": null, "id": 14, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "parameters": { "id": 6, "nodeType": "ParameterList", "parameters": [], "src": "134:2:0" }, "returnParameters": { "id": 7, "nodeType": "ParameterList", "parameters": [], "src": "144:0:0" }, "scope": 56, "src": "123:50:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 22, "nodeType": "Block", "src": "199:37:0", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address", "typeString": "address" }, "id": 19, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 16, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "209:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 17, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "209:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "id": 18, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "223:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "src": "209:19:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 21, "nodeType": "IfStatement", "src": "205:26:0", "trueBody": { "id": 20, "nodeType": "PlaceholderStatement", "src": "230:1:0" } } ] }, "documentation": null, "id": 23, "name": "restricted", "nodeType": "ModifierDefinition", "parameters": { "id": 15, "nodeType": "ParameterList", "parameters": [], "src": "196:2:0" }, "src": "177:59:0", "visibility": "internal" }, { "body": { "id": 34, "nodeType": "Block", "src": "296:47:0", "statements": [ { "expression": { "argumentTypes": null, "id": 32, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 30, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "302:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 31, "name": "completed", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 25, "src": "329:9:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "302:36:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 33, "nodeType": "ExpressionStatement", "src": "302:36:0" } ] }, "documentation": null, "id": 35, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 28, "modifierName": { "argumentTypes": null, "id": 27, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "285:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "285:10:0" } ], "name": "setCompleted", "nodeType": "FunctionDefinition", "parameters": { "id": 26, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 25, "name": "completed", "nodeType": "VariableDeclaration", "scope": 35, "src": "262:14:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 24, "name": "uint", "nodeType": "ElementaryTypeName", "src": "262:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "261:16:0" }, "returnParameters": { "id": 29, "nodeType": "ParameterList", "parameters": [], "src": "296:0:0" }, "scope": 56, "src": "240:103:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 54, "nodeType": "Block", "src": "403:109:0", "statements": [ { "assignments": [ 43 ], "declarations": [ { "constant": false, "id": 43, "name": "upgraded", "nodeType": "VariableDeclaration", "scope": 54, "src": "409:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" }, "typeName": { "contractScope": null, "id": 42, "name": "Migrations", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 56, "src": "409:10:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "value": null, "visibility": "internal" } ], "id": 47, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 45, "name": "new_address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 37, "src": "442:11:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 44, "name": "Migrations", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 56, "src": "431:10:0", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_Migrations_$56_$", "typeString": "type(contract Migrations)" } }, "id": 46, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "431:23:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "nodeType": "VariableDeclarationStatement", "src": "409:45:0" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 51, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "482:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 48, "name": "upgraded", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 43, "src": "460:8:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "id": 50, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "setCompleted", "nodeType": "MemberAccess", "referencedDeclaration": 35, "src": "460:21:0", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256) external" } }, "id": 52, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "460:47:0", "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 53, "nodeType": "ExpressionStatement", "src": "460:47:0" } ] }, "documentation": null, "id": 55, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 40, "modifierName": { "argumentTypes": null, "id": 39, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "392:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "392:10:0" } ], "name": "upgrade", "nodeType": "FunctionDefinition", "parameters": { "id": 38, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 37, "name": "new_address", "nodeType": "VariableDeclaration", "scope": 55, "src": "364:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 36, "name": "address", "nodeType": "ElementaryTypeName", "src": "364:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "363:21:0" }, "returnParameters": { "id": 41, "nodeType": "ParameterList", "parameters": [], "src": "403:0:0" }, "scope": 56, "src": "347:165:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" } ], "scope": 57, "src": "34:480:0" } ], "src": "0:515:0" }, "compiler": { "name": "solc", "version": "0.5.2+commit.1df8f40c.Emscripten.clang" }, "networks": { "1583918911727": { "events": {}, "links": {}, "address": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550", "transactionHash": "0xc59b0daeb6b17beb9ea57bb0586fd626fb7b6f092d20bf22406168f7381ac949" } }, "schemaVersion": "3.0.19", "updatedAt": "2020-03-11T13:47:51.887Z", "networkType": "ethereum", "devdoc": { "methods": {} }, "userdoc": { "methods": {} } } ================================================ FILE: packages/fetchai/contracts/erc1155/build/erc1155.json ================================================ { "contractName": "erc1155", "abi": [ { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event" }, { "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor" }, { "name": "getAddress", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_addr" } ], "constant": true, "payable": false, "type": "function", "gas": 370 }, { "name": "getHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 11446 }, { "name": "getSingleHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 2041 }, { "name": "supportsInterface", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "bytes32", "name": "_interfaceID" } ], "constant": true, "payable": false, "type": "function", "gas": 868 }, { "name": "is_nonce_used", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "addr" }, { "type": "uint256", "name": "nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 1052 }, { "name": "is_token_id_exists", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "uint256", "name": "token_id" } ], "constant": true, "payable": false, "type": "function", "gas": 928 }, { "name": "safeTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_value" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 80803 }, { "name": "safeBatchTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_values" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 748184 }, { "name": "balanceOf", "outputs": [ { "type": "uint256", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "uint256", "name": "_id" } ], "constant": true, "payable": false, "type": "function", "gas": 1172 }, { "name": "balanceOfBatch", "outputs": [ { "type": "uint256[10]", "name": "out" } ], "inputs": [ { "type": "address[10]", "name": "_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": true, "payable": false, "type": "function", "gas": 7524 }, { "name": "setApprovalForAll", "outputs": [], "inputs": [ { "type": "address", "name": "_operator" }, { "type": "bool", "name": "_approved" } ], "constant": false, "payable": false, "type": "function", "gas": 38136 }, { "name": "isApprovedForAll", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "address", "name": "_operator" } ], "constant": true, "payable": false, "type": "function", "gas": 1301 }, { "name": "createSingle", "outputs": [], "inputs": [ { "type": "address", "name": "_item_owner" }, { "type": "uint256", "name": "_id" }, { "type": "string", "name": "_path" } ], "constant": false, "payable": false, "type": "function", "gas": 597461 }, { "name": "createBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_items_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": false, "payable": false, "type": "function", "gas": 927926 }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "burn", "outputs": [], "inputs": [ { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function", "gas": 40353 }, { "name": "burnBatch", "outputs": [], "inputs": [ { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function", "gas": 382149 }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "owner", "outputs": [ { "type": "address", "name": "out" } ], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1263 } ], "bytecode": "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6000600155600160006301ffc9a760e05260c052604060c020556001600063d9b67a2660e05260c052604060c020553360025561405a56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd5b6100d061405a036100d06000396100d061405a036000f3", "deployedBytecode": "0x600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd", "source": "# Author: Sören Steiger, github.com/ssteiger\n# Author: Fetch.ai, github.com/fetchai\n# License: MIT\n\n# ERC1155 Token Standard\n# https://eips.ethereum.org/EIPS/eip-1155\n\n########################EXTERNAL-CONTRACTS####################################\n\ncontract ERC1155TokenReceiver:\n # Note: The ERC-165 identifier for this interface is 0x4e2312e0.\n\n def onERC1155Received(_operator: address, _from: address, _id: uint256, _value: uint256,\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of a single ERC1155 token type.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))` (i.e. 0xf23a6e61) if it accepts the transfer.\n # This function MUST revert if it rejects the transfer.\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _id The ID of the token being transferred\n # @param _value The amount of tokens being transferred\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))`\n # \"\"\"\n\n def onERC1155BatchReceived(_operator: address, _from: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE],\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of multiple ERC1155 token types.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))` (i.e. 0xbc197c81) if it accepts the transfer(s).\n # This function MUST revert if it rejects the transfer(s).\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the batch transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _ids An array containing ids of each token being transferred (order and length must match _values array)\n # @param _values An array containing amounts of each token being transferred (order and length must match _ids array)\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))`\n # \"\"\"\n\n########################END-EXTERNAL-CONTRACTS####################################\n########################EVENTS####################################\n\nMAX_URI_SIZE: constant(uint256) = 1024\n\nTransferSingle: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address), _id: uint256,\n _value: uint256})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_id` argument MUST be the token type being transferred.\n# The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nTransferBatch: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address),\n _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE]})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_ids` argument MUST be the list of tokens being transferred.\n# The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nApprovalForAll: event({_owner: indexed(address), _operator: indexed(address), _approved: bool})\n# @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled).\n\n\nURI: event({_value: string[MAX_URI_SIZE], _id: indexed(uint256)})\n# @dev MUST emit when the URI is updated for a token ID.\n# URIs are defined in RFC 3986.\n# The URI MUST point to a JSON file that conforms to the \"ERC-1155 Metadata URI JSON Schema\".\n\n########################END-EVENTS####################################\n########################INITIALIZATION####################################\n\nsupportedInterfaces: map(bytes32, bool)\n# https://eips.ethereum.org/EIPS/eip-165\nERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7\nERC1155_INTERFACE_ID: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26\ntokensIdCount: uint256\nowner: public(address)\n\nbalancesOf: map(address, map(uint256, uint256))\nnoncesOf: map(address, map(uint256, bool))\nuri: map(uint256, string[256])\noperators: map(address, map(address, bool))\ntoken_ids: map(uint256, bool)\n\n# This is to be set before contract migration!\nBATCH_SIZE: constant(uint256) = 10\n\n\n@public\ndef __init__():\n \"\"\"\n @notice Called once and only upon contract deployment.\n \"\"\"\n self.tokensIdCount = convert(0, uint256)\n self.supportedInterfaces[ERC165_INTERFACE_ID] = True\n self.supportedInterfaces[ERC1155_INTERFACE_ID] = True\n self.owner = msg.sender\n\n########################END-INITIALIZATION####################################\n########################PRIVATE-FUNCTIONS####################################\n\n\n######### THIS IS A TEMPORARY SOLUTION #################\n@public\n@constant\ndef getAddress(_addr: address) -> bytes32:\n hash: bytes32 = convert(_addr, bytes32)\n return hash\n##################### END ##############################\n\n@private\n@constant\ndef _getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n aggregate_hash: bytes32 = keccak256(concat(convert(_ids[0], bytes32), convert(_from_supplies[0], bytes32), convert(_to_supplies[0], bytes32)))\n for i in range(BATCH_SIZE):\n if not i == 0:\n aggregate_hash = keccak256(concat(aggregate_hash, convert(_ids[i], bytes32), convert(_from_supplies[i], bytes32), convert(_to_supplies[i], bytes32)))\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n aggregate_hash,\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n\n@private\n@constant\ndef getHashOld(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_ids[0], bytes32),\n convert(_ids[1], bytes32),\n convert(_ids[2], bytes32),\n convert(_ids[3], bytes32),\n convert(_ids[4], bytes32),\n convert(_ids[5], bytes32),\n convert(_ids[6], bytes32),\n convert(_ids[7], bytes32),\n convert(_ids[8], bytes32),\n convert(_ids[9], bytes32),\n convert(_from_supplies[0], bytes32),\n convert(_from_supplies[1], bytes32),\n convert(_from_supplies[2], bytes32),\n convert(_from_supplies[3], bytes32),\n convert(_from_supplies[4], bytes32),\n convert(_from_supplies[5], bytes32),\n convert(_from_supplies[6], bytes32),\n convert(_from_supplies[7], bytes32),\n convert(_from_supplies[8], bytes32),\n convert(_from_supplies[9], bytes32),\n convert(_to_supplies[0], bytes32),\n convert(_to_supplies[1], bytes32),\n convert(_to_supplies[2], bytes32),\n convert(_to_supplies[3], bytes32),\n convert(_to_supplies[4], bytes32),\n convert(_to_supplies[5], bytes32),\n convert(_to_supplies[6], bytes32),\n convert(_to_supplies[7], bytes32),\n convert(_to_supplies[8], bytes32),\n convert(_to_supplies[9], bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@private\n@constant\ndef _getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_id, bytes32),\n convert(_from_supply, bytes32),\n convert(_to_supply, bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n\n@private\n@constant\ndef ecrecoverSig(_hash: bytes32, _sig: bytes[65]) -> address:\n \"\"\"\n @notice Check whether the the signature matches the hash.\n @param _hash The hash to be checked.\n @param _sig The signature which is meant to match the hash.\n @return the address which signed the signature or the zero address\n \"\"\"\n if len(_sig) != 65:\n return ZERO_ADDRESS\n # ref. https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d\n # The signature format is a compact form of:\n # {bytes32 r}{bytes32 s}{uint8 v}\n r: bytes32 = extract32(_sig, 0, type=bytes32)\n s: bytes32 = extract32(_sig, 32, type=bytes32)\n v: int128 = convert(slice(_sig, start=64, len=1), int128)\n # Version of signature should be 27 or 28, but 0 and 1 are also possible versions.\n # geth uses [0, 1] and some clients have followed. This might change, see:\n # https://github.com/ethereum/go-ethereum/issues/2053\n if v < 27:\n v += 27\n if v in [27, 28]:\n return ecrecover(_hash, convert(v, uint256), convert(r, uint256), convert(s, uint256))\n return ZERO_ADDRESS\n\n\n@private\n@constant\ndef decode_id(id: uint256) -> int128:\n \"\"\"\n @notice Decodes the id of the token inorder to find out if it NFT or FT.\n @param id: uint256\n @return token_id : int128 (Specified id for FT and NFT.)\n @dev shift(x, -y): returns x with the bits shifted to the right by y places, which is equivalent to dividing x by 2**y.\n \"\"\"\n decoded_token_id: int128 = convert(shift(id, -128), int128)\n decoded_index: int128 = convert(id % 2 ** 128, int128)\n return decoded_token_id\n\n########################END-PRIVATE-FUNCTIONS################################\n########################PUBLIC-FUNCTIONS#####################################\n\n@public\n@constant\ndef supportsInterface(_interfaceID: bytes32) -> bool:\n \"\"\"\n @notice Check whether the interface id is supported.\n @param _interfaceID The interface id\n @return True if the interface id is supported.\n \"\"\"\n return self.supportedInterfaces[_interfaceID]\n\n@public\n@constant\ndef is_nonce_used(addr: address, nonce: uint256) -> bool:\n \"\"\"\n @notice Checks if the given nonce for the give address is unused.\n @param nonce: uint256 the counter of the transaction\n @param address: the address that want to transact.\n \"\"\"\n return self.noncesOf[addr][nonce]\n\n@public\n@constant\ndef is_token_id_exists(token_id: uint256) -> bool:\n \"\"\"\n @notice Checks if the given token_id is already created.\n @param token_id: uint256 the id of the token.\n \"\"\"\n return self.token_ids[token_id]\n\n@public\ndef safeTransferFrom(_from: address, _to: address, _id: uint256, _value: uint256, _data: bytes[256]):\n \"\"\"\n @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if balance of holder for token `_id` is lower than the `_value` sent.\n MUST revert on any other error.\n MUST emit the `TransferSingle` event to reflect the balance change (see \"Safe Transfer Rules\" section of the standard).\n After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _id ID of the token type\n @param _value Transfer amount\n @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n assert self.balancesOf[_from][_id] >= _value, \"Not enough tokens.\"\n\n self.balancesOf[_from][_id] -= _value\n self.balancesOf[_to][_id] += _value\n\n log.TransferSingle(msg.sender, _from, _to, _id, _value)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef safeBatchTransferFrom(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE], _data: bytes[256]):\n \"\"\"\n @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if length of `_ids` is not the same as length of `_values`.\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _ids IDs of each token type (order and length must match _values array)\n @param _values Transfer amounts per token type (order and length must match _ids array)\n @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[_from][id] >= _values[i]\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _values)\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_from][id] -= _values[i]\n self.balancesOf[_to][id] += _values[i]\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _values, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256[BATCH_SIZE],uint256[BATCH_SIZE],bytes)\", bytes32)\n\n\n@public\n@constant\ndef balanceOf(_owner: address, _id: uint256) -> uint256:\n \"\"\"\n @notice Get the balance of an account's tokens.\n @param _owner The address of the token holder\n @param _id ID of the token\n @return The _owner's balance of the token type requested\n \"\"\"\n return self.balancesOf[_owner][_id]\n\n\n@public\n@constant\ndef balanceOfBatch( _owner: address[BATCH_SIZE], _ids: uint256[BATCH_SIZE]) -> uint256[BATCH_SIZE]:\n \"\"\"\n @notice Get the balance of multiple account/token pairs\n @param _owners The addresses of the token holders\n @param _ids ID of the tokens\n @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair)\n \"\"\"\n returnBalances: uint256[BATCH_SIZE]\n for i in range(BATCH_SIZE):\n returnBalances[i] = self.balancesOf[_owner[i]][_ids[i]]\n return returnBalances\n\n\n@public\ndef setApprovalForAll(_operator: address, _approved: bool):\n \"\"\"\n @notice Enable or disable approval for a third party (\"operator\") to manage all of the caller's tokens.\n @dev MUST emit the ApprovalForAll event on success.\n @param _operator Address to add to the set of authorized operators\n @param _approved True if the operator is approved, false to revoke approval\n @return None\n \"\"\"\n (self.operators[msg.sender])[_operator] = _approved\n log.ApprovalForAll(msg.sender, _operator, _approved)\n\n\n@public\n@constant\ndef isApprovedForAll(_owner: address, _operator: address) -> bool:\n \"\"\"\n @notice Queries the approval status of an operator for a given owner.\n @param _owner The owner of the tokens.\n @param _operator Address of authorized operator.\n @return True if the operator is approved, false if not\n \"\"\"\n return (self.operators[_owner])[_operator]\n\n\n@public\ndef createSingle(_item_owner: address, _id: uint256, _path: string[256]):\n \"\"\"\n @notice Create a new token type that we can mint later.\n @param _item_owner The owner of the item.\n @param _id The id of the token.\n @param _path The path to the token data.\n @return None\n \"\"\"\n assert _item_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create item.\"\n self.balancesOf[_item_owner][_id] = 0\n self.tokensIdCount += 1\n self.token_ids[_id] = True\n self.uri[_id] = _path\n log.URI(_path, _id)\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _item_owner, _id, 0)\n\n\n@public\ndef createBatch(_items_owner: address, _ids: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Create new token types that we can mint later.\n @param _items_owner The owner of the items.\n @param _ids The ids of the tokens.\n @return None\n \"\"\"\n assert _items_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create items.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_items_owner][id] = 0\n self.tokensIdCount += 1\n self.token_ids[id] = True\n zero_supply: uint256[BATCH_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _items_owner, _ids, zero_supply)\n\n\n@public\ndef mint(_to: address, _id: uint256, _supply: uint256, _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a token.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _id The id of the token.\n @param _supply The supply to be minted for the token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n decoded_id: int128 = self.decode_id(_id)\n assert decoded_id == 1 or decoded_id == 2\n if decoded_id == 1 :\n assert _supply == 1, \"Cannot mint NFT with _supply more than 1\"\n self.balancesOf[_to][_id] = _supply\n\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, _id, _supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _id, _supply, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef mintBatch(_to: address, _ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a batch of tokens.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _supplies The supply to be minted for each token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n decoded_id: int128 = self.decode_id(id)\n assert decoded_id == 1 or decoded_id == 2\n\n if decoded_id == 1 :\n assert _supplies[i] == 1\n\n self.balancesOf[_to][id] = _supplies[i]\n\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, _ids, _supplies)\n\n for i in range(BATCH_SIZE):\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _ids[i], _supplies[i], _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef burn(_id: uint256, _supply: uint256):\n \"\"\"\n @notice Burns the supply of the specified token.\n @param _id The id of the token\n @param _supply Supply to be burned\n @return None\n \"\"\"\n assert self.balancesOf[msg.sender][_id] >= _supply, \"Not enough tokens to burn.\"\n self.balancesOf[msg.sender][_id] -= _supply\n log.TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, _id, _supply)\n\n\n@public\ndef burnBatch(_ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Burns the supply of the specified tokens.\n @dev At this point anyone can burn items if they own it.\n @param _ids The ids of the token\n @param _supplies Supplies to be burned\n @return None\n \"\"\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[msg.sender][id] >= _supplies[i]\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[msg.sender][id] -= _supplies[i]\n log.TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, _ids, _supplies)\n\n\n@public\n@payable\ndef tradeBatch(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supplies[i] > 0 and _to_supplies[i] > 0\n MUST revert if len(_ids) != len(_from_supplies) != len(_to_supplies)\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `positive and negative values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n\n assert _from == msg.sender or (self.operators[_from])[msg.sender], \"_from must be the sender or approved address\"\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n assert _to_supplies[i] == 0\n assert self.balancesOf[_from][id] >= _from_supplies[i]\n else:\n assert _from_supplies[i] == 0\n assert self.balancesOf[_to][id] >= _to_supplies[i]\n\n # Create hash from variables.\n hash: bytes32 = self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n self.balancesOf[_from][id] -= _from_supplies[i]\n self.balancesOf[_to][id] += _from_supplies[i]\n else:\n self.balancesOf[_from][id] += _to_supplies[i]\n self.balancesOf[_to][id] -= _to_supplies[i]\n\n send(_to, msg.value)\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _from_supplies)\n log.TransferBatch(msg.sender, _to, _from, _ids, _to_supplies)\n\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _to, _ids, _from_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155BatchReceived(msg.sender, _from, _ids, _to_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n\n@public\n@payable\ndef trade(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supply > 0 and _to_supply > 0\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_id` is lower than the respective amount in `positive or negative value` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The from address (seller of eth, potential receiver of tokens).\n @param _to The receiver address (receiver of tokens).\n @param _id The id of the token\n @param _from_supply The change in value of token (for _from)\n @param _to_supply The change in value of token (for _to)\n @param _value_eth The value of the ETH sent to the _from address.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n if _from_supply > 0:\n assert _to_supply == 0\n assert self.balancesOf[_from][_id] >= _from_supply\n else:\n assert _from_supply == 0\n assert self.balancesOf[_to][_id] >= _to_supply\n\n # Create hash from variables.\n hash: bytes32 = self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n if _from_supply > 0:\n self.balancesOf[_from][_id] -= _from_supply\n self.balancesOf[_to][_id] += _from_supply\n else:\n self.balancesOf[_from][_id] += _to_supply\n self.balancesOf[_to][_id] -= _to_supply\n\n send(_to, msg.value)\n\n log.TransferSingle(msg.sender, _from, _to, _id, _from_supply)\n log.TransferSingle(msg.sender, _to, _from, _id, _to_supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _to, _id, _from_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155Received(msg.sender, _from, _id, _to_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n", "sourcePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/erc1155.vy", "compiler": { "name": "vyper", "version": "0.1.0b12+commit.a01cdc8" }, "networks": { "1583918911727": { "events": { "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" }, "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8": { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8" }, "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31" }, "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event", "signature": "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b" } }, "links": {}, "address": "0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7", "transactionHash": "0x816b272ebd644b189a3addfbd429b0ea0fa7b2403cca3aa038bcd59f09d9c116" } }, "schemaVersion": "3.0.19", "updatedAt": "2020-03-11T13:47:51.885Z", "networkType": "ethereum" } ================================================ FILE: packages/fetchai/contracts/erc1155/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the erc1155 contract definition.""" import logging import random from typing import Dict, List, Optional, cast from aea_ledger_cosmos import CosmosApi from aea_ledger_ethereum import EthereumApi from aea_ledger_fetchai import FetchAIApi from google.protobuf.any_pb2 import Any as ProtoAny from aea.common import Address, JSONLike from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi _default_logger = logging.getLogger("aea.packages.fetchai.contracts.erc1155.contract") MAX_UINT_256 = 2 ^ 256 - 1 PUBLIC_ID = PublicId.from_str("fetchai/erc1155:0.23.3") DEFAUT_ETH_ATOMIC_SWAP_GAS_LIMIT = 2818111 DEFAUT_COSMOS_ATOMIC_SWAP_GAS_LIMIT = 1500000 DEFAUT_ETH_SINGLE_TASK_GAS_LIMIT = 300000 DEFAUT_COSMOS_SINGLE_TASK_GAS_LIMIT = 300000 DEFAUT_ETH_BATCH_TASK_GAS_LIMIT = 500000 DEFAUT_COSMOS_BATCH_TASK_GAS_LIMIT = 500000 def keccak256(input_: bytes) -> bytes: """Compute hash.""" return bytes(bytearray.fromhex(EthereumApi.get_hash(input_)[2:])) class ERC1155Contract(Contract): """The ERC1155 contract class which acts as a bridge between AEA framework and ERC1155 ABI.""" contract_id = PUBLIC_ID @classmethod def generate_token_ids( cls, token_type: int, nb_tokens: int, starting_index: int = 0 ) -> List[int]: """ Generate token_ids. :param token_type: the token type (nft or ft) :param nb_tokens: the number of tokens :param starting_index: the index at which to start constructing ids :return: the list of token ids generated """ token_ids = [] for i in range(nb_tokens): index = starting_index + i token_id = cls._generate_id(index, token_type) token_ids.append(token_id) return token_ids @staticmethod def _generate_id(index: int, token_type: int) -> int: """ Generate a token_id. :param index: the index to byte-shift :param token_type: the token type :return: the token id """ token_id = (token_type << 128) + index return token_id @classmethod def get_create_batch_transaction( # pylint: disable=unused-argument cls, ledger_api: LedgerApi, contract_address: Address, deployer_address: Address, token_ids: List[int], data: Optional[bytes] = b"", gas: Optional[int] = None, tx_fee: int = 0, ) -> JSONLike: """ Get the transaction to create a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param token_ids: the list of token ids for creation :param data: the data to include in the transaction :param gas: the gas to be used :param tx_fee: the tx_fee to be used :return: the transaction object """ if ledger_api.identifier == EthereumApi.identifier: gas = gas if gas is not None else DEFAUT_ETH_BATCH_TASK_GAS_LIMIT nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.createBatch( deployer_address, token_ids ).buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: gas = gas if gas is not None else DEFAUT_COSMOS_BATCH_TASK_GAS_LIMIT tokens = [] for token_id in token_ids: tokens.append({"id": str(token_id), "path": str(token_id)}) msg = { "create_batch": {"item_owner": str(deployer_address), "tokens": tokens} } cosmos_api = cast(CosmosApi, ledger_api) tx = cosmos_api.get_handle_transaction( deployer_address, contract_address, msg, amount=0, tx_fee=tx_fee, gas=gas, ) return tx raise NotImplementedError @classmethod def get_create_single_transaction( cls, ledger_api: LedgerApi, contract_address: Address, deployer_address: Address, token_id: int, data: Optional[bytes] = b"", gas: Optional[int] = None, tx_fee: int = 0, ) -> JSONLike: """ Get the transaction to create a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param token_id: the token id for creation :param data: the data to include in the transaction :param gas: the gas to be used :param tx_fee: the tx_fee to be used :return: the transaction object """ if ledger_api.identifier == EthereumApi.identifier: gas = gas if gas is not None else DEFAUT_COSMOS_SINGLE_TASK_GAS_LIMIT nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.createSingle( deployer_address, token_id, data ).buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: gas = gas if gas is not None else DEFAUT_ETH_SINGLE_TASK_GAS_LIMIT msg = { "create_single": { "item_owner": deployer_address, "id": str(token_id), "path": str(data), } } cosmos_api = cast(CosmosApi, ledger_api) tx = cosmos_api.get_handle_transaction( deployer_address, contract_address, msg, amount=0, tx_fee=tx_fee, gas=gas, ) return tx raise NotImplementedError @classmethod def get_mint_batch_transaction( cls, ledger_api: LedgerApi, contract_address: Address, deployer_address: Address, recipient_address: Address, token_ids: List[int], mint_quantities: List[int], data: Optional[bytes] = b"", gas: Optional[int] = None, tx_fee: int = 0, ) -> JSONLike: """ Get the transaction to mint a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param recipient_address: the address of the recipient :param token_ids: the token ids :param mint_quantities: the quantity to mint for each token :param data: the data to include in the transaction :param gas: the gas to be used :param tx_fee: the tx_fee to be used :return: the transaction object """ cls.validate_mint_quantities(token_ids, mint_quantities) if ledger_api.identifier == EthereumApi.identifier: gas = gas if gas is not None else DEFAUT_ETH_BATCH_TASK_GAS_LIMIT nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.mintBatch( recipient_address, token_ids, mint_quantities, data ).buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: gas = gas if gas is not None else DEFAUT_COSMOS_BATCH_TASK_GAS_LIMIT tokens = [] for token_id, quantity in zip(token_ids, mint_quantities): tokens.append({"id": str(token_id), "value": str(quantity)}) msg = { "mint_batch": { "to_address": recipient_address, "data": str(data), "tokens": tokens, } } cosmos_api = cast(CosmosApi, ledger_api) tx = cosmos_api.get_handle_transaction( deployer_address, contract_address, msg, amount=0, tx_fee=tx_fee, gas=gas, ) return tx raise NotImplementedError @classmethod def validate_mint_quantities( cls, token_ids: List[int], mint_quantities: List[int] ) -> None: """Validate the mint quantities.""" for token_id, mint_quantity in zip(token_ids, mint_quantities): decoded_type = cls.decode_id(token_id) if decoded_type not in [ 1, 2, ]: raise ValueError( "The token type must be 1 or 2. Found type={} for token_id={}".format( decoded_type, token_id ) ) if decoded_type == 1: if mint_quantity != 1: raise ValueError( "Cannot mint NFT (token_id={}) with mint_quantity more than 1 (found={})".format( token_id, mint_quantity ) ) @staticmethod def decode_id(token_id: int) -> int: """ Decode a give token id. :param token_id: the byte shifted token id :return: the non-shifted id """ decoded_type = token_id >> 128 return decoded_type @classmethod def get_mint_single_transaction( cls, ledger_api: LedgerApi, contract_address: Address, deployer_address: Address, recipient_address: Address, token_id: int, mint_quantity: int, data: Optional[bytes] = b"", gas: Optional[int] = None, tx_fee: int = 0, ) -> JSONLike: """ Get the transaction to mint a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param recipient_address: the address of the recipient :param token_id: the token id :param mint_quantity: the quantity to mint :param data: the data to include in the transaction :param gas: the gas to be used :param tx_fee: the tx_fee to be used :return: the transaction object """ if ledger_api.identifier == EthereumApi.identifier: gas = gas if gas is not None else DEFAUT_ETH_SINGLE_TASK_GAS_LIMIT nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.mint( recipient_address, token_id, mint_quantity, data ).buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: gas = gas if gas is not None else DEFAUT_COSMOS_SINGLE_TASK_GAS_LIMIT msg = { "mint_single": { "to_address": recipient_address, "id": str(token_id), "supply": str(mint_quantity), "data": str(data), } } cosmos_api = cast(CosmosApi, ledger_api) tx = cosmos_api.get_handle_transaction( deployer_address, contract_address, msg, amount=0, tx_fee=tx_fee, gas=gas, ) return tx raise NotImplementedError @classmethod def get_balance( cls, ledger_api: LedgerApi, contract_address: Address, agent_address: Address, token_id: int, ) -> JSONLike: """ Get the balance for a specific token id. :param ledger_api: the ledger API :param contract_address: the address of the contract :param agent_address: the address :param token_id: the token id :return: the balance in a dictionary - {"balance": {token_id: int, balance: int}} """ if ledger_api.identifier == EthereumApi.identifier: instance = cls.get_instance(ledger_api, contract_address) balance = instance.functions.balanceOf(agent_address, token_id).call() result = {token_id: balance} return {"balance": result} if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: cosmos_api = cast(CosmosApi, ledger_api) msg: JSONLike = { "balance": {"address": str(agent_address), "id": str(token_id)} } query_res = cosmos_api.execute_contract_query(contract_address, msg) if query_res is None: raise ValueError("call to contract returned None") # Convert {"balance": balance: str} balances to Dict[token_id: int, balance: int] result = {token_id: int(cast(str, query_res["balance"]))} return {"balance": result} raise NotImplementedError @classmethod def get_atomic_swap_single_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, to_address: Address, token_id: int, from_supply: int, to_supply: int, value: int, trade_nonce: int, signature: Optional[str] = None, data: Optional[bytes] = b"", gas: Optional[int] = None, from_pubkey: Optional[str] = None, to_pubkey: Optional[str] = None, tx_fee: int = 0, ) -> JSONLike: """ Get the transaction for a trustless trade between two agents for a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param from_address: the address of the agent sending tokens, receiving ether :param to_address: the address of the agent receiving tokens, sending ether :param token_id: the token id :param from_supply: the supply of tokens by the sender :param to_supply: the supply of tokens by the receiver :param value: the amount of ether sent from the to_address to the from_address :param trade_nonce: the nonce of the trade, this is separate from the nonce of the transaction :param signature: the signature of the trade - used on Ethereum :param data: the data to include in the transaction :param gas: the gas to be used :param from_pubkey: Public key associated with from_address - Used on Cosmos/Fetch :param to_pubkey: Public key associated with to_address - Used on Cosmos/Fetch :param tx_fee: the tx_fee to be used :return: a ledger transaction object """ if from_supply > 0 and to_supply > 0: raise RuntimeError( "Can't determine direction of swap because from_supply and to_supply are both non-zero." ) if from_supply == 0 and to_supply == 0 and value == 0: raise RuntimeError("Invalid atomic swap with all supplies to be zero.") if ledger_api.identifier == EthereumApi.identifier: if signature is None: raise RuntimeError("Signature expected for Eth based contract.") if from_pubkey is not None or to_pubkey is not None: raise RuntimeError("Pubkeys not expected for Eth based contract.") gas = gas if gas is not None else DEFAUT_ETH_ATOMIC_SWAP_GAS_LIMIT nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) value_eth_wei = ledger_api.api.toWei(value, "ether") tx = instance.functions.trade( from_address, to_address, token_id, from_supply, to_supply, value_eth_wei, trade_nonce, signature, data, ).buildTransaction( { "gas": gas, "from": from_address, "value": value_eth_wei, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: if signature is not None: raise RuntimeError( "Signature not expected for Cosmos/Fetch based contract." ) cosmos_api = cast(CosmosApi, ledger_api) msgs: List[ProtoAny] = [] from_pubkey_required: bool = False to_pubkey_required: bool = False gas = gas if gas is not None else DEFAUT_COSMOS_ATOMIC_SWAP_GAS_LIMIT # from_address sends tokens if from_supply > 0: contract_msg: JSONLike = { "transfer_single": { "operator": str(from_address), "from_address": str(from_address), "to_address": str(to_address), "id": str(token_id), "value": str(from_supply), } } msgs.append( cosmos_api.get_packed_exec_msg( sender_address=from_address, contract_address=contract_address, msg=contract_msg, ) ) from_pubkey_required = True # to_address sends tokens if to_supply > 0: contract_msg = { "transfer_single": { "operator": str(to_address), "from_address": str(to_address), "to_address": str(from_address), "id": str(token_id), "value": str(to_supply), } } msgs.append( cosmos_api.get_packed_exec_msg( sender_address=to_address, contract_address=contract_address, msg=contract_msg, ) ) to_pubkey_required = True # Sending native tokens from to_address to from_address if value > 0: msgs.append( cosmos_api.get_packed_send_msg(to_address, from_address, value) ) to_pubkey_required = True # Determine required signers and generate tx if to_pubkey_required and not from_pubkey_required: if to_pubkey is None: raise RuntimeError( "to_pubkey is missing and required for Cosmos/Fetch based contract." ) tx = cosmos_api.get_multi_transaction( from_addresses=[to_address], pub_keys=[bytes.fromhex(to_pubkey)], msgs=msgs, gas=gas, tx_fee=tx_fee, ) elif to_pubkey_required and from_pubkey_required: if from_pubkey is None: raise RuntimeError( "from_pubkey is missing and required for Cosmos/Fetch based contract." ) if to_pubkey is None: raise RuntimeError( "to_pubkey is missing and required for Cosmos/Fetch based contract." ) tx = cosmos_api.get_multi_transaction( from_addresses=[from_address, to_address], pub_keys=[bytes.fromhex(from_pubkey), bytes.fromhex(to_pubkey)], msgs=msgs, gas=gas, tx_fee=tx_fee, ) else: if from_pubkey is None: raise RuntimeError( "from_pubkey is missing and required for Cosmos/Fetch based contract." ) tx = cosmos_api.get_multi_transaction( from_addresses=[from_address], pub_keys=[bytes.fromhex(from_pubkey)], msgs=msgs, gas=gas, tx_fee=tx_fee, ) return tx raise NotImplementedError # pragma: nocover @classmethod def get_balances( cls, ledger_api: LedgerApi, contract_address: Address, agent_address: Address, token_ids: List[int], ) -> JSONLike: """ Get the balances for a batch of specific token ids. :param ledger_api: the ledger API :param contract_address: the address of the contract :param agent_address: the address :param token_ids: the token id :return: the balances in dictionary - {"balances": {id: int, balance: int}} """ if ledger_api.identifier == EthereumApi.identifier: instance = cls.get_instance(ledger_api, contract_address) balances = instance.functions.balanceOfBatch( [agent_address] * 10, token_ids ).call() result = dict(zip(token_ids, balances)) return {"balances": result} if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: tokens = [] for token_id in token_ids: tokens.append({"address": agent_address, "id": str(token_id)}) msg: JSONLike = {"balance_batch": {"addresses": tokens}} cosmos_api = cast(CosmosApi, ledger_api) query_res = cosmos_api.execute_contract_query(contract_address, msg) # Convert List[balances: str] balances to Dict[token_id: int, balance: int] if query_res is None: raise ValueError("call to contract returned None") result = { token_id: int(balance) for token_id, balance in zip( token_ids, cast(List[str], query_res["balances"]) ) } return {"balances": result} raise NotImplementedError # pragma: nocover @classmethod def get_atomic_swap_batch_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, to_address: Address, token_ids: List[int], from_supplies: List[int], to_supplies: List[int], value: int, trade_nonce: int, signature: Optional[str] = None, data: Optional[bytes] = b"", gas: Optional[int] = None, from_pubkey: Optional[str] = None, to_pubkey: Optional[str] = None, tx_fee: int = 0, ) -> JSONLike: """ Get the transaction for a trustless trade between two agents for a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param from_address: the address of the agent sending tokens, receiving ether :param to_address: the address of the agent receiving tokens, sending ether :param token_ids: the token ids :param from_supplies: the supply of tokens by the sender :param to_supplies: the supply of tokens by the receiver :param value: the amount of ether sent from the to_address to the from_address :param trade_nonce: the nonce of the trade, this is separate from the nonce of the transaction :param signature: the signature of the trade - used on Ethereum :param data: the data to include in the transaction :param gas: the gas to be used :param from_pubkey: Public key associated with from_address - Used on Cosmos/Fetch :param to_pubkey: Public key associated with to_address - Used on Cosmos/Fetch :param tx_fee: the tx_fee to be used :return: a ledger transaction object """ if ledger_api.identifier == EthereumApi.identifier: if signature is None: raise RuntimeError("Signature expected for Eth based contract.") if from_pubkey is not None or to_pubkey is not None: raise RuntimeError("Pubkeys not expected for Eth based contract.") gas = gas if gas is not None else DEFAUT_ETH_ATOMIC_SWAP_GAS_LIMIT nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) value_eth_wei = ledger_api.api.toWei(value, "ether") tx = instance.functions.tradeBatch( from_address, to_address, token_ids, from_supplies, to_supplies, value_eth_wei, trade_nonce, signature, data, ).buildTransaction( { "gas": gas, "from": from_address, "value": value_eth_wei, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: if signature is not None: raise RuntimeError( "Signature not expected for Cosmos/Fetch based contract." ) gas = gas if gas is not None else DEFAUT_COSMOS_ATOMIC_SWAP_GAS_LIMIT cosmos_api = cast(CosmosApi, ledger_api) msgs: List[ProtoAny] = [] from_pubkey_required: bool = False to_pubkey_required: bool = False # Split token transfers to two batch transfers for each side from_tokens: List[Dict[str, str]] = [] to_tokens: List[Dict[str, str]] = [] for token_id, from_supply, to_supply in zip( token_ids, from_supplies, to_supplies ): if from_supply > 0: from_tokens.append({"id": str(token_id), "value": str(from_supply)}) if to_supply > 0: to_tokens.append({"id": str(token_id), "value": str(to_supply)}) # First direction of swap if len(from_tokens) != 0: contract_msg: JSONLike = { "transfer_batch": { "operator": str(from_address), "from_address": str(from_address), "to_address": str(to_address), "tokens": from_tokens, } } msgs.append( cosmos_api.get_packed_exec_msg( sender_address=from_address, contract_address=contract_address, msg=contract_msg, ) ) from_pubkey_required = True # Second direction of swap if len(to_tokens) != 0: contract_msg = { "transfer_batch": { "operator": str(to_address), "from_address": str(to_address), "to_address": str(from_address), "tokens": to_tokens, } } msgs.append( cosmos_api.get_packed_exec_msg( sender_address=to_address, contract_address=contract_address, msg=contract_msg, ) ) to_pubkey_required = True # Sending native tokens from to_address to from_address if value > 0: msgs.append( cosmos_api.get_packed_send_msg(to_address, from_address, value) ) to_pubkey_required = True if len(from_tokens) == 0 and len(to_tokens) == 0 and value == 0: raise RuntimeError("Invalid atomic swap with all supplies to be zero.") # Determine required signers and generate tx if to_pubkey_required and not from_pubkey_required: if to_pubkey is None: raise RuntimeError( "to_pubkey is missing and required for Cosmos/Fetch based contract." ) tx = cosmos_api.get_multi_transaction( from_addresses=[to_address], pub_keys=[bytes.fromhex(to_pubkey)], msgs=msgs, gas=gas, tx_fee=tx_fee, ) elif to_pubkey_required and from_pubkey_required: if from_pubkey is None: raise RuntimeError( "to_pubkey is missing and required for Cosmos/Fetch based contract." ) if to_pubkey is None: raise RuntimeError( "to_pubkey is missing and required for Cosmos/Fetch based contract." ) tx = cosmos_api.get_multi_transaction( from_addresses=[from_address, to_address], pub_keys=[bytes.fromhex(from_pubkey), bytes.fromhex(to_pubkey)], msgs=msgs, gas=gas, tx_fee=tx_fee, ) else: if from_pubkey is None: raise RuntimeError( "to_pubkey is missing and required for Cosmos/Fetch based contract." ) tx = cosmos_api.get_multi_transaction( from_addresses=[from_address], pub_keys=[bytes.fromhex(from_pubkey)], msgs=msgs, gas=gas, tx_fee=tx_fee, ) return tx raise NotImplementedError # pragma: nocover @classmethod def get_hash_single( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, to_address: Address, token_id: int, from_supply: int, to_supply: int, value: int, trade_nonce: int, ) -> bytes: """ Get the hash for a trustless trade between two agents for a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param from_address: the address of the agent sending tokens, receiving ether :param to_address: the address of the agent receiving tokens, sending ether :param token_id: the token id :param from_supply: the supply of tokens by the sender :param to_supply: the supply of tokens by the receiver :param value: the amount of ether sent from the to_address to the from_address :param trade_nonce: the nonce used in the trade :return: the transaction hash in a dict """ if ledger_api.identifier == EthereumApi.identifier: instance = cls.get_instance(ledger_api, contract_address) from_address_hash = instance.functions.getAddress(from_address).call() to_address_hash = instance.functions.getAddress(to_address).call() value_eth_wei = ledger_api.api.toWei(value, "ether") tx_hash = cls._get_hash_single( _from=from_address_hash, _to=to_address_hash, _id=token_id, _from_value=from_supply, _to_value=to_supply, _value_eth_wei=value_eth_wei, _nonce=trade_nonce, ) if ( tx_hash != instance.functions.getSingleHash( from_address, to_address, token_id, from_supply, to_supply, value_eth_wei, trade_nonce, ).call() ): raise ValueError( # pragma: nocover "On-chain and off-chain hash computation do not agree!" ) return tx_hash raise NotImplementedError # pragma: nocover @staticmethod def _get_hash_single( _from: bytes, _to: bytes, _id: int, _from_value: int, _to_value: int, _value_eth_wei: int, _nonce: int, ) -> bytes: """ Generate a hash mirroring the way we are creating this in the contract. :param _from: the from address hashed :param _to: the to address hashed :param _id: the token id :param _from_value: the from value :param _to_value: the to value :param _value_eth_wei: the value eth (in wei) :param _nonce: the trade nonce :return: the hash in bytes string representation """ return keccak256( b"".join( [ _from, _to, _id.to_bytes(32, "big"), _from_value.to_bytes(32, "big"), _to_value.to_bytes(32, "big"), _value_eth_wei.to_bytes(32, "big"), _nonce.to_bytes(32, "big"), ] ) ) @classmethod def get_hash_batch( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, to_address: Address, token_ids: List[int], from_supplies: List[int], to_supplies: List[int], value: int, trade_nonce: int, ) -> bytes: """ Get the hash for a trustless trade between two agents for a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param from_address: the address of the agent sending tokens, receiving ether :param to_address: the address of the agent receiving tokens, sending ether :param token_ids: the list of token ids for the bash transaction :param from_supplies: the quantities of tokens sent from the from_address to the to_address :param to_supplies: the quantities of tokens sent from the to_address to the from_address :param value: the value of ether sent from the from_address to the to_address :param trade_nonce: the trade nonce :return: the transaction hash in a dict """ if ledger_api.identifier == EthereumApi.identifier: instance = cls.get_instance(ledger_api, contract_address) from_address_hash = instance.functions.getAddress(from_address).call() to_address_hash = instance.functions.getAddress(to_address).call() value_eth_wei = ledger_api.api.toWei(value, "ether") tx_hash = cls._get_hash_batch( _from=from_address_hash, _to=to_address_hash, _ids=token_ids, _from_values=from_supplies, _to_values=to_supplies, _value_eth_wei=value_eth_wei, _nonce=trade_nonce, ) if ( tx_hash != instance.functions.getHash( from_address, to_address, token_ids, from_supplies, to_supplies, value_eth_wei, trade_nonce, ).call() ): raise ValueError( "On-chain and off-chain hash computation do not agree!" ) return tx_hash raise NotImplementedError # pragma: nocover @staticmethod def _get_hash_batch( _from: bytes, _to: bytes, _ids: List[int], _from_values: List[int], _to_values: List[int], _value_eth_wei: int, _nonce: int, ) -> bytes: """ Generate a hash mirroring the way we are creating this in the contract. :param _from: the from address hashed :param _to: the to address hashed :param _ids: the token ids :param _from_values: the from values :param _to_values: the to values :param _value_eth_wei: the value of eth (in wei) :param _nonce: the trade nonce :return: the hash in bytes string representation """ aggregate_hash = keccak256( b"".join( [ _ids[0].to_bytes(32, "big"), _from_values[0].to_bytes(32, "big"), _to_values[0].to_bytes(32, "big"), ] ) ) for idx, _id in enumerate(_ids): if not idx == 0: aggregate_hash = keccak256( b"".join( [ aggregate_hash, _id.to_bytes(32, "big"), _from_values[idx].to_bytes(32, "big"), _to_values[idx].to_bytes(32, "big"), ] ) ) m_list = [] m_list.append(_from) m_list.append(_to) m_list.append(aggregate_hash) m_list.append(_value_eth_wei.to_bytes(32, "big")) m_list.append(_nonce.to_bytes(32, "big")) return keccak256(b"".join(m_list)) @classmethod def generate_trade_nonce( cls, ledger_api: LedgerApi, contract_address: Address, agent_address: Address ) -> Dict[str, int]: """ Generate a valid trade nonce. :param ledger_api: the ledger API :param contract_address: the address of the contract :param agent_address: the address to use :return: the generated trade nonce """ if ledger_api.identifier == EthereumApi.identifier: instance = cls.get_instance(ledger_api, contract_address) trade_nonce = random.randrange(0, MAX_UINT_256) # nosec while instance.functions.is_nonce_used(agent_address, trade_nonce).call(): trade_nonce = random.randrange(0, MAX_UINT_256) # nosec return {"trade_nonce": trade_nonce} raise NotImplementedError # pragma: nocover ================================================ FILE: packages/fetchai/contracts/erc1155/contract.yaml ================================================ name: erc1155 author: fetchai version: 0.23.3 type: contract description: The erc1155 contract implements an ERC1155 contract package. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmVKKf8Uyub6iUQytLSKVJBrFW9h6ALtHY4c8XF7pihuhg __init__.py: QmTDUtfiUxkNFz3yemKkghfwsC7wYcyVJ9zzs99PXrrrVf build/Migrations.json: QmfFYYWoq1L1Ni6YPBWWoRPvCZKBLZ7qzN3UDX537mCeuE build/erc1155.json: Qma5n7au2NDCg1nLwYfYnmFNwWChFuXtu65w5DV7wAZRvw build/erc1155.wasm: QmVWAjvDT1qQFyZ8GxVkCm4gzR4KgE93BM5KrqfbDtwp2v contract.py: QmR4Etcu8w7KkyTmSrj6udFCWpFYarDKNRc9jR6NxG7TEc contracts/Migrations.sol: QmbW34mYrj3uLteyHf3S46pnp9bnwovtCXHbdBHfzMkSZx contracts/erc1155.vy: QmXwob8G1uX7fDvtuuKW139LALWtQmGw2vvaTRBVAWRxTx migrations/1_initial_migration.js: QmcxaWKQ2yPkQBmnpXmcuxPZQUMuUudmPmX3We8Z9vtAf7 migrations/2_deploy_contracts.js: QmeLraUZBgtJK5KAhsf4Bb5S9amcYugfMJsjbhZgfmEfuP fingerprint_ignore_patterns: [] class_name: ERC1155Contract contract_interface_paths: cosmos: build/erc1155.wasm ethereum: build/erc1155.json fetchai: build/erc1155.wasm dependencies: aea-ledger-cosmos: version: <2.0.0,>=1.0.0 aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/contracts/erc1155/contracts/Migrations.sol ================================================ pragma solidity >=0.4.21 <0.6.0; contract Migrations { address public owner; uint public last_completed_migration; constructor() public { owner = msg.sender; } modifier restricted() { if (msg.sender == owner) _; } function setCompleted(uint completed) public restricted { last_completed_migration = completed; } function upgrade(address new_address) public restricted { Migrations upgraded = Migrations(new_address); upgraded.setCompleted(last_completed_migration); } } ================================================ FILE: packages/fetchai/contracts/erc1155/contracts/erc1155.vy ================================================ # Author: Sören Steiger, github.com/ssteiger # Author: Fetch.ai, github.com/fetchai # License: MIT # ERC1155 Token Standard # https://eips.ethereum.org/EIPS/eip-1155 ########################EXTERNAL-CONTRACTS#################################### contract ERC1155TokenReceiver: # Note: The ERC-165 identifier for this interface is 0x4e2312e0. def onERC1155Received(_operator: address, _from: address, _id: uint256, _value: uint256, _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4 # """ # @notice Handle the receipt of a single ERC1155 token type. # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. # This function MUST return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` (i.e. 0xf23a6e61) if it accepts the transfer. # This function MUST revert if it rejects the transfer. # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. # @param _operator The address which initiated the transfer (i.e. msg.sender) # @param _from The address which previously owned the token # @param _id The ID of the token being transferred # @param _value The amount of tokens being transferred # @param _data Additional data with no specified format # @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` # """ def onERC1155BatchReceived(_operator: address, _from: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE], _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4 # """ # @notice Handle the receipt of multiple ERC1155 token types. # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. # This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s). # This function MUST revert if it rejects the transfer(s). # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. # @param _operator The address which initiated the batch transfer (i.e. msg.sender) # @param _from The address which previously owned the token # @param _ids An array containing ids of each token being transferred (order and length must match _values array) # @param _values An array containing amounts of each token being transferred (order and length must match _ids array) # @param _data Additional data with no specified format # @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` # """ ########################END-EXTERNAL-CONTRACTS#################################### ########################EVENTS#################################### MAX_URI_SIZE: constant(uint256) = 1024 TransferSingle: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address), _id: uint256, _value: uint256}) # @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). # The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). # The `_from` argument MUST be the address of the holder whose balance is decreased. # The `_to` argument MUST be the address of the recipient whose balance is increased. # The `_id` argument MUST be the token type being transferred. # The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. # When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). # When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). TransferBatch: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address), _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE]}) # @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). # The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). # The `_from` argument MUST be the address of the holder whose balance is decreased. # The `_to` argument MUST be the address of the recipient whose balance is increased. # The `_ids` argument MUST be the list of tokens being transferred. # The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. # When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). # When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). ApprovalForAll: event({_owner: indexed(address), _operator: indexed(address), _approved: bool}) # @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). URI: event({_value: string[MAX_URI_SIZE], _id: indexed(uint256)}) # @dev MUST emit when the URI is updated for a token ID. # URIs are defined in RFC 3986. # The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". ########################END-EVENTS#################################### ########################INITIALIZATION#################################### supportedInterfaces: map(bytes32, bool) # https://eips.ethereum.org/EIPS/eip-165 ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7 ERC1155_INTERFACE_ID: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26 tokensIdCount: uint256 owner: public(address) balancesOf: map(address, map(uint256, uint256)) noncesOf: map(address, map(uint256, bool)) uri: map(uint256, string[256]) operators: map(address, map(address, bool)) token_ids: map(uint256, bool) # This is to be set before contract migration! BATCH_SIZE: constant(uint256) = 10 @public def __init__(): """ @notice Called once and only upon contract deployment. """ self.tokensIdCount = convert(0, uint256) self.supportedInterfaces[ERC165_INTERFACE_ID] = True self.supportedInterfaces[ERC1155_INTERFACE_ID] = True self.owner = msg.sender ########################END-INITIALIZATION#################################### ########################PRIVATE-FUNCTIONS#################################### ######### THIS IS A TEMPORARY SOLUTION ################# @public @constant def getAddress(_addr: address) -> bytes32: hash: bytes32 = convert(_addr, bytes32) return hash ##################### END ############################## @private @constant def _getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32: """ @notice Get the hash from the tx values. @param _from The address of the sender. @param _to The address of the receiver. @param _ids The ids of the tokens. @param _from_supplies The supply of token values that will send the _from. @param _to_supplies The supply of token values that will send the _from. @param _value_eth The value of the ether. @param _nonce The nonce. @return the hash """ aggregate_hash: bytes32 = keccak256(concat(convert(_ids[0], bytes32), convert(_from_supplies[0], bytes32), convert(_to_supplies[0], bytes32))) for i in range(BATCH_SIZE): if not i == 0: aggregate_hash = keccak256(concat(aggregate_hash, convert(_ids[i], bytes32), convert(_from_supplies[i], bytes32), convert(_to_supplies[i], bytes32))) hash: bytes32 = keccak256(concat(convert(_from, bytes32), convert(_to, bytes32), aggregate_hash, convert(_value_eth, bytes32), convert(_nonce, bytes32))) return hash @public @constant def getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32: """ @notice Get the hash from the tx values. @param _from The address of the sender. @param _to The address of the receiver. @param _ids The ids of the tokens. @param _from_supplies The supply of token values that will send the _from. @param _to_supplies The supply of token values that will send the _from. @param _value_eth The value of the ether. @param _nonce The nonce. @return the hash """ return self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce) @private @constant def getHashOld(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32: """ @notice Get the hash from the tx values. @param _from The address of the sender. @param _to The address of the receiver. @param _ids The ids of the tokens. @param _from_supplies The supply of token values that will send the _from. @param _to_supplies The supply of token values that will send the _from. @param _value_eth The value of the ether. @param _nonce The nonce. @return the hash """ hash: bytes32 = keccak256(concat(convert(_from, bytes32), convert(_to, bytes32), convert(_ids[0], bytes32), convert(_ids[1], bytes32), convert(_ids[2], bytes32), convert(_ids[3], bytes32), convert(_ids[4], bytes32), convert(_ids[5], bytes32), convert(_ids[6], bytes32), convert(_ids[7], bytes32), convert(_ids[8], bytes32), convert(_ids[9], bytes32), convert(_from_supplies[0], bytes32), convert(_from_supplies[1], bytes32), convert(_from_supplies[2], bytes32), convert(_from_supplies[3], bytes32), convert(_from_supplies[4], bytes32), convert(_from_supplies[5], bytes32), convert(_from_supplies[6], bytes32), convert(_from_supplies[7], bytes32), convert(_from_supplies[8], bytes32), convert(_from_supplies[9], bytes32), convert(_to_supplies[0], bytes32), convert(_to_supplies[1], bytes32), convert(_to_supplies[2], bytes32), convert(_to_supplies[3], bytes32), convert(_to_supplies[4], bytes32), convert(_to_supplies[5], bytes32), convert(_to_supplies[6], bytes32), convert(_to_supplies[7], bytes32), convert(_to_supplies[8], bytes32), convert(_to_supplies[9], bytes32), convert(_value_eth, bytes32), convert(_nonce, bytes32))) return hash @private @constant def _getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32: """ @notice Get the hash from the tx values. @param _from The address of the sender. @param _to The address of the receiver. @param _id The id of the tokens. @param _from_supply The from token value. (_from sends) @param _to_supply The to token value (_to sends). @param _value_eth The value of the ether. @param _nonce The nonce. @return the hash """ hash: bytes32 = keccak256(concat(convert(_from, bytes32), convert(_to, bytes32), convert(_id, bytes32), convert(_from_supply, bytes32), convert(_to_supply, bytes32), convert(_value_eth, bytes32), convert(_nonce, bytes32))) return hash @public @constant def getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32: """ @notice Get the hash from the tx values. @param _from The address of the sender. @param _to The address of the receiver. @param _id The id of the tokens. @param _from_supply The from token value. (_from sends) @param _to_supply The to token value (_to sends). @param _value_eth The value of the ether. @param _nonce The nonce. @return the hash """ return self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce) @private @constant def ecrecoverSig(_hash: bytes32, _sig: bytes[65]) -> address: """ @notice Check whether the the signature matches the hash. @param _hash The hash to be checked. @param _sig The signature which is meant to match the hash. @return the address which signed the signature or the zero address """ if len(_sig) != 65: return ZERO_ADDRESS # ref. https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d # The signature format is a compact form of: # {bytes32 r}{bytes32 s}{uint8 v} r: bytes32 = extract32(_sig, 0, type=bytes32) s: bytes32 = extract32(_sig, 32, type=bytes32) v: int128 = convert(slice(_sig, start=64, len=1), int128) # Version of signature should be 27 or 28, but 0 and 1 are also possible versions. # geth uses [0, 1] and some clients have followed. This might change, see: # https://github.com/ethereum/go-ethereum/issues/2053 if v < 27: v += 27 if v in [27, 28]: return ecrecover(_hash, convert(v, uint256), convert(r, uint256), convert(s, uint256)) return ZERO_ADDRESS @private @constant def decode_id(id: uint256) -> int128: """ @notice Decodes the id of the token inorder to find out if it NFT or FT. @param id: uint256 @return token_id : int128 (Specified id for FT and NFT.) @dev shift(x, -y): returns x with the bits shifted to the right by y places, which is equivalent to dividing x by 2**y. """ decoded_token_id: int128 = convert(shift(id, -128), int128) decoded_index: int128 = convert(id % 2 ** 128, int128) return decoded_token_id ########################END-PRIVATE-FUNCTIONS################################ ########################PUBLIC-FUNCTIONS##################################### @public @constant def supportsInterface(_interfaceID: bytes32) -> bool: """ @notice Check whether the interface id is supported. @param _interfaceID The interface id @return True if the interface id is supported. """ return self.supportedInterfaces[_interfaceID] @public @constant def is_nonce_used(addr: address, nonce: uint256) -> bool: """ @notice Checks if the given nonce for the give address is unused. @param nonce: uint256 the counter of the transaction @param address: the address that want to transact. """ return self.noncesOf[addr][nonce] @public @constant def is_token_id_exists(token_id: uint256) -> bool: """ @notice Checks if the given token_id is already created. @param token_id: uint256 the id of the token. """ return self.token_ids[token_id] @public def safeTransferFrom(_from: address, _to: address, _id: uint256, _value: uint256, _data: bytes[256]): """ @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). MUST revert if `_to` is the zero address. MUST revert if balance of holder for token `_id` is lower than the `_value` sent. MUST revert on any other error. MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). @param _from Source address @param _to Target address @param _id ID of the token type @param _value Transfer amount @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` @return None """ assert _from == msg.sender or (self.operators[_from])[msg.sender] assert _to != ZERO_ADDRESS, "Cannot transfer to zero address." assert self.balancesOf[_from][_id] >= _value, "Not enough tokens." self.balancesOf[_from][_id] -= _value self.balancesOf[_to][_id] += _value log.TransferSingle(msg.sender, _from, _to, _id, _value) if _to.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data) assert returnValue == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", bytes32) @public def safeBatchTransferFrom(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE], _data: bytes[256]): """ @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). MUST revert if `_to` is the zero address. MUST revert if length of `_ids` is not the same as length of `_values`. MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. MUST revert on any other error. MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). @param _from Source address @param _to Target address @param _ids IDs of each token type (order and length must match _values array) @param _values Transfer amounts per token type (order and length must match _ids array) @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` @return None """ assert _from == msg.sender or (self.operators[_from])[msg.sender] assert _to != ZERO_ADDRESS, "Cannot transfer to zero address." for i in range(BATCH_SIZE): id: uint256 = _ids[i] assert self.balancesOf[_from][id] >= _values[i] log.TransferBatch(msg.sender, _from, _to, _ids, _values) for i in range(BATCH_SIZE): id: uint256 = _ids[i] self.balancesOf[_from][id] -= _values[i] self.balancesOf[_to][id] += _values[i] if _to.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _values, _data) assert returnValue == method_id("onERC1155BatchReceived(address,address,uint256[BATCH_SIZE],uint256[BATCH_SIZE],bytes)", bytes32) @public @constant def balanceOf(_owner: address, _id: uint256) -> uint256: """ @notice Get the balance of an account's tokens. @param _owner The address of the token holder @param _id ID of the token @return The _owner's balance of the token type requested """ return self.balancesOf[_owner][_id] @public @constant def balanceOfBatch( _owner: address[BATCH_SIZE], _ids: uint256[BATCH_SIZE]) -> uint256[BATCH_SIZE]: """ @notice Get the balance of multiple account/token pairs @param _owners The addresses of the token holders @param _ids ID of the tokens @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) """ returnBalances: uint256[BATCH_SIZE] for i in range(BATCH_SIZE): returnBalances[i] = self.balancesOf[_owner[i]][_ids[i]] return returnBalances @public def setApprovalForAll(_operator: address, _approved: bool): """ @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. @dev MUST emit the ApprovalForAll event on success. @param _operator Address to add to the set of authorized operators @param _approved True if the operator is approved, false to revoke approval @return None """ (self.operators[msg.sender])[_operator] = _approved log.ApprovalForAll(msg.sender, _operator, _approved) @public @constant def isApprovedForAll(_owner: address, _operator: address) -> bool: """ @notice Queries the approval status of an operator for a given owner. @param _owner The owner of the tokens. @param _operator Address of authorized operator. @return True if the operator is approved, false if not """ return (self.operators[_owner])[_operator] @public def createSingle(_item_owner: address, _id: uint256, _path: string[256]): """ @notice Create a new token type that we can mint later. @param _item_owner The owner of the item. @param _id The id of the token. @param _path The path to the token data. @return None """ assert _item_owner != ZERO_ADDRESS assert self.owner == msg.sender, "Owner only can create item." self.balancesOf[_item_owner][_id] = 0 self.tokensIdCount += 1 self.token_ids[_id] = True self.uri[_id] = _path log.URI(_path, _id) log.TransferSingle(msg.sender, ZERO_ADDRESS, _item_owner, _id, 0) @public def createBatch(_items_owner: address, _ids: uint256[BATCH_SIZE]): """ @notice Create new token types that we can mint later. @param _items_owner The owner of the items. @param _ids The ids of the tokens. @return None """ assert _items_owner != ZERO_ADDRESS assert self.owner == msg.sender, "Owner only can create items." for i in range(BATCH_SIZE): id: uint256 = _ids[i] self.balancesOf[_items_owner][id] = 0 self.tokensIdCount += 1 self.token_ids[id] = True zero_supply: uint256[BATCH_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] log.TransferBatch(msg.sender, ZERO_ADDRESS, _items_owner, _ids, zero_supply) @public def mint(_to: address, _id: uint256, _supply: uint256, _data: bytes[256]=""): """ @notice Mint a token. @dev This is not part of the standard. @param _to The address of the receiver. @param _id The id of the token. @param _supply The supply to be minted for the token. @param _data The data. @return None """ assert _to != ZERO_ADDRESS assert self.owner == msg.sender, "Owner only can mint items." decoded_id: int128 = self.decode_id(_id) assert decoded_id == 1 or decoded_id == 2 if decoded_id == 1 : assert _supply == 1, "Cannot mint NFT with _supply more than 1" self.balancesOf[_to][_id] = _supply log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, _id, _supply) if _to.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _id, _supply, _data) assert returnValue == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", bytes32) @public def mintBatch(_to: address, _ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE], _data: bytes[256]=""): """ @notice Mint a batch of tokens. @dev This is not part of the standard. @param _to The address of the receiver. @param _ids The ids of the tokens. @param _supplies The supply to be minted for each token. @param _data The data. @return None """ assert _to != ZERO_ADDRESS assert self.owner == msg.sender, "Owner only can mint items." for i in range(BATCH_SIZE): id: uint256 = _ids[i] decoded_id: int128 = self.decode_id(id) assert decoded_id == 1 or decoded_id == 2 if decoded_id == 1 : assert _supplies[i] == 1 self.balancesOf[_to][id] = _supplies[i] log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, _ids, _supplies) for i in range(BATCH_SIZE): if _to.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _ids[i], _supplies[i], _data) assert returnValue == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", bytes32) @public def burn(_id: uint256, _supply: uint256): """ @notice Burns the supply of the specified token. @param _id The id of the token @param _supply Supply to be burned @return None """ assert self.balancesOf[msg.sender][_id] >= _supply, "Not enough tokens to burn." self.balancesOf[msg.sender][_id] -= _supply log.TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, _id, _supply) @public def burnBatch(_ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE]): """ @notice Burns the supply of the specified tokens. @dev At this point anyone can burn items if they own it. @param _ids The ids of the token @param _supplies Supplies to be burned @return None """ for i in range(BATCH_SIZE): id: uint256 = _ids[i] assert self.balancesOf[msg.sender][id] >= _supplies[i] for i in range(BATCH_SIZE): id: uint256 = _ids[i] self.balancesOf[msg.sender][id] -= _supplies[i] log.TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, _ids, _supplies) @public @payable def tradeBatch(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=""): """ @notice Trade (atomically swap) tokens with tokens or eth. @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). MUST revert if `_to` is the zero address. MUST revert if _from_supplies[i] > 0 and _to_supplies[i] > 0 MUST revert if len(_ids) != len(_from_supplies) != len(_to_supplies) MUST revert if _value_eth != msg.value MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `positive and negative values` sent to the recipient. MUST revert on any other error. MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). @param _from The address of the sender. @param _to The address of the receiver. @param _ids The ids of the tokens. @param _from_supplies The supply of token values that will send the _from. @param _to_supplies The supply of token values that will send the _from. @param _value_eth The value of the ether. @param _nonce The nonce. @param _signature The signature of the _to address. @param _data The data. @return None """ # Assert the value of the transaction is less than the balance of A. assert _from == msg.sender or (self.operators[_from])[msg.sender], "_from must be the sender or approved address" assert _to != ZERO_ADDRESS, "Destination address must be non-zero." assert self.noncesOf[_from][_nonce] == False, "Nonce must be unused." assert _value_eth == msg.value, "Sender has not provided enough ether." for i in range(BATCH_SIZE): id: uint256 = _ids[i] if _from_supplies[i] > 0: assert _to_supplies[i] == 0 assert self.balancesOf[_from][id] >= _from_supplies[i] else: assert _from_supplies[i] == 0 assert self.balancesOf[_to][id] >= _to_supplies[i] # Create hash from variables. hash: bytes32 = self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce) # Assert that the ecrecover(address,signature) returns true. recovered_to: address = self.ecrecoverSig(hash, _signature) assert recovered_to == _to, "Signer does not match signature." # Store the nonce self.noncesOf[msg.sender][_nonce] = True # Update the balances for i in range(BATCH_SIZE): id: uint256 = _ids[i] if _from_supplies[i] > 0: self.balancesOf[_from][id] -= _from_supplies[i] self.balancesOf[_to][id] += _from_supplies[i] else: self.balancesOf[_from][id] += _to_supplies[i] self.balancesOf[_to][id] -= _to_supplies[i] send(_to, msg.value) log.TransferBatch(msg.sender, _from, _to, _ids, _from_supplies) log.TransferBatch(msg.sender, _to, _from, _ids, _to_supplies) if _to.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _to, _ids, _from_supplies, _data) assert returnValue == method_id("onERC1155BatchReceived(address,address,uint256,uint256,bytes)", bytes32) if _from.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155BatchReceived(msg.sender, _from, _ids, _to_supplies, _data) assert returnValue == method_id("onERC1155BatchReceived(address,address,uint256,uint256,bytes)", bytes32) @public @payable def trade(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=""): """ @notice Trade (atomically swap) tokens with tokens or eth. @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). MUST revert if `_to` is the zero address. MUST revert if _from_supply > 0 and _to_supply > 0 MUST revert if _value_eth != msg.value MUST revert if any of the balance(s) of the holder(s) for token(s) in `_id` is lower than the respective amount in `positive or negative value` sent to the recipient. MUST revert on any other error. MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). @param _from The from address (seller of eth, potential receiver of tokens). @param _to The receiver address (receiver of tokens). @param _id The id of the token @param _from_supply The change in value of token (for _from) @param _to_supply The change in value of token (for _to) @param _value_eth The value of the ETH sent to the _from address. @param _nonce The nonce. @param _signature The signature of the _to address. @param _data The data. @return None """ # Assert the value of the transaction is less than the balance of A. assert _from == msg.sender or (self.operators[_from])[msg.sender] assert _to != ZERO_ADDRESS, "Destination address must be non-zero." assert self.noncesOf[_from][_nonce] == False, "Nonce must be unused." assert _value_eth == msg.value, "Sender has not provided enough ether." if _from_supply > 0: assert _to_supply == 0 assert self.balancesOf[_from][_id] >= _from_supply else: assert _from_supply == 0 assert self.balancesOf[_to][_id] >= _to_supply # Create hash from variables. hash: bytes32 = self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce) # Assert that the ecrecover(address,signature) returns true. recovered_to: address = self.ecrecoverSig(hash, _signature) assert recovered_to == _to, "Signer does not match signature." # Store the nonce self.noncesOf[msg.sender][_nonce] = True # Update the balances if _from_supply > 0: self.balancesOf[_from][_id] -= _from_supply self.balancesOf[_to][_id] += _from_supply else: self.balancesOf[_from][_id] += _to_supply self.balancesOf[_to][_id] -= _to_supply send(_to, msg.value) log.TransferSingle(msg.sender, _from, _to, _id, _from_supply) log.TransferSingle(msg.sender, _to, _from, _id, _to_supply) if _to.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _to, _id, _from_supply, _data) assert returnValue == method_id("onERC1155BatchReceived(address,address,uint256,uint256,bytes)", bytes32) if _from.is_contract: returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155Received(msg.sender, _from, _id, _to_supply, _data) assert returnValue == method_id("onERC1155BatchReceived(address,address,uint256,uint256,bytes)", bytes32) ================================================ FILE: packages/fetchai/contracts/erc1155/migrations/1_initial_migration.js ================================================ var Migrations = artifacts.require("./Migrations.sol"); module.exports = function(deployer) { deployer.deploy(Migrations); }; ================================================ FILE: packages/fetchai/contracts/erc1155/migrations/2_deploy_contracts.js ================================================ var ERC1155 = artifacts.require("erc1155"); module.exports = function(deployer) { deployer.deploy(ERC1155); }; ================================================ FILE: packages/fetchai/contracts/fet_erc20/README.md ================================================ # Fetch ERC20 Contract ## Description This contract package is used to interface with a Fetch ERC20 contract. ## Functions - approve(spender, amount): approve address `spender` to transfer up to `amount` tokens on behalf of sender - transfer(receiver, amount): transfer `amount` tokens from sender to receiver ================================================ FILE: packages/fetchai/contracts/fet_erc20/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the support resources for the fet_erc20 contract.""" ================================================ FILE: packages/fetchai/contracts/fet_erc20/build/FetERC20Mock.json ================================================ { "abi": [ { "inputs": [ { "internalType": "string", "name": "name", "type": "string" }, { "internalType": "string", "name": "symbol", "type": "string" }, { "internalType": "uint256", "name": "initialSupply", "type": "uint256" }, { "internalType": "uint8", "name": "decimals_", "type": "uint8" } ], "stateMutability": "payable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "approveInternal", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "balanceOf", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "burn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "decimals", "outputs": [ { "internalType": "uint8", "name": "", "type": "uint8" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } ], "name": "decreaseAllowance", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "addedValue", "type": "uint256" } ], "name": "increaseAllowance", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "mint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "name", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalSupply", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transfer", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "transferInternal", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ], "allSourcePaths": { "1": "contracts/Address.sol", "12": "contracts/SafeMath.sol", "13": "interfaces/IERC20.sol", "2": "contracts/Context.sol", "3": "contracts/ERC20.sol", "4": "contracts/ERC20Mock.sol", "6": "contracts/FetERC20Mock.sol" }, "ast": { "absolutePath": "contracts/FetERC20Mock.sol", "exportedSymbols": { "FetERC20Mock": [ 1516 ] }, "id": 1517, "license": "Apache-2.0", "nodeType": "SourceUnit", "nodes": [ { "id": 1489, "literals": [ "solidity", "^", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "820:23:6" }, { "absolutePath": "contracts/ERC20Mock.sol", "file": "./ERC20Mock.sol", "id": 1490, "nodeType": "ImportDirective", "scope": 1517, "sourceUnit": 1093, "src": "845:25:6", "symbolAliases": [], "unitAlias": "" }, { "abstract": false, "baseContracts": [ { "arguments": null, "baseName": { "contractScope": null, "id": 1491, "name": "ERC20Mock", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 1092, "src": "897:9:6", "typeDescriptions": { "typeIdentifier": "t_contract$_ERC20Mock_$1092", "typeString": "contract ERC20Mock" } }, "id": 1492, "nodeType": "InheritanceSpecifier", "src": "897:9:6" } ], "contractDependencies": [ 500, 1007, 1092, 2713 ], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 1516, "linearizedBaseContracts": [ 1516, 1092, 1007, 2713, 500 ], "name": "FetERC20Mock", "nodeType": "ContractDefinition", "nodes": [ { "body": { "id": 1514, "nodeType": "Block", "src": "1136:42:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1511, "name": "decimals_", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1500, "src": "1161:9:6", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint8", "typeString": "uint8" } ], "id": 1510, "name": "_setupDecimals", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 995, "src": "1146:14:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint8_$returns$__$", "typeString": "function (uint8)" } }, "id": 1512, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1146:25:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1513, "nodeType": "ExpressionStatement", "src": "1146:25:6" } ] }, "documentation": null, "id": 1515, "implemented": true, "kind": "constructor", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1503, "name": "name", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1494, "src": "1091:4:6", "typeDescriptions": { "typeIdentifier": "t_string_memory_ptr", "typeString": "string memory" } }, { "argumentTypes": null, "id": 1504, "name": "symbol", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1496, "src": "1097:6:6", "typeDescriptions": { "typeIdentifier": "t_string_memory_ptr", "typeString": "string memory" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1505, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "1105:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1506, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "1105:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1507, "name": "initialSupply", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1498, "src": "1117:13:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1508, "modifierName": { "argumentTypes": null, "id": 1502, "name": "ERC20Mock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1092, "src": "1081:9:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_ERC20Mock_$1092_$", "typeString": "type(contract ERC20Mock)" } }, "nodeType": "ModifierInvocation", "src": "1081:50:6" } ], "name": "", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1501, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1494, "mutability": "mutable", "name": "name", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1515, "src": "935:18:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_string_memory_ptr", "typeString": "string" }, "typeName": { "id": 1493, "name": "string", "nodeType": "ElementaryTypeName", "src": "935:6:6", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1496, "mutability": "mutable", "name": "symbol", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1515, "src": "963:20:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_string_memory_ptr", "typeString": "string" }, "typeName": { "id": 1495, "name": "string", "nodeType": "ElementaryTypeName", "src": "963:6:6", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1498, "mutability": "mutable", "name": "initialSupply", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1515, "src": "993:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1497, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "993:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1500, "mutability": "mutable", "name": "decimals_", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1515, "src": "1024:15:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 1499, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "1024:5:6", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "internal" } ], "src": "925:124:6" }, "returnParameters": { "id": 1509, "nodeType": "ParameterList", "parameters": [], "src": "1136:0:6" }, "scope": 1516, "src": "913:265:6", "stateMutability": "payable", "virtual": false, "visibility": "public" } ], "scope": 1517, "src": "872:308:6" } ], "src": "820:361:6" }, "bytecode": "6080604052604051620011eb380380620011eb833981810160405260808110156200002957600080fd5b81019080805160405193929190846401000000008211156200004a57600080fd5b9083019060208201858111156200006057600080fd5b82516401000000008111828201881017156200007b57600080fd5b82525081516020918201929091019080838360005b83811015620000aa57818101518382015260200162000090565b50505050905090810190601f168015620000d85780820380516001836020036101000a031916815260200191505b5060405260200180516040519392919084640100000000821115620000fc57600080fd5b9083019060208201858111156200011257600080fd5b82516401000000008111828201881017156200012d57600080fd5b82525081516020918201929091019080838360005b838110156200015c57818101518382015260200162000142565b50505050905090810190601f1680156200018a5780820380516001836020036101000a031916815260200191505b506040908152602082810151929091015186519294509250859185913391869185918591620001bf916003918501906200039c565b508051620001d59060049060208401906200039c565b50506005805460ff1916601217905550620001f1828262000210565b5050505062000206816200031f60201b60201c565b5050505062000438565b6001600160a01b0382166200026c576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6200027a6000838362000335565b62000296816002546200033a60201b620006b81790919060201c565b6002556001600160a01b03821660009081526020818152604090912054620002c9918390620006b86200033a821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6005805460ff191660ff92909216919091179055565b505050565b60008282018381101562000395576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620003df57805160ff19168380011785556200040f565b828001600101855582156200040f579182015b828111156200040f578251825591602001919060010190620003f2565b506200041d92915062000421565b5090565b5b808211156200041d576000815560010162000422565b610da380620004486000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c806340c10f19116100975780639dc29fac116100665780639dc29fac14610319578063a457c2d714610345578063a9059cbb14610371578063dd62ed3e1461039d576100f5565b806340c10f191461028957806356189cb4146102b557806370a08231146102eb57806395d89b4114610311576100f5565b8063222f5be0116100d3578063222f5be0146101d157806323b872dd14610209578063313ce5671461023f578063395093511461025d576100f5565b806306fdde03146100fa578063095ea7b31461017757806318160ddd146101b7575b600080fd5b6101026103cb565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561013c578181015183820152602001610124565b50505050905090810190601f1680156101695780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101a36004803603604081101561018d57600080fd5b506001600160a01b038135169060200135610461565b604080519115158252519081900360200190f35b6101bf61047e565b60408051918252519081900360200190f35b610207600480360360608110156101e757600080fd5b506001600160a01b03813581169160208101359091169060400135610484565b005b6101a36004803603606081101561021f57600080fd5b506001600160a01b03813581169160208101359091169060400135610494565b61024761051b565b6040805160ff9092168252519081900360200190f35b6101a36004803603604081101561027357600080fd5b506001600160a01b038135169060200135610524565b6102076004803603604081101561029f57600080fd5b506001600160a01b038135169060200135610572565b610207600480360360608110156102cb57600080fd5b506001600160a01b03813581169160208101359091169060400135610580565b6101bf6004803603602081101561030157600080fd5b50356001600160a01b031661058b565b6101026105a6565b6102076004803603604081101561032f57600080fd5b506001600160a01b038135169060200135610607565b6101a36004803603604081101561035b57600080fd5b506001600160a01b038135169060200135610611565b6101a36004803603604081101561038757600080fd5b506001600160a01b038135169060200135610679565b6101bf600480360360408110156103b357600080fd5b506001600160a01b038135811691602001351661068d565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156104575780601f1061042c57610100808354040283529160200191610457565b820191906000526020600020905b81548152906001019060200180831161043a57829003601f168201915b5050505050905090565b600061047561046e610719565b848461071d565b50600192915050565b60025490565b61048f838383610809565b505050565b60006104a1848484610809565b610511846104ad610719565b61050c85604051806060016040528060288152602001610cb7602891396001600160a01b038a166000908152600160205260408120906104eb610719565b6001600160a01b031681526020810191909152604001600020549190610964565b61071d565b5060019392505050565b60055460ff1690565b6000610475610531610719565b8461050c8560016000610542610719565b6001600160a01b03908116825260208083019390935260409182016000908120918c1681529252902054906106b8565b61057c82826109fb565b5050565b61048f83838361071d565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156104575780601f1061042c57610100808354040283529160200191610457565b61057c8282610aeb565b600061047561061e610719565b8461050c85604051806060016040528060258152602001610d496025913960016000610648610719565b6001600160a01b03908116825260208083019390935260409182016000908120918d16815292529020549190610964565b6000610475610686610719565b8484610809565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600082820183811015610712576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166107625760405162461bcd60e51b8152600401808060200182810382526024815260200180610d256024913960400191505060405180910390fd5b6001600160a01b0382166107a75760405162461bcd60e51b8152600401808060200182810382526022815260200180610c6f6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b03831661084e5760405162461bcd60e51b8152600401808060200182810382526025815260200180610d006025913960400191505060405180910390fd5b6001600160a01b0382166108935760405162461bcd60e51b8152600401808060200182810382526023815260200180610c2a6023913960400191505060405180910390fd5b61089e83838361048f565b6108db81604051806060016040528060268152602001610c91602691396001600160a01b0386166000908152602081905260409020549190610964565b6001600160a01b03808516600090815260208190526040808220939093559084168152205461090a90826106b8565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109f35760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156109b85781810151838201526020016109a0565b50505050905090810190601f1680156109e55780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6001600160a01b038216610a56576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b610a626000838361048f565b600254610a6f90826106b8565b6002556001600160a01b038216600090815260208190526040902054610a9590826106b8565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6001600160a01b038216610b305760405162461bcd60e51b8152600401808060200182810382526021815260200180610cdf6021913960400191505060405180910390fd5b610b3c8260008361048f565b610b7981604051806060016040528060228152602001610c4d602291396001600160a01b0385166000908152602081905260409020549190610964565b6001600160a01b038316600090815260208190526040902055600254610b9f9082610be7565b6002556040805182815290516000916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b600061071283836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061096456fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122025eb021c0ed77f150fa57486a8a5859aa0bd55f4a7e710b5e08119b2c079293964736f6c634300060c0033", "bytecodeSha1": "ea60ac335378f826c77f1866c8f5cdddd74e1e95", "compiler": { "evm_version": "istanbul", "optimizer": { "enabled": true, "runs": 200 }, "version": "0.6.12" }, "contractName": "FetERC20Mock", "coverageMap": { "branches": { "1": {}, "12": { "SafeMath.add": { "49": [ 986, 992, true ] }, "SafeMath.sub": { "50": [ 1859, 1865, true ] } }, "13": {}, "2": {}, "3": { "ERC20._approve": { "43": [ 9445, 9464, true ], "44": [ 9523, 9544, true ] }, "ERC20._burn": { "48": [ 8592, 8613, true ] }, "ERC20._mint": { "47": [ 7903, 7924, true ] }, "ERC20._transfer": { "45": [ 7125, 7145, true ], "46": [ 7205, 7228, true ] } }, "4": {}, "6": {} }, "statements": { "1": {}, "12": { "SafeMath.add": { "18": [ 978, 1024 ], "19": [ 1035, 1043 ] }, "SafeMath.sub": { "31": [ 1851, 1880 ], "42": [ 1398, 1448 ] } }, "13": {}, "2": { "Context._msgSender": { "20": [ 670, 687 ] } }, "3": { "ERC20._approve": { "21": [ 9437, 9505 ], "22": [ 9515, 9583 ], "23": [ 9594, 9630 ], "24": [ 9640, 9677 ] }, "ERC20._burn": { "37": [ 8584, 8651 ], "38": [ 8662, 8711 ], "39": [ 8722, 8811 ], "40": [ 8821, 8860 ], "41": [ 8870, 8912 ] }, "ERC20._mint": { "32": [ 7895, 7960 ], "33": [ 7971, 8020 ], "34": [ 8031, 8070 ], "35": [ 8080, 8131 ], "36": [ 8141, 8183 ] }, "ERC20._transfer": { "25": [ 7117, 7187 ], "26": [ 7197, 7268 ], "27": [ 7279, 7326 ], "28": [ 7337, 7428 ], "29": [ 7438, 7493 ], "30": [ 7503, 7543 ] }, "ERC20.allowance": { "17": [ 4061, 4095 ] }, "ERC20.approve": { "1": [ 4339, 4378 ], "2": [ 4388, 4399 ] }, "ERC20.balanceOf": { "12": [ 3488, 3513 ] }, "ERC20.decimals": { "8": [ 3164, 3180 ] }, "ERC20.decreaseAllowance": { "15": [ 6389, 6518 ] }, "ERC20.increaseAllowance": { "9": [ 5682, 5765 ] }, "ERC20.name": { "0": [ 2266, 2278 ] }, "ERC20.symbol": { "13": [ 2462, 2476 ] }, "ERC20.totalSupply": { "3": [ 3319, 3338 ] }, "ERC20.transfer": { "16": [ 3825, 3867 ] }, "ERC20.transferFrom": { "5": [ 4988, 5024 ], "6": [ 5034, 5155 ], "7": [ 5165, 5176 ] } }, "4": { "ERC20Mock.approveInternal": { "11": [ 787, 818 ] }, "ERC20Mock.burn": { "14": [ 540, 562 ] }, "ERC20Mock.mint": { "10": [ 441, 463 ] }, "ERC20Mock.transferInternal": { "4": [ 659, 685 ] } }, "6": {} } }, "dependencies": [ "Address", "Context", "ERC20", "ERC20Mock", "IERC20", "SafeMath" ], "deployedBytecode": "608060405234801561001057600080fd5b50600436106100f55760003560e01c806340c10f19116100975780639dc29fac116100665780639dc29fac14610319578063a457c2d714610345578063a9059cbb14610371578063dd62ed3e1461039d576100f5565b806340c10f191461028957806356189cb4146102b557806370a08231146102eb57806395d89b4114610311576100f5565b8063222f5be0116100d3578063222f5be0146101d157806323b872dd14610209578063313ce5671461023f578063395093511461025d576100f5565b806306fdde03146100fa578063095ea7b31461017757806318160ddd146101b7575b600080fd5b6101026103cb565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561013c578181015183820152602001610124565b50505050905090810190601f1680156101695780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101a36004803603604081101561018d57600080fd5b506001600160a01b038135169060200135610461565b604080519115158252519081900360200190f35b6101bf61047e565b60408051918252519081900360200190f35b610207600480360360608110156101e757600080fd5b506001600160a01b03813581169160208101359091169060400135610484565b005b6101a36004803603606081101561021f57600080fd5b506001600160a01b03813581169160208101359091169060400135610494565b61024761051b565b6040805160ff9092168252519081900360200190f35b6101a36004803603604081101561027357600080fd5b506001600160a01b038135169060200135610524565b6102076004803603604081101561029f57600080fd5b506001600160a01b038135169060200135610572565b610207600480360360608110156102cb57600080fd5b506001600160a01b03813581169160208101359091169060400135610580565b6101bf6004803603602081101561030157600080fd5b50356001600160a01b031661058b565b6101026105a6565b6102076004803603604081101561032f57600080fd5b506001600160a01b038135169060200135610607565b6101a36004803603604081101561035b57600080fd5b506001600160a01b038135169060200135610611565b6101a36004803603604081101561038757600080fd5b506001600160a01b038135169060200135610679565b6101bf600480360360408110156103b357600080fd5b506001600160a01b038135811691602001351661068d565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156104575780601f1061042c57610100808354040283529160200191610457565b820191906000526020600020905b81548152906001019060200180831161043a57829003601f168201915b5050505050905090565b600061047561046e610719565b848461071d565b50600192915050565b60025490565b61048f838383610809565b505050565b60006104a1848484610809565b610511846104ad610719565b61050c85604051806060016040528060288152602001610cb7602891396001600160a01b038a166000908152600160205260408120906104eb610719565b6001600160a01b031681526020810191909152604001600020549190610964565b61071d565b5060019392505050565b60055460ff1690565b6000610475610531610719565b8461050c8560016000610542610719565b6001600160a01b03908116825260208083019390935260409182016000908120918c1681529252902054906106b8565b61057c82826109fb565b5050565b61048f83838361071d565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156104575780601f1061042c57610100808354040283529160200191610457565b61057c8282610aeb565b600061047561061e610719565b8461050c85604051806060016040528060258152602001610d496025913960016000610648610719565b6001600160a01b03908116825260208083019390935260409182016000908120918d16815292529020549190610964565b6000610475610686610719565b8484610809565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600082820183811015610712576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166107625760405162461bcd60e51b8152600401808060200182810382526024815260200180610d256024913960400191505060405180910390fd5b6001600160a01b0382166107a75760405162461bcd60e51b8152600401808060200182810382526022815260200180610c6f6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b03831661084e5760405162461bcd60e51b8152600401808060200182810382526025815260200180610d006025913960400191505060405180910390fd5b6001600160a01b0382166108935760405162461bcd60e51b8152600401808060200182810382526023815260200180610c2a6023913960400191505060405180910390fd5b61089e83838361048f565b6108db81604051806060016040528060268152602001610c91602691396001600160a01b0386166000908152602081905260409020549190610964565b6001600160a01b03808516600090815260208190526040808220939093559084168152205461090a90826106b8565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109f35760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156109b85781810151838201526020016109a0565b50505050905090810190601f1680156109e55780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6001600160a01b038216610a56576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b610a626000838361048f565b600254610a6f90826106b8565b6002556001600160a01b038216600090815260208190526040902054610a9590826106b8565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6001600160a01b038216610b305760405162461bcd60e51b8152600401808060200182810382526021815260200180610cdf6021913960400191505060405180910390fd5b610b3c8260008361048f565b610b7981604051806060016040528060228152602001610c4d602291396001600160a01b0385166000908152602081905260409020549190610964565b6001600160a01b038316600090815260208190526040902055600254610b9f9082610be7565b6002556040805182815290516000916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b600061071283836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061096456fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122025eb021c0ed77f150fa57486a8a5859aa0bd55f4a7e710b5e08119b2c079293964736f6c634300060c0033", "deployedSourceMap": "872:308:6:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2204:81:3;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4240:166;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;4240:166:3;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;3247:98;;;:::i;:::-;;;;;;;;;;;;;;;;575:117:4;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;575:117:4;;;;;;;;;;;;;;;;;:::i;:::-;;4866:317:3;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;4866:317:3;;;;;;;;;;;;;;;;;:::i;3106:81::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;5578:215;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;5578:215:3;;;;;;;;:::i;377:93:4:-;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;377:93:4;;;;;;;;:::i;698:127::-;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;698:127:4;;;;;;;;;;;;;;;;;:::i;3403:117:3:-;;;;;;;;;;;;;;;;-1:-1:-1;3403:117:3;-1:-1:-1;;;;;3403:117:3;;:::i;2398:85::-;;;:::i;476:93:4:-;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;476:93:4;;;;;;;;:::i;6280:266:3:-;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;6280:266:3;;;;;;;;:::i;3723:172::-;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;3723:172:3;;;;;;;;:::i;3953:149::-;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;3953:149:3;;;;;;;;;;:::i;2204:81::-;2273:5;2266:12;;;;;;;;-1:-1:-1;;2266:12:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2241:13;;2266:12;;2273:5;;2266:12;;2273:5;2266:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2204:81;:::o;4240:166::-;4323:4;4339:39;4348:12;:10;:12::i;:::-;4362:7;4371:6;4339:8;:39::i;:::-;-1:-1:-1;4395:4:3;4240:166;;;;:::o;3247:98::-;3326:12;;3247:98;:::o;575:117:4:-;659:26;669:4;675:2;679:5;659:9;:26::i;:::-;575:117;;;:::o;4866:317:3:-;4972:4;4988:36;4998:6;5006:9;5017:6;4988:9;:36::i;:::-;5034:121;5043:6;5051:12;:10;:12::i;:::-;5065:89;5103:6;5065:89;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;5065:19:3;;;;;;:11;:19;;;;;;5085:12;:10;:12::i;:::-;-1:-1:-1;;;;;5065:33:3;;;;;;;;;;;;-1:-1:-1;5065:33:3;;;:89;:37;:89::i;:::-;5034:8;:121::i;:::-;-1:-1:-1;5172:4:3;4866:317;;;;;:::o;3106:81::-;3171:9;;;;3106:81;:::o;5578:215::-;5666:4;5682:83;5691:12;:10;:12::i;:::-;5705:7;5714:50;5753:10;5714:11;:25;5726:12;:10;:12::i;:::-;-1:-1:-1;;;;;5714:25:3;;;;;;;;;;;;;;;;;-1:-1:-1;5714:25:3;;;:34;;;;;;;;;;;:38;:50::i;377:93:4:-;441:22;447:7;456:6;441:5;:22::i;:::-;377:93;;:::o;698:127::-;787:31;796:5;803:7;812:5;787:8;:31::i;3403:117:3:-;-1:-1:-1;;;;;3495:18:3;3469:7;3495:18;;;;;;;;;;;;3403:117::o;2398:85::-;2469:7;2462:14;;;;;;;;-1:-1:-1;;2462:14:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2437:13;;2462:14;;2469:7;;2462:14;;2469:7;2462:14;;;;;;;;;;;;;;;;;;;;;;;;476:93:4;540:22;546:7;555:6;540:5;:22::i;6280:266:3:-;6373:4;6389:129;6398:12;:10;:12::i;:::-;6412:7;6421:96;6460:15;6421:96;;;;;;;;;;;;;;;;;:11;:25;6433:12;:10;:12::i;:::-;-1:-1:-1;;;;;6421:25:3;;;;;;;;;;;;;;;;;-1:-1:-1;6421:25:3;;;:34;;;;;;;;;;;:96;:38;:96::i;3723:172::-;3809:4;3825:42;3835:12;:10;:12::i;:::-;3849:9;3860:6;3825:9;:42::i;3953:149::-;-1:-1:-1;;;;;4068:18:3;;;4042:7;4068:18;;;:11;:18;;;;;;;;:27;;;;;;;;;;;;;3953:149::o;874:176:12:-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;1042:1;874:176;-1:-1:-1;;;874:176:12:o;590:104:2:-;677:10;590:104;:::o;9344:340:3:-;-1:-1:-1;;;;;9445:19:3;;9437:68;;;;-1:-1:-1;;;9437:68:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;9523:21:3;;9515:68;;;;-1:-1:-1;;;9515:68:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;9594:18:3;;;;;;;:11;:18;;;;;;;;:27;;;;;;;;;;;;;:36;;;9645:32;;;;;;;;;;;;;;;;;9344:340;;;:::o;7020:530::-;-1:-1:-1;;;;;7125:20:3;;7117:70;;;;-1:-1:-1;;;7117:70:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7205:23:3;;7197:71;;;;-1:-1:-1;;;7197:71:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7279:47;7300:6;7308:9;7319:6;7279:20;:47::i;:::-;7357:71;7379:6;7357:71;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7357:17:3;;:9;:17;;;;;;;;;;;;:71;:21;:71::i;:::-;-1:-1:-1;;;;;7337:17:3;;;:9;:17;;;;;;;;;;;:91;;;;7461:20;;;;;;;:32;;7486:6;7461:24;:32::i;:::-;-1:-1:-1;;;;;7438:20:3;;;:9;:20;;;;;;;;;;;;:55;;;;7508:35;;;;;;;7438:20;;7508:35;;;;;;;;;;;;;7020:530;;;:::o;1746:187:12:-;1832:7;1867:12;1859:6;;;;1851:29;;;;-1:-1:-1;;;1851:29:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1902:5:12;;;1746:187::o;7820:370:3:-;-1:-1:-1;;;;;7903:21:3;;7895:65;;;;;-1:-1:-1;;;7895:65:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;7971:49;8000:1;8004:7;8013:6;7971:20;:49::i;:::-;8046:12;;:24;;8063:6;8046:16;:24::i;:::-;8031:12;:39;-1:-1:-1;;;;;8101:18:3;;:9;:18;;;;;;;;;;;:30;;8124:6;8101:22;:30::i;:::-;-1:-1:-1;;;;;8080:18:3;;:9;:18;;;;;;;;;;;:51;;;;8146:37;;;;;;;8080:18;;:9;;8146:37;;;;;;;;;;7820:370;;:::o;8509:410::-;-1:-1:-1;;;;;8592:21:3;;8584:67;;;;-1:-1:-1;;;8584:67:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8662:49;8683:7;8700:1;8704:6;8662:20;:49::i;:::-;8743:68;8766:6;8743:68;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;8743:18:3;;:9;:18;;;;;;;;;;;;:68;:22;:68::i;:::-;-1:-1:-1;;;;;8722:18:3;;:9;:18;;;;;;;;;;:89;8836:12;;:24;;8853:6;8836:16;:24::i;:::-;8821:12;:39;8875:37;;;;;;;;8901:1;;-1:-1:-1;;;;;8875:37:3;;;;;;;;;;;;8509:410;;:::o;1321:134:12:-;1379:7;1405:43;1409:1;1412;1405:43;;;;;;;;;;;;;;;;;:3;:43::i", "language": "Solidity", "natspec": { "kind": "dev", "methods": { "allowance(address,address)": { "details": "See {IERC20-allowance}." }, "approve(address,uint256)": { "details": "See {IERC20-approve}. Requirements: - `spender` cannot be the zero address." }, "balanceOf(address)": { "details": "See {IERC20-balanceOf}." }, "decimals()": { "details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5,05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is called. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}." }, "decreaseAllowance(address,uint256)": { "details": "Atomically decreases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`." }, "increaseAllowance(address,uint256)": { "details": "Atomically increases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address." }, "name()": { "details": "Returns the name of the token." }, "symbol()": { "details": "Returns the symbol of the token, usually a shorter version of the name." }, "totalSupply()": { "details": "See {IERC20-totalSupply}." }, "transfer(address,uint256)": { "details": "See {IERC20-transfer}. Requirements: - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`." }, "transferFrom(address,address,uint256)": { "details": "See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}; Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `amount`. - the caller must have allowance for ``sender``'s tokens of at least `amount`." } }, "version": 1 }, "offset": [ 872, 1180 ], "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xF5 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x40C10F19 GT PUSH2 0x97 JUMPI DUP1 PUSH4 0x9DC29FAC GT PUSH2 0x66 JUMPI DUP1 PUSH4 0x9DC29FAC EQ PUSH2 0x319 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x345 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x371 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x39D JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x40C10F19 EQ PUSH2 0x289 JUMPI DUP1 PUSH4 0x56189CB4 EQ PUSH2 0x2B5 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x2EB JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x311 JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x222F5BE0 GT PUSH2 0xD3 JUMPI DUP1 PUSH4 0x222F5BE0 EQ PUSH2 0x1D1 JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x209 JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x23F JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x25D JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xFA JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0x177 JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0x1B7 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x102 PUSH2 0x3CB JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x13C JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x124 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x169 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1A3 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x18D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD AND SWAP1 PUSH1 0x20 ADD CALLDATALOAD PUSH2 0x461 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST PUSH2 0x1BF PUSH2 0x47E JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST PUSH2 0x207 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x1E7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD DUP2 AND SWAP2 PUSH1 0x20 DUP2 ADD CALLDATALOAD SWAP1 SWAP2 AND SWAP1 PUSH1 0x40 ADD CALLDATALOAD PUSH2 0x484 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x1A3 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x21F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD DUP2 AND SWAP2 PUSH1 0x20 DUP2 ADD CALLDATALOAD SWAP1 SWAP2 AND SWAP1 PUSH1 0x40 ADD CALLDATALOAD PUSH2 0x494 JUMP JUMPDEST PUSH2 0x247 PUSH2 0x51B JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0xFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST PUSH2 0x1A3 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x273 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD AND SWAP1 PUSH1 0x20 ADD CALLDATALOAD PUSH2 0x524 JUMP JUMPDEST PUSH2 0x207 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x29F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD AND SWAP1 PUSH1 0x20 ADD CALLDATALOAD PUSH2 0x572 JUMP JUMPDEST PUSH2 0x207 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x2CB JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD DUP2 AND SWAP2 PUSH1 0x20 DUP2 ADD CALLDATALOAD SWAP1 SWAP2 AND SWAP1 PUSH1 0x40 ADD CALLDATALOAD PUSH2 0x580 JUMP JUMPDEST PUSH2 0x1BF PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x301 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP CALLDATALOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB AND PUSH2 0x58B JUMP JUMPDEST PUSH2 0x102 PUSH2 0x5A6 JUMP JUMPDEST PUSH2 0x207 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x32F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD AND SWAP1 PUSH1 0x20 ADD CALLDATALOAD PUSH2 0x607 JUMP JUMPDEST PUSH2 0x1A3 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x35B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD AND SWAP1 PUSH1 0x20 ADD CALLDATALOAD PUSH2 0x611 JUMP JUMPDEST PUSH2 0x1A3 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x387 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD AND SWAP1 PUSH1 0x20 ADD CALLDATALOAD PUSH2 0x679 JUMP JUMPDEST PUSH2 0x1BF PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x3B3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP2 CALLDATALOAD DUP2 AND SWAP2 PUSH1 0x20 ADD CALLDATALOAD AND PUSH2 0x68D JUMP JUMPDEST PUSH1 0x3 DUP1 SLOAD PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 PUSH1 0x1F PUSH1 0x2 PUSH1 0x0 NOT PUSH2 0x100 PUSH1 0x1 DUP9 AND ISZERO MUL ADD SWAP1 SWAP6 AND SWAP5 SWAP1 SWAP5 DIV SWAP4 DUP5 ADD DUP2 SWAP1 DIV DUP2 MUL DUP3 ADD DUP2 ADD SWAP1 SWAP3 MSTORE DUP3 DUP2 MSTORE PUSH1 0x60 SWAP4 SWAP1 SWAP3 SWAP1 SWAP2 DUP4 ADD DUP3 DUP3 DUP1 ISZERO PUSH2 0x457 JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x42C JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x457 JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x43A JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x475 PUSH2 0x46E PUSH2 0x719 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x71D JUMP JUMPDEST POP PUSH1 0x1 SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x2 SLOAD SWAP1 JUMP JUMPDEST PUSH2 0x48F DUP4 DUP4 DUP4 PUSH2 0x809 JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4A1 DUP5 DUP5 DUP5 PUSH2 0x809 JUMP JUMPDEST PUSH2 0x511 DUP5 PUSH2 0x4AD PUSH2 0x719 JUMP JUMPDEST PUSH2 0x50C DUP6 PUSH1 0x40 MLOAD DUP1 PUSH1 0x60 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0xCB7 PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP11 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 SWAP1 PUSH2 0x4EB PUSH2 0x719 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB AND DUP2 MSTORE PUSH1 0x20 DUP2 ADD SWAP2 SWAP1 SWAP2 MSTORE PUSH1 0x40 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP2 SWAP1 PUSH2 0x964 JUMP JUMPDEST PUSH2 0x71D JUMP JUMPDEST POP PUSH1 0x1 SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x5 SLOAD PUSH1 0xFF AND SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x475 PUSH2 0x531 PUSH2 0x719 JUMP JUMPDEST DUP5 PUSH2 0x50C DUP6 PUSH1 0x1 PUSH1 0x0 PUSH2 0x542 PUSH2 0x719 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP1 DUP2 AND DUP3 MSTORE PUSH1 0x20 DUP1 DUP4 ADD SWAP4 SWAP1 SWAP4 MSTORE PUSH1 0x40 SWAP2 DUP3 ADD PUSH1 0x0 SWAP1 DUP2 KECCAK256 SWAP2 DUP13 AND DUP2 MSTORE SWAP3 MSTORE SWAP1 KECCAK256 SLOAD SWAP1 PUSH2 0x6B8 JUMP JUMPDEST PUSH2 0x57C DUP3 DUP3 PUSH2 0x9FB JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH2 0x48F DUP4 DUP4 DUP4 PUSH2 0x71D JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD SWAP1 JUMP JUMPDEST PUSH1 0x4 DUP1 SLOAD PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 PUSH1 0x1F PUSH1 0x2 PUSH1 0x0 NOT PUSH2 0x100 PUSH1 0x1 DUP9 AND ISZERO MUL ADD SWAP1 SWAP6 AND SWAP5 SWAP1 SWAP5 DIV SWAP4 DUP5 ADD DUP2 SWAP1 DIV DUP2 MUL DUP3 ADD DUP2 ADD SWAP1 SWAP3 MSTORE DUP3 DUP2 MSTORE PUSH1 0x60 SWAP4 SWAP1 SWAP3 SWAP1 SWAP2 DUP4 ADD DUP3 DUP3 DUP1 ISZERO PUSH2 0x457 JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x42C JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x457 JUMP JUMPDEST PUSH2 0x57C DUP3 DUP3 PUSH2 0xAEB JUMP JUMPDEST PUSH1 0x0 PUSH2 0x475 PUSH2 0x61E PUSH2 0x719 JUMP JUMPDEST DUP5 PUSH2 0x50C DUP6 PUSH1 0x40 MLOAD DUP1 PUSH1 0x60 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x25 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0xD49 PUSH1 0x25 SWAP2 CODECOPY PUSH1 0x1 PUSH1 0x0 PUSH2 0x648 PUSH2 0x719 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP1 DUP2 AND DUP3 MSTORE PUSH1 0x20 DUP1 DUP4 ADD SWAP4 SWAP1 SWAP4 MSTORE PUSH1 0x40 SWAP2 DUP3 ADD PUSH1 0x0 SWAP1 DUP2 KECCAK256 SWAP2 DUP14 AND DUP2 MSTORE SWAP3 MSTORE SWAP1 KECCAK256 SLOAD SWAP2 SWAP1 PUSH2 0x964 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x475 PUSH2 0x686 PUSH2 0x719 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x809 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP2 DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 SWAP4 SWAP1 SWAP5 AND DUP3 MSTORE SWAP2 SWAP1 SWAP2 MSTORE KECCAK256 SLOAD SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 ADD DUP4 DUP2 LT ISZERO PUSH2 0x712 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x536166654D6174683A206164646974696F6E206F766572666C6F770000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST SWAP4 SWAP3 POP POP POP JUMP JUMPDEST CALLER SWAP1 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP4 AND PUSH2 0x762 JUMPI PUSH1 0x40 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x24 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0xD25 PUSH1 0x24 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP3 AND PUSH2 0x7A7 JUMPI PUSH1 0x40 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x22 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0xC6F PUSH1 0x22 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP1 DUP5 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 SWAP5 DUP8 AND DUP1 DUP5 MSTORE SWAP5 DUP3 MSTORE SWAP2 DUP3 SWAP1 KECCAK256 DUP6 SWAP1 SSTORE DUP2 MLOAD DUP6 DUP2 MSTORE SWAP2 MLOAD PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 SWAP3 DUP2 SWAP1 SUB SWAP1 SWAP2 ADD SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP4 AND PUSH2 0x84E JUMPI PUSH1 0x40 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x25 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0xD00 PUSH1 0x25 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP3 AND PUSH2 0x893 JUMPI PUSH1 0x40 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0xC2A PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x89E DUP4 DUP4 DUP4 PUSH2 0x48F JUMP JUMPDEST PUSH2 0x8DB DUP2 PUSH1 0x40 MLOAD DUP1 PUSH1 0x60 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x26 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0xC91 PUSH1 0x26 SWAP2 CODECOPY PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP7 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD SWAP2 SWAP1 PUSH2 0x964 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP1 DUP6 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP3 KECCAK256 SWAP4 SWAP1 SWAP4 SSTORE SWAP1 DUP5 AND DUP2 MSTORE KECCAK256 SLOAD PUSH2 0x90A SWAP1 DUP3 PUSH2 0x6B8 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP1 DUP5 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x20 DUP2 DUP2 MSTORE PUSH1 0x40 SWAP2 DUP3 SWAP1 KECCAK256 SWAP5 SWAP1 SWAP5 SSTORE DUP1 MLOAD DUP6 DUP2 MSTORE SWAP1 MLOAD SWAP2 SWAP4 SWAP3 DUP8 AND SWAP3 PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF SWAP3 SWAP2 DUP3 SWAP1 SUB ADD SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 DUP5 DUP5 GT ISZERO PUSH2 0x9F3 JUMPI PUSH1 0x40 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x9B8 JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x9A0 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x9E5 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP SWAP1 SUB SWAP1 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP3 AND PUSH2 0xA56 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1F PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x45524332303A206D696E7420746F20746865207A65726F206164647265737300 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH2 0xA62 PUSH1 0x0 DUP4 DUP4 PUSH2 0x48F JUMP JUMPDEST PUSH1 0x2 SLOAD PUSH2 0xA6F SWAP1 DUP3 PUSH2 0x6B8 JUMP JUMPDEST PUSH1 0x2 SSTORE PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH2 0xA95 SWAP1 DUP3 PUSH2 0x6B8 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP4 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x20 DUP2 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 SWAP5 SWAP1 SWAP5 SSTORE DUP4 MLOAD DUP6 DUP2 MSTORE SWAP4 MLOAD SWAP3 SWAP4 SWAP2 SWAP3 PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF SWAP3 DUP2 SWAP1 SUB SWAP1 SWAP2 ADD SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP3 AND PUSH2 0xB30 JUMPI PUSH1 0x40 MLOAD PUSH3 0x461BCD PUSH1 0xE5 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x21 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0xCDF PUSH1 0x21 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xB3C DUP3 PUSH1 0x0 DUP4 PUSH2 0x48F JUMP JUMPDEST PUSH2 0xB79 DUP2 PUSH1 0x40 MLOAD DUP1 PUSH1 0x60 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x22 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0xC4D PUSH1 0x22 SWAP2 CODECOPY PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP6 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD SWAP2 SWAP1 PUSH2 0x964 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP4 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SSTORE PUSH1 0x2 SLOAD PUSH2 0xB9F SWAP1 DUP3 PUSH2 0xBE7 JUMP JUMPDEST PUSH1 0x2 SSTORE PUSH1 0x40 DUP1 MLOAD DUP3 DUP2 MSTORE SWAP1 MLOAD PUSH1 0x0 SWAP2 PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB DUP6 AND SWAP2 PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF SWAP2 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x712 DUP4 DUP4 PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x536166654D6174683A207375627472616374696F6E206F766572666C6F770000 DUP2 MSTORE POP PUSH2 0x964 JUMP INVALID GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E7366657220746F20746865207A65726F2061 PUSH5 0x6472657373 GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH3 0x75726E KECCAK256 PUSH2 0x6D6F PUSH22 0x6E7420657863656564732062616C616E636545524332 ADDRESS GASPRICE KECCAK256 PUSH2 0x7070 PUSH19 0x6F766520746F20746865207A65726F20616464 PUSH19 0x65737345524332303A207472616E7366657220 PUSH2 0x6D6F PUSH22 0x6E7420657863656564732062616C616E636545524332 ADDRESS GASPRICE KECCAK256 PUSH21 0x72616E7366657220616D6F756E7420657863656564 PUSH20 0x20616C6C6F77616E636545524332303A20627572 PUSH15 0x2066726F6D20746865207A65726F20 PUSH2 0x6464 PUSH19 0x65737345524332303A207472616E7366657220 PUSH7 0x726F6D20746865 KECCAK256 PUSH27 0x65726F206164647265737345524332303A20617070726F76652066 PUSH19 0x6F6D20746865207A65726F2061646472657373 GASLIMIT MSTORE NUMBER ORIGIN ADDRESS GASPRICE KECCAK256 PUSH5 0x6563726561 PUSH20 0x656420616C6C6F77616E63652062656C6F77207A PUSH6 0x726FA2646970 PUSH7 0x735822122025EB MUL SHR 0xE 0xD7 PUSH32 0x150FA57486A8A5859AA0BD55F4A7E710B5E08119B2C079293964736F6C634300 MOD 0xC STOP CALLER ", "pcMap": { "0": { "offset": [ 872, 1180 ], "op": "PUSH1", "path": "6", "value": "0x80" }, "2": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH1", "path": "6", "value": "0x40" }, "4": { "fn": null, "offset": [ 872, 1180 ], "op": "MSTORE", "path": "6" }, "5": { "fn": null, "offset": [ 872, 1180 ], "op": "CALLVALUE", "path": "6" }, "6": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "7": { "fn": null, "offset": [ 872, 1180 ], "op": "ISZERO", "path": "6" }, "8": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x10" }, "11": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "12": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH1", "path": "6", "value": "0x0" }, "14": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "15": { "dev": "Cannot send ether to nonpayable function", "fn": null, "offset": [ 872, 1180 ], "op": "REVERT", "path": "6" }, "16": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPDEST", "path": "6" }, "17": { "fn": null, "offset": [ 872, 1180 ], "op": "POP", "path": "6" }, "18": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH1", "path": "6", "value": "0x4" }, "20": { "fn": null, "offset": [ 872, 1180 ], "op": "CALLDATASIZE", "path": "6" }, "21": { "fn": null, "offset": [ 872, 1180 ], "op": "LT", "path": "6" }, "22": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0xF5" }, "25": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "26": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH1", "path": "6", "value": "0x0" }, "28": { "fn": null, "offset": [ 872, 1180 ], "op": "CALLDATALOAD", "path": "6" }, "29": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH1", "path": "6", "value": "0xE0" }, "31": { "fn": null, "offset": [ 872, 1180 ], "op": "SHR", "path": "6" }, "32": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "33": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x40C10F19" }, "38": { "fn": null, "offset": [ 872, 1180 ], "op": "GT", "path": "6" }, "39": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x97" }, "42": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "43": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "44": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x9DC29FAC" }, "49": { "fn": null, "offset": [ 872, 1180 ], "op": "GT", "path": "6" }, "50": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x66" }, "53": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "54": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "55": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x9DC29FAC" }, "60": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "61": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x319" }, "64": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "65": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "66": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0xA457C2D7" }, "71": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "72": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x345" }, "75": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "76": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "77": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0xA9059CBB" }, "82": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "83": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x371" }, "86": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "87": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "88": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0xDD62ED3E" }, "93": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "94": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x39D" }, "97": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "98": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0xF5" }, "101": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMP", "path": "6" }, "102": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPDEST", "path": "6" }, "103": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "104": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x40C10F19" }, "109": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "110": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x289" }, "113": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "114": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "115": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x56189CB4" }, "120": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "121": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x2B5" }, "124": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "125": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "126": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x70A08231" }, "131": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "132": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x2EB" }, "135": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "136": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "137": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x95D89B41" }, "142": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "143": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x311" }, "146": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "147": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0xF5" }, "150": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMP", "path": "6" }, "151": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPDEST", "path": "6" }, "152": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "153": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x222F5BE0" }, "158": { "fn": null, "offset": [ 872, 1180 ], "op": "GT", "path": "6" }, "159": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0xD3" }, "162": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "163": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "164": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x222F5BE0" }, "169": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "170": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x1D1" }, "173": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "174": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "175": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x23B872DD" }, "180": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "181": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x209" }, "184": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "185": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "186": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x313CE567" }, "191": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "192": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x23F" }, "195": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "196": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "197": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x39509351" }, "202": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "203": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x25D" }, "206": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "207": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0xF5" }, "210": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMP", "path": "6" }, "211": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPDEST", "path": "6" }, "212": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "213": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x6FDDE03" }, "218": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "219": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0xFA" }, "222": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "223": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "224": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x95EA7B3" }, "229": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "230": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x177" }, "233": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "234": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "235": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH4", "path": "6", "value": "0x18160DDD" }, "240": { "fn": null, "offset": [ 872, 1180 ], "op": "EQ", "path": "6" }, "241": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH2", "path": "6", "value": "0x1B7" }, "244": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPI", "path": "6" }, "245": { "fn": null, "offset": [ 872, 1180 ], "op": "JUMPDEST", "path": "6" }, "246": { "fn": null, "offset": [ 872, 1180 ], "op": "PUSH1", "path": "6", "value": "0x0" }, "248": { "fn": null, "offset": [ 872, 1180 ], "op": "DUP1", "path": "6" }, "249": { "first_revert": true, "fn": null, "offset": [ 872, 1180 ], "op": "REVERT", "path": "6" }, "250": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPDEST", "path": "3" }, "251": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH2", "path": "3", "value": "0x102" }, "254": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH2", "path": "3", "value": "0x3CB" }, "257": { "fn": "ERC20.name", "jump": "i", "offset": [ 2204, 2285 ], "op": "JUMP", "path": "3" }, "258": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPDEST", "path": "3" }, "259": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "261": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP1", "path": "3" }, "262": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MLOAD", "path": "3" }, "263": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "265": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP1", "path": "3" }, "266": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP3", "path": "3" }, "267": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MSTORE", "path": "3" }, "268": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "269": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MLOAD", "path": "3" }, "270": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP2", "path": "3" }, "271": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "272": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "273": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MSTORE", "path": "3" }, "274": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "275": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MLOAD", "path": "3" }, "276": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP2", "path": "3" }, "277": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP3", "path": "3" }, "278": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "279": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP3", "path": "3" }, "280": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP1", "path": "3" }, "281": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "282": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "283": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP2", "path": "3" }, "284": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP6", "path": "3" }, "285": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "286": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP1", "path": "3" }, "287": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP1", "path": "3" }, "288": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "289": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "290": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "292": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPDEST", "path": "3" }, "293": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "294": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP2", "path": "3" }, "295": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "LT", "path": "3" }, "296": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ISZERO", "path": "3" }, "297": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH2", "path": "3", "value": "0x13C" }, "300": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPI", "path": "3" }, "301": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP2", "path": "3" }, "302": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP2", "path": "3" }, "303": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "304": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MLOAD", "path": "3" }, "305": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "306": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP3", "path": "3" }, "307": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "308": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MSTORE", "path": "3" }, "309": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "311": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "312": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH2", "path": "3", "value": "0x124" }, "315": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMP", "path": "3" }, "316": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPDEST", "path": "3" }, "317": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "318": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "319": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "320": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "321": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP1", "path": "3" }, "322": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "323": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP1", "path": "3" }, "324": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP2", "path": "3" }, "325": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "326": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP1", "path": "3" }, "327": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x1F" }, "329": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "AND", "path": "3" }, "330": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP1", "path": "3" }, "331": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ISZERO", "path": "3" }, "332": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH2", "path": "3", "value": "0x169" }, "335": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPI", "path": "3" }, "336": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP1", "path": "3" }, "337": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP3", "path": "3" }, "338": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SUB", "path": "3" }, "339": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP1", "path": "3" }, "340": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MLOAD", "path": "3" }, "341": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "343": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP4", "path": "3" }, "344": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "346": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SUB", "path": "3" }, "347": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH2", "path": "3", "value": "0x100" }, "350": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "EXP", "path": "3" }, "351": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SUB", "path": "3" }, "352": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "NOT", "path": "3" }, "353": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "AND", "path": "3" }, "354": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP2", "path": "3" }, "355": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MSTORE", "path": "3" }, "356": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "358": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "ADD", "path": "3" }, "359": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP2", "path": "3" }, "360": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "361": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPDEST", "path": "3" }, "362": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "363": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP3", "path": "3" }, "364": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "365": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "366": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "POP", "path": "3" }, "367": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "369": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "MLOAD", "path": "3" }, "370": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "DUP1", "path": "3" }, "371": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP2", "path": "3" }, "372": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SUB", "path": "3" }, "373": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP1", "path": "3" }, "374": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "RETURN", "path": "3" }, "375": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "JUMPDEST", "path": "3" }, "376": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH2", "path": "3", "value": "0x1A3" }, "379": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "381": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "DUP1", "path": "3" }, "382": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "CALLDATASIZE", "path": "3" }, "383": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SUB", "path": "3" }, "384": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "386": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "DUP2", "path": "3" }, "387": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "LT", "path": "3" }, "388": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "ISZERO", "path": "3" }, "389": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH2", "path": "3", "value": "0x18D" }, "392": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "JUMPI", "path": "3" }, "393": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "395": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "DUP1", "path": "3" }, "396": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "REVERT", "path": "3" }, "397": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "JUMPDEST", "path": "3" }, "398": { "op": "POP" }, "399": { "op": "PUSH1", "value": "0x1" }, "401": { "op": "PUSH1", "value": "0x1" }, "403": { "op": "PUSH1", "value": "0xA0" }, "405": { "op": "SHL" }, "406": { "op": "SUB" }, "407": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "DUP2", "path": "3" }, "408": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "CALLDATALOAD", "path": "3" }, "409": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "AND", "path": "3" }, "410": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SWAP1", "path": "3" }, "411": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "413": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "ADD", "path": "3" }, "414": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "CALLDATALOAD", "path": "3" }, "415": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH2", "path": "3", "value": "0x461" }, "418": { "fn": "ERC20.approve", "jump": "i", "offset": [ 4240, 4406 ], "op": "JUMP", "path": "3" }, "419": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "JUMPDEST", "path": "3" }, "420": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "422": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "DUP1", "path": "3" }, "423": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "MLOAD", "path": "3" }, "424": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SWAP2", "path": "3" }, "425": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "ISZERO", "path": "3" }, "426": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "ISZERO", "path": "3" }, "427": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "DUP3", "path": "3" }, "428": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "MSTORE", "path": "3" }, "429": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "MLOAD", "path": "3" }, "430": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SWAP1", "path": "3" }, "431": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "DUP2", "path": "3" }, "432": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SWAP1", "path": "3" }, "433": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SUB", "path": "3" }, "434": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "436": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "ADD", "path": "3" }, "437": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SWAP1", "path": "3" }, "438": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "RETURN", "path": "3" }, "439": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "JUMPDEST", "path": "3" }, "440": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "PUSH2", "path": "3", "value": "0x1BF" }, "443": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "PUSH2", "path": "3", "value": "0x47E" }, "446": { "fn": "ERC20.totalSupply", "jump": "i", "offset": [ 3247, 3345 ], "op": "JUMP", "path": "3" }, "447": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "JUMPDEST", "path": "3" }, "448": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "450": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "DUP1", "path": "3" }, "451": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "MLOAD", "path": "3" }, "452": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "SWAP2", "path": "3" }, "453": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "DUP3", "path": "3" }, "454": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "MSTORE", "path": "3" }, "455": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "MLOAD", "path": "3" }, "456": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "SWAP1", "path": "3" }, "457": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "DUP2", "path": "3" }, "458": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "SWAP1", "path": "3" }, "459": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "SUB", "path": "3" }, "460": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "462": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "ADD", "path": "3" }, "463": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "SWAP1", "path": "3" }, "464": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "RETURN", "path": "3" }, "465": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "JUMPDEST", "path": "4" }, "466": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH2", "path": "4", "value": "0x207" }, "469": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH1", "path": "4", "value": "0x4" }, "471": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "DUP1", "path": "4" }, "472": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "CALLDATASIZE", "path": "4" }, "473": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "SUB", "path": "4" }, "474": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH1", "path": "4", "value": "0x60" }, "476": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "DUP2", "path": "4" }, "477": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "LT", "path": "4" }, "478": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "ISZERO", "path": "4" }, "479": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH2", "path": "4", "value": "0x1E7" }, "482": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "JUMPI", "path": "4" }, "483": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH1", "path": "4", "value": "0x0" }, "485": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "DUP1", "path": "4" }, "486": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "REVERT", "path": "4" }, "487": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "JUMPDEST", "path": "4" }, "488": { "op": "POP" }, "489": { "op": "PUSH1", "value": "0x1" }, "491": { "op": "PUSH1", "value": "0x1" }, "493": { "op": "PUSH1", "value": "0xA0" }, "495": { "op": "SHL" }, "496": { "op": "SUB" }, "497": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "DUP2", "path": "4" }, "498": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "CALLDATALOAD", "path": "4" }, "499": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "DUP2", "path": "4" }, "500": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "AND", "path": "4" }, "501": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "SWAP2", "path": "4" }, "502": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH1", "path": "4", "value": "0x20" }, "504": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "DUP2", "path": "4" }, "505": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "ADD", "path": "4" }, "506": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "CALLDATALOAD", "path": "4" }, "507": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "SWAP1", "path": "4" }, "508": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "SWAP2", "path": "4" }, "509": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "AND", "path": "4" }, "510": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "SWAP1", "path": "4" }, "511": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH1", "path": "4", "value": "0x40" }, "513": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "ADD", "path": "4" }, "514": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "CALLDATALOAD", "path": "4" }, "515": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "PUSH2", "path": "4", "value": "0x484" }, "518": { "fn": "ERC20Mock.transferInternal", "jump": "i", "offset": [ 575, 692 ], "op": "JUMP", "path": "4" }, "519": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "JUMPDEST", "path": "4" }, "520": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "STOP", "path": "4" }, "521": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "JUMPDEST", "path": "3" }, "522": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH2", "path": "3", "value": "0x1A3" }, "525": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "527": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "DUP1", "path": "3" }, "528": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "CALLDATASIZE", "path": "3" }, "529": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "SUB", "path": "3" }, "530": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH1", "path": "3", "value": "0x60" }, "532": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "DUP2", "path": "3" }, "533": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "LT", "path": "3" }, "534": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "ISZERO", "path": "3" }, "535": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH2", "path": "3", "value": "0x21F" }, "538": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "JUMPI", "path": "3" }, "539": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "541": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "DUP1", "path": "3" }, "542": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "REVERT", "path": "3" }, "543": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "JUMPDEST", "path": "3" }, "544": { "op": "POP" }, "545": { "op": "PUSH1", "value": "0x1" }, "547": { "op": "PUSH1", "value": "0x1" }, "549": { "op": "PUSH1", "value": "0xA0" }, "551": { "op": "SHL" }, "552": { "op": "SUB" }, "553": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "DUP2", "path": "3" }, "554": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "CALLDATALOAD", "path": "3" }, "555": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "DUP2", "path": "3" }, "556": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "AND", "path": "3" }, "557": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "SWAP2", "path": "3" }, "558": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "560": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "DUP2", "path": "3" }, "561": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "ADD", "path": "3" }, "562": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "CALLDATALOAD", "path": "3" }, "563": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "SWAP1", "path": "3" }, "564": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "SWAP2", "path": "3" }, "565": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "AND", "path": "3" }, "566": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "SWAP1", "path": "3" }, "567": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "569": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "ADD", "path": "3" }, "570": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "CALLDATALOAD", "path": "3" }, "571": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "PUSH2", "path": "3", "value": "0x494" }, "574": { "fn": "ERC20.transferFrom", "jump": "i", "offset": [ 4866, 5183 ], "op": "JUMP", "path": "3" }, "575": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "JUMPDEST", "path": "3" }, "576": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "PUSH2", "path": "3", "value": "0x247" }, "579": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "PUSH2", "path": "3", "value": "0x51B" }, "582": { "fn": "ERC20.decimals", "jump": "i", "offset": [ 3106, 3187 ], "op": "JUMP", "path": "3" }, "583": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "JUMPDEST", "path": "3" }, "584": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "586": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "DUP1", "path": "3" }, "587": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "MLOAD", "path": "3" }, "588": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "PUSH1", "path": "3", "value": "0xFF" }, "590": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "SWAP1", "path": "3" }, "591": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "SWAP3", "path": "3" }, "592": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "AND", "path": "3" }, "593": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "DUP3", "path": "3" }, "594": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "MSTORE", "path": "3" }, "595": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "MLOAD", "path": "3" }, "596": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "SWAP1", "path": "3" }, "597": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "DUP2", "path": "3" }, "598": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "SWAP1", "path": "3" }, "599": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "SUB", "path": "3" }, "600": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "602": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "ADD", "path": "3" }, "603": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "SWAP1", "path": "3" }, "604": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "RETURN", "path": "3" }, "605": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "JUMPDEST", "path": "3" }, "606": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "PUSH2", "path": "3", "value": "0x1A3" }, "609": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "611": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "DUP1", "path": "3" }, "612": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "CALLDATASIZE", "path": "3" }, "613": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "SUB", "path": "3" }, "614": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "616": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "DUP2", "path": "3" }, "617": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "LT", "path": "3" }, "618": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "ISZERO", "path": "3" }, "619": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "PUSH2", "path": "3", "value": "0x273" }, "622": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "JUMPI", "path": "3" }, "623": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "625": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "DUP1", "path": "3" }, "626": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "REVERT", "path": "3" }, "627": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "JUMPDEST", "path": "3" }, "628": { "op": "POP" }, "629": { "op": "PUSH1", "value": "0x1" }, "631": { "op": "PUSH1", "value": "0x1" }, "633": { "op": "PUSH1", "value": "0xA0" }, "635": { "op": "SHL" }, "636": { "op": "SUB" }, "637": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "DUP2", "path": "3" }, "638": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "CALLDATALOAD", "path": "3" }, "639": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "AND", "path": "3" }, "640": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "SWAP1", "path": "3" }, "641": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "643": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "ADD", "path": "3" }, "644": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "CALLDATALOAD", "path": "3" }, "645": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "PUSH2", "path": "3", "value": "0x524" }, "648": { "fn": "ERC20.increaseAllowance", "jump": "i", "offset": [ 5578, 5793 ], "op": "JUMP", "path": "3" }, "649": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "JUMPDEST", "path": "4" }, "650": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "PUSH2", "path": "4", "value": "0x207" }, "653": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "PUSH1", "path": "4", "value": "0x4" }, "655": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "DUP1", "path": "4" }, "656": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "CALLDATASIZE", "path": "4" }, "657": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "SUB", "path": "4" }, "658": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "PUSH1", "path": "4", "value": "0x40" }, "660": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "DUP2", "path": "4" }, "661": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "LT", "path": "4" }, "662": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "ISZERO", "path": "4" }, "663": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "PUSH2", "path": "4", "value": "0x29F" }, "666": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "JUMPI", "path": "4" }, "667": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "PUSH1", "path": "4", "value": "0x0" }, "669": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "DUP1", "path": "4" }, "670": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "REVERT", "path": "4" }, "671": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "JUMPDEST", "path": "4" }, "672": { "op": "POP" }, "673": { "op": "PUSH1", "value": "0x1" }, "675": { "op": "PUSH1", "value": "0x1" }, "677": { "op": "PUSH1", "value": "0xA0" }, "679": { "op": "SHL" }, "680": { "op": "SUB" }, "681": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "DUP2", "path": "4" }, "682": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "CALLDATALOAD", "path": "4" }, "683": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "AND", "path": "4" }, "684": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "SWAP1", "path": "4" }, "685": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "PUSH1", "path": "4", "value": "0x20" }, "687": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "ADD", "path": "4" }, "688": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "CALLDATALOAD", "path": "4" }, "689": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "PUSH2", "path": "4", "value": "0x572" }, "692": { "fn": "ERC20Mock.mint", "jump": "i", "offset": [ 377, 470 ], "op": "JUMP", "path": "4" }, "693": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "JUMPDEST", "path": "4" }, "694": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH2", "path": "4", "value": "0x207" }, "697": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH1", "path": "4", "value": "0x4" }, "699": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "DUP1", "path": "4" }, "700": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "CALLDATASIZE", "path": "4" }, "701": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "SUB", "path": "4" }, "702": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH1", "path": "4", "value": "0x60" }, "704": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "DUP2", "path": "4" }, "705": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "LT", "path": "4" }, "706": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "ISZERO", "path": "4" }, "707": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH2", "path": "4", "value": "0x2CB" }, "710": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "JUMPI", "path": "4" }, "711": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH1", "path": "4", "value": "0x0" }, "713": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "DUP1", "path": "4" }, "714": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "REVERT", "path": "4" }, "715": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "JUMPDEST", "path": "4" }, "716": { "op": "POP" }, "717": { "op": "PUSH1", "value": "0x1" }, "719": { "op": "PUSH1", "value": "0x1" }, "721": { "op": "PUSH1", "value": "0xA0" }, "723": { "op": "SHL" }, "724": { "op": "SUB" }, "725": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "DUP2", "path": "4" }, "726": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "CALLDATALOAD", "path": "4" }, "727": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "DUP2", "path": "4" }, "728": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "AND", "path": "4" }, "729": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "SWAP2", "path": "4" }, "730": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH1", "path": "4", "value": "0x20" }, "732": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "DUP2", "path": "4" }, "733": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "ADD", "path": "4" }, "734": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "CALLDATALOAD", "path": "4" }, "735": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "SWAP1", "path": "4" }, "736": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "SWAP2", "path": "4" }, "737": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "AND", "path": "4" }, "738": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "SWAP1", "path": "4" }, "739": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH1", "path": "4", "value": "0x40" }, "741": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "ADD", "path": "4" }, "742": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "CALLDATALOAD", "path": "4" }, "743": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "PUSH2", "path": "4", "value": "0x580" }, "746": { "fn": "ERC20Mock.approveInternal", "jump": "i", "offset": [ 698, 825 ], "op": "JUMP", "path": "4" }, "747": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "JUMPDEST", "path": "3" }, "748": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "PUSH2", "path": "3", "value": "0x1BF" }, "751": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "753": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "DUP1", "path": "3" }, "754": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "CALLDATASIZE", "path": "3" }, "755": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "SUB", "path": "3" }, "756": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "758": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "DUP2", "path": "3" }, "759": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "LT", "path": "3" }, "760": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "ISZERO", "path": "3" }, "761": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "PUSH2", "path": "3", "value": "0x301" }, "764": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "JUMPI", "path": "3" }, "765": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "767": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "DUP1", "path": "3" }, "768": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "REVERT", "path": "3" }, "769": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "JUMPDEST", "path": "3" }, "770": { "op": "POP" }, "771": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "CALLDATALOAD", "path": "3" }, "772": { "op": "PUSH1", "value": "0x1" }, "774": { "op": "PUSH1", "value": "0x1" }, "776": { "op": "PUSH1", "value": "0xA0" }, "778": { "op": "SHL" }, "779": { "op": "SUB" }, "780": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "AND", "path": "3" }, "781": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "PUSH2", "path": "3", "value": "0x58B" }, "784": { "fn": "ERC20.balanceOf", "jump": "i", "offset": [ 3403, 3520 ], "op": "JUMP", "path": "3" }, "785": { "fn": "ERC20.symbol", "offset": [ 2398, 2483 ], "op": "JUMPDEST", "path": "3" }, "786": { "fn": "ERC20.symbol", "offset": [ 2398, 2483 ], "op": "PUSH2", "path": "3", "value": "0x102" }, "789": { "fn": "ERC20.symbol", "offset": [ 2398, 2483 ], "op": "PUSH2", "path": "3", "value": "0x5A6" }, "792": { "fn": "ERC20.symbol", "jump": "i", "offset": [ 2398, 2483 ], "op": "JUMP", "path": "3" }, "793": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "JUMPDEST", "path": "4" }, "794": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "PUSH2", "path": "4", "value": "0x207" }, "797": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "PUSH1", "path": "4", "value": "0x4" }, "799": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "DUP1", "path": "4" }, "800": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "CALLDATASIZE", "path": "4" }, "801": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "SUB", "path": "4" }, "802": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "PUSH1", "path": "4", "value": "0x40" }, "804": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "DUP2", "path": "4" }, "805": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "LT", "path": "4" }, "806": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "ISZERO", "path": "4" }, "807": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "PUSH2", "path": "4", "value": "0x32F" }, "810": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "JUMPI", "path": "4" }, "811": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "PUSH1", "path": "4", "value": "0x0" }, "813": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "DUP1", "path": "4" }, "814": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "REVERT", "path": "4" }, "815": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "JUMPDEST", "path": "4" }, "816": { "op": "POP" }, "817": { "op": "PUSH1", "value": "0x1" }, "819": { "op": "PUSH1", "value": "0x1" }, "821": { "op": "PUSH1", "value": "0xA0" }, "823": { "op": "SHL" }, "824": { "op": "SUB" }, "825": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "DUP2", "path": "4" }, "826": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "CALLDATALOAD", "path": "4" }, "827": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "AND", "path": "4" }, "828": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "SWAP1", "path": "4" }, "829": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "PUSH1", "path": "4", "value": "0x20" }, "831": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "ADD", "path": "4" }, "832": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "CALLDATALOAD", "path": "4" }, "833": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "PUSH2", "path": "4", "value": "0x607" }, "836": { "fn": "ERC20Mock.burn", "jump": "i", "offset": [ 476, 569 ], "op": "JUMP", "path": "4" }, "837": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "JUMPDEST", "path": "3" }, "838": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "PUSH2", "path": "3", "value": "0x1A3" }, "841": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "843": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "DUP1", "path": "3" }, "844": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "CALLDATASIZE", "path": "3" }, "845": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "SUB", "path": "3" }, "846": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "848": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "DUP2", "path": "3" }, "849": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "LT", "path": "3" }, "850": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "ISZERO", "path": "3" }, "851": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "PUSH2", "path": "3", "value": "0x35B" }, "854": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "JUMPI", "path": "3" }, "855": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "857": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "DUP1", "path": "3" }, "858": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "REVERT", "path": "3" }, "859": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "JUMPDEST", "path": "3" }, "860": { "op": "POP" }, "861": { "op": "PUSH1", "value": "0x1" }, "863": { "op": "PUSH1", "value": "0x1" }, "865": { "op": "PUSH1", "value": "0xA0" }, "867": { "op": "SHL" }, "868": { "op": "SUB" }, "869": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "DUP2", "path": "3" }, "870": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "CALLDATALOAD", "path": "3" }, "871": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "AND", "path": "3" }, "872": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "SWAP1", "path": "3" }, "873": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "875": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "ADD", "path": "3" }, "876": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "CALLDATALOAD", "path": "3" }, "877": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "PUSH2", "path": "3", "value": "0x611" }, "880": { "fn": "ERC20.decreaseAllowance", "jump": "i", "offset": [ 6280, 6546 ], "op": "JUMP", "path": "3" }, "881": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "JUMPDEST", "path": "3" }, "882": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "PUSH2", "path": "3", "value": "0x1A3" }, "885": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "887": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "DUP1", "path": "3" }, "888": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "CALLDATASIZE", "path": "3" }, "889": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "SUB", "path": "3" }, "890": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "892": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "DUP2", "path": "3" }, "893": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "LT", "path": "3" }, "894": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "ISZERO", "path": "3" }, "895": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "PUSH2", "path": "3", "value": "0x387" }, "898": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "JUMPI", "path": "3" }, "899": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "901": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "DUP1", "path": "3" }, "902": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "REVERT", "path": "3" }, "903": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "JUMPDEST", "path": "3" }, "904": { "op": "POP" }, "905": { "op": "PUSH1", "value": "0x1" }, "907": { "op": "PUSH1", "value": "0x1" }, "909": { "op": "PUSH1", "value": "0xA0" }, "911": { "op": "SHL" }, "912": { "op": "SUB" }, "913": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "DUP2", "path": "3" }, "914": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "CALLDATALOAD", "path": "3" }, "915": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "AND", "path": "3" }, "916": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "SWAP1", "path": "3" }, "917": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "919": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "ADD", "path": "3" }, "920": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "CALLDATALOAD", "path": "3" }, "921": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "PUSH2", "path": "3", "value": "0x679" }, "924": { "fn": "ERC20.transfer", "jump": "i", "offset": [ 3723, 3895 ], "op": "JUMP", "path": "3" }, "925": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "JUMPDEST", "path": "3" }, "926": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "PUSH2", "path": "3", "value": "0x1BF" }, "929": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "931": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "DUP1", "path": "3" }, "932": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "CALLDATASIZE", "path": "3" }, "933": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "SUB", "path": "3" }, "934": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "936": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "DUP2", "path": "3" }, "937": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "LT", "path": "3" }, "938": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "ISZERO", "path": "3" }, "939": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "PUSH2", "path": "3", "value": "0x3B3" }, "942": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "JUMPI", "path": "3" }, "943": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "945": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "DUP1", "path": "3" }, "946": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "REVERT", "path": "3" }, "947": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "JUMPDEST", "path": "3" }, "948": { "op": "POP" }, "949": { "op": "PUSH1", "value": "0x1" }, "951": { "op": "PUSH1", "value": "0x1" }, "953": { "op": "PUSH1", "value": "0xA0" }, "955": { "op": "SHL" }, "956": { "op": "SUB" }, "957": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "DUP2", "path": "3" }, "958": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "CALLDATALOAD", "path": "3" }, "959": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "DUP2", "path": "3" }, "960": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "AND", "path": "3" }, "961": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "SWAP2", "path": "3" }, "962": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "964": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "ADD", "path": "3" }, "965": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "CALLDATALOAD", "path": "3" }, "966": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "AND", "path": "3" }, "967": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "PUSH2", "path": "3", "value": "0x68D" }, "970": { "fn": "ERC20.allowance", "jump": "i", "offset": [ 3953, 4102 ], "op": "JUMP", "path": "3" }, "971": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "JUMPDEST", "path": "3" }, "972": { "fn": "ERC20.name", "offset": [ 2273, 2278 ], "op": "PUSH1", "path": "3", "statement": 0, "value": "0x3" }, "974": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP1", "path": "3" }, "975": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SLOAD", "path": "3" }, "976": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "978": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP1", "path": "3" }, "979": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MLOAD", "path": "3" }, "980": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "982": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x1F" }, "984": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x2" }, "986": { "op": "PUSH1", "value": "0x0" }, "988": { "op": "NOT" }, "989": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH2", "path": "3", "value": "0x100" }, "992": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "994": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP9", "path": "3" }, "995": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "AND", "path": "3" }, "996": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ISZERO", "path": "3" }, "997": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MUL", "path": "3" }, "998": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "999": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1000": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP6", "path": "3" }, "1001": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "AND", "path": "3" }, "1002": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP5", "path": "3" }, "1003": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1004": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP5", "path": "3" }, "1005": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DIV", "path": "3" }, "1006": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP4", "path": "3" }, "1007": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP5", "path": "3" }, "1008": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1009": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP2", "path": "3" }, "1010": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1011": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DIV", "path": "3" }, "1012": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP2", "path": "3" }, "1013": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MUL", "path": "3" }, "1014": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP3", "path": "3" }, "1015": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1016": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP2", "path": "3" }, "1017": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1018": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1019": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP3", "path": "3" }, "1020": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MSTORE", "path": "3" }, "1021": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP3", "path": "3" }, "1022": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP2", "path": "3" }, "1023": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MSTORE", "path": "3" }, "1024": { "fn": "ERC20.name", "offset": [ 2241, 2254 ], "op": "PUSH1", "path": "3", "value": "0x60" }, "1026": { "fn": "ERC20.name", "offset": [ 2241, 2254 ], "op": "SWAP4", "path": "3" }, "1027": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1028": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP3", "path": "3" }, "1029": { "fn": "ERC20.name", "offset": [ 2273, 2278 ], "op": "SWAP1", "path": "3" }, "1030": { "fn": "ERC20.name", "offset": [ 2273, 2278 ], "op": "SWAP2", "path": "3" }, "1031": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP4", "path": "3" }, "1032": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1033": { "fn": "ERC20.name", "offset": [ 2273, 2278 ], "op": "DUP3", "path": "3" }, "1034": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP3", "path": "3" }, "1035": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP1", "path": "3" }, "1036": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ISZERO", "path": "3" }, "1037": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH2", "path": "3", "value": "0x457" }, "1040": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "JUMPI", "path": "3" }, "1041": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP1", "path": "3" }, "1042": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x1F" }, "1044": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "LT", "path": "3" }, "1045": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH2", "path": "3", "value": "0x42C" }, "1048": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "JUMPI", "path": "3" }, "1049": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH2", "path": "3", "value": "0x100" }, "1052": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP1", "path": "3" }, "1053": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP4", "path": "3" }, "1054": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SLOAD", "path": "3" }, "1055": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DIV", "path": "3" }, "1056": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MUL", "path": "3" }, "1057": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP4", "path": "3" }, "1058": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MSTORE", "path": "3" }, "1059": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP2", "path": "3" }, "1060": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1062": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1063": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP2", "path": "3" }, "1064": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH2", "path": "3", "value": "0x457" }, "1067": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "JUMP", "path": "3" }, "1068": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "JUMPDEST", "path": "3" }, "1069": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP3", "path": "3" }, "1070": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1071": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP2", "path": "3" }, "1072": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1073": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1075": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MSTORE", "path": "3" }, "1076": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1078": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1080": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "KECCAK256", "path": "3" }, "1081": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1082": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "JUMPDEST", "path": "3" }, "1083": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP2", "path": "3" }, "1084": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SLOAD", "path": "3" }, "1085": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP2", "path": "3" }, "1086": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "MSTORE", "path": "3" }, "1087": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1088": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "1090": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1091": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1092": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1094": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1095": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP1", "path": "3" }, "1096": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP4", "path": "3" }, "1097": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "GT", "path": "3" }, "1098": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH2", "path": "3", "value": "0x43A" }, "1101": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "JUMPI", "path": "3" }, "1102": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP3", "path": "3" }, "1103": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1104": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SUB", "path": "3" }, "1105": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "PUSH1", "path": "3", "value": "0x1F" }, "1107": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "AND", "path": "3" }, "1108": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "DUP3", "path": "3" }, "1109": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "ADD", "path": "3" }, "1110": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP2", "path": "3" }, "1111": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "JUMPDEST", "path": "3" }, "1112": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "POP", "path": "3" }, "1113": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "POP", "path": "3" }, "1114": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "POP", "path": "3" }, "1115": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "POP", "path": "3" }, "1116": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "POP", "path": "3" }, "1117": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "SWAP1", "path": "3" }, "1118": { "fn": "ERC20.name", "offset": [ 2266, 2278 ], "op": "POP", "path": "3" }, "1119": { "fn": "ERC20.name", "offset": [ 2204, 2285 ], "op": "SWAP1", "path": "3" }, "1120": { "fn": "ERC20.name", "jump": "o", "offset": [ 2204, 2285 ], "op": "JUMP", "path": "3" }, "1121": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "JUMPDEST", "path": "3" }, "1122": { "fn": "ERC20.approve", "offset": [ 4323, 4327 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1124": { "fn": "ERC20.approve", "offset": [ 4339, 4378 ], "op": "PUSH2", "path": "3", "statement": 1, "value": "0x475" }, "1127": { "fn": "ERC20.approve", "offset": [ 4348, 4360 ], "op": "PUSH2", "path": "3", "value": "0x46E" }, "1130": { "fn": "ERC20.approve", "offset": [ 4348, 4358 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1133": { "fn": "ERC20.approve", "jump": "i", "offset": [ 4348, 4360 ], "op": "JUMP", "path": "3" }, "1134": { "fn": "ERC20.approve", "offset": [ 4348, 4360 ], "op": "JUMPDEST", "path": "3" }, "1135": { "fn": "ERC20.approve", "offset": [ 4362, 4369 ], "op": "DUP5", "path": "3" }, "1136": { "fn": "ERC20.approve", "offset": [ 4371, 4377 ], "op": "DUP5", "path": "3" }, "1137": { "fn": "ERC20.approve", "offset": [ 4339, 4347 ], "op": "PUSH2", "path": "3", "value": "0x71D" }, "1140": { "fn": "ERC20.approve", "jump": "i", "offset": [ 4339, 4378 ], "op": "JUMP", "path": "3" }, "1141": { "fn": "ERC20.approve", "offset": [ 4339, 4378 ], "op": "JUMPDEST", "path": "3" }, "1142": { "op": "POP" }, "1143": { "fn": "ERC20.approve", "offset": [ 4395, 4399 ], "op": "PUSH1", "path": "3", "statement": 2, "value": "0x1" }, "1145": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SWAP3", "path": "3" }, "1146": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "SWAP2", "path": "3" }, "1147": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "POP", "path": "3" }, "1148": { "fn": "ERC20.approve", "offset": [ 4240, 4406 ], "op": "POP", "path": "3" }, "1149": { "fn": "ERC20.approve", "jump": "o", "offset": [ 4240, 4406 ], "op": "JUMP", "path": "3" }, "1150": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "JUMPDEST", "path": "3" }, "1151": { "fn": "ERC20.totalSupply", "offset": [ 3326, 3338 ], "op": "PUSH1", "path": "3", "statement": 3, "value": "0x2" }, "1153": { "fn": "ERC20.totalSupply", "offset": [ 3326, 3338 ], "op": "SLOAD", "path": "3" }, "1154": { "fn": "ERC20.totalSupply", "offset": [ 3247, 3345 ], "op": "SWAP1", "path": "3" }, "1155": { "fn": "ERC20.totalSupply", "jump": "o", "offset": [ 3247, 3345 ], "op": "JUMP", "path": "3" }, "1156": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "JUMPDEST", "path": "4" }, "1157": { "fn": "ERC20Mock.transferInternal", "offset": [ 659, 685 ], "op": "PUSH2", "path": "4", "statement": 4, "value": "0x48F" }, "1160": { "fn": "ERC20Mock.transferInternal", "offset": [ 669, 673 ], "op": "DUP4", "path": "4" }, "1161": { "fn": "ERC20Mock.transferInternal", "offset": [ 675, 677 ], "op": "DUP4", "path": "4" }, "1162": { "fn": "ERC20Mock.transferInternal", "offset": [ 679, 684 ], "op": "DUP4", "path": "4" }, "1163": { "fn": "ERC20Mock.transferInternal", "offset": [ 659, 668 ], "op": "PUSH2", "path": "4", "value": "0x809" }, "1166": { "fn": "ERC20Mock.transferInternal", "jump": "i", "offset": [ 659, 685 ], "op": "JUMP", "path": "4" }, "1167": { "fn": "ERC20Mock.transferInternal", "offset": [ 659, 685 ], "op": "JUMPDEST", "path": "4" }, "1168": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "POP", "path": "4" }, "1169": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "POP", "path": "4" }, "1170": { "fn": "ERC20Mock.transferInternal", "offset": [ 575, 692 ], "op": "POP", "path": "4" }, "1171": { "fn": "ERC20Mock.transferInternal", "jump": "o", "offset": [ 575, 692 ], "op": "JUMP", "path": "4" }, "1172": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "JUMPDEST", "path": "3" }, "1173": { "fn": "ERC20.transferFrom", "offset": [ 4972, 4976 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1175": { "fn": "ERC20.transferFrom", "offset": [ 4988, 5024 ], "op": "PUSH2", "path": "3", "statement": 5, "value": "0x4A1" }, "1178": { "fn": "ERC20.transferFrom", "offset": [ 4998, 5004 ], "op": "DUP5", "path": "3" }, "1179": { "fn": "ERC20.transferFrom", "offset": [ 5006, 5015 ], "op": "DUP5", "path": "3" }, "1180": { "fn": "ERC20.transferFrom", "offset": [ 5017, 5023 ], "op": "DUP5", "path": "3" }, "1181": { "fn": "ERC20.transferFrom", "offset": [ 4988, 4997 ], "op": "PUSH2", "path": "3", "value": "0x809" }, "1184": { "fn": "ERC20.transferFrom", "jump": "i", "offset": [ 4988, 5024 ], "op": "JUMP", "path": "3" }, "1185": { "fn": "ERC20.transferFrom", "offset": [ 4988, 5024 ], "op": "JUMPDEST", "path": "3" }, "1186": { "fn": "ERC20.transferFrom", "offset": [ 5034, 5155 ], "op": "PUSH2", "path": "3", "statement": 6, "value": "0x511" }, "1189": { "fn": "ERC20.transferFrom", "offset": [ 5043, 5049 ], "op": "DUP5", "path": "3" }, "1190": { "fn": "ERC20.transferFrom", "offset": [ 5051, 5063 ], "op": "PUSH2", "path": "3", "value": "0x4AD" }, "1193": { "fn": "ERC20.transferFrom", "offset": [ 5051, 5061 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1196": { "fn": "ERC20.transferFrom", "jump": "i", "offset": [ 5051, 5063 ], "op": "JUMP", "path": "3" }, "1197": { "fn": "ERC20.transferFrom", "offset": [ 5051, 5063 ], "op": "JUMPDEST", "path": "3" }, "1198": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH2", "path": "3", "value": "0x50C" }, "1201": { "fn": "ERC20.transferFrom", "offset": [ 5103, 5109 ], "op": "DUP6", "path": "3" }, "1202": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1204": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "MLOAD", "path": "3" }, "1205": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "DUP1", "path": "3" }, "1206": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH1", "path": "3", "value": "0x60" }, "1208": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "ADD", "path": "3" }, "1209": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1211": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "MSTORE", "path": "3" }, "1212": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "DUP1", "path": "3" }, "1213": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH1", "path": "3", "value": "0x28" }, "1215": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "DUP2", "path": "3" }, "1216": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "MSTORE", "path": "3" }, "1217": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1219": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "ADD", "path": "3" }, "1220": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH2", "path": "3", "value": "0xCB7" }, "1223": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "PUSH1", "path": "3", "value": "0x28" }, "1225": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "SWAP2", "path": "3" }, "1226": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "CODECOPY", "path": "3" }, "1227": { "op": "PUSH1", "value": "0x1" }, "1229": { "op": "PUSH1", "value": "0x1" }, "1231": { "op": "PUSH1", "value": "0xA0" }, "1233": { "op": "SHL" }, "1234": { "op": "SUB" }, "1235": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "DUP11", "path": "3" }, "1236": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "AND", "path": "3" }, "1237": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1239": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "SWAP1", "path": "3" }, "1240": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "DUP2", "path": "3" }, "1241": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "MSTORE", "path": "3" }, "1242": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5076 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "1244": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1246": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "MSTORE", "path": "3" }, "1247": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1249": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "DUP2", "path": "3" }, "1250": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "KECCAK256", "path": "3" }, "1251": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5084 ], "op": "SWAP1", "path": "3" }, "1252": { "fn": "ERC20.transferFrom", "offset": [ 5085, 5097 ], "op": "PUSH2", "path": "3", "value": "0x4EB" }, "1255": { "fn": "ERC20.transferFrom", "offset": [ 5085, 5095 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1258": { "fn": "ERC20.transferFrom", "jump": "i", "offset": [ 5085, 5097 ], "op": "JUMP", "path": "3" }, "1259": { "fn": "ERC20.transferFrom", "offset": [ 5085, 5097 ], "op": "JUMPDEST", "path": "3" }, "1260": { "op": "PUSH1", "value": "0x1" }, "1262": { "op": "PUSH1", "value": "0x1" }, "1264": { "op": "PUSH1", "value": "0xA0" }, "1266": { "op": "SHL" }, "1267": { "op": "SUB" }, "1268": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "AND", "path": "3" }, "1269": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "DUP2", "path": "3" }, "1270": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "MSTORE", "path": "3" }, "1271": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1273": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "DUP2", "path": "3" }, "1274": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "ADD", "path": "3" }, "1275": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "SWAP2", "path": "3" }, "1276": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "SWAP1", "path": "3" }, "1277": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "SWAP2", "path": "3" }, "1278": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "MSTORE", "path": "3" }, "1279": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1281": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "ADD", "path": "3" }, "1282": { "op": "PUSH1", "value": "0x0" }, "1284": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "KECCAK256", "path": "3" }, "1285": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "SLOAD", "path": "3" }, "1286": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5098 ], "op": "SWAP2", "path": "3" }, "1287": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "SWAP1", "path": "3" }, "1288": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5102 ], "op": "PUSH2", "path": "3", "value": "0x964" }, "1291": { "fn": "ERC20.transferFrom", "jump": "i", "offset": [ 5065, 5154 ], "op": "JUMP", "path": "3" }, "1292": { "fn": "ERC20.transferFrom", "offset": [ 5065, 5154 ], "op": "JUMPDEST", "path": "3" }, "1293": { "fn": "ERC20.transferFrom", "offset": [ 5034, 5042 ], "op": "PUSH2", "path": "3", "value": "0x71D" }, "1296": { "fn": "ERC20.transferFrom", "jump": "i", "offset": [ 5034, 5155 ], "op": "JUMP", "path": "3" }, "1297": { "fn": "ERC20.transferFrom", "offset": [ 5034, 5155 ], "op": "JUMPDEST", "path": "3" }, "1298": { "op": "POP" }, "1299": { "fn": "ERC20.transferFrom", "offset": [ 5172, 5176 ], "op": "PUSH1", "path": "3", "statement": 7, "value": "0x1" }, "1301": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "SWAP4", "path": "3" }, "1302": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "SWAP3", "path": "3" }, "1303": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "POP", "path": "3" }, "1304": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "POP", "path": "3" }, "1305": { "fn": "ERC20.transferFrom", "offset": [ 4866, 5183 ], "op": "POP", "path": "3" }, "1306": { "fn": "ERC20.transferFrom", "jump": "o", "offset": [ 4866, 5183 ], "op": "JUMP", "path": "3" }, "1307": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "JUMPDEST", "path": "3" }, "1308": { "fn": "ERC20.decimals", "offset": [ 3171, 3180 ], "op": "PUSH1", "path": "3", "statement": 8, "value": "0x5" }, "1310": { "fn": "ERC20.decimals", "offset": [ 3171, 3180 ], "op": "SLOAD", "path": "3" }, "1311": { "fn": "ERC20.decimals", "offset": [ 3171, 3180 ], "op": "PUSH1", "path": "3", "value": "0xFF" }, "1313": { "fn": "ERC20.decimals", "offset": [ 3171, 3180 ], "op": "AND", "path": "3" }, "1314": { "fn": "ERC20.decimals", "offset": [ 3106, 3187 ], "op": "SWAP1", "path": "3" }, "1315": { "fn": "ERC20.decimals", "jump": "o", "offset": [ 3106, 3187 ], "op": "JUMP", "path": "3" }, "1316": { "fn": "ERC20.increaseAllowance", "offset": [ 5578, 5793 ], "op": "JUMPDEST", "path": "3" }, "1317": { "fn": "ERC20.increaseAllowance", "offset": [ 5666, 5670 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1319": { "fn": "ERC20.increaseAllowance", "offset": [ 5682, 5765 ], "op": "PUSH2", "path": "3", "statement": 9, "value": "0x475" }, "1322": { "fn": "ERC20.increaseAllowance", "offset": [ 5691, 5703 ], "op": "PUSH2", "path": "3", "value": "0x531" }, "1325": { "fn": "ERC20.increaseAllowance", "offset": [ 5691, 5701 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1328": { "fn": "ERC20.increaseAllowance", "jump": "i", "offset": [ 5691, 5703 ], "op": "JUMP", "path": "3" }, "1329": { "fn": "ERC20.increaseAllowance", "offset": [ 5691, 5703 ], "op": "JUMPDEST", "path": "3" }, "1330": { "fn": "ERC20.increaseAllowance", "offset": [ 5705, 5712 ], "op": "DUP5", "path": "3" }, "1331": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5764 ], "op": "PUSH2", "path": "3", "value": "0x50C" }, "1334": { "fn": "ERC20.increaseAllowance", "offset": [ 5753, 5763 ], "op": "DUP6", "path": "3" }, "1335": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5725 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "1337": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1339": { "fn": "ERC20.increaseAllowance", "offset": [ 5726, 5738 ], "op": "PUSH2", "path": "3", "value": "0x542" }, "1342": { "fn": "ERC20.increaseAllowance", "offset": [ 5726, 5736 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1345": { "fn": "ERC20.increaseAllowance", "jump": "i", "offset": [ 5726, 5738 ], "op": "JUMP", "path": "3" }, "1346": { "fn": "ERC20.increaseAllowance", "offset": [ 5726, 5738 ], "op": "JUMPDEST", "path": "3" }, "1347": { "op": "PUSH1", "value": "0x1" }, "1349": { "op": "PUSH1", "value": "0x1" }, "1351": { "op": "PUSH1", "value": "0xA0" }, "1353": { "op": "SHL" }, "1354": { "op": "SUB" }, "1355": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "SWAP1", "path": "3" }, "1356": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "DUP2", "path": "3" }, "1357": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "AND", "path": "3" }, "1358": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "DUP3", "path": "3" }, "1359": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "MSTORE", "path": "3" }, "1360": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1362": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "DUP1", "path": "3" }, "1363": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "DUP4", "path": "3" }, "1364": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "ADD", "path": "3" }, "1365": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "SWAP4", "path": "3" }, "1366": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "SWAP1", "path": "3" }, "1367": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "SWAP4", "path": "3" }, "1368": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "MSTORE", "path": "3" }, "1369": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1371": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "SWAP2", "path": "3" }, "1372": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "DUP3", "path": "3" }, "1373": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "ADD", "path": "3" }, "1374": { "op": "PUSH1", "value": "0x0" }, "1376": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "SWAP1", "path": "3" }, "1377": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "DUP2", "path": "3" }, "1378": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5739 ], "op": "KECCAK256", "path": "3" }, "1379": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "SWAP2", "path": "3" }, "1380": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "DUP13", "path": "3" }, "1381": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "AND", "path": "3" }, "1382": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "DUP2", "path": "3" }, "1383": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "MSTORE", "path": "3" }, "1384": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "SWAP3", "path": "3" }, "1385": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "MSTORE", "path": "3" }, "1386": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "SWAP1", "path": "3" }, "1387": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "KECCAK256", "path": "3" }, "1388": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "SLOAD", "path": "3" }, "1389": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5748 ], "op": "SWAP1", "path": "3" }, "1390": { "fn": "ERC20.increaseAllowance", "offset": [ 5714, 5752 ], "op": "PUSH2", "path": "3", "value": "0x6B8" }, "1393": { "fn": "ERC20.increaseAllowance", "jump": "i", "offset": [ 5714, 5764 ], "op": "JUMP", "path": "3" }, "1394": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "JUMPDEST", "path": "4" }, "1395": { "fn": "ERC20Mock.mint", "offset": [ 441, 463 ], "op": "PUSH2", "path": "4", "statement": 10, "value": "0x57C" }, "1398": { "fn": "ERC20Mock.mint", "offset": [ 447, 454 ], "op": "DUP3", "path": "4" }, "1399": { "fn": "ERC20Mock.mint", "offset": [ 456, 462 ], "op": "DUP3", "path": "4" }, "1400": { "fn": "ERC20Mock.mint", "offset": [ 441, 446 ], "op": "PUSH2", "path": "4", "value": "0x9FB" }, "1403": { "fn": "ERC20Mock.mint", "jump": "i", "offset": [ 441, 463 ], "op": "JUMP", "path": "4" }, "1404": { "fn": "ERC20Mock.mint", "offset": [ 441, 463 ], "op": "JUMPDEST", "path": "4" }, "1405": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "POP", "path": "4" }, "1406": { "fn": "ERC20Mock.mint", "offset": [ 377, 470 ], "op": "POP", "path": "4" }, "1407": { "fn": "ERC20Mock.mint", "jump": "o", "offset": [ 377, 470 ], "op": "JUMP", "path": "4" }, "1408": { "fn": "ERC20Mock.approveInternal", "offset": [ 698, 825 ], "op": "JUMPDEST", "path": "4" }, "1409": { "fn": "ERC20Mock.approveInternal", "offset": [ 787, 818 ], "op": "PUSH2", "path": "4", "statement": 11, "value": "0x48F" }, "1412": { "fn": "ERC20Mock.approveInternal", "offset": [ 796, 801 ], "op": "DUP4", "path": "4" }, "1413": { "fn": "ERC20Mock.approveInternal", "offset": [ 803, 810 ], "op": "DUP4", "path": "4" }, "1414": { "fn": "ERC20Mock.approveInternal", "offset": [ 812, 817 ], "op": "DUP4", "path": "4" }, "1415": { "fn": "ERC20Mock.approveInternal", "offset": [ 787, 795 ], "op": "PUSH2", "path": "4", "value": "0x71D" }, "1418": { "fn": "ERC20Mock.approveInternal", "jump": "i", "offset": [ 787, 818 ], "op": "JUMP", "path": "4" }, "1419": { "fn": "ERC20.balanceOf", "offset": [ 3403, 3520 ], "op": "JUMPDEST", "path": "3" }, "1420": { "op": "PUSH1", "value": "0x1" }, "1422": { "op": "PUSH1", "value": "0x1" }, "1424": { "op": "PUSH1", "value": "0xA0" }, "1426": { "op": "SHL" }, "1427": { "op": "SUB" }, "1428": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "AND", "path": "3", "statement": 12 }, "1429": { "fn": "ERC20.balanceOf", "offset": [ 3469, 3476 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1431": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "SWAP1", "path": "3" }, "1432": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "DUP2", "path": "3" }, "1433": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "MSTORE", "path": "3" }, "1434": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1436": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "DUP2", "path": "3" }, "1437": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "SWAP1", "path": "3" }, "1438": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "MSTORE", "path": "3" }, "1439": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1441": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "SWAP1", "path": "3" }, "1442": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "KECCAK256", "path": "3" }, "1443": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "SLOAD", "path": "3" }, "1444": { "fn": "ERC20.balanceOf", "offset": [ 3495, 3513 ], "op": "SWAP1", "path": "3" }, "1445": { "fn": "ERC20.balanceOf", "jump": "o", "offset": [ 3403, 3520 ], "op": "JUMP", "path": "3" }, "1446": { "fn": "ERC20.symbol", "offset": [ 2398, 2483 ], "op": "JUMPDEST", "path": "3" }, "1447": { "fn": "ERC20.symbol", "offset": [ 2469, 2476 ], "op": "PUSH1", "path": "3", "statement": 13, "value": "0x4" }, "1449": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP1", "path": "3" }, "1450": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SLOAD", "path": "3" }, "1451": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1453": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP1", "path": "3" }, "1454": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "MLOAD", "path": "3" }, "1455": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1457": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH1", "path": "3", "value": "0x1F" }, "1459": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH1", "path": "3", "value": "0x2" }, "1461": { "op": "PUSH1", "value": "0x0" }, "1463": { "op": "NOT" }, "1464": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH2", "path": "3", "value": "0x100" }, "1467": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "1469": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP9", "path": "3" }, "1470": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "AND", "path": "3" }, "1471": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ISZERO", "path": "3" }, "1472": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "MUL", "path": "3" }, "1473": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ADD", "path": "3" }, "1474": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP1", "path": "3" }, "1475": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP6", "path": "3" }, "1476": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "AND", "path": "3" }, "1477": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP5", "path": "3" }, "1478": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP1", "path": "3" }, "1479": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP5", "path": "3" }, "1480": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DIV", "path": "3" }, "1481": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP4", "path": "3" }, "1482": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP5", "path": "3" }, "1483": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ADD", "path": "3" }, "1484": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP2", "path": "3" }, "1485": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP1", "path": "3" }, "1486": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DIV", "path": "3" }, "1487": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP2", "path": "3" }, "1488": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "MUL", "path": "3" }, "1489": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP3", "path": "3" }, "1490": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ADD", "path": "3" }, "1491": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP2", "path": "3" }, "1492": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ADD", "path": "3" }, "1493": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP1", "path": "3" }, "1494": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP3", "path": "3" }, "1495": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "MSTORE", "path": "3" }, "1496": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP3", "path": "3" }, "1497": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP2", "path": "3" }, "1498": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "MSTORE", "path": "3" }, "1499": { "fn": "ERC20.symbol", "offset": [ 2437, 2450 ], "op": "PUSH1", "path": "3", "value": "0x60" }, "1501": { "fn": "ERC20.symbol", "offset": [ 2437, 2450 ], "op": "SWAP4", "path": "3" }, "1502": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP1", "path": "3" }, "1503": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP3", "path": "3" }, "1504": { "fn": "ERC20.symbol", "offset": [ 2469, 2476 ], "op": "SWAP1", "path": "3" }, "1505": { "fn": "ERC20.symbol", "offset": [ 2469, 2476 ], "op": "SWAP2", "path": "3" }, "1506": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP4", "path": "3" }, "1507": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ADD", "path": "3" }, "1508": { "fn": "ERC20.symbol", "offset": [ 2469, 2476 ], "op": "DUP3", "path": "3" }, "1509": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP3", "path": "3" }, "1510": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP1", "path": "3" }, "1511": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ISZERO", "path": "3" }, "1512": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH2", "path": "3", "value": "0x457" }, "1515": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "JUMPI", "path": "3" }, "1516": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP1", "path": "3" }, "1517": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH1", "path": "3", "value": "0x1F" }, "1519": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "LT", "path": "3" }, "1520": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH2", "path": "3", "value": "0x42C" }, "1523": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "JUMPI", "path": "3" }, "1524": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH2", "path": "3", "value": "0x100" }, "1527": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP1", "path": "3" }, "1528": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP4", "path": "3" }, "1529": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SLOAD", "path": "3" }, "1530": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DIV", "path": "3" }, "1531": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "MUL", "path": "3" }, "1532": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "DUP4", "path": "3" }, "1533": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "MSTORE", "path": "3" }, "1534": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP2", "path": "3" }, "1535": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1537": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "ADD", "path": "3" }, "1538": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "SWAP2", "path": "3" }, "1539": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "PUSH2", "path": "3", "value": "0x457" }, "1542": { "fn": "ERC20.symbol", "offset": [ 2462, 2476 ], "op": "JUMP", "path": "3" }, "1543": { "fn": "ERC20Mock.burn", "offset": [ 476, 569 ], "op": "JUMPDEST", "path": "4" }, "1544": { "fn": "ERC20Mock.burn", "offset": [ 540, 562 ], "op": "PUSH2", "path": "4", "statement": 14, "value": "0x57C" }, "1547": { "fn": "ERC20Mock.burn", "offset": [ 546, 553 ], "op": "DUP3", "path": "4" }, "1548": { "fn": "ERC20Mock.burn", "offset": [ 555, 561 ], "op": "DUP3", "path": "4" }, "1549": { "fn": "ERC20Mock.burn", "offset": [ 540, 545 ], "op": "PUSH2", "path": "4", "value": "0xAEB" }, "1552": { "fn": "ERC20Mock.burn", "jump": "i", "offset": [ 540, 562 ], "op": "JUMP", "path": "4" }, "1553": { "fn": "ERC20.decreaseAllowance", "offset": [ 6280, 6546 ], "op": "JUMPDEST", "path": "3" }, "1554": { "fn": "ERC20.decreaseAllowance", "offset": [ 6373, 6377 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1556": { "fn": "ERC20.decreaseAllowance", "offset": [ 6389, 6518 ], "op": "PUSH2", "path": "3", "statement": 15, "value": "0x475" }, "1559": { "fn": "ERC20.decreaseAllowance", "offset": [ 6398, 6410 ], "op": "PUSH2", "path": "3", "value": "0x61E" }, "1562": { "fn": "ERC20.decreaseAllowance", "offset": [ 6398, 6408 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1565": { "fn": "ERC20.decreaseAllowance", "jump": "i", "offset": [ 6398, 6410 ], "op": "JUMP", "path": "3" }, "1566": { "fn": "ERC20.decreaseAllowance", "offset": [ 6398, 6410 ], "op": "JUMPDEST", "path": "3" }, "1567": { "fn": "ERC20.decreaseAllowance", "offset": [ 6412, 6419 ], "op": "DUP5", "path": "3" }, "1568": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH2", "path": "3", "value": "0x50C" }, "1571": { "fn": "ERC20.decreaseAllowance", "offset": [ 6460, 6475 ], "op": "DUP6", "path": "3" }, "1572": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1574": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "MLOAD", "path": "3" }, "1575": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "DUP1", "path": "3" }, "1576": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH1", "path": "3", "value": "0x60" }, "1578": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "ADD", "path": "3" }, "1579": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1581": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "MSTORE", "path": "3" }, "1582": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "DUP1", "path": "3" }, "1583": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH1", "path": "3", "value": "0x25" }, "1585": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "DUP2", "path": "3" }, "1586": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "MSTORE", "path": "3" }, "1587": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1589": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "ADD", "path": "3" }, "1590": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH2", "path": "3", "value": "0xD49" }, "1593": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "PUSH1", "path": "3", "value": "0x25" }, "1595": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "SWAP2", "path": "3" }, "1596": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "CODECOPY", "path": "3" }, "1597": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6432 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "1599": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1601": { "fn": "ERC20.decreaseAllowance", "offset": [ 6433, 6445 ], "op": "PUSH2", "path": "3", "value": "0x648" }, "1604": { "fn": "ERC20.decreaseAllowance", "offset": [ 6433, 6443 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1607": { "fn": "ERC20.decreaseAllowance", "jump": "i", "offset": [ 6433, 6445 ], "op": "JUMP", "path": "3" }, "1608": { "fn": "ERC20.decreaseAllowance", "offset": [ 6433, 6445 ], "op": "JUMPDEST", "path": "3" }, "1609": { "op": "PUSH1", "value": "0x1" }, "1611": { "op": "PUSH1", "value": "0x1" }, "1613": { "op": "PUSH1", "value": "0xA0" }, "1615": { "op": "SHL" }, "1616": { "op": "SUB" }, "1617": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "SWAP1", "path": "3" }, "1618": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "DUP2", "path": "3" }, "1619": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "AND", "path": "3" }, "1620": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "DUP3", "path": "3" }, "1621": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "MSTORE", "path": "3" }, "1622": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1624": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "DUP1", "path": "3" }, "1625": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "DUP4", "path": "3" }, "1626": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "ADD", "path": "3" }, "1627": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "SWAP4", "path": "3" }, "1628": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "SWAP1", "path": "3" }, "1629": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "SWAP4", "path": "3" }, "1630": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "MSTORE", "path": "3" }, "1631": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1633": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "SWAP2", "path": "3" }, "1634": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "DUP3", "path": "3" }, "1635": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "ADD", "path": "3" }, "1636": { "op": "PUSH1", "value": "0x0" }, "1638": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "SWAP1", "path": "3" }, "1639": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "DUP2", "path": "3" }, "1640": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6446 ], "op": "KECCAK256", "path": "3" }, "1641": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "SWAP2", "path": "3" }, "1642": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "DUP14", "path": "3" }, "1643": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "AND", "path": "3" }, "1644": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "DUP2", "path": "3" }, "1645": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "MSTORE", "path": "3" }, "1646": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "SWAP3", "path": "3" }, "1647": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "MSTORE", "path": "3" }, "1648": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "SWAP1", "path": "3" }, "1649": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "KECCAK256", "path": "3" }, "1650": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "SLOAD", "path": "3" }, "1651": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6455 ], "op": "SWAP2", "path": "3" }, "1652": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6517 ], "op": "SWAP1", "path": "3" }, "1653": { "fn": "ERC20.decreaseAllowance", "offset": [ 6421, 6459 ], "op": "PUSH2", "path": "3", "value": "0x964" }, "1656": { "fn": "ERC20.decreaseAllowance", "jump": "i", "offset": [ 6421, 6517 ], "op": "JUMP", "path": "3" }, "1657": { "fn": "ERC20.transfer", "offset": [ 3723, 3895 ], "op": "JUMPDEST", "path": "3" }, "1658": { "fn": "ERC20.transfer", "offset": [ 3809, 3813 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1660": { "fn": "ERC20.transfer", "offset": [ 3825, 3867 ], "op": "PUSH2", "path": "3", "statement": 16, "value": "0x475" }, "1663": { "fn": "ERC20.transfer", "offset": [ 3835, 3847 ], "op": "PUSH2", "path": "3", "value": "0x686" }, "1666": { "fn": "ERC20.transfer", "offset": [ 3835, 3845 ], "op": "PUSH2", "path": "3", "value": "0x719" }, "1669": { "fn": "ERC20.transfer", "jump": "i", "offset": [ 3835, 3847 ], "op": "JUMP", "path": "3" }, "1670": { "fn": "ERC20.transfer", "offset": [ 3835, 3847 ], "op": "JUMPDEST", "path": "3" }, "1671": { "fn": "ERC20.transfer", "offset": [ 3849, 3858 ], "op": "DUP5", "path": "3" }, "1672": { "fn": "ERC20.transfer", "offset": [ 3860, 3866 ], "op": "DUP5", "path": "3" }, "1673": { "fn": "ERC20.transfer", "offset": [ 3825, 3834 ], "op": "PUSH2", "path": "3", "value": "0x809" }, "1676": { "fn": "ERC20.transfer", "jump": "i", "offset": [ 3825, 3867 ], "op": "JUMP", "path": "3" }, "1677": { "fn": "ERC20.allowance", "offset": [ 3953, 4102 ], "op": "JUMPDEST", "path": "3" }, "1678": { "op": "PUSH1", "value": "0x1" }, "1680": { "op": "PUSH1", "value": "0x1" }, "1682": { "op": "PUSH1", "value": "0xA0" }, "1684": { "op": "SHL" }, "1685": { "op": "SUB" }, "1686": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "SWAP2", "path": "3", "statement": 17 }, "1687": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "DUP3", "path": "3" }, "1688": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "AND", "path": "3" }, "1689": { "fn": "ERC20.allowance", "offset": [ 4042, 4049 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1691": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "SWAP1", "path": "3" }, "1692": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "DUP2", "path": "3" }, "1693": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "MSTORE", "path": "3" }, "1694": { "fn": "ERC20.allowance", "offset": [ 4068, 4079 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "1696": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1698": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "SWAP1", "path": "3" }, "1699": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "DUP2", "path": "3" }, "1700": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "MSTORE", "path": "3" }, "1701": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1703": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "DUP1", "path": "3" }, "1704": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "DUP4", "path": "3" }, "1705": { "fn": "ERC20.allowance", "offset": [ 4068, 4086 ], "op": "KECCAK256", "path": "3" }, "1706": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SWAP4", "path": "3" }, "1707": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SWAP1", "path": "3" }, "1708": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SWAP5", "path": "3" }, "1709": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "AND", "path": "3" }, "1710": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "DUP3", "path": "3" }, "1711": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "MSTORE", "path": "3" }, "1712": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SWAP2", "path": "3" }, "1713": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SWAP1", "path": "3" }, "1714": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SWAP2", "path": "3" }, "1715": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "MSTORE", "path": "3" }, "1716": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "KECCAK256", "path": "3" }, "1717": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SLOAD", "path": "3" }, "1718": { "fn": "ERC20.allowance", "offset": [ 4068, 4095 ], "op": "SWAP1", "path": "3" }, "1719": { "fn": "ERC20.allowance", "jump": "o", "offset": [ 3953, 4102 ], "op": "JUMP", "path": "3" }, "1720": { "fn": "SafeMath.add", "offset": [ 874, 1050 ], "op": "JUMPDEST", "path": "12" }, "1721": { "fn": "SafeMath.add", "offset": [ 932, 939 ], "op": "PUSH1", "path": "12", "value": "0x0" }, "1723": { "fn": "SafeMath.add", "offset": [ 963, 968 ], "op": "DUP3", "path": "12" }, "1724": { "fn": "SafeMath.add", "offset": [ 963, 968 ], "op": "DUP3", "path": "12" }, "1725": { "fn": "SafeMath.add", "offset": [ 963, 968 ], "op": "ADD", "path": "12" }, "1726": { "fn": "SafeMath.add", "offset": [ 986, 992 ], "op": "DUP4", "path": "12", "statement": 18 }, "1727": { "fn": "SafeMath.add", "offset": [ 986, 992 ], "op": "DUP2", "path": "12" }, "1728": { "fn": "SafeMath.add", "offset": [ 986, 992 ], "op": "LT", "path": "12" }, "1729": { "branch": 49, "fn": "SafeMath.add", "offset": [ 986, 992 ], "op": "ISZERO", "path": "12" }, "1730": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH2", "path": "12", "value": "0x712" }, "1733": { "branch": 49, "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "JUMPI", "path": "12" }, "1734": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH1", "path": "12", "value": "0x40" }, "1736": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "DUP1", "path": "12" }, "1737": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "MLOAD", "path": "12" }, "1738": { "op": "PUSH3", "value": "0x461BCD" }, "1742": { "op": "PUSH1", "value": "0xE5" }, "1744": { "op": "SHL" }, "1745": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "DUP2", "path": "12" }, "1746": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "MSTORE", "path": "12" }, "1747": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "1749": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH1", "path": "12", "value": "0x4" }, "1751": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "DUP3", "path": "12" }, "1752": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "ADD", "path": "12" }, "1753": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "MSTORE", "path": "12" }, "1754": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH1", "path": "12", "value": "0x1B" }, "1756": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH1", "path": "12", "value": "0x24" }, "1758": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "DUP3", "path": "12" }, "1759": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "ADD", "path": "12" }, "1760": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "MSTORE", "path": "12" }, "1761": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH32", "path": "12", "value": "0x536166654D6174683A206164646974696F6E206F766572666C6F770000000000" }, "1794": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH1", "path": "12", "value": "0x44" }, "1796": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "DUP3", "path": "12" }, "1797": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "ADD", "path": "12" }, "1798": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "MSTORE", "path": "12" }, "1799": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "SWAP1", "path": "12" }, "1800": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "MLOAD", "path": "12" }, "1801": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "SWAP1", "path": "12" }, "1802": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "DUP2", "path": "12" }, "1803": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "SWAP1", "path": "12" }, "1804": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "SUB", "path": "12" }, "1805": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "PUSH1", "path": "12", "value": "0x64" }, "1807": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "ADD", "path": "12" }, "1808": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "SWAP1", "path": "12" }, "1809": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "REVERT", "path": "12" }, "1810": { "fn": "SafeMath.add", "offset": [ 978, 1024 ], "op": "JUMPDEST", "path": "12" }, "1811": { "fn": "SafeMath.add", "offset": [ 1042, 1043 ], "op": "SWAP4", "path": "12", "statement": 19 }, "1812": { "fn": "SafeMath.add", "offset": [ 874, 1050 ], "op": "SWAP3", "path": "12" }, "1813": { "op": "POP" }, "1814": { "op": "POP" }, "1815": { "op": "POP" }, "1816": { "fn": "SafeMath.add", "jump": "o", "offset": [ 874, 1050 ], "op": "JUMP", "path": "12" }, "1817": { "fn": "Context._msgSender", "offset": [ 590, 694 ], "op": "JUMPDEST", "path": "2" }, "1818": { "fn": "Context._msgSender", "offset": [ 677, 687 ], "op": "CALLER", "path": "2", "statement": 20 }, "1819": { "fn": "Context._msgSender", "offset": [ 590, 694 ], "op": "SWAP1", "path": "2" }, "1820": { "fn": "Context._msgSender", "jump": "o", "offset": [ 590, 694 ], "op": "JUMP", "path": "2" }, "1821": { "fn": "ERC20._approve", "offset": [ 9344, 9684 ], "op": "JUMPDEST", "path": "3" }, "1822": { "op": "PUSH1", "value": "0x1" }, "1824": { "op": "PUSH1", "value": "0x1" }, "1826": { "op": "PUSH1", "value": "0xA0" }, "1828": { "op": "SHL" }, "1829": { "op": "SUB" }, "1830": { "fn": "ERC20._approve", "offset": [ 9445, 9464 ], "op": "DUP4", "path": "3", "statement": 21 }, "1831": { "branch": 43, "fn": "ERC20._approve", "offset": [ 9445, 9464 ], "op": "AND", "path": "3" }, "1832": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH2", "path": "3", "value": "0x762" }, "1835": { "branch": 43, "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "JUMPI", "path": "3" }, "1836": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1838": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "MLOAD", "path": "3" }, "1839": { "op": "PUSH3", "value": "0x461BCD" }, "1843": { "op": "PUSH1", "value": "0xE5" }, "1845": { "op": "SHL" }, "1846": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP2", "path": "3" }, "1847": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "MSTORE", "path": "3" }, "1848": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "1850": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "ADD", "path": "3" }, "1851": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP1", "path": "3" }, "1852": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP1", "path": "3" }, "1853": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1855": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "ADD", "path": "3" }, "1856": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP3", "path": "3" }, "1857": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP2", "path": "3" }, "1858": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "SUB", "path": "3" }, "1859": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP3", "path": "3" }, "1860": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "MSTORE", "path": "3" }, "1861": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x24" }, "1863": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP2", "path": "3" }, "1864": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "MSTORE", "path": "3" }, "1865": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1867": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "ADD", "path": "3" }, "1868": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP1", "path": "3" }, "1869": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH2", "path": "3", "value": "0xD25" }, "1872": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x24" }, "1874": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "SWAP2", "path": "3" }, "1875": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "CODECOPY", "path": "3" }, "1876": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1878": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "ADD", "path": "3" }, "1879": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "SWAP2", "path": "3" }, "1880": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "POP", "path": "3" }, "1881": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "POP", "path": "3" }, "1882": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1884": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "MLOAD", "path": "3" }, "1885": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "DUP1", "path": "3" }, "1886": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "SWAP2", "path": "3" }, "1887": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "SUB", "path": "3" }, "1888": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "SWAP1", "path": "3" }, "1889": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "REVERT", "path": "3" }, "1890": { "fn": "ERC20._approve", "offset": [ 9437, 9505 ], "op": "JUMPDEST", "path": "3" }, "1891": { "op": "PUSH1", "value": "0x1" }, "1893": { "op": "PUSH1", "value": "0x1" }, "1895": { "op": "PUSH1", "value": "0xA0" }, "1897": { "op": "SHL" }, "1898": { "op": "SUB" }, "1899": { "fn": "ERC20._approve", "offset": [ 9523, 9544 ], "op": "DUP3", "path": "3", "statement": 22 }, "1900": { "branch": 44, "fn": "ERC20._approve", "offset": [ 9523, 9544 ], "op": "AND", "path": "3" }, "1901": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH2", "path": "3", "value": "0x7A7" }, "1904": { "branch": 44, "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "JUMPI", "path": "3" }, "1905": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1907": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "MLOAD", "path": "3" }, "1908": { "op": "PUSH3", "value": "0x461BCD" }, "1912": { "op": "PUSH1", "value": "0xE5" }, "1914": { "op": "SHL" }, "1915": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP2", "path": "3" }, "1916": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "MSTORE", "path": "3" }, "1917": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "1919": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "ADD", "path": "3" }, "1920": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP1", "path": "3" }, "1921": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP1", "path": "3" }, "1922": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1924": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "ADD", "path": "3" }, "1925": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP3", "path": "3" }, "1926": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP2", "path": "3" }, "1927": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "SUB", "path": "3" }, "1928": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP3", "path": "3" }, "1929": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "MSTORE", "path": "3" }, "1930": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x22" }, "1932": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP2", "path": "3" }, "1933": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "MSTORE", "path": "3" }, "1934": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1936": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "ADD", "path": "3" }, "1937": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP1", "path": "3" }, "1938": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH2", "path": "3", "value": "0xC6F" }, "1941": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x22" }, "1943": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "SWAP2", "path": "3" }, "1944": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "CODECOPY", "path": "3" }, "1945": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1947": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "ADD", "path": "3" }, "1948": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "SWAP2", "path": "3" }, "1949": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "POP", "path": "3" }, "1950": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "POP", "path": "3" }, "1951": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1953": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "MLOAD", "path": "3" }, "1954": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "DUP1", "path": "3" }, "1955": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "SWAP2", "path": "3" }, "1956": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "SUB", "path": "3" }, "1957": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "SWAP1", "path": "3" }, "1958": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "REVERT", "path": "3" }, "1959": { "fn": "ERC20._approve", "offset": [ 9515, 9583 ], "op": "JUMPDEST", "path": "3" }, "1960": { "op": "PUSH1", "value": "0x1" }, "1962": { "op": "PUSH1", "value": "0x1" }, "1964": { "op": "PUSH1", "value": "0xA0" }, "1966": { "op": "SHL" }, "1967": { "op": "SUB" }, "1968": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "DUP1", "path": "3", "statement": 23 }, "1969": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "DUP5", "path": "3" }, "1970": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "AND", "path": "3" }, "1971": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "1973": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "DUP2", "path": "3" }, "1974": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "DUP2", "path": "3" }, "1975": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "MSTORE", "path": "3" }, "1976": { "fn": "ERC20._approve", "offset": [ 9594, 9605 ], "op": "PUSH1", "path": "3", "value": "0x1" }, "1978": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "1980": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "SWAP1", "path": "3" }, "1981": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "DUP2", "path": "3" }, "1982": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "MSTORE", "path": "3" }, "1983": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "1985": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "DUP1", "path": "3" }, "1986": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "DUP4", "path": "3" }, "1987": { "fn": "ERC20._approve", "offset": [ 9594, 9612 ], "op": "KECCAK256", "path": "3" }, "1988": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "SWAP5", "path": "3" }, "1989": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "DUP8", "path": "3" }, "1990": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "AND", "path": "3" }, "1991": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "DUP1", "path": "3" }, "1992": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "DUP5", "path": "3" }, "1993": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "MSTORE", "path": "3" }, "1994": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "SWAP5", "path": "3" }, "1995": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "DUP3", "path": "3" }, "1996": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "MSTORE", "path": "3" }, "1997": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "SWAP2", "path": "3" }, "1998": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "DUP3", "path": "3" }, "1999": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "SWAP1", "path": "3" }, "2000": { "fn": "ERC20._approve", "offset": [ 9594, 9621 ], "op": "KECCAK256", "path": "3" }, "2001": { "fn": "ERC20._approve", "offset": [ 9594, 9630 ], "op": "DUP6", "path": "3" }, "2002": { "fn": "ERC20._approve", "offset": [ 9594, 9630 ], "op": "SWAP1", "path": "3" }, "2003": { "fn": "ERC20._approve", "offset": [ 9594, 9630 ], "op": "SSTORE", "path": "3" }, "2004": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "DUP2", "path": "3", "statement": 24 }, "2005": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "MLOAD", "path": "3" }, "2006": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "DUP6", "path": "3" }, "2007": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "DUP2", "path": "3" }, "2008": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "MSTORE", "path": "3" }, "2009": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "SWAP2", "path": "3" }, "2010": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "MLOAD", "path": "3" }, "2011": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "PUSH32", "path": "3", "value": "0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925" }, "2044": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "SWAP3", "path": "3" }, "2045": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "DUP2", "path": "3" }, "2046": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "SWAP1", "path": "3" }, "2047": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "SUB", "path": "3" }, "2048": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "SWAP1", "path": "3" }, "2049": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "SWAP2", "path": "3" }, "2050": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "ADD", "path": "3" }, "2051": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "SWAP1", "path": "3" }, "2052": { "fn": "ERC20._approve", "offset": [ 9645, 9677 ], "op": "LOG3", "path": "3" }, "2053": { "fn": "ERC20._approve", "offset": [ 9344, 9684 ], "op": "POP", "path": "3" }, "2054": { "fn": "ERC20._approve", "offset": [ 9344, 9684 ], "op": "POP", "path": "3" }, "2055": { "fn": "ERC20._approve", "offset": [ 9344, 9684 ], "op": "POP", "path": "3" }, "2056": { "fn": "ERC20._approve", "jump": "o", "offset": [ 9344, 9684 ], "op": "JUMP", "path": "3" }, "2057": { "fn": "ERC20._transfer", "offset": [ 7020, 7550 ], "op": "JUMPDEST", "path": "3" }, "2058": { "op": "PUSH1", "value": "0x1" }, "2060": { "op": "PUSH1", "value": "0x1" }, "2062": { "op": "PUSH1", "value": "0xA0" }, "2064": { "op": "SHL" }, "2065": { "op": "SUB" }, "2066": { "fn": "ERC20._transfer", "offset": [ 7125, 7145 ], "op": "DUP4", "path": "3", "statement": 25 }, "2067": { "branch": 45, "fn": "ERC20._transfer", "offset": [ 7125, 7145 ], "op": "AND", "path": "3" }, "2068": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH2", "path": "3", "value": "0x84E" }, "2071": { "branch": 45, "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "JUMPI", "path": "3" }, "2072": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2074": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "MLOAD", "path": "3" }, "2075": { "op": "PUSH3", "value": "0x461BCD" }, "2079": { "op": "PUSH1", "value": "0xE5" }, "2081": { "op": "SHL" }, "2082": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP2", "path": "3" }, "2083": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "MSTORE", "path": "3" }, "2084": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "2086": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "ADD", "path": "3" }, "2087": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP1", "path": "3" }, "2088": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP1", "path": "3" }, "2089": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2091": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "ADD", "path": "3" }, "2092": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP3", "path": "3" }, "2093": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP2", "path": "3" }, "2094": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "SUB", "path": "3" }, "2095": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP3", "path": "3" }, "2096": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "MSTORE", "path": "3" }, "2097": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x25" }, "2099": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP2", "path": "3" }, "2100": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "MSTORE", "path": "3" }, "2101": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2103": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "ADD", "path": "3" }, "2104": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP1", "path": "3" }, "2105": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH2", "path": "3", "value": "0xD00" }, "2108": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x25" }, "2110": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "SWAP2", "path": "3" }, "2111": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "CODECOPY", "path": "3" }, "2112": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2114": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "ADD", "path": "3" }, "2115": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "SWAP2", "path": "3" }, "2116": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "POP", "path": "3" }, "2117": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "POP", "path": "3" }, "2118": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2120": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "MLOAD", "path": "3" }, "2121": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "DUP1", "path": "3" }, "2122": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "SWAP2", "path": "3" }, "2123": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "SUB", "path": "3" }, "2124": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "SWAP1", "path": "3" }, "2125": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "REVERT", "path": "3" }, "2126": { "fn": "ERC20._transfer", "offset": [ 7117, 7187 ], "op": "JUMPDEST", "path": "3" }, "2127": { "op": "PUSH1", "value": "0x1" }, "2129": { "op": "PUSH1", "value": "0x1" }, "2131": { "op": "PUSH1", "value": "0xA0" }, "2133": { "op": "SHL" }, "2134": { "op": "SUB" }, "2135": { "fn": "ERC20._transfer", "offset": [ 7205, 7228 ], "op": "DUP3", "path": "3", "statement": 26 }, "2136": { "branch": 46, "fn": "ERC20._transfer", "offset": [ 7205, 7228 ], "op": "AND", "path": "3" }, "2137": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH2", "path": "3", "value": "0x893" }, "2140": { "branch": 46, "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "JUMPI", "path": "3" }, "2141": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2143": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "MLOAD", "path": "3" }, "2144": { "op": "PUSH3", "value": "0x461BCD" }, "2148": { "op": "PUSH1", "value": "0xE5" }, "2150": { "op": "SHL" }, "2151": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP2", "path": "3" }, "2152": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "MSTORE", "path": "3" }, "2153": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "2155": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "ADD", "path": "3" }, "2156": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP1", "path": "3" }, "2157": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP1", "path": "3" }, "2158": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2160": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "ADD", "path": "3" }, "2161": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP3", "path": "3" }, "2162": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP2", "path": "3" }, "2163": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "SUB", "path": "3" }, "2164": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP3", "path": "3" }, "2165": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "MSTORE", "path": "3" }, "2166": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x23" }, "2168": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP2", "path": "3" }, "2169": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "MSTORE", "path": "3" }, "2170": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2172": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "ADD", "path": "3" }, "2173": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP1", "path": "3" }, "2174": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH2", "path": "3", "value": "0xC2A" }, "2177": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x23" }, "2179": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "SWAP2", "path": "3" }, "2180": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "CODECOPY", "path": "3" }, "2181": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2183": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "ADD", "path": "3" }, "2184": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "SWAP2", "path": "3" }, "2185": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "POP", "path": "3" }, "2186": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "POP", "path": "3" }, "2187": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2189": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "MLOAD", "path": "3" }, "2190": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "DUP1", "path": "3" }, "2191": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "SWAP2", "path": "3" }, "2192": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "SUB", "path": "3" }, "2193": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "SWAP1", "path": "3" }, "2194": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "REVERT", "path": "3" }, "2195": { "fn": "ERC20._transfer", "offset": [ 7197, 7268 ], "op": "JUMPDEST", "path": "3" }, "2196": { "fn": "ERC20._transfer", "offset": [ 7279, 7326 ], "op": "PUSH2", "path": "3", "statement": 27, "value": "0x89E" }, "2199": { "fn": "ERC20._transfer", "offset": [ 7300, 7306 ], "op": "DUP4", "path": "3" }, "2200": { "fn": "ERC20._transfer", "offset": [ 7308, 7317 ], "op": "DUP4", "path": "3" }, "2201": { "fn": "ERC20._transfer", "offset": [ 7319, 7325 ], "op": "DUP4", "path": "3" }, "2202": { "fn": "ERC20._transfer", "offset": [ 7279, 7299 ], "op": "PUSH2", "path": "3", "value": "0x48F" }, "2205": { "fn": "ERC20._transfer", "jump": "i", "offset": [ 7279, 7326 ], "op": "JUMP", "path": "3" }, "2206": { "fn": "ERC20._transfer", "offset": [ 7279, 7326 ], "op": "JUMPDEST", "path": "3" }, "2207": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH2", "path": "3", "statement": 28, "value": "0x8DB" }, "2210": { "fn": "ERC20._transfer", "offset": [ 7379, 7385 ], "op": "DUP2", "path": "3" }, "2211": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2213": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "MLOAD", "path": "3" }, "2214": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "DUP1", "path": "3" }, "2215": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH1", "path": "3", "value": "0x60" }, "2217": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "ADD", "path": "3" }, "2218": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2220": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "MSTORE", "path": "3" }, "2221": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "DUP1", "path": "3" }, "2222": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH1", "path": "3", "value": "0x26" }, "2224": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "DUP2", "path": "3" }, "2225": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "MSTORE", "path": "3" }, "2226": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2228": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "ADD", "path": "3" }, "2229": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH2", "path": "3", "value": "0xC91" }, "2232": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "PUSH1", "path": "3", "value": "0x26" }, "2234": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "SWAP2", "path": "3" }, "2235": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "CODECOPY", "path": "3" }, "2236": { "op": "PUSH1", "value": "0x1" }, "2238": { "op": "PUSH1", "value": "0x1" }, "2240": { "op": "PUSH1", "value": "0xA0" }, "2242": { "op": "SHL" }, "2243": { "op": "SUB" }, "2244": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "DUP7", "path": "3" }, "2245": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "AND", "path": "3" }, "2246": { "fn": "ERC20._transfer", "offset": [ 7357, 7366 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2248": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "SWAP1", "path": "3" }, "2249": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "DUP2", "path": "3" }, "2250": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "MSTORE", "path": "3" }, "2251": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2253": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "DUP2", "path": "3" }, "2254": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "SWAP1", "path": "3" }, "2255": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "MSTORE", "path": "3" }, "2256": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2258": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "SWAP1", "path": "3" }, "2259": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "KECCAK256", "path": "3" }, "2260": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "SLOAD", "path": "3" }, "2261": { "fn": "ERC20._transfer", "offset": [ 7357, 7374 ], "op": "SWAP2", "path": "3" }, "2262": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "SWAP1", "path": "3" }, "2263": { "fn": "ERC20._transfer", "offset": [ 7357, 7378 ], "op": "PUSH2", "path": "3", "value": "0x964" }, "2266": { "fn": "ERC20._transfer", "jump": "i", "offset": [ 7357, 7428 ], "op": "JUMP", "path": "3" }, "2267": { "fn": "ERC20._transfer", "offset": [ 7357, 7428 ], "op": "JUMPDEST", "path": "3" }, "2268": { "op": "PUSH1", "value": "0x1" }, "2270": { "op": "PUSH1", "value": "0x1" }, "2272": { "op": "PUSH1", "value": "0xA0" }, "2274": { "op": "SHL" }, "2275": { "op": "SUB" }, "2276": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "DUP1", "path": "3" }, "2277": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "DUP6", "path": "3" }, "2278": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "AND", "path": "3" }, "2279": { "fn": "ERC20._transfer", "offset": [ 7337, 7346 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2281": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "SWAP1", "path": "3" }, "2282": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "DUP2", "path": "3" }, "2283": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "MSTORE", "path": "3" }, "2284": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2286": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "DUP2", "path": "3" }, "2287": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "SWAP1", "path": "3" }, "2288": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "MSTORE", "path": "3" }, "2289": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2291": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "DUP1", "path": "3" }, "2292": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "DUP3", "path": "3" }, "2293": { "fn": "ERC20._transfer", "offset": [ 7337, 7354 ], "op": "KECCAK256", "path": "3" }, "2294": { "fn": "ERC20._transfer", "offset": [ 7337, 7428 ], "op": "SWAP4", "path": "3" }, "2295": { "fn": "ERC20._transfer", "offset": [ 7337, 7428 ], "op": "SWAP1", "path": "3" }, "2296": { "fn": "ERC20._transfer", "offset": [ 7337, 7428 ], "op": "SWAP4", "path": "3" }, "2297": { "fn": "ERC20._transfer", "offset": [ 7337, 7428 ], "op": "SSTORE", "path": "3" }, "2298": { "fn": "ERC20._transfer", "offset": [ 7461, 7481 ], "op": "SWAP1", "path": "3", "statement": 29 }, "2299": { "fn": "ERC20._transfer", "offset": [ 7461, 7481 ], "op": "DUP5", "path": "3" }, "2300": { "fn": "ERC20._transfer", "offset": [ 7461, 7481 ], "op": "AND", "path": "3" }, "2301": { "fn": "ERC20._transfer", "offset": [ 7461, 7481 ], "op": "DUP2", "path": "3" }, "2302": { "fn": "ERC20._transfer", "offset": [ 7461, 7481 ], "op": "MSTORE", "path": "3" }, "2303": { "fn": "ERC20._transfer", "offset": [ 7461, 7481 ], "op": "KECCAK256", "path": "3" }, "2304": { "fn": "ERC20._transfer", "offset": [ 7461, 7481 ], "op": "SLOAD", "path": "3" }, "2305": { "fn": "ERC20._transfer", "offset": [ 7461, 7493 ], "op": "PUSH2", "path": "3", "value": "0x90A" }, "2308": { "fn": "ERC20._transfer", "offset": [ 7461, 7493 ], "op": "SWAP1", "path": "3" }, "2309": { "fn": "ERC20._transfer", "offset": [ 7486, 7492 ], "op": "DUP3", "path": "3" }, "2310": { "fn": "ERC20._transfer", "offset": [ 7461, 7485 ], "op": "PUSH2", "path": "3", "value": "0x6B8" }, "2313": { "fn": "ERC20._transfer", "jump": "i", "offset": [ 7461, 7493 ], "op": "JUMP", "path": "3" }, "2314": { "fn": "ERC20._transfer", "offset": [ 7461, 7493 ], "op": "JUMPDEST", "path": "3" }, "2315": { "op": "PUSH1", "value": "0x1" }, "2317": { "op": "PUSH1", "value": "0x1" }, "2319": { "op": "PUSH1", "value": "0xA0" }, "2321": { "op": "SHL" }, "2322": { "op": "SUB" }, "2323": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "DUP1", "path": "3" }, "2324": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "DUP5", "path": "3" }, "2325": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "AND", "path": "3" }, "2326": { "fn": "ERC20._transfer", "offset": [ 7438, 7447 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2328": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "DUP2", "path": "3" }, "2329": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "DUP2", "path": "3" }, "2330": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "MSTORE", "path": "3" }, "2331": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2333": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "DUP2", "path": "3" }, "2334": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "DUP2", "path": "3" }, "2335": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "MSTORE", "path": "3" }, "2336": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2338": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "SWAP2", "path": "3" }, "2339": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "DUP3", "path": "3" }, "2340": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "SWAP1", "path": "3" }, "2341": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "KECCAK256", "path": "3" }, "2342": { "fn": "ERC20._transfer", "offset": [ 7438, 7493 ], "op": "SWAP5", "path": "3" }, "2343": { "fn": "ERC20._transfer", "offset": [ 7438, 7493 ], "op": "SWAP1", "path": "3" }, "2344": { "fn": "ERC20._transfer", "offset": [ 7438, 7493 ], "op": "SWAP5", "path": "3" }, "2345": { "fn": "ERC20._transfer", "offset": [ 7438, 7493 ], "op": "SSTORE", "path": "3" }, "2346": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "DUP1", "path": "3", "statement": 30 }, "2347": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "MLOAD", "path": "3" }, "2348": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "DUP6", "path": "3" }, "2349": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "DUP2", "path": "3" }, "2350": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "MSTORE", "path": "3" }, "2351": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SWAP1", "path": "3" }, "2352": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "MLOAD", "path": "3" }, "2353": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "SWAP2", "path": "3" }, "2354": { "fn": "ERC20._transfer", "offset": [ 7438, 7458 ], "op": "SWAP4", "path": "3" }, "2355": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SWAP3", "path": "3" }, "2356": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "DUP8", "path": "3" }, "2357": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "AND", "path": "3" }, "2358": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SWAP3", "path": "3" }, "2359": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "PUSH32", "path": "3", "value": "0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF" }, "2392": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SWAP3", "path": "3" }, "2393": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SWAP2", "path": "3" }, "2394": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "DUP3", "path": "3" }, "2395": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SWAP1", "path": "3" }, "2396": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SUB", "path": "3" }, "2397": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "ADD", "path": "3" }, "2398": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "SWAP1", "path": "3" }, "2399": { "fn": "ERC20._transfer", "offset": [ 7508, 7543 ], "op": "LOG3", "path": "3" }, "2400": { "fn": "ERC20._transfer", "offset": [ 7020, 7550 ], "op": "POP", "path": "3" }, "2401": { "fn": "ERC20._transfer", "offset": [ 7020, 7550 ], "op": "POP", "path": "3" }, "2402": { "fn": "ERC20._transfer", "offset": [ 7020, 7550 ], "op": "POP", "path": "3" }, "2403": { "fn": "ERC20._transfer", "jump": "o", "offset": [ 7020, 7550 ], "op": "JUMP", "path": "3" }, "2404": { "fn": "SafeMath.sub", "offset": [ 1746, 1933 ], "op": "JUMPDEST", "path": "12" }, "2405": { "fn": "SafeMath.sub", "offset": [ 1832, 1839 ], "op": "PUSH1", "path": "12", "value": "0x0" }, "2407": { "fn": "SafeMath.sub", "offset": [ 1867, 1879 ], "op": "DUP2", "path": "12", "statement": 31 }, "2408": { "fn": "SafeMath.sub", "offset": [ 1859, 1865 ], "op": "DUP5", "path": "12" }, "2409": { "fn": "SafeMath.sub", "offset": [ 1859, 1865 ], "op": "DUP5", "path": "12" }, "2410": { "fn": "SafeMath.sub", "offset": [ 1859, 1865 ], "op": "GT", "path": "12" }, "2411": { "branch": 50, "fn": "SafeMath.sub", "offset": [ 1859, 1865 ], "op": "ISZERO", "path": "12" }, "2412": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH2", "path": "12", "value": "0x9F3" }, "2415": { "branch": 50, "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMPI", "path": "12" }, "2416": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x40" }, "2418": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MLOAD", "path": "12" }, "2419": { "op": "PUSH3", "value": "0x461BCD" }, "2423": { "op": "PUSH1", "value": "0xE5" }, "2425": { "op": "SHL" }, "2426": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2427": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MSTORE", "path": "12" }, "2428": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x4" }, "2430": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2431": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2432": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2433": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "2435": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2436": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP3", "path": "12" }, "2437": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2438": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SUB", "path": "12" }, "2439": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP3", "path": "12" }, "2440": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MSTORE", "path": "12" }, "2441": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP4", "path": "12" }, "2442": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2443": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2444": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MLOAD", "path": "12" }, "2445": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2446": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MSTORE", "path": "12" }, "2447": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "2449": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2450": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP2", "path": "12" }, "2451": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2452": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2453": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MLOAD", "path": "12" }, "2454": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP1", "path": "12" }, "2455": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "2457": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2458": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP1", "path": "12" }, "2459": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2460": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP4", "path": "12" }, "2461": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP4", "path": "12" }, "2462": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x0" }, "2464": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMPDEST", "path": "12" }, "2465": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP4", "path": "12" }, "2466": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2467": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "LT", "path": "12" }, "2468": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ISZERO", "path": "12" }, "2469": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH2", "path": "12", "value": "0x9B8" }, "2472": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMPI", "path": "12" }, "2473": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2474": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2475": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2476": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MLOAD", "path": "12" }, "2477": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP4", "path": "12" }, "2478": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP3", "path": "12" }, "2479": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2480": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MSTORE", "path": "12" }, "2481": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "2483": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2484": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH2", "path": "12", "value": "0x9A0" }, "2487": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMP", "path": "12" }, "2488": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMPDEST", "path": "12" }, "2489": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2490": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2491": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2492": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2493": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP1", "path": "12" }, "2494": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2495": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP1", "path": "12" }, "2496": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2497": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2498": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP1", "path": "12" }, "2499": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x1F" }, "2501": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "AND", "path": "12" }, "2502": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2503": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ISZERO", "path": "12" }, "2504": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH2", "path": "12", "value": "0x9E5" }, "2507": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMPI", "path": "12" }, "2508": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2509": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP3", "path": "12" }, "2510": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SUB", "path": "12" }, "2511": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2512": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MLOAD", "path": "12" }, "2513": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x1" }, "2515": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP4", "path": "12" }, "2516": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "2518": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SUB", "path": "12" }, "2519": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH2", "path": "12", "value": "0x100" }, "2522": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "EXP", "path": "12" }, "2523": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SUB", "path": "12" }, "2524": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "NOT", "path": "12" }, "2525": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "AND", "path": "12" }, "2526": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP2", "path": "12" }, "2527": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MSTORE", "path": "12" }, "2528": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "2530": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "ADD", "path": "12" }, "2531": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP2", "path": "12" }, "2532": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2533": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMPDEST", "path": "12" }, "2534": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2535": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP3", "path": "12" }, "2536": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2537": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2538": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "POP", "path": "12" }, "2539": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "PUSH1", "path": "12", "value": "0x40" }, "2541": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "MLOAD", "path": "12" }, "2542": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "DUP1", "path": "12" }, "2543": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP2", "path": "12" }, "2544": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SUB", "path": "12" }, "2545": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "SWAP1", "path": "12" }, "2546": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "REVERT", "path": "12" }, "2547": { "fn": "SafeMath.sub", "offset": [ 1851, 1880 ], "op": "JUMPDEST", "path": "12" }, "2548": { "op": "POP" }, "2549": { "op": "POP" }, "2550": { "op": "POP" }, "2551": { "fn": "SafeMath.sub", "offset": [ 1902, 1907 ], "op": "SWAP1", "path": "12" }, "2552": { "fn": "SafeMath.sub", "offset": [ 1902, 1907 ], "op": "SUB", "path": "12" }, "2553": { "fn": "SafeMath.sub", "offset": [ 1902, 1907 ], "op": "SWAP1", "path": "12" }, "2554": { "fn": "SafeMath.sub", "jump": "o", "offset": [ 1746, 1933 ], "op": "JUMP", "path": "12" }, "2555": { "fn": "ERC20._mint", "offset": [ 7820, 8190 ], "op": "JUMPDEST", "path": "3" }, "2556": { "op": "PUSH1", "value": "0x1" }, "2558": { "op": "PUSH1", "value": "0x1" }, "2560": { "op": "PUSH1", "value": "0xA0" }, "2562": { "op": "SHL" }, "2563": { "op": "SUB" }, "2564": { "fn": "ERC20._mint", "offset": [ 7903, 7924 ], "op": "DUP3", "path": "3", "statement": 32 }, "2565": { "branch": 47, "fn": "ERC20._mint", "offset": [ 7903, 7924 ], "op": "AND", "path": "3" }, "2566": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH2", "path": "3", "value": "0xA56" }, "2569": { "branch": 47, "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "JUMPI", "path": "3" }, "2570": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2572": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "DUP1", "path": "3" }, "2573": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "MLOAD", "path": "3" }, "2574": { "op": "PUSH3", "value": "0x461BCD" }, "2578": { "op": "PUSH1", "value": "0xE5" }, "2580": { "op": "SHL" }, "2581": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "DUP2", "path": "3" }, "2582": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "MSTORE", "path": "3" }, "2583": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2585": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "2587": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "DUP3", "path": "3" }, "2588": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "ADD", "path": "3" }, "2589": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "MSTORE", "path": "3" }, "2590": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH1", "path": "3", "value": "0x1F" }, "2592": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH1", "path": "3", "value": "0x24" }, "2594": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "DUP3", "path": "3" }, "2595": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "ADD", "path": "3" }, "2596": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "MSTORE", "path": "3" }, "2597": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH32", "path": "3", "value": "0x45524332303A206D696E7420746F20746865207A65726F206164647265737300" }, "2630": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH1", "path": "3", "value": "0x44" }, "2632": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "DUP3", "path": "3" }, "2633": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "ADD", "path": "3" }, "2634": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "MSTORE", "path": "3" }, "2635": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "SWAP1", "path": "3" }, "2636": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "MLOAD", "path": "3" }, "2637": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "SWAP1", "path": "3" }, "2638": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "DUP2", "path": "3" }, "2639": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "SWAP1", "path": "3" }, "2640": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "SUB", "path": "3" }, "2641": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "PUSH1", "path": "3", "value": "0x64" }, "2643": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "ADD", "path": "3" }, "2644": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "SWAP1", "path": "3" }, "2645": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "REVERT", "path": "3" }, "2646": { "fn": "ERC20._mint", "offset": [ 7895, 7960 ], "op": "JUMPDEST", "path": "3" }, "2647": { "fn": "ERC20._mint", "offset": [ 7971, 8020 ], "op": "PUSH2", "path": "3", "statement": 33, "value": "0xA62" }, "2650": { "fn": "ERC20._mint", "offset": [ 8000, 8001 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2652": { "fn": "ERC20._mint", "offset": [ 8004, 8011 ], "op": "DUP4", "path": "3" }, "2653": { "fn": "ERC20._mint", "offset": [ 8013, 8019 ], "op": "DUP4", "path": "3" }, "2654": { "fn": "ERC20._mint", "offset": [ 7971, 7991 ], "op": "PUSH2", "path": "3", "value": "0x48F" }, "2657": { "fn": "ERC20._mint", "jump": "i", "offset": [ 7971, 8020 ], "op": "JUMP", "path": "3" }, "2658": { "fn": "ERC20._mint", "offset": [ 7971, 8020 ], "op": "JUMPDEST", "path": "3" }, "2659": { "fn": "ERC20._mint", "offset": [ 8046, 8058 ], "op": "PUSH1", "path": "3", "statement": 34, "value": "0x2" }, "2661": { "fn": "ERC20._mint", "offset": [ 8046, 8058 ], "op": "SLOAD", "path": "3" }, "2662": { "fn": "ERC20._mint", "offset": [ 8046, 8070 ], "op": "PUSH2", "path": "3", "value": "0xA6F" }, "2665": { "fn": "ERC20._mint", "offset": [ 8046, 8070 ], "op": "SWAP1", "path": "3" }, "2666": { "fn": "ERC20._mint", "offset": [ 8063, 8069 ], "op": "DUP3", "path": "3" }, "2667": { "fn": "ERC20._mint", "offset": [ 8046, 8062 ], "op": "PUSH2", "path": "3", "value": "0x6B8" }, "2670": { "fn": "ERC20._mint", "jump": "i", "offset": [ 8046, 8070 ], "op": "JUMP", "path": "3" }, "2671": { "fn": "ERC20._mint", "offset": [ 8046, 8070 ], "op": "JUMPDEST", "path": "3" }, "2672": { "fn": "ERC20._mint", "offset": [ 8031, 8043 ], "op": "PUSH1", "path": "3", "value": "0x2" }, "2674": { "fn": "ERC20._mint", "offset": [ 8031, 8070 ], "op": "SSTORE", "path": "3" }, "2675": { "op": "PUSH1", "value": "0x1" }, "2677": { "op": "PUSH1", "value": "0x1" }, "2679": { "op": "PUSH1", "value": "0xA0" }, "2681": { "op": "SHL" }, "2682": { "op": "SUB" }, "2683": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "DUP3", "path": "3", "statement": 35 }, "2684": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "AND", "path": "3" }, "2685": { "fn": "ERC20._mint", "offset": [ 8101, 8110 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2687": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "SWAP1", "path": "3" }, "2688": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "DUP2", "path": "3" }, "2689": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "MSTORE", "path": "3" }, "2690": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2692": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "DUP2", "path": "3" }, "2693": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "SWAP1", "path": "3" }, "2694": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "MSTORE", "path": "3" }, "2695": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2697": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "SWAP1", "path": "3" }, "2698": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "KECCAK256", "path": "3" }, "2699": { "fn": "ERC20._mint", "offset": [ 8101, 8119 ], "op": "SLOAD", "path": "3" }, "2700": { "fn": "ERC20._mint", "offset": [ 8101, 8131 ], "op": "PUSH2", "path": "3", "value": "0xA95" }, "2703": { "fn": "ERC20._mint", "offset": [ 8101, 8131 ], "op": "SWAP1", "path": "3" }, "2704": { "fn": "ERC20._mint", "offset": [ 8124, 8130 ], "op": "DUP3", "path": "3" }, "2705": { "fn": "ERC20._mint", "offset": [ 8101, 8123 ], "op": "PUSH2", "path": "3", "value": "0x6B8" }, "2708": { "fn": "ERC20._mint", "jump": "i", "offset": [ 8101, 8131 ], "op": "JUMP", "path": "3" }, "2709": { "fn": "ERC20._mint", "offset": [ 8101, 8131 ], "op": "JUMPDEST", "path": "3" }, "2710": { "op": "PUSH1", "value": "0x1" }, "2712": { "op": "PUSH1", "value": "0x1" }, "2714": { "op": "PUSH1", "value": "0xA0" }, "2716": { "op": "SHL" }, "2717": { "op": "SUB" }, "2718": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "DUP4", "path": "3" }, "2719": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "AND", "path": "3" }, "2720": { "fn": "ERC20._mint", "offset": [ 8080, 8089 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2722": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "DUP2", "path": "3" }, "2723": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "DUP2", "path": "3" }, "2724": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "MSTORE", "path": "3" }, "2725": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2727": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "DUP2", "path": "3" }, "2728": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "DUP2", "path": "3" }, "2729": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "MSTORE", "path": "3" }, "2730": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2732": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "DUP1", "path": "3" }, "2733": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "DUP4", "path": "3" }, "2734": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "KECCAK256", "path": "3" }, "2735": { "fn": "ERC20._mint", "offset": [ 8080, 8131 ], "op": "SWAP5", "path": "3" }, "2736": { "fn": "ERC20._mint", "offset": [ 8080, 8131 ], "op": "SWAP1", "path": "3" }, "2737": { "fn": "ERC20._mint", "offset": [ 8080, 8131 ], "op": "SWAP5", "path": "3" }, "2738": { "fn": "ERC20._mint", "offset": [ 8080, 8131 ], "op": "SSTORE", "path": "3" }, "2739": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "DUP4", "path": "3", "statement": 36 }, "2740": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "MLOAD", "path": "3" }, "2741": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "DUP6", "path": "3" }, "2742": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "DUP2", "path": "3" }, "2743": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "MSTORE", "path": "3" }, "2744": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "SWAP4", "path": "3" }, "2745": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "MLOAD", "path": "3" }, "2746": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "SWAP3", "path": "3" }, "2747": { "fn": "ERC20._mint", "offset": [ 8080, 8098 ], "op": "SWAP4", "path": "3" }, "2748": { "fn": "ERC20._mint", "offset": [ 8080, 8089 ], "op": "SWAP2", "path": "3" }, "2749": { "fn": "ERC20._mint", "offset": [ 8080, 8089 ], "op": "SWAP3", "path": "3" }, "2750": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "PUSH32", "path": "3", "value": "0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF" }, "2783": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "SWAP3", "path": "3" }, "2784": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "DUP2", "path": "3" }, "2785": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "SWAP1", "path": "3" }, "2786": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "SUB", "path": "3" }, "2787": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "SWAP1", "path": "3" }, "2788": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "SWAP2", "path": "3" }, "2789": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "ADD", "path": "3" }, "2790": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "SWAP1", "path": "3" }, "2791": { "fn": "ERC20._mint", "offset": [ 8146, 8183 ], "op": "LOG3", "path": "3" }, "2792": { "fn": "ERC20._mint", "offset": [ 7820, 8190 ], "op": "POP", "path": "3" }, "2793": { "fn": "ERC20._mint", "offset": [ 7820, 8190 ], "op": "POP", "path": "3" }, "2794": { "fn": "ERC20._mint", "jump": "o", "offset": [ 7820, 8190 ], "op": "JUMP", "path": "3" }, "2795": { "fn": "ERC20._burn", "offset": [ 8509, 8919 ], "op": "JUMPDEST", "path": "3" }, "2796": { "op": "PUSH1", "value": "0x1" }, "2798": { "op": "PUSH1", "value": "0x1" }, "2800": { "op": "PUSH1", "value": "0xA0" }, "2802": { "op": "SHL" }, "2803": { "op": "SUB" }, "2804": { "fn": "ERC20._burn", "offset": [ 8592, 8613 ], "op": "DUP3", "path": "3", "statement": 37 }, "2805": { "branch": 48, "fn": "ERC20._burn", "offset": [ 8592, 8613 ], "op": "AND", "path": "3" }, "2806": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH2", "path": "3", "value": "0xB30" }, "2809": { "branch": 48, "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "JUMPI", "path": "3" }, "2810": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2812": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "MLOAD", "path": "3" }, "2813": { "op": "PUSH3", "value": "0x461BCD" }, "2817": { "op": "PUSH1", "value": "0xE5" }, "2819": { "op": "SHL" }, "2820": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP2", "path": "3" }, "2821": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "MSTORE", "path": "3" }, "2822": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x4" }, "2824": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "ADD", "path": "3" }, "2825": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP1", "path": "3" }, "2826": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP1", "path": "3" }, "2827": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2829": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "ADD", "path": "3" }, "2830": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP3", "path": "3" }, "2831": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP2", "path": "3" }, "2832": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "SUB", "path": "3" }, "2833": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP3", "path": "3" }, "2834": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "MSTORE", "path": "3" }, "2835": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x21" }, "2837": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP2", "path": "3" }, "2838": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "MSTORE", "path": "3" }, "2839": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2841": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "ADD", "path": "3" }, "2842": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP1", "path": "3" }, "2843": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH2", "path": "3", "value": "0xCDF" }, "2846": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x21" }, "2848": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "SWAP2", "path": "3" }, "2849": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "CODECOPY", "path": "3" }, "2850": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2852": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "ADD", "path": "3" }, "2853": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "SWAP2", "path": "3" }, "2854": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "POP", "path": "3" }, "2855": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "POP", "path": "3" }, "2856": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2858": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "MLOAD", "path": "3" }, "2859": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "DUP1", "path": "3" }, "2860": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "SWAP2", "path": "3" }, "2861": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "SUB", "path": "3" }, "2862": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "SWAP1", "path": "3" }, "2863": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "REVERT", "path": "3" }, "2864": { "fn": "ERC20._burn", "offset": [ 8584, 8651 ], "op": "JUMPDEST", "path": "3" }, "2865": { "fn": "ERC20._burn", "offset": [ 8662, 8711 ], "op": "PUSH2", "path": "3", "statement": 38, "value": "0xB3C" }, "2868": { "fn": "ERC20._burn", "offset": [ 8683, 8690 ], "op": "DUP3", "path": "3" }, "2869": { "fn": "ERC20._burn", "offset": [ 8700, 8701 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2871": { "fn": "ERC20._burn", "offset": [ 8704, 8710 ], "op": "DUP4", "path": "3" }, "2872": { "fn": "ERC20._burn", "offset": [ 8662, 8682 ], "op": "PUSH2", "path": "3", "value": "0x48F" }, "2875": { "fn": "ERC20._burn", "jump": "i", "offset": [ 8662, 8711 ], "op": "JUMP", "path": "3" }, "2876": { "fn": "ERC20._burn", "offset": [ 8662, 8711 ], "op": "JUMPDEST", "path": "3" }, "2877": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH2", "path": "3", "statement": 39, "value": "0xB79" }, "2880": { "fn": "ERC20._burn", "offset": [ 8766, 8772 ], "op": "DUP2", "path": "3" }, "2881": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2883": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "MLOAD", "path": "3" }, "2884": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "DUP1", "path": "3" }, "2885": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH1", "path": "3", "value": "0x60" }, "2887": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "ADD", "path": "3" }, "2888": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2890": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "MSTORE", "path": "3" }, "2891": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "DUP1", "path": "3" }, "2892": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH1", "path": "3", "value": "0x22" }, "2894": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "DUP2", "path": "3" }, "2895": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "MSTORE", "path": "3" }, "2896": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2898": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "ADD", "path": "3" }, "2899": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH2", "path": "3", "value": "0xC4D" }, "2902": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "PUSH1", "path": "3", "value": "0x22" }, "2904": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "SWAP2", "path": "3" }, "2905": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "CODECOPY", "path": "3" }, "2906": { "op": "PUSH1", "value": "0x1" }, "2908": { "op": "PUSH1", "value": "0x1" }, "2910": { "op": "PUSH1", "value": "0xA0" }, "2912": { "op": "SHL" }, "2913": { "op": "SUB" }, "2914": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "DUP6", "path": "3" }, "2915": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "AND", "path": "3" }, "2916": { "fn": "ERC20._burn", "offset": [ 8743, 8752 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2918": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "SWAP1", "path": "3" }, "2919": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "DUP2", "path": "3" }, "2920": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "MSTORE", "path": "3" }, "2921": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2923": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "DUP2", "path": "3" }, "2924": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "SWAP1", "path": "3" }, "2925": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "MSTORE", "path": "3" }, "2926": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2928": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "SWAP1", "path": "3" }, "2929": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "KECCAK256", "path": "3" }, "2930": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "SLOAD", "path": "3" }, "2931": { "fn": "ERC20._burn", "offset": [ 8743, 8761 ], "op": "SWAP2", "path": "3" }, "2932": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "SWAP1", "path": "3" }, "2933": { "fn": "ERC20._burn", "offset": [ 8743, 8765 ], "op": "PUSH2", "path": "3", "value": "0x964" }, "2936": { "fn": "ERC20._burn", "jump": "i", "offset": [ 8743, 8811 ], "op": "JUMP", "path": "3" }, "2937": { "fn": "ERC20._burn", "offset": [ 8743, 8811 ], "op": "JUMPDEST", "path": "3" }, "2938": { "op": "PUSH1", "value": "0x1" }, "2940": { "op": "PUSH1", "value": "0x1" }, "2942": { "op": "PUSH1", "value": "0xA0" }, "2944": { "op": "SHL" }, "2945": { "op": "SUB" }, "2946": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "DUP4", "path": "3" }, "2947": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "AND", "path": "3" }, "2948": { "fn": "ERC20._burn", "offset": [ 8722, 8731 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2950": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "SWAP1", "path": "3" }, "2951": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "DUP2", "path": "3" }, "2952": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "MSTORE", "path": "3" }, "2953": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "2955": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "DUP2", "path": "3" }, "2956": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "SWAP1", "path": "3" }, "2957": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "MSTORE", "path": "3" }, "2958": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "PUSH1", "path": "3", "value": "0x40" }, "2960": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "SWAP1", "path": "3" }, "2961": { "fn": "ERC20._burn", "offset": [ 8722, 8740 ], "op": "KECCAK256", "path": "3" }, "2962": { "fn": "ERC20._burn", "offset": [ 8722, 8811 ], "op": "SSTORE", "path": "3" }, "2963": { "fn": "ERC20._burn", "offset": [ 8836, 8848 ], "op": "PUSH1", "path": "3", "statement": 40, "value": "0x2" }, "2965": { "fn": "ERC20._burn", "offset": [ 8836, 8848 ], "op": "SLOAD", "path": "3" }, "2966": { "fn": "ERC20._burn", "offset": [ 8836, 8860 ], "op": "PUSH2", "path": "3", "value": "0xB9F" }, "2969": { "fn": "ERC20._burn", "offset": [ 8836, 8860 ], "op": "SWAP1", "path": "3" }, "2970": { "fn": "ERC20._burn", "offset": [ 8853, 8859 ], "op": "DUP3", "path": "3" }, "2971": { "fn": "ERC20._burn", "offset": [ 8836, 8852 ], "op": "PUSH2", "path": "3", "value": "0xBE7" }, "2974": { "fn": "ERC20._burn", "jump": "i", "offset": [ 8836, 8860 ], "op": "JUMP", "path": "3" }, "2975": { "fn": "ERC20._burn", "offset": [ 8836, 8860 ], "op": "JUMPDEST", "path": "3" }, "2976": { "fn": "ERC20._burn", "offset": [ 8821, 8833 ], "op": "PUSH1", "path": "3", "value": "0x2" }, "2978": { "fn": "ERC20._burn", "offset": [ 8821, 8860 ], "op": "SSTORE", "path": "3" }, "2979": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "PUSH1", "path": "3", "statement": 41, "value": "0x40" }, "2981": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "DUP1", "path": "3" }, "2982": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "MLOAD", "path": "3" }, "2983": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "DUP3", "path": "3" }, "2984": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "DUP2", "path": "3" }, "2985": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "MSTORE", "path": "3" }, "2986": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "SWAP1", "path": "3" }, "2987": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "MLOAD", "path": "3" }, "2988": { "fn": "ERC20._burn", "offset": [ 8901, 8902 ], "op": "PUSH1", "path": "3", "value": "0x0" }, "2990": { "fn": "ERC20._burn", "offset": [ 8901, 8902 ], "op": "SWAP2", "path": "3" }, "2991": { "op": "PUSH1", "value": "0x1" }, "2993": { "op": "PUSH1", "value": "0x1" }, "2995": { "op": "PUSH1", "value": "0xA0" }, "2997": { "op": "SHL" }, "2998": { "op": "SUB" }, "2999": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "DUP6", "path": "3" }, "3000": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "AND", "path": "3" }, "3001": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "SWAP2", "path": "3" }, "3002": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "PUSH32", "path": "3", "value": "0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF" }, "3035": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "SWAP2", "path": "3" }, "3036": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "DUP2", "path": "3" }, "3037": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "SWAP1", "path": "3" }, "3038": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "SUB", "path": "3" }, "3039": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "PUSH1", "path": "3", "value": "0x20" }, "3041": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "ADD", "path": "3" }, "3042": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "SWAP1", "path": "3" }, "3043": { "fn": "ERC20._burn", "offset": [ 8875, 8912 ], "op": "LOG3", "path": "3" }, "3044": { "fn": "ERC20._burn", "offset": [ 8509, 8919 ], "op": "POP", "path": "3" }, "3045": { "fn": "ERC20._burn", "offset": [ 8509, 8919 ], "op": "POP", "path": "3" }, "3046": { "fn": "ERC20._burn", "jump": "o", "offset": [ 8509, 8919 ], "op": "JUMP", "path": "3" }, "3047": { "fn": "SafeMath.sub", "offset": [ 1321, 1455 ], "op": "JUMPDEST", "path": "12" }, "3048": { "fn": "SafeMath.sub", "offset": [ 1379, 1386 ], "op": "PUSH1", "path": "12", "value": "0x0" }, "3050": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "PUSH2", "path": "12", "statement": 42, "value": "0x712" }, "3053": { "fn": "SafeMath.sub", "offset": [ 1409, 1410 ], "op": "DUP4", "path": "12" }, "3054": { "fn": "SafeMath.sub", "offset": [ 1412, 1413 ], "op": "DUP4", "path": "12" }, "3055": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "PUSH1", "path": "12", "value": "0x40" }, "3057": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "MLOAD", "path": "12" }, "3058": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "DUP1", "path": "12" }, "3059": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "PUSH1", "path": "12", "value": "0x40" }, "3061": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "ADD", "path": "12" }, "3062": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "PUSH1", "path": "12", "value": "0x40" }, "3064": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "MSTORE", "path": "12" }, "3065": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "DUP1", "path": "12" }, "3066": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "PUSH1", "path": "12", "value": "0x1E" }, "3068": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "DUP2", "path": "12" }, "3069": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "MSTORE", "path": "12" }, "3070": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "PUSH1", "path": "12", "value": "0x20" }, "3072": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "ADD", "path": "12" }, "3073": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "PUSH32", "path": "12", "value": "0x536166654D6174683A207375627472616374696F6E206F766572666C6F770000" }, "3106": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "DUP2", "path": "12" }, "3107": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "MSTORE", "path": "12" }, "3108": { "fn": "SafeMath.sub", "offset": [ 1405, 1448 ], "op": "POP", "path": "12" }, "3109": { "fn": "SafeMath.sub", "offset": [ 1405, 1408 ], "op": "PUSH2", "path": "12", "value": "0x964" }, "3112": { "fn": "SafeMath.sub", "jump": "i", "offset": [ 1405, 1448 ], "op": "JUMP", "path": "12" } }, "sha1": "0da0cdb26e7a68b3659b1cec22c9c49a42259a3e", "source": "// SPDX-License-Identifier:Apache-2.0\n//------------------------------------------------------------------------------\n//\n// Copyright 2020 Fetch.AI Limited\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//------------------------------------------------------------------------------\n\npragma solidity ^0.6.0;\n\nimport \"./ERC20Mock.sol\";\n\ncontract FetERC20Mock is ERC20Mock\n{\n constructor (\n string memory name,\n string memory symbol,\n uint256 initialSupply,\n uint8 decimals_\n )\n public payable\n ERC20Mock(name, symbol, msg.sender, initialSupply)\n {\n _setupDecimals(decimals_);\n }\n}\n", "sourceMap": "872:308:6:-:0;;;913:265;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;913:265:6;;;;;;;;;;-1:-1:-1;913:265:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;913:265:6;;;;;;;;;;-1:-1:-1;913:265:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;913:265:6;;;;;;;;;;;;;;2070:12:3;;913:265:6;;-1:-1:-1;913:265:6;-1:-1:-1;1091:4:6;;1097:6;;1105:10;;913:265;;1091:4;;1097:6;;2070:12:3;;:5;;:12;;;;:::i;:::-;-1:-1:-1;2092:16:3;;;;:7;;:16;;;;;:::i;:::-;-1:-1:-1;;2118:9:3;:14;;-1:-1:-1;;2118:14:3;2130:2;2118:14;;;-1:-1:-1;327:37:4::1;333:14:::0;349;327:5:::1;:37::i;:::-;141:230:::0;;;;1146:25:6::1;1161:9;1146:14;;;:25;;:::i;:::-;913:265:::0;;;;872:308;;7820:370:3;-1:-1:-1;;;;;7903:21:3;;7895:65;;;;;-1:-1:-1;;;7895:65:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;7971:49;8000:1;8004:7;8013:6;7971:20;:49::i;:::-;8046:24;8063:6;8046:12;;:16;;;;;;:24;;;;:::i;:::-;8031:12;:39;-1:-1:-1;;;;;8101:18:3;;:9;:18;;;;;;;;;;;;:30;;8124:6;;8101:22;;;;;:30;;:::i;:::-;-1:-1:-1;;;;;8080:18:3;;:9;:18;;;;;;;;;;;:51;;;;8146:37;;;;;;;8080:18;;:9;;8146:37;;;;;;;;;;7820:370;;:::o;10007:88::-;10067:9;:21;;-1:-1:-1;;10067:21:3;;;;;;;;;;;;10007:88::o;10682:92::-;;;;:::o;874:176:12:-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;1042:1;874:176;-1:-1:-1;;;874:176:12:o;872:308:6:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;872:308:6;;;-1:-1:-1;872:308:6;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;", "sourcePath": "contracts/FetERC20Mock.sol", "type": "contract" } ================================================ FILE: packages/fetchai/contracts/fet_erc20/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the FET ERC20 contract definition.""" import logging from aea_ledger_ethereum import EthereumApi from aea.common import Address, JSONLike from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi _default_logger = logging.getLogger("aea.packages.fetchai.contracts.fet_erc20.contract") PUBLIC_ID = PublicId.from_str("fetchai/fet_erc20:0.9.2") class FetERC20(Contract): """The FetERC20 contract class which acts as a bridge between AEA framework and ERC20 ABI.""" contract_id = PUBLIC_ID @classmethod def get_approve_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, spender: Address, amount: int, gas: int = 0, ) -> JSONLike: """ Get transaction to approve oracle client contract transactions on behalf of sender. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param from_address: the address of the approver. :param spender: the address approved to spend on behalf of sender. :param amount: the amount approved to be spent. :param gas: the gas limit for the transaction. :return: the approve transaction """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) function = instance.functions.approve intermediate = function(spender, amount) tx = intermediate.buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError @classmethod def get_transfer_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, receiver: Address, amount: int, gas: int = 0, ) -> JSONLike: """ Get transaction to transfer tokens to an account :param ledger_api: the ledger apis. :param contract_address: the contract address. :param from_address: the address of the sender. :param receiver: the address to which to transfer tokens. :param amount: the amount of tokens to transfer. :param gas: the gas limit for the transaction. :return: the transfer transaction """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) function = instance.functions.transfer intermediate = function(receiver, amount) tx = intermediate.buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError ================================================ FILE: packages/fetchai/contracts/fet_erc20/contract.yaml ================================================ name: fet_erc20 author: fetchai version: 0.9.2 type: contract description: The fet_erc20 contract contains a mock Fetch ERC20 contract license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmWVwuKSyna278svBZ18tdxHtVfhHuTV6pZ79UH5gmaNes __init__.py: QmRCPaWpwzAgESvZPpgyNQwSHAfDcU9rnEH2PauuLgfVor build/FetERC20Mock.json: QmPKt6BUTUotWS7mtdHLxfg7dEw3cATzNojNBiJ1nifwF9 contract.py: QmZBS2cDYWHQVdBogFRpCPUjqQjXCDbkz5mzP1XprLrYAv fingerprint_ignore_patterns: [] class_name: FetERC20 contract_interface_paths: ethereum: build/FetERC20Mock.json dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/contracts/oracle/README.md ================================================ # Fetch Oracle Contract ## Description This contract package is used to interface with a Fetch Oracle contract, which makes real-world data available to a smart-contract-capable blockchain. ## Functions - `grantRole(oracle_role, oracle_address)`: grant oracle role to address `oracle_address` - `updateOracleValue(value, decimals, txExpirationBlock)`: update oracle contract value to `value` with `decimals` decimal places, to expire at block `txExpirationBlock` ================================================ FILE: packages/fetchai/contracts/oracle/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the support resources for the Fetch oracle contract.""" ================================================ FILE: packages/fetchai/contracts/oracle/build/FetchOracle.json ================================================ { "contractName": "FetchOracle", "abi": [ { "inputs": [ { "internalType": "address", "name": "ERC20Address", "type": "address" }, { "internalType": "uint256", "name": "initialFee", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "address", "name": "targetAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "AccruedFeesWithdrawal", "type": "event" }, { "anonymous": false, "inputs": [], "name": "ContractDeleted", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "address", "name": "targetAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "ExcessTokenWithdrawal", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "fee", "type": "uint256" } ], "name": "FeeUpdated", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "atBlock", "type": "uint256" } ], "name": "OracleValueUpdated", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "sinceBlock", "type": "uint256" } ], "name": "Pause", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "previousAdminRole", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "newAdminRole", "type": "bytes32" } ], "name": "RoleAdminChanged", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } ], "name": "RoleGranted", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } ], "name": "RoleRevoked", "type": "event" }, { "inputs": [], "name": "DEFAULT_ADMIN_ROLE", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "DELEGATE_ROLE", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "DELETE_PROTECTION_PERIOD", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "ORACLE_ROLE", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_accruedFeesAmount", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_earliestDelete", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_fee", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_feeCurrency", "outputs": [ { "internalType": "enum FetchOracle.FeeCurrency", "name": "", "type": "uint8" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_pausedSinceBlock", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_token", "outputs": [ { "internalType": "contract IERC20", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" } ], "name": "getRoleAdmin", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "uint256", "name": "index", "type": "uint256" } ], "name": "getRoleMember", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" } ], "name": "getRoleMemberCount", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "grantRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "hasRole", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "renounceRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "revokeRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "uint8", "name": "decimals", "type": "uint8" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "updateOracleValue", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "queryOracleValue", "outputs": [ { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "uint8", "name": "decimals", "type": "uint8" }, { "internalType": "uint256", "name": "updatedAtEthBlockNumber", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "fee", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "setFee", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "block_number", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "pauseSince", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address payable", "name": "targetAddress", "type": "address" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdrawAccruedFees", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address payable", "name": "targetAddress", "type": "address" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdrawExcessTokens", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address payable", "name": "payoutAddress", "type": "address" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "deleteContract", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "oracleValueLastUpdated", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" } ], "metadata": "{\"compiler\":{\"version\":\"0.6.8+commit.0bbfe453\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ERC20Address\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"initialFee\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"AccruedFeesWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"ContractDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ExcessTokenWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"FeeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"atBlock\",\"type\":\"uint256\"}],\"name\":\"OracleValueUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"sinceBlock\",\"type\":\"uint256\"}],\"name\":\"Pause\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DELEGATE_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DELETE_PROTECTION_PERIOD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ORACLE_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_accruedFeesAmount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_earliestDelete\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_feeCurrency\",\"outputs\":[{\"internalType\":\"enum FetchOracle.FeeCurrency\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_pausedSinceBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_token\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"payoutAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"deleteContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oracleValueLastUpdated\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"block_number\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"pauseSince\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"queryOracleValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"updatedAtEthBlockNumber\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"updateOracleValue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdrawAccruedFees\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdrawExcessTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"The contract *INTENTIONALLY* does not contain the `receive()` or fallback functions in order to ensure that all unsolicited direct ETH transfers in to this contract (in direct Transaction) will fail. Only way how to make successful fee transfer in ETH currency (*IF* contract is initialised for ETH as fee currency), is to make transfer ETH to caller(client) contract and client contract then is responsible for further rogrammatic* (= *not* via Tx) transfer of fee to this this contract.\",\"methods\":{\"constructor\":{\"params\":{\"ERC20Address\":\"address of the ERC20 contract\"}},\"deleteContract(address,uint256)\":{\"details\":\"Delete the contract, transfers the remaining token and ether balance to the specified payoutAddressowner only + only on or after `_earliestDelete` block\",\"params\":{\"payoutAddress\":\"address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\"}},\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. * To change a role's admin, use {_setRoleAdmin}.\"},\"getRoleMember(bytes32,uint256)\":{\"details\":\"Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. * Role bearers are not sorted in any particular way, and their ordering may change at any point. * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information.\"},\"getRoleMemberCount(bytes32)\":{\"details\":\"Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. * If `account` had not been already granted `role`, emits a {RoleGranted} event. * Requirements: * - the caller must have ``role``'s admin role.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"pauseSince(uint256,uint256)\":{\"details\":\"Pause the non-administrative interaction with the contractOwners only\",\"params\":{\"block_number\":\"disallow non-admin. interactions with contract for a _getBlockNumber() >= block_number\"}},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. * Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). * If the calling account had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must be `account`.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. * If `account` had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must have ``role``'s admin role.\"},\"setFee(uint256,uint256)\":{\"details\":\"Pause the non-administrative interaction with the contractOwners only\",\"params\":{\"fee\":\"- value of fee\",\"txExpirationBlock\":\"- block number defined by Tx sender beyond which transaction becomes invalid\"}},\"withdrawAccruedFees(address,uint256)\":{\"details\":\"Withdraw whole balance of all fees accrued in the contract so far.Owners only\",\"params\":{\"targetAddress\":\": address to send tokens to\",\"txExpirationBlock\":\": block number until which is the transaction valid (inclusive). When transaction is processed after this block, it fails.\"}},\"withdrawExcessTokens(address,uint256)\":{\"details\":\"Withdraw \\\"excess\\\" tokens, which were sent to contract address directly via direct ERC20 transfer(...) or transferFrom(...), without interacting with API of this (FetchOracle) contract, what could be done only by mistake. Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such \\\"excess\\\" tokens out of contract.\",\"params\":{\"targetAddress\":\": address to send tokens to\",\"txExpirationBlock\":\": block number until which is the transaction valid (inclusive). When transaction is processed after this block, it fails.\"}}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/home/james/Code/cosmos-drb-oracle/ethereum/contracts/FetchOracle.sol\":\"FetchOracle\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/james/Code/cosmos-drb-oracle/ethereum/contracts/FetchOracle.sol\":{\"keccak256\":\"0x9692300de48590be183d31bc7b918e25b1defc6a685a6da9483cac3ba69c0a76\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://72d7f74f5784f94c735e2e47c822fa5d24a6dc47d40bef17d22f115f41cc8940\",\"dweb:/ipfs/QmeuyFTWyzcx8rULNoHMYJmqJXPauHMnBgLckyh2XaPNEV\"]},\"/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/GSN/Context.sol\":{\"keccak256\":\"0xdb26cbf4d028490f49831a7865c2fe1b28db44b535ca8d343785a3b768aae183\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://840b14ce0315c47d49ba328f1f9fa7654ded1c9e1559e6c5e777a7b2dc28bf0a\",\"dweb:/ipfs/QmTLLabn4wcfGro9LEmUXUN2nwKqZSotXMvjDCLXEnLtZP\"]},\"/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/access/AccessControl.sol\":{\"keccak256\":\"0x92f7900d382761c7faefeaced81c6b4f1aae909ed0551803bfe8f27101956360\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://407c0864143968542e5cf5aa7556916d2cf292b201e3dadb65662e9a3aa24187\",\"dweb:/ipfs/QmSnXzYAUaGLGr7uofRbgQraTJvatbjQLBPhyYiMd18oUJ\"]},\"/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5\",\"dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV\"]},\"/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017\",\"dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5\"]},\"/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/utils/Address.sol\":{\"keccak256\":\"0xf5fa8cbdffa5ef8be49b246b5628facc30b71707e78a45d80d93b64eff3fe390\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://774e78a9ff32792cc95db4d2ceaf3a7965bb7f0bea5e6cb7cff182b450d44b37\",\"dweb:/ipfs/QmRRMC4uj7eAcLW7chynA3sNEYULMFazdLwQHKHQPyzAbA\"]},\"/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/utils/EnumerableSet.sol\":{\"keccak256\":\"0xb2a11b236f073662f5a196995863f51c11d006bf7c3de158b316dfa1506c4b79\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8651649cf0b9efa18c3b01c030276fa320d41adbdc286833417e7f36e357b2f3\",\"dweb:/ipfs/QmafhM2Nd1aP43QVB1eRRZaqRXQKswNfQcWi8U8xjrxCfN\"]}},\"version\":1}", "bytecode": "0x60806040523480156200001157600080fd5b5060405162001bc538038062001bc5833981810160405260408110156200003757600080fd5b508051602090910151620000566000336001600160e01b03620000e016565b6001600160a01b03821662000078576009805460ff191660011790556200009e565b6009805460ff19169055600180546001600160a01b0319166001600160a01b0384161790555b620000cd6205a66d620000b96001600160e01b03620000f916565b620000fd60201b620010611790919060201c565b6002556000196003556004555062000277565b620000f582826001600160e01b036200016116565b5050565b4390565b60008282018381101562000158576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b60008281526020818152604090912062000186918390620010bb620001e3821b17901c565b15620000f5576200019f6001600160e01b036200020316565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600062000158836001600160a01b0384166001600160e01b036200020716565b3390565b60006200021e83836001600160e01b036200025f16565b62000256575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556200015b565b5060006200015b565b60009081526001919091016020526040902054151590565b61193e80620002876000396000f3fe6080604052600436106101665760003560e01c806391d14854116100d1578063c5b37c221161008a578063d83222ec11610064578063d83222ec14610527578063ecd0c0c31461053c578063f1209ef714610551578063f7e1c7481461056657610166565b8063c5b37c22146104af578063ca15c873146104c4578063d547741f146104ee57610166565b806391d14854146103b15780639608df4b146103fe578063993240a3146104375780639bc1890914610470578063a217fddf14610485578063c0ba241b1461049a57610166565b806336568abe1161012357806336568abe14610275578063372646bb146102ae57806352f7c988146102c357806353e052ac146102f357806359f9f7cc1461032c5780639010d07c1461036557610166565b806307e2cea51461016b578063248a9ca3146101925780632f2ff15d146101bc578063323f4501146101f757806332a1bd7014610230578063347908df14610245575b600080fd5b34801561017757600080fd5b5061018061058e565b60408051918252519081900360200190f35b34801561019e57600080fd5b50610180600480360360208110156101b557600080fd5b50356105b1565b3480156101c857600080fd5b506101f5600480360360408110156101df57600080fd5b50803590602001356001600160a01b03166105c6565b005b34801561020357600080fd5b506101f56004803603604081101561021a57600080fd5b506001600160a01b038135169060200135610632565b34801561023c57600080fd5b50610180610748565b34801561025157600080fd5b506101f56004803603604081101561026857600080fd5b508035906020013561074e565b34801561028157600080fd5b506101f56004803603604081101561029857600080fd5b50803590602001356001600160a01b031661086e565b3480156102ba57600080fd5b506101806108cf565b3480156102cf57600080fd5b506101f5600480360360408110156102e657600080fd5b50803590602001356108d6565b3480156102ff57600080fd5b506101f56004803603604081101561031657600080fd5b506001600160a01b0381351690602001356109db565b34801561033857600080fd5b506101f56004803603606081101561034f57600080fd5b5080359060ff6020820135169060400135610baa565b34801561037157600080fd5b506103956004803603604081101561038857600080fd5b5080359060200135610cd1565b604080516001600160a01b039092168252519081900360200190f35b3480156103bd57600080fd5b506103ea600480360360408110156103d457600080fd5b50803590602001356001600160a01b0316610cf8565b604080519115158252519081900360200190f35b34801561040a57600080fd5b506101f56004803603604081101561042157600080fd5b506001600160a01b038135169060200135610d16565b34801561044357600080fd5b5061044c610f74565b6040518082600181111561045c57fe5b60ff16815260200191505060405180910390f35b34801561047c57600080fd5b50610180610f7d565b34801561049157600080fd5b50610180610f83565b3480156104a657600080fd5b50610180610f88565b3480156104bb57600080fd5b50610180610fad565b3480156104d057600080fd5b50610180600480360360208110156104e757600080fd5b5035610fb3565b3480156104fa57600080fd5b506101f56004803603604081101561051157600080fd5b50803590602001356001600160a01b0316610fca565b34801561053357600080fd5b50610180611023565b34801561054857600080fd5b50610395611029565b34801561055d57600080fd5b50610180611038565b61056e61103e565b6040805193845260ff909216602084015282820152519081900360600190f35b604080516a4f5241434c455f524f4c4560a81b8152905190819003600b01902081565b60009081526020819052604090206002015490565b6000828152602081905260409020600201546105e9906105e46110d0565b610cf8565b6106245760405162461bcd60e51b815260040180806020018281038252602f815260200180611857602f913960400191505060405180910390fd5b61062e82826110d4565b5050565b808061063c611143565b1115610685576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b61068d611147565b6106de576040805162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f7420616e2061646d696e6973747261746f720000604482015290519081900360640190fd5b6005546106ea57610743565b6106f683600554611158565b600554604080516001600160a01b0386168152602081019290925280517fe6646aed3b0543182f7396de55e4a49dfbddb4e684eece060fbaa5b03ff275149281900390910190a160006005555b505050565b60025481565b8080610758611143565b11156107a1576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b6107a9611147565b806107db5750604080516c44454c45474154455f524f4c4560981b8152905190819003600d0190206107db9033610cf8565b6108165760405162461bcd60e51b81526004018080602001828103825260248152602001806118866024913960400191505060405180910390fd5b6000610820611143565b905080841061082f5783610831565b805b600381905560408051918252517f68b095021b1f40fe513109f513c66692f0b3219aee674a69f4efc57badb8201d9181900360200190a150505050565b6108766110d0565b6001600160a01b0316816001600160a01b0316146108c55760405162461bcd60e51b815260040180806020018281038252602f8152602001806118da602f913960400191505060405180910390fd5b61062e828261133f565b6205a66d81565b80806108e0611143565b1115610929576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b610931611147565b806109635750604080516c44454c45474154455f524f4c4560981b8152905190819003600d0190206109639033610cf8565b61099e5760405162461bcd60e51b81526004018080602001828103825260248152602001806118866024913960400191505060405180910390fd5b60048390556040805184815290517f8c4d35e54a3f2ef1134138fd8ea3daee6a3c89e10d2665996babdf70261e2c769181900360200190a1505050565b80806109e5611143565b1115610a2e576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b610a36611147565b610a87576040805162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f7420616e2061646d696e6973747261746f720000604482015290519081900360640190fd5b6000600160095460ff166001811115610a9c57fe5b1415610aa9575047610b3c565b600060095460ff166001811115610abc57fe5b1415610b3c57600154604080516370a0823160e01b815230600482015290516001600160a01b03909216916370a0823191602480820192602092909190829003018186803b158015610b0d57600080fd5b505afa158015610b21573d6000803e3d6000fd5b505050506040513d6020811015610b3757600080fd5b505190505b6000610b53600554836113ae90919063ffffffff16565b9050610b5f8582611158565b604080516001600160a01b03871681526020810183905281517f7f66376a5ef2f39ab4ee2ee6e400606624e66929dba5d82df5b14dd0070a8a87929181900390910190a15050505050565b8080610bb4611143565b1115610bfd576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b604080516a4f5241434c455f524f4c4560a81b8152905190819003600b019020610c279033610cf8565b610c78576040805162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420616e206f7261636c65000000000000000000604482015290519081900360640190fd5b60068490556007805460ff191660ff8516179055610c94611143565b600881905560408051918252517f47d1a578550704475fb893bcd176030186929dfaf9c7a4cac4ddc882bd000d429181900360200190a150505050565b6000828152602081905260408120610cef908363ffffffff6113f016565b90505b92915050565b6000828152602081905260408120610cef908363ffffffff6113fc16565b8080610d20611143565b1115610d69576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b610d71611147565b610dc2576040805162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f7420616e2061646d696e6973747261746f720000604482015290519081900360640190fd5b610dca611143565b6002541015610e20576040805162461bcd60e51b815260206004820152601b60248201527f4561726c696573742064656c657465206e6f7420726561636865640000000000604482015290519081900360640190fd5b600060095460ff166001811115610e3357fe5b1415610f3f57600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015610e8457600080fd5b505afa158015610e98573d6000803e3d6000fd5b505050506040513d6020811015610eae57600080fd5b50516001546040805163a9059cbb60e01b81526001600160a01b03888116600483015260248201859052915193945091169163a9059cbb916044808201926020929091908290030181600087803b158015610f0857600080fd5b505af1158015610f1c573d6000803e3d6000fd5b505050506040513d6020811015610f3257600080fd5b5051610f3d57600080fd5b505b6040517f41dbff2a3f1765d729d9e0651e4a45e813fb9a45fc4dd4f99ac3aa35bbab579290600090a1826001600160a01b0316ff5b60095460ff1681565b60085490565b600081565b604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902081565b60045481565b6000818152602081905260408120610cf290611411565b600082815260208190526040902060020154610fe8906105e46110d0565b6108c55760405162461bcd60e51b81526004018080602001828103825260308152602001806118aa6030913960400191505060405180910390fd5b60055481565b6001546001600160a01b031681565b60035481565b600080600061104b61141c565b5050600654600754600854919360ff9091169250565b600082820183811015610cef576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6000610cef836001600160a01b0384166115f8565b3390565b60008281526020819052604090206110f2908263ffffffff6110bb16565b1561062e576110ff6110d0565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b4390565b60006111538133610cf8565b905090565b6001600160a01b0382166111ac576040805162461bcd60e51b8152602060048201526016602482015275546172676574206973207a65726f206164647265737360501b604482015290519081900360640190fd5b600160095460ff1660018111156111bf57fe5b1415611201576040516001600160a01b0383169082156108fc029083906000818181858888f193505050501580156111fb573d6000803e3d6000fd5b5061062e565b600060095460ff16600181111561121457fe5b14156112f2576001546040805163a9059cbb60e01b81526001600160a01b038581166004830152602482018590529151919092169163a9059cbb9160448083019260209291908290030181600087803b15801561127057600080fd5b505af1158015611284573d6000803e3d6000fd5b505050506040513d602081101561129a57600080fd5b50516112ed576040805162461bcd60e51b815260206004820181905260248201527f496e737566662e204645542066756e6473206f6e20636f6e74722e2061646472604482015290519081900360640190fd5b61062e565b6040805162461bcd60e51b815260206004820181905260248201527f556e657870656374656420636f6e7472616374206665652063757272656e6379604482015290519081900360640190fd5b600082815260208190526040902061135d908263ffffffff61164216565b1561062e5761136a6110d0565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b6000610cef83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250611657565b6000610cef83836116ee565b6000610cef836001600160a01b038416611752565b6000610cf28261176a565b600160095460ff16600181111561142f57fe5b14156114e857600454341415611444576114e3565b6004543411156114965760045433906108fc9061146890349063ffffffff6113ae16565b6040518115909202916000818181858888f19350505050158015611490573d6000803e3d6000fd5b506114e3565b6040805162461bcd60e51b815260206004820181905260248201527f496e7375662e2045544820616d6f756e742073656e742062792063616c6c6572604482015290519081900360640190fd5b6115de565b600060095460ff1660018111156114fb57fe5b14156112f25760015460048054604080516323b872dd60e01b815233938101939093523060248401526044830191909152516001600160a01b03909216916323b872dd916064808201926020929091908290030181600087803b15801561156157600080fd5b505af1158015611575573d6000803e3d6000fd5b505050506040513d602081101561158b57600080fd5b50516114e3576040805162461bcd60e51b815260206004820181905260248201527f496e7375662e2046455420616c6c6f772e206f6e2063616c6c65722061646472604482015290519081900360640190fd5b6004546005546115f39163ffffffff61106116565b600555565b60006116048383611752565b61163a57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610cf2565b506000610cf2565b6000610cef836001600160a01b03841661176e565b600081848411156116e65760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156116ab578181015183820152602001611693565b50505050905090810190601f1680156116d85780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b815460009082106117305760405162461bcd60e51b81526004018080602001828103825260228152602001806118356022913960400191505060405180910390fd5b82600001828154811061173f57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6000818152600183016020526040812054801561182a57835460001980830191908101906000908790839081106117a157fe5b90600052602060002001549050808760000184815481106117be57fe5b6000918252602080832090910192909255828152600189810190925260409020908401905586548790806117ee57fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610cf2565b6000915050610cf256fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e7443616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b65416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220cc47090461aca71dac0f4022e5d6bb4238845310292224f2b873ef1d2a226c1464736f6c63430006080033", "deployedBytecode": "0x6080604052600436106101665760003560e01c806391d14854116100d1578063c5b37c221161008a578063d83222ec11610064578063d83222ec14610527578063ecd0c0c31461053c578063f1209ef714610551578063f7e1c7481461056657610166565b8063c5b37c22146104af578063ca15c873146104c4578063d547741f146104ee57610166565b806391d14854146103b15780639608df4b146103fe578063993240a3146104375780639bc1890914610470578063a217fddf14610485578063c0ba241b1461049a57610166565b806336568abe1161012357806336568abe14610275578063372646bb146102ae57806352f7c988146102c357806353e052ac146102f357806359f9f7cc1461032c5780639010d07c1461036557610166565b806307e2cea51461016b578063248a9ca3146101925780632f2ff15d146101bc578063323f4501146101f757806332a1bd7014610230578063347908df14610245575b600080fd5b34801561017757600080fd5b5061018061058e565b60408051918252519081900360200190f35b34801561019e57600080fd5b50610180600480360360208110156101b557600080fd5b50356105b1565b3480156101c857600080fd5b506101f5600480360360408110156101df57600080fd5b50803590602001356001600160a01b03166105c6565b005b34801561020357600080fd5b506101f56004803603604081101561021a57600080fd5b506001600160a01b038135169060200135610632565b34801561023c57600080fd5b50610180610748565b34801561025157600080fd5b506101f56004803603604081101561026857600080fd5b508035906020013561074e565b34801561028157600080fd5b506101f56004803603604081101561029857600080fd5b50803590602001356001600160a01b031661086e565b3480156102ba57600080fd5b506101806108cf565b3480156102cf57600080fd5b506101f5600480360360408110156102e657600080fd5b50803590602001356108d6565b3480156102ff57600080fd5b506101f56004803603604081101561031657600080fd5b506001600160a01b0381351690602001356109db565b34801561033857600080fd5b506101f56004803603606081101561034f57600080fd5b5080359060ff6020820135169060400135610baa565b34801561037157600080fd5b506103956004803603604081101561038857600080fd5b5080359060200135610cd1565b604080516001600160a01b039092168252519081900360200190f35b3480156103bd57600080fd5b506103ea600480360360408110156103d457600080fd5b50803590602001356001600160a01b0316610cf8565b604080519115158252519081900360200190f35b34801561040a57600080fd5b506101f56004803603604081101561042157600080fd5b506001600160a01b038135169060200135610d16565b34801561044357600080fd5b5061044c610f74565b6040518082600181111561045c57fe5b60ff16815260200191505060405180910390f35b34801561047c57600080fd5b50610180610f7d565b34801561049157600080fd5b50610180610f83565b3480156104a657600080fd5b50610180610f88565b3480156104bb57600080fd5b50610180610fad565b3480156104d057600080fd5b50610180600480360360208110156104e757600080fd5b5035610fb3565b3480156104fa57600080fd5b506101f56004803603604081101561051157600080fd5b50803590602001356001600160a01b0316610fca565b34801561053357600080fd5b50610180611023565b34801561054857600080fd5b50610395611029565b34801561055d57600080fd5b50610180611038565b61056e61103e565b6040805193845260ff909216602084015282820152519081900360600190f35b604080516a4f5241434c455f524f4c4560a81b8152905190819003600b01902081565b60009081526020819052604090206002015490565b6000828152602081905260409020600201546105e9906105e46110d0565b610cf8565b6106245760405162461bcd60e51b815260040180806020018281038252602f815260200180611857602f913960400191505060405180910390fd5b61062e82826110d4565b5050565b808061063c611143565b1115610685576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b61068d611147565b6106de576040805162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f7420616e2061646d696e6973747261746f720000604482015290519081900360640190fd5b6005546106ea57610743565b6106f683600554611158565b600554604080516001600160a01b0386168152602081019290925280517fe6646aed3b0543182f7396de55e4a49dfbddb4e684eece060fbaa5b03ff275149281900390910190a160006005555b505050565b60025481565b8080610758611143565b11156107a1576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b6107a9611147565b806107db5750604080516c44454c45474154455f524f4c4560981b8152905190819003600d0190206107db9033610cf8565b6108165760405162461bcd60e51b81526004018080602001828103825260248152602001806118866024913960400191505060405180910390fd5b6000610820611143565b905080841061082f5783610831565b805b600381905560408051918252517f68b095021b1f40fe513109f513c66692f0b3219aee674a69f4efc57badb8201d9181900360200190a150505050565b6108766110d0565b6001600160a01b0316816001600160a01b0316146108c55760405162461bcd60e51b815260040180806020018281038252602f8152602001806118da602f913960400191505060405180910390fd5b61062e828261133f565b6205a66d81565b80806108e0611143565b1115610929576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b610931611147565b806109635750604080516c44454c45474154455f524f4c4560981b8152905190819003600d0190206109639033610cf8565b61099e5760405162461bcd60e51b81526004018080602001828103825260248152602001806118866024913960400191505060405180910390fd5b60048390556040805184815290517f8c4d35e54a3f2ef1134138fd8ea3daee6a3c89e10d2665996babdf70261e2c769181900360200190a1505050565b80806109e5611143565b1115610a2e576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b610a36611147565b610a87576040805162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f7420616e2061646d696e6973747261746f720000604482015290519081900360640190fd5b6000600160095460ff166001811115610a9c57fe5b1415610aa9575047610b3c565b600060095460ff166001811115610abc57fe5b1415610b3c57600154604080516370a0823160e01b815230600482015290516001600160a01b03909216916370a0823191602480820192602092909190829003018186803b158015610b0d57600080fd5b505afa158015610b21573d6000803e3d6000fd5b505050506040513d6020811015610b3757600080fd5b505190505b6000610b53600554836113ae90919063ffffffff16565b9050610b5f8582611158565b604080516001600160a01b03871681526020810183905281517f7f66376a5ef2f39ab4ee2ee6e400606624e66929dba5d82df5b14dd0070a8a87929181900390910190a15050505050565b8080610bb4611143565b1115610bfd576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b604080516a4f5241434c455f524f4c4560a81b8152905190819003600b019020610c279033610cf8565b610c78576040805162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420616e206f7261636c65000000000000000000604482015290519081900360640190fd5b60068490556007805460ff191660ff8516179055610c94611143565b600881905560408051918252517f47d1a578550704475fb893bcd176030186929dfaf9c7a4cac4ddc882bd000d429181900360200190a150505050565b6000828152602081905260408120610cef908363ffffffff6113f016565b90505b92915050565b6000828152602081905260408120610cef908363ffffffff6113fc16565b8080610d20611143565b1115610d69576040805162461bcd60e51b8152602060048201526013602482015272151c985b9cd858dd1a5bdb88195e1c1a5c9959606a1b604482015290519081900360640190fd5b610d71611147565b610dc2576040805162461bcd60e51b815260206004820152601e60248201527f43616c6c6572206973206e6f7420616e2061646d696e6973747261746f720000604482015290519081900360640190fd5b610dca611143565b6002541015610e20576040805162461bcd60e51b815260206004820152601b60248201527f4561726c696573742064656c657465206e6f7420726561636865640000000000604482015290519081900360640190fd5b600060095460ff166001811115610e3357fe5b1415610f3f57600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015610e8457600080fd5b505afa158015610e98573d6000803e3d6000fd5b505050506040513d6020811015610eae57600080fd5b50516001546040805163a9059cbb60e01b81526001600160a01b03888116600483015260248201859052915193945091169163a9059cbb916044808201926020929091908290030181600087803b158015610f0857600080fd5b505af1158015610f1c573d6000803e3d6000fd5b505050506040513d6020811015610f3257600080fd5b5051610f3d57600080fd5b505b6040517f41dbff2a3f1765d729d9e0651e4a45e813fb9a45fc4dd4f99ac3aa35bbab579290600090a1826001600160a01b0316ff5b60095460ff1681565b60085490565b600081565b604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902081565b60045481565b6000818152602081905260408120610cf290611411565b600082815260208190526040902060020154610fe8906105e46110d0565b6108c55760405162461bcd60e51b81526004018080602001828103825260308152602001806118aa6030913960400191505060405180910390fd5b60055481565b6001546001600160a01b031681565b60035481565b600080600061104b61141c565b5050600654600754600854919360ff9091169250565b600082820183811015610cef576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6000610cef836001600160a01b0384166115f8565b3390565b60008281526020819052604090206110f2908263ffffffff6110bb16565b1561062e576110ff6110d0565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b4390565b60006111538133610cf8565b905090565b6001600160a01b0382166111ac576040805162461bcd60e51b8152602060048201526016602482015275546172676574206973207a65726f206164647265737360501b604482015290519081900360640190fd5b600160095460ff1660018111156111bf57fe5b1415611201576040516001600160a01b0383169082156108fc029083906000818181858888f193505050501580156111fb573d6000803e3d6000fd5b5061062e565b600060095460ff16600181111561121457fe5b14156112f2576001546040805163a9059cbb60e01b81526001600160a01b038581166004830152602482018590529151919092169163a9059cbb9160448083019260209291908290030181600087803b15801561127057600080fd5b505af1158015611284573d6000803e3d6000fd5b505050506040513d602081101561129a57600080fd5b50516112ed576040805162461bcd60e51b815260206004820181905260248201527f496e737566662e204645542066756e6473206f6e20636f6e74722e2061646472604482015290519081900360640190fd5b61062e565b6040805162461bcd60e51b815260206004820181905260248201527f556e657870656374656420636f6e7472616374206665652063757272656e6379604482015290519081900360640190fd5b600082815260208190526040902061135d908263ffffffff61164216565b1561062e5761136a6110d0565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b6000610cef83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250611657565b6000610cef83836116ee565b6000610cef836001600160a01b038416611752565b6000610cf28261176a565b600160095460ff16600181111561142f57fe5b14156114e857600454341415611444576114e3565b6004543411156114965760045433906108fc9061146890349063ffffffff6113ae16565b6040518115909202916000818181858888f19350505050158015611490573d6000803e3d6000fd5b506114e3565b6040805162461bcd60e51b815260206004820181905260248201527f496e7375662e2045544820616d6f756e742073656e742062792063616c6c6572604482015290519081900360640190fd5b6115de565b600060095460ff1660018111156114fb57fe5b14156112f25760015460048054604080516323b872dd60e01b815233938101939093523060248401526044830191909152516001600160a01b03909216916323b872dd916064808201926020929091908290030181600087803b15801561156157600080fd5b505af1158015611575573d6000803e3d6000fd5b505050506040513d602081101561158b57600080fd5b50516114e3576040805162461bcd60e51b815260206004820181905260248201527f496e7375662e2046455420616c6c6f772e206f6e2063616c6c65722061646472604482015290519081900360640190fd5b6004546005546115f39163ffffffff61106116565b600555565b60006116048383611752565b61163a57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610cf2565b506000610cf2565b6000610cef836001600160a01b03841661176e565b600081848411156116e65760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156116ab578181015183820152602001611693565b50505050905090810190601f1680156116d85780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b815460009082106117305760405162461bcd60e51b81526004018080602001828103825260228152602001806118356022913960400191505060405180910390fd5b82600001828154811061173f57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6000818152600183016020526040812054801561182a57835460001980830191908101906000908790839081106117a157fe5b90600052602060002001549050808760000184815481106117be57fe5b6000918252602080832090910192909255828152600189810190925260409020908401905586548790806117ee57fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610cf2565b6000915050610cf256fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e7443616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b65416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220cc47090461aca71dac0f4022e5d6bb4238845310292224f2b873ef1d2a226c1464736f6c63430006080033", "sourceMap": "1550:9995:4:-:0;;;3624:467;5:9:-1;2:2;;;27:1;24;17:12;2:2;3624:467:4;;;;;;;;;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3624:467:4;;;;;;;3695:42;1762:4:7;3726:10:4;-1:-1:-1;;;;;3695:10:4;:42;:::i;:::-;-1:-1:-1;;;;;3752:26:4;;3748:193;;3794:12;:30;;-1:-1:-1;;3794:30:4;3809:15;3794:30;;;3748:193;;;3855:12;:32;;-1:-1:-1;;3855:32:4;;;-1:-1:-1;3901:29:4;;-1:-1:-1;;;;;;3901:29:4;-1:-1:-1;;;;;3901:29:4;;;;;3748:193;3969:47;2272:6;3969:17;-1:-1:-1;;;;;3969:15:4;:17;:::i;:::-;:21;;;;;;:47;;;;:::i;:::-;3951:15;:65;-1:-1:-1;;4026:17:4;:31;4067:4;:17;-1:-1:-1;1550:9995:4;;6578:110:7;6656:25;6667:4;6673:7;-1:-1:-1;;;;;6656:10:7;:25;:::i;:::-;6578:110;;:::o;9537:106:4:-;9624:12;9537:106;:::o;874:176:8:-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;1042:1;-1:-1:-1;874:176:8;;;;;:::o;7015:184:7:-;7088:6;:12;;;;;;;;;;;:33;;7113:7;;7088:24;;;;;:33;;:::i;:::-;7084:109;;;7169:12;-1:-1:-1;;;;;7169:10:7;:12;:::i;:::-;-1:-1:-1;;;;;7142:40:7;7160:7;-1:-1:-1;;;;;7142:40:7;7154:4;7142:40;;;;;;;;;;7015:184;;:::o;4864:141:13:-;4934:4;4957:41;4962:3;-1:-1:-1;;;;;4982:14:13;;-1:-1:-1;;;;;4957:4:13;:41;:::i;590:104:6:-;677:10;590:104;:::o;1611:404:13:-;1674:4;1695:21;1705:3;1710:5;-1:-1:-1;;;;;1695:9:13;:21;:::i;:::-;1690:319;;-1:-1:-1;27:10;;39:1;23:18;;;45:23;;1732:11:13;:23;;;;;;;;;;;;;1912:18;;1890:19;;;:12;;;:19;;;;;;:40;;;;1944:11;;1690:319;-1:-1:-1;1993:5:13;1986:12;;3776:127;3849:4;3872:19;;;:12;;;;;:19;;;;;;:24;;;3776:127::o;1550:9995:4:-;;;;;;;", "deployedSourceMap": "1550:9995:4:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;9;2:12;2081:62:4;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2081:62:4;;;:::i;:::-;;;;;;;;;;;;;;;;4272:112:7;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4272:112:7;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4272:112:7;;:::i;4634:223::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4634:223:7;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4634:223:7;;;;;;-1:-1:-1;;;;;4634:223:7;;:::i;:::-;;6270:538:4;;5:9:-1;2:2;;;27:1;24;17:12;2:2;6270:538:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;6270:538:4;;;;;;;;:::i;2361:30::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2361:30:4;;;:::i;5544:355::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;5544:355:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5544:355:4;;;;;;;:::i;5808:205:7:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;5808:205:7;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5808:205:7;;;;;;-1:-1:-1;;;;;5808:205:7;;:::i;2221:57:4:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2221:57:4;;;:::i;5110:202::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;5110:202:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5110:202:4;;;;;;;:::i;7471:967::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;7471:967:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;7471:967:4;;;;;;;;:::i;4098:428::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4098:428:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4098:428:4;;;;;;;;;;;;;;:::i;3955:136:7:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;3955:136:7;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3955:136:7;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;3955:136:7;;;;;;;;;;;;;;2940:137;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2940:137:7;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;2940:137:7;;;;;;-1:-1:-1;;;;;2940:137:7;;:::i;:::-;;;;;;;;;;;;;;;;;;8759:636:4;;5:9:-1;2:2;;;27:1;24;17:12;2:2;8759:636:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;8759:636:4;;;;;;;;:::i;2537:31::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2537:31:4;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9402:129;;5:9:-1;2:2;;;27:1;24;17:12;2:2;9402:129:4;;;:::i;1717:49:7:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1717:49:7;;;:::i;2149:66:4:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2149:66:4;;;:::i;2435:19::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2435:19:4;;;:::i;3245:125:7:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;3245:125:7;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3245:125:7;;:::i;5091:226::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;5091:226:7;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5091:226:7;;;;;;-1:-1:-1;;;;;5091:226:7;;:::i;2460:33:4:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2460:33:4;;;:::i;2335:20::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2335:20:4;;;:::i;2397:32::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2397:32:4;;;:::i;4533:317::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;2081:62;2119:24;;;-1:-1:-1;;;2119:24:4;;;;;;;;;;;;2081:62;:::o;4272:112:7:-;4329:7;4355:12;;;;;;;;;;:22;;;;4272:112::o;4634:223::-;4725:6;:12;;;;;;;;;;:22;;;4717:45;;4749:12;:10;:12::i;:::-;4717:7;:45::i;:::-;4709:105;;;;-1:-1:-1;;;4709:105:7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4825:25;4836:4;4842:7;4825:10;:25::i;:::-;4634:223;;:::o;6270:538:4:-;6401:17;3283:15;3262:17;:15;:17::i;:::-;:36;;3254:68;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;;;;2764:10:::1;:8;:10::i;:::-;2756:53;;;::::0;;-1:-1:-1;;;2756:53:4;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;::::1;::::0;;;;;;;;;;;;;::::1;;6456:18:::2;::::0;6452:60:::2;;6495:7;;6452:60;6641:56;6663:13;6678:18;;6641:21;:56::i;:::-;6750:18;::::0;6713:56:::2;::::0;;-1:-1:-1;;;;;6713:56:4;::::2;::::0;;::::2;::::0;::::2;::::0;;;;;;::::2;::::0;;;;;;;;::::2;6800:1;6779:18;:22:::0;2819:1:::2;6270:538:::0;;;:::o;2361:30::-;;;;:::o;5544:355::-;5657:17;3283:15;3262:17;:15;:17::i;:::-;:36;;3254:68;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;;;;2920:10:::1;:8;:10::i;:::-;:48;;;-1:-1:-1::0;2189:26:4::1;::::0;;-1:-1:-1;;;2189:26:4;;;;;;;;::::1;::::0;;;2934:34:::1;::::0;2957:10:::1;2934:7;:34::i;:::-;2912:97;;;;-1:-1:-1::0;;;2912:97:4::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5711:25:::2;5739:17;:15;:17::i;:::-;5711:45;;5801:17;5786:12;:32;:67;;5841:12;5786:67;;;5821:17;5786:67;5766:17;:87:::0;;;5868:24:::2;::::0;;;;;;::::2;::::0;;;;::::2;::::0;;::::2;3019:1;5544:355:::0;;;:::o;5808:205:7:-;5905:12;:10;:12::i;:::-;-1:-1:-1;;;;;5894:23:7;:7;-1:-1:-1;;;;;5894:23:7;;5886:83;;;;-1:-1:-1;;;5886:83:7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5980:26;5992:4;5998:7;5980:11;:26::i;2221:57:4:-;2272:6;2221:57;:::o;5110:202::-;5210:17;3283:15;3262:17;:15;:17::i;:::-;:36;;3254:68;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;;;;2920:10:::1;:8;:10::i;:::-;:48;;;-1:-1:-1::0;2189:26:4::1;::::0;;-1:-1:-1;;;2189:26:4;;;;;;;;::::1;::::0;;;2934:34:::1;::::0;2957:10:::1;2934:7;:34::i;:::-;2912:97;;;;-1:-1:-1::0;;;2912:97:4::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5264:4:::2;:10:::0;;;5289:16:::2;::::0;;;;;;;::::2;::::0;;;;::::2;::::0;;::::2;5110:202:::0;;;:::o;7471:967::-;7603:17;3283:15;3262:17;:15;:17::i;:::-;:36;;3254:68;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;;;;2764:10:::1;:8;:10::i;:::-;2756:53;;;::::0;;-1:-1:-1;;;2756:53:4;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;::::1;::::0;;;;;;;;;;;;;::::1;;7654:23:::2;7708:15;7692:12;::::0;::::2;;::::0;:31;::::2;;;;;;;7688:236;;;-1:-1:-1::0;7765:21:4::2;7688:236;;;7831:17;7815:12;::::0;::::2;;::::0;:33;::::2;;;;;;;7811:113;;;7882:6;::::0;:31:::2;::::0;;-1:-1:-1;;;7882:31:4;;7907:4:::2;7882:31;::::0;::::2;::::0;;;-1:-1:-1;;;;;7882:6:4;;::::2;::::0;:16:::2;::::0;:31;;;;;::::2;::::0;;;;;;;;;:6;:31;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;7882:31:4;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;7882:31:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;7882:31:4;;-1:-1:-1;7811:113:4::2;8244:20;8267:39;8287:18;;8267:15;:19;;:39;;;;:::i;:::-;8244:62;;8316:50;8338:13;8353:12;8316:21;:50::i;:::-;8381;::::0;;-1:-1:-1;;;;;8381:50:4;::::2;::::0;;::::2;::::0;::::2;::::0;;;;;::::2;::::0;;;;;;;;;::::2;2819:1;;7471:967:::0;;;:::o;4098:428::-;4261:17;3283:15;3262:17;:15;:17::i;:::-;:36;;3254:68;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;;;;2119:24:::1;::::0;;-1:-1:-1;;;2119:24:4;;;;;;;;::::1;::::0;;;3107:32:::1;::::0;3128:10:::1;3107:7;:32::i;:::-;3099:68;;;::::0;;-1:-1:-1;;;3099:68:4;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;::::1;::::0;;;;;;;;;;;;;::::1;;4313:12:::2;:26:::0;;;4349:21;:32;;-1:-1:-1;;4349:32:4::2;;::::0;::::2;;::::0;;4430:17:::2;:15;:17::i;:::-;4391:36:::0;:56;;;4462::::2;::::0;;;;;;::::2;::::0;;;;::::2;::::0;;::::2;4098:428:::0;;;;:::o;3955:136:7:-;4028:7;4054:12;;;;;;;;;;:30;;4078:5;4054:30;:23;:30;:::i;:::-;4047:37;;3955:136;;;;;:::o;2940:137::-;3009:4;3032:12;;;;;;;;;;:38;;3062:7;3032:38;:29;:38;:::i;8759:636:4:-;8885:17;3283:15;3262:17;:15;:17::i;:::-;:36;;3254:68;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;-1:-1:-1;;;3254:68:4;;;;;;;;;;;;;;;2764:10:::1;:8;:10::i;:::-;2756:53;;;::::0;;-1:-1:-1;;;2756:53:4;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;::::1;::::0;;;;;;;;;;;;;::::1;;8963:17:::2;:15;:17::i;:::-;8944:15;;:36;;8936:76;;;::::0;;-1:-1:-1;;;8936:76:4;;::::2;;::::0;::::2;::::0;::::2;::::0;;;;::::2;::::0;;;;;;;;;;;;;::::2;;9042:17;9026:12;::::0;::::2;;::::0;:33;::::2;;;;;;;9022:191;;;9101:6;::::0;:31:::2;::::0;;-1:-1:-1;;;9101:31:4;;9126:4:::2;9101:31;::::0;::::2;::::0;;;9075:23:::2;::::0;-1:-1:-1;;;;;9101:6:4::2;::::0;:16:::2;::::0;:31;;;;;::::2;::::0;;;;;;;;:6;:31;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;9101:31:4;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;9101:31:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;9101:31:4;9154:6:::2;::::0;:47:::2;::::0;;-1:-1:-1;;;9154:47:4;;-1:-1:-1;;;;;9154:47:4;;::::2;;::::0;::::2;::::0;;;;;;;;;9101:31;;-1:-1:-1;9154:6:4;::::2;::::0;:15:::2;::::0;:47;;;;;9101:31:::2;::::0;9154:47;;;;;;;;:6:::2;::::0;:47;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;9154:47:4;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;9154:47:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;9154:47:4;9146:56:::2;;12:1:-1;9::::0;2:12:::2;9146:56:4;9022:191;;9227:17;::::0;::::2;::::0;;;::::2;9374:13;-1:-1:-1::0;;;;;9361:27:4::2;;2537:31:::0;;;;;;:::o;9402:129::-;9488:36;;9402:129;:::o;1717:49:7:-;1762:4;1717:49;:::o;2149:66:4:-;2189:26;;;-1:-1:-1;;;2189:26:4;;;;;;;;;;;;2149:66;:::o;2435:19::-;;;;:::o;3245:125:7:-;3308:7;3334:12;;;;;;;;;;:29;;:27;:29::i;5091:226::-;5183:6;:12;;;;;;;;;;:22;;;5175:45;;5207:12;:10;:12::i;5175:45::-;5167:106;;;;-1:-1:-1;;;5167:106:7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2460:33:4;;;;:::o;2335:20::-;;;-1:-1:-1;;;;;2335:20:4;;:::o;2397:32::-;;;;:::o;4533:317::-;4601:13;4616:14;4632:31;4679:14;:12;:14::i;:::-;-1:-1:-1;;4711:12:4;:18;4750:21;;4807:36;;4711:18;;4750:21;;;;;-1:-1:-1;4533:317:4:o;874:176:8:-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:8;;;;;;;;;;;;;;;;;;;;;;;;;;;4864:141:13;4934:4;4957:41;4962:3;-1:-1:-1;;;;;4982:14:13;;4957:4;:41::i;590:104:6:-;677:10;590:104;:::o;7015:184:7:-;7088:6;:12;;;;;;;;;;:33;;7113:7;7088:33;:24;:33;:::i;:::-;7084:109;;;7169:12;:10;:12::i;:::-;-1:-1:-1;;;;;7142:40:7;7160:7;-1:-1:-1;;;;;7142:40:7;7154:4;7142:40;;;;;;;;;;7015:184;;:::o;9537:106:4:-;9624:12;9537:106;:::o;2575:111::-;2617:4;2640:39;2617:4;2668:10;2640:7;:39::i;:::-;2633:46;;2575:111;:::o;10753:790::-;-1:-1:-1;;;;;10862:27:4;;10854:62;;;;;-1:-1:-1;;;10854:62:4;;;;;;;;;;;;-1:-1:-1;;;10854:62:4;;;;;;;;;;;;;;;10947:15;10931:12;;;;;:31;;;;;;;;10927:610;;;11108:30;;-1:-1:-1;;;;;11108:22:4;;;:30;;;;;11131:6;;11108:30;;;;11131:6;11108:22;:30;;;;;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;11108:30:4;10927:610;;;11183:17;11167:12;;;;;:33;;;;;;;;11163:374;;;11354:6;;:38;;;-1:-1:-1;;;11354:38:4;;-1:-1:-1;;;;;11354:38:4;;;;;;;;;;;;;;;:6;;;;;:15;;:38;;;;;;;;;;;;;;:6;;:38;;;2:2:-1;;;;27:1;24;17:12;2:2;11354:38:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;11354:38:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;11354:38:4;11346:83;;;;;-1:-1:-1;;;11346:83:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11163:374;;;11476:50;;;-1:-1:-1;;;11476:50:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;7205:188:7;7279:6;:12;;;;;;;;;;:36;;7307:7;7279:36;:27;:36;:::i;:::-;7275:112;;;7363:12;:10;:12::i;:::-;-1:-1:-1;;;;;7336:40:7;7354:7;-1:-1:-1;;;;;7336:40:7;7348:4;7336:40;;;;;;;;;;7205:188;;:::o;1321:134:8:-;1379:7;1405:43;1409:1;1412;1405:43;;;;;;;;;;;;;;;;;:3;:43::i;6085:147:13:-;6159:7;6201:22;6205:3;6217:5;6201:3;:22::i;5401:156::-;5481:4;5504:46;5514:3;-1:-1:-1;;;;;5534:14:13;;5504:9;:46::i;5638:115::-;5701:7;5727:19;5735:3;5727:7;:19::i;9708:1038:4:-;9775:15;9759:12;;;;;:31;;;;;;;;9755:925;;;9830:4;;9817:9;:17;9813:577;;;;;;10093:4;;10081:9;:16;10077:313;;;10261:4;;10227:10;;:40;;10247:19;;:9;;:19;:13;:19;:::i;:::-;10227:40;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;10227:40:4;10077:313;;;10326:50;;;-1:-1:-1;;;10326:50:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9755:925;;;10434:17;10418:12;;;;;:33;;;;;;;;10414:266;;;10483:6;;10530:4;;;10483:52;;;-1:-1:-1;;;10483:52:4;;10503:10;10483:52;;;;;;;10523:4;10483:52;;;;;;;;;;;;-1:-1:-1;;;;;10483:6:4;;;;:19;;:52;;;;;;;;;;;;;;;:6;;:52;;;2:2:-1;;;;27:1;24;17:12;2:2;10483:52:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;10483:52:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;10483:52:4;10475:97;;;;;-1:-1:-1;;;10475:97:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;10619:50;10734:4;;10711:18;;:28;;;:22;:28;:::i;:::-;10690:18;:49;9708:1038::o;1611:404:13:-;1674:4;1695:21;1705:3;1710:5;1695:9;:21::i;:::-;1690:319;;-1:-1:-1;27:10;;39:1;23:18;;;45:23;;1732:11:13;:23;;;;;;;;;;;;;1912:18;;1890:19;;;:12;;;:19;;;;;;:40;;;;1944:11;;1690:319;-1:-1:-1;1993:5:13;1986:12;;5173:147;5246:4;5269:44;5277:3;-1:-1:-1;;;;;5297:14:13;;5269:7;:44::i;1746:187:8:-;1832:7;1867:12;1859:6;;;;1851:29;;;;-1:-1:-1;;;1851:29:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;1851:29:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1902:5:8;;;1746:187::o;4423:201:13:-;4517:18;;4490:7;;4517:26;-1:-1:-1;4509:73:13;;;;-1:-1:-1;;;4509:73:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4599:3;:11;;4611:5;4599:18;;;;;;;;;;;;;;;;4592:25;;4423:201;;;;:::o;3776:127::-;3849:4;3872:19;;;:12;;;;;:19;;;;;;:24;;;3776:127::o;3984:107::-;4066:18;;3984:107::o;2183:1512::-;2249:4;2386:19;;;:12;;;:19;;;;;;2420:15;;2416:1273;;2849:18;;-1:-1:-1;;2801:14:13;;;;2849:22;;;;2777:21;;2849:3;;:22;;3131;;;;;;;;;;;;;;3111:42;;3274:9;3245:3;:11;;3257:13;3245:26;;;;;;;;;;;;;;;;;;;:38;;;;3349:23;;;3391:1;3349:12;;;:23;;;;;;3375:17;;;3349:43;;3498:17;;3349:3;;3498:17;;;;;;;;;;;;;;;;;;;;;;3590:3;:12;;:19;3603:5;3590:19;;;;;;;;;;;3583:26;;;3631:4;3624:11;;;;;;;;2416:1273;3673:5;3666:12;;;;", "source": "// SPDX-License-Identifier:Apache-2.0\n//------------------------------------------------------------------------------\n//\n// Copyright 2020 Fetch.AI Limited\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//------------------------------------------------------------------------------\n\npragma solidity ^0.6.0;\n\nimport \"../openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"../openzeppelin/contracts/access/AccessControl.sol\";\nimport \"../openzeppelin/contracts/math/SafeMath.sol\";\n\n\n/**\n @dev The contract *INTENTIONALLY* does not contain the `receive()` or fallback functions in order to ensure that all\n unsolicited direct ETH transfers in to this contract (in direct Transaction) will fail.\n Only way how to make successful fee transfer in ETH currency (*IF* contract is initialised for ETH as fee currency),\n is to make transfer ETH to caller(client) contract and client contract then is responsible for further\n *programmatic* (= *not* via Tx) transfer of fee to this this contract.\n */\ncontract FetchOracle is AccessControl {\n using SafeMath for uint256;\n\n\n enum FeeCurrency { ERC20, ETH }\n\n struct OracleValue {\n uint256 value;\n uint8 decimals;\n uint256 updatedAtEthBlockNumber;\n }\n\n event FeeUpdated(uint256 fee);\n event Pause(uint256 sinceBlock);\n event ExcessTokenWithdrawal(address targetAddress, uint256 amount);\n event AccruedFeesWithdrawal(address targetAddress, uint256 amount);\n event ContractDeleted();\n event OracleValueUpdated(uint256 atBlock); \n\n\n bytes32 public constant ORACLE_ROLE = keccak256(\"ORACLE_ROLE\");\n bytes32 public constant DELEGATE_ROLE = keccak256(\"DELEGATE_ROLE\");\n uint256 public constant DELETE_PROTECTION_PERIOD = 370285;// 60*24*60*60[s] / (14[s/block]) = 370285[block];\n\n IERC20 public _token;\n uint256 public _earliestDelete;\n uint256 public _pausedSinceBlock;\n uint256 public _fee;\n uint256 public _accruedFeesAmount;\n OracleValue private _oracleValue;\n FeeCurrency public _feeCurrency;\n\n function _isOwner() internal view returns(bool) {\n return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);\n }\n\n /* Only callable by owner */\n modifier onlyOwner() {\n require(_isOwner(), \"Caller is not an administrator\");\n _;\n }\n\n /* Only callable by owner or delegate */\n modifier onlyDelegate() {\n require(_isOwner() || hasRole(DELEGATE_ROLE, msg.sender), \"Caller is neither owner nor delegate\");\n _;\n }\n\n /* Only callable by oracle */\n modifier onlyOracle() {\n require(hasRole(ORACLE_ROLE, msg.sender), \"Caller is not an oracle\");\n _;\n }\n\n modifier verifyTxExpiration(uint256 expirationBlock) {\n require(_getBlockNumber() <= expirationBlock, \"Transaction expired\");\n _;\n }\n\n modifier verifyNotPaused() {\n require(_pausedSinceBlock > _getBlockNumber(), \"Contract has been paused\");\n _;\n }\n\n\n /*******************\n Contract start\n *******************/\n /**\n * @param ERC20Address address of the ERC20 contract\n */\n constructor(address ERC20Address, uint256 initialFee) public {\n _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);\n\n if (ERC20Address == address(0)) {\n _feeCurrency = FeeCurrency.ETH;\n } else {\n _feeCurrency = FeeCurrency.ERC20;\n _token = IERC20(ERC20Address);\n }\n\n _earliestDelete = _getBlockNumber().add(DELETE_PROTECTION_PERIOD);\n _pausedSinceBlock = ~uint256(0);\n _fee = initialFee;\n }\n\n\n function updateOracleValue(\n uint256 value,\n uint8 decimals,\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n onlyOracle\n {\n _oracleValue.value = value;\n _oracleValue.decimals = decimals;\n _oracleValue.updatedAtEthBlockNumber = _getBlockNumber();\n emit OracleValueUpdated(_oracleValue.updatedAtEthBlockNumber); \n }\n\n\n function queryOracleValue()\n public payable\n returns (uint256 value, uint8 decimals, uint256 updatedAtEthBlockNumber)\n {\n _withdrawFee();\n value = _oracleValue.value;\n decimals = _oracleValue.decimals;\n updatedAtEthBlockNumber = _oracleValue.updatedAtEthBlockNumber;\n }\n\n\n /**\n * @dev Pause the non-administrative interaction with the contract\n * @param fee - value of fee\n * @param txExpirationBlock - block number defined by Tx sender beyond which transaction becomes invalid\n * @dev Owners only\n */\n function setFee(uint256 fee, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyDelegate\n {\n _fee = fee;\n emit FeeUpdated(_fee);\n }\n\n\n /**\n * @dev Pause the non-administrative interaction with the contract\n * @param block_number disallow non-admin. interactions with contract for a _getBlockNumber() >= block_number\n * @dev Owners only\n */\n function pauseSince(uint256 block_number, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyDelegate\n {\n uint256 curr_block_number = _getBlockNumber();\n _pausedSinceBlock = block_number < curr_block_number ? curr_block_number : block_number;\n emit Pause(_pausedSinceBlock);\n }\n\n\n /**\n * @dev Withdraw whole balance of all fees accrued in the contract so far.\n * @param targetAddress : address to send tokens to\n * @param txExpirationBlock : block number until which is the transaction valid (inclusive).\n * When transaction is processed after this block, it fails.\n * @dev Owners only\n */\n function withdrawAccruedFees(address payable targetAddress, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyOwner\n {\n if (_accruedFeesAmount == 0) {\n return;\n }\n\n // The following method will revert in the case of issue (e.g. not enough balance for transfer, zero address):\n _withdrawFromContract(targetAddress, _accruedFeesAmount);\n\n emit AccruedFeesWithdrawal(targetAddress, _accruedFeesAmount);\n _accruedFeesAmount = 0;\n }\n\n\n /**\n * @dev Withdraw \"excess\" tokens, which were sent to contract address directly via direct ERC20 transfer(...) or transferFrom(...),\n * without interacting with API of this (FetchOracle) contract, what could be done only by mistake.\n * Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such\n * \"excess\" tokens out of contract.\n * @param targetAddress : address to send tokens to\n * @param txExpirationBlock : block number until which is the transaction valid (inclusive).\n * When transaction is processed after this block, it fails.\n */\n function withdrawExcessTokens(address payable targetAddress, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyOwner\n {\n uint256 contractBalance;\n\n if (_feeCurrency == FeeCurrency.ETH)\n {\n contractBalance = address(this).balance;\n }\n else if (_feeCurrency == FeeCurrency.ERC20) {\n contractBalance = _token.balanceOf(address(this));\n } // NOTE(pb): Final else{...} is not necessary since it is checked in the `_withdrawFromContract(...)` call bellow\n\n // NOTE(pb): The following subtraction shall *fail* (revert) IF the contract is in *INCONSISTENT* state,\n // = when contract balance is less than minial expected balance:\n uint256 excessAmount = contractBalance.sub(_accruedFeesAmount);\n _withdrawFromContract(targetAddress, excessAmount);\n emit ExcessTokenWithdrawal(targetAddress, excessAmount);\n }\n\n\n /**\n * @dev Delete the contract, transfers the remaining token and ether balance to the specified\n payoutAddress\n * @param payoutAddress address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\n * @dev owner only + only on or after `_earliestDelete` block\n */\n function deleteContract(address payable payoutAddress, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyOwner\n {\n require(_earliestDelete >= _getBlockNumber(), \"Earliest delete not reached\");\n if (_feeCurrency == FeeCurrency.ERC20) {\n uint256 contractBalance = _token.balanceOf(address(this));\n require(_token.transfer(payoutAddress, contractBalance));\n }\n emit ContractDeleted();\n // The selfdestruct, by design, transfers *whole* ETH balance from this contract to targetAddress:\n selfdestruct(payoutAddress);\n }\n\n\n function oracleValueLastUpdated() external view returns(uint256)\n {\n return _oracleValue.updatedAtEthBlockNumber;\n }\n\n function _getBlockNumber() internal view virtual returns(uint256)\n {\n return block.number;\n }\n\n\n /**\n * @dev Withdraw fee from the caller.\n */\n function _withdrawFee() internal\n {\n if (_feeCurrency == FeeCurrency.ETH)\n {\n if (msg.value == _fee)\n {\n // No action necessary, since exact expected fee amount has been already transferred by caller,\n // this this is just placeholder block to capture the logical condition.\n }\n else if (msg.value > _fee)\n {\n // Refund (back to caller) the excess caller paid on top of the expected _fee value:\n msg.sender.transfer(msg.value.sub(_fee));\n }\n else\n {\n require(false, \"Insuf. ETH amount sent by caller\");\n }\n }\n else if (_feeCurrency == FeeCurrency.ERC20)\n {\n require(_token.transferFrom(msg.sender, address(this), _fee), \"Insuf. FET allow. on caller addr\");\n }\n else\n {\n require(false, \"Unexpected contract fee currency\");\n }\n\n _accruedFeesAmount = _accruedFeesAmount.add(_fee);\n }\n\n\n function _withdrawFromContract(address payable targetAddress, uint256 amount) internal\n {\n require(targetAddress != address(0), \"Target is zero address\");\n\n if (_feeCurrency == FeeCurrency.ETH)\n {\n // NOTE(pb): The following transfer shall *fail* with revert IF contract balance is insufficient for transfer\n targetAddress.transfer(amount);\n }\n else if (_feeCurrency == FeeCurrency.ERC20)\n {\n // NOTE(pb): The following transfer shall *fail* with revert IF contract balance is insufficient for transfer\n require(_token.transfer(targetAddress, amount), \"Insuff. FET funds on contr. addr\");\n }\n else\n {\n require(false, \"Unexpected contract fee currency\");\n }\n }\n}\n", "sourcePath": "/home/james/Code/cosmos-drb-oracle/ethereum/contracts/FetchOracle.sol", "ast": { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/contracts/FetchOracle.sol", "exportedSymbols": { "FetchOracle": [ 1440 ] }, "id": 1441, "license": "Apache-2.0", "nodeType": "SourceUnit", "nodes": [ { "id": 815, "literals": [ "solidity", "^", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "820:23:4" }, { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/token/ERC20/IERC20.sol", "file": "../openzeppelin/contracts/token/ERC20/IERC20.sol", "id": 816, "nodeType": "ImportDirective", "scope": 1441, "sourceUnit": 2650, "src": "845:58:4", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/access/AccessControl.sol", "file": "../openzeppelin/contracts/access/AccessControl.sol", "id": 817, "nodeType": "ImportDirective", "scope": 1441, "sourceUnit": 1784, "src": "904:60:4", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/math/SafeMath.sol", "file": "../openzeppelin/contracts/math/SafeMath.sol", "id": 818, "nodeType": "ImportDirective", "scope": 1441, "sourceUnit": 1980, "src": "965:53:4", "symbolAliases": [], "unitAlias": "" }, { "abstract": false, "baseContracts": [ { "arguments": null, "baseName": { "contractScope": null, "id": 820, "name": "AccessControl", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 1783, "src": "1574:13:4", "typeDescriptions": { "typeIdentifier": "t_contract$_AccessControl_$1783", "typeString": "contract AccessControl" } }, "id": 821, "nodeType": "InheritanceSpecifier", "src": "1574:13:4" } ], "contractDependencies": [ 1500, 1783 ], "contractKind": "contract", "documentation": { "id": 819, "nodeType": "StructuredDocumentation", "src": "1021:528:4", "text": "@dev The contract *INTENTIONALLY* does not contain the `receive()` or fallback functions in order to ensure that all\nunsolicited direct ETH transfers in to this contract (in direct Transaction) will fail.\nOnly way how to make successful fee transfer in ETH currency (*IF* contract is initialised for ETH as fee currency),\nis to make transfer ETH to caller(client) contract and client contract then is responsible for further\nrogrammatic* (= *not* via Tx) transfer of fee to this this contract." }, "fullyImplemented": true, "id": 1440, "linearizedBaseContracts": [ 1440, 1783, 1500 ], "name": "FetchOracle", "nodeType": "ContractDefinition", "nodes": [ { "id": 824, "libraryName": { "contractScope": null, "id": 822, "name": "SafeMath", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 1979, "src": "1600:8:4", "typeDescriptions": { "typeIdentifier": "t_contract$_SafeMath_$1979", "typeString": "library SafeMath" } }, "nodeType": "UsingForDirective", "src": "1594:27:4", "typeName": { "id": 823, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1613:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } }, { "canonicalName": "FetchOracle.FeeCurrency", "id": 827, "members": [ { "id": 825, "name": "ERC20", "nodeType": "EnumValue", "src": "1648:5:4" }, { "id": 826, "name": "ETH", "nodeType": "EnumValue", "src": "1655:3:4" } ], "name": "FeeCurrency", "nodeType": "EnumDefinition", "src": "1628:32:4" }, { "canonicalName": "FetchOracle.OracleValue", "id": 834, "members": [ { "constant": false, "id": 829, "mutability": "mutable", "name": "value", "nodeType": "VariableDeclaration", "overrides": null, "scope": 834, "src": "1695:13:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 828, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1695:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 831, "mutability": "mutable", "name": "decimals", "nodeType": "VariableDeclaration", "overrides": null, "scope": 834, "src": "1718:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 830, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "1718:5:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 833, "mutability": "mutable", "name": "updatedAtEthBlockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 834, "src": "1742:31:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 832, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1742:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "name": "OracleValue", "nodeType": "StructDefinition", "scope": 1440, "src": "1666:114:4", "visibility": "public" }, { "anonymous": false, "documentation": null, "id": 838, "name": "FeeUpdated", "nodeType": "EventDefinition", "parameters": { "id": 837, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 836, "indexed": false, "mutability": "mutable", "name": "fee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 838, "src": "1803:11:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 835, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1803:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1802:13:4" }, "src": "1786:30:4" }, { "anonymous": false, "documentation": null, "id": 842, "name": "Pause", "nodeType": "EventDefinition", "parameters": { "id": 841, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 840, "indexed": false, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 842, "src": "1833:18:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 839, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1833:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1832:20:4" }, "src": "1821:32:4" }, { "anonymous": false, "documentation": null, "id": 848, "name": "ExcessTokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 847, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 844, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 848, "src": "1886:21:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 843, "name": "address", "nodeType": "ElementaryTypeName", "src": "1886:7:4", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 846, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 848, "src": "1909:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 845, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1909:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1885:39:4" }, "src": "1858:67:4" }, { "anonymous": false, "documentation": null, "id": 854, "name": "AccruedFeesWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 853, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 850, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 854, "src": "1958:21:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 849, "name": "address", "nodeType": "ElementaryTypeName", "src": "1958:7:4", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 852, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 854, "src": "1981:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 851, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1981:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1957:39:4" }, "src": "1930:67:4" }, { "anonymous": false, "documentation": null, "id": 856, "name": "ContractDeleted", "nodeType": "EventDefinition", "parameters": { "id": 855, "nodeType": "ParameterList", "parameters": [], "src": "2023:2:4" }, "src": "2002:24:4" }, { "anonymous": false, "documentation": null, "id": 860, "name": "OracleValueUpdated", "nodeType": "EventDefinition", "parameters": { "id": 859, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 858, "indexed": false, "mutability": "mutable", "name": "atBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 860, "src": "2056:15:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 857, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2056:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "2055:17:4" }, "src": "2031:42:4" }, { "constant": true, "functionSelector": "07e2cea5", "id": 865, "mutability": "constant", "name": "ORACLE_ROLE", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2081:62:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, "typeName": { "id": 861, "name": "bytes32", "nodeType": "ElementaryTypeName", "src": "2081:7:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "value": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "4f5241434c455f524f4c45", "id": 863, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2129:13:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_68e79a7bf1e0bc45d0a330c573bc367f9cf464fd326078812f301165fbda4ef1", "typeString": "literal_string \"ORACLE_ROLE\"" }, "value": "ORACLE_ROLE" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_stringliteral_68e79a7bf1e0bc45d0a330c573bc367f9cf464fd326078812f301165fbda4ef1", "typeString": "literal_string \"ORACLE_ROLE\"" } ], "id": 862, "name": "keccak256", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -8, "src": "2119:9:4", "typeDescriptions": { "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", "typeString": "function (bytes memory) pure returns (bytes32)" } }, "id": 864, "isConstant": false, "isLValue": false, "isPure": true, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2119:24:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "visibility": "public" }, { "constant": true, "functionSelector": "c0ba241b", "id": 870, "mutability": "constant", "name": "DELEGATE_ROLE", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2149:66:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, "typeName": { "id": 866, "name": "bytes32", "nodeType": "ElementaryTypeName", "src": "2149:7:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "value": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "44454c45474154455f524f4c45", "id": 868, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2199:15:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" }, "value": "DELEGATE_ROLE" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" } ], "id": 867, "name": "keccak256", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -8, "src": "2189:9:4", "typeDescriptions": { "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", "typeString": "function (bytes memory) pure returns (bytes32)" } }, "id": 869, "isConstant": false, "isLValue": false, "isPure": true, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2189:26:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "visibility": "public" }, { "constant": true, "functionSelector": "372646bb", "id": 873, "mutability": "constant", "name": "DELETE_PROTECTION_PERIOD", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2221:57:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 871, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2221:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": { "argumentTypes": null, "hexValue": "333730323835", "id": 872, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "2272:6:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_370285_by_1", "typeString": "int_const 370285" }, "value": "370285" }, "visibility": "public" }, { "constant": false, "functionSelector": "ecd0c0c3", "id": 875, "mutability": "mutable", "name": "_token", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2335:20:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" }, "typeName": { "contractScope": null, "id": 874, "name": "IERC20", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2649, "src": "2335:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "32a1bd70", "id": 877, "mutability": "mutable", "name": "_earliestDelete", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2361:30:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 876, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2361:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "f1209ef7", "id": 879, "mutability": "mutable", "name": "_pausedSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2397:32:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 878, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2397:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "c5b37c22", "id": 881, "mutability": "mutable", "name": "_fee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2435:19:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 880, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2435:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "d83222ec", "id": 883, "mutability": "mutable", "name": "_accruedFeesAmount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2460:33:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 882, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2460:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "id": 885, "mutability": "mutable", "name": "_oracleValue", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2499:32:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue" }, "typeName": { "contractScope": null, "id": 884, "name": "OracleValue", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 834, "src": "2499:11:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage_ptr", "typeString": "struct FetchOracle.OracleValue" } }, "value": null, "visibility": "private" }, { "constant": false, "functionSelector": "993240a3", "id": 887, "mutability": "mutable", "name": "_feeCurrency", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2537:31:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "typeName": { "contractScope": null, "id": 886, "name": "FeeCurrency", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 827, "src": "2537:11:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "value": null, "visibility": "public" }, { "body": { "id": 898, "nodeType": "Block", "src": "2623:63:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 893, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1526, "src": "2648:18:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 894, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "2668:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 895, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "2668:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 892, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1572, "src": "2640:7:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 896, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2640:39:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "functionReturnParameters": 891, "id": 897, "nodeType": "Return", "src": "2633:46:4" } ] }, "documentation": null, "id": 899, "implemented": true, "kind": "function", "modifiers": [], "name": "_isOwner", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 888, "nodeType": "ParameterList", "parameters": [], "src": "2592:2:4" }, "returnParameters": { "id": 891, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 890, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 899, "src": "2617:4:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 889, "name": "bool", "nodeType": "ElementaryTypeName", "src": "2617:4:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "src": "2616:6:4" }, "scope": 1440, "src": "2575:111:4", "stateMutability": "view", "virtual": false, "visibility": "internal" }, { "body": { "id": 908, "nodeType": "Block", "src": "2746:81:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 902, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 899, "src": "2764:8:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 903, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2764:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e6f7420616e2061646d696e6973747261746f72", "id": 904, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2776:32:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fff29d4bd770cdc17b9f156c578c5f49981008f3769f218da7df946262b27392", "typeString": "literal_string \"Caller is not an administrator\"" }, "value": "Caller is not an administrator" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fff29d4bd770cdc17b9f156c578c5f49981008f3769f218da7df946262b27392", "typeString": "literal_string \"Caller is not an administrator\"" } ], "id": 901, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "2756:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 905, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2756:53:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 906, "nodeType": "ExpressionStatement", "src": "2756:53:4" }, { "id": 907, "nodeType": "PlaceholderStatement", "src": "2819:1:4" } ] }, "documentation": null, "id": 909, "name": "onlyOwner", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 900, "nodeType": "ParameterList", "parameters": [], "src": "2743:2:4" }, "src": "2725:102:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 924, "nodeType": "Block", "src": "2902:125:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_bool", "typeString": "bool" }, "id": 919, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 912, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 899, "src": "2920:8:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 913, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2920:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "BinaryOperation", "operator": "||", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 915, "name": "DELEGATE_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 870, "src": "2942:13:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 916, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "2957:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 917, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "2957:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 914, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1572, "src": "2934:7:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 918, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2934:34:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "2920:48:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465", "id": 920, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2970:38:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" }, "value": "Caller is neither owner nor delegate" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" } ], "id": 911, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "2912:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 921, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2912:97:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 922, "nodeType": "ExpressionStatement", "src": "2912:97:4" }, { "id": 923, "nodeType": "PlaceholderStatement", "src": "3019:1:4" } ] }, "documentation": null, "id": 925, "name": "onlyDelegate", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 910, "nodeType": "ParameterList", "parameters": [], "src": "2899:2:4" }, "src": "2878:149:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 937, "nodeType": "Block", "src": "3089:96:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 929, "name": "ORACLE_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 865, "src": "3115:11:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 930, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "3128:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 931, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3128:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 928, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1572, "src": "3107:7:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 932, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3107:32:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e6f7420616e206f7261636c65", "id": 933, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "3141:25:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_8c1dc436c657406c837ef43aefe277157f6f9572ac3e706ad659a5d014391325", "typeString": "literal_string \"Caller is not an oracle\"" }, "value": "Caller is not an oracle" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_8c1dc436c657406c837ef43aefe277157f6f9572ac3e706ad659a5d014391325", "typeString": "literal_string \"Caller is not an oracle\"" } ], "id": 927, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "3099:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 934, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3099:68:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 935, "nodeType": "ExpressionStatement", "src": "3099:68:4" }, { "id": 936, "nodeType": "PlaceholderStatement", "src": "3177:1:4" } ] }, "documentation": null, "id": 938, "name": "onlyOracle", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 926, "nodeType": "ParameterList", "parameters": [], "src": "3086:2:4" }, "src": "3067:118:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 951, "nodeType": "Block", "src": "3244:96:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 946, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 943, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "3262:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 944, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3262:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 945, "name": "expirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 940, "src": "3283:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "3262:36:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73616374696f6e2065787069726564", "id": 947, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "3300:21:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" }, "value": "Transaction expired" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" } ], "id": 942, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "3254:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 948, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3254:68:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 949, "nodeType": "ExpressionStatement", "src": "3254:68:4" }, { "id": 950, "nodeType": "PlaceholderStatement", "src": "3332:1:4" } ] }, "documentation": null, "id": 952, "name": "verifyTxExpiration", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 941, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 940, "mutability": "mutable", "name": "expirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 952, "src": "3219:23:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 939, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3219:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3218:25:4" }, "src": "3191:149:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 963, "nodeType": "Block", "src": "3373:102:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 958, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 955, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "3391:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 956, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "3411:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 957, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3411:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "3391:37:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "436f6e747261637420686173206265656e20706175736564", "id": 959, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "3430:26:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" }, "value": "Contract has been paused" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" } ], "id": 954, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "3383:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 960, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3383:74:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 961, "nodeType": "ExpressionStatement", "src": "3383:74:4" }, { "id": 962, "nodeType": "PlaceholderStatement", "src": "3467:1:4" } ] }, "documentation": null, "id": 964, "name": "verifyNotPaused", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 953, "nodeType": "ParameterList", "parameters": [], "src": "3370:2:4" }, "src": "3346:129:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 1023, "nodeType": "Block", "src": "3685:406:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 973, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1526, "src": "3706:18:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 974, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "3726:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 975, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3726:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 972, "name": "_setupRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1709, "src": "3695:10:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_bytes32_$_t_address_$returns$__$", "typeString": "function (bytes32,address)" } }, "id": 976, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3695:42:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 977, "nodeType": "ExpressionStatement", "src": "3695:42:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address", "typeString": "address" }, "id": 983, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 978, "name": "ERC20Address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 967, "src": "3752:12:4", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "30", "id": 981, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "3776:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" } ], "id": 980, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "3768:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 979, "name": "address", "nodeType": "ElementaryTypeName", "src": "3768:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 982, "isConstant": false, "isLValue": false, "isPure": true, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3768:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "3752:26:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1001, "nodeType": "Block", "src": "3841:100:4", "statements": [ { "expression": { "argumentTypes": null, "id": 993, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 990, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "3855:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 991, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "3870:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 992, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3870:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "3855:32:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "id": 994, "nodeType": "ExpressionStatement", "src": "3855:32:4" }, { "expression": { "argumentTypes": null, "id": 999, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 995, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "3901:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 997, "name": "ERC20Address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 967, "src": "3917:12:4", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 996, "name": "IERC20", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 2649, "src": "3910:6:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_IERC20_$2649_$", "typeString": "type(contract IERC20)" } }, "id": 998, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3910:20:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "src": "3901:29:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1000, "nodeType": "ExpressionStatement", "src": "3901:29:4" } ] }, "id": 1002, "nodeType": "IfStatement", "src": "3748:193:4", "trueBody": { "id": 989, "nodeType": "Block", "src": "3780:55:4", "statements": [ { "expression": { "argumentTypes": null, "id": 987, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 984, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "3794:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 985, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "3809:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 986, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3809:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "3794:30:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "id": 988, "nodeType": "ExpressionStatement", "src": "3794:30:4" } ] } }, { "expression": { "argumentTypes": null, "id": 1009, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1003, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 877, "src": "3951:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1007, "name": "DELETE_PROTECTION_PERIOD", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 873, "src": "3991:24:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1004, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "3969:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1005, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3969:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1006, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 1812, "src": "3969:21:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1008, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3969:47:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "3951:65:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1010, "nodeType": "ExpressionStatement", "src": "3951:65:4" }, { "expression": { "argumentTypes": null, "id": 1017, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1011, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "4026:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1016, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "~", "prefix": true, "src": "4046:11:4", "subExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "30", "id": 1014, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "4055:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" } ], "id": 1013, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "4047:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_uint256_$", "typeString": "type(uint256)" }, "typeName": { "id": 1012, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4047:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1015, "isConstant": false, "isLValue": false, "isPure": true, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4047:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4026:31:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1018, "nodeType": "ExpressionStatement", "src": "4026:31:4" }, { "expression": { "argumentTypes": null, "id": 1021, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1019, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "4067:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1020, "name": "initialFee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 969, "src": "4074:10:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4067:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1022, "nodeType": "ExpressionStatement", "src": "4067:17:4" } ] }, "documentation": { "id": 965, "nodeType": "StructuredDocumentation", "src": "3551:68:4", "text": "@param ERC20Address address of the ERC20 contract" }, "id": 1024, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 970, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 967, "mutability": "mutable", "name": "ERC20Address", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1024, "src": "3636:20:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 966, "name": "address", "nodeType": "ElementaryTypeName", "src": "3636:7:4", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 969, "mutability": "mutable", "name": "initialFee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1024, "src": "3658:18:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 968, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3658:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3635:42:4" }, "returnParameters": { "id": 971, "nodeType": "ParameterList", "parameters": [], "src": "3685:0:4" }, "scope": 1440, "src": "3624:467:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "body": { "id": 1062, "nodeType": "Block", "src": "4303:223:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1042, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1038, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4313:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1040, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": 829, "src": "4313:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1041, "name": "value", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1026, "src": "4334:5:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4313:26:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1043, "nodeType": "ExpressionStatement", "src": "4313:26:4" }, { "expression": { "argumentTypes": null, "id": 1048, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1044, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4349:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1046, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "decimals", "nodeType": "MemberAccess", "referencedDeclaration": 831, "src": "4349:21:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1047, "name": "decimals", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1028, "src": "4373:8:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "src": "4349:32:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "id": 1049, "nodeType": "ExpressionStatement", "src": "4349:32:4" }, { "expression": { "argumentTypes": null, "id": 1055, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1050, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4391:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1052, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "4391:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1053, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "4430:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1054, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4430:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4391:56:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1056, "nodeType": "ExpressionStatement", "src": "4391:56:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1058, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4481:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1059, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "4481:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1057, "name": "OracleValueUpdated", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 860, "src": "4462:18:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1060, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4462:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1061, "nodeType": "EmitStatement", "src": "4457:61:4" } ] }, "documentation": null, "functionSelector": "59f9f7cc", "id": 1063, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1033, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1030, "src": "4261:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1034, "modifierName": { "argumentTypes": null, "id": 1032, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "4242:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "4242:37:4" }, { "arguments": null, "id": 1036, "modifierName": { "argumentTypes": null, "id": 1035, "name": "onlyOracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 938, "src": "4288:10:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "4288:10:4" } ], "name": "updateOracleValue", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1031, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1026, "mutability": "mutable", "name": "value", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1063, "src": "4134:13:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1025, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4134:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1028, "mutability": "mutable", "name": "decimals", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1063, "src": "4157:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 1027, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "4157:5:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1030, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1063, "src": "4181:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1029, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4181:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4124:92:4" }, "returnParameters": { "id": 1037, "nodeType": "ParameterList", "parameters": [], "src": "4303:0:4" }, "scope": 1440, "src": "4098:428:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1090, "nodeType": "Block", "src": "4669:181:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1072, "name": "_withdrawFee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1388, "src": "4679:12:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 1073, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4679:14:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1074, "nodeType": "ExpressionStatement", "src": "4679:14:4" }, { "expression": { "argumentTypes": null, "id": 1078, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1075, "name": "value", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1066, "src": "4703:5:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1076, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4711:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1077, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": 829, "src": "4711:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4703:26:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1079, "nodeType": "ExpressionStatement", "src": "4703:26:4" }, { "expression": { "argumentTypes": null, "id": 1083, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1080, "name": "decimals", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1068, "src": "4739:8:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1081, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4750:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1082, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "decimals", "nodeType": "MemberAccess", "referencedDeclaration": 831, "src": "4750:21:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "src": "4739:32:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "id": 1084, "nodeType": "ExpressionStatement", "src": "4739:32:4" }, { "expression": { "argumentTypes": null, "id": 1088, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1085, "name": "updatedAtEthBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1070, "src": "4781:23:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1086, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4807:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1087, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "4807:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4781:62:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1089, "nodeType": "ExpressionStatement", "src": "4781:62:4" } ] }, "documentation": null, "functionSelector": "f7e1c748", "id": 1091, "implemented": true, "kind": "function", "modifiers": [], "name": "queryOracleValue", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1064, "nodeType": "ParameterList", "parameters": [], "src": "4558:2:4" }, "returnParameters": { "id": 1071, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1066, "mutability": "mutable", "name": "value", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1091, "src": "4601:13:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1065, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4601:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1068, "mutability": "mutable", "name": "decimals", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1091, "src": "4616:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 1067, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "4616:5:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1070, "mutability": "mutable", "name": "updatedAtEthBlockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1091, "src": "4632:31:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1069, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4632:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4600:64:4" }, "scope": 1440, "src": "4533:317:4", "stateMutability": "payable", "virtual": false, "visibility": "public" }, { "body": { "id": 1112, "nodeType": "Block", "src": "5254:58:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1106, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1104, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "5264:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1105, "name": "fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1094, "src": "5271:3:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "5264:10:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1107, "nodeType": "ExpressionStatement", "src": "5264:10:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1109, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "5300:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1108, "name": "FeeUpdated", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 838, "src": "5289:10:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1110, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "5289:16:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1111, "nodeType": "EmitStatement", "src": "5284:21:4" } ] }, "documentation": { "id": 1092, "nodeType": "StructuredDocumentation", "src": "4857:248:4", "text": "@dev Pause the non-administrative interaction with the contract\n@param fee - value of fee\n@param txExpirationBlock - block number defined by Tx sender beyond which transaction becomes invalid\n@dev Owners only" }, "functionSelector": "52f7c988", "id": 1113, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1099, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1096, "src": "5210:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1100, "modifierName": { "argumentTypes": null, "id": 1098, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "5191:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "5191:37:4" }, { "arguments": null, "id": 1102, "modifierName": { "argumentTypes": null, "id": 1101, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 925, "src": "5237:12:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "5237:12:4" } ], "name": "setFee", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1097, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1094, "mutability": "mutable", "name": "fee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1113, "src": "5126:11:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1093, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5126:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1096, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1113, "src": "5139:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1095, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5139:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "5125:40:4" }, "returnParameters": { "id": 1103, "nodeType": "ParameterList", "parameters": [], "src": "5254:0:4" }, "scope": 1440, "src": "5110:202:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1144, "nodeType": "Block", "src": "5701:198:4", "statements": [ { "assignments": [ 1127 ], "declarations": [ { "constant": false, "id": 1127, "mutability": "mutable", "name": "curr_block_number", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1144, "src": "5711:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1126, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5711:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1130, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1128, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "5739:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1129, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "5739:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "5711:45:4" }, { "expression": { "argumentTypes": null, "id": 1138, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1131, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "5766:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1134, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1132, "name": "block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1116, "src": "5786:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 1133, "name": "curr_block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1127, "src": "5801:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "5786:32:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseExpression": { "argumentTypes": null, "id": 1136, "name": "block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1116, "src": "5841:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1137, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "Conditional", "src": "5786:67:4", "trueExpression": { "argumentTypes": null, "id": 1135, "name": "curr_block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1127, "src": "5821:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "5766:87:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1139, "nodeType": "ExpressionStatement", "src": "5766:87:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1141, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "5874:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1140, "name": "Pause", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 842, "src": "5868:5:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1142, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "5868:24:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1143, "nodeType": "EmitStatement", "src": "5863:29:4" } ] }, "documentation": { "id": 1114, "nodeType": "StructuredDocumentation", "src": "5319:220:4", "text": "@dev Pause the non-administrative interaction with the contract\n@param block_number disallow non-admin. interactions with contract for a _getBlockNumber() >= block_number\n@dev Owners only" }, "functionSelector": "347908df", "id": 1145, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1121, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1118, "src": "5657:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1122, "modifierName": { "argumentTypes": null, "id": 1120, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "5638:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "5638:37:4" }, { "arguments": null, "id": 1124, "modifierName": { "argumentTypes": null, "id": 1123, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 925, "src": "5684:12:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "5684:12:4" } ], "name": "pauseSince", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1119, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1116, "mutability": "mutable", "name": "block_number", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1145, "src": "5564:20:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1115, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5564:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1118, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1145, "src": "5586:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1117, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5586:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "5563:49:4" }, "returnParameters": { "id": 1125, "nodeType": "ParameterList", "parameters": [], "src": "5701:0:4" }, "scope": 1440, "src": "5544:355:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1178, "nodeType": "Block", "src": "6442:366:4", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1160, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1158, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6456:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 1159, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "6478:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "6456:23:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 1163, "nodeType": "IfStatement", "src": "6452:60:4", "trueBody": { "id": 1162, "nodeType": "Block", "src": "6481:31:4", "statements": [ { "expression": null, "functionReturnParameters": 1157, "id": 1161, "nodeType": "Return", "src": "6495:7:4" } ] } }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1165, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1148, "src": "6663:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1166, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6678:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1164, "name": "_withdrawFromContract", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1439, "src": "6641:21:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_payable_$_t_uint256_$returns$__$", "typeString": "function (address payable,uint256)" } }, "id": 1167, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6641:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1168, "nodeType": "ExpressionStatement", "src": "6641:56:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1170, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1148, "src": "6735:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1171, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6750:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1169, "name": "AccruedFeesWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 854, "src": "6713:21:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 1172, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6713:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1173, "nodeType": "EmitStatement", "src": "6708:61:4" }, { "expression": { "argumentTypes": null, "id": 1176, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1174, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6779:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 1175, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "6800:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "6779:22:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1177, "nodeType": "ExpressionStatement", "src": "6779:22:4" } ] }, "documentation": { "id": 1146, "nodeType": "StructuredDocumentation", "src": "5906:359:4", "text": "@dev Withdraw whole balance of all fees accrued in the contract so far.\n@param targetAddress : address to send tokens to\n@param txExpirationBlock : block number until which is the transaction valid (inclusive).\n When transaction is processed after this block, it fails.\n@dev Owners only" }, "functionSelector": "323f4501", "id": 1179, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1153, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1150, "src": "6401:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1154, "modifierName": { "argumentTypes": null, "id": 1152, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "6382:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "6382:37:4" }, { "arguments": null, "id": 1156, "modifierName": { "argumentTypes": null, "id": 1155, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 909, "src": "6428:9:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "6428:9:4" } ], "name": "withdrawAccruedFees", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1151, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1148, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1179, "src": "6299:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1147, "name": "address", "nodeType": "ElementaryTypeName", "src": "6299:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1150, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1179, "src": "6330:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1149, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6330:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "6298:58:4" }, "returnParameters": { "id": 1157, "nodeType": "ParameterList", "parameters": [], "src": "6442:0:4" }, "scope": 1440, "src": "6270:538:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1242, "nodeType": "Block", "src": "7644:794:4", "statements": [ { "assignments": [ 1193 ], "declarations": [ { "constant": false, "id": 1193, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1242, "src": "7654:23:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1192, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "7654:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1194, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "7654:23:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1198, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1195, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "7692:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1196, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "7708:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1197, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7708:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "7692:31:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1211, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1208, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "7815:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1209, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "7831:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1210, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7831:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "7815:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 1223, "nodeType": "IfStatement", "src": "7811:113:4", "trueBody": { "id": 1222, "nodeType": "Block", "src": "7850:74:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1220, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1212, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1193, "src": "7864:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1217, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "7907:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1216, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "7899:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1215, "name": "address", "nodeType": "ElementaryTypeName", "src": "7899:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1218, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7899:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 1213, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "7882:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1214, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 2588, "src": "7882:16:4", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 1219, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7882:31:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "7864:49:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1221, "nodeType": "ExpressionStatement", "src": "7864:49:4" } ] } }, "id": 1224, "nodeType": "IfStatement", "src": "7688:236:4", "trueBody": { "id": 1207, "nodeType": "Block", "src": "7733:64:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1205, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1199, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1193, "src": "7747:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1202, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "7773:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1201, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "7765:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1200, "name": "address", "nodeType": "ElementaryTypeName", "src": "7765:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1203, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7765:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "id": 1204, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balance", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7765:21:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "7747:39:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1206, "nodeType": "ExpressionStatement", "src": "7747:39:4" } ] } }, { "assignments": [ 1226 ], "declarations": [ { "constant": false, "id": 1226, "mutability": "mutable", "name": "excessAmount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1242, "src": "8244:20:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1225, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8244:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1231, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1229, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "8287:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1227, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1193, "src": "8267:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1228, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 1829, "src": "8267:19:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1230, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8267:39:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "8244:62:4" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1233, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1182, "src": "8338:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1234, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1226, "src": "8353:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1232, "name": "_withdrawFromContract", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1439, "src": "8316:21:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_payable_$_t_uint256_$returns$__$", "typeString": "function (address payable,uint256)" } }, "id": 1235, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8316:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1236, "nodeType": "ExpressionStatement", "src": "8316:50:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1238, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1182, "src": "8403:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1239, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1226, "src": "8418:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1237, "name": "ExcessTokenWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 848, "src": "8381:21:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 1240, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8381:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1241, "nodeType": "EmitStatement", "src": "8376:55:4" } ] }, "documentation": { "id": 1180, "nodeType": "StructuredDocumentation", "src": "6815:651:4", "text": "@dev Withdraw \"excess\" tokens, which were sent to contract address directly via direct ERC20 transfer(...) or transferFrom(...),\n without interacting with API of this (FetchOracle) contract, what could be done only by mistake.\n Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such\n \"excess\" tokens out of contract.\n@param targetAddress : address to send tokens to\n@param txExpirationBlock : block number until which is the transaction valid (inclusive).\n When transaction is processed after this block, it fails." }, "functionSelector": "53e052ac", "id": 1243, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1187, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1184, "src": "7603:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1188, "modifierName": { "argumentTypes": null, "id": 1186, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "7584:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "7584:37:4" }, { "arguments": null, "id": 1190, "modifierName": { "argumentTypes": null, "id": 1189, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 909, "src": "7630:9:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "7630:9:4" } ], "name": "withdrawExcessTokens", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1185, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1182, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1243, "src": "7501:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1181, "name": "address", "nodeType": "ElementaryTypeName", "src": "7501:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1184, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1243, "src": "7532:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1183, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "7532:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "7500:58:4" }, "returnParameters": { "id": 1191, "nodeType": "ParameterList", "parameters": [], "src": "7644:0:4" }, "scope": 1440, "src": "7471:967:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1295, "nodeType": "Block", "src": "8926:469:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1260, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1257, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 877, "src": "8944:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">=", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1258, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "8963:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1259, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8963:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "8944:36:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4561726c696573742064656c657465206e6f742072656163686564", "id": 1261, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "8982:29:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" }, "value": "Earliest delete not reached" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" } ], "id": 1256, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "8936:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1262, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8936:76:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1263, "nodeType": "ExpressionStatement", "src": "8936:76:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1267, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1264, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "9026:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1265, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "9042:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1266, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9042:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "9026:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 1287, "nodeType": "IfStatement", "src": "9022:191:4", "trueBody": { "id": 1286, "nodeType": "Block", "src": "9061:152:4", "statements": [ { "assignments": [ 1269 ], "declarations": [ { "constant": false, "id": 1269, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1286, "src": "9075:23:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1268, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9075:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1277, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1274, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "9126:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1273, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "9118:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1272, "name": "address", "nodeType": "ElementaryTypeName", "src": "9118:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1275, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9118:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 1270, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "9101:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1271, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 2588, "src": "9101:16:4", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 1276, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9101:31:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "9075:57:4" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1281, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1246, "src": "9170:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1282, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1269, "src": "9185:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1279, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "9154:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1280, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 2598, "src": "9154:15:4", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 1283, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9154:47:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" } ], "id": 1278, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "9146:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, "id": 1284, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9146:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1285, "nodeType": "ExpressionStatement", "src": "9146:56:4" } ] } }, { "eventCall": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1288, "name": "ContractDeleted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 856, "src": "9227:15:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 1289, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9227:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1290, "nodeType": "EmitStatement", "src": "9222:22:4" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1292, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1246, "src": "9374:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 1291, "name": "selfdestruct", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -21, "src": "9361:12:4", "typeDescriptions": { "typeIdentifier": "t_function_selfdestruct_nonpayable$_t_address_payable_$returns$__$", "typeString": "function (address payable)" } }, "id": 1293, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9361:27:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1294, "nodeType": "ExpressionStatement", "src": "9361:27:4" } ] }, "documentation": { "id": 1244, "nodeType": "StructuredDocumentation", "src": "8445:309:4", "text": "@dev Delete the contract, transfers the remaining token and ether balance to the specified\npayoutAddress\n@param payoutAddress address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\n@dev owner only + only on or after `_earliestDelete` block" }, "functionSelector": "9608df4b", "id": 1296, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1251, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1248, "src": "8885:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1252, "modifierName": { "argumentTypes": null, "id": 1250, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "8866:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "8866:37:4" }, { "arguments": null, "id": 1254, "modifierName": { "argumentTypes": null, "id": 1253, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 909, "src": "8912:9:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "8912:9:4" } ], "name": "deleteContract", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1249, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1246, "mutability": "mutable", "name": "payoutAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1296, "src": "8783:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1245, "name": "address", "nodeType": "ElementaryTypeName", "src": "8783:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1248, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1296, "src": "8814:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1247, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8814:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "8782:58:4" }, "returnParameters": { "id": 1255, "nodeType": "ParameterList", "parameters": [], "src": "8926:0:4" }, "scope": 1440, "src": "8759:636:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1304, "nodeType": "Block", "src": "9471:60:4", "statements": [ { "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1301, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "9488:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1302, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "9488:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 1300, "id": 1303, "nodeType": "Return", "src": "9481:43:4" } ] }, "documentation": null, "functionSelector": "9bc18909", "id": 1305, "implemented": true, "kind": "function", "modifiers": [], "name": "oracleValueLastUpdated", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1297, "nodeType": "ParameterList", "parameters": [], "src": "9433:2:4" }, "returnParameters": { "id": 1300, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1299, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1305, "src": "9458:7:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1298, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9458:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "9457:9:4" }, "scope": 1440, "src": "9402:129:4", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 1313, "nodeType": "Block", "src": "9607:36:4", "statements": [ { "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1310, "name": "block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -4, "src": "9624:5:4", "typeDescriptions": { "typeIdentifier": "t_magic_block", "typeString": "block" } }, "id": 1311, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "number", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9624:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 1309, "id": 1312, "nodeType": "Return", "src": "9617:19:4" } ] }, "documentation": null, "id": 1314, "implemented": true, "kind": "function", "modifiers": [], "name": "_getBlockNumber", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1306, "nodeType": "ParameterList", "parameters": [], "src": "9561:2:4" }, "returnParameters": { "id": 1309, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1308, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1314, "src": "9594:7:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1307, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9594:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "9593:9:4" }, "scope": 1440, "src": "9537:106:4", "stateMutability": "view", "virtual": true, "visibility": "internal" }, { "body": { "id": 1387, "nodeType": "Block", "src": "9745:1001:4", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1321, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1318, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "9759:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1319, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "9775:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1320, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9775:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "9759:31:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1356, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1353, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "10418:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1354, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "10434:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1355, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10434:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "10418:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1377, "nodeType": "Block", "src": "10605:75:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "66616c7365", "id": 1373, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", "src": "10627:5:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, { "argumentTypes": null, "hexValue": "556e657870656374656420636f6e7472616374206665652063757272656e6379", "id": 1374, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10634:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" }, "value": "Unexpected contract fee currency" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" } ], "id": 1372, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10619:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1375, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10619:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1376, "nodeType": "ExpressionStatement", "src": "10619:50:4" } ] }, "id": 1378, "nodeType": "IfStatement", "src": "10414:266:4", "trueBody": { "id": 1371, "nodeType": "Block", "src": "10461:122:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1360, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10503:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1361, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10503:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1364, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "10523:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1363, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "10515:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1362, "name": "address", "nodeType": "ElementaryTypeName", "src": "10515:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1365, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10515:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 1366, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10530:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1358, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "10483:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1359, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transferFrom", "nodeType": "MemberAccess", "referencedDeclaration": 2630, "src": "10483:19:4", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,address,uint256) external returns (bool)" } }, "id": 1367, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10483:52:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "496e7375662e2046455420616c6c6f772e206f6e2063616c6c65722061646472", "id": 1368, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10537:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_a65cdee615e67dbd2cf8397ad58720c2c52ee993f0d8f62cb315ac2d7f1f9f3f", "typeString": "literal_string \"Insuf. FET allow. on caller addr\"" }, "value": "Insuf. FET allow. on caller addr" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_a65cdee615e67dbd2cf8397ad58720c2c52ee993f0d8f62cb315ac2d7f1f9f3f", "typeString": "literal_string \"Insuf. FET allow. on caller addr\"" } ], "id": 1357, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10475:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1369, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10475:97:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1370, "nodeType": "ExpressionStatement", "src": "10475:97:4" } ] } }, "id": 1379, "nodeType": "IfStatement", "src": "9755:925:4", "trueBody": { "id": 1352, "nodeType": "Block", "src": "9800:600:4", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1325, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1322, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9817:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1323, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9817:9:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "id": 1324, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "9830:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "9817:17:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1330, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1327, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10081:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1328, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10081:9:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "id": 1329, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10093:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "10081:16:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1349, "nodeType": "Block", "src": "10309:81:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "66616c7365", "id": 1345, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", "src": "10334:5:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, { "argumentTypes": null, "hexValue": "496e7375662e2045544820616d6f756e742073656e742062792063616c6c6572", "id": 1346, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10341:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_cb0058c10826e687d07067bad869c8c10a79c4914786ac0532f9d46b4ce50fc7", "typeString": "literal_string \"Insuf. ETH amount sent by caller\"" }, "value": "Insuf. ETH amount sent by caller" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_cb0058c10826e687d07067bad869c8c10a79c4914786ac0532f9d46b4ce50fc7", "typeString": "literal_string \"Insuf. ETH amount sent by caller\"" } ], "id": 1344, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10326:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1347, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10326:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1348, "nodeType": "ExpressionStatement", "src": "10326:50:4" } ] }, "id": 1350, "nodeType": "IfStatement", "src": "10077:313:4", "trueBody": { "id": 1343, "nodeType": "Block", "src": "10110:171:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1339, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10261:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1336, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10247:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1337, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10247:9:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1338, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 1829, "src": "10247:13:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1340, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10247:19:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1331, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10227:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1334, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10227:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "id": 1335, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10227:19:4", "typeDescriptions": { "typeIdentifier": "t_function_transfer_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1341, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10227:40:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1342, "nodeType": "ExpressionStatement", "src": "10227:40:4" } ] } }, "id": 1351, "nodeType": "IfStatement", "src": "9813:577:4", "trueBody": { "id": 1326, "nodeType": "Block", "src": "9847:213:4", "statements": [] } } ] } }, { "expression": { "argumentTypes": null, "id": 1385, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1380, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "10690:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1383, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10734:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1381, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "10711:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1382, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 1812, "src": "10711:22:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1384, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10711:28:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "10690:49:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1386, "nodeType": "ExpressionStatement", "src": "10690:49:4" } ] }, "documentation": { "id": 1315, "nodeType": "StructuredDocumentation", "src": "9650:53:4", "text": "@dev Withdraw fee from the caller." }, "id": 1388, "implemented": true, "kind": "function", "modifiers": [], "name": "_withdrawFee", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1316, "nodeType": "ParameterList", "parameters": [], "src": "9729:2:4" }, "returnParameters": { "id": 1317, "nodeType": "ParameterList", "parameters": [], "src": "9745:0:4" }, "scope": 1440, "src": "9708:1038:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 1438, "nodeType": "Block", "src": "10844:699:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "id": 1401, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1396, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1390, "src": "10862:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "30", "id": 1399, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "10887:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" } ], "id": 1398, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "10879:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1397, "name": "address", "nodeType": "ElementaryTypeName", "src": "10879:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1400, "isConstant": false, "isLValue": false, "isPure": true, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10879:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "10862:27:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "546172676574206973207a65726f2061646472657373", "id": 1402, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10891:24:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_cccbd8782d968807cd85c549e83adb6604b8463e9d07bb08e05c43c68c493c61", "typeString": "literal_string \"Target is zero address\"" }, "value": "Target is zero address" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_cccbd8782d968807cd85c549e83adb6604b8463e9d07bb08e05c43c68c493c61", "typeString": "literal_string \"Target is zero address\"" } ], "id": 1395, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10854:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1403, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10854:62:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1404, "nodeType": "ExpressionStatement", "src": "10854:62:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1408, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1405, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "10931:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1406, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "10947:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1407, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10947:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "10931:31:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1419, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1416, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "11167:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1417, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "11183:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1418, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11183:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "11167:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1435, "nodeType": "Block", "src": "11462:75:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "66616c7365", "id": 1431, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", "src": "11484:5:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, { "argumentTypes": null, "hexValue": "556e657870656374656420636f6e7472616374206665652063757272656e6379", "id": 1432, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "11491:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" }, "value": "Unexpected contract fee currency" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" } ], "id": 1430, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "11476:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1433, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11476:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1434, "nodeType": "ExpressionStatement", "src": "11476:50:4" } ] }, "id": 1436, "nodeType": "IfStatement", "src": "11163:374:4", "trueBody": { "id": 1429, "nodeType": "Block", "src": "11210:230:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1423, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1390, "src": "11370:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1424, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1392, "src": "11385:6:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1421, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "11354:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1422, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 2598, "src": "11354:15:4", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 1425, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11354:38:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "496e737566662e204645542066756e6473206f6e20636f6e74722e2061646472", "id": 1426, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "11394:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_ac4273e35d12186f8e3dd3c3e48a2a5436d20bacdfa1045742d7a790fa7c7376", "typeString": "literal_string \"Insuff. FET funds on contr. addr\"" }, "value": "Insuff. FET funds on contr. addr" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_ac4273e35d12186f8e3dd3c3e48a2a5436d20bacdfa1045742d7a790fa7c7376", "typeString": "literal_string \"Insuff. FET funds on contr. addr\"" } ], "id": 1420, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "11346:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1427, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11346:83:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1428, "nodeType": "ExpressionStatement", "src": "11346:83:4" } ] } }, "id": 1437, "nodeType": "IfStatement", "src": "10927:610:4", "trueBody": { "id": 1415, "nodeType": "Block", "src": "10972:177:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1412, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1392, "src": "11131:6:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1409, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1390, "src": "11108:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "id": 1411, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11108:22:4", "typeDescriptions": { "typeIdentifier": "t_function_transfer_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1413, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11108:30:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1414, "nodeType": "ExpressionStatement", "src": "11108:30:4" } ] } } ] }, "documentation": null, "id": 1439, "implemented": true, "kind": "function", "modifiers": [], "name": "_withdrawFromContract", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1393, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1390, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1439, "src": "10784:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1389, "name": "address", "nodeType": "ElementaryTypeName", "src": "10784:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1392, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1439, "src": "10815:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1391, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10815:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "10783:47:4" }, "returnParameters": { "id": 1394, "nodeType": "ParameterList", "parameters": [], "src": "10844:0:4" }, "scope": 1440, "src": "10753:790:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" } ], "scope": 1441, "src": "1550:9995:4" } ], "src": "820:10726:4" }, "legacyAST": { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/contracts/FetchOracle.sol", "exportedSymbols": { "FetchOracle": [ 1440 ] }, "id": 1441, "license": "Apache-2.0", "nodeType": "SourceUnit", "nodes": [ { "id": 815, "literals": [ "solidity", "^", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "820:23:4" }, { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/token/ERC20/IERC20.sol", "file": "../openzeppelin/contracts/token/ERC20/IERC20.sol", "id": 816, "nodeType": "ImportDirective", "scope": 1441, "sourceUnit": 2650, "src": "845:58:4", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/access/AccessControl.sol", "file": "../openzeppelin/contracts/access/AccessControl.sol", "id": 817, "nodeType": "ImportDirective", "scope": 1441, "sourceUnit": 1784, "src": "904:60:4", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/home/james/Code/cosmos-drb-oracle/ethereum/openzeppelin/contracts/math/SafeMath.sol", "file": "../openzeppelin/contracts/math/SafeMath.sol", "id": 818, "nodeType": "ImportDirective", "scope": 1441, "sourceUnit": 1980, "src": "965:53:4", "symbolAliases": [], "unitAlias": "" }, { "abstract": false, "baseContracts": [ { "arguments": null, "baseName": { "contractScope": null, "id": 820, "name": "AccessControl", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 1783, "src": "1574:13:4", "typeDescriptions": { "typeIdentifier": "t_contract$_AccessControl_$1783", "typeString": "contract AccessControl" } }, "id": 821, "nodeType": "InheritanceSpecifier", "src": "1574:13:4" } ], "contractDependencies": [ 1500, 1783 ], "contractKind": "contract", "documentation": { "id": 819, "nodeType": "StructuredDocumentation", "src": "1021:528:4", "text": "@dev The contract *INTENTIONALLY* does not contain the `receive()` or fallback functions in order to ensure that all\nunsolicited direct ETH transfers in to this contract (in direct Transaction) will fail.\nOnly way how to make successful fee transfer in ETH currency (*IF* contract is initialised for ETH as fee currency),\nis to make transfer ETH to caller(client) contract and client contract then is responsible for further\nrogrammatic* (= *not* via Tx) transfer of fee to this this contract." }, "fullyImplemented": true, "id": 1440, "linearizedBaseContracts": [ 1440, 1783, 1500 ], "name": "FetchOracle", "nodeType": "ContractDefinition", "nodes": [ { "id": 824, "libraryName": { "contractScope": null, "id": 822, "name": "SafeMath", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 1979, "src": "1600:8:4", "typeDescriptions": { "typeIdentifier": "t_contract$_SafeMath_$1979", "typeString": "library SafeMath" } }, "nodeType": "UsingForDirective", "src": "1594:27:4", "typeName": { "id": 823, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1613:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } }, { "canonicalName": "FetchOracle.FeeCurrency", "id": 827, "members": [ { "id": 825, "name": "ERC20", "nodeType": "EnumValue", "src": "1648:5:4" }, { "id": 826, "name": "ETH", "nodeType": "EnumValue", "src": "1655:3:4" } ], "name": "FeeCurrency", "nodeType": "EnumDefinition", "src": "1628:32:4" }, { "canonicalName": "FetchOracle.OracleValue", "id": 834, "members": [ { "constant": false, "id": 829, "mutability": "mutable", "name": "value", "nodeType": "VariableDeclaration", "overrides": null, "scope": 834, "src": "1695:13:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 828, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1695:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 831, "mutability": "mutable", "name": "decimals", "nodeType": "VariableDeclaration", "overrides": null, "scope": 834, "src": "1718:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 830, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "1718:5:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 833, "mutability": "mutable", "name": "updatedAtEthBlockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 834, "src": "1742:31:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 832, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1742:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "name": "OracleValue", "nodeType": "StructDefinition", "scope": 1440, "src": "1666:114:4", "visibility": "public" }, { "anonymous": false, "documentation": null, "id": 838, "name": "FeeUpdated", "nodeType": "EventDefinition", "parameters": { "id": 837, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 836, "indexed": false, "mutability": "mutable", "name": "fee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 838, "src": "1803:11:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 835, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1803:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1802:13:4" }, "src": "1786:30:4" }, { "anonymous": false, "documentation": null, "id": 842, "name": "Pause", "nodeType": "EventDefinition", "parameters": { "id": 841, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 840, "indexed": false, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 842, "src": "1833:18:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 839, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1833:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1832:20:4" }, "src": "1821:32:4" }, { "anonymous": false, "documentation": null, "id": 848, "name": "ExcessTokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 847, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 844, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 848, "src": "1886:21:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 843, "name": "address", "nodeType": "ElementaryTypeName", "src": "1886:7:4", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 846, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 848, "src": "1909:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 845, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1909:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1885:39:4" }, "src": "1858:67:4" }, { "anonymous": false, "documentation": null, "id": 854, "name": "AccruedFeesWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 853, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 850, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 854, "src": "1958:21:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 849, "name": "address", "nodeType": "ElementaryTypeName", "src": "1958:7:4", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 852, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 854, "src": "1981:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 851, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1981:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "1957:39:4" }, "src": "1930:67:4" }, { "anonymous": false, "documentation": null, "id": 856, "name": "ContractDeleted", "nodeType": "EventDefinition", "parameters": { "id": 855, "nodeType": "ParameterList", "parameters": [], "src": "2023:2:4" }, "src": "2002:24:4" }, { "anonymous": false, "documentation": null, "id": 860, "name": "OracleValueUpdated", "nodeType": "EventDefinition", "parameters": { "id": 859, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 858, "indexed": false, "mutability": "mutable", "name": "atBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 860, "src": "2056:15:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 857, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2056:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "2055:17:4" }, "src": "2031:42:4" }, { "constant": true, "functionSelector": "07e2cea5", "id": 865, "mutability": "constant", "name": "ORACLE_ROLE", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2081:62:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, "typeName": { "id": 861, "name": "bytes32", "nodeType": "ElementaryTypeName", "src": "2081:7:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "value": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "4f5241434c455f524f4c45", "id": 863, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2129:13:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_68e79a7bf1e0bc45d0a330c573bc367f9cf464fd326078812f301165fbda4ef1", "typeString": "literal_string \"ORACLE_ROLE\"" }, "value": "ORACLE_ROLE" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_stringliteral_68e79a7bf1e0bc45d0a330c573bc367f9cf464fd326078812f301165fbda4ef1", "typeString": "literal_string \"ORACLE_ROLE\"" } ], "id": 862, "name": "keccak256", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -8, "src": "2119:9:4", "typeDescriptions": { "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", "typeString": "function (bytes memory) pure returns (bytes32)" } }, "id": 864, "isConstant": false, "isLValue": false, "isPure": true, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2119:24:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "visibility": "public" }, { "constant": true, "functionSelector": "c0ba241b", "id": 870, "mutability": "constant", "name": "DELEGATE_ROLE", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2149:66:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, "typeName": { "id": 866, "name": "bytes32", "nodeType": "ElementaryTypeName", "src": "2149:7:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "value": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "44454c45474154455f524f4c45", "id": 868, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2199:15:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" }, "value": "DELEGATE_ROLE" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" } ], "id": 867, "name": "keccak256", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -8, "src": "2189:9:4", "typeDescriptions": { "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", "typeString": "function (bytes memory) pure returns (bytes32)" } }, "id": 869, "isConstant": false, "isLValue": false, "isPure": true, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2189:26:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "visibility": "public" }, { "constant": true, "functionSelector": "372646bb", "id": 873, "mutability": "constant", "name": "DELETE_PROTECTION_PERIOD", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2221:57:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 871, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2221:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": { "argumentTypes": null, "hexValue": "333730323835", "id": 872, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "2272:6:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_370285_by_1", "typeString": "int_const 370285" }, "value": "370285" }, "visibility": "public" }, { "constant": false, "functionSelector": "ecd0c0c3", "id": 875, "mutability": "mutable", "name": "_token", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2335:20:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" }, "typeName": { "contractScope": null, "id": 874, "name": "IERC20", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2649, "src": "2335:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "32a1bd70", "id": 877, "mutability": "mutable", "name": "_earliestDelete", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2361:30:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 876, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2361:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "f1209ef7", "id": 879, "mutability": "mutable", "name": "_pausedSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2397:32:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 878, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2397:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "c5b37c22", "id": 881, "mutability": "mutable", "name": "_fee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2435:19:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 880, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2435:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "d83222ec", "id": 883, "mutability": "mutable", "name": "_accruedFeesAmount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2460:33:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 882, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2460:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "id": 885, "mutability": "mutable", "name": "_oracleValue", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2499:32:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue" }, "typeName": { "contractScope": null, "id": 884, "name": "OracleValue", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 834, "src": "2499:11:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage_ptr", "typeString": "struct FetchOracle.OracleValue" } }, "value": null, "visibility": "private" }, { "constant": false, "functionSelector": "993240a3", "id": 887, "mutability": "mutable", "name": "_feeCurrency", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1440, "src": "2537:31:4", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "typeName": { "contractScope": null, "id": 886, "name": "FeeCurrency", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 827, "src": "2537:11:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "value": null, "visibility": "public" }, { "body": { "id": 898, "nodeType": "Block", "src": "2623:63:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 893, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1526, "src": "2648:18:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 894, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "2668:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 895, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "2668:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 892, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1572, "src": "2640:7:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 896, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2640:39:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "functionReturnParameters": 891, "id": 897, "nodeType": "Return", "src": "2633:46:4" } ] }, "documentation": null, "id": 899, "implemented": true, "kind": "function", "modifiers": [], "name": "_isOwner", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 888, "nodeType": "ParameterList", "parameters": [], "src": "2592:2:4" }, "returnParameters": { "id": 891, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 890, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 899, "src": "2617:4:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 889, "name": "bool", "nodeType": "ElementaryTypeName", "src": "2617:4:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "src": "2616:6:4" }, "scope": 1440, "src": "2575:111:4", "stateMutability": "view", "virtual": false, "visibility": "internal" }, { "body": { "id": 908, "nodeType": "Block", "src": "2746:81:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 902, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 899, "src": "2764:8:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 903, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2764:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e6f7420616e2061646d696e6973747261746f72", "id": 904, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2776:32:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fff29d4bd770cdc17b9f156c578c5f49981008f3769f218da7df946262b27392", "typeString": "literal_string \"Caller is not an administrator\"" }, "value": "Caller is not an administrator" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fff29d4bd770cdc17b9f156c578c5f49981008f3769f218da7df946262b27392", "typeString": "literal_string \"Caller is not an administrator\"" } ], "id": 901, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "2756:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 905, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2756:53:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 906, "nodeType": "ExpressionStatement", "src": "2756:53:4" }, { "id": 907, "nodeType": "PlaceholderStatement", "src": "2819:1:4" } ] }, "documentation": null, "id": 909, "name": "onlyOwner", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 900, "nodeType": "ParameterList", "parameters": [], "src": "2743:2:4" }, "src": "2725:102:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 924, "nodeType": "Block", "src": "2902:125:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_bool", "typeString": "bool" }, "id": 919, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 912, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 899, "src": "2920:8:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 913, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2920:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "BinaryOperation", "operator": "||", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 915, "name": "DELEGATE_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 870, "src": "2942:13:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 916, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "2957:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 917, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "2957:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 914, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1572, "src": "2934:7:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 918, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2934:34:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "2920:48:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465", "id": 920, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "2970:38:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" }, "value": "Caller is neither owner nor delegate" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" } ], "id": 911, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "2912:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 921, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "2912:97:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 922, "nodeType": "ExpressionStatement", "src": "2912:97:4" }, { "id": 923, "nodeType": "PlaceholderStatement", "src": "3019:1:4" } ] }, "documentation": null, "id": 925, "name": "onlyDelegate", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 910, "nodeType": "ParameterList", "parameters": [], "src": "2899:2:4" }, "src": "2878:149:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 937, "nodeType": "Block", "src": "3089:96:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 929, "name": "ORACLE_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 865, "src": "3115:11:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 930, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "3128:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 931, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3128:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 928, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1572, "src": "3107:7:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 932, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3107:32:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e6f7420616e206f7261636c65", "id": 933, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "3141:25:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_8c1dc436c657406c837ef43aefe277157f6f9572ac3e706ad659a5d014391325", "typeString": "literal_string \"Caller is not an oracle\"" }, "value": "Caller is not an oracle" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_8c1dc436c657406c837ef43aefe277157f6f9572ac3e706ad659a5d014391325", "typeString": "literal_string \"Caller is not an oracle\"" } ], "id": 927, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "3099:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 934, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3099:68:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 935, "nodeType": "ExpressionStatement", "src": "3099:68:4" }, { "id": 936, "nodeType": "PlaceholderStatement", "src": "3177:1:4" } ] }, "documentation": null, "id": 938, "name": "onlyOracle", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 926, "nodeType": "ParameterList", "parameters": [], "src": "3086:2:4" }, "src": "3067:118:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 951, "nodeType": "Block", "src": "3244:96:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 946, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 943, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "3262:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 944, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3262:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 945, "name": "expirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 940, "src": "3283:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "3262:36:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73616374696f6e2065787069726564", "id": 947, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "3300:21:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" }, "value": "Transaction expired" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" } ], "id": 942, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "3254:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 948, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3254:68:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 949, "nodeType": "ExpressionStatement", "src": "3254:68:4" }, { "id": 950, "nodeType": "PlaceholderStatement", "src": "3332:1:4" } ] }, "documentation": null, "id": 952, "name": "verifyTxExpiration", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 941, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 940, "mutability": "mutable", "name": "expirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 952, "src": "3219:23:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 939, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3219:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3218:25:4" }, "src": "3191:149:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 963, "nodeType": "Block", "src": "3373:102:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 958, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 955, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "3391:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 956, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "3411:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 957, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3411:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "3391:37:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "436f6e747261637420686173206265656e20706175736564", "id": 959, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "3430:26:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" }, "value": "Contract has been paused" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" } ], "id": 954, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "3383:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 960, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3383:74:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 961, "nodeType": "ExpressionStatement", "src": "3383:74:4" }, { "id": 962, "nodeType": "PlaceholderStatement", "src": "3467:1:4" } ] }, "documentation": null, "id": 964, "name": "verifyNotPaused", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 953, "nodeType": "ParameterList", "parameters": [], "src": "3370:2:4" }, "src": "3346:129:4", "virtual": false, "visibility": "internal" }, { "body": { "id": 1023, "nodeType": "Block", "src": "3685:406:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 973, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1526, "src": "3706:18:4", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 974, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "3726:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 975, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3726:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 972, "name": "_setupRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1709, "src": "3695:10:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_bytes32_$_t_address_$returns$__$", "typeString": "function (bytes32,address)" } }, "id": 976, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3695:42:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 977, "nodeType": "ExpressionStatement", "src": "3695:42:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address", "typeString": "address" }, "id": 983, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 978, "name": "ERC20Address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 967, "src": "3752:12:4", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "30", "id": 981, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "3776:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" } ], "id": 980, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "3768:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 979, "name": "address", "nodeType": "ElementaryTypeName", "src": "3768:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 982, "isConstant": false, "isLValue": false, "isPure": true, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3768:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "3752:26:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1001, "nodeType": "Block", "src": "3841:100:4", "statements": [ { "expression": { "argumentTypes": null, "id": 993, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 990, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "3855:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 991, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "3870:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 992, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3870:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "3855:32:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "id": 994, "nodeType": "ExpressionStatement", "src": "3855:32:4" }, { "expression": { "argumentTypes": null, "id": 999, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 995, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "3901:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 997, "name": "ERC20Address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 967, "src": "3917:12:4", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 996, "name": "IERC20", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 2649, "src": "3910:6:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_IERC20_$2649_$", "typeString": "type(contract IERC20)" } }, "id": 998, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3910:20:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "src": "3901:29:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1000, "nodeType": "ExpressionStatement", "src": "3901:29:4" } ] }, "id": 1002, "nodeType": "IfStatement", "src": "3748:193:4", "trueBody": { "id": 989, "nodeType": "Block", "src": "3780:55:4", "statements": [ { "expression": { "argumentTypes": null, "id": 987, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 984, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "3794:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 985, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "3809:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 986, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "3809:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "3794:30:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "id": 988, "nodeType": "ExpressionStatement", "src": "3794:30:4" } ] } }, { "expression": { "argumentTypes": null, "id": 1009, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1003, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 877, "src": "3951:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1007, "name": "DELETE_PROTECTION_PERIOD", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 873, "src": "3991:24:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1004, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "3969:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1005, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3969:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1006, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 1812, "src": "3969:21:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1008, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "3969:47:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "3951:65:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1010, "nodeType": "ExpressionStatement", "src": "3951:65:4" }, { "expression": { "argumentTypes": null, "id": 1017, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1011, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "4026:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1016, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "~", "prefix": true, "src": "4046:11:4", "subExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "30", "id": 1014, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "4055:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" } ], "id": 1013, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "4047:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_uint256_$", "typeString": "type(uint256)" }, "typeName": { "id": 1012, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4047:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1015, "isConstant": false, "isLValue": false, "isPure": true, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4047:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4026:31:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1018, "nodeType": "ExpressionStatement", "src": "4026:31:4" }, { "expression": { "argumentTypes": null, "id": 1021, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1019, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "4067:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1020, "name": "initialFee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 969, "src": "4074:10:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4067:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1022, "nodeType": "ExpressionStatement", "src": "4067:17:4" } ] }, "documentation": { "id": 965, "nodeType": "StructuredDocumentation", "src": "3551:68:4", "text": "@param ERC20Address address of the ERC20 contract" }, "id": 1024, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 970, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 967, "mutability": "mutable", "name": "ERC20Address", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1024, "src": "3636:20:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 966, "name": "address", "nodeType": "ElementaryTypeName", "src": "3636:7:4", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 969, "mutability": "mutable", "name": "initialFee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1024, "src": "3658:18:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 968, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3658:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3635:42:4" }, "returnParameters": { "id": 971, "nodeType": "ParameterList", "parameters": [], "src": "3685:0:4" }, "scope": 1440, "src": "3624:467:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "body": { "id": 1062, "nodeType": "Block", "src": "4303:223:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1042, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1038, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4313:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1040, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": 829, "src": "4313:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1041, "name": "value", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1026, "src": "4334:5:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4313:26:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1043, "nodeType": "ExpressionStatement", "src": "4313:26:4" }, { "expression": { "argumentTypes": null, "id": 1048, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1044, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4349:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1046, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "decimals", "nodeType": "MemberAccess", "referencedDeclaration": 831, "src": "4349:21:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1047, "name": "decimals", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1028, "src": "4373:8:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "src": "4349:32:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "id": 1049, "nodeType": "ExpressionStatement", "src": "4349:32:4" }, { "expression": { "argumentTypes": null, "id": 1055, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1050, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4391:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1052, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "4391:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1053, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "4430:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1054, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4430:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4391:56:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1056, "nodeType": "ExpressionStatement", "src": "4391:56:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1058, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4481:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1059, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "4481:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1057, "name": "OracleValueUpdated", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 860, "src": "4462:18:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1060, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4462:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1061, "nodeType": "EmitStatement", "src": "4457:61:4" } ] }, "documentation": null, "functionSelector": "59f9f7cc", "id": 1063, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1033, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1030, "src": "4261:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1034, "modifierName": { "argumentTypes": null, "id": 1032, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "4242:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "4242:37:4" }, { "arguments": null, "id": 1036, "modifierName": { "argumentTypes": null, "id": 1035, "name": "onlyOracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 938, "src": "4288:10:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "4288:10:4" } ], "name": "updateOracleValue", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1031, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1026, "mutability": "mutable", "name": "value", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1063, "src": "4134:13:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1025, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4134:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1028, "mutability": "mutable", "name": "decimals", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1063, "src": "4157:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 1027, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "4157:5:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1030, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1063, "src": "4181:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1029, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4181:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4124:92:4" }, "returnParameters": { "id": 1037, "nodeType": "ParameterList", "parameters": [], "src": "4303:0:4" }, "scope": 1440, "src": "4098:428:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1090, "nodeType": "Block", "src": "4669:181:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1072, "name": "_withdrawFee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1388, "src": "4679:12:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 1073, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4679:14:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1074, "nodeType": "ExpressionStatement", "src": "4679:14:4" }, { "expression": { "argumentTypes": null, "id": 1078, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1075, "name": "value", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1066, "src": "4703:5:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1076, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4711:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1077, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": 829, "src": "4711:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4703:26:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1079, "nodeType": "ExpressionStatement", "src": "4703:26:4" }, { "expression": { "argumentTypes": null, "id": 1083, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1080, "name": "decimals", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1068, "src": "4739:8:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1081, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4750:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1082, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "decimals", "nodeType": "MemberAccess", "referencedDeclaration": 831, "src": "4750:21:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "src": "4739:32:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "id": 1084, "nodeType": "ExpressionStatement", "src": "4739:32:4" }, { "expression": { "argumentTypes": null, "id": 1088, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1085, "name": "updatedAtEthBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1070, "src": "4781:23:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1086, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "4807:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1087, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "4807:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "4781:62:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1089, "nodeType": "ExpressionStatement", "src": "4781:62:4" } ] }, "documentation": null, "functionSelector": "f7e1c748", "id": 1091, "implemented": true, "kind": "function", "modifiers": [], "name": "queryOracleValue", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1064, "nodeType": "ParameterList", "parameters": [], "src": "4558:2:4" }, "returnParameters": { "id": 1071, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1066, "mutability": "mutable", "name": "value", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1091, "src": "4601:13:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1065, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4601:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1068, "mutability": "mutable", "name": "decimals", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1091, "src": "4616:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 1067, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "4616:5:4", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1070, "mutability": "mutable", "name": "updatedAtEthBlockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1091, "src": "4632:31:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1069, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4632:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4600:64:4" }, "scope": 1440, "src": "4533:317:4", "stateMutability": "payable", "virtual": false, "visibility": "public" }, { "body": { "id": 1112, "nodeType": "Block", "src": "5254:58:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1106, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1104, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "5264:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 1105, "name": "fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1094, "src": "5271:3:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "5264:10:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1107, "nodeType": "ExpressionStatement", "src": "5264:10:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1109, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "5300:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1108, "name": "FeeUpdated", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 838, "src": "5289:10:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1110, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "5289:16:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1111, "nodeType": "EmitStatement", "src": "5284:21:4" } ] }, "documentation": { "id": 1092, "nodeType": "StructuredDocumentation", "src": "4857:248:4", "text": "@dev Pause the non-administrative interaction with the contract\n@param fee - value of fee\n@param txExpirationBlock - block number defined by Tx sender beyond which transaction becomes invalid\n@dev Owners only" }, "functionSelector": "52f7c988", "id": 1113, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1099, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1096, "src": "5210:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1100, "modifierName": { "argumentTypes": null, "id": 1098, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "5191:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "5191:37:4" }, { "arguments": null, "id": 1102, "modifierName": { "argumentTypes": null, "id": 1101, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 925, "src": "5237:12:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "5237:12:4" } ], "name": "setFee", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1097, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1094, "mutability": "mutable", "name": "fee", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1113, "src": "5126:11:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1093, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5126:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1096, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1113, "src": "5139:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1095, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5139:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "5125:40:4" }, "returnParameters": { "id": 1103, "nodeType": "ParameterList", "parameters": [], "src": "5254:0:4" }, "scope": 1440, "src": "5110:202:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1144, "nodeType": "Block", "src": "5701:198:4", "statements": [ { "assignments": [ 1127 ], "declarations": [ { "constant": false, "id": 1127, "mutability": "mutable", "name": "curr_block_number", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1144, "src": "5711:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1126, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5711:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1130, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1128, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "5739:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1129, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "5739:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "5711:45:4" }, { "expression": { "argumentTypes": null, "id": 1138, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1131, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "5766:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1134, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1132, "name": "block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1116, "src": "5786:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 1133, "name": "curr_block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1127, "src": "5801:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "5786:32:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseExpression": { "argumentTypes": null, "id": 1136, "name": "block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1116, "src": "5841:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1137, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "Conditional", "src": "5786:67:4", "trueExpression": { "argumentTypes": null, "id": 1135, "name": "curr_block_number", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1127, "src": "5821:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "5766:87:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1139, "nodeType": "ExpressionStatement", "src": "5766:87:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1141, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 879, "src": "5874:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1140, "name": "Pause", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 842, "src": "5868:5:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1142, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "5868:24:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1143, "nodeType": "EmitStatement", "src": "5863:29:4" } ] }, "documentation": { "id": 1114, "nodeType": "StructuredDocumentation", "src": "5319:220:4", "text": "@dev Pause the non-administrative interaction with the contract\n@param block_number disallow non-admin. interactions with contract for a _getBlockNumber() >= block_number\n@dev Owners only" }, "functionSelector": "347908df", "id": 1145, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1121, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1118, "src": "5657:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1122, "modifierName": { "argumentTypes": null, "id": 1120, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "5638:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "5638:37:4" }, { "arguments": null, "id": 1124, "modifierName": { "argumentTypes": null, "id": 1123, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 925, "src": "5684:12:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "5684:12:4" } ], "name": "pauseSince", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1119, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1116, "mutability": "mutable", "name": "block_number", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1145, "src": "5564:20:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1115, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5564:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1118, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1145, "src": "5586:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1117, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5586:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "5563:49:4" }, "returnParameters": { "id": 1125, "nodeType": "ParameterList", "parameters": [], "src": "5701:0:4" }, "scope": 1440, "src": "5544:355:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1178, "nodeType": "Block", "src": "6442:366:4", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1160, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1158, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6456:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 1159, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "6478:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "6456:23:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 1163, "nodeType": "IfStatement", "src": "6452:60:4", "trueBody": { "id": 1162, "nodeType": "Block", "src": "6481:31:4", "statements": [ { "expression": null, "functionReturnParameters": 1157, "id": 1161, "nodeType": "Return", "src": "6495:7:4" } ] } }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1165, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1148, "src": "6663:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1166, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6678:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1164, "name": "_withdrawFromContract", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1439, "src": "6641:21:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_payable_$_t_uint256_$returns$__$", "typeString": "function (address payable,uint256)" } }, "id": 1167, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6641:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1168, "nodeType": "ExpressionStatement", "src": "6641:56:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1170, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1148, "src": "6735:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1171, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6750:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1169, "name": "AccruedFeesWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 854, "src": "6713:21:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 1172, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6713:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1173, "nodeType": "EmitStatement", "src": "6708:61:4" }, { "expression": { "argumentTypes": null, "id": 1176, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1174, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "6779:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 1175, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "6800:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "6779:22:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1177, "nodeType": "ExpressionStatement", "src": "6779:22:4" } ] }, "documentation": { "id": 1146, "nodeType": "StructuredDocumentation", "src": "5906:359:4", "text": "@dev Withdraw whole balance of all fees accrued in the contract so far.\n@param targetAddress : address to send tokens to\n@param txExpirationBlock : block number until which is the transaction valid (inclusive).\n When transaction is processed after this block, it fails.\n@dev Owners only" }, "functionSelector": "323f4501", "id": 1179, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1153, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1150, "src": "6401:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1154, "modifierName": { "argumentTypes": null, "id": 1152, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "6382:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "6382:37:4" }, { "arguments": null, "id": 1156, "modifierName": { "argumentTypes": null, "id": 1155, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 909, "src": "6428:9:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "6428:9:4" } ], "name": "withdrawAccruedFees", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1151, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1148, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1179, "src": "6299:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1147, "name": "address", "nodeType": "ElementaryTypeName", "src": "6299:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1150, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1179, "src": "6330:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1149, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6330:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "6298:58:4" }, "returnParameters": { "id": 1157, "nodeType": "ParameterList", "parameters": [], "src": "6442:0:4" }, "scope": 1440, "src": "6270:538:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1242, "nodeType": "Block", "src": "7644:794:4", "statements": [ { "assignments": [ 1193 ], "declarations": [ { "constant": false, "id": 1193, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1242, "src": "7654:23:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1192, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "7654:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1194, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "7654:23:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1198, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1195, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "7692:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1196, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "7708:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1197, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7708:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "7692:31:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1211, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1208, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "7815:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1209, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "7831:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1210, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7831:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "7815:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 1223, "nodeType": "IfStatement", "src": "7811:113:4", "trueBody": { "id": 1222, "nodeType": "Block", "src": "7850:74:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1220, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1212, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1193, "src": "7864:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1217, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "7907:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1216, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "7899:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1215, "name": "address", "nodeType": "ElementaryTypeName", "src": "7899:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1218, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7899:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 1213, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "7882:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1214, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 2588, "src": "7882:16:4", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 1219, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7882:31:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "7864:49:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1221, "nodeType": "ExpressionStatement", "src": "7864:49:4" } ] } }, "id": 1224, "nodeType": "IfStatement", "src": "7688:236:4", "trueBody": { "id": 1207, "nodeType": "Block", "src": "7733:64:4", "statements": [ { "expression": { "argumentTypes": null, "id": 1205, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1199, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1193, "src": "7747:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1202, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "7773:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1201, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "7765:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1200, "name": "address", "nodeType": "ElementaryTypeName", "src": "7765:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1203, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7765:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "id": 1204, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balance", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7765:21:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "7747:39:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1206, "nodeType": "ExpressionStatement", "src": "7747:39:4" } ] } }, { "assignments": [ 1226 ], "declarations": [ { "constant": false, "id": 1226, "mutability": "mutable", "name": "excessAmount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1242, "src": "8244:20:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1225, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8244:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1231, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1229, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "8287:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1227, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1193, "src": "8267:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1228, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 1829, "src": "8267:19:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1230, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8267:39:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "8244:62:4" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1233, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1182, "src": "8338:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1234, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1226, "src": "8353:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1232, "name": "_withdrawFromContract", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1439, "src": "8316:21:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_payable_$_t_uint256_$returns$__$", "typeString": "function (address payable,uint256)" } }, "id": 1235, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8316:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1236, "nodeType": "ExpressionStatement", "src": "8316:50:4" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1238, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1182, "src": "8403:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1239, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1226, "src": "8418:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 1237, "name": "ExcessTokenWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 848, "src": "8381:21:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 1240, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8381:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1241, "nodeType": "EmitStatement", "src": "8376:55:4" } ] }, "documentation": { "id": 1180, "nodeType": "StructuredDocumentation", "src": "6815:651:4", "text": "@dev Withdraw \"excess\" tokens, which were sent to contract address directly via direct ERC20 transfer(...) or transferFrom(...),\n without interacting with API of this (FetchOracle) contract, what could be done only by mistake.\n Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such\n \"excess\" tokens out of contract.\n@param targetAddress : address to send tokens to\n@param txExpirationBlock : block number until which is the transaction valid (inclusive).\n When transaction is processed after this block, it fails." }, "functionSelector": "53e052ac", "id": 1243, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1187, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1184, "src": "7603:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1188, "modifierName": { "argumentTypes": null, "id": 1186, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "7584:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "7584:37:4" }, { "arguments": null, "id": 1190, "modifierName": { "argumentTypes": null, "id": 1189, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 909, "src": "7630:9:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "7630:9:4" } ], "name": "withdrawExcessTokens", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1185, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1182, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1243, "src": "7501:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1181, "name": "address", "nodeType": "ElementaryTypeName", "src": "7501:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1184, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1243, "src": "7532:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1183, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "7532:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "7500:58:4" }, "returnParameters": { "id": 1191, "nodeType": "ParameterList", "parameters": [], "src": "7644:0:4" }, "scope": 1440, "src": "7471:967:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1295, "nodeType": "Block", "src": "8926:469:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1260, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1257, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 877, "src": "8944:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">=", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1258, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1314, "src": "8963:15:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 1259, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8963:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "8944:36:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4561726c696573742064656c657465206e6f742072656163686564", "id": 1261, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "8982:29:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" }, "value": "Earliest delete not reached" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" } ], "id": 1256, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "8936:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1262, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8936:76:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1263, "nodeType": "ExpressionStatement", "src": "8936:76:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1267, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1264, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "9026:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1265, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "9042:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1266, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9042:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "9026:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 1287, "nodeType": "IfStatement", "src": "9022:191:4", "trueBody": { "id": 1286, "nodeType": "Block", "src": "9061:152:4", "statements": [ { "assignments": [ 1269 ], "declarations": [ { "constant": false, "id": 1269, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1286, "src": "9075:23:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1268, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9075:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 1277, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1274, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "9126:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1273, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "9118:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1272, "name": "address", "nodeType": "ElementaryTypeName", "src": "9118:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1275, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9118:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 1270, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "9101:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1271, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 2588, "src": "9101:16:4", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 1276, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9101:31:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "9075:57:4" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1281, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1246, "src": "9170:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1282, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1269, "src": "9185:15:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1279, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "9154:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1280, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 2598, "src": "9154:15:4", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 1283, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9154:47:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" } ], "id": 1278, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "9146:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, "id": 1284, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9146:56:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1285, "nodeType": "ExpressionStatement", "src": "9146:56:4" } ] } }, { "eventCall": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 1288, "name": "ContractDeleted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 856, "src": "9227:15:4", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 1289, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9227:17:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1290, "nodeType": "EmitStatement", "src": "9222:22:4" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1292, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1246, "src": "9374:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 1291, "name": "selfdestruct", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -21, "src": "9361:12:4", "typeDescriptions": { "typeIdentifier": "t_function_selfdestruct_nonpayable$_t_address_payable_$returns$__$", "typeString": "function (address payable)" } }, "id": 1293, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9361:27:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1294, "nodeType": "ExpressionStatement", "src": "9361:27:4" } ] }, "documentation": { "id": 1244, "nodeType": "StructuredDocumentation", "src": "8445:309:4", "text": "@dev Delete the contract, transfers the remaining token and ether balance to the specified\npayoutAddress\n@param payoutAddress address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\n@dev owner only + only on or after `_earliestDelete` block" }, "functionSelector": "9608df4b", "id": 1296, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 1251, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1248, "src": "8885:17:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 1252, "modifierName": { "argumentTypes": null, "id": 1250, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 952, "src": "8866:18:4", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "8866:37:4" }, { "arguments": null, "id": 1254, "modifierName": { "argumentTypes": null, "id": 1253, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 909, "src": "8912:9:4", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "8912:9:4" } ], "name": "deleteContract", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1249, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1246, "mutability": "mutable", "name": "payoutAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1296, "src": "8783:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1245, "name": "address", "nodeType": "ElementaryTypeName", "src": "8783:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1248, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1296, "src": "8814:25:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1247, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8814:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "8782:58:4" }, "returnParameters": { "id": 1255, "nodeType": "ParameterList", "parameters": [], "src": "8926:0:4" }, "scope": 1440, "src": "8759:636:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 1304, "nodeType": "Block", "src": "9471:60:4", "statements": [ { "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1301, "name": "_oracleValue", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 885, "src": "9488:12:4", "typeDescriptions": { "typeIdentifier": "t_struct$_OracleValue_$834_storage", "typeString": "struct FetchOracle.OracleValue storage ref" } }, "id": 1302, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "updatedAtEthBlockNumber", "nodeType": "MemberAccess", "referencedDeclaration": 833, "src": "9488:36:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 1300, "id": 1303, "nodeType": "Return", "src": "9481:43:4" } ] }, "documentation": null, "functionSelector": "9bc18909", "id": 1305, "implemented": true, "kind": "function", "modifiers": [], "name": "oracleValueLastUpdated", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1297, "nodeType": "ParameterList", "parameters": [], "src": "9433:2:4" }, "returnParameters": { "id": 1300, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1299, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1305, "src": "9458:7:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1298, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9458:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "9457:9:4" }, "scope": 1440, "src": "9402:129:4", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 1313, "nodeType": "Block", "src": "9607:36:4", "statements": [ { "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1310, "name": "block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -4, "src": "9624:5:4", "typeDescriptions": { "typeIdentifier": "t_magic_block", "typeString": "block" } }, "id": 1311, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "number", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9624:12:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 1309, "id": 1312, "nodeType": "Return", "src": "9617:19:4" } ] }, "documentation": null, "id": 1314, "implemented": true, "kind": "function", "modifiers": [], "name": "_getBlockNumber", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1306, "nodeType": "ParameterList", "parameters": [], "src": "9561:2:4" }, "returnParameters": { "id": 1309, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1308, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1314, "src": "9594:7:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1307, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9594:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "9593:9:4" }, "scope": 1440, "src": "9537:106:4", "stateMutability": "view", "virtual": true, "visibility": "internal" }, { "body": { "id": 1387, "nodeType": "Block", "src": "9745:1001:4", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1321, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1318, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "9759:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1319, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "9775:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1320, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9775:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "9759:31:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1356, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1353, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "10418:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1354, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "10434:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1355, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10434:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "10418:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1377, "nodeType": "Block", "src": "10605:75:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "66616c7365", "id": 1373, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", "src": "10627:5:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, { "argumentTypes": null, "hexValue": "556e657870656374656420636f6e7472616374206665652063757272656e6379", "id": 1374, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10634:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" }, "value": "Unexpected contract fee currency" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" } ], "id": 1372, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10619:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1375, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10619:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1376, "nodeType": "ExpressionStatement", "src": "10619:50:4" } ] }, "id": 1378, "nodeType": "IfStatement", "src": "10414:266:4", "trueBody": { "id": 1371, "nodeType": "Block", "src": "10461:122:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1360, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10503:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1361, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10503:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1364, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "10523:4:4", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$1440", "typeString": "contract FetchOracle" } ], "id": 1363, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "10515:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1362, "name": "address", "nodeType": "ElementaryTypeName", "src": "10515:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1365, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10515:13:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 1366, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10530:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1358, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "10483:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1359, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transferFrom", "nodeType": "MemberAccess", "referencedDeclaration": 2630, "src": "10483:19:4", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,address,uint256) external returns (bool)" } }, "id": 1367, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10483:52:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "496e7375662e2046455420616c6c6f772e206f6e2063616c6c65722061646472", "id": 1368, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10537:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_a65cdee615e67dbd2cf8397ad58720c2c52ee993f0d8f62cb315ac2d7f1f9f3f", "typeString": "literal_string \"Insuf. FET allow. on caller addr\"" }, "value": "Insuf. FET allow. on caller addr" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_a65cdee615e67dbd2cf8397ad58720c2c52ee993f0d8f62cb315ac2d7f1f9f3f", "typeString": "literal_string \"Insuf. FET allow. on caller addr\"" } ], "id": 1357, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10475:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1369, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10475:97:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1370, "nodeType": "ExpressionStatement", "src": "10475:97:4" } ] } }, "id": 1379, "nodeType": "IfStatement", "src": "9755:925:4", "trueBody": { "id": 1352, "nodeType": "Block", "src": "9800:600:4", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1325, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1322, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9817:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1323, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9817:9:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "id": 1324, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "9830:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "9817:17:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 1330, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1327, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10081:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1328, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10081:9:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "id": 1329, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10093:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "10081:16:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1349, "nodeType": "Block", "src": "10309:81:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "66616c7365", "id": 1345, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", "src": "10334:5:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, { "argumentTypes": null, "hexValue": "496e7375662e2045544820616d6f756e742073656e742062792063616c6c6572", "id": 1346, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10341:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_cb0058c10826e687d07067bad869c8c10a79c4914786ac0532f9d46b4ce50fc7", "typeString": "literal_string \"Insuf. ETH amount sent by caller\"" }, "value": "Insuf. ETH amount sent by caller" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_cb0058c10826e687d07067bad869c8c10a79c4914786ac0532f9d46b4ce50fc7", "typeString": "literal_string \"Insuf. ETH amount sent by caller\"" } ], "id": 1344, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10326:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1347, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10326:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1348, "nodeType": "ExpressionStatement", "src": "10326:50:4" } ] }, "id": 1350, "nodeType": "IfStatement", "src": "10077:313:4", "trueBody": { "id": 1343, "nodeType": "Block", "src": "10110:171:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1339, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10261:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1336, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10247:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1337, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "value", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10247:9:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1338, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 1829, "src": "10247:13:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1340, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10247:19:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1331, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10227:3:4", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 1334, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10227:10:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "id": 1335, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10227:19:4", "typeDescriptions": { "typeIdentifier": "t_function_transfer_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1341, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10227:40:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1342, "nodeType": "ExpressionStatement", "src": "10227:40:4" } ] } }, "id": 1351, "nodeType": "IfStatement", "src": "9813:577:4", "trueBody": { "id": 1326, "nodeType": "Block", "src": "9847:213:4", "statements": [] } } ] } }, { "expression": { "argumentTypes": null, "id": 1385, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 1380, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "10690:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1383, "name": "_fee", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 881, "src": "10734:4:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1381, "name": "_accruedFeesAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 883, "src": "10711:18:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1382, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 1812, "src": "10711:22:4", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 1384, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10711:28:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "10690:49:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 1386, "nodeType": "ExpressionStatement", "src": "10690:49:4" } ] }, "documentation": { "id": 1315, "nodeType": "StructuredDocumentation", "src": "9650:53:4", "text": "@dev Withdraw fee from the caller." }, "id": 1388, "implemented": true, "kind": "function", "modifiers": [], "name": "_withdrawFee", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1316, "nodeType": "ParameterList", "parameters": [], "src": "9729:2:4" }, "returnParameters": { "id": 1317, "nodeType": "ParameterList", "parameters": [], "src": "9745:0:4" }, "scope": 1440, "src": "9708:1038:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 1438, "nodeType": "Block", "src": "10844:699:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "id": 1401, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1396, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1390, "src": "10862:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "30", "id": 1399, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "10887:1:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" } ], "id": 1398, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "10879:7:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 1397, "name": "address", "nodeType": "ElementaryTypeName", "src": "10879:7:4", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 1400, "isConstant": false, "isLValue": false, "isPure": true, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10879:10:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "10862:27:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "546172676574206973207a65726f2061646472657373", "id": 1402, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "10891:24:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_cccbd8782d968807cd85c549e83adb6604b8463e9d07bb08e05c43c68c493c61", "typeString": "literal_string \"Target is zero address\"" }, "value": "Target is zero address" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_cccbd8782d968807cd85c549e83adb6604b8463e9d07bb08e05c43c68c493c61", "typeString": "literal_string \"Target is zero address\"" } ], "id": 1395, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "10854:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1403, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10854:62:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1404, "nodeType": "ExpressionStatement", "src": "10854:62:4" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1408, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1405, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "10931:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1406, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "10947:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1407, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ETH", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10947:15:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "10931:31:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" }, "id": 1419, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 1416, "name": "_feeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 887, "src": "11167:12:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 1417, "name": "FeeCurrency", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 827, "src": "11183:11:4", "typeDescriptions": { "typeIdentifier": "t_type$_t_enum$_FeeCurrency_$827_$", "typeString": "type(enum FetchOracle.FeeCurrency)" } }, "id": 1418, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "memberName": "ERC20", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11183:17:4", "typeDescriptions": { "typeIdentifier": "t_enum$_FeeCurrency_$827", "typeString": "enum FetchOracle.FeeCurrency" } }, "src": "11167:33:4", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 1435, "nodeType": "Block", "src": "11462:75:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "66616c7365", "id": 1431, "isConstant": false, "isLValue": false, "isPure": true, "kind": "bool", "lValueRequested": false, "nodeType": "Literal", "src": "11484:5:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "value": "false" }, { "argumentTypes": null, "hexValue": "556e657870656374656420636f6e7472616374206665652063757272656e6379", "id": 1432, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "11491:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" }, "value": "Unexpected contract fee currency" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_f131716b743e5ec47535766db932d6974cfad176c58ab32b82f54addc42de82c", "typeString": "literal_string \"Unexpected contract fee currency\"" } ], "id": 1430, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "11476:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1433, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11476:50:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1434, "nodeType": "ExpressionStatement", "src": "11476:50:4" } ] }, "id": 1436, "nodeType": "IfStatement", "src": "11163:374:4", "trueBody": { "id": 1429, "nodeType": "Block", "src": "11210:230:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1423, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1390, "src": "11370:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 1424, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1392, "src": "11385:6:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1421, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 875, "src": "11354:6:4", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$2649", "typeString": "contract IERC20" } }, "id": 1422, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 2598, "src": "11354:15:4", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 1425, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11354:38:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "496e737566662e204645542066756e6473206f6e20636f6e74722e2061646472", "id": 1426, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "11394:34:4", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_ac4273e35d12186f8e3dd3c3e48a2a5436d20bacdfa1045742d7a790fa7c7376", "typeString": "literal_string \"Insuff. FET funds on contr. addr\"" }, "value": "Insuff. FET funds on contr. addr" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_ac4273e35d12186f8e3dd3c3e48a2a5436d20bacdfa1045742d7a790fa7c7376", "typeString": "literal_string \"Insuff. FET funds on contr. addr\"" } ], "id": 1420, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "11346:7:4", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 1427, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11346:83:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1428, "nodeType": "ExpressionStatement", "src": "11346:83:4" } ] } }, "id": 1437, "nodeType": "IfStatement", "src": "10927:610:4", "trueBody": { "id": 1415, "nodeType": "Block", "src": "10972:177:4", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 1412, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1392, "src": "11131:6:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 1409, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 1390, "src": "11108:13:4", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "id": 1411, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11108:22:4", "typeDescriptions": { "typeIdentifier": "t_function_transfer_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 1413, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11108:30:4", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 1414, "nodeType": "ExpressionStatement", "src": "11108:30:4" } ] } } ] }, "documentation": null, "id": 1439, "implemented": true, "kind": "function", "modifiers": [], "name": "_withdrawFromContract", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 1393, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 1390, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1439, "src": "10784:29:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 1389, "name": "address", "nodeType": "ElementaryTypeName", "src": "10784:15:4", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 1392, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 1439, "src": "10815:14:4", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 1391, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10815:7:4", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "10783:47:4" }, "returnParameters": { "id": 1394, "nodeType": "ParameterList", "parameters": [], "src": "10844:0:4" }, "scope": 1440, "src": "10753:790:4", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" } ], "scope": 1441, "src": "1550:9995:4" } ], "src": "820:10726:4" }, "compiler": { "name": "solc", "version": "0.6.8+commit.0bbfe453.Emscripten.clang" }, "networks": {}, "schemaVersion": "3.0.23", "updatedAt": "2020-11-17T14:40:20.557Z", "devdoc": { "details": "The contract *INTENTIONALLY* does not contain the `receive()` or fallback functions in order to ensure that all unsolicited direct ETH transfers in to this contract (in direct Transaction) will fail. Only way how to make successful fee transfer in ETH currency (*IF* contract is initialised for ETH as fee currency), is to make transfer ETH to caller(client) contract and client contract then is responsible for further rogrammatic* (= *not* via Tx) transfer of fee to this this contract.", "methods": { "constructor": { "params": { "ERC20Address": "address of the ERC20 contract" } }, "deleteContract(address,uint256)": { "details": "Delete the contract, transfers the remaining token and ether balance to the specified payoutAddressowner only + only on or after `_earliestDelete` block", "params": { "payoutAddress": "address to transfer the balances to. Ensure that this is able to handle ERC20 tokens" } }, "getRoleAdmin(bytes32)": { "details": "Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. * To change a role's admin, use {_setRoleAdmin}." }, "getRoleMember(bytes32,uint256)": { "details": "Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. * Role bearers are not sorted in any particular way, and their ordering may change at any point. * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information." }, "getRoleMemberCount(bytes32)": { "details": "Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role." }, "grantRole(bytes32,address)": { "details": "Grants `role` to `account`. * If `account` had not been already granted `role`, emits a {RoleGranted} event. * Requirements: * - the caller must have ``role``'s admin role." }, "hasRole(bytes32,address)": { "details": "Returns `true` if `account` has been granted `role`." }, "pauseSince(uint256,uint256)": { "details": "Pause the non-administrative interaction with the contractOwners only", "params": { "block_number": "disallow non-admin. interactions with contract for a _getBlockNumber() >= block_number" } }, "renounceRole(bytes32,address)": { "details": "Revokes `role` from the calling account. * Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). * If the calling account had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must be `account`." }, "revokeRole(bytes32,address)": { "details": "Revokes `role` from `account`. * If `account` had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must have ``role``'s admin role." }, "setFee(uint256,uint256)": { "details": "Pause the non-administrative interaction with the contractOwners only", "params": { "fee": "- value of fee", "txExpirationBlock": "- block number defined by Tx sender beyond which transaction becomes invalid" } }, "withdrawAccruedFees(address,uint256)": { "details": "Withdraw whole balance of all fees accrued in the contract so far.Owners only", "params": { "targetAddress": ": address to send tokens to", "txExpirationBlock": ": block number until which is the transaction valid (inclusive). When transaction is processed after this block, it fails." } }, "withdrawExcessTokens(address,uint256)": { "details": "Withdraw \"excess\" tokens, which were sent to contract address directly via direct ERC20 transfer(...) or transferFrom(...), without interacting with API of this (FetchOracle) contract, what could be done only by mistake. Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such \"excess\" tokens out of contract.", "params": { "targetAddress": ": address to send tokens to", "txExpirationBlock": ": block number until which is the transaction valid (inclusive). When transaction is processed after this block, it fails." } } } }, "userdoc": { "methods": {} } } ================================================ FILE: packages/fetchai/contracts/oracle/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the class to connect to an Oracle contract.""" import logging from typing import Any, Dict, cast from aea_ledger_ethereum import EthereumApi from aea_ledger_fetchai import FetchAIApi from aea.common import Address, JSONLike from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi PUBLIC_ID = PublicId.from_str("fetchai/oracle:0.12.3") def keccak256(input_: bytes) -> bytes: """Compute hash.""" return bytes(bytearray.fromhex(EthereumApi.get_hash(input_)[2:])) ORACLE_ROLE = "ORACLE_ROLE" _default_logger = logging.getLogger("aea.packages.fetchai.contracts.oracle.contract") class FetchOracleContract(Contract): """The Fetch oracle contract.""" @classmethod def get_grant_role_transaction( cls, ledger_api: LedgerApi, contract_address: Address, oracle_address: Address, gas: int = 0, tx_fee: int = 0, ) -> JSONLike: """ Get transaction to grant oracle role to recipient_address :param ledger_api: the ledger API :param contract_address: the contract address :param oracle_address: the address of the oracle :param gas: the gas limit :param tx_fee: the transaction fee :return: the transaction object """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(oracle_address) instance = cls.get_instance(ledger_api, contract_address) oracle_role = keccak256(ORACLE_ROLE.encode("utf-8")) tx = instance.functions.grantRole( oracle_role, oracle_address ).buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier == FetchAIApi.identifier: msg = {"grant_oracle_role": {"address": oracle_address}} fetchai_api = cast(FetchAIApi, ledger_api) tx = fetchai_api.get_handle_transaction( oracle_address, contract_address, msg, amount=0, tx_fee=tx_fee, gas=gas ) return tx raise NotImplementedError @classmethod def get_update_transaction( cls, ledger_api: LedgerApi, contract_address: Address, oracle_address: Address, update_function: str, update_kwargs: Dict[str, Any], gas: int = 0, tx_fee: int = 0, ) -> JSONLike: """ Update oracle value in contract :param ledger_api: the ledger apis. :param contract_address: the contract address. :param oracle_address: the oracle address. :param update_function: the oracle value update function. :param update_kwargs: the arguments to the contract's update function. :param gas: the gas limit :param tx_fee: the transaction fee :return: transaction json """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(oracle_address) instance = cls.get_instance(ledger_api, contract_address) function = getattr(instance.functions, update_function) update_args = list(update_kwargs.values()) intermediate = function(*update_args) tx = intermediate.buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in FetchAIApi.identifier: # Convert all values to strings for CosmWasm message update_kwargs_str = { key: str(value) for (key, value) in update_kwargs.items() } msg = {update_function: update_kwargs_str} fetchai_api = cast(FetchAIApi, ledger_api) tx = fetchai_api.get_handle_transaction( oracle_address, contract_address, msg, amount=0, tx_fee=tx_fee, gas=gas ) return tx raise NotImplementedError ================================================ FILE: packages/fetchai/contracts/oracle/contract.yaml ================================================ name: oracle author: fetchai version: 0.12.3 type: contract description: Fetch oracle contract license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmQvXSNH6CN9uNqF8bjuCiv7ThQ5D7SCEZvNgKw88uYTdT __init__.py: QmYWrZY2q18XGiS8EDki97v3Gi9KHLi1YhC7e7cpbkEXU2 build/FetchOracle.json: QmfEMai1yxPtWoshFahBk2EyVHd9Mo8pSp1SAE83rRvQgH build/oracle.wasm: QmPkM6EDcizaV39AhqynLSu7bFpy27Kda27r1bJ61W73AA contract.py: QmUdjMGUHvbvQBXEC4zTzg5525pt89VATTQuq1hunJJjE3 contracts/FetchOracle.sol: QmadnUCtsVobBGMxiWAkqMptC9acSMBGj5x4Z4V2UhnFx8 fingerprint_ignore_patterns: [] class_name: FetchOracleContract contract_interface_paths: ethereum: build/FetchOracle.json fetchai: build/oracle.wasm dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/contracts/oracle/contracts/FetchOracle.sol ================================================ // SPDX-License-Identifier:Apache-2.0 //------------------------------------------------------------------------------ // // Copyright 2020 Fetch.AI Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //------------------------------------------------------------------------------ pragma solidity ^0.6.0; import "../openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../openzeppelin/contracts/access/AccessControl.sol"; import "../openzeppelin/contracts/math/SafeMath.sol"; /** @dev The contract *INTENTIONALLY* does not contain the `receive()` or fallback functions in order to ensure that all unsolicited direct ETH transfers in to this contract (in direct Transaction) will fail. Only way how to make successful fee transfer in ETH currency (*IF* contract is initialised for ETH as fee currency), is to make transfer ETH to caller(client) contract and client contract then is responsible for further *programmatic* (= *not* via Tx) transfer of fee to this this contract. */ contract FetchOracle is AccessControl { using SafeMath for uint256; enum FeeCurrency { ERC20, ETH } struct OracleValue { uint256 value; uint8 decimals; uint256 updatedAtEthBlockNumber; } event FeeUpdated(uint256 fee); event Pause(uint256 sinceBlock); event ExcessTokenWithdrawal(address targetAddress, uint256 amount); event AccruedFeesWithdrawal(address targetAddress, uint256 amount); event ContractDeleted(); event OracleValueUpdated(uint256 atBlock); bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE"); bytes32 public constant DELEGATE_ROLE = keccak256("DELEGATE_ROLE"); uint256 public constant DELETE_PROTECTION_PERIOD = 370285;// 60*24*60*60[s] / (14[s/block]) = 370285[block]; IERC20 public _token; uint256 public _earliestDelete; uint256 public _pausedSinceBlock; uint256 public _fee; uint256 public _accruedFeesAmount; OracleValue private _oracleValue; FeeCurrency public _feeCurrency; function _isOwner() internal view returns(bool) { return hasRole(DEFAULT_ADMIN_ROLE, msg.sender); } /* Only callable by owner */ modifier onlyOwner() { require(_isOwner(), "Caller is not an administrator"); _; } /* Only callable by owner or delegate */ modifier onlyDelegate() { require(_isOwner() || hasRole(DELEGATE_ROLE, msg.sender), "Caller is neither owner nor delegate"); _; } /* Only callable by oracle */ modifier onlyOracle() { require(hasRole(ORACLE_ROLE, msg.sender), "Caller is not an oracle"); _; } modifier verifyTxExpiration(uint256 expirationBlock) { require(_getBlockNumber() <= expirationBlock, "Transaction expired"); _; } modifier verifyNotPaused() { require(_pausedSinceBlock > _getBlockNumber(), "Contract has been paused"); _; } /******************* Contract start *******************/ /** * @param ERC20Address address of the ERC20 contract */ constructor(address ERC20Address, uint256 initialFee) public { _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); if (ERC20Address == address(0)) { _feeCurrency = FeeCurrency.ETH; } else { _feeCurrency = FeeCurrency.ERC20; _token = IERC20(ERC20Address); } _earliestDelete = _getBlockNumber().add(DELETE_PROTECTION_PERIOD); _pausedSinceBlock = ~uint256(0); _fee = initialFee; } function updateOracleValue( uint256 value, uint8 decimals, uint256 txExpirationBlock ) external verifyTxExpiration(txExpirationBlock) onlyOracle { _oracleValue.value = value; _oracleValue.decimals = decimals; _oracleValue.updatedAtEthBlockNumber = _getBlockNumber(); emit OracleValueUpdated(_oracleValue.updatedAtEthBlockNumber); } function queryOracleValue() public payable returns (uint256 value, uint8 decimals, uint256 updatedAtEthBlockNumber) { _withdrawFee(); value = _oracleValue.value; decimals = _oracleValue.decimals; updatedAtEthBlockNumber = _oracleValue.updatedAtEthBlockNumber; } /** * @dev Pause the non-administrative interaction with the contract * @param fee - value of fee * @param txExpirationBlock - block number defined by Tx sender beyond which transaction becomes invalid * @dev Owners only */ function setFee(uint256 fee, uint256 txExpirationBlock) external verifyTxExpiration(txExpirationBlock) onlyDelegate { _fee = fee; emit FeeUpdated(_fee); } /** * @dev Pause the non-administrative interaction with the contract * @param block_number disallow non-admin. interactions with contract for a _getBlockNumber() >= block_number * @dev Owners only */ function pauseSince(uint256 block_number, uint256 txExpirationBlock) external verifyTxExpiration(txExpirationBlock) onlyDelegate { uint256 curr_block_number = _getBlockNumber(); _pausedSinceBlock = block_number < curr_block_number ? curr_block_number : block_number; emit Pause(_pausedSinceBlock); } /** * @dev Withdraw whole balance of all fees accrued in the contract so far. * @param targetAddress : address to send tokens to * @param txExpirationBlock : block number until which is the transaction valid (inclusive). * When transaction is processed after this block, it fails. * @dev Owners only */ function withdrawAccruedFees(address payable targetAddress, uint256 txExpirationBlock) external verifyTxExpiration(txExpirationBlock) onlyOwner { if (_accruedFeesAmount == 0) { return; } // The following method will revert in the case of issue (e.g. not enough balance for transfer, zero address): _withdrawFromContract(targetAddress, _accruedFeesAmount); emit AccruedFeesWithdrawal(targetAddress, _accruedFeesAmount); _accruedFeesAmount = 0; } /** * @dev Withdraw "excess" tokens, which were sent to contract address directly via direct ERC20 transfer(...) or transferFrom(...), * without interacting with API of this (FetchOracle) contract, what could be done only by mistake. * Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such * "excess" tokens out of contract. * @param targetAddress : address to send tokens to * @param txExpirationBlock : block number until which is the transaction valid (inclusive). * When transaction is processed after this block, it fails. */ function withdrawExcessTokens(address payable targetAddress, uint256 txExpirationBlock) external verifyTxExpiration(txExpirationBlock) onlyOwner { uint256 contractBalance; if (_feeCurrency == FeeCurrency.ETH) { contractBalance = address(this).balance; } else if (_feeCurrency == FeeCurrency.ERC20) { contractBalance = _token.balanceOf(address(this)); } // NOTE(pb): Final else{...} is not necessary since it is checked in the `_withdrawFromContract(...)` call bellow // NOTE(pb): The following subtraction shall *fail* (revert) IF the contract is in *INCONSISTENT* state, // = when contract balance is less than minial expected balance: uint256 excessAmount = contractBalance.sub(_accruedFeesAmount); _withdrawFromContract(targetAddress, excessAmount); emit ExcessTokenWithdrawal(targetAddress, excessAmount); } /** * @dev Delete the contract, transfers the remaining token and ether balance to the specified payoutAddress * @param payoutAddress address to transfer the balances to. Ensure that this is able to handle ERC20 tokens * @dev owner only + only on or after `_earliestDelete` block */ function deleteContract(address payable payoutAddress, uint256 txExpirationBlock) external verifyTxExpiration(txExpirationBlock) onlyOwner { require(_earliestDelete >= _getBlockNumber(), "Earliest delete not reached"); if (_feeCurrency == FeeCurrency.ERC20) { uint256 contractBalance = _token.balanceOf(address(this)); require(_token.transfer(payoutAddress, contractBalance)); } emit ContractDeleted(); // The selfdestruct, by design, transfers *whole* ETH balance from this contract to targetAddress: selfdestruct(payoutAddress); } function oracleValueLastUpdated() external view returns(uint256) { return _oracleValue.updatedAtEthBlockNumber; } function _getBlockNumber() internal view virtual returns(uint256) { return block.number; } /** * @dev Withdraw fee from the caller. */ function _withdrawFee() internal { if (_feeCurrency == FeeCurrency.ETH) { if (msg.value == _fee) { // No action necessary, since exact expected fee amount has been already transferred by caller, // this this is just placeholder block to capture the logical condition. } else if (msg.value > _fee) { // Refund (back to caller) the excess caller paid on top of the expected _fee value: msg.sender.transfer(msg.value.sub(_fee)); } else { require(false, "Insuf. ETH amount sent by caller"); } } else if (_feeCurrency == FeeCurrency.ERC20) { require(_token.transferFrom(msg.sender, address(this), _fee), "Insuf. FET allow. on caller addr"); } else { require(false, "Unexpected contract fee currency"); } _accruedFeesAmount = _accruedFeesAmount.add(_fee); } function _withdrawFromContract(address payable targetAddress, uint256 amount) internal { require(targetAddress != address(0), "Target is zero address"); if (_feeCurrency == FeeCurrency.ETH) { // NOTE(pb): The following transfer shall *fail* with revert IF contract balance is insufficient for transfer targetAddress.transfer(amount); } else if (_feeCurrency == FeeCurrency.ERC20) { // NOTE(pb): The following transfer shall *fail* with revert IF contract balance is insufficient for transfer require(_token.transfer(targetAddress, amount), "Insuff. FET funds on contr. addr"); } else { require(false, "Unexpected contract fee currency"); } } } ================================================ FILE: packages/fetchai/contracts/oracle_client/README.md ================================================ # Fetch Oracle Client Contract ## Description This contract package is used to interface with a Fetch Oracle Client contract, which requests a real-world value from a Fetch Oracle contract. ## Functions - `queryOracleValue()`: call contract method that requests oracle value from associated oracle contract ================================================ FILE: packages/fetchai/contracts/oracle_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the support resources for the Fetch oracle contract.""" ================================================ FILE: packages/fetchai/contracts/oracle_client/build/FetchOracleTestClient.json ================================================ { "abi": [ { "inputs": [ { "internalType": "address", "name": "fetchOracleContractAddress", "type": "address" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [], "name": "_decimals", "outputs": [ { "internalType": "uint8", "name": "", "type": "uint8" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_fetch_oracle", "outputs": [ { "internalType": "contract FetchOracle", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_updatedAtEthBlockNumber", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_value", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "queryOracleValue", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ], "allSourcePaths": { "12": "interfaces/IFetchOracleClient.sol", "9": "contracts/FetchOracleTestClient.sol" }, "ast": { "absolutePath": "contracts/FetchOracleTestClient.sol", "exportedSymbols": { "FetchOracleTestClient": [ 188 ], "FetchOracleTestClientETH": [ 200 ] }, "id": 201, "license": "Apache-2.0", "nodeType": "SourceUnit", "nodes": [ { "id": 119, "literals": [ "solidity", "^", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "820:23:9" }, { "absolutePath": "contracts/FetchOracleMock.sol", "file": "./FetchOracleMock.sol", "id": 120, "nodeType": "ImportDirective", "scope": 201, "sourceUnit": 118, "src": "845:31:9", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "interfaces/IFetchOracleClient.sol", "file": "../interfaces/IFetchOracleClient.sol", "id": 121, "nodeType": "ImportDirective", "scope": 201, "sourceUnit": 448, "src": "877:46:9", "symbolAliases": [], "unitAlias": "" }, { "abstract": false, "baseContracts": [ { "arguments": null, "baseName": { "contractScope": null, "id": 122, "name": "IFetchOracleClient", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 447, "src": "959:18:9", "typeDescriptions": { "typeIdentifier": "t_contract$_IFetchOracleClient_$447", "typeString": "contract IFetchOracleClient" } }, "id": 123, "nodeType": "InheritanceSpecifier", "src": "959:18:9" } ], "contractDependencies": [ 447 ], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 188, "linearizedBaseContracts": [ 188, 447 ], "name": "FetchOracleTestClient", "nodeType": "ContractDefinition", "nodes": [ { "constant": false, "functionSelector": "19cc4b5a", "id": 125, "mutability": "mutable", "name": "_fetch_oracle", "nodeType": "VariableDeclaration", "overrides": null, "scope": 188, "src": "983:32:9", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" }, "typeName": { "contractScope": null, "id": 124, "name": "FetchOracle", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 441, "src": "983:11:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "b895c74a", "id": 127, "mutability": "mutable", "name": "_value", "nodeType": "VariableDeclaration", "overrides": null, "scope": 188, "src": "1022:21:9", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 126, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1022:7:9", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "32424aa3", "id": 129, "mutability": "mutable", "name": "_decimals", "nodeType": "VariableDeclaration", "overrides": null, "scope": 188, "src": "1049:22:9", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" }, "typeName": { "id": 128, "name": "uint8", "nodeType": "ElementaryTypeName", "src": "1049:5:9", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "4e5551ce", "id": 131, "mutability": "mutable", "name": "_updatedAtEthBlockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 188, "src": "1077:39:9", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 130, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1077:7:9", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "body": { "id": 142, "nodeType": "Block", "src": "1179:144:9", "statements": [ { "expression": { "argumentTypes": null, "id": 140, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 136, "name": "_fetch_oracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 125, "src": "1257:13:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 138, "name": "fetchOracleContractAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 133, "src": "1289:26:9", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 137, "name": "FetchOracleMock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 101, "src": "1273:15:9", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_FetchOracleMock_$101_$", "typeString": "type(contract FetchOracleMock)" } }, "id": 139, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1273:43:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracleMock_$101", "typeString": "contract FetchOracleMock" } }, "src": "1257:59:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "id": 141, "nodeType": "ExpressionStatement", "src": "1257:59:9" } ] }, "documentation": null, "id": 143, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 134, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 133, "mutability": "mutable", "name": "fetchOracleContractAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 143, "src": "1136:34:9", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 132, "name": "address", "nodeType": "ElementaryTypeName", "src": "1136:7:9", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "1135:36:9" }, "returnParameters": { "id": 135, "nodeType": "ParameterList", "parameters": [], "src": "1179:0:9" }, "scope": 188, "src": "1124:199:9", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "baseFunctions": [ 446 ], "body": { "id": 186, "nodeType": "Block", "src": "1380:277:9", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 152, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "1426:3:9", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 153, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "1426:10:9", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 156, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "1446:4:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracleTestClient_$188", "typeString": "contract FetchOracleTestClient" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracleTestClient_$188", "typeString": "contract FetchOracleTestClient" } ], "id": 155, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "1438:7:9", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 154, "name": "address", "nodeType": "ElementaryTypeName", "src": "1438:7:9", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 157, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1438:13:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 158, "name": "_fetch_oracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 125, "src": "1453:13:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "id": 159, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "_fee", "nodeType": "MemberAccess", "referencedDeclaration": 491, "src": "1453:18:9", "typeDescriptions": { "typeIdentifier": "t_function_external_view$__$returns$_t_uint256_$", "typeString": "function () view external returns (uint256)" } }, "id": 160, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1453:20:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 147, "name": "_fetch_oracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 125, "src": "1390:13:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "id": 149, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "_token", "nodeType": "MemberAccess", "referencedDeclaration": 485, "src": "1390:20:9", "typeDescriptions": { "typeIdentifier": "t_function_external_view$__$returns$_t_contract$_IERC20_$1542_$", "typeString": "function () view external returns (contract IERC20)" } }, "id": 150, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1390:22:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$1542", "typeString": "contract IERC20" } }, "id": 151, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transferFrom", "nodeType": "MemberAccess", "referencedDeclaration": 1523, "src": "1390:35:9", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,address,uint256) external returns (bool)" } }, "id": 161, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1390:84:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 162, "nodeType": "ExpressionStatement", "src": "1390:84:9" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 170, "name": "_fetch_oracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 125, "src": "1523:13:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } ], "id": 169, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "1515:7:9", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 168, "name": "address", "nodeType": "ElementaryTypeName", "src": "1515:7:9", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 171, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1515:22:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 172, "name": "_fetch_oracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 125, "src": "1539:13:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "id": 173, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "_fee", "nodeType": "MemberAccess", "referencedDeclaration": 491, "src": "1539:18:9", "typeDescriptions": { "typeIdentifier": "t_function_external_view$__$returns$_t_uint256_$", "typeString": "function () view external returns (uint256)" } }, "id": 174, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1539:20:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 163, "name": "_fetch_oracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 125, "src": "1484:13:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "id": 165, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "_token", "nodeType": "MemberAccess", "referencedDeclaration": 485, "src": "1484:20:9", "typeDescriptions": { "typeIdentifier": "t_function_external_view$__$returns$_t_contract$_IERC20_$1542_$", "typeString": "function () view external returns (contract IERC20)" } }, "id": 166, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1484:22:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$1542", "typeString": "contract IERC20" } }, "id": 167, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "approve", "nodeType": "MemberAccess", "referencedDeclaration": 1511, "src": "1484:30:9", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 175, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1484:76:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 176, "nodeType": "ExpressionStatement", "src": "1484:76:9" }, { "expression": { "argumentTypes": null, "id": 184, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "components": [ { "argumentTypes": null, "id": 177, "name": "_value", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 127, "src": "1571:6:9", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "id": 178, "name": "_decimals", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 129, "src": "1579:9:9", "typeDescriptions": { "typeIdentifier": "t_uint8", "typeString": "uint8" } }, { "argumentTypes": null, "id": 179, "name": "_updatedAtEthBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 131, "src": "1590:24:9", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 180, "isConstant": false, "isInlineArray": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "TupleExpression", "src": "1570:45:9", "typeDescriptions": { "typeIdentifier": "t_tuple$_t_uint256_$_t_uint8_$_t_uint256_$", "typeString": "tuple(uint256,uint8,uint256)" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 181, "name": "_fetch_oracle", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 125, "src": "1618:13:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracle_$441", "typeString": "contract FetchOracle" } }, "id": 182, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "queryOracleValue", "nodeType": "MemberAccess", "referencedDeclaration": 431, "src": "1618:30:9", "typeDescriptions": { "typeIdentifier": "t_function_external_payable$__$returns$_t_uint256_$_t_uint8_$_t_uint256_$", "typeString": "function () payable external returns (uint256,uint8,uint256)" } }, "id": 183, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "1618:32:9", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_uint256_$_t_uint8_$_t_uint256_$", "typeString": "tuple(uint256,uint8,uint256)" } }, "src": "1570:80:9", "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 185, "nodeType": "ExpressionStatement", "src": "1570:80:9" } ] }, "documentation": null, "functionSelector": "f7e1c748", "id": 187, "implemented": true, "kind": "function", "modifiers": [], "name": "queryOracleValue", "nodeType": "FunctionDefinition", "overrides": { "id": 145, "nodeType": "OverrideSpecifier", "overrides": [], "src": "1367:8:9" }, "parameters": { "id": 144, "nodeType": "ParameterList", "parameters": [], "src": "1355:2:9" }, "returnParameters": { "id": 146, "nodeType": "ParameterList", "parameters": [], "src": "1380:0:9" }, "scope": 188, "src": "1330:327:9", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" } ], "scope": 201, "src": "925:734:9" }, { "abstract": false, "baseContracts": [ { "arguments": null, "baseName": { "contractScope": null, "id": 189, "name": "FetchOracleTestClient", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 188, "src": "1769:21:9", "typeDescriptions": { "typeIdentifier": "t_contract$_FetchOracleTestClient_$188", "typeString": "contract FetchOracleTestClient" } }, "id": 190, "nodeType": "InheritanceSpecifier", "src": "1769:21:9" } ], "contractDependencies": [ 188, 447 ], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 200, "linearizedBaseContracts": [ 200, 188, 447 ], "name": "FetchOracleTestClientETH", "nodeType": "ContractDefinition", "nodes": [ { "body": { "id": 198, "nodeType": "Block", "src": "1902:7:9", "statements": [] }, "documentation": null, "id": 199, "implemented": true, "kind": "constructor", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 195, "name": "fetchOracleContractAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 192, "src": "1874:26:9", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "id": 196, "modifierName": { "argumentTypes": null, "id": 194, "name": "FetchOracleTestClient", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 188, "src": "1852:21:9", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_FetchOracleTestClient_$188_$", "typeString": "type(contract FetchOracleTestClient)" } }, "nodeType": "ModifierInvocation", "src": "1852:49:9" } ], "name": "", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 193, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 192, "mutability": "mutable", "name": "fetchOracleContractAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 199, "src": "1809:34:9", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 191, "name": "address", "nodeType": "ElementaryTypeName", "src": "1809:7:9", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "1808:36:9" }, "returnParameters": { "id": 197, "nodeType": "ParameterList", "parameters": [], "src": "1902:0:9" }, "scope": 200, "src": "1797:112:9", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" } ], "scope": 201, "src": "1732:179:9" } ], "src": "820:1092:9" }, "bytecode": "608060405234801561001057600080fd5b506040516105053803806105058339818101604052602081101561003357600080fd5b5051600080546001600160a01b039092166001600160a01b03199092169190911790556104a0806100656000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c806319cc4b5a1461005c57806332424aa3146100805780634e5551ce1461009e578063b895c74a146100b8578063f7e1c748146100c0575b600080fd5b6100646100ca565b604080516001600160a01b039092168252519081900360200190f35b6100886100d9565b6040805160ff9092168252519081900360200190f35b6100a66100e2565b60408051918252519081900360200190f35b6100a66100e8565b6100c86100ee565b005b6000546001600160a01b031681565b60025460ff1681565b60035481565b60015481565b60008054906101000a90046001600160a01b03166001600160a01b031663ecd0c0c36040518163ffffffff1660e01b815260040160206040518083038186803b15801561013a57600080fd5b505afa15801561014e573d6000803e3d6000fd5b505050506040513d602081101561016457600080fd5b5051600054604080516362d9be1160e11b815290516001600160a01b03938416936323b872dd9333933093919092169163c5b37c22916004808301926020929190829003018186803b1580156101b957600080fd5b505afa1580156101cd573d6000803e3d6000fd5b505050506040513d60208110156101e357600080fd5b5051604080516001600160e01b031960e087901b1681526001600160a01b0394851660048201529290931660248301526044820152905160648083019260209291908290030181600087803b15801561023b57600080fd5b505af115801561024f573d6000803e3d6000fd5b505050506040513d602081101561026557600080fd5b50506000546040805163ecd0c0c360e01b815290516001600160a01b039092169163ecd0c0c391600480820192602092909190829003018186803b1580156102ac57600080fd5b505afa1580156102c0573d6000803e3d6000fd5b505050506040513d60208110156102d657600080fd5b5051600054604080516362d9be1160e11b815290516001600160a01b039384169363095ea7b3931691829163c5b37c2291600480820192602092909190829003018186803b15801561032757600080fd5b505afa15801561033b573d6000803e3d6000fd5b505050506040513d602081101561035157600080fd5b5051604080516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091525160448083019260209291908290030181600087803b1580156103a257600080fd5b505af11580156103b6573d6000803e3d6000fd5b505050506040513d60208110156103cc57600080fd5b50506000805460408051631efc38e960e31b815290516001600160a01b039092169263f7e1c748926004808401936060939083900390910190829087803b15801561041657600080fd5b505af115801561042a573d6000803e3d6000fd5b505050506040513d606081101561044057600080fd5b50805160208201516040909201516003556002805460ff191660ff9093169290921790915560015556fea26469706673582212203ed415b0bf9e63200b9658ae26b80a450e549e9a0e9ad3934c5c1496ec57aa0e64736f6c634300060c0033", "bytecodeSha1": "796e98fdfc59410c81344db30cddb2856d6dfd13", "compiler": { "evm_version": "istanbul", "optimizer": { "enabled": true, "runs": 200 }, "version": "0.6.12" }, "contractName": "FetchOracleTestClient", "coverageMap": { "branches": { "12": {}, "9": {} }, "statements": { "12": {}, "9": { "FetchOracleTestClient.queryOracleValue": { "0": [ 1390, 1474 ], "1": [ 1484, 1560 ], "2": [ 1570, 1650 ] } } } }, "dependencies": [ "IFetchOracleClient" ], "deployedBytecode": "608060405234801561001057600080fd5b50600436106100575760003560e01c806319cc4b5a1461005c57806332424aa3146100805780634e5551ce1461009e578063b895c74a146100b8578063f7e1c748146100c0575b600080fd5b6100646100ca565b604080516001600160a01b039092168252519081900360200190f35b6100886100d9565b6040805160ff9092168252519081900360200190f35b6100a66100e2565b60408051918252519081900360200190f35b6100a66100e8565b6100c86100ee565b005b6000546001600160a01b031681565b60025460ff1681565b60035481565b60015481565b60008054906101000a90046001600160a01b03166001600160a01b031663ecd0c0c36040518163ffffffff1660e01b815260040160206040518083038186803b15801561013a57600080fd5b505afa15801561014e573d6000803e3d6000fd5b505050506040513d602081101561016457600080fd5b5051600054604080516362d9be1160e11b815290516001600160a01b03938416936323b872dd9333933093919092169163c5b37c22916004808301926020929190829003018186803b1580156101b957600080fd5b505afa1580156101cd573d6000803e3d6000fd5b505050506040513d60208110156101e357600080fd5b5051604080516001600160e01b031960e087901b1681526001600160a01b0394851660048201529290931660248301526044820152905160648083019260209291908290030181600087803b15801561023b57600080fd5b505af115801561024f573d6000803e3d6000fd5b505050506040513d602081101561026557600080fd5b50506000546040805163ecd0c0c360e01b815290516001600160a01b039092169163ecd0c0c391600480820192602092909190829003018186803b1580156102ac57600080fd5b505afa1580156102c0573d6000803e3d6000fd5b505050506040513d60208110156102d657600080fd5b5051600054604080516362d9be1160e11b815290516001600160a01b039384169363095ea7b3931691829163c5b37c2291600480820192602092909190829003018186803b15801561032757600080fd5b505afa15801561033b573d6000803e3d6000fd5b505050506040513d602081101561035157600080fd5b5051604080516001600160e01b031960e086901b1681526001600160a01b03909316600484015260248301919091525160448083019260209291908290030181600087803b1580156103a257600080fd5b505af11580156103b6573d6000803e3d6000fd5b505050506040513d60208110156103cc57600080fd5b50506000805460408051631efc38e960e31b815290516001600160a01b039092169263f7e1c748926004808401936060939083900390910190829087803b15801561041657600080fd5b505af115801561042a573d6000803e3d6000fd5b505050506040513d606081101561044057600080fd5b50805160208201516040909201516003556002805460ff191660ff9093169290921790915560015556fea26469706673582212203ed415b0bf9e63200b9658ae26b80a450e549e9a0e9ad3934c5c1496ec57aa0e64736f6c634300060c0033", "deployedSourceMap": "925:734:9:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;983:32;;;:::i;:::-;;;;-1:-1:-1;;;;;983:32:9;;;;;;;;;;;;;;1049:22;;;:::i;:::-;;;;;;;;;;;;;;;;;;;1077:39;;;:::i;:::-;;;;;;;;;;;;;;;;1022:21;;;:::i;1330:327::-;;;:::i;:::-;;983:32;;;-1:-1:-1;;;;;983:32:9;;:::o;1049:22::-;;;;;;:::o;1077:39::-;;;;:::o;1022:21::-;;;;:::o;1330:327::-;1390:13;;;;;;;;-1:-1:-1;;;;;1390:13:9;-1:-1:-1;;;;;1390:20:9;;:22;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1390:22:9;1453:13;;:20;;;-1:-1:-1;;;1453:20:9;;;;-1:-1:-1;;;;;1390:35:9;;;;;;1426:10;;1446:4;;1453:13;;;;;:18;;:20;;;;;1390:22;;1453:20;;;;;;;:13;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1453:20:9;1390:84;;;-1:-1:-1;;;;;;1390:84:9;;;;;;;-1:-1:-1;;;;;1390:84:9;;;;;;;;;;;;;;;;;;;;;;;;;;1453:20;;1390:84;;;;;;;-1:-1:-1;1390:84:9;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;1484:13:9;;:22;;;-1:-1:-1;;;1484:22:9;;;;-1:-1:-1;;;;;1484:13:9;;;;:20;;:22;;;;;1390:84;;1484:22;;;;;;;;:13;:22;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1484:22:9;1523:13;;1539:20;;;-1:-1:-1;;;1539:20:9;;;;-1:-1:-1;;;;;1484:30:9;;;;;;1523:13;;;;1539:18;;:20;;;;;1484:22;;1539:20;;;;;;;;1523:13;1539:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1539:20:9;1484:76;;;-1:-1:-1;;;;;;1484:76:9;;;;;;;-1:-1:-1;;;;;1484:76:9;;;;;;;;;;;;;;;;;;;;1539:20;;1484:76;;;;;;;-1:-1:-1;1484:76:9;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;1618:13:9;;;:32;;;-1:-1:-1;;;1618:32:9;;;;-1:-1:-1;;;;;1618:13:9;;;;:30;;:32;;;;;;;;;;;;;;;;;:13;:32;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1618:32:9;;;;;;;;;;;1590:24;1570:80;1579:9;1570:80;;-1:-1:-1;;1570:80:9;;;;;;;;;;;;-1:-1:-1;1570:80:9;1330:327::o", "language": "Solidity", "natspec": { "kind": "dev", "methods": {}, "version": 1 }, "offset": [ 925, 1659 ], "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x57 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x19CC4B5A EQ PUSH2 0x5C JUMPI DUP1 PUSH4 0x32424AA3 EQ PUSH2 0x80 JUMPI DUP1 PUSH4 0x4E5551CE EQ PUSH2 0x9E JUMPI DUP1 PUSH4 0xB895C74A EQ PUSH2 0xB8 JUMPI DUP1 PUSH4 0xF7E1C748 EQ PUSH2 0xC0 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x64 PUSH2 0xCA JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST PUSH2 0x88 PUSH2 0xD9 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0xFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST PUSH2 0xA6 PUSH2 0xE2 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST PUSH2 0xA6 PUSH2 0xE8 JUMP JUMPDEST PUSH2 0xC8 PUSH2 0xEE JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 SLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB AND DUP2 JUMP JUMPDEST PUSH1 0x2 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x3 SLOAD DUP2 JUMP JUMPDEST PUSH1 0x1 SLOAD DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB AND PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB AND PUSH4 0xECD0C0C3 PUSH1 0x40 MLOAD DUP2 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP7 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x13A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS STATICCALL ISZERO DUP1 ISZERO PUSH2 0x14E JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x164 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP MLOAD PUSH1 0x0 SLOAD PUSH1 0x40 DUP1 MLOAD PUSH4 0x62D9BE11 PUSH1 0xE1 SHL DUP2 MSTORE SWAP1 MLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP4 DUP5 AND SWAP4 PUSH4 0x23B872DD SWAP4 CALLER SWAP4 ADDRESS SWAP4 SWAP2 SWAP1 SWAP3 AND SWAP2 PUSH4 0xC5B37C22 SWAP2 PUSH1 0x4 DUP1 DUP4 ADD SWAP3 PUSH1 0x20 SWAP3 SWAP2 SWAP1 DUP3 SWAP1 SUB ADD DUP2 DUP7 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x1B9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS STATICCALL ISZERO DUP1 ISZERO PUSH2 0x1CD JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x1E3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP MLOAD PUSH1 0x40 DUP1 MLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xE0 SHL SUB NOT PUSH1 0xE0 DUP8 SWAP1 SHL AND DUP2 MSTORE PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP5 DUP6 AND PUSH1 0x4 DUP3 ADD MSTORE SWAP3 SWAP1 SWAP4 AND PUSH1 0x24 DUP4 ADD MSTORE PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD PUSH1 0x64 DUP1 DUP4 ADD SWAP3 PUSH1 0x20 SWAP3 SWAP2 SWAP1 DUP3 SWAP1 SUB ADD DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x23B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x24F JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x265 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP POP PUSH1 0x0 SLOAD PUSH1 0x40 DUP1 MLOAD PUSH4 0xECD0C0C3 PUSH1 0xE0 SHL DUP2 MSTORE SWAP1 MLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP1 SWAP3 AND SWAP2 PUSH4 0xECD0C0C3 SWAP2 PUSH1 0x4 DUP1 DUP3 ADD SWAP3 PUSH1 0x20 SWAP3 SWAP1 SWAP2 SWAP1 DUP3 SWAP1 SUB ADD DUP2 DUP7 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x2AC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS STATICCALL ISZERO DUP1 ISZERO PUSH2 0x2C0 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x2D6 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP MLOAD PUSH1 0x0 SLOAD PUSH1 0x40 DUP1 MLOAD PUSH4 0x62D9BE11 PUSH1 0xE1 SHL DUP2 MSTORE SWAP1 MLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP4 DUP5 AND SWAP4 PUSH4 0x95EA7B3 SWAP4 AND SWAP2 DUP3 SWAP2 PUSH4 0xC5B37C22 SWAP2 PUSH1 0x4 DUP1 DUP3 ADD SWAP3 PUSH1 0x20 SWAP3 SWAP1 SWAP2 SWAP1 DUP3 SWAP1 SUB ADD DUP2 DUP7 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x327 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS STATICCALL ISZERO DUP1 ISZERO PUSH2 0x33B JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x351 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP MLOAD PUSH1 0x40 DUP1 MLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xE0 SHL SUB NOT PUSH1 0xE0 DUP7 SWAP1 SHL AND DUP2 MSTORE PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP1 SWAP4 AND PUSH1 0x4 DUP5 ADD MSTORE PUSH1 0x24 DUP4 ADD SWAP2 SWAP1 SWAP2 MSTORE MLOAD PUSH1 0x44 DUP1 DUP4 ADD SWAP3 PUSH1 0x20 SWAP3 SWAP2 SWAP1 DUP3 SWAP1 SUB ADD DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x3A2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x3B6 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x3CC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP POP PUSH1 0x0 DUP1 SLOAD PUSH1 0x40 DUP1 MLOAD PUSH4 0x1EFC38E9 PUSH1 0xE3 SHL DUP2 MSTORE SWAP1 MLOAD PUSH1 0x1 PUSH1 0x1 PUSH1 0xA0 SHL SUB SWAP1 SWAP3 AND SWAP3 PUSH4 0xF7E1C748 SWAP3 PUSH1 0x4 DUP1 DUP5 ADD SWAP4 PUSH1 0x60 SWAP4 SWAP1 DUP4 SWAP1 SUB SWAP1 SWAP2 ADD SWAP1 DUP3 SWAP1 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x416 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x42A JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x440 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP DUP1 MLOAD PUSH1 0x20 DUP3 ADD MLOAD PUSH1 0x40 SWAP1 SWAP3 ADD MLOAD PUSH1 0x3 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH1 0xFF NOT AND PUSH1 0xFF SWAP1 SWAP4 AND SWAP3 SWAP1 SWAP3 OR SWAP1 SWAP2 SSTORE PUSH1 0x1 SSTORE JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 RETURNDATACOPY 0xD4 ISZERO 0xB0 0xBF SWAP15 PUSH4 0x200B9658 0xAE 0x26 0xB8 EXP GASLIMIT 0xE SLOAD SWAP15 SWAP11 0xE SWAP11 0xD3 SWAP4 0x4C 0x5C EQ SWAP7 0xEC JUMPI 0xAA 0xE PUSH5 0x736F6C6343 STOP MOD 0xC STOP CALLER ", "pcMap": { "0": { "offset": [ 925, 1659 ], "op": "PUSH1", "path": "9", "value": "0x80" }, "2": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "4": { "fn": null, "offset": [ 925, 1659 ], "op": "MSTORE", "path": "9" }, "5": { "fn": null, "offset": [ 925, 1659 ], "op": "CALLVALUE", "path": "9" }, "6": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "7": { "fn": null, "offset": [ 925, 1659 ], "op": "ISZERO", "path": "9" }, "8": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH2", "path": "9", "value": "0x10" }, "11": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPI", "path": "9" }, "12": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "14": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "15": { "dev": "Cannot send ether to nonpayable function", "fn": null, "offset": [ 925, 1659 ], "op": "REVERT", "path": "9" }, "16": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPDEST", "path": "9" }, "17": { "fn": null, "offset": [ 925, 1659 ], "op": "POP", "path": "9" }, "18": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "20": { "fn": null, "offset": [ 925, 1659 ], "op": "CALLDATASIZE", "path": "9" }, "21": { "fn": null, "offset": [ 925, 1659 ], "op": "LT", "path": "9" }, "22": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH2", "path": "9", "value": "0x57" }, "25": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPI", "path": "9" }, "26": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "28": { "fn": null, "offset": [ 925, 1659 ], "op": "CALLDATALOAD", "path": "9" }, "29": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH1", "path": "9", "value": "0xE0" }, "31": { "fn": null, "offset": [ 925, 1659 ], "op": "SHR", "path": "9" }, "32": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "33": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH4", "path": "9", "value": "0x19CC4B5A" }, "38": { "fn": null, "offset": [ 925, 1659 ], "op": "EQ", "path": "9" }, "39": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH2", "path": "9", "value": "0x5C" }, "42": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPI", "path": "9" }, "43": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "44": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH4", "path": "9", "value": "0x32424AA3" }, "49": { "fn": null, "offset": [ 925, 1659 ], "op": "EQ", "path": "9" }, "50": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH2", "path": "9", "value": "0x80" }, "53": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPI", "path": "9" }, "54": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "55": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH4", "path": "9", "value": "0x4E5551CE" }, "60": { "fn": null, "offset": [ 925, 1659 ], "op": "EQ", "path": "9" }, "61": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH2", "path": "9", "value": "0x9E" }, "64": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPI", "path": "9" }, "65": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "66": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH4", "path": "9", "value": "0xB895C74A" }, "71": { "fn": null, "offset": [ 925, 1659 ], "op": "EQ", "path": "9" }, "72": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH2", "path": "9", "value": "0xB8" }, "75": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPI", "path": "9" }, "76": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "77": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH4", "path": "9", "value": "0xF7E1C748" }, "82": { "fn": null, "offset": [ 925, 1659 ], "op": "EQ", "path": "9" }, "83": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH2", "path": "9", "value": "0xC0" }, "86": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPI", "path": "9" }, "87": { "fn": null, "offset": [ 925, 1659 ], "op": "JUMPDEST", "path": "9" }, "88": { "fn": null, "offset": [ 925, 1659 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "90": { "fn": null, "offset": [ 925, 1659 ], "op": "DUP1", "path": "9" }, "91": { "first_revert": true, "fn": null, "offset": [ 925, 1659 ], "op": "REVERT", "path": "9" }, "92": { "offset": [ 983, 1015 ], "op": "JUMPDEST", "path": "9" }, "93": { "fn": null, "offset": [ 983, 1015 ], "op": "PUSH2", "path": "9", "value": "0x64" }, "96": { "fn": null, "offset": [ 983, 1015 ], "op": "PUSH2", "path": "9", "value": "0xCA" }, "99": { "fn": null, "jump": "i", "offset": [ 983, 1015 ], "op": "JUMP", "path": "9" }, "100": { "fn": null, "offset": [ 983, 1015 ], "op": "JUMPDEST", "path": "9" }, "101": { "fn": null, "offset": [ 983, 1015 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "103": { "fn": null, "offset": [ 983, 1015 ], "op": "DUP1", "path": "9" }, "104": { "fn": null, "offset": [ 983, 1015 ], "op": "MLOAD", "path": "9" }, "105": { "op": "PUSH1", "value": "0x1" }, "107": { "op": "PUSH1", "value": "0x1" }, "109": { "op": "PUSH1", "value": "0xA0" }, "111": { "op": "SHL" }, "112": { "op": "SUB" }, "113": { "offset": [ 983, 1015 ], "op": "SWAP1", "path": "9" }, "114": { "fn": null, "offset": [ 983, 1015 ], "op": "SWAP3", "path": "9" }, "115": { "fn": null, "offset": [ 983, 1015 ], "op": "AND", "path": "9" }, "116": { "fn": null, "offset": [ 983, 1015 ], "op": "DUP3", "path": "9" }, "117": { "fn": null, "offset": [ 983, 1015 ], "op": "MSTORE", "path": "9" }, "118": { "fn": null, "offset": [ 983, 1015 ], "op": "MLOAD", "path": "9" }, "119": { "fn": null, "offset": [ 983, 1015 ], "op": "SWAP1", "path": "9" }, "120": { "fn": null, "offset": [ 983, 1015 ], "op": "DUP2", "path": "9" }, "121": { "fn": null, "offset": [ 983, 1015 ], "op": "SWAP1", "path": "9" }, "122": { "fn": null, "offset": [ 983, 1015 ], "op": "SUB", "path": "9" }, "123": { "fn": null, "offset": [ 983, 1015 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "125": { "fn": null, "offset": [ 983, 1015 ], "op": "ADD", "path": "9" }, "126": { "fn": null, "offset": [ 983, 1015 ], "op": "SWAP1", "path": "9" }, "127": { "fn": null, "offset": [ 983, 1015 ], "op": "RETURN", "path": "9" }, "128": { "offset": [ 1049, 1071 ], "op": "JUMPDEST", "path": "9" }, "129": { "fn": null, "offset": [ 1049, 1071 ], "op": "PUSH2", "path": "9", "value": "0x88" }, "132": { "fn": null, "offset": [ 1049, 1071 ], "op": "PUSH2", "path": "9", "value": "0xD9" }, "135": { "fn": null, "jump": "i", "offset": [ 1049, 1071 ], "op": "JUMP", "path": "9" }, "136": { "fn": null, "offset": [ 1049, 1071 ], "op": "JUMPDEST", "path": "9" }, "137": { "fn": null, "offset": [ 1049, 1071 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "139": { "fn": null, "offset": [ 1049, 1071 ], "op": "DUP1", "path": "9" }, "140": { "fn": null, "offset": [ 1049, 1071 ], "op": "MLOAD", "path": "9" }, "141": { "fn": null, "offset": [ 1049, 1071 ], "op": "PUSH1", "path": "9", "value": "0xFF" }, "143": { "fn": null, "offset": [ 1049, 1071 ], "op": "SWAP1", "path": "9" }, "144": { "fn": null, "offset": [ 1049, 1071 ], "op": "SWAP3", "path": "9" }, "145": { "fn": null, "offset": [ 1049, 1071 ], "op": "AND", "path": "9" }, "146": { "fn": null, "offset": [ 1049, 1071 ], "op": "DUP3", "path": "9" }, "147": { "fn": null, "offset": [ 1049, 1071 ], "op": "MSTORE", "path": "9" }, "148": { "fn": null, "offset": [ 1049, 1071 ], "op": "MLOAD", "path": "9" }, "149": { "fn": null, "offset": [ 1049, 1071 ], "op": "SWAP1", "path": "9" }, "150": { "fn": null, "offset": [ 1049, 1071 ], "op": "DUP2", "path": "9" }, "151": { "fn": null, "offset": [ 1049, 1071 ], "op": "SWAP1", "path": "9" }, "152": { "fn": null, "offset": [ 1049, 1071 ], "op": "SUB", "path": "9" }, "153": { "fn": null, "offset": [ 1049, 1071 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "155": { "fn": null, "offset": [ 1049, 1071 ], "op": "ADD", "path": "9" }, "156": { "fn": null, "offset": [ 1049, 1071 ], "op": "SWAP1", "path": "9" }, "157": { "fn": null, "offset": [ 1049, 1071 ], "op": "RETURN", "path": "9" }, "158": { "offset": [ 1077, 1116 ], "op": "JUMPDEST", "path": "9" }, "159": { "fn": null, "offset": [ 1077, 1116 ], "op": "PUSH2", "path": "9", "value": "0xA6" }, "162": { "fn": null, "offset": [ 1077, 1116 ], "op": "PUSH2", "path": "9", "value": "0xE2" }, "165": { "fn": null, "jump": "i", "offset": [ 1077, 1116 ], "op": "JUMP", "path": "9" }, "166": { "fn": null, "offset": [ 1077, 1116 ], "op": "JUMPDEST", "path": "9" }, "167": { "fn": null, "offset": [ 1077, 1116 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "169": { "fn": null, "offset": [ 1077, 1116 ], "op": "DUP1", "path": "9" }, "170": { "fn": null, "offset": [ 1077, 1116 ], "op": "MLOAD", "path": "9" }, "171": { "fn": null, "offset": [ 1077, 1116 ], "op": "SWAP2", "path": "9" }, "172": { "fn": null, "offset": [ 1077, 1116 ], "op": "DUP3", "path": "9" }, "173": { "fn": null, "offset": [ 1077, 1116 ], "op": "MSTORE", "path": "9" }, "174": { "fn": null, "offset": [ 1077, 1116 ], "op": "MLOAD", "path": "9" }, "175": { "fn": null, "offset": [ 1077, 1116 ], "op": "SWAP1", "path": "9" }, "176": { "fn": null, "offset": [ 1077, 1116 ], "op": "DUP2", "path": "9" }, "177": { "fn": null, "offset": [ 1077, 1116 ], "op": "SWAP1", "path": "9" }, "178": { "fn": null, "offset": [ 1077, 1116 ], "op": "SUB", "path": "9" }, "179": { "fn": null, "offset": [ 1077, 1116 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "181": { "fn": null, "offset": [ 1077, 1116 ], "op": "ADD", "path": "9" }, "182": { "fn": null, "offset": [ 1077, 1116 ], "op": "SWAP1", "path": "9" }, "183": { "fn": null, "offset": [ 1077, 1116 ], "op": "RETURN", "path": "9" }, "184": { "offset": [ 1022, 1043 ], "op": "JUMPDEST", "path": "9" }, "185": { "fn": null, "offset": [ 1022, 1043 ], "op": "PUSH2", "path": "9", "value": "0xA6" }, "188": { "fn": null, "offset": [ 1022, 1043 ], "op": "PUSH2", "path": "9", "value": "0xE8" }, "191": { "fn": null, "jump": "i", "offset": [ 1022, 1043 ], "op": "JUMP", "path": "9" }, "192": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1330, 1657 ], "op": "JUMPDEST", "path": "9" }, "193": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1330, 1657 ], "op": "PUSH2", "path": "9", "value": "0xC8" }, "196": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1330, 1657 ], "op": "PUSH2", "path": "9", "value": "0xEE" }, "199": { "fn": "FetchOracleTestClient.queryOracleValue", "jump": "i", "offset": [ 1330, 1657 ], "op": "JUMP", "path": "9" }, "200": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1330, 1657 ], "op": "JUMPDEST", "path": "9" }, "201": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1330, 1657 ], "op": "STOP", "path": "9" }, "202": { "offset": [ 983, 1015 ], "op": "JUMPDEST", "path": "9" }, "203": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 983, 1015 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "205": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 983, 1015 ], "op": "SLOAD", "path": "9" }, "206": { "op": "PUSH1", "value": "0x1" }, "208": { "op": "PUSH1", "value": "0x1" }, "210": { "op": "PUSH1", "value": "0xA0" }, "212": { "op": "SHL" }, "213": { "op": "SUB" }, "214": { "offset": [ 983, 1015 ], "op": "AND", "path": "9" }, "215": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 983, 1015 ], "op": "DUP2", "path": "9" }, "216": { "fn": "FetchOracleTestClient.queryOracleValue", "jump": "o", "offset": [ 983, 1015 ], "op": "JUMP", "path": "9" }, "217": { "offset": [ 1049, 1071 ], "op": "JUMPDEST", "path": "9" }, "218": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1049, 1071 ], "op": "PUSH1", "path": "9", "value": "0x2" }, "220": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1049, 1071 ], "op": "SLOAD", "path": "9" }, "221": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1049, 1071 ], "op": "PUSH1", "path": "9", "value": "0xFF" }, "223": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1049, 1071 ], "op": "AND", "path": "9" }, "224": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1049, 1071 ], "op": "DUP2", "path": "9" }, "225": { "fn": "FetchOracleTestClient.queryOracleValue", "jump": "o", "offset": [ 1049, 1071 ], "op": "JUMP", "path": "9" }, "226": { "offset": [ 1077, 1116 ], "op": "JUMPDEST", "path": "9" }, "227": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1077, 1116 ], "op": "PUSH1", "path": "9", "value": "0x3" }, "229": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1077, 1116 ], "op": "SLOAD", "path": "9" }, "230": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1077, 1116 ], "op": "DUP2", "path": "9" }, "231": { "fn": "FetchOracleTestClient.queryOracleValue", "jump": "o", "offset": [ 1077, 1116 ], "op": "JUMP", "path": "9" }, "232": { "offset": [ 1022, 1043 ], "op": "JUMPDEST", "path": "9" }, "233": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1022, 1043 ], "op": "PUSH1", "path": "9", "value": "0x1" }, "235": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1022, 1043 ], "op": "SLOAD", "path": "9" }, "236": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1022, 1043 ], "op": "DUP2", "path": "9" }, "237": { "fn": "FetchOracleTestClient.queryOracleValue", "jump": "o", "offset": [ 1022, 1043 ], "op": "JUMP", "path": "9" }, "238": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1330, 1657 ], "op": "JUMPDEST", "path": "9" }, "239": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "PUSH1", "path": "9", "statement": 0, "value": "0x0" }, "241": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "DUP1", "path": "9" }, "242": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "SLOAD", "path": "9" }, "243": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "SWAP1", "path": "9" }, "244": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "PUSH2", "path": "9", "value": "0x100" }, "247": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "EXP", "path": "9" }, "248": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "SWAP1", "path": "9" }, "249": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "DIV", "path": "9" }, "250": { "op": "PUSH1", "value": "0x1" }, "252": { "op": "PUSH1", "value": "0x1" }, "254": { "op": "PUSH1", "value": "0xA0" }, "256": { "op": "SHL" }, "257": { "op": "SUB" }, "258": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1403 ], "op": "AND", "path": "9" }, "259": { "op": "PUSH1", "value": "0x1" }, "261": { "op": "PUSH1", "value": "0x1" }, "263": { "op": "PUSH1", "value": "0xA0" }, "265": { "op": "SHL" }, "266": { "op": "SUB" }, "267": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1410 ], "op": "AND", "path": "9" }, "268": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1410 ], "op": "PUSH4", "path": "9", "value": "0xECD0C0C3" }, "273": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "275": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "MLOAD", "path": "9" }, "276": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP2", "path": "9" }, "277": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH4", "path": "9", "value": "0xFFFFFFFF" }, "282": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "AND", "path": "9" }, "283": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0xE0" }, "285": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "SHL", "path": "9" }, "286": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP2", "path": "9" }, "287": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "MSTORE", "path": "9" }, "288": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "290": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "ADD", "path": "9" }, "291": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "293": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "295": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "MLOAD", "path": "9" }, "296": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP1", "path": "9" }, "297": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP4", "path": "9" }, "298": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "SUB", "path": "9" }, "299": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP2", "path": "9" }, "300": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP7", "path": "9" }, "301": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP1", "path": "9" }, "302": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "EXTCODESIZE", "path": "9" }, "303": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "ISZERO", "path": "9" }, "304": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP1", "path": "9" }, "305": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "ISZERO", "path": "9" }, "306": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH2", "path": "9", "value": "0x13A" }, "309": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "JUMPI", "path": "9" }, "310": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "312": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP1", "path": "9" }, "313": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "REVERT", "path": "9" }, "314": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "JUMPDEST", "path": "9" }, "315": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "POP", "path": "9" }, "316": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "GAS", "path": "9" }, "317": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "STATICCALL", "path": "9" }, "318": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "ISZERO", "path": "9" }, "319": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP1", "path": "9" }, "320": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "ISZERO", "path": "9" }, "321": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH2", "path": "9", "value": "0x14E" }, "324": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "JUMPI", "path": "9" }, "325": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "RETURNDATASIZE", "path": "9" }, "326": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "328": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP1", "path": "9" }, "329": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "RETURNDATACOPY", "path": "9" }, "330": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "RETURNDATASIZE", "path": "9" }, "331": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "333": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "REVERT", "path": "9" }, "334": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "JUMPDEST", "path": "9" }, "335": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "POP", "path": "9" }, "336": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "POP", "path": "9" }, "337": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "POP", "path": "9" }, "338": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "POP", "path": "9" }, "339": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "341": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "MLOAD", "path": "9" }, "342": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "RETURNDATASIZE", "path": "9" }, "343": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "345": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP2", "path": "9" }, "346": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "LT", "path": "9" }, "347": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "ISZERO", "path": "9" }, "348": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH2", "path": "9", "value": "0x164" }, "351": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "JUMPI", "path": "9" }, "352": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "354": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "DUP1", "path": "9" }, "355": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "REVERT", "path": "9" }, "356": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "JUMPDEST", "path": "9" }, "357": { "op": "POP" }, "358": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "MLOAD", "path": "9" }, "359": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "361": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "SLOAD", "path": "9" }, "362": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "364": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "365": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "MLOAD", "path": "9" }, "366": { "op": "PUSH4", "value": "0x62D9BE11" }, "371": { "op": "PUSH1", "value": "0xE1" }, "373": { "op": "SHL" }, "374": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP2", "path": "9" }, "375": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "MSTORE", "path": "9" }, "376": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "SWAP1", "path": "9" }, "377": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "MLOAD", "path": "9" }, "378": { "op": "PUSH1", "value": "0x1" }, "380": { "op": "PUSH1", "value": "0x1" }, "382": { "op": "PUSH1", "value": "0xA0" }, "384": { "op": "SHL" }, "385": { "op": "SUB" }, "386": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1425 ], "op": "SWAP4", "path": "9" }, "387": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1425 ], "op": "DUP5", "path": "9" }, "388": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1425 ], "op": "AND", "path": "9" }, "389": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1425 ], "op": "SWAP4", "path": "9" }, "390": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1425 ], "op": "PUSH4", "path": "9", "value": "0x23B872DD" }, "395": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1425 ], "op": "SWAP4", "path": "9" }, "396": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1426, 1436 ], "op": "CALLER", "path": "9" }, "397": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1426, 1436 ], "op": "SWAP4", "path": "9" }, "398": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1446, 1450 ], "op": "ADDRESS", "path": "9" }, "399": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1446, 1450 ], "op": "SWAP4", "path": "9" }, "400": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "SWAP2", "path": "9" }, "401": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "SWAP1", "path": "9" }, "402": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "SWAP3", "path": "9" }, "403": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "AND", "path": "9" }, "404": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "SWAP2", "path": "9" }, "405": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1471 ], "op": "PUSH4", "path": "9", "value": "0xC5B37C22" }, "410": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1471 ], "op": "SWAP2", "path": "9" }, "411": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "413": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "414": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP4", "path": "9" }, "415": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "ADD", "path": "9" }, "416": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "SWAP3", "path": "9" }, "417": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "419": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1412 ], "op": "SWAP3", "path": "9" }, "420": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "SWAP2", "path": "9" }, "421": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "SWAP1", "path": "9" }, "422": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP3", "path": "9" }, "423": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "SWAP1", "path": "9" }, "424": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "SUB", "path": "9" }, "425": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "ADD", "path": "9" }, "426": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP2", "path": "9" }, "427": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1466 ], "op": "DUP7", "path": "9" }, "428": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "429": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "EXTCODESIZE", "path": "9" }, "430": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "ISZERO", "path": "9" }, "431": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "432": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "ISZERO", "path": "9" }, "433": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH2", "path": "9", "value": "0x1B9" }, "436": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "JUMPI", "path": "9" }, "437": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "439": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "440": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "REVERT", "path": "9" }, "441": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "JUMPDEST", "path": "9" }, "442": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "POP", "path": "9" }, "443": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "GAS", "path": "9" }, "444": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "STATICCALL", "path": "9" }, "445": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "ISZERO", "path": "9" }, "446": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "447": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "ISZERO", "path": "9" }, "448": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH2", "path": "9", "value": "0x1CD" }, "451": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "JUMPI", "path": "9" }, "452": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "RETURNDATASIZE", "path": "9" }, "453": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "455": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "456": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "RETURNDATACOPY", "path": "9" }, "457": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "RETURNDATASIZE", "path": "9" }, "458": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "460": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "REVERT", "path": "9" }, "461": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "JUMPDEST", "path": "9" }, "462": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "POP", "path": "9" }, "463": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "POP", "path": "9" }, "464": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "POP", "path": "9" }, "465": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "POP", "path": "9" }, "466": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "468": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "MLOAD", "path": "9" }, "469": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "RETURNDATASIZE", "path": "9" }, "470": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "472": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP2", "path": "9" }, "473": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "LT", "path": "9" }, "474": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "ISZERO", "path": "9" }, "475": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH2", "path": "9", "value": "0x1E3" }, "478": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "JUMPI", "path": "9" }, "479": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "481": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "DUP1", "path": "9" }, "482": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "REVERT", "path": "9" }, "483": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "JUMPDEST", "path": "9" }, "484": { "op": "POP" }, "485": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "MLOAD", "path": "9" }, "486": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "488": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "489": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "MLOAD", "path": "9" }, "490": { "op": "PUSH1", "value": "0x1" }, "492": { "op": "PUSH1", "value": "0x1" }, "494": { "op": "PUSH1", "value": "0xE0" }, "496": { "op": "SHL" }, "497": { "op": "SUB" }, "498": { "op": "NOT" }, "499": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0xE0" }, "501": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP8", "path": "9" }, "502": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP1", "path": "9" }, "503": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SHL", "path": "9" }, "504": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "AND", "path": "9" }, "505": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP2", "path": "9" }, "506": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "MSTORE", "path": "9" }, "507": { "op": "PUSH1", "value": "0x1" }, "509": { "op": "PUSH1", "value": "0x1" }, "511": { "op": "PUSH1", "value": "0xA0" }, "513": { "op": "SHL" }, "514": { "op": "SUB" }, "515": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP5", "path": "9" }, "516": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP6", "path": "9" }, "517": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "AND", "path": "9" }, "518": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "520": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP3", "path": "9" }, "521": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ADD", "path": "9" }, "522": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "MSTORE", "path": "9" }, "523": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP3", "path": "9" }, "524": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP1", "path": "9" }, "525": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP4", "path": "9" }, "526": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "AND", "path": "9" }, "527": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x24" }, "529": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP4", "path": "9" }, "530": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ADD", "path": "9" }, "531": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "MSTORE", "path": "9" }, "532": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x44" }, "534": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP3", "path": "9" }, "535": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ADD", "path": "9" }, "536": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "MSTORE", "path": "9" }, "537": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP1", "path": "9" }, "538": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "MLOAD", "path": "9" }, "539": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x64" }, "541": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "542": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP4", "path": "9" }, "543": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ADD", "path": "9" }, "544": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP3", "path": "9" }, "545": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "547": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1453, 1473 ], "op": "SWAP3", "path": "9" }, "548": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP2", "path": "9" }, "549": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP1", "path": "9" }, "550": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP3", "path": "9" }, "551": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP1", "path": "9" }, "552": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SUB", "path": "9" }, "553": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ADD", "path": "9" }, "554": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP2", "path": "9" }, "555": { "op": "PUSH1", "value": "0x0" }, "557": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP8", "path": "9" }, "558": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "559": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "EXTCODESIZE", "path": "9" }, "560": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ISZERO", "path": "9" }, "561": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "562": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ISZERO", "path": "9" }, "563": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH2", "path": "9", "value": "0x23B" }, "566": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "JUMPI", "path": "9" }, "567": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "569": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "570": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "REVERT", "path": "9" }, "571": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "JUMPDEST", "path": "9" }, "572": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "POP", "path": "9" }, "573": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "GAS", "path": "9" }, "574": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "CALL", "path": "9" }, "575": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ISZERO", "path": "9" }, "576": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "577": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ISZERO", "path": "9" }, "578": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH2", "path": "9", "value": "0x24F" }, "581": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "JUMPI", "path": "9" }, "582": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "RETURNDATASIZE", "path": "9" }, "583": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "585": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "586": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "RETURNDATACOPY", "path": "9" }, "587": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "RETURNDATASIZE", "path": "9" }, "588": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "590": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "REVERT", "path": "9" }, "591": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "JUMPDEST", "path": "9" }, "592": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "POP", "path": "9" }, "593": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "POP", "path": "9" }, "594": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "POP", "path": "9" }, "595": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "POP", "path": "9" }, "596": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "598": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "MLOAD", "path": "9" }, "599": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "RETURNDATASIZE", "path": "9" }, "600": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "602": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP2", "path": "9" }, "603": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "LT", "path": "9" }, "604": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "ISZERO", "path": "9" }, "605": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH2", "path": "9", "value": "0x265" }, "608": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "JUMPI", "path": "9" }, "609": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "611": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "DUP1", "path": "9" }, "612": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "REVERT", "path": "9" }, "613": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "JUMPDEST", "path": "9" }, "614": { "op": "POP" }, "615": { "op": "POP" }, "616": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1497 ], "op": "PUSH1", "path": "9", "statement": 1, "value": "0x0" }, "618": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1497 ], "op": "SLOAD", "path": "9" }, "619": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "621": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "622": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "MLOAD", "path": "9" }, "623": { "op": "PUSH4", "value": "0xECD0C0C3" }, "628": { "op": "PUSH1", "value": "0xE0" }, "630": { "op": "SHL" }, "631": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP2", "path": "9" }, "632": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "MSTORE", "path": "9" }, "633": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SWAP1", "path": "9" }, "634": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "MLOAD", "path": "9" }, "635": { "op": "PUSH1", "value": "0x1" }, "637": { "op": "PUSH1", "value": "0x1" }, "639": { "op": "PUSH1", "value": "0xA0" }, "641": { "op": "SHL" }, "642": { "op": "SUB" }, "643": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1497 ], "op": "SWAP1", "path": "9" }, "644": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1497 ], "op": "SWAP3", "path": "9" }, "645": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1497 ], "op": "AND", "path": "9" }, "646": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1497 ], "op": "SWAP2", "path": "9" }, "647": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1504 ], "op": "PUSH4", "path": "9", "value": "0xECD0C0C3" }, "652": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1504 ], "op": "SWAP2", "path": "9" }, "653": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "655": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "656": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP3", "path": "9" }, "657": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "ADD", "path": "9" }, "658": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SWAP3", "path": "9" }, "659": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "661": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1390, 1474 ], "op": "SWAP3", "path": "9" }, "662": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SWAP1", "path": "9" }, "663": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SWAP2", "path": "9" }, "664": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SWAP1", "path": "9" }, "665": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP3", "path": "9" }, "666": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SWAP1", "path": "9" }, "667": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SUB", "path": "9" }, "668": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "ADD", "path": "9" }, "669": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP2", "path": "9" }, "670": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1497 ], "op": "DUP7", "path": "9" }, "671": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "672": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "EXTCODESIZE", "path": "9" }, "673": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "ISZERO", "path": "9" }, "674": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "675": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "ISZERO", "path": "9" }, "676": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH2", "path": "9", "value": "0x2AC" }, "679": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "JUMPI", "path": "9" }, "680": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "682": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "683": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "REVERT", "path": "9" }, "684": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "JUMPDEST", "path": "9" }, "685": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "POP", "path": "9" }, "686": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "GAS", "path": "9" }, "687": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "STATICCALL", "path": "9" }, "688": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "ISZERO", "path": "9" }, "689": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "690": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "ISZERO", "path": "9" }, "691": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH2", "path": "9", "value": "0x2C0" }, "694": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "JUMPI", "path": "9" }, "695": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "RETURNDATASIZE", "path": "9" }, "696": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "698": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "699": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "RETURNDATACOPY", "path": "9" }, "700": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "RETURNDATASIZE", "path": "9" }, "701": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "703": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "REVERT", "path": "9" }, "704": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "JUMPDEST", "path": "9" }, "705": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "POP", "path": "9" }, "706": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "POP", "path": "9" }, "707": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "POP", "path": "9" }, "708": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "POP", "path": "9" }, "709": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "711": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "MLOAD", "path": "9" }, "712": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "RETURNDATASIZE", "path": "9" }, "713": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "715": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP2", "path": "9" }, "716": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "LT", "path": "9" }, "717": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "ISZERO", "path": "9" }, "718": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH2", "path": "9", "value": "0x2D6" }, "721": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "JUMPI", "path": "9" }, "722": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "724": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "DUP1", "path": "9" }, "725": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "REVERT", "path": "9" }, "726": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "JUMPDEST", "path": "9" }, "727": { "op": "POP" }, "728": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "MLOAD", "path": "9" }, "729": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1523, 1536 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "731": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1523, 1536 ], "op": "SLOAD", "path": "9" }, "732": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "734": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "735": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "MLOAD", "path": "9" }, "736": { "op": "PUSH4", "value": "0x62D9BE11" }, "741": { "op": "PUSH1", "value": "0xE1" }, "743": { "op": "SHL" }, "744": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP2", "path": "9" }, "745": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "MSTORE", "path": "9" }, "746": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SWAP1", "path": "9" }, "747": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "MLOAD", "path": "9" }, "748": { "op": "PUSH1", "value": "0x1" }, "750": { "op": "PUSH1", "value": "0x1" }, "752": { "op": "PUSH1", "value": "0xA0" }, "754": { "op": "SHL" }, "755": { "op": "SUB" }, "756": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1514 ], "op": "SWAP4", "path": "9" }, "757": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1514 ], "op": "DUP5", "path": "9" }, "758": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1514 ], "op": "AND", "path": "9" }, "759": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1514 ], "op": "SWAP4", "path": "9" }, "760": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1514 ], "op": "PUSH4", "path": "9", "value": "0x95EA7B3" }, "765": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1514 ], "op": "SWAP4", "path": "9" }, "766": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1523, 1536 ], "op": "AND", "path": "9" }, "767": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1523, 1536 ], "op": "SWAP2", "path": "9" }, "768": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1523, 1536 ], "op": "DUP3", "path": "9" }, "769": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1523, 1536 ], "op": "SWAP2", "path": "9" }, "770": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1557 ], "op": "PUSH4", "path": "9", "value": "0xC5B37C22" }, "775": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1557 ], "op": "SWAP2", "path": "9" }, "776": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "778": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "779": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP3", "path": "9" }, "780": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "ADD", "path": "9" }, "781": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SWAP3", "path": "9" }, "782": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "784": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1506 ], "op": "SWAP3", "path": "9" }, "785": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SWAP1", "path": "9" }, "786": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SWAP2", "path": "9" }, "787": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SWAP1", "path": "9" }, "788": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP3", "path": "9" }, "789": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SWAP1", "path": "9" }, "790": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SUB", "path": "9" }, "791": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "ADD", "path": "9" }, "792": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP2", "path": "9" }, "793": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1523, 1536 ], "op": "DUP7", "path": "9" }, "794": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "795": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "EXTCODESIZE", "path": "9" }, "796": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "ISZERO", "path": "9" }, "797": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "798": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "ISZERO", "path": "9" }, "799": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH2", "path": "9", "value": "0x327" }, "802": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "JUMPI", "path": "9" }, "803": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "805": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "806": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "REVERT", "path": "9" }, "807": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "JUMPDEST", "path": "9" }, "808": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "POP", "path": "9" }, "809": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "GAS", "path": "9" }, "810": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "STATICCALL", "path": "9" }, "811": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "ISZERO", "path": "9" }, "812": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "813": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "ISZERO", "path": "9" }, "814": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH2", "path": "9", "value": "0x33B" }, "817": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "JUMPI", "path": "9" }, "818": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "RETURNDATASIZE", "path": "9" }, "819": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "821": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "822": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "RETURNDATACOPY", "path": "9" }, "823": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "RETURNDATASIZE", "path": "9" }, "824": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "826": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "REVERT", "path": "9" }, "827": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "JUMPDEST", "path": "9" }, "828": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "POP", "path": "9" }, "829": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "POP", "path": "9" }, "830": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "POP", "path": "9" }, "831": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "POP", "path": "9" }, "832": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "834": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "MLOAD", "path": "9" }, "835": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "RETURNDATASIZE", "path": "9" }, "836": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "838": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP2", "path": "9" }, "839": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "LT", "path": "9" }, "840": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "ISZERO", "path": "9" }, "841": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH2", "path": "9", "value": "0x351" }, "844": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "JUMPI", "path": "9" }, "845": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "847": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "DUP1", "path": "9" }, "848": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "REVERT", "path": "9" }, "849": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "JUMPDEST", "path": "9" }, "850": { "op": "POP" }, "851": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "MLOAD", "path": "9" }, "852": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "854": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "855": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "MLOAD", "path": "9" }, "856": { "op": "PUSH1", "value": "0x1" }, "858": { "op": "PUSH1", "value": "0x1" }, "860": { "op": "PUSH1", "value": "0xE0" }, "862": { "op": "SHL" }, "863": { "op": "SUB" }, "864": { "op": "NOT" }, "865": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0xE0" }, "867": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP7", "path": "9" }, "868": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP1", "path": "9" }, "869": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SHL", "path": "9" }, "870": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "AND", "path": "9" }, "871": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP2", "path": "9" }, "872": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "MSTORE", "path": "9" }, "873": { "op": "PUSH1", "value": "0x1" }, "875": { "op": "PUSH1", "value": "0x1" }, "877": { "op": "PUSH1", "value": "0xA0" }, "879": { "op": "SHL" }, "880": { "op": "SUB" }, "881": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP1", "path": "9" }, "882": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP4", "path": "9" }, "883": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "AND", "path": "9" }, "884": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "886": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP5", "path": "9" }, "887": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ADD", "path": "9" }, "888": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "MSTORE", "path": "9" }, "889": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x24" }, "891": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP4", "path": "9" }, "892": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ADD", "path": "9" }, "893": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP2", "path": "9" }, "894": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP1", "path": "9" }, "895": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP2", "path": "9" }, "896": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "MSTORE", "path": "9" }, "897": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "MLOAD", "path": "9" }, "898": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x44" }, "900": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "901": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP4", "path": "9" }, "902": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ADD", "path": "9" }, "903": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP3", "path": "9" }, "904": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "906": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1539, 1559 ], "op": "SWAP3", "path": "9" }, "907": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP2", "path": "9" }, "908": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP1", "path": "9" }, "909": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP3", "path": "9" }, "910": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SWAP1", "path": "9" }, "911": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "SUB", "path": "9" }, "912": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ADD", "path": "9" }, "913": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP2", "path": "9" }, "914": { "op": "PUSH1", "value": "0x0" }, "916": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP8", "path": "9" }, "917": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "918": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "EXTCODESIZE", "path": "9" }, "919": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ISZERO", "path": "9" }, "920": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "921": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ISZERO", "path": "9" }, "922": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH2", "path": "9", "value": "0x3A2" }, "925": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "JUMPI", "path": "9" }, "926": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "928": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "929": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "REVERT", "path": "9" }, "930": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "JUMPDEST", "path": "9" }, "931": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "POP", "path": "9" }, "932": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "GAS", "path": "9" }, "933": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "CALL", "path": "9" }, "934": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ISZERO", "path": "9" }, "935": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "936": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ISZERO", "path": "9" }, "937": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH2", "path": "9", "value": "0x3B6" }, "940": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "JUMPI", "path": "9" }, "941": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "RETURNDATASIZE", "path": "9" }, "942": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "944": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "945": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "RETURNDATACOPY", "path": "9" }, "946": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "RETURNDATASIZE", "path": "9" }, "947": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "949": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "REVERT", "path": "9" }, "950": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "JUMPDEST", "path": "9" }, "951": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "POP", "path": "9" }, "952": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "POP", "path": "9" }, "953": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "POP", "path": "9" }, "954": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "POP", "path": "9" }, "955": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "957": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "MLOAD", "path": "9" }, "958": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "RETURNDATASIZE", "path": "9" }, "959": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "961": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP2", "path": "9" }, "962": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "LT", "path": "9" }, "963": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "ISZERO", "path": "9" }, "964": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH2", "path": "9", "value": "0x3CC" }, "967": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "JUMPI", "path": "9" }, "968": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "970": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "DUP1", "path": "9" }, "971": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "REVERT", "path": "9" }, "972": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1484, 1560 ], "op": "JUMPDEST", "path": "9" }, "973": { "op": "POP" }, "974": { "op": "POP" }, "975": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "PUSH1", "path": "9", "statement": 2, "value": "0x0" }, "977": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "DUP1", "path": "9" }, "978": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "SLOAD", "path": "9" }, "979": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "981": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "982": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "MLOAD", "path": "9" }, "983": { "op": "PUSH4", "value": "0x1EFC38E9" }, "988": { "op": "PUSH1", "value": "0xE3" }, "990": { "op": "SHL" }, "991": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP2", "path": "9" }, "992": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "MSTORE", "path": "9" }, "993": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP1", "path": "9" }, "994": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "MLOAD", "path": "9" }, "995": { "op": "PUSH1", "value": "0x1" }, "997": { "op": "PUSH1", "value": "0x1" }, "999": { "op": "PUSH1", "value": "0xA0" }, "1001": { "op": "SHL" }, "1002": { "op": "SUB" }, "1003": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "SWAP1", "path": "9" }, "1004": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "SWAP3", "path": "9" }, "1005": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "AND", "path": "9" }, "1006": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "SWAP3", "path": "9" }, "1007": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1648 ], "op": "PUSH4", "path": "9", "value": "0xF7E1C748" }, "1012": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1648 ], "op": "SWAP3", "path": "9" }, "1013": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x4" }, "1015": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1016": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP5", "path": "9" }, "1017": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ADD", "path": "9" }, "1018": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP4", "path": "9" }, "1019": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x60" }, "1021": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP4", "path": "9" }, "1022": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP1", "path": "9" }, "1023": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP4", "path": "9" }, "1024": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP1", "path": "9" }, "1025": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SUB", "path": "9" }, "1026": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP1", "path": "9" }, "1027": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP2", "path": "9" }, "1028": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ADD", "path": "9" }, "1029": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP1", "path": "9" }, "1030": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP3", "path": "9" }, "1031": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP1", "path": "9" }, "1032": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1631 ], "op": "DUP8", "path": "9" }, "1033": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1034": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "EXTCODESIZE", "path": "9" }, "1035": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ISZERO", "path": "9" }, "1036": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1037": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ISZERO", "path": "9" }, "1038": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH2", "path": "9", "value": "0x416" }, "1041": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "JUMPI", "path": "9" }, "1042": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "1044": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1045": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "REVERT", "path": "9" }, "1046": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "JUMPDEST", "path": "9" }, "1047": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "POP", "path": "9" }, "1048": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "GAS", "path": "9" }, "1049": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "CALL", "path": "9" }, "1050": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ISZERO", "path": "9" }, "1051": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1052": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ISZERO", "path": "9" }, "1053": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH2", "path": "9", "value": "0x42A" }, "1056": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "JUMPI", "path": "9" }, "1057": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "RETURNDATASIZE", "path": "9" }, "1058": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "1060": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1061": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "RETURNDATACOPY", "path": "9" }, "1062": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "RETURNDATASIZE", "path": "9" }, "1063": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "1065": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "REVERT", "path": "9" }, "1066": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "JUMPDEST", "path": "9" }, "1067": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "POP", "path": "9" }, "1068": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "POP", "path": "9" }, "1069": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "POP", "path": "9" }, "1070": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "POP", "path": "9" }, "1071": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "1073": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "MLOAD", "path": "9" }, "1074": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "RETURNDATASIZE", "path": "9" }, "1075": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x60" }, "1077": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP2", "path": "9" }, "1078": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "LT", "path": "9" }, "1079": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ISZERO", "path": "9" }, "1080": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH2", "path": "9", "value": "0x440" }, "1083": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "JUMPI", "path": "9" }, "1084": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x0" }, "1086": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1087": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "REVERT", "path": "9" }, "1088": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "JUMPDEST", "path": "9" }, "1089": { "op": "POP" }, "1090": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP1", "path": "9" }, "1091": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "MLOAD", "path": "9" }, "1092": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x20" }, "1094": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "DUP3", "path": "9" }, "1095": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ADD", "path": "9" }, "1096": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "MLOAD", "path": "9" }, "1097": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "PUSH1", "path": "9", "value": "0x40" }, "1099": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP1", "path": "9" }, "1100": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "SWAP3", "path": "9" }, "1101": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "ADD", "path": "9" }, "1102": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1618, 1650 ], "op": "MLOAD", "path": "9" }, "1103": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1590, 1614 ], "op": "PUSH1", "path": "9", "value": "0x3" }, "1105": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SSTORE", "path": "9" }, "1106": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1579, 1588 ], "op": "PUSH1", "path": "9", "value": "0x2" }, "1108": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "DUP1", "path": "9" }, "1109": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SLOAD", "path": "9" }, "1110": { "op": "PUSH1", "value": "0xFF" }, "1112": { "op": "NOT" }, "1113": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "AND", "path": "9" }, "1114": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "PUSH1", "path": "9", "value": "0xFF" }, "1116": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SWAP1", "path": "9" }, "1117": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SWAP4", "path": "9" }, "1118": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "AND", "path": "9" }, "1119": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SWAP3", "path": "9" }, "1120": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SWAP1", "path": "9" }, "1121": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SWAP3", "path": "9" }, "1122": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "OR", "path": "9" }, "1123": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SWAP1", "path": "9" }, "1124": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SWAP2", "path": "9" }, "1125": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SSTORE", "path": "9" }, "1126": { "op": "PUSH1", "value": "0x1" }, "1128": { "fn": "FetchOracleTestClient.queryOracleValue", "offset": [ 1570, 1650 ], "op": "SSTORE", "path": "9" }, "1129": { "fn": "FetchOracleTestClient.queryOracleValue", "jump": "o", "offset": [ 1330, 1657 ], "op": "JUMP", "path": "9" } }, "sha1": "1e1432f308d186ca35fb73d8f7bc376195fdd503", "source": "// SPDX-License-Identifier:Apache-2.0\n//------------------------------------------------------------------------------\n//\n// Copyright 2020 Fetch.AI Limited\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//------------------------------------------------------------------------------\n\npragma solidity ^0.6.0;\n\nimport \"./FetchOracleMock.sol\";\nimport \"../interfaces/IFetchOracleClient.sol\";\n\ncontract FetchOracleTestClient is IFetchOracleClient{\n FetchOracle public _fetch_oracle;\n\n uint256 public _value;\n uint8 public _decimals;\n uint256 public _updatedAtEthBlockNumber;\n\n\n constructor(address fetchOracleContractAddress) public {\n // NOTE(pb): We expect this to de deployed on testnet only:\n _fetch_oracle = FetchOracleMock(fetchOracleContractAddress);\n }\n\n\n function queryOracleValue() external override\n {\n _fetch_oracle._token().transferFrom(msg.sender, address(this), _fetch_oracle._fee());\n _fetch_oracle._token().approve(address(_fetch_oracle), _fetch_oracle._fee());\n (_value, _decimals, _updatedAtEthBlockNumber) = _fetch_oracle.queryOracleValue();\n }\n}\n\n// Contract created exclusively for more comfortable usage in truffle:\ncontract FetchOracleTestClientETH is FetchOracleTestClient {\n constructor(address fetchOracleContractAddress) public FetchOracleTestClient(fetchOracleContractAddress) {\n }\n}\n", "sourceMap": "925:734:9:-:0;;;1124:199;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1124:199:9;1257:13;:59;;-1:-1:-1;;;;;1257:59:9;;;-1:-1:-1;;;;;;1257:59:9;;;;;;;;;925:734;;;;;;", "sourcePath": "contracts/FetchOracleTestClient.sol", "type": "contract" } ================================================ FILE: packages/fetchai/contracts/oracle_client/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the class to connect to an oracle client contract.""" import logging from typing import cast from aea_ledger_ethereum import EthereumApi from aea_ledger_fetchai import FetchAIApi from aea.common import Address, JSONLike from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi PUBLIC_ID = PublicId.from_str("fetchai/oracle_client:0.11.3") _default_logger = logging.getLogger( "aea.packages.fetchai.contracts.oracle_client.contract" ) class FetchOracleClientContract(Contract): """The Fetch oracle client contract.""" @classmethod def get_query_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, query_function: str, amount: int = 0, gas: int = 0, tx_fee: int = 0, ) -> JSONLike: """ Get transaction to query oracle value in contract :param ledger_api: the ledger apis. :param contract_address: the contract address. :param from_address: the address of the transaction sender. :param query_function: the query oracle value function. :param amount: the amount to transfer as part of the transaction. :param gas: the gas limit for the transaction. :param tx_fee: the transaction fee. :return: the query transaction """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) function = getattr(instance.functions, query_function) query_args = () intermediate = function(*query_args) tx = intermediate.buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "from": from_address, "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier == FetchAIApi.identifier: msg = {"query_oracle_value": {}} # type: JSONLike fetchai_api = cast(FetchAIApi, ledger_api) tx = fetchai_api.get_handle_transaction( from_address, contract_address, msg, amount=amount, tx_fee=tx_fee, gas=gas, ) return tx raise NotImplementedError ================================================ FILE: packages/fetchai/contracts/oracle_client/contract.yaml ================================================ name: oracle_client author: fetchai version: 0.11.3 type: contract description: Fetch oracle client contract license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qmasz3mr6zrBNWULfiMeeMC9py561evkcwsCbkjEZnwto5 __init__.py: QmYWrZY2q18XGiS8EDki97v3Gi9KHLi1YhC7e7cpbkEXU2 build/FetchOracleTestClient.json: QmbqwQiYs8Tb2DKB1BDiigqmXxVt1BmfM5RVXHwkvysdqf build/oracle_client.wasm: QmXE7H9JqjJFNEzjUiMv5Sv6NyaMqB4eYDwSVym66L52xU contract.py: QmfHnZjmgtAmzJCVVWbePYy6D2brchHt2iCQX3E2rPsG86 contracts/FetchOracleTestClient.sol: QmWpUJ4aBrNreiyuXe6EgfSfE7T7hWz3xHDdT7fFye3WCG fingerprint_ignore_patterns: [] class_name: FetchOracleClientContract contract_interface_paths: ethereum: build/FetchOracleTestClient.json fetchai: build/oracle_client.wasm dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/contracts/oracle_client/contracts/FetchOracleTestClient.sol ================================================ // SPDX-License-Identifier:Apache-2.0 //------------------------------------------------------------------------------ // // Copyright 2020 Fetch.AI Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //------------------------------------------------------------------------------ pragma solidity ^0.6.0; import "./FetchOracleMock.sol"; import "../interfaces/IFetchOracleClient.sol"; contract FetchOracleTestClient is IFetchOracleClient{ FetchOracle public _fetch_oracle; uint256 public _value; uint8 public _decimals; uint256 public _updatedAtEthBlockNumber; constructor(address fetchOracleContractAddress) public { // NOTE(pb): We expect this to de deployed on testnet only: _fetch_oracle = FetchOracleMock(fetchOracleContractAddress); } function queryOracleValue() external override { _fetch_oracle._token().transferFrom(msg.sender, address(this), _fetch_oracle._fee()); _fetch_oracle._token().approve(address(_fetch_oracle), _fetch_oracle._fee()); (_value, _decimals, _updatedAtEthBlockNumber) = _fetch_oracle.queryOracleValue(); } } // Contract created exclusively for more comfortable usage in truffle: contract FetchOracleTestClientETH is FetchOracleTestClient { constructor(address fetchOracleContractAddress) public FetchOracleTestClient(fetchOracleContractAddress) { } } ================================================ FILE: packages/fetchai/contracts/staking_erc20/README.md ================================================ # Fetch Staking ERC20 Contract ## Description This contract package is used to interface with a Fetch staking ERC20 contract. ## Functions - `get_stake(address)`: Get the balance for a specific `address` ================================================ FILE: packages/fetchai/contracts/staking_erc20/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the support resources for the staking_erc20 contract.""" ================================================ FILE: packages/fetchai/contracts/staking_erc20/build/Migrations.json ================================================ { "contractName": "Migrations", "abi": [ { "constant": true, "inputs": [], "name": "last_completed_migration", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "constant": false, "inputs": [ { "name": "completed", "type": "uint256" } ], "name": "setCompleted", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "new_address", "type": "address" } ], "name": "upgrade", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ], "metadata": "{\"compiler\":{\"version\":\"0.5.2+commit.1df8f40c\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":false,\"inputs\":[{\"name\":\"new_address\",\"type\":\"address\"}],\"name\":\"upgrade\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"last_completed_migration\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"completed\",\"type\":\"uint256\"}],\"name\":\"setCompleted\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol\":\"Migrations\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol\":{\"keccak256\":\"0xfdb731592344e2a2890faf03baec7b4bee7057ffba18ba6dbb6eec8db85f8f4c\",\"urls\":[\"bzzr://ddc8801d0a2a7220c2c9bf3881b4921817e72fdd96827ec8be4428fa009ace07\"]}},\"version\":1}", "bytecode": "0x608060405234801561001057600080fd5b5060008054600160a060020a03191633179055610230806100326000396000f3fe608060405234801561001057600080fd5b5060043610610068577c010000000000000000000000000000000000000000000000000000000060003504630900f010811461006d578063445df0ac146100a25780638da5cb5b146100bc578063fdacd576146100ed575b600080fd5b6100a06004803603602081101561008357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661010a565b005b6100aa6101bd565b60408051918252519081900360200190f35b6100c46101c3565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b6100a06004803603602081101561010357600080fd5b50356101df565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760008190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101a057600080fd5b505af11580156101b4573d6000803e3d6000fd5b50505050505b50565b60015481565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760015556fea165627a7a7230582065546060ae2694c0faa5ae3a86876c1f4f60c32218234b846e28adec7370506c0029", "deployedBytecode": "0x608060405234801561001057600080fd5b5060043610610068577c010000000000000000000000000000000000000000000000000000000060003504630900f010811461006d578063445df0ac146100a25780638da5cb5b146100bc578063fdacd576146100ed575b600080fd5b6100a06004803603602081101561008357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661010a565b005b6100aa6101bd565b60408051918252519081900360200190f35b6100c46101c3565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b6100a06004803603602081101561010357600080fd5b50356101df565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760008190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101a057600080fd5b505af11580156101b4573d6000803e3d6000fd5b50505050505b50565b60015481565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314156101ba5760015556fea165627a7a7230582065546060ae2694c0faa5ae3a86876c1f4f60c32218234b846e28adec7370506c0029", "sourceMap": "34:480:0:-;;;123:50;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;150:5:0;:18;;-1:-1:-1;;;;;;150:18:0;158:10;150:18;;;34:480;;;;;;", "deployedSourceMap": "34:480:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;34:480:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;347:165;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;347:165:0;;;;:::i;:::-;;82:36;;;:::i;:::-;;;;;;;;;;;;;;;;58:20;;;:::i;:::-;;;;;;;;;;;;;;;;;;;240:103;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;240:103:0;;:::i;347:165::-;223:5;;;;209:10;:19;205:26;;;409:19;442:11;409:45;;460:8;:21;;;482:24;;460:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;460:47:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;460:47:0;;;;230:1;205:26;347:165;:::o;82:36::-;;;;:::o;58:20::-;;;;;;:::o;240:103::-;223:5;;;;209:10;:19;205:26;;;302:24;:36;240:103::o", "source": "pragma solidity >=0.4.21 <0.6.0;\n\ncontract Migrations {\n address public owner;\n uint public last_completed_migration;\n\n constructor() public {\n owner = msg.sender;\n }\n\n modifier restricted() {\n if (msg.sender == owner) _;\n }\n\n function setCompleted(uint completed) public restricted {\n last_completed_migration = completed;\n }\n\n function upgrade(address new_address) public restricted {\n Migrations upgraded = Migrations(new_address);\n upgraded.setCompleted(last_completed_migration);\n }\n}\n", "sourcePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol", "ast": { "absolutePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol", "exportedSymbols": { "Migrations": [ 56 ] }, "id": 57, "nodeType": "SourceUnit", "nodes": [ { "id": 1, "literals": [ "solidity", ">=", "0.4", ".21", "<", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "0:32:0" }, { "baseContracts": [], "contractDependencies": [], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 56, "linearizedBaseContracts": [ 56 ], "name": "Migrations", "nodeType": "ContractDefinition", "nodes": [ { "constant": false, "id": 3, "name": "owner", "nodeType": "VariableDeclaration", "scope": 56, "src": "58:20:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 2, "name": "address", "nodeType": "ElementaryTypeName", "src": "58:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "public" }, { "constant": false, "id": 5, "name": "last_completed_migration", "nodeType": "VariableDeclaration", "scope": 56, "src": "82:36:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4, "name": "uint", "nodeType": "ElementaryTypeName", "src": "82:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "body": { "id": 13, "nodeType": "Block", "src": "144:29:0", "statements": [ { "expression": { "argumentTypes": null, "id": 11, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 8, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "150:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 9, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "158:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 10, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "158:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "150:18:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "id": 12, "nodeType": "ExpressionStatement", "src": "150:18:0" } ] }, "documentation": null, "id": 14, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "parameters": { "id": 6, "nodeType": "ParameterList", "parameters": [], "src": "134:2:0" }, "returnParameters": { "id": 7, "nodeType": "ParameterList", "parameters": [], "src": "144:0:0" }, "scope": 56, "src": "123:50:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 22, "nodeType": "Block", "src": "199:37:0", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address", "typeString": "address" }, "id": 19, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 16, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "209:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 17, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "209:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "id": 18, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "223:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "src": "209:19:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 21, "nodeType": "IfStatement", "src": "205:26:0", "trueBody": { "id": 20, "nodeType": "PlaceholderStatement", "src": "230:1:0" } } ] }, "documentation": null, "id": 23, "name": "restricted", "nodeType": "ModifierDefinition", "parameters": { "id": 15, "nodeType": "ParameterList", "parameters": [], "src": "196:2:0" }, "src": "177:59:0", "visibility": "internal" }, { "body": { "id": 34, "nodeType": "Block", "src": "296:47:0", "statements": [ { "expression": { "argumentTypes": null, "id": 32, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 30, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "302:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 31, "name": "completed", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 25, "src": "329:9:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "302:36:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 33, "nodeType": "ExpressionStatement", "src": "302:36:0" } ] }, "documentation": null, "id": 35, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 28, "modifierName": { "argumentTypes": null, "id": 27, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "285:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "285:10:0" } ], "name": "setCompleted", "nodeType": "FunctionDefinition", "parameters": { "id": 26, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 25, "name": "completed", "nodeType": "VariableDeclaration", "scope": 35, "src": "262:14:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 24, "name": "uint", "nodeType": "ElementaryTypeName", "src": "262:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "261:16:0" }, "returnParameters": { "id": 29, "nodeType": "ParameterList", "parameters": [], "src": "296:0:0" }, "scope": 56, "src": "240:103:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 54, "nodeType": "Block", "src": "403:109:0", "statements": [ { "assignments": [ 43 ], "declarations": [ { "constant": false, "id": 43, "name": "upgraded", "nodeType": "VariableDeclaration", "scope": 54, "src": "409:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" }, "typeName": { "contractScope": null, "id": 42, "name": "Migrations", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 56, "src": "409:10:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "value": null, "visibility": "internal" } ], "id": 47, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 45, "name": "new_address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 37, "src": "442:11:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 44, "name": "Migrations", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 56, "src": "431:10:0", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_Migrations_$56_$", "typeString": "type(contract Migrations)" } }, "id": 46, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "431:23:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "nodeType": "VariableDeclarationStatement", "src": "409:45:0" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 51, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "482:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 48, "name": "upgraded", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 43, "src": "460:8:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "id": 50, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "setCompleted", "nodeType": "MemberAccess", "referencedDeclaration": 35, "src": "460:21:0", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256) external" } }, "id": 52, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "460:47:0", "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 53, "nodeType": "ExpressionStatement", "src": "460:47:0" } ] }, "documentation": null, "id": 55, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 40, "modifierName": { "argumentTypes": null, "id": 39, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "392:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "392:10:0" } ], "name": "upgrade", "nodeType": "FunctionDefinition", "parameters": { "id": 38, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 37, "name": "new_address", "nodeType": "VariableDeclaration", "scope": 55, "src": "364:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 36, "name": "address", "nodeType": "ElementaryTypeName", "src": "364:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "363:21:0" }, "returnParameters": { "id": 41, "nodeType": "ParameterList", "parameters": [], "src": "403:0:0" }, "scope": 56, "src": "347:165:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" } ], "scope": 57, "src": "34:480:0" } ], "src": "0:515:0" }, "legacyAST": { "absolutePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/Migrations.sol", "exportedSymbols": { "Migrations": [ 56 ] }, "id": 57, "nodeType": "SourceUnit", "nodes": [ { "id": 1, "literals": [ "solidity", ">=", "0.4", ".21", "<", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "0:32:0" }, { "baseContracts": [], "contractDependencies": [], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 56, "linearizedBaseContracts": [ 56 ], "name": "Migrations", "nodeType": "ContractDefinition", "nodes": [ { "constant": false, "id": 3, "name": "owner", "nodeType": "VariableDeclaration", "scope": 56, "src": "58:20:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 2, "name": "address", "nodeType": "ElementaryTypeName", "src": "58:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "public" }, { "constant": false, "id": 5, "name": "last_completed_migration", "nodeType": "VariableDeclaration", "scope": 56, "src": "82:36:0", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4, "name": "uint", "nodeType": "ElementaryTypeName", "src": "82:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "body": { "id": 13, "nodeType": "Block", "src": "144:29:0", "statements": [ { "expression": { "argumentTypes": null, "id": 11, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 8, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "150:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 9, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "158:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 10, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "158:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "src": "150:18:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "id": 12, "nodeType": "ExpressionStatement", "src": "150:18:0" } ] }, "documentation": null, "id": 14, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "parameters": { "id": 6, "nodeType": "ParameterList", "parameters": [], "src": "134:2:0" }, "returnParameters": { "id": 7, "nodeType": "ParameterList", "parameters": [], "src": "144:0:0" }, "scope": 56, "src": "123:50:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 22, "nodeType": "Block", "src": "199:37:0", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_address", "typeString": "address" }, "id": 19, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 16, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 71, "src": "209:3:0", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 17, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "209:10:0", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "id": 18, "name": "owner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3, "src": "223:5:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "src": "209:19:0", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 21, "nodeType": "IfStatement", "src": "205:26:0", "trueBody": { "id": 20, "nodeType": "PlaceholderStatement", "src": "230:1:0" } } ] }, "documentation": null, "id": 23, "name": "restricted", "nodeType": "ModifierDefinition", "parameters": { "id": 15, "nodeType": "ParameterList", "parameters": [], "src": "196:2:0" }, "src": "177:59:0", "visibility": "internal" }, { "body": { "id": 34, "nodeType": "Block", "src": "296:47:0", "statements": [ { "expression": { "argumentTypes": null, "id": 32, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 30, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "302:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 31, "name": "completed", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 25, "src": "329:9:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "302:36:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 33, "nodeType": "ExpressionStatement", "src": "302:36:0" } ] }, "documentation": null, "id": 35, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 28, "modifierName": { "argumentTypes": null, "id": 27, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "285:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "285:10:0" } ], "name": "setCompleted", "nodeType": "FunctionDefinition", "parameters": { "id": 26, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 25, "name": "completed", "nodeType": "VariableDeclaration", "scope": 35, "src": "262:14:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 24, "name": "uint", "nodeType": "ElementaryTypeName", "src": "262:4:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "261:16:0" }, "returnParameters": { "id": 29, "nodeType": "ParameterList", "parameters": [], "src": "296:0:0" }, "scope": 56, "src": "240:103:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" }, { "body": { "id": 54, "nodeType": "Block", "src": "403:109:0", "statements": [ { "assignments": [ 43 ], "declarations": [ { "constant": false, "id": 43, "name": "upgraded", "nodeType": "VariableDeclaration", "scope": 54, "src": "409:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" }, "typeName": { "contractScope": null, "id": 42, "name": "Migrations", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 56, "src": "409:10:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "value": null, "visibility": "internal" } ], "id": 47, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 45, "name": "new_address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 37, "src": "442:11:0", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 44, "name": "Migrations", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 56, "src": "431:10:0", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_Migrations_$56_$", "typeString": "type(contract Migrations)" } }, "id": 46, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "431:23:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "nodeType": "VariableDeclarationStatement", "src": "409:45:0" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 51, "name": "last_completed_migration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5, "src": "482:24:0", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 48, "name": "upgraded", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 43, "src": "460:8:0", "typeDescriptions": { "typeIdentifier": "t_contract$_Migrations_$56", "typeString": "contract Migrations" } }, "id": 50, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "setCompleted", "nodeType": "MemberAccess", "referencedDeclaration": 35, "src": "460:21:0", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256) external" } }, "id": 52, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "460:47:0", "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 53, "nodeType": "ExpressionStatement", "src": "460:47:0" } ] }, "documentation": null, "id": 55, "implemented": true, "kind": "function", "modifiers": [ { "arguments": null, "id": 40, "modifierName": { "argumentTypes": null, "id": 39, "name": "restricted", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 23, "src": "392:10:0", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "392:10:0" } ], "name": "upgrade", "nodeType": "FunctionDefinition", "parameters": { "id": 38, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 37, "name": "new_address", "nodeType": "VariableDeclaration", "scope": 55, "src": "364:19:0", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 36, "name": "address", "nodeType": "ElementaryTypeName", "src": "364:7:0", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "363:21:0" }, "returnParameters": { "id": 41, "nodeType": "ParameterList", "parameters": [], "src": "403:0:0" }, "scope": 56, "src": "347:165:0", "stateMutability": "nonpayable", "superFunction": null, "visibility": "public" } ], "scope": 57, "src": "34:480:0" } ], "src": "0:515:0" }, "compiler": { "name": "solc", "version": "0.5.2+commit.1df8f40c.Emscripten.clang" }, "networks": { "1583918911727": { "events": {}, "links": {}, "address": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550", "transactionHash": "0xc59b0daeb6b17beb9ea57bb0586fd626fb7b6f092d20bf22406168f7381ac949" } }, "schemaVersion": "3.0.19", "updatedAt": "2020-03-11T13:47:51.887Z", "networkType": "ethereum", "devdoc": { "methods": {} }, "userdoc": { "methods": {} } } ================================================ FILE: packages/fetchai/contracts/staking_erc20/build/staking_erc20.json ================================================ { "contractName": "Staking", "abi": [ { "inputs": [ { "internalType": "address", "name": "ERC20Address", "type": "address" }, { "internalType": "uint256", "name": "interestRatePerBlock", "type": "uint256" }, { "internalType": "uint256", "name": "pausedSinceBlock", "type": "uint256" }, { "internalType": "uint64", "name": "lockPeriodInBlocks", "type": "uint64" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "stakerAddress", "type": "address" }, { "indexed": true, "internalType": "uint256", "name": "sinceInterestRateIndex", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "principal", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "name": "BindStake", "type": "event" }, { "anonymous": false, "inputs": [], "name": "DeleteContract", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "address", "name": "targetAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "ExcessTokenWithdrawal", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "stakerAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "LiquidityDeposited", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "stakerAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "principal", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "name": "LiquidityUnlocked", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint64", "name": "numOfBlocks", "type": "uint64" } ], "name": "LockPeriod", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "rate", "type": "uint256" } ], "name": "NewInterestRate", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "sinceBlock", "type": "uint256" } ], "name": "Pause", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "address", "name": "sender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "RewardsPoolTokenTopUp", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "address", "name": "targetAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "RewardsPoolTokenWithdrawal", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "previousAdminRole", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "newAdminRole", "type": "bytes32" } ], "name": "RoleAdminChanged", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } ], "name": "RoleGranted", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" } ], "name": "RoleRevoked", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "stakerAddress", "type": "address" }, { "indexed": true, "internalType": "uint256", "name": "sinceInterestRateIndex", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "principal", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "name": "StakeCompoundInterest", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "address", "name": "targetAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "TokenWithdrawal", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "stakerAddress", "type": "address" }, { "indexed": true, "internalType": "uint256", "name": "liquidSinceBlock", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "principal", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "name": "UnbindStake", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "stakerAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "principal", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "name": "Withdraw", "type": "event" }, { "inputs": [], "name": "DEFAULT_ADMIN_ROLE", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "DELEGATE_ROLE", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "DELETE_PROTECTION_PERIOD", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_accruedGlobalLiquidity", "outputs": [ { "internalType": "uint256", "name": "principal", "type": "uint256" }, { "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_accruedGlobalLocked", "outputs": [ { "internalType": "uint256", "name": "principal", "type": "uint256" }, { "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_accruedGlobalPrincipal", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_earliestDelete", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "name": "_interestRates", "outputs": [ { "internalType": "uint256", "name": "sinceBlock", "type": "uint256" }, { "internalType": "uint256", "name": "rate", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_interestRatesNextIdx", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_interestRatesStartIdx", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "", "type": "address" } ], "name": "_liquidity", "outputs": [ { "internalType": "uint256", "name": "principal", "type": "uint256" }, { "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_lockPeriodInBlocks", "outputs": [ { "internalType": "uint64", "name": "", "type": "uint64" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_pausedSinceBlock", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_rewardsPoolBalance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_token", "outputs": [ { "internalType": "contract IERC20", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" } ], "name": "getRoleAdmin", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "uint256", "name": "index", "type": "uint256" } ], "name": "getRoleMember", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" } ], "name": "getRoleMemberCount", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "grantRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "hasRole", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "renounceRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" } ], "name": "revokeRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "rate", "type": "uint256" }, { "internalType": "uint256", "name": "expirationBlock", "type": "uint256" } ], "name": "addInterestRate", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "deposit", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdraw", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdrawPrincipal", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdrawCompoundInterest", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdrawWholeLiquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "bindStake", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "unbindStake", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "getRewardsPoolBalance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getEarliestDeleteBlock", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "forAddress", "type": "address" } ], "name": "getNumberOfLockedAssetsForUser", "outputs": [ { "internalType": "uint256", "name": "length", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "forAddress", "type": "address" } ], "name": "getLockedAssetsAggregateForUser", "outputs": [ { "internalType": "uint256", "name": "principal", "type": "uint256" }, { "internalType": "uint256", "name": "compoundInterest", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "forAddress", "type": "address" } ], "name": "getLockedAssetsForUser", "outputs": [ { "internalType": "uint256[]", "name": "principal", "type": "uint256[]" }, { "internalType": "uint256[]", "name": "compoundInterest", "type": "uint256[]" }, { "internalType": "uint256[]", "name": "liquidSinceBlock", "type": "uint256[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "forAddress", "type": "address" } ], "name": "getStakeForUser", "outputs": [ { "internalType": "uint256", "name": "principal", "type": "uint256" }, { "internalType": "uint256", "name": "compoundInterest", "type": "uint256" }, { "internalType": "uint256", "name": "sinceBlock", "type": "uint256" }, { "internalType": "uint256", "name": "sinceInterestRateIndex", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "topUpRewardsPool", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint64", "name": "numOfBlocks", "type": "uint64" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "updateLockPeriod", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "pauseSince", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "address payable", "name": "targetAddress", "type": "address" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdrawFromRewardsPool", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address payable", "name": "targetAddress", "type": "address" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "withdrawExcessTokens", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address payable", "name": "payoutAddress", "type": "address" }, { "internalType": "uint256", "name": "txExpirationBlock", "type": "uint256" } ], "name": "deleteContract", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ], "metadata": "{\"compiler\":{\"version\":\"0.6.8+commit.0bbfe453\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ERC20Address\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"interestRatePerBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"pausedSinceBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"lockPeriodInBlocks\",\"type\":\"uint64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"stakerAddress\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"sinceInterestRateIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"name\":\"BindStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"DeleteContract\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ExcessTokenWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"stakerAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"LiquidityDeposited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"stakerAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"name\":\"LiquidityUnlocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"numOfBlocks\",\"type\":\"uint64\"}],\"name\":\"LockPeriod\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rate\",\"type\":\"uint256\"}],\"name\":\"NewInterestRate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"sinceBlock\",\"type\":\"uint256\"}],\"name\":\"Pause\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"RewardsPoolTokenTopUp\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"RewardsPoolTokenWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"stakerAddress\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"sinceInterestRateIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"name\":\"StakeCompoundInterest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TokenWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"stakerAddress\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"liquidSinceBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"name\":\"UnbindStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"stakerAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DELEGATE_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DELETE_PROTECTION_PERIOD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_accruedGlobalLiquidity\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_accruedGlobalLocked\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_accruedGlobalPrincipal\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_earliestDelete\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"_interestRates\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"sinceBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"rate\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_interestRatesNextIdx\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_interestRatesStartIdx\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"_liquidity\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_lockPeriodInBlocks\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_pausedSinceBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_rewardsPoolBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_token\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"rate\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expirationBlock\",\"type\":\"uint256\"}],\"name\":\"addInterestRate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"bindStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"payoutAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"deleteContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getEarliestDeleteBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forAddress\",\"type\":\"address\"}],\"name\":\"getLockedAssetsAggregateForUser\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forAddress\",\"type\":\"address\"}],\"name\":\"getLockedAssetsForUser\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"principal\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"compoundInterest\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"liquidSinceBlock\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forAddress\",\"type\":\"address\"}],\"name\":\"getNumberOfLockedAssetsForUser\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRewardsPoolBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forAddress\",\"type\":\"address\"}],\"name\":\"getStakeForUser\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"principal\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"compoundInterest\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sinceBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sinceInterestRateIndex\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"pauseSince\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"topUpRewardsPool\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"unbindStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"numOfBlocks\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"updateLockPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdrawCompoundInterest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdrawExcessTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address payable\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdrawFromRewardsPool\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdrawPrincipal\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"txExpirationBlock\",\"type\":\"uint256\"}],\"name\":\"withdrawWholeLiquidity\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{\"addInterestRate(uint256,uint256)\":{\"details\":\"expiration period\",\"params\":{\"expirationBlock\":\"- block number beyond which is the carrier Tx considered expired, and so rejected. This is for protection of Tx sender to exactly define lifecycle length of the Tx, and so avoiding uncertainty of how long Tx sender needs to wait for Tx processing. Tx can be withheld\",\"rate\":\"- signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18\"}},\"constructor\":{\"params\":{\"ERC20Address\":\"address of the ERC20 contract\"}},\"deleteContract(address,uint256)\":{\"details\":\"owner only + only on or after `_earliestDelete` block\",\"params\":{\"payoutAddress\":\"address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\"}},\"getLockedAssetsForUser(address)\":{\"details\":\"Returns locked assets decomposed in to 3 separate arrays (principal, compound interest, liquid since block) NOTE(pb): This method might be quite expensive, depending on size of locked assets\"},\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. * To change a role's admin, use {_setRoleAdmin}.\"},\"getRoleMember(bytes32,uint256)\":{\"details\":\"Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. * Role bearers are not sorted in any particular way, and their ordering may change at any point. * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information.\"},\"getRoleMemberCount(bytes32)\":{\"details\":\"Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. * If `account` had not been already granted `role`, emits a {RoleGranted} event. * Requirements: * - the caller must have ``role``'s admin role.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"pauseSince(uint256,uint256)\":{\"details\":\"Delegate only\",\"params\":{\"blockNumber\":\"block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)\"}},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. * Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). * If the calling account had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must be `account`.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. * If `account` had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must have ``role``'s admin role.\"},\"topUpRewardsPool(uint256,uint256)\":{\"details\":\"Even though this is considered as administrative action (is not affected by by contract paused state, it can be executed by anyone who wishes to top-up the rewards pool (funds are sent in to contract, *not* the other way around). The Rewards Pool is exclusively dedicated to cover withdrawals of user' compound interest, which is effectively the reward.\"},\"unbindStake(uint256,uint256)\":{\"details\":\"public access\",\"params\":{\"amount\":\"- value to un-bind from the stake If `amount=0` then the **WHOLE** stake (including compound interest) will be unbound.\"}},\"updateLockPeriod(uint64,uint256)\":{\"details\":\"Delegate only\",\"params\":{\"numOfBlocks\":\"length of the lock period\"}},\"withdraw(uint256,uint256)\":{\"details\":\"public access\",\"params\":{\"amount\":\"- value to withdraw\"}},\"withdrawCompoundInterest(uint256)\":{\"details\":\"public access\"},\"withdrawExcessTokens(address,uint256)\":{\"details\":\"Withdraw \\\"excess\\\" tokens, which were sent to contract directly via direct ERC20.transfer(...), without interacting with API of this (Staking) contract, what could be done only by mistake. Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such \\\"excess\\\" tokens out of contract.\",\"params\":{\"targetAddress\":\": address to send tokens to\",\"txExpirationBlock\":\": block number until which is the transaction valid (inclusive). When transaction is processed after this block, it fails.\"}},\"withdrawFromRewardsPool(uint256,address,uint256)\":{\"details\":\"Withdraw tokens from rewards pool.\",\"params\":{\"amount\":\": amount to withdraw. If `amount == 0` then whole amount in rewards pool will be withdrawn.\",\"targetAddress\":\": address to send tokens to\"}},\"withdrawPrincipal(uint256)\":{\"details\":\"public access\"},\"withdrawWholeLiquidity(uint256)\":{\"details\":\"public access\"}}},\"userdoc\":{\"methods\":{\"addInterestRate(uint256,uint256)\":{\"notice\":\"Add new interest rate in to the ordered container of previously added interest rates\"},\"deleteContract(address,uint256)\":{\"notice\":\"Delete the contract, transfers the remaining token and ether balance to the specified payoutAddress\"},\"pauseSince(uint256,uint256)\":{\"notice\":\"Pauses all NON-administrative interaction with the contract since the specidfed block number \"},\"unbindStake(uint256,uint256)\":{\"notice\":\"Unbinds amount from the stake of sender of the transaction, and *LOCKS* it for number of blocks defined by value of the `_lockPeriodInBlocks` state of this contract at the point of this call. The locked amount can *NOT* be withdrawn from the contract *BEFORE* the lock period ends. * Unbinding (=calling this method) also means, that compound interest will be calculated for period since la.\"},\"updateLockPeriod(uint64,uint256)\":{\"notice\":\"Updates Lock Period value\"},\"withdraw(uint256,uint256)\":{\"notice\":\"Withdraws amount from sender' available liquidity pool back to sender address, preferring withdrawal from compound interest dimension of liquidity.\"},\"withdrawCompoundInterest(uint256)\":{\"notice\":\"Withdraws *WHOLE* compound interest amount available to sender.\"},\"withdrawPrincipal(uint256)\":{\"notice\":\"Withdraws *WHOLE* compound interest amount available to sender.\"},\"withdrawWholeLiquidity(uint256)\":{\"notice\":\"Withdraws whole liquidity available to sender back to sender' address,\"}}}},\"settings\":{\"compilationTarget\":{\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Staking.sol\":\"Staking\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/abdk-libraries/ABDKMath64x64.sol\":{\"keccak256\":\"0x67609bc0923563d05d3a8a7c681056f9702a92120777cb0bcbb40d0afbb4a015\",\"license\":\"BSD-4-Clause\",\"urls\":[\"bzz-raw://55e817969394d4e0201a8cd3763ad6776bc9fddc986febe9b4acf120e8b7ad0d\",\"dweb:/ipfs/QmNMve5ZjUfA8DJYic4sYTrDEAN6VyxcC7jWUTX25Wmnnp\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/AssetLib.sol\":{\"keccak256\":\"0x627891fef396f63fae2a38078dd28861689cac7e2d50286eaddf6b9a1109376a\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://e187942e72c2ed510626977753274ead2201b9a6655bbebac608964837d91d49\",\"dweb:/ipfs/QmTiUziuoAVEo2twWu9qydMKuwKUHW4WojQxweJGnUTDgM\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Finance.sol\":{\"keccak256\":\"0x1295bbe45c79971d2f7228e9ab5ce2b6563aec12cff2539704a2a5f69433225f\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://85de9e0ddb40020077f653f294cf9cbcffeb276fa82702e210805396c08c2698\",\"dweb:/ipfs/QmeqoWfLk21PN5ebnLbp58GifGsrheksRPYqKFmLyd1P97\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Staking.sol\":{\"keccak256\":\"0x53912db09c77d22c3a3880515ac7e9effb7d820598f27bddb970a10a8c95c92c\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://ddf0503ac09a8af192e946efb3248600e3cf71e6cd0c5ad68842380c332eaf60\",\"dweb:/ipfs/QmbbJ18e8MRoU1C3L59YnVHCoKfd1mgeCoYjQArmF25JQA\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/GSN/Context.sol\":{\"keccak256\":\"0xdb26cbf4d028490f49831a7865c2fe1b28db44b535ca8d343785a3b768aae183\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://840b14ce0315c47d49ba328f1f9fa7654ded1c9e1559e6c5e777a7b2dc28bf0a\",\"dweb:/ipfs/QmTLLabn4wcfGro9LEmUXUN2nwKqZSotXMvjDCLXEnLtZP\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/access/AccessControl.sol\":{\"keccak256\":\"0x92f7900d382761c7faefeaced81c6b4f1aae909ed0551803bfe8f27101956360\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://407c0864143968542e5cf5aa7556916d2cf292b201e3dadb65662e9a3aa24187\",\"dweb:/ipfs/QmSnXzYAUaGLGr7uofRbgQraTJvatbjQLBPhyYiMd18oUJ\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5\",\"dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017\",\"dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/utils/Address.sol\":{\"keccak256\":\"0xdfb4f812600ba4ce6738c35584ceb8c9433472583051b48ba5b1f66cb758a498\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://df02dffe1c1de089d9b4f6192f0dcf464526f2230f420b3deec4645e0cdd2bff\",\"dweb:/ipfs/QmcqXGAU3KJqwrgUVoGJ2W8osomhSJ4R5kdsRpbuW3fELS\"]},\"/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/utils/EnumerableSet.sol\":{\"keccak256\":\"0xb2a11b236f073662f5a196995863f51c11d006bf7c3de158b316dfa1506c4b79\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8651649cf0b9efa18c3b01c030276fa320d41adbdc286833417e7f36e357b2f3\",\"dweb:/ipfs/QmafhM2Nd1aP43QVB1eRRZaqRXQKswNfQcWi8U8xjrxCfN\"]}},\"version\":1}", "bytecode": "0x60806040523480156200001157600080fd5b506040516200391838038062003918833981810160405260808110156200003757600080fd5b5080516020820151604083015160609093015191929091620000646000336001600160e01b03620000ee16565b600180546001600160a01b0319166001600160a01b038616179055620000a56205a66d6200009162000107565b6200010b60201b620023d81790919060201c565b600255620000bc816001600160e01b036200016f16565b620000d0836001600160e01b03620001c316565b620000e4826001600160e01b036200025f16565b50505050620003d8565b6200010382826001600160e01b03620002c216565b5050565b4390565b60008282018381101562000166576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b600480546001600160401b0383166001600160401b0319909116811790915560408051918252517f6b24ea7c7a8ce7fd2e6a6cb675dbabcd38a90f83ddd581c4435610ce90456e8f9181900360200190a150565b600c546040805180820190915280620001e46001600160e01b036200010716565b815260209081018490526000838152600d8252604090208251815591810151600192830155600c5462000222929091620023d86200010b821b17901c565b600c5560408051838152905182917fbcd200f406c53d9438c6cdc2966a1e99c35f336588c95bc3639e539df21c2f53919081900360200190a25050565b6000620002746001600160e01b036200010716565b905080821062000285578162000287565b805b600381905560408051918252517f68b095021b1f40fe513109f513c66692f0b3219aee674a69f4efc57badb8201d9181900360200190a15050565b600082815260208181526040909120620002e79183906200243262000344821b17901c565b156200010357620003006001600160e01b036200036416565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600062000166836001600160a01b0384166001600160e01b036200036816565b3390565b60006200037f83836001600160e01b03620003c016565b620003b75750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915562000169565b50600062000169565b60009081526001919091016020526040902054151590565b61353080620003e86000396000f3fe608060405234801561001057600080fd5b506004361061025e5760003560e01c80639010d07c11610146578063c8984ab2116100c3578063d9d8e78311610087578063d9d8e783146107bf578063dca2aa5c146107c7578063e2bbb158146107e4578063ecd0c0c314610807578063f0895e521461080f578063f1209ef71461083c5761025e565b8063c8984ab214610749578063ca15c87314610751578063d547741f1461076e578063d8cd39991461079a578063d9b202c9146107b75761025e565b8063aa700ff21161010a578063aa700ff2146106ad578063adf55101146106ca578063ba92a4c5146106d2578063c0ba241b146106f5578063c30f75cf146106fd5761025e565b80639010d07c146105d457806391d14854146106135780639608df4b14610653578063a217fddf1461067f578063a8f1b4c4146106875761025e565b8063372646bb116101df57806353e052ac116101a357806353e052ac146103f35780635f208f341461041f578063658e28a41461052357806367ce50c01461054057806368bccfcf1461057f57806388cedd5e146105b15761025e565b8063372646bb1461037757806340e979031461037f5780634164b001146103a5578063441a3e70146103ad57806352885d8d146103d05761025e565b8063292911fb11610226578063292911fb146102ec5780632f2ff15d146102f457806332a1bd7014610320578063347908df1461032857806336568abe1461034b5761025e565b8063085313ec146102635780630c450f9d1461027d5780631338403914610285578063248a9ca3146102aa5780632514c50a146102c7575b600080fd5b61026b610844565b60408051918252519081900360200190f35b61026b61084b565b6102a86004803603604081101561029b57600080fd5b5080359060200135610851565b005b61026b600480360360208110156102c057600080fd5b503561091f565b6102cf610934565b6040805167ffffffffffffffff9092168252519081900360200190f35b61026b610944565b6102a86004803603604081101561030a57600080fd5b50803590602001356001600160a01b031661094a565b61026b6109b6565b6102a86004803603604081101561033e57600080fd5b50803590602001356109bc565b6102a86004803603604081101561036157600080fd5b50803590602001356001600160a01b0316610a85565b61026b610ae6565b61026b6004803603602081101561039557600080fd5b50356001600160a01b0316610aed565b61026b610b0b565b6102a8600480360360408110156103c357600080fd5b5080359060200135610b11565b6102a8600480360360408110156103e657600080fd5b5080359060200135610bf2565b6102a86004803603604081101561040957600080fd5b506001600160a01b03813516906020013561108b565b6104456004803603602081101561043557600080fd5b50356001600160a01b03166112ef565b60405180806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b8381101561048d578181015183820152602001610475565b50505050905001848103835286818151815260200191508051906020019060200280838360005b838110156104cc5781810151838201526020016104b4565b50505050905001848103825285818151815260200191508051906020019060200280838360005b8381101561050b5781810151838201526020016104f3565b50505050905001965050505050505060405180910390f35b6102a86004803603602081101561053957600080fd5b5035611482565b6105666004803603602081101561055657600080fd5b50356001600160a01b031661155b565b6040805192835260208301919091528051918290030190f35b6102a86004803603606081101561059557600080fd5b508035906001600160a01b036020820135169060400135611574565b6102a8600480360360408110156105c757600080fd5b5080359060200135611886565b6105f7600480360360408110156105ea57600080fd5b5080359060200135611a15565b604080516001600160a01b039092168252519081900360200190f35b61063f6004803603604081101561062957600080fd5b50803590602001356001600160a01b0316611a3c565b604080519115158252519081900360200190f35b6102a86004803603604081101561066957600080fd5b506001600160a01b038135169060200135611a5a565b61026b611c8e565b6105666004803603602081101561069d57600080fd5b50356001600160a01b0316611c93565b610566600480360360208110156106c357600080fd5b5035611cb6565b61026b611ccf565b6102a8600480360360408110156106e857600080fd5b5080359060200135611cd5565b61026b611e49565b6107236004803603602081101561071357600080fd5b50356001600160a01b0316611e6e565b604080519485526020850193909352838301919091526060830152519081900360800190f35b610566611e9f565b61026b6004803603602081101561076757600080fd5b5035611ea8565b6102a86004803603604081101561078457600080fd5b50803590602001356001600160a01b0316611ebf565b6102a8600480360360208110156107b057600080fd5b5035611f18565b610566611ff2565b61026b611ffb565b6102a8600480360360208110156107dd57600080fd5b5035612001565b6102a8600480360360408110156107fa57600080fd5b50803590602001356120f2565b6105f76122fa565b6102a86004803603604081101561082557600080fd5b5067ffffffffffffffff8135169060200135612309565b61026b6123d2565b6002545b90565b60055481565b610859612447565b8061088b5750604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902061088b9033611a3c565b6108c65760405162461bcd60e51b81526004018080602001828103825260248152602001806134186024913960400191505060405180910390fd5b80806108d0612458565b1115610911576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b61091a8361245c565b505050565b60009081526020819052604090206002015490565b60045467ffffffffffffffff1681565b60065481565b60008281526020819052604090206002015461096d906109686124e5565b611a3c565b6109a85760405162461bcd60e51b815260040180806020018281038252602f8152602001806133e9602f913960400191505060405180910390fd5b6109b282826124e9565b5050565b60025481565b80806109c6612458565b1115610a07576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b610a0f612447565b80610a415750604080516c44454c45474154455f524f4c4560981b8152905190819003600d019020610a419033611a3c565b610a7c5760405162461bcd60e51b81526004018080602001828103825260248152602001806134186024913960400191505060405180910390fd5b61091a83612558565b610a8d6124e5565b6001600160a01b0316816001600160a01b031614610adc5760405162461bcd60e51b815260040180806020018281038252602f8152602001806134cc602f913960400191505060405180910390fd5b6109b282826125ae565b6205a66d81565b6001600160a01b03166000908152600f602052604090206002015490565b600c5481565b8080610b1b612458565b1115610b5c576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b610b64612458565b60035411610ba7576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b336000610bb2612458565b90506000610bc0838361261d565b50915050610bcc61333e565b610bdc828863ffffffff61287b16565b9050610be98482896128f4565b50505050505050565b8080610bfc612458565b1115610c3d576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b610c45612458565b60035411610c88576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b6000610c92612458565b9050336000610ca18284612aa1565b90506000610cb182600201612c1a565b9050610cbb61333e565b8715610ebd5781881115610d16576040805162461bcd60e51b815260206004820152601b60248201527f416d6f756e7420697320686967686572207468616e207374616b650000000000604482015290519081900360640190fd5b60045467ffffffffffffffff16610dee576001600160a01b0384166000908152601060205260409020610d549060028501908a63ffffffff612c3416565b9050610d6760078263ffffffff612c5916565b80516020808301516040805193845291830152805187926001600160a01b0388169260008051602061348c83398151915292918290030190a38051602080830151604080519384529183015280516001600160a01b038716927fa219ed960eef85ed4cd072d3a9f240ea94727c0d6fb15e9a91a8cd4bf18ee3a692908290030190a2610eb8565b6001600160a01b0384166000908152600f6020908152604082206002810180546001810182559084529190922060045460039092020190610e4090889067ffffffffffffffff1663ffffffff6123d816565b8155610e5960028601600183018c63ffffffff612c3416565b9250610e6c60098463ffffffff612c5916565b610e7c828463ffffffff612c5916565b80548351602080860151604080519384529183015280516001600160a01b038a169260008051602061348c83398151915292908290030190a350505b611081565b81610ecc57505050505061091a565b5060408051808201909152600283018054825260038401805460208401526000918290555560045467ffffffffffffffff16610fc2576001600160a01b0384166000908152601060205260409020610f2a908263ffffffff612c5916565b610f3b60078263ffffffff612c5916565b80516020808301516040805193845291830152805187926001600160a01b0388169260008051602061348c83398151915292918290030190a38051602080830151604080519384529183015280516001600160a01b038716927fa219ed960eef85ed4cd072d3a9f240ea94727c0d6fb15e9a91a8cd4bf18ee3a692908290030190a2611081565b6001600160a01b0384166000908152600f602090815260408220600281018054600181018255908452919092206004546003909202019061101490889067ffffffffffffffff1663ffffffff6123d816565b8155825160018201556020830151600282015561103860098463ffffffff612c5916565b611048828463ffffffff612c5916565b8054600182015460028301546040805192835260208301919091528051339260008051602061348c83398151915292908290030190a350505b5050505050505050565b8080611095612458565b11156110d6576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6110de612447565b611128576040805162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1030b71037bbb732b960511b604482015290519081900360640190fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b15801561117357600080fd5b505afa158015611187573d6000803e3d6000fd5b505050506040513d602081101561119d57600080fd5b50516005546006549192506000916111ba9163ffffffff6123d816565b905060006111ce838363ffffffff612c9316565b6001546040805163a9059cbb60e01b81526001600160a01b038a8116600483015260248201859052915193945091169163a9059cbb916044808201926020929091908290030181600087803b15801561122657600080fd5b505af115801561123a573d6000803e3d6000fd5b505050506040513d602081101561125057600080fd5b50516112a3576040805162461bcd60e51b815260206004820181905260248201527f4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e604482015290519081900360640190fd5b604080516001600160a01b03881681526020810183905281517f7f66376a5ef2f39ab4ee2ee6e400606624e66929dba5d82df5b14dd0070a8a87929181900390910190a1505050505050565b6001600160a01b0381166000908152600f60205260409020600201805460609182918291908015611479578067ffffffffffffffff8111801561133157600080fd5b5060405190808252806020026020018201604052801561135b578160200160208202803683370190505b5094508067ffffffffffffffff8111801561137557600080fd5b5060405190808252806020026020018201604052801561139f578160200160208202803683370190505b5093508067ffffffffffffffff811180156113b957600080fd5b506040519080825280602002602001820160405280156113e3578160200160208202803683370190505b50925060005b818110156114775760008382815481106113ff57fe5b906000526020600020906003020190506000816001019050806000015488848151811061142857fe5b602002602001018181525050806001015487848151811061144557fe5b602002602001018181525050816000015486848151811061146257fe5b602090810291909101015250506001016113e9565b505b50509193909250565b808061148c612458565b11156114cd576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6114d5612458565b60035411611518576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b336000611523612458565b90506000611531838361261d565b5091505061153d61333e565b81548082526000835561155390859083906128f4565b505050505050565b6010602052600090815260409020805460019091015482565b808061157e612458565b11156115bf576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6115c7612447565b611611576040805162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1030b71037bbb732b960511b604482015290519081900360640190fd5b83611620576005549350611677565b600554841115611677576040805162461bcd60e51b815260206004820152601f60248201527f416d6f756e7420686967686572207468616e207265776172647320706f6f6c00604482015290519081900360640190fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b1580156116c257600080fd5b505afa1580156116d6573d6000803e3d6000fd5b505050506040513d60208110156116ec57600080fd5b5051600654909150600090611707908763ffffffff6123d816565b90508181111561175e576040805162461bcd60e51b815260206004820152601760248201527f436f6e747261637420696e636f6e73697374656e63792e000000000000000000604482015290519081900360640190fd5b6001546040805163a9059cbb60e01b81526001600160a01b038881166004830152602482018a90529151919092169163a9059cbb9160448083019260209291908290030181600087803b1580156117b457600080fd5b505af11580156117c8573d6000803e3d6000fd5b505050506040513d60208110156117de57600080fd5b5051611831576040805162461bcd60e51b815260206004820181905260248201527f4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e604482015290519081900360640190fd5b600580548790039055604080516001600160a01b03871681526020810188905281517f8a652d32cc9efc0a4f0fc3708c9fb81d5a924a7b9af2a37c46e9e4910d0dc325929181900390910190a1505050505050565b8080611890612458565b11156118d1576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6118d9612458565b6003541161191c576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b8261196e576040805162461bcd60e51b815260206004820152601f60248201527f416d6f756e74206d75737420626520686967686572207468616e207a65726f00604482015290519081900360640190fd5b6000611978612458565b90506000611986338361261d565b5091505060006119963384612aa1565b90506119a061333e565b6119b483600284018963ffffffff612cd516565b90506119c760078263ffffffff612ce716565b600182015481516020808401516040805193845291830152805133927f0fb741c5ad2cad8215be7b4cab4e3f202f907d4540668ebf954f8e742efd89cf92908290030190a350505050505050565b6000828152602081905260408120611a33908363ffffffff612d1516565b90505b92915050565b6000828152602081905260408120611a33908363ffffffff612d2116565b8080611a64612458565b1115611aa5576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b611aad612447565b611af7576040805162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1030b71037bbb732b960511b604482015290519081900360640190fd5b611aff612458565b6002541015611b55576040805162461bcd60e51b815260206004820152601b60248201527f4561726c696573742064656c657465206e6f7420726561636865640000000000604482015290519081900360640190fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015611ba057600080fd5b505afa158015611bb4573d6000803e3d6000fd5b505050506040513d6020811015611bca57600080fd5b50516001546040805163a9059cbb60e01b81526001600160a01b03888116600483015260248201859052915193945091169163a9059cbb916044808201926020929091908290030181600087803b158015611c2457600080fd5b505af1158015611c38573d6000803e3d6000fd5b505050506040513d6020811015611c4e57600080fd5b5051611c5957600080fd5b6040517fe4dedc7f698868c432fb679313d1381b06601dfb7174d9b18b2e35378b67633390600090a1836001600160a01b0316ff5b600081565b6001600160a01b03166000908152600f6020526040902080546001909101549091565b600d602052600090815260409020805460019091015482565b60055490565b8080611cdf612458565b1115611d20576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b82611d2a5761091a565b600154604080516323b872dd60e01b81523360048201523060248201526044810186905290516001600160a01b03909216916323b872dd916064808201926020929091908290030181600087803b158015611d8457600080fd5b505af1158015611d98573d6000803e3d6000fd5b505050506040513d6020811015611dae57600080fd5b5051611df3576040805162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b604482015290519081900360640190fd5b600554611e06908463ffffffff6123d816565b600555604080513381526020810185905281517fb562e06b275d420e5848bb9083041a1c68466a478df92491b473ce99a4c68f68929181900390910190a1505050565b604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902081565b6001600160a01b03166000908152600e60205260409020600281015460038201548254600190930154919390929190565b60075460085482565b6000818152602081905260408120611a3690612d36565b600082815260208190526040902060020154611edd906109686124e5565b610adc5760405162461bcd60e51b815260040180806020018281038252603081526020018061343c6030913960400191505060405180910390fd5b8080611f22612458565b1115611f63576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b611f6b612458565b60035411611fae576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b336000611fb9612458565b90506000611fc7838361261d565b50915050611fd361333e565b60018201805460208301819052600090915561155390859083906128f4565b600954600a5482565b600b5481565b808061200b612458565b111561204c576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b612054612458565b60035411612097576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b3360006120a2612458565b905060006120b0838361261d565b506040805180820190915281548152600182015460208201529092506120e1915084906120dc84612c1a565b6128f4565b600060018201819055905550505050565b80806120fc612458565b111561213d576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b612145612458565b60035411612188576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b82158015906122bd57600154604080516323b872dd60e01b81523360048201523060248201526044810187905290516001600160a01b03909216916323b872dd916064808201926020929091908290030181600087803b1580156121eb57600080fd5b505af11580156121ff573d6000803e3d6000fd5b505050506040513d602081101561221557600080fd5b505161225a576040805162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b604482015290519081900360640190fd5b60065461226d908563ffffffff6123d816565b600655600754612283908563ffffffff6123d816565b60075560408051858152905133917f7ff07ce9a287649537e4b012e45cf012d90228b12e2b56bb03515a6b5436fcdf919081900360200190a25b60006122c7612458565b905060006122d5338361261d565b5091505082156115535780546122f1908763ffffffff6123d816565b90555050505050565b6001546001600160a01b031681565b8080612313612458565b1115612354576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b61235c612447565b8061238e5750604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902061238e9033611a3c565b6123c95760405162461bcd60e51b81526004018080602001828103825260248152602001806134186024913960400191505060405180910390fd5b61091a83612d41565b60035481565b600082820183811015611a33576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6000611a33836001600160a01b038416612d97565b60006124538133611a3c565b905090565b4390565b600c546040805180820190915280612472612458565b815260209081018490526000838152600d82526040902082518155910151600191820155600c546124a89163ffffffff6123d816565b600c5560408051838152905182917fbcd200f406c53d9438c6cdc2966a1e99c35f336588c95bc3639e539df21c2f53919081900360200190a25050565b3390565b6000828152602081905260409020612507908263ffffffff61243216565b156109b2576125146124e5565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000612562612458565b90508082106125715781612573565b805b600381905560408051918252517f68b095021b1f40fe513109f513c66692f0b3219aee674a69f4efc57badb8201d9181900360200190a15050565b60008281526020819052604090206125cc908263ffffffff612de116565b156109b2576125d96124e5565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b61262561333e565b6001600160a01b0383166000908152600f6020908152604080832060109092528220919060028101825b815481101561278c57612660613358565b82828154811061266c57fe5b600091825260209182902060408051808201825260039093029091018054835281518083019092526001810154825260020154818401529181019190915280519091508810156126bf5750600101612787565b60208101515187516126d69163ffffffff6123d816565b8752602080820151810151908801516126f49163ffffffff6123d816565b60208801528254600019018281146127555783818154811061271257fe5b906000526020600020906003020184848154811061272c57fe5b600091825260209091208254600390920201908155600180830154908201556002918201549101555b8380548061275f57fe5b6000828152602081206003600019909301928302018181556001810182905560020155905550505b61264f565b5080546127c6576001600160a01b0387166000908152600f6020526040812081815560018101829055906127c36002830182613377565b50505b84511515806127d85750602085015115155b92508215612872578451602080870151604080519384529183015280516001600160a01b038a16927fa219ed960eef85ed4cd072d3a9f240ea94727c0d6fb15e9a91a8cd4bf18ee3a692908290030190a261283a60098663ffffffff612ce716565b80541561285157612851828663ffffffff612ce716565b61286260078663ffffffff612c5916565b612872848663ffffffff612c5916565b50509250925092565b61288361333e565b818360010154106128b45760018301546128a3908363ffffffff612c9316565b600184015560208101829052611a36565b60018301546128ca90839063ffffffff612c9316565b80825283546128de9163ffffffff612c9316565b8355600190920180546020840152600090555090565b801561091a5781602001516005541015612955576040805162461bcd60e51b815260206004820181905260248201527f4e6f7420656e6f7567682066756e647320696e207265776172647320706f6f6c604482015290519081900360640190fd5b6001546040805163a9059cbb60e01b81526001600160a01b038681166004830152602482018590529151919092169163a9059cbb9160448083019260209291908290030181600087803b1580156129ab57600080fd5b505af11580156129bf573d6000803e3d6000fd5b505050506040513d60208110156129d557600080fd5b5051612a1a576040805162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b604482015290519081900360640190fd5b6020820151600554612a319163ffffffff612c9316565b6005558151600654612a489163ffffffff612c9316565b600655612a5c60078363ffffffff612ce716565b81516020808401516040805193845291830152805133927ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b56892908290030190a2505050565b6001600160a01b0382166000908152600e6020526040812090612ac660028301612c1a565b90508015612ba557815460018301545b600c54811015612b87576000818152600d602052604090208054831015612b44576040805162461bcd60e51b815260206004820152601860248201527f73696e6365426c6f636b20696e636f6e73697374656e63790000000000000000604482015290519081900360640190fd5b600c5486906001840190811015612b67576000818152600d602052604090205491505b612b78868460010154878503612df6565b95509093505050600101612ad6565b506002830154612b9e90839063ffffffff612c9316565b6003840155505b828255600c54612bb6576000612bbd565b6001600c54035b600183018190556002830154600384015460408051928352602083019190915280516001600160a01b038816927f6b6b43cdaf8f2ae06bdc6f6be433bb374b703ce8c88db4adce9dd8aefe44fe6d92908290030190a35092915050565b60018101548154600091611a36919063ffffffff6123d816565b612c3c61333e565b612c46848361287b565b9050612c528382612c59565b9392505050565b80518254612c6c9163ffffffff6123d816565b825560208101516001830154612c879163ffffffff6123d816565b82600101819055505050565b6000611a3383836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612e36565b612cdd61333e565b612c468483612ecd565b80518254612cfa9163ffffffff612c9316565b825560208101516001830154612c879163ffffffff612c9316565b6000611a338383612f39565b6000611a33836001600160a01b038416612f9d565b6000611a3682612fb5565b6004805467ffffffffffffffff831667ffffffffffffffff19909116811790915560408051918252517f6b24ea7c7a8ce7fd2e6a6cb675dbabcd38a90f83ddd581c4435610ce90456e8f9181900360200190a150565b6000612da38383612f9d565b612dd957508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611a36565b506000611a36565b6000611a33836001600160a01b038416612fb9565b6000612e2e612e28612e22612e0b600161307f565b612e1d87670de0b6b3a764000061309d565b6130d4565b84613108565b85613156565b949350505050565b60008184841115612ec55760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612e8a578181015183820152602001612e72565b50505050905090810190601f168015612eb75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b612ed561333e565b82548211612ef9578254612eef908363ffffffff612c9316565b8355818152611a36565b8254612f0c90839063ffffffff612c9316565b602082018190526001840154612f279163ffffffff612c9316565b60018401558254815260009092555090565b81546000908210612f7b5760405162461bcd60e51b81526004018080602001828103825260228152602001806133c76022913960400191505060405180910390fd5b826000018281548110612f8a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b600081815260018301602052604081205480156130755783546000198083019190810190600090879083908110612fec57fe5b906000526020600020015490508087600001848154811061300957fe5b60009182526020808320909101929092558281526001898101909252604090209084019055865487908061303957fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050611a36565b6000915050611a36565b6000677fffffffffffffff82111561309657600080fd5b5060401b90565b6000816130a957600080fd5b60006130b584846131be565b905060016001607f1b036001600160801b0382161115611a3357600080fd5b6000600f83810b9083900b0160016001607f1b031981128015906130ff575060016001607f1b038113155b611a3357600080fd5b6000613114600161307f565b90505b8115611a3657600182161561313d576131308184613308565b9050600182039150613151565b6131478384613308565b9250600182901c91505b613117565b60008161316557506000611a36565b600083600f0b121561317657600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b038111156131a557600080fd5b60401b81198111156131b657600080fd5b019392505050565b6000816131ca57600080fd5b60006001600160c01b0384116131ef5782604085901b816131e757fe5b0490506132f4565b60c084811c6401000000008110613208576020918201911c5b62010000811061321a576010918201911c5b610100811061322b576008918201911c5b6010811061323b576004918201911c5b6004811061324b576002918201911c5b6002811061325a576001820191505b60bf820360018603901c6001018260ff0387901b8161327557fe5b0492506001600160801b0383111561328c57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b828110156132b8576001820391505b608084901b929003828110156132cf576001820391505b829003608084901c82146132df57fe5b8881816132e857fe5b04870196505050505050505b6001600160801b03811115611a3357600080fd5b6000600f83810b9083900b0260401d60016001607f1b031981128015906130ff575060016001607f1b03811315611a3357600080fd5b604051806040016040528060008152602001600081525090565b60405180604001604052806000815260200161337261333e565b905290565b5080546000825560030290600052602060002090810190613398919061339b565b50565b61084891905b808211156133c25760008082556001820181905560028201556003016133a1565b509056fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e7443616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b655472616e73616374696f6e2065787069726564000000000000000000000000005bfe1e16d75ca1fa001989652cd88c5b8ee8a59b5df6df99836337def4d1c82d436f6e747261637420686173206265656e207061757365640000000000000000416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a26469706673582212205436678265352631f897e2cef39869cf23c267c4a2f500784a8480a5ff10f77664736f6c63430006080033", "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061025e5760003560e01c80639010d07c11610146578063c8984ab2116100c3578063d9d8e78311610087578063d9d8e783146107bf578063dca2aa5c146107c7578063e2bbb158146107e4578063ecd0c0c314610807578063f0895e521461080f578063f1209ef71461083c5761025e565b8063c8984ab214610749578063ca15c87314610751578063d547741f1461076e578063d8cd39991461079a578063d9b202c9146107b75761025e565b8063aa700ff21161010a578063aa700ff2146106ad578063adf55101146106ca578063ba92a4c5146106d2578063c0ba241b146106f5578063c30f75cf146106fd5761025e565b80639010d07c146105d457806391d14854146106135780639608df4b14610653578063a217fddf1461067f578063a8f1b4c4146106875761025e565b8063372646bb116101df57806353e052ac116101a357806353e052ac146103f35780635f208f341461041f578063658e28a41461052357806367ce50c01461054057806368bccfcf1461057f57806388cedd5e146105b15761025e565b8063372646bb1461037757806340e979031461037f5780634164b001146103a5578063441a3e70146103ad57806352885d8d146103d05761025e565b8063292911fb11610226578063292911fb146102ec5780632f2ff15d146102f457806332a1bd7014610320578063347908df1461032857806336568abe1461034b5761025e565b8063085313ec146102635780630c450f9d1461027d5780631338403914610285578063248a9ca3146102aa5780632514c50a146102c7575b600080fd5b61026b610844565b60408051918252519081900360200190f35b61026b61084b565b6102a86004803603604081101561029b57600080fd5b5080359060200135610851565b005b61026b600480360360208110156102c057600080fd5b503561091f565b6102cf610934565b6040805167ffffffffffffffff9092168252519081900360200190f35b61026b610944565b6102a86004803603604081101561030a57600080fd5b50803590602001356001600160a01b031661094a565b61026b6109b6565b6102a86004803603604081101561033e57600080fd5b50803590602001356109bc565b6102a86004803603604081101561036157600080fd5b50803590602001356001600160a01b0316610a85565b61026b610ae6565b61026b6004803603602081101561039557600080fd5b50356001600160a01b0316610aed565b61026b610b0b565b6102a8600480360360408110156103c357600080fd5b5080359060200135610b11565b6102a8600480360360408110156103e657600080fd5b5080359060200135610bf2565b6102a86004803603604081101561040957600080fd5b506001600160a01b03813516906020013561108b565b6104456004803603602081101561043557600080fd5b50356001600160a01b03166112ef565b60405180806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b8381101561048d578181015183820152602001610475565b50505050905001848103835286818151815260200191508051906020019060200280838360005b838110156104cc5781810151838201526020016104b4565b50505050905001848103825285818151815260200191508051906020019060200280838360005b8381101561050b5781810151838201526020016104f3565b50505050905001965050505050505060405180910390f35b6102a86004803603602081101561053957600080fd5b5035611482565b6105666004803603602081101561055657600080fd5b50356001600160a01b031661155b565b6040805192835260208301919091528051918290030190f35b6102a86004803603606081101561059557600080fd5b508035906001600160a01b036020820135169060400135611574565b6102a8600480360360408110156105c757600080fd5b5080359060200135611886565b6105f7600480360360408110156105ea57600080fd5b5080359060200135611a15565b604080516001600160a01b039092168252519081900360200190f35b61063f6004803603604081101561062957600080fd5b50803590602001356001600160a01b0316611a3c565b604080519115158252519081900360200190f35b6102a86004803603604081101561066957600080fd5b506001600160a01b038135169060200135611a5a565b61026b611c8e565b6105666004803603602081101561069d57600080fd5b50356001600160a01b0316611c93565b610566600480360360208110156106c357600080fd5b5035611cb6565b61026b611ccf565b6102a8600480360360408110156106e857600080fd5b5080359060200135611cd5565b61026b611e49565b6107236004803603602081101561071357600080fd5b50356001600160a01b0316611e6e565b604080519485526020850193909352838301919091526060830152519081900360800190f35b610566611e9f565b61026b6004803603602081101561076757600080fd5b5035611ea8565b6102a86004803603604081101561078457600080fd5b50803590602001356001600160a01b0316611ebf565b6102a8600480360360208110156107b057600080fd5b5035611f18565b610566611ff2565b61026b611ffb565b6102a8600480360360208110156107dd57600080fd5b5035612001565b6102a8600480360360408110156107fa57600080fd5b50803590602001356120f2565b6105f76122fa565b6102a86004803603604081101561082557600080fd5b5067ffffffffffffffff8135169060200135612309565b61026b6123d2565b6002545b90565b60055481565b610859612447565b8061088b5750604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902061088b9033611a3c565b6108c65760405162461bcd60e51b81526004018080602001828103825260248152602001806134186024913960400191505060405180910390fd5b80806108d0612458565b1115610911576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b61091a8361245c565b505050565b60009081526020819052604090206002015490565b60045467ffffffffffffffff1681565b60065481565b60008281526020819052604090206002015461096d906109686124e5565b611a3c565b6109a85760405162461bcd60e51b815260040180806020018281038252602f8152602001806133e9602f913960400191505060405180910390fd5b6109b282826124e9565b5050565b60025481565b80806109c6612458565b1115610a07576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b610a0f612447565b80610a415750604080516c44454c45474154455f524f4c4560981b8152905190819003600d019020610a419033611a3c565b610a7c5760405162461bcd60e51b81526004018080602001828103825260248152602001806134186024913960400191505060405180910390fd5b61091a83612558565b610a8d6124e5565b6001600160a01b0316816001600160a01b031614610adc5760405162461bcd60e51b815260040180806020018281038252602f8152602001806134cc602f913960400191505060405180910390fd5b6109b282826125ae565b6205a66d81565b6001600160a01b03166000908152600f602052604090206002015490565b600c5481565b8080610b1b612458565b1115610b5c576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b610b64612458565b60035411610ba7576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b336000610bb2612458565b90506000610bc0838361261d565b50915050610bcc61333e565b610bdc828863ffffffff61287b16565b9050610be98482896128f4565b50505050505050565b8080610bfc612458565b1115610c3d576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b610c45612458565b60035411610c88576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b6000610c92612458565b9050336000610ca18284612aa1565b90506000610cb182600201612c1a565b9050610cbb61333e565b8715610ebd5781881115610d16576040805162461bcd60e51b815260206004820152601b60248201527f416d6f756e7420697320686967686572207468616e207374616b650000000000604482015290519081900360640190fd5b60045467ffffffffffffffff16610dee576001600160a01b0384166000908152601060205260409020610d549060028501908a63ffffffff612c3416565b9050610d6760078263ffffffff612c5916565b80516020808301516040805193845291830152805187926001600160a01b0388169260008051602061348c83398151915292918290030190a38051602080830151604080519384529183015280516001600160a01b038716927fa219ed960eef85ed4cd072d3a9f240ea94727c0d6fb15e9a91a8cd4bf18ee3a692908290030190a2610eb8565b6001600160a01b0384166000908152600f6020908152604082206002810180546001810182559084529190922060045460039092020190610e4090889067ffffffffffffffff1663ffffffff6123d816565b8155610e5960028601600183018c63ffffffff612c3416565b9250610e6c60098463ffffffff612c5916565b610e7c828463ffffffff612c5916565b80548351602080860151604080519384529183015280516001600160a01b038a169260008051602061348c83398151915292908290030190a350505b611081565b81610ecc57505050505061091a565b5060408051808201909152600283018054825260038401805460208401526000918290555560045467ffffffffffffffff16610fc2576001600160a01b0384166000908152601060205260409020610f2a908263ffffffff612c5916565b610f3b60078263ffffffff612c5916565b80516020808301516040805193845291830152805187926001600160a01b0388169260008051602061348c83398151915292918290030190a38051602080830151604080519384529183015280516001600160a01b038716927fa219ed960eef85ed4cd072d3a9f240ea94727c0d6fb15e9a91a8cd4bf18ee3a692908290030190a2611081565b6001600160a01b0384166000908152600f602090815260408220600281018054600181018255908452919092206004546003909202019061101490889067ffffffffffffffff1663ffffffff6123d816565b8155825160018201556020830151600282015561103860098463ffffffff612c5916565b611048828463ffffffff612c5916565b8054600182015460028301546040805192835260208301919091528051339260008051602061348c83398151915292908290030190a350505b5050505050505050565b8080611095612458565b11156110d6576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6110de612447565b611128576040805162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1030b71037bbb732b960511b604482015290519081900360640190fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b15801561117357600080fd5b505afa158015611187573d6000803e3d6000fd5b505050506040513d602081101561119d57600080fd5b50516005546006549192506000916111ba9163ffffffff6123d816565b905060006111ce838363ffffffff612c9316565b6001546040805163a9059cbb60e01b81526001600160a01b038a8116600483015260248201859052915193945091169163a9059cbb916044808201926020929091908290030181600087803b15801561122657600080fd5b505af115801561123a573d6000803e3d6000fd5b505050506040513d602081101561125057600080fd5b50516112a3576040805162461bcd60e51b815260206004820181905260248201527f4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e604482015290519081900360640190fd5b604080516001600160a01b03881681526020810183905281517f7f66376a5ef2f39ab4ee2ee6e400606624e66929dba5d82df5b14dd0070a8a87929181900390910190a1505050505050565b6001600160a01b0381166000908152600f60205260409020600201805460609182918291908015611479578067ffffffffffffffff8111801561133157600080fd5b5060405190808252806020026020018201604052801561135b578160200160208202803683370190505b5094508067ffffffffffffffff8111801561137557600080fd5b5060405190808252806020026020018201604052801561139f578160200160208202803683370190505b5093508067ffffffffffffffff811180156113b957600080fd5b506040519080825280602002602001820160405280156113e3578160200160208202803683370190505b50925060005b818110156114775760008382815481106113ff57fe5b906000526020600020906003020190506000816001019050806000015488848151811061142857fe5b602002602001018181525050806001015487848151811061144557fe5b602002602001018181525050816000015486848151811061146257fe5b602090810291909101015250506001016113e9565b505b50509193909250565b808061148c612458565b11156114cd576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6114d5612458565b60035411611518576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b336000611523612458565b90506000611531838361261d565b5091505061153d61333e565b81548082526000835561155390859083906128f4565b505050505050565b6010602052600090815260409020805460019091015482565b808061157e612458565b11156115bf576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6115c7612447565b611611576040805162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1030b71037bbb732b960511b604482015290519081900360640190fd5b83611620576005549350611677565b600554841115611677576040805162461bcd60e51b815260206004820152601f60248201527f416d6f756e7420686967686572207468616e207265776172647320706f6f6c00604482015290519081900360640190fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b1580156116c257600080fd5b505afa1580156116d6573d6000803e3d6000fd5b505050506040513d60208110156116ec57600080fd5b5051600654909150600090611707908763ffffffff6123d816565b90508181111561175e576040805162461bcd60e51b815260206004820152601760248201527f436f6e747261637420696e636f6e73697374656e63792e000000000000000000604482015290519081900360640190fd5b6001546040805163a9059cbb60e01b81526001600160a01b038881166004830152602482018a90529151919092169163a9059cbb9160448083019260209291908290030181600087803b1580156117b457600080fd5b505af11580156117c8573d6000803e3d6000fd5b505050506040513d60208110156117de57600080fd5b5051611831576040805162461bcd60e51b815260206004820181905260248201527f4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e604482015290519081900360640190fd5b600580548790039055604080516001600160a01b03871681526020810188905281517f8a652d32cc9efc0a4f0fc3708c9fb81d5a924a7b9af2a37c46e9e4910d0dc325929181900390910190a1505050505050565b8080611890612458565b11156118d1576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b6118d9612458565b6003541161191c576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b8261196e576040805162461bcd60e51b815260206004820152601f60248201527f416d6f756e74206d75737420626520686967686572207468616e207a65726f00604482015290519081900360640190fd5b6000611978612458565b90506000611986338361261d565b5091505060006119963384612aa1565b90506119a061333e565b6119b483600284018963ffffffff612cd516565b90506119c760078263ffffffff612ce716565b600182015481516020808401516040805193845291830152805133927f0fb741c5ad2cad8215be7b4cab4e3f202f907d4540668ebf954f8e742efd89cf92908290030190a350505050505050565b6000828152602081905260408120611a33908363ffffffff612d1516565b90505b92915050565b6000828152602081905260408120611a33908363ffffffff612d2116565b8080611a64612458565b1115611aa5576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b611aad612447565b611af7576040805162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1030b71037bbb732b960511b604482015290519081900360640190fd5b611aff612458565b6002541015611b55576040805162461bcd60e51b815260206004820152601b60248201527f4561726c696573742064656c657465206e6f7420726561636865640000000000604482015290519081900360640190fd5b600154604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015611ba057600080fd5b505afa158015611bb4573d6000803e3d6000fd5b505050506040513d6020811015611bca57600080fd5b50516001546040805163a9059cbb60e01b81526001600160a01b03888116600483015260248201859052915193945091169163a9059cbb916044808201926020929091908290030181600087803b158015611c2457600080fd5b505af1158015611c38573d6000803e3d6000fd5b505050506040513d6020811015611c4e57600080fd5b5051611c5957600080fd5b6040517fe4dedc7f698868c432fb679313d1381b06601dfb7174d9b18b2e35378b67633390600090a1836001600160a01b0316ff5b600081565b6001600160a01b03166000908152600f6020526040902080546001909101549091565b600d602052600090815260409020805460019091015482565b60055490565b8080611cdf612458565b1115611d20576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b82611d2a5761091a565b600154604080516323b872dd60e01b81523360048201523060248201526044810186905290516001600160a01b03909216916323b872dd916064808201926020929091908290030181600087803b158015611d8457600080fd5b505af1158015611d98573d6000803e3d6000fd5b505050506040513d6020811015611dae57600080fd5b5051611df3576040805162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b604482015290519081900360640190fd5b600554611e06908463ffffffff6123d816565b600555604080513381526020810185905281517fb562e06b275d420e5848bb9083041a1c68466a478df92491b473ce99a4c68f68929181900390910190a1505050565b604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902081565b6001600160a01b03166000908152600e60205260409020600281015460038201548254600190930154919390929190565b60075460085482565b6000818152602081905260408120611a3690612d36565b600082815260208190526040902060020154611edd906109686124e5565b610adc5760405162461bcd60e51b815260040180806020018281038252603081526020018061343c6030913960400191505060405180910390fd5b8080611f22612458565b1115611f63576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b611f6b612458565b60035411611fae576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b336000611fb9612458565b90506000611fc7838361261d565b50915050611fd361333e565b60018201805460208301819052600090915561155390859083906128f4565b600954600a5482565b600b5481565b808061200b612458565b111561204c576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b612054612458565b60035411612097576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b3360006120a2612458565b905060006120b0838361261d565b506040805180820190915281548152600182015460208201529092506120e1915084906120dc84612c1a565b6128f4565b600060018201819055905550505050565b80806120fc612458565b111561213d576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b612145612458565b60035411612188576040805162461bcd60e51b815260206004820152601860248201526000805160206134ac833981519152604482015290519081900360640190fd5b82158015906122bd57600154604080516323b872dd60e01b81523360048201523060248201526044810187905290516001600160a01b03909216916323b872dd916064808201926020929091908290030181600087803b1580156121eb57600080fd5b505af11580156121ff573d6000803e3d6000fd5b505050506040513d602081101561221557600080fd5b505161225a576040805162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b604482015290519081900360640190fd5b60065461226d908563ffffffff6123d816565b600655600754612283908563ffffffff6123d816565b60075560408051858152905133917f7ff07ce9a287649537e4b012e45cf012d90228b12e2b56bb03515a6b5436fcdf919081900360200190a25b60006122c7612458565b905060006122d5338361261d565b5091505082156115535780546122f1908763ffffffff6123d816565b90555050505050565b6001546001600160a01b031681565b8080612313612458565b1115612354576040805162461bcd60e51b8152602060048201526013602482015260008051602061346c833981519152604482015290519081900360640190fd5b61235c612447565b8061238e5750604080516c44454c45474154455f524f4c4560981b8152905190819003600d01902061238e9033611a3c565b6123c95760405162461bcd60e51b81526004018080602001828103825260248152602001806134186024913960400191505060405180910390fd5b61091a83612d41565b60035481565b600082820183811015611a33576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6000611a33836001600160a01b038416612d97565b60006124538133611a3c565b905090565b4390565b600c546040805180820190915280612472612458565b815260209081018490526000838152600d82526040902082518155910151600191820155600c546124a89163ffffffff6123d816565b600c5560408051838152905182917fbcd200f406c53d9438c6cdc2966a1e99c35f336588c95bc3639e539df21c2f53919081900360200190a25050565b3390565b6000828152602081905260409020612507908263ffffffff61243216565b156109b2576125146124e5565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000612562612458565b90508082106125715781612573565b805b600381905560408051918252517f68b095021b1f40fe513109f513c66692f0b3219aee674a69f4efc57badb8201d9181900360200190a15050565b60008281526020819052604090206125cc908263ffffffff612de116565b156109b2576125d96124e5565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b61262561333e565b6001600160a01b0383166000908152600f6020908152604080832060109092528220919060028101825b815481101561278c57612660613358565b82828154811061266c57fe5b600091825260209182902060408051808201825260039093029091018054835281518083019092526001810154825260020154818401529181019190915280519091508810156126bf5750600101612787565b60208101515187516126d69163ffffffff6123d816565b8752602080820151810151908801516126f49163ffffffff6123d816565b60208801528254600019018281146127555783818154811061271257fe5b906000526020600020906003020184848154811061272c57fe5b600091825260209091208254600390920201908155600180830154908201556002918201549101555b8380548061275f57fe5b6000828152602081206003600019909301928302018181556001810182905560020155905550505b61264f565b5080546127c6576001600160a01b0387166000908152600f6020526040812081815560018101829055906127c36002830182613377565b50505b84511515806127d85750602085015115155b92508215612872578451602080870151604080519384529183015280516001600160a01b038a16927fa219ed960eef85ed4cd072d3a9f240ea94727c0d6fb15e9a91a8cd4bf18ee3a692908290030190a261283a60098663ffffffff612ce716565b80541561285157612851828663ffffffff612ce716565b61286260078663ffffffff612c5916565b612872848663ffffffff612c5916565b50509250925092565b61288361333e565b818360010154106128b45760018301546128a3908363ffffffff612c9316565b600184015560208101829052611a36565b60018301546128ca90839063ffffffff612c9316565b80825283546128de9163ffffffff612c9316565b8355600190920180546020840152600090555090565b801561091a5781602001516005541015612955576040805162461bcd60e51b815260206004820181905260248201527f4e6f7420656e6f7567682066756e647320696e207265776172647320706f6f6c604482015290519081900360640190fd5b6001546040805163a9059cbb60e01b81526001600160a01b038681166004830152602482018590529151919092169163a9059cbb9160448083019260209291908290030181600087803b1580156129ab57600080fd5b505af11580156129bf573d6000803e3d6000fd5b505050506040513d60208110156129d557600080fd5b5051612a1a576040805162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b604482015290519081900360640190fd5b6020820151600554612a319163ffffffff612c9316565b6005558151600654612a489163ffffffff612c9316565b600655612a5c60078363ffffffff612ce716565b81516020808401516040805193845291830152805133927ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b56892908290030190a2505050565b6001600160a01b0382166000908152600e6020526040812090612ac660028301612c1a565b90508015612ba557815460018301545b600c54811015612b87576000818152600d602052604090208054831015612b44576040805162461bcd60e51b815260206004820152601860248201527f73696e6365426c6f636b20696e636f6e73697374656e63790000000000000000604482015290519081900360640190fd5b600c5486906001840190811015612b67576000818152600d602052604090205491505b612b78868460010154878503612df6565b95509093505050600101612ad6565b506002830154612b9e90839063ffffffff612c9316565b6003840155505b828255600c54612bb6576000612bbd565b6001600c54035b600183018190556002830154600384015460408051928352602083019190915280516001600160a01b038816927f6b6b43cdaf8f2ae06bdc6f6be433bb374b703ce8c88db4adce9dd8aefe44fe6d92908290030190a35092915050565b60018101548154600091611a36919063ffffffff6123d816565b612c3c61333e565b612c46848361287b565b9050612c528382612c59565b9392505050565b80518254612c6c9163ffffffff6123d816565b825560208101516001830154612c879163ffffffff6123d816565b82600101819055505050565b6000611a3383836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612e36565b612cdd61333e565b612c468483612ecd565b80518254612cfa9163ffffffff612c9316565b825560208101516001830154612c879163ffffffff612c9316565b6000611a338383612f39565b6000611a33836001600160a01b038416612f9d565b6000611a3682612fb5565b6004805467ffffffffffffffff831667ffffffffffffffff19909116811790915560408051918252517f6b24ea7c7a8ce7fd2e6a6cb675dbabcd38a90f83ddd581c4435610ce90456e8f9181900360200190a150565b6000612da38383612f9d565b612dd957508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611a36565b506000611a36565b6000611a33836001600160a01b038416612fb9565b6000612e2e612e28612e22612e0b600161307f565b612e1d87670de0b6b3a764000061309d565b6130d4565b84613108565b85613156565b949350505050565b60008184841115612ec55760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612e8a578181015183820152602001612e72565b50505050905090810190601f168015612eb75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b612ed561333e565b82548211612ef9578254612eef908363ffffffff612c9316565b8355818152611a36565b8254612f0c90839063ffffffff612c9316565b602082018190526001840154612f279163ffffffff612c9316565b60018401558254815260009092555090565b81546000908210612f7b5760405162461bcd60e51b81526004018080602001828103825260228152602001806133c76022913960400191505060405180910390fd5b826000018281548110612f8a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b600081815260018301602052604081205480156130755783546000198083019190810190600090879083908110612fec57fe5b906000526020600020015490508087600001848154811061300957fe5b60009182526020808320909101929092558281526001898101909252604090209084019055865487908061303957fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050611a36565b6000915050611a36565b6000677fffffffffffffff82111561309657600080fd5b5060401b90565b6000816130a957600080fd5b60006130b584846131be565b905060016001607f1b036001600160801b0382161115611a3357600080fd5b6000600f83810b9083900b0160016001607f1b031981128015906130ff575060016001607f1b038113155b611a3357600080fd5b6000613114600161307f565b90505b8115611a3657600182161561313d576131308184613308565b9050600182039150613151565b6131478384613308565b9250600182901c91505b613117565b60008161316557506000611a36565b600083600f0b121561317657600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b038111156131a557600080fd5b60401b81198111156131b657600080fd5b019392505050565b6000816131ca57600080fd5b60006001600160c01b0384116131ef5782604085901b816131e757fe5b0490506132f4565b60c084811c6401000000008110613208576020918201911c5b62010000811061321a576010918201911c5b610100811061322b576008918201911c5b6010811061323b576004918201911c5b6004811061324b576002918201911c5b6002811061325a576001820191505b60bf820360018603901c6001018260ff0387901b8161327557fe5b0492506001600160801b0383111561328c57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b828110156132b8576001820391505b608084901b929003828110156132cf576001820391505b829003608084901c82146132df57fe5b8881816132e857fe5b04870196505050505050505b6001600160801b03811115611a3357600080fd5b6000600f83810b9083900b0260401d60016001607f1b031981128015906130ff575060016001607f1b03811315611a3357600080fd5b604051806040016040528060008152602001600081525090565b60405180604001604052806000815260200161337261333e565b905290565b5080546000825560030290600052602060002090810190613398919061339b565b50565b61084891905b808211156133c25760008082556001820181905560028201556003016133a1565b509056fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e7443616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b655472616e73616374696f6e2065787069726564000000000000000000000000005bfe1e16d75ca1fa001989652cd88c5b8ee8a59b5df6df99836337def4d1c82d436f6e747261637420686173206265656e207061757365640000000000000000416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a26469706673582212205436678265352631f897e2cef39869cf23c267c4a2f500784a8480a5ff10f77664736f6c63430006080033", "immutableReferences": {}, "sourceMap": "1040:33269:6:-:0;;;6829:813;5:9:-1;2:2;;;27:1;24;17:12;2:2;6829:813:6;;;;;;;;;;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;6829:813:6;;;;;;;;;;;;;;;;;;;7012:42;1762:4:9;7043:10:6;-1:-1:-1;;;;;7012:10:6;:42;:::i;:::-;7065:6;:29;;-1:-1:-1;;;;;;7065:29:6;-1:-1:-1;;;;;7065:29:6;;;;;7122:47;4364:6;7122:17;:15;:17::i;:::-;:21;;;;;;:47;;;;:::i;:::-;7104:15;:65;7494:37;7512:18;-1:-1:-1;;;;;7494:17:6;:37;:::i;:::-;7541:38;7558:20;-1:-1:-1;;;;;7541:16:6;:38;:::i;:::-;7589:46;7601:16;-1:-1:-1;;;;;7589:11:6;:46;:::i;:::-;6829:813;;;;1040:33269;;6578:110:9;6656:25;6667:4;6673:7;-1:-1:-1;;;;;6656:10:9;:25;:::i;:::-;6578:110;;:::o;25560:106:6:-;25647:12;25560:106;:::o;874:176:10:-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:10;;;;;;;;;;;;;;;;;;;;;;;;;;;;1042:1;-1:-1:-1;874:176:10;;;;;:::o;26522:148:6:-;26592:19;:33;;-1:-1:-1;;;;;26592:33:6;;-1:-1:-1;;;;;;26592:33:6;;;;;;;;26640:23;;;;;;;;;;;;;;;;26522:148;:::o;26015:390::-;26093:21;;26146:148;;;;;;;;;;26195:17;-1:-1:-1;;;;;26195:15:6;:17;:::i;:::-;26146:148;;;;;;;;;-1:-1:-1;26124:19:6;;;:14;:19;;;;;:170;;;;;;;;;;;;;26328:21;;:28;;:21;;:25;;;;;:28;;:::i;:::-;26304:21;:52;26372:26;;;;;;;;26388:3;;26372:26;;;;;;;;;;26015:390;;:::o;26933:255::-;26999:26;27028:17;-1:-1:-1;;;;;27028:15:6;:17;:::i;:::-;26999:46;;27089:18;27075:11;:32;:67;;27131:11;27075:67;;;27110:18;27075:67;27055:17;:87;;;27157:24;;;;;;;;;;;;;;;;26933:255;;:::o;7015:184:9:-;7088:6;:12;;;;;;;;;;;:33;;7113:7;;7088:24;;;;;:33;;:::i;:::-;7084:109;;;7169:12;-1:-1:-1;;;;;7169:10:9;:12;:::i;:::-;-1:-1:-1;;;;;7142:40:9;7160:7;-1:-1:-1;;;;;7142:40:9;7154:4;7142:40;;;;;;;;;;7015:184;;:::o;4864:141:15:-;4934:4;4957:41;4962:3;-1:-1:-1;;;;;4982:14:15;;-1:-1:-1;;;;;4957:4:15;:41;:::i;590:104:8:-;677:10;590:104;:::o;1611:404:15:-;1674:4;1695:21;1705:3;1710:5;-1:-1:-1;;;;;1695:9:15;:21;:::i;:::-;1690:319;;-1:-1:-1;27:10;;39:1;23:18;;;45:23;;1732:11:15;:23;;;;;;;;;;;;;1912:18;;1890:19;;;:12;;;:19;;;;;;:40;;;;1944:11;;1690:319;-1:-1:-1;1993:5:15;1986:12;;3776:127;3849:4;3872:19;;;:12;;;;;:19;;;;;;:24;;;3776:127::o;1040:33269:6:-;;;;;;;", "deployedSourceMap": "1040:33269:6:-:0;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1040:33269:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;9;2:12;17935:104:6;;;:::i;:::-;;;;;;;;;;;;;;;;5441:34;;;:::i;8281:217::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;8281:217:6;;;;;;;:::i;:::-;;4272:112:9;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4272:112:9;;:::i;5280:33:6:-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;5608:38;;;:::i;4634:223:9:-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4634:223:9;;;;;;-1:-1:-1;;;;;4634:223:9;;:::i;5201:30:6:-;;;:::i;21515:197::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;21515:197:6;;;;;;;:::i;5808:205:9:-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5808:205:9;;;;;;-1:-1:-1;;;;;5808:205:9;;:::i;4313:57:6:-;;;:::i;18046:157::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;18046:157:6;-1:-1:-1;;;;;18046:157:6;;:::i;5813:36::-;;;:::i;9618:495::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;9618:495:6;;;;;;;:::i;14174:3640::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;14174:3640:6;;;;;;;:::i;23734:782::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;23734:782:6;;;;;;;;:::i;18723:846::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;18723:846:6;-1:-1:-1;;;;;18723:846:6;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;18723:846:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;18723:846:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;18723:846:6;;;;;;;;;;;;;;;;;;;;;10248:529;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;10248:529:6;;:::i;6003:52::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;6003:52:6;-1:-1:-1;;;;;6003:52:6;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;21981:1120;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;21981:1120:6;;;-1:-1:-1;;;;;21981:1120:6;;;;;;;;;;:::i;12116:1291::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;12116:1291:6;;;;;;;:::i;3955:136:9:-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3955:136:9;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;3955:136:9;;;;;;;;;;;;;;2940:137;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;2940:137:9;;;;;;-1:-1:-1;;;;;2940:137:9;;:::i;:::-;;;;;;;;;;;;;;;;;;24840:449:6;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;24840:449:6;;;;;;;;:::i;1717:49:9:-;;;:::i;18210:275:6:-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;18210:275:6;-1:-1:-1;;;;;18210:275:6;;:::i;5855:62::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5855:62:6;;:::i;17821:107::-;;;:::i;20437:438::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;20437:438:6;;;;;;;:::i;4241:66::-;;;:::i;19576:424::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;19576:424:6;-1:-1:-1;;;;;19576:424:6;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5652:45;;;:::i;3245:125:9:-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3245:125:9;;:::i;5091:226::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5091:226:9;;;;;;-1:-1:-1;;;;;5091:226:9;;:::i;10912:564:6:-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;10912:564:6;;:::i;5712:42::-;;;:::i;5770:37::-;;;:::i;11618:491::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;11618:491:6;;:::i;8505:831::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;8505:831:6;;;;;;;:::i;4427:20::-;;;:::i;21018:208::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;21018:208:6;;;;;;;;;:::i;5242:32::-;;;:::i;17935:104::-;18017:15;;17935:104;;:::o;5441:34::-;;;;:::o;8281:217::-;6283:10;:8;:10::i;:::-;:48;;;-1:-1:-1;4281:26:6;;;-1:-1:-1;;;4281:26:6;;;;;;;;;;;;6297:34;;6320:10;6297:7;:34::i;:::-;6275:97;;;;-1:-1:-1;;;6275:97:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8438:15:::1;6488;6467:17;:15;:17::i;:::-;:36;;6459:68;;;::::0;;-1:-1:-1;;;6459:68:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;::::1;;8469:22:::2;8486:4;8469:16;:22::i;:::-;6382:1:::1;8281:217:::0;;:::o;4272:112:9:-;4329:7;4355:12;;;;;;;;;;:22;;;;4272:112::o;5280:33:6:-;;;;;;:::o;5608:38::-;;;;:::o;4634:223:9:-;4725:6;:12;;;;;;;;;;:22;;;4717:45;;4749:12;:10;:12::i;:::-;4717:7;:45::i;:::-;4709:105;;;;-1:-1:-1;;;4709:105:9;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4825:25;4836:4;4842:7;4825:10;:25::i;:::-;4634:223;;:::o;5201:30:6:-;;;;:::o;21515:197::-;21627:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6283:10:::1;:8;:10::i;:::-;:48;;;-1:-1:-1::0;4281:26:6::1;::::0;;-1:-1:-1;;;4281:26:6;;;;;;;;::::1;::::0;;;6297:34:::1;::::0;6320:10:::1;6297:7;:34::i;:::-;6275:97;;;;-1:-1:-1::0;;;6275:97:6::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21681:24:::2;21693:11;21681;:24::i;5808:205:9:-:0;5905:12;:10;:12::i;:::-;-1:-1:-1;;;;;5894:23:9;:7;-1:-1:-1;;;;;5894:23:9;;5886:83;;;;-1:-1:-1;;;5886:83:9;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5980:26;5992:4;5998:7;5980:11;:26::i;4313:57:6:-;4364:6;4313:57;:::o;18046:157::-;-1:-1:-1;;;;;18163:19:6;18128:14;18163:19;;;:7;:19;;;;;:26;;:33;;18046:157::o;5813:36::-;;;;:::o;9618:495::-;9749:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6616:17:::1;:15;:17::i;:::-;6596;;:37;6588:74;;;::::0;;-1:-1:-1;;;6588:74:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6588:74:6;;;;;;;;;;;;;::::1;;9823:10:::2;9806:14;9864:17;:15;:17::i;:::-;9843:38;;9894:32;9931:37;9949:6;9957:10;9931:17;:37::i;:::-;9891:77;;;;9979:29;;:::i;:::-;10011:43;:9:::0;10047:6;10011:43:::2;:35;:43;:::i;:::-;9979:75;;10064:42;10082:6;10090:7;10099:6;10064:17;:42::i;:::-;6672:1;;;;9618:495:::0;;;:::o;14174:3640::-;14353:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6616:17:::1;:15;:17::i;:::-;6596;;:37;6588:74;;;::::0;;-1:-1:-1;;;6588:74:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6588:74:6;;;;;;;;;;;;;::::1;;14410:18:::2;14431:17;:15;:17::i;:::-;14410:38:::0;-1:-1:-1;14475:10:6::2;14458:14;14517:48;14475:10:::0;14410:38;14517:28:::2;:48::i;:::-;14495:70;;14576:23;14602;:5;:11;;:21;:23::i;:::-;14576:49;;14635:29;;:::i;:::-;14679:10:::0;;14675:3133:::2;;15435:15;15425:6;:25;;15417:65;;;::::0;;-1:-1:-1;;;15417:65:6;;::::2;;::::0;::::2;::::0;::::2;::::0;;;;::::2;::::0;;;;;;;;;;;;;::::2;;15501:19;::::0;::::2;;15497:1049;;-1:-1:-1::0;;;;;15598:18:6;::::2;;::::0;;;:10:::2;:18;::::0;;;;15555:70:::2;::::0;:11:::2;::::0;::::2;::::0;15618:6;15555:70:::2;:42;:70;:::i;:::-;15545:80:::0;-1:-1:-1;15643:37:6::2;:23;15545:80:::0;15643:37:::2;:28;:37;:::i;:::-;15735:17:::0;;15754:24:::2;::::0;;::::2;::::0;15703:76:::2;::::0;;;;;;;::::2;::::0;;;15723:10;;-1:-1:-1;;;;;15703:76:6;::::2;::::0;-1:-1:-1;;;;;;;;;;;15703:76:6;;;;;;;::::2;15828:17:::0;;15847:24:::2;::::0;;::::2;::::0;15802:70:::2;::::0;;;;;;;::::2;::::0;;;-1:-1:-1;;;;;15802:70:6;::::2;::::0;::::2;::::0;;;;;;;::::2;15497:1049;;;-1:-1:-1::0;;;;;15935:15:6;::::2;15911:21;15935:15:::0;;;:7:::2;:15;::::0;;;;;;16005:13:::2;::::0;::::2;27:10:-1::0;;16005:20:6::2;23:18:-1::0;::::2;45:23:::0;;16005:20:6;;;;;;;16092:19:::2;::::0;16005:20:::2;::::0;;::::2;;::::0;16077:35:::2;::::0;:10;;16092:19:::2;;16077:35;:14;:35;:::i;:::-;16043:69:::0;;16140:72:::2;:11;::::0;::::2;16183:20;::::0;::::2;16205:6:::0;16140:72:::2;:42;:72;:::i;:::-;16130:82:::0;-1:-1:-1;16231:34:6::2;:20;16130:82:::0;16231:34:::2;:25;:34;:::i;:::-;16283:30;:6:::0;16305:7;16283:30:::2;:21;:30;:::i;:::-;16454:31:::0;;16487:17;;16506:24:::2;::::0;;::::2;::::0;16434:97:::2;::::0;;;;;;;::::2;::::0;;;-1:-1:-1;;;;;16434:97:6;::::2;::::0;-1:-1:-1;;;;;;;;;;;16434:97:6;;;;;;;::::2;15497:1049;;;14675:3133;;;16580:20:::0;16576:108:::2;;16663:7;;;;;;;16576:108;-1:-1:-1::0;16698:21:6::2;::::0;;;;::::2;::::0;;;16708:11:::2;::::0;::::2;16698:21:::0;;;;;;;;;::::2;::::0;::::2;::::0;-1:-1:-1;16733:25:6;;;;16772:32;16823:19:::2;::::0;::::2;;16819:979;;-1:-1:-1::0;;;;;16867:18:6;::::2;;::::0;;;:10:::2;:18;::::0;;;;:32:::2;::::0;16891:7;16867:32:::2;:23;:32;:::i;:::-;16917:37;:23;16946:7:::0;16917:37:::2;:28;:37;:::i;:::-;17009:17:::0;;17028:24:::2;::::0;;::::2;::::0;16977:76:::2;::::0;;;;;;;::::2;::::0;;;16997:10;;-1:-1:-1;;;;;16977:76:6;::::2;::::0;-1:-1:-1;;;;;;;;;;;16977:76:6;;;;;;;::::2;17102:17:::0;;17121:24:::2;::::0;;::::2;::::0;17076:70:::2;::::0;;;;;;;::::2;::::0;;;-1:-1:-1;;;;;17076:70:6;::::2;::::0;::::2;::::0;;;;;;;::::2;16819:979;;;-1:-1:-1::0;;;;;17209:15:6;::::2;17185:21;17209:15:::0;;;:7:::2;:15;::::0;;;;;;17279:13:::2;::::0;::::2;27:10:-1::0;;17279:20:6::2;23:18:-1::0;::::2;45:23:::0;;17279:20:6;;;;;;;17366:19:::2;::::0;17279:20:::2;::::0;;::::2;;::::0;17351:35:::2;::::0;:10;;17366:19:::2;;17351:35;:14;:35;:::i;:::-;17317:69:::0;;17404:30;;:20:::2;::::0;::::2;:30:::0;::::2;::::0;::::2;::::0;;;;;17453:34:::2;:20;17427:7:::0;17453:34:::2;:25;:34;:::i;:::-;17505:30;:6:::0;17527:7;17505:30:::2;:21;:30;:::i;:::-;17680:31:::0;;17713:20:::2;::::0;::::2;:30:::0;17745:37;;;;17656:127:::2;::::0;;;;;::::2;::::0;::::2;::::0;;;;;;17668:10:::2;::::0;-1:-1:-1;;;;;;;;;;;17656:127:6;;;;;;;::::2;16819:979;;;6672:1;;;;;14174:3640:::0;;;:::o;23734:782::-;23866:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6135:10:::1;:8;:10::i;:::-;6127:45;;;::::0;;-1:-1:-1;;;6127:45:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;6127:45:6;;;;;;;;;;;;;::::1;;23943:6:::2;::::0;:31:::2;::::0;;-1:-1:-1;;;23943:31:6;;23968:4:::2;23943:31;::::0;::::2;::::0;;;23917:23:::2;::::0;-1:-1:-1;;;;;23943:6:6::2;::::0;:16:::2;::::0;:31;;;;;::::2;::::0;;;;;;;;:6;:31;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;23943:31:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;23943:31:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;23943:31:6;24049:19:::2;::::0;24021:23:::2;::::0;23943:31;;-1:-1:-1;23984:34:6::2;::::0;24021:48:::2;::::0;::::2;:27;:48;:::i;:::-;23984:85:::0;-1:-1:-1;24275:20:6::2;24298:47;:15:::0;23984:85;24298:47:::2;:19;:47;:::i;:::-;24363:6;::::0;:44:::2;::::0;;-1:-1:-1;;;24363:44:6;;-1:-1:-1;;;;;24363:44:6;;::::2;;::::0;::::2;::::0;;;;;;;;;24275:70;;-1:-1:-1;24363:6:6;::::2;::::0;:15:::2;::::0;:44;;;;;::::2;::::0;;;;;;;;;:6:::2;::::0;:44;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;24363:44:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;24363:44:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;24363:44:6;24355:89:::2;;;::::0;;-1:-1:-1;;;24355:89:6;;::::2;;::::0;::::2;::::0;;;;;;;::::2;::::0;;;;;;;;;;;;;::::2;;24459:50;::::0;;-1:-1:-1;;;;;24459:50:6;::::2;::::0;;::::2;::::0;::::2;::::0;;;;;::::2;::::0;;;;;;;;;::::2;6182:1;;;23734:782:::0;;;:::o;18723:846::-;-1:-1:-1;;;;;18962:19:6;;18925:34;18962:19;;;:7;:19;;;;;:26;;19015:19;;18813:26;;;;;;18962;19048:11;;19044:519;;19101:6;19087:21;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;19087:21:6;;;;;;;;;;;;;;;;;;;;;;;29:2:-1;21:6;17:15;125:4;109:14;101:6;88:42;144:17;;-1:-1;19087:21:6;;19075:33;;19155:6;19141:21;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;19141:21:6;;;;;;;;;;;;;;;;;;;;;;;29:2:-1;21:6;17:15;125:4;109:14;101:6;88:42;144:17;;-1:-1;19141:21:6;;19122:40;;19209:6;19195:21;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;19195:21:6;;;;;;;;;;;;;;;;;;;;;;;29:2:-1;21:6;17:15;125:4;109:14;101:6;88:42;144:17;;-1:-1;19195:21:6;-1:-1:-1;19176:40:6;-1:-1:-1;19236:9:6;19231:322;19253:6;19249:1;:10;19231:322;;;19284:22;19309:12;19322:1;19309:15;;;;;;;;;;;;;;;;;;19284:40;;19342:24;19369:2;:8;;19342:35;;19410:1;:11;;;19395:9;19405:1;19395:12;;;;;;;;;;;;;:26;;;;;19461:1;:18;;;19439:16;19456:1;19439:19;;;;;;;;;;;;;:40;;;;;19519:2;:19;;;19497:16;19514:1;19497:19;;;;;;;;;;;;;;;;;:41;-1:-1:-1;;19261:3:6;;19231:322;;;;19044:519;18723:846;;;;;;;:::o;10248:529::-;10364:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6616:17:::1;:15;:17::i;:::-;6596;;:37;6588:74;;;::::0;;-1:-1:-1;;;6588:74:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6588:74:6;;;;;;;;;;;;;::::1;;10438:10:::2;10421:14;10479:17;:15;:17::i;:::-;10458:38;;10509:32;10547:37;10565:6;10573:10;10547:17;:37::i;:::-;10506:78;;;;10595:29;;:::i;:::-;10654:19:::0;;10634:39;;;10654:19:::2;10683:23:::0;;10717:53:::2;::::0;10735:6;;10634:7;;10717:17:::2;:53::i;:::-;6672:1;;;;10248:529:::0;;:::o;6003:52::-;;;;;;;;;;;;;;;;;;;:::o;21981:1120::-;22149:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6135:10:::1;:8;:10::i;:::-;6127:45;;;::::0;;-1:-1:-1;;;6127:45:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;6127:45:6;;;;;;;;;;;;;::::1;;22204:11:::0;22200:174:::2;;22240:19;;22231:28;;22200:174;;;22308:19;;22298:6;:29;;22290:73;;;::::0;;-1:-1:-1;;;22290:73:6;;::::2;;::::0;::::2;::::0;::::2;::::0;;;;::::2;::::0;;;;;;;;;;;;;::::2;;22588:6;::::0;:31:::2;::::0;;-1:-1:-1;;;22588:31:6;;22613:4:::2;22588:31;::::0;::::2;::::0;;;22562:23:::2;::::0;-1:-1:-1;;;;;22588:6:6::2;::::0;:16:::2;::::0;:31;;;;;::::2;::::0;;;;;;;;:6;:31;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;22588:31:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;22588:31:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;22588:31:6;22666:23:::2;::::0;22588:31;;-1:-1:-1;22629:34:6::2;::::0;22666:35:::2;::::0;22694:6;22666:35:::2;:27;:35;:::i;:::-;22629:72;;22749:15;22719:26;:45;;22711:81;;;::::0;;-1:-1:-1;;;22711:81:6;;::::2;;::::0;::::2;::::0;::::2;::::0;;;;::::2;::::0;;;;;;;;;;;;;::::2;;22811:6;::::0;:38:::2;::::0;;-1:-1:-1;;;22811:38:6;;-1:-1:-1;;;;;22811:38:6;;::::2;;::::0;::::2;::::0;;;;;;;;;:6;;;::::2;::::0;:15:::2;::::0;:38;;;;;::::2;::::0;;;;;;;;:6:::2;::::0;:38;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;22811:38:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;22811:38:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;22811:38:6;22803:83:::2;;;::::0;;-1:-1:-1;;;22803:83:6;;::::2;;::::0;::::2;::::0;;;;;;;::::2;::::0;;;;;;;;;;;;;::::2;;23000:19;:29:::0;;;;::::2;::::0;;23045:49:::2;::::0;;-1:-1:-1;;;;;23045:49:6;::::2;::::0;;::::2;::::0;::::2;::::0;;;;;::::2;::::0;;;;;;;;;::::2;6182:1;;21981:1120:::0;;;;:::o;12116:1291::-;12248:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6616:17:::1;:15;:17::i;:::-;6596;;:37;6588:74;;;::::0;;-1:-1:-1;;;6588:74:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6588:74:6;;;;;;;;;;;;;::::1;;12313:11:::0;12305:55:::2;;;::::0;;-1:-1:-1;;;12305:55:6;;::::2;;::::0;::::2;::::0;::::2;::::0;;;;::::2;::::0;;;;;;;;;;;;;::::2;;12371:18;12392:17;:15;:17::i;:::-;12371:38;;12423:32;12461:41;12479:10;12491;12461:17;:41::i;:::-;12420:82;;;;12848:19;12870:52;12899:10;12911;12870:28;:52::i;:::-;12848:74;;12932:29;;:::i;:::-;12964:54;:9:::0;12998:11:::2;::::0;::::2;13011:6:::0;12964:54:::2;:33;:54;:::i;:::-;12932:86:::0;-1:-1:-1;13028:37:6::2;:23;12932:86:::0;13028:37:::2;:28;:37;:::i;:::-;13314:28;::::0;::::2;::::0;13344:17;;13363:24:::2;::::0;;::::2;::::0;13292:96:::2;::::0;;;;;;;::::2;::::0;;;13302:10:::2;::::0;13292:96:::2;::::0;;;;;;;::::2;6672:1;;;;12116:1291:::0;;;:::o;3955:136:9:-;4028:7;4054:12;;;;;;;;;;:30;;4078:5;4054:30;:23;:30;:::i;:::-;4047:37;;3955:136;;;;;:::o;2940:137::-;3009:4;3032:12;;;;;;;;;;:38;;3062:7;3032:38;:29;:38;:::i;24840:449:6:-;24958:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6135:10:::1;:8;:10::i;:::-;6127:45;;;::::0;;-1:-1:-1;;;6127:45:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;6127:45:6;;;;;;;;;;;;;::::1;;25032:17:::2;:15;:17::i;:::-;25013:15;;:36;;25005:76;;;::::0;;-1:-1:-1;;;25005:76:6;;::::2;;::::0;::::2;::::0;::::2;::::0;;;;::::2;::::0;;;;;;;;;;;;;::::2;;25117:6;::::0;:31:::2;::::0;;-1:-1:-1;;;25117:31:6;;25142:4:::2;25117:31;::::0;::::2;::::0;;;25091:23:::2;::::0;-1:-1:-1;;;;;25117:6:6::2;::::0;:16:::2;::::0;:31;;;;;::::2;::::0;;;;;;;;:6;:31;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;25117:31:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;25117:31:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;25117:31:6;25166:6:::2;::::0;:47:::2;::::0;;-1:-1:-1;;;25166:47:6;;-1:-1:-1;;;;;25166:47:6;;::::2;;::::0;::::2;::::0;;;;;;;;;25117:31;;-1:-1:-1;25166:6:6;::::2;::::0;:15:::2;::::0;:47;;;;;25117:31:::2;::::0;25166:47;;;;;;;;:6:::2;::::0;:47;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;25166:47:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;25166:47:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;25166:47:6;25158:56:::2;;12:1:-1;9::::0;2:12:::2;25158:56:6;25229:16;::::0;::::2;::::0;;;::::2;25268:13;-1:-1:-1::0;;;;;25255:27:6::2;;1717:49:9::0;1762:4;1717:49;:::o;18210:275:6:-;-1:-1:-1;;;;;18383:19:6;18293:17;18383:19;;;:7;:19;;;;;18430;;18451:26;;;;;18430:19;;18210:275::o;5855:62::-;;;;;;;;;;;;;;;;;;;:::o;17821:107::-;17902:19;;17821:107;:::o;20437:438::-;20576:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;20613:11;20609:48:::1;;20640:7;;20609:48;20675:6;::::0;:54:::1;::::0;;-1:-1:-1;;;20675:54:6;;20695:10:::1;20675:54;::::0;::::1;::::0;20715:4:::1;20675:54:::0;;;;;;;;;;;;-1:-1:-1;;;;;20675:6:6;;::::1;::::0;:19:::1;::::0;:54;;;;;::::1;::::0;;;;;;;;;:6:::1;::::0;:54;::::1;;2:2:-1::0;::::1;;;27:1;24::::0;17:12:::1;2:2;20675:54:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::1;77:16;74:1;67:27;5:2;20675:54:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::1;4:2;-1:-1:::0;20675:54:6;20667:82:::1;;;::::0;;-1:-1:-1;;;20667:82:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;20667:82:6;;;;;;;;;;;;;::::1;;20781:19;::::0;:31:::1;::::0;20805:6;20781:31:::1;:23;:31;:::i;:::-;20759:19;:53:::0;20827:41:::1;::::0;;20849:10:::1;20827:41:::0;;::::1;::::0;::::1;::::0;;;;;::::1;::::0;;;;;;;;;::::1;20437:438:::0;;;:::o;4241:66::-;4281:26;;;-1:-1:-1;;;4281:26:6;;;;;;;;;;;;4241:66;:::o;19576:424::-;-1:-1:-1;;;;;19772:19:6;19643:17;19772:19;;;:7;:19;;;;;19813:11;;;:21;19863:28;;;;19914:16;;19863:28;19965;;;;19813:21;;19863:28;;19914:16;19965:28;19576:424::o;5652:45::-;;;;;;:::o;3245:125:9:-;3308:7;3334:12;;;;;;;;;;:29;;:27;:29::i;5091:226::-;5183:6;:12;;;;;;;;;;:22;;;5175:45;;5207:12;:10;:12::i;5175:45::-;5167:106;;;;-1:-1:-1;;;5167:106:9;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10912:564:6;11035:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6616:17:::1;:15;:17::i;:::-;6596;;:37;6588:74;;;::::0;;-1:-1:-1;;;6588:74:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6588:74:6;;;;;;;;;;;;;::::1;;11109:10:::2;11092:14;11150:17;:15;:17::i;:::-;11129:38;;11180:32;11218:37;11236:6;11244:10;11218:17;:37::i;:::-;11177:78;;;;11266:29;;:::i;:::-;11332:26;::::0;::::2;::::0;;11305:24:::2;::::0;::::2;:53:::0;;;11397:1:::2;11368:30:::0;;;11409:60:::2;::::0;11427:6;;11305:7;;11409:17:::2;:60::i;5712:42::-:0;;;;;;:::o;5770:37::-;;;;:::o;11618:491::-;11739:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6616:17:::1;:15;:17::i;:::-;6596;;:37;6588:74;;;::::0;;-1:-1:-1;;;6588:74:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6588:74:6;;;;;;;;;;;;;::::1;;11813:10:::2;11796:14;11854:17;:15;:17::i;:::-;11833:38;;11884:32;11922:37;11940:6;11948:10;11922:17;:37::i;:::-;-1:-1:-1::0;11970:59:6::2;::::0;;;;::::2;::::0;;;;;;;::::2;::::0;::::2;::::0;::::2;::::0;::::2;::::0;11881:78;;-1:-1:-1;11970:59:6::2;::::0;-1:-1:-1;11988:6:6;;12007:21:::2;11881:78:::0;12007:19:::2;:21::i;:::-;11970:17;:59::i;:::-;12068:1;12039:26;::::0;::::2;:30:::0;;;12079:23;;-1:-1:-1;;;;11618:491:6:o;8505:831::-;8635:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6616:17:::1;:15;:17::i;:::-;6596;;:37;6588:74;;;::::0;;-1:-1:-1;;;6588:74:6;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;;;;;;;;;6588:74:6;;;;;;;;;;;;;::::1;;8712:11:::0;;;::::2;::::0;8733:352:::2;;8773:6;::::0;:54:::2;::::0;;-1:-1:-1;;;8773:54:6;;8793:10:::2;8773:54;::::0;::::2;::::0;8813:4:::2;8773:54:::0;;;;;;;;;;;;-1:-1:-1;;;;;8773:6:6;;::::2;::::0;:19:::2;::::0;:54;;;;;::::2;::::0;;;;;;;;;:6:::2;::::0;:54;::::2;;2:2:-1::0;::::2;;;27:1;24::::0;17:12:::2;2:2;8773:54:6;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::2;77:16;74:1;67:27;5:2;8773:54:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::2;4:2;-1:-1:::0;8773:54:6;8765:82:::2;;;::::0;;-1:-1:-1;;;8765:82:6;;::::2;;::::0;::::2;::::0;::::2;::::0;;;;-1:-1:-1;;;8765:82:6;;;;;;;;;;;;;::::2;;8887:23;::::0;:35:::2;::::0;8915:6;8887:35:::2;:27;:35;:::i;:::-;8861:23;:61:::0;8972:23:::2;:33:::0;:45:::2;::::0;9010:6;8972:45:::2;:37;:45;:::i;:::-;8936:23;:81:::0;9036:38:::2;::::0;;;;;;;9055:10:::2;::::0;9036:38:::2;::::0;;;;;::::2;::::0;;::::2;8733:352;9095:18;9116:17;:15;:17::i;:::-;9095:38;;9146:32;9183:41;9201:10;9213;9183:17;:41::i;:::-;9143:81;;;;9239:12;9235:95;;;9289:19:::0;;:31:::2;::::0;9313:6;9289:31:::2;:23;:31;:::i;:::-;9267:53:::0;;-1:-1:-1;;;;;8505:831:6:o;4427:20::-;;;-1:-1:-1;;;;;4427:20:6;;:::o;21018:208::-;21135:17;6488:15;6467:17;:15;:17::i;:::-;:36;;6459:68;;;;;-1:-1:-1;;;6459:68:6;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;6459:68:6;;;;;;;;;;;;;;;6283:10:::1;:8;:10::i;:::-;:48;;;-1:-1:-1::0;4281:26:6::1;::::0;;-1:-1:-1;;;4281:26:6;;;;;;;;::::1;::::0;;;6297:34:::1;::::0;6320:10:::1;6297:7;:34::i;:::-;6275:97;;;;-1:-1:-1::0;;;6275:97:6::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21189:30:::2;21207:11;21189:17;:30::i;5242:32::-:0;;;;:::o;874:176:10:-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:10;;;;;;;;;;;;;;;;;;;;;;;;;;;4864:141:15;4934:4;4957:41;4962:3;-1:-1:-1;;;;;4982:14:15;;4957:4;:41::i;25673:111:6:-;25715:4;25738:39;25715:4;25766:10;25738:7;:39::i;:::-;25731:46;;25673:111;:::o;25560:106::-;25647:12;25560:106;:::o;26015:390::-;26093:21;;26146:148;;;;;;;;;;26195:17;:15;:17::i;:::-;26146:148;;;;;;;;;-1:-1:-1;26124:19:6;;;:14;:19;;;;;:170;;;;;;;;;;;;26328:21;;:28;;;:25;:28;:::i;:::-;26304:21;:52;26372:26;;;;;;;;26388:3;;26372:26;;;;;;;;;;26015:390;;:::o;590:104:8:-;677:10;590:104;:::o;7015:184:9:-;7088:6;:12;;;;;;;;;;:33;;7113:7;7088:33;:24;:33;:::i;:::-;7084:109;;;7169:12;:10;:12::i;:::-;-1:-1:-1;;;;;7142:40:9;7160:7;-1:-1:-1;;;;;7142:40:9;7154:4;7142:40;;;;;;;;;;7015:184;;:::o;26933:255:6:-;26999:26;27028:17;:15;:17::i;:::-;26999:46;;27089:18;27075:11;:32;:67;;27131:11;27075:67;;;27110:18;27075:67;27055:17;:87;;;27157:24;;;;;;;;;;;;;;;;26933:255;;:::o;7205:188:9:-;7279:6;:12;;;;;;;;;;:36;;7307:7;7279:36;:27;:36;:::i;:::-;7275:112;;;7363:12;:10;:12::i;:::-;-1:-1:-1;;;;;7336:40:9;7354:7;-1:-1:-1;;;;;7336:40:9;7348:4;7336:40;;;;;;;;;;7205:188;;:::o;31612:2694:6:-;31706:39;;:::i;:::-;-1:-1:-1;;;;;31835:15:6;;31747:32;31835:15;;;:7;:15;;;;;;;;31932:10;:18;;;;;;31747:32;31897:13;;;31747:32;31961:1673;31983:19;;31979:23;;31961:1673;;;32020:20;;:::i;:::-;32043:12;32056:1;32043:15;;;;;;;;;;;;;;;;;32020:38;;;;;;;;32043:15;;;;;;;32020:38;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;32077:18;;32020:38;;-1:-1:-1;32077:29:6;-1:-1:-1;32073:191:6;;;-1:-1:-1;32126:3:6;;32241:8;;32073:191;32340:7;;;;:17;32308:27;;:50;;;:31;:50;:::i;:::-;32278:80;;32652:7;;;;;:24;;;32613:34;;;;:64;;;:38;:64;:::i;:::-;32576:34;;;:101;32978:19;;-1:-1:-1;;32978:23:6;33019:13;;;33015:92;;33070:12;33083:8;33070:22;;;;;;;;;;;;;;;;;;33052:12;33065:1;33052:15;;;;;;;;;;;;;;;;:40;;:15;;;;;:40;;;;;;;;;;;;;;;;;;;;33015:92;33605:12;:18;;;;;;;;;;;;;;;-1:-1:-1;;33605:18:6;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;31961:1673:6;;;;-1:-1:-1;33699:19:6;;33695:77;;-1:-1:-1;;;;;33746:15:6;;;;;;:7;:15;;;;;33739:22;;;;;;;;;33746:15;33739:22;;;;33746:15;33739:22;:::i;:::-;;;33695:77;33794:27;;:32;;;:75;;-1:-1:-1;33830:34:6;;;;:39;;33794:75;33782:87;;33883:9;33879:421;;;33940:27;;33969:34;;;;;33914:90;;;;;;;;;;;;-1:-1:-1;;;;;33914:90:6;;;;;;;;;;;;34019:44;:20;34045:17;34019:44;:25;:44;:::i;:::-;34081:19;;:24;34077:103;;34125:40;:6;34147:17;34125:40;:21;:40;:::i;:::-;34194:47;:23;34223:17;34194:47;:28;:47;:::i;:::-;34256:33;:9;34271:17;34256:33;:14;:33;:::i;:::-;31612:2694;;;;;;;:::o;2944:671:1:-;3040:20;;:::i;:::-;3105:6;3080:4;:21;;;:31;3076:533;;3151:21;;;;:33;;3177:6;3151:33;:25;:33;:::i;:::-;3127:21;;;:57;3198:24;;;:33;;;3076:533;;;3293:21;;;;3282:33;;:6;;:33;:10;:33;:::i;:::-;3262:53;;;3459:14;;:37;;;:18;:37;:::i;:::-;3442:54;;3538:21;;;;;;3511:24;;;:48;3442:14;3573:25;;-1:-1:-1;3511:24:1;2944:671::o;27885:818:6:-;27999:11;;27995:702;;28057:7;:24;;;28034:19;;:47;;28026:92;;;;;-1:-1:-1;;;28026:92:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;28140:6;;:31;;;-1:-1:-1;;;28140:31:6;;-1:-1:-1;;;;;28140:31:6;;;;;;;;;;;;;;;:6;;;;;:15;;:31;;;;;;;;;;;;;;:6;;:31;;;2:2:-1;;;;27:1;24;17:12;2:2;28140:31:6;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;28140:31:6;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;28140:31:6;28132:59;;;;;-1:-1:-1;;;28132:59:6;;;;;;;;;;;;-1:-1:-1;;;28132:59:6;;;;;;;;;;;;;;;28252:24;;;;28228:19;;:49;;;:23;:49;:::i;:::-;28206:19;:71;28345:17;;28317:23;;:46;;;:27;:46;:::i;:::-;28291:23;:72;28377:37;:23;28406:7;28377:37;:28;:37;:::i;:::-;28641:17;;28660:24;;;;;28620:65;;;;;;;;;;;;28629:10;;28620:65;;;;;;;;;27885:818;;;:::o;28710:2895::-;-1:-1:-1;;;;;28858:15:6;;28815:19;28858:15;;;:7;:15;;;;;;28903:23;:11;;;:21;:23::i;:::-;28883:43;-1:-1:-1;28940:14:6;;28936:1369;;29070:16;;29349:28;;;;29334:879;29383:21;;29379:1;:25;29334:879;;;29429:37;29469:17;;;:14;:17;;;;;29697:19;;:34;-1:-1:-1;29697:34:6;29689:71;;;;;-1:-1:-1;;;29689:71:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;29868:21;;29798:8;;29841:1;29837:5;;;29864:25;;29860:192;;;29913:42;29958:17;;;:14;:17;;;;;30009:24;;-1:-1:-1;29860:192:6;30082:75;30107:9;30118:8;:13;;;30145:11;30133:9;:23;30082:24;:75::i;:::-;30070:87;-1:-1:-1;30189:9:6;;-1:-1:-1;;;29406:3:6;;29334:879;;;-1:-1:-1;30272:11:6;;;:21;30258:36;;:9;;:36;:13;:36;:::i;:::-;30227:28;;;:67;-1:-1:-1;28936:1369:6;30315:27;;;30384:21;;:58;;30441:1;30384:58;;;30437:1;30413:21;;:25;30384:58;30352:28;;;:91;;;31546:11;;;:21;31569:28;;;;31486:112;;;;;;;;;;;;;;;-1:-1:-1;;;;;31486:112:6;;;;;;;;;;;;28710:2895;;;;;:::o;1046:150:1:-;1166:22;;;;1146:15;;1116:7;;1146:43;;:15;:43;:19;:43;:::i;4355:238::-;4474:20;;:::i;:::-;4520:39;4546:4;4552:6;4520:25;:39::i;:::-;4510:49;;4569:17;4574:2;4578:7;4569:4;:17::i;:::-;4355:238;;;;;:::o;1594:220::-;1710:16;;1693:12;;:34;;;:16;:34;:::i;:::-;1678:49;;1783:23;;;;1759:19;;;;:48;;;:23;:48;:::i;:::-;1737:2;:19;;:70;;;;1594:220;;:::o;1321:134:10:-;1379:7;1405:43;1409:1;1412;1405:43;;;;;;;;;;;;;;;;;:3;:43::i;3873:224:1:-;3985:20;;:::i;:::-;4031:32;4050:4;4056:6;4031:18;:32::i;2065:230::-;2187:16;;2168:14;;:36;;;:18;:36;:::i;:::-;2151:53;;2264:23;;;;2238:21;;;;:50;;;:25;:50;:::i;6085:147:15:-;6159:7;6201:22;6205:3;6217:5;6201:3;:22::i;5401:156::-;5481:4;5504:46;5514:3;-1:-1:-1;;;;;5534:14:15;;5504:9;:46::i;5638:115::-;5701:7;5727:19;5735:3;5727:7;:19::i;26522:148:6:-;26592:19;:33;;;;;-1:-1:-1;;26592:33:6;;;;;;;;26640:23;;;;;;;;;;;;;;;;26522:148;:::o;1611:404:15:-;1674:4;1695:21;1705:3;1710:5;1695:9;:21::i;:::-;1690:319;;-1:-1:-1;27:10;;39:1;23:18;;;45:23;;1732:11:15;:23;;;;;;;;;;;;;1912:18;;1890:19;;;:12;;;:19;;;;;;:40;;;;1944:11;;1690:319;-1:-1:-1;1993:5:15;1986:12;;5173:147;5246:4;5269:44;5277:3;-1:-1:-1;;;;;5297:14:15;;5269:7;:44::i;1391:457:3:-;1494:7;1524:317;1557:260;1579:197;1619:26;1643:1;1619:22;:26::i;:::-;1667:87;1714:5;1747:6;1667:18;:87::i;:::-;1579:17;:197::i;:::-;1798:1;1557:3;:260::i;:::-;1831:9;1524:18;:317::i;:::-;1517:324;1391:457;-1:-1:-1;;;;1391:457:3:o;1746:187:10:-;1832:7;1867:12;1859:6;;;;1851:29;;;;-1:-1:-1;;;1851:29:10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;1851:29:10;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1902:5:10;;;1746:187::o;2302:635:1:-;2391:20;;:::i;:::-;2431:14;;:24;-1:-1:-1;2427:504:1;;2488:14;;:26;;2507:6;2488:26;:18;:26;:::i;:::-;2471:43;;2528:26;;;2427:504;;;2622:14;;2611:26;;:6;;:26;:10;:26;:::i;:::-;2584:24;;;:53;;;2788:21;;;;:51;;;:25;:51;:::i;:::-;2764:21;;;:75;2874:14;;2854:34;;2874:14;2902:18;;;-1:-1:-1;2854:34:1;2302:635::o;4423:201:15:-;4517:18;;4490:7;;4517:26;-1:-1:-1;4509:73:15;;;;-1:-1:-1;;;4509:73:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4599:3;:11;;4611:5;4599:18;;;;;;;;;;;;;;;;4592:25;;4423:201;;;;:::o;3776:127::-;3849:4;3872:19;;;:12;;;;;:19;;;;;;:24;;;3776:127::o;3984:107::-;4066:18;;3984:107::o;2183:1512::-;2249:4;2386:19;;;:12;;;:19;;;;;;2420:15;;2416:1273;;2849:18;;-1:-1:-1;;2801:14:15;;;;2849:22;;;;2777:21;;2849:3;;:22;;3131;;;;;;;;;;;;;;3111:42;;3274:9;3245:3;:11;;3257:13;3245:26;;;;;;;;;;;;;;;;;;;:38;;;;3349:23;;;3391:1;3349:12;;;:23;;;;;;3375:17;;;3349:43;;3498:17;;3349:3;;3498:17;;;;;;;;;;;;;;;;;;;;;;3590:3;:12;;:19;3603:5;3590:19;;;;;;;;;;;3583:26;;;3631:4;3624:11;;;;;;;;2416:1273;3673:5;3666:12;;;;;1928:134:0;1981:6;2009:18;2004:1;:23;;1995:33;;12:1:-1;9;2:12;1995:33:0;-1:-1:-1;2054:2:0;2049:7;;1928:134::o;8234:203::-;8294:6;8317;8308:16;;12:1:-1;9;2:12;8308:16:0;8330:14;8347:12;8354:1;8357;8347:5;:12::i;:::-;8330:29;-1:-1:-1;;;;;;;;;;;8374:29:0;;;;8365:39;;12:1:-1;9;2:12;3364:191:0;3421:6;3451:13;:9;;;:13;;;;;-1:-1:-1;;;;;;3479:19:0;;;;;:42;;-1:-1:-1;;;;;;3502:19:0;;;3479:42;3470:52;;12:1:-1;9;2:12;1013:371:3;1079:8;1107:26;1131:1;1107:22;:26::i;:::-;1103:30;;1144:234;1151:6;;1144:234;;1182:1;1178:5;;1177:12;1173:195;;1213:24;1232:1;1235;1213:17;:24::i;:::-;1209:28;;1260:1;1255:6;;;;1173:195;;;1304:24;1323:1;1326;1304:17;:24::i;:::-;1300:28;;1352:1;1346:7;;;;;1173:195;1144:234;;5958:455:0;6017:7;6036:6;6032:20;;-1:-1:-1;6051:1:0;6044:8;;6032:20;6073:1;6068;:6;;;;6059:16;;12:1:-1;9;2:12;6059:16:0;6096:11;;;;-1:-1:-1;;;;;6111:38:0;;6096:54;;6155:2;6095:62;;6196:3;6191:8;;;6176:24;-1:-1:-1;;;;;6216:56:0;;;6207:66;;12:1:-1;9;2:12;6207:66:0;6286:2;6279:9;6316:71;;6304:83;;;6295:93;;12:1:-1;9;2:12;6295:93:0;6401:7;;5958:455;-1:-1:-1;;;5958:455:0:o;20447:1218::-;20507:7;20531:6;20522:16;;12:1:-1;9;2:12;20522:16:0;20545:14;-1:-1:-1;;;;;20570:1:0;:55;20566:1005;;20654:1;20648:2;20643:1;:7;;20642:13;;;;;;20633:22;;20566:1005;;;20688:3;20712:8;;;20738:11;20732:17;;20728:48;;20760:2;20764:9;;;;20753;20728:48;20793:7;20787:2;:13;20783:44;;20811:2;20815:9;;;;20804;20783:44;20844:5;20838:2;:11;20834:40;;20860:1;20863:8;;;;20853;20834:40;20891:4;20885:2;:10;20881:39;;20906:1;20909:8;;;;20899;20881:39;20937:3;20931:2;:9;20927:38;;20951:1;20954:8;;;;20944;20927:38;20982:3;20976:2;:9;20972:23;;20994:1;20987:8;;;;20972:23;21081:3;21075;:9;21070:1;21066;:5;:18;;21088:1;21065:24;21057:3;21051;:9;21046:1;:14;;21045:45;;;;;;21036:54;;-1:-1:-1;;;;;21107:6:0;:44;;21098:54;;12:1:-1;9;2:12;21098:54:0;21189:3;21184:8;;;21174:19;;-1:-1:-1;;;;;21224:38:0;;21214:49;;21290:3;21285:8;;;21319:2;21314:7;;;21334;;;21330:20;;;21349:1;21343:7;;;;21330:20;21422:3;21416:9;;;;21358:8;;21437:7;;;21433:20;;;21452:1;21446:7;;;;21433:20;21461:8;;;21535:3;21529:9;;;21523:15;;21515:24;;;;21563:1;21558:2;:6;;;;;;21548:16;;;;20566:1005;;;;;;;-1:-1:-1;;;;;21586:6:0;:44;;21577:54;;12:1:-1;9;2:12;4186:197:0;4243:6;4273:13;:9;;;:13;;;;;4290:2;4273:19;-1:-1:-1;;;;;;4307:19:0;;;;;:42;;-1:-1:-1;;;;;;4330:19:0;;;4298:52;;12:1:-1;9;2:12;-1:-1;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", "source": "// SPDX-License-Identifier:Apache-2.0\n//------------------------------------------------------------------------------\n//\n// Copyright 2020 Fetch.AI Limited\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//------------------------------------------------------------------------------\n\npragma solidity ^0.6.0;\n\nimport \"../openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"../openzeppelin/contracts/access/AccessControl.sol\";\nimport \"./Finance.sol\";\n\n\n// [Canonical ERC20-FET] = 10**(-18)x[ECR20-FET]\ncontract Staking is AccessControl {\n using SafeMath for uint256;\n using AssetLib for AssetLib.Asset;\n\n struct InterestRatePerBlock {\n uint256 sinceBlock;\n // NOTE(pb): To simplify, interest rate value can *not* be negative\n uint256 rate; // Signed interest rate in [10**18] units => real_rate = rate / 10**18.\n //// Number of users who bound stake while this particular interest rate was still in effect.\n //// This enables to identify when we can delete interest rates which are no more used by anyone\n //// (continuously from the beginning).\n //uint256 numberOfRegisteredUsers;\n }\n\n struct Stake {\n uint256 sinceBlock;\n uint256 sinceInterestRateIndex;\n AssetLib.Asset asset;\n }\n\n struct LockedAsset {\n uint256 liquidSinceBlock;\n AssetLib.Asset asset;\n }\n\n struct Locked {\n AssetLib.Asset aggregate;\n LockedAsset[] assets;\n }\n\n // ******* EVENTS ********\n event BindStake(\n address indexed stakerAddress\n , uint256 indexed sinceInterestRateIndex\n , uint256 principal\n , uint256 compoundInterest\n );\n\n /**\n * @dev This event is triggered exclusivelly to recalculate the compount interest of ALREADY staked asset\n * for the poriod since it was calculated the last time. This means this event does *NOT* include *YET*\n * any added (resp. removed) asset user is currently binding (resp. unbinding).\n * The main motivation for this event is to give listener opportunity to get feedback what is the \n * user's staked asset value with compound interrest recalculated to *CURRENT* block *BEFORE* user's\n * action (binding resp. unbinding) affects user's staked asset value.\n */\n event StakeCompoundInterest(\n address indexed stakerAddress\n , uint256 indexed sinceInterestRateIndex\n , uint256 principal // = previous_principal\n , uint256 compoundInterest // = previous_principal * (pow(1+interest, _getBlockNumber()-since_block) - 1)\n );\n\n event LiquidityDeposited(\n address indexed stakerAddress\n , uint256 amount\n );\n\n event LiquidityUnlocked(\n address indexed stakerAddress\n , uint256 principal\n , uint256 compoundInterest\n );\n\n event UnbindStake(\n address indexed stakerAddress\n , uint256 indexed liquidSinceBlock\n , uint256 principal\n , uint256 compoundInterest\n );\n\n event NewInterestRate(\n uint256 indexed index\n , uint256 rate // Signed interest rate in [10**18] units => real_rate = rate / 10**18\n );\n\n event Withdraw(\n address indexed stakerAddress\n , uint256 principal\n , uint256 compoundInterest\n );\n\n event LockPeriod(uint64 numOfBlocks);\n event Pause(uint256 sinceBlock);\n event TokenWithdrawal(address targetAddress, uint256 amount);\n event ExcessTokenWithdrawal(address targetAddress, uint256 amount);\n event RewardsPoolTokenTopUp(address sender, uint256 amount);\n event RewardsPoolTokenWithdrawal(address targetAddress, uint256 amount);\n event DeleteContract();\n\n\n bytes32 public constant DELEGATE_ROLE = keccak256(\"DELEGATE_ROLE\");\n uint256 public constant DELETE_PROTECTION_PERIOD = 370285;// 60*24*60*60[s] / (14[s/block]) = 370285[block];\n\n IERC20 public _token;\n\n // NOTE(pb): This needs to be either completely replaced by multisig concept,\n // or at least joined with multisig.\n // This contract does not have, by-design on conceptual level, any clearly defined repeating\n // life-cycle behaviour (for instance: `initialise -> staking-period -> locked-period` cycle\n // with clear start & end of each life-cycle. Life-cycle of this contract is single monolithic\n // period `creation -> delete-contract`, where there is no clear place where to `update` the\n // earliest deletion block value, thus it would need to be set once at the contract creation\n // point what completely defeats the protection by time delay.\n uint256 public _earliestDelete;\n \n uint256 public _pausedSinceBlock;\n uint64 public _lockPeriodInBlocks;\n\n // Represents amount of reward funds which are dedicated to cover accrued compound interest during user withdrawals.\n uint256 public _rewardsPoolBalance;\n // Accumulated global value of all principals (from all users) currently held in this contract (liquid, bound and locked).\n uint256 public _accruedGlobalPrincipal;\n AssetLib.Asset public _accruedGlobalLiquidity; // Exact\n AssetLib.Asset public _accruedGlobalLocked; // Exact\n\n uint256 public _interestRatesStartIdx;\n uint256 public _interestRatesNextIdx;\n mapping(uint256 => InterestRatePerBlock) public _interestRates;\n\n mapping(address => Stake) _stakes;\n mapping(address => Locked) _locked;\n mapping(address => AssetLib.Asset) public _liquidity;\n\n\n /* Only callable by owner */\n modifier onlyOwner() {\n require(_isOwner(), \"Caller is not an owner\");\n _;\n }\n\n /* Only callable by owner or delegate */\n modifier onlyDelegate() {\n require(_isOwner() || hasRole(DELEGATE_ROLE, msg.sender), \"Caller is neither owner nor delegate\");\n _;\n }\n\n modifier verifyTxExpiration(uint256 expirationBlock) {\n require(_getBlockNumber() <= expirationBlock, \"Transaction expired\");\n _;\n }\n\n modifier verifyNotPaused() {\n require(_pausedSinceBlock > _getBlockNumber(), \"Contract has been paused\");\n _;\n }\n\n\n /*******************\n Contract start\n *******************/\n /**\n * @param ERC20Address address of the ERC20 contract\n */\n constructor(\n address ERC20Address\n , uint256 interestRatePerBlock\n , uint256 pausedSinceBlock\n , uint64 lockPeriodInBlocks) \n public \n {\n _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);\n\n _token = IERC20(ERC20Address);\n _earliestDelete = _getBlockNumber().add(DELETE_PROTECTION_PERIOD);\n \n // NOTE(pb): Unnecessary initialisations, shall be done implicitly by VM\n //_interestRatesStartIdx = 0;\n //_interestRatesNextIdx = 0;\n //_rewardsPoolBalance = 0;\n //_accruedGlobalPrincipal = 0;\n //_accruedGlobalLiquidity = 0;\n //_accruedGlobalLocked = 0;\n\n _updateLockPeriod(lockPeriodInBlocks);\n _addInterestRate(interestRatePerBlock);\n _pauseSince(pausedSinceBlock /* uint256(0) */);\n }\n\n\n /**\n * @notice Add new interest rate in to the ordered container of previously added interest rates\n * @param rate - signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18\n * @param expirationBlock - block number beyond which is the carrier Tx considered expired, and so rejected.\n * This is for protection of Tx sender to exactly define lifecycle length of the Tx,\n * and so avoiding uncertainty of how long Tx sender needs to wait for Tx processing.\n * Tx can be withheld\n * @dev expiration period\n */\n function addInterestRate(\n uint256 rate,\n uint256 expirationBlock\n )\n external\n onlyDelegate()\n verifyTxExpiration(expirationBlock)\n {\n _addInterestRate(rate);\n }\n\n\n function deposit(\n uint256 amount,\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n verifyNotPaused\n {\n bool makeTransfer = amount != 0;\n if (makeTransfer) {\n require(_token.transferFrom(msg.sender, address(this), amount), \"Transfer failed\");\n _accruedGlobalPrincipal = _accruedGlobalPrincipal.add(amount);\n _accruedGlobalLiquidity.principal = _accruedGlobalLiquidity.principal.add(amount);\n emit LiquidityDeposited(msg.sender, amount);\n }\n\n uint256 curr_block = _getBlockNumber();\n (, AssetLib.Asset storage liquidity,) = _collectLiquidity(msg.sender, curr_block);\n\n if (makeTransfer) {\n liquidity.principal = liquidity.principal.add(amount);\n }\n }\n\n\n /**\n * @notice Withdraws amount from sender' available liquidity pool back to sender address,\n * preferring withdrawal from compound interest dimension of liquidity.\n *\n * @param amount - value to withdraw\n *\n * @dev public access\n */\n function withdraw(\n uint256 amount,\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n verifyNotPaused\n {\n address sender = msg.sender;\n uint256 curr_block = _getBlockNumber();\n (, AssetLib.Asset storage liquidity,) = _collectLiquidity(sender, curr_block);\n\n AssetLib.Asset memory _amount = liquidity.iSubCompoundInterestFirst(amount);\n _finaliseWithdraw(sender, _amount, amount);\n }\n\n\n /**\n * @notice Withdraws *WHOLE* compound interest amount available to sender.\n *\n * @dev public access\n */\n function withdrawPrincipal(\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n verifyNotPaused\n {\n address sender = msg.sender;\n uint256 curr_block = _getBlockNumber();\n (, AssetLib.Asset storage liquidity, ) = _collectLiquidity(sender, curr_block);\n\n AssetLib.Asset memory _amount;\n _amount.principal = liquidity.principal;\n liquidity.principal = 0;\n\n _finaliseWithdraw(sender, _amount, _amount.principal);\n }\n\n\n /**\n * @notice Withdraws *WHOLE* compound interest amount available to sender.\n *\n * @dev public access\n */\n function withdrawCompoundInterest(\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n verifyNotPaused\n {\n address sender = msg.sender;\n uint256 curr_block = _getBlockNumber();\n (, AssetLib.Asset storage liquidity, ) = _collectLiquidity(sender, curr_block);\n\n AssetLib.Asset memory _amount;\n _amount.compoundInterest = liquidity.compoundInterest;\n liquidity.compoundInterest = 0;\n\n _finaliseWithdraw(sender, _amount, _amount.compoundInterest);\n }\n\n\n /**\n * @notice Withdraws whole liquidity available to sender back to sender' address,\n *\n * @dev public access\n */\n function withdrawWholeLiquidity(\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n verifyNotPaused\n {\n address sender = msg.sender;\n uint256 curr_block = _getBlockNumber();\n (, AssetLib.Asset storage liquidity, ) = _collectLiquidity(sender, curr_block);\n\n _finaliseWithdraw(sender, liquidity, liquidity.composite());\n liquidity.compoundInterest = 0;\n liquidity.principal = 0;\n }\n\n\n function bindStake(\n uint256 amount,\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n verifyNotPaused\n {\n require(amount != 0, \"Amount must be higher than zero\");\n\n uint256 curr_block = _getBlockNumber();\n\n (, AssetLib.Asset storage liquidity, ) = _collectLiquidity(msg.sender, curr_block);\n\n //// NOTE(pb): Strictly speaking, the following check is not necessary, since the requirement will be checked\n //// during the `iRelocatePrincipalFirst(...)` method code flow (see bellow).\n //uint256 composite = liquidity.composite();\n //require(amount <= composite, \"Insufficient liquidity.\");\n\n Stake storage stake = _updateStakeCompoundInterest(msg.sender, curr_block);\n AssetLib.Asset memory _amount = liquidity.iRelocatePrincipalFirst(stake.asset, amount);\n _accruedGlobalLiquidity.iSub(_amount);\n\n //// NOTE(pb): Emitting only info about Tx input `amount` value, decomposed to principal & compound interest\n //// coordinates based on liquidity available.\n //if (amount > 0) {\n emit BindStake(msg.sender, stake.sinceInterestRateIndex, _amount.principal, _amount.compoundInterest);\n //}\n }\n\n\n /**\n * @notice Unbinds amount from the stake of sender of the transaction,\n * and *LOCKS* it for number of blocks defined by value of the\n * `_lockPeriodInBlocks` state of this contract at the point\n * of this call.\n * The locked amount can *NOT* be withdrawn from the contract\n * *BEFORE* the lock period ends.\n *\n * Unbinding (=calling this method) also means, that compound\n * interest will be calculated for period since la.\n *\n * @param amount - value to un-bind from the stake\n * If `amount=0` then the **WHOLE** stake (including\n * compound interest) will be unbound.\n *\n * @dev public access\n */\n function unbindStake(\n uint256 amount, //NOTE: If zero, then all stake is withdrawn\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n verifyNotPaused\n {\n uint256 curr_block = _getBlockNumber();\n address sender = msg.sender;\n Stake storage stake = _updateStakeCompoundInterest(sender, curr_block);\n\n uint256 stake_composite = stake.asset.composite();\n AssetLib.Asset memory _amount;\n\n if (amount > 0) {\n // TODO(pb): Failing this way is expensive (causing rollback of state change).\n // It would be beneficial to retain newly calculated liquidity value\n // in to the state, thus the invested calculation would not come to wain.\n // However that comes with another implication - this would need\n // to return status/error code instead of reverting = caller MUST actually\n // check the return value, what might be trap for callers who do not expect\n // this behaviour (Tx execution passed , but in fact the essential feature\n // has not been fully executed).\n require(amount <= stake_composite, \"Amount is higher than stake\");\n\n if (_lockPeriodInBlocks == 0) {\n _amount = stake.asset.iRelocateCompoundInterestFirst(_liquidity[sender], amount);\n _accruedGlobalLiquidity.iAdd(_amount);\n emit UnbindStake(sender, curr_block, _amount.principal, _amount.compoundInterest);\n emit LiquidityUnlocked(sender, _amount.principal, _amount.compoundInterest);\n } else {\n Locked storage locked = _locked[sender];\n LockedAsset storage newLockedAsset = locked.assets.push();\n newLockedAsset.liquidSinceBlock = curr_block.add(_lockPeriodInBlocks);\n _amount = stake.asset.iRelocateCompoundInterestFirst(newLockedAsset.asset, amount);\n\n _accruedGlobalLocked.iAdd(_amount);\n locked.aggregate.iAdd(_amount);\n\n // NOTE: Emitting only info about Tx input values, not resulting compound values\n emit UnbindStake(sender, newLockedAsset.liquidSinceBlock, _amount.principal, _amount.compoundInterest);\n }\n } else {\n if (stake_composite == 0) {\n // NOTE(pb): Nothing to do\n return;\n }\n\n _amount = stake.asset;\n stake.asset.principal = 0;\n stake.asset.compoundInterest = 0;\n\n if (_lockPeriodInBlocks == 0) {\n _liquidity[sender].iAdd(_amount);\n _accruedGlobalLiquidity.iAdd(_amount);\n emit UnbindStake(sender, curr_block, _amount.principal, _amount.compoundInterest);\n emit LiquidityUnlocked(sender, _amount.principal, _amount.compoundInterest);\n } else {\n Locked storage locked = _locked[sender];\n LockedAsset storage newLockedAsset = locked.assets.push();\n newLockedAsset.liquidSinceBlock = curr_block.add(_lockPeriodInBlocks);\n newLockedAsset.asset = _amount;\n\n _accruedGlobalLocked.iAdd(_amount);\n locked.aggregate.iAdd(_amount);\n\n // NOTE: Emitting only info about Tx input values, not resulting compound values\n emit UnbindStake(msg.sender, newLockedAsset.liquidSinceBlock, newLockedAsset.asset.principal, newLockedAsset.asset.compoundInterest);\n }\n }\n }\n\n\n function getRewardsPoolBalance() external view returns(uint256) {\n return _rewardsPoolBalance;\n }\n\n\n function getEarliestDeleteBlock() external view returns(uint256) {\n return _earliestDelete;\n }\n\n\n function getNumberOfLockedAssetsForUser(address forAddress) external view returns(uint256 length) {\n length = _locked[forAddress].assets.length;\n }\n\n\n function getLockedAssetsAggregateForUser(address forAddress) external view returns(uint256 principal, uint256 compoundInterest) {\n AssetLib.Asset storage aggregate = _locked[forAddress].aggregate;\n return (aggregate.principal, aggregate.compoundInterest);\n }\n\n\n /**\n * @dev Returns locked assets decomposed in to 3 separate arrays (principal, compound interest, liquid since block)\n * NOTE(pb): This method might be quite expensive, depending on size of locked assets\n */\n function getLockedAssetsForUser(address forAddress)\n external view\n returns(uint256[] memory principal, uint256[] memory compoundInterest, uint256[] memory liquidSinceBlock)\n {\n LockedAsset[] storage lockedAssets = _locked[forAddress].assets;\n uint256 length = lockedAssets.length;\n if (length != 0) {\n principal = new uint256[](length);\n compoundInterest = new uint256[](length);\n liquidSinceBlock = new uint256[](length);\n\n for (uint256 i=0; i < length; ++i) {\n LockedAsset storage la = lockedAssets[i];\n AssetLib.Asset storage a = la.asset;\n principal[i] = a.principal;\n compoundInterest[i] = a.compoundInterest;\n liquidSinceBlock[i] = la.liquidSinceBlock;\n }\n }\n }\n\n\n function getStakeForUser(address forAddress) external view returns(uint256 principal, uint256 compoundInterest, uint256 sinceBlock, uint256 sinceInterestRateIndex) {\n Stake storage stake = _stakes[forAddress];\n principal = stake.asset.principal;\n compoundInterest = stake.asset.compoundInterest;\n sinceBlock = stake.sinceBlock;\n sinceInterestRateIndex = stake.sinceInterestRateIndex;\n }\n\n\n /**\n @dev Even though this is considered as administrative action (is not affected by\n by contract paused state, it can be executed by anyone who wishes to\n top-up the rewards pool (funds are sent in to contract, *not* the other way around).\n The Rewards Pool is exclusively dedicated to cover withdrawals of user' compound interest,\n which is effectively the reward.\n */\n function topUpRewardsPool(\n uint256 amount,\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n {\n if (amount == 0) {\n return;\n }\n\n require(_token.transferFrom(msg.sender, address(this), amount), \"Transfer failed\");\n _rewardsPoolBalance = _rewardsPoolBalance.add(amount);\n emit RewardsPoolTokenTopUp(msg.sender, amount);\n }\n\n\n /**\n * @notice Updates Lock Period value\n * @param numOfBlocks length of the lock period\n * @dev Delegate only\n */\n function updateLockPeriod(uint64 numOfBlocks, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyDelegate\n {\n _updateLockPeriod(numOfBlocks);\n }\n\n\n /**\n * @notice Pauses all NON-administrative interaction with the contract since the specidfed block number \n * @param blockNumber block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)\n * @dev Delegate only\n */\n function pauseSince(uint256 blockNumber, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyDelegate\n {\n _pauseSince(blockNumber);\n }\n\n\n /**\n * @dev Withdraw tokens from rewards pool.\n *\n * @param amount : amount to withdraw.\n * If `amount == 0` then whole amount in rewards pool will be withdrawn.\n * @param targetAddress : address to send tokens to\n */\n function withdrawFromRewardsPool(uint256 amount, address payable targetAddress,\n uint256 txExpirationBlock\n )\n external\n verifyTxExpiration(txExpirationBlock)\n onlyOwner\n {\n if (amount == 0) {\n amount = _rewardsPoolBalance;\n } else {\n require(amount <= _rewardsPoolBalance, \"Amount higher than rewards pool\");\n }\n\n // NOTE(pb): Strictly speaking, consistency check in following lines is not necessary,\n // the if-else code above guarantees that everything is alright:\n uint256 contractBalance = _token.balanceOf(address(this));\n uint256 expectedMinContractBalance = _accruedGlobalPrincipal.add(amount);\n require(expectedMinContractBalance <= contractBalance, \"Contract inconsistency.\");\n\n require(_token.transfer(targetAddress, amount), \"Not enough funds on contr. addr.\");\n\n // NOTE(pb): No need for SafeMath.sub since the overflow is checked in the if-else code above.\n _rewardsPoolBalance -= amount;\n\n emit RewardsPoolTokenWithdrawal(targetAddress, amount);\n }\n\n\n /**\n * @dev Withdraw \"excess\" tokens, which were sent to contract directly via direct ERC20.transfer(...),\n * without interacting with API of this (Staking) contract, what could be done only by mistake.\n * Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such\n * \"excess\" tokens out of contract.\n * @param targetAddress : address to send tokens to\n * @param txExpirationBlock : block number until which is the transaction valid (inclusive).\n * When transaction is processed after this block, it fails.\n */\n function withdrawExcessTokens(address payable targetAddress, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyOwner\n {\n uint256 contractBalance = _token.balanceOf(address(this));\n uint256 expectedMinContractBalance = _accruedGlobalPrincipal.add(_rewardsPoolBalance);\n // NOTE(pb): The following subtraction shall *fail* (revert) IF the contract is in *INCONSISTENT* state,\n // = when contract balance is less than minial expected balance:\n uint256 excessAmount = contractBalance.sub(expectedMinContractBalance);\n require(_token.transfer(targetAddress, excessAmount), \"Not enough funds on contr. addr.\");\n emit ExcessTokenWithdrawal(targetAddress, excessAmount);\n }\n\n\n /**\n * @notice Delete the contract, transfers the remaining token and ether balance to the specified\n payoutAddress\n * @param payoutAddress address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\n * @dev owner only + only on or after `_earliestDelete` block\n */\n function deleteContract(address payable payoutAddress, uint256 txExpirationBlock)\n external\n verifyTxExpiration(txExpirationBlock)\n onlyOwner\n {\n require(_earliestDelete >= _getBlockNumber(), \"Earliest delete not reached\");\n uint256 contractBalance = _token.balanceOf(address(this));\n require(_token.transfer(payoutAddress, contractBalance));\n emit DeleteContract();\n selfdestruct(payoutAddress);\n }\n \n\n // **********************************************************\n // ****************** INTERNAL METHODS *****************\n\n\n /**\n * @dev VIRTUAL Method returning bock number. Introduced for \n * testing purposes (allows mocking).\n */\n function _getBlockNumber() internal view virtual returns(uint256)\n {\n return block.number;\n }\n\n\n function _isOwner() internal view returns(bool) {\n return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);\n }\n\n\n /**\n * @notice Add new interest rate in to the ordered container of previously added interest rates\n * @param rate - signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18\n */\n function _addInterestRate(uint256 rate) internal \n {\n uint256 idx = _interestRatesNextIdx;\n _interestRates[idx] = InterestRatePerBlock({\n sinceBlock: _getBlockNumber()\n , rate: rate\n //,numberOfRegisteredUsers: 0\n });\n _interestRatesNextIdx = _interestRatesNextIdx.add(1);\n\n emit NewInterestRate(idx, rate);\n }\n\n\n /**\n * @notice Updates Lock Period value\n * @param numOfBlocks length of the lock period\n */\n function _updateLockPeriod(uint64 numOfBlocks) internal\n {\n _lockPeriodInBlocks = numOfBlocks;\n emit LockPeriod(numOfBlocks);\n }\n\n\n /**\n * @notice Pauses all NON-administrative interaction with the contract since the specidfed block number \n * @param blockNumber block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)\n */\n function _pauseSince(uint256 blockNumber) internal \n {\n uint256 currentBlockNumber = _getBlockNumber();\n _pausedSinceBlock = blockNumber < currentBlockNumber ? currentBlockNumber : blockNumber;\n emit Pause(_pausedSinceBlock);\n }\n\n\n /**\n * @notice Withdraws amount from sender' available liquidity pool back to sender address,\n * preferring withdrawal from compound interest dimension of liquidity.\n *\n * @param amount - value to withdraw\n *\n * @dev NOTE(pb): Passing redundant `uint256 amount` (on top of the `Asset _amount`) in the name\n * of performance to avoid calculating it again from `_amount` (or the other way around).\n * IMPLICATION: Caller **MUST** pass correct values, ensuring that `amount == _amount.composite()`,\n * since this private method is **NOT** verifying this condition due to performance reasons.\n */\n function _finaliseWithdraw(address sender, AssetLib.Asset memory _amount, uint256 amount) internal {\n if (amount != 0) {\n require(_rewardsPoolBalance >= _amount.compoundInterest, \"Not enough funds in rewards pool\");\n require(_token.transfer(sender, amount), \"Transfer failed\");\n\n _rewardsPoolBalance = _rewardsPoolBalance.sub(_amount.compoundInterest);\n _accruedGlobalPrincipal = _accruedGlobalPrincipal.sub(_amount.principal);\n _accruedGlobalLiquidity.iSub(_amount);\n\n // NOTE(pb): Emitting only info about Tx input `amount` value, decomposed to principal & compound interest\n // coordinates based on liquidity available.\n emit Withdraw(msg.sender, _amount.principal, _amount.compoundInterest);\n }\n }\n\n\n function _updateStakeCompoundInterest(address sender, uint256 at_block)\n internal\n returns(Stake storage stake)\n {\n stake = _stakes[sender];\n uint256 composite = stake.asset.composite();\n if (composite != 0)\n {\n // TODO(pb): There is more effective algorithm than this.\n uint256 start_block = stake.sinceBlock;\n // NOTE(pb): Probability of `++i` or `j=i+1` overflowing is limitly approaching zero,\n // since we would need to create `(1<<256)-1`, resp `1<<256)-2`, number of interrest rates in order to reach the overflow\n for (uint256 i=stake.sinceInterestRateIndex; i < _interestRatesNextIdx; ++i) {\n InterestRatePerBlock storage interest = _interestRates[i];\n // TODO(pb): It is not strictly necessary to do this assert, and rather fully rely\n // on correctness of `addInterestRate(...)` implementation.\n require(interest.sinceBlock <= start_block, \"sinceBlock inconsistency\");\n uint256 end_block = at_block;\n\n uint256 j = i + 1;\n if (j < _interestRatesNextIdx) {\n InterestRatePerBlock storage next_interest = _interestRates[j];\n end_block = next_interest.sinceBlock;\n }\n\n composite = Finance.compoundInterest(composite, interest.rate, end_block - start_block);\n start_block = end_block;\n }\n\n stake.asset.compoundInterest = composite.sub(stake.asset.principal);\n }\n\n stake.sinceBlock = at_block;\n stake.sinceInterestRateIndex = (_interestRatesNextIdx != 0 ? _interestRatesNextIdx - 1 : 0);\n // TODO(pb): Careful: The `StakeCompoundInterest` event doers not carry explicit block number value - it relies\n // on the fact that Event implicitly carries value block.number where the event has been triggered,\n // what however can be different than value of the `at_block` input parameter passed in.\n // Thus this method needs to be EITHER refactored to drop the `at_block` parameter (and so get the\n // value internally by calling the `_getBlockNumber()` method), OR the `StakeCompoundInterest` event\n // needs to be extended to include the `uint256 sinceBlock` attribute.\n // The original reason for passing the `at_block` parameter was to spare gas for calling the\n // `_getBlockNumber()` method twice (by the caller of this method + by this method), what might NOT be\n // relevant anymore (after refactoring), since caller might not need to use the block number value anymore.\n emit StakeCompoundInterest(sender, stake.sinceInterestRateIndex, stake.asset.principal, stake.asset.compoundInterest);\n }\n\n\n function _collectLiquidity(address sender, uint256 at_block)\n internal\n returns(AssetLib.Asset memory unlockedLiquidity, AssetLib.Asset storage liquidity, bool collected)\n {\n Locked storage locked = _locked[sender];\n LockedAsset[] storage lockedAssets = locked.assets;\n liquidity = _liquidity[sender];\n\n for (uint256 i=0; i < lockedAssets.length; ) {\n LockedAsset memory l = lockedAssets[i];\n\n if (l.liquidSinceBlock > at_block) {\n ++i; // NOTE(pb): Probability of overflow is zero, what is ensured by condition in this for cycle.\n continue;\n }\n\n unlockedLiquidity.principal = unlockedLiquidity.principal.add(l.asset.principal);\n // NOTE(pb): The following can potentially overflow, since accrued compound interest can be high, depending on values on sequence of interest rates & length of compounding intervals involved.\n unlockedLiquidity.compoundInterest = unlockedLiquidity.compoundInterest.add(l.asset.compoundInterest);\n\n // Copying last element of the array in to the current one,\n // so that the last one can be popped out of the array.\n // NOTE(pb): Probability of overflow during `-` operation is zero, what is ensured by condition in this for cycle.\n uint256 last_idx = lockedAssets.length - 1;\n if (i != last_idx) {\n lockedAssets[i] = lockedAssets[last_idx];\n }\n // TODO(pb): It will be cheaper (GAS consumption-wise) to simply leave\n // elements in array (do NOT delete them) and rather store \"amortised\"\n // size of the array in secondary separate store variable (= do NOT\n // use `array.length` as primary indication of array length).\n // Destruction of the array items is expensive. Excess of \"allocated\"\n // array storage can be left temporarily (or even permanently) unused.\n lockedAssets.pop();\n }\n\n // TODO(pb): This should not be necessary.\n if (lockedAssets.length == 0) {\n delete _locked[sender];\n }\n\n collected = unlockedLiquidity.principal != 0 || unlockedLiquidity.compoundInterest != 0;\n if (collected) {\n emit LiquidityUnlocked(sender, unlockedLiquidity.principal, unlockedLiquidity.compoundInterest);\n\n _accruedGlobalLocked.iSub(unlockedLiquidity);\n if (lockedAssets.length != 0) {\n locked.aggregate.iSub(unlockedLiquidity);\n }\n\n _accruedGlobalLiquidity.iAdd(unlockedLiquidity);\n\n liquidity.iAdd(unlockedLiquidity);\n }\n }\n\n}\n", "sourcePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Staking.sol", "ast": { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Staking.sol", "exportedSymbols": { "Staking": [ 5490 ] }, "id": 5491, "license": "Apache-2.0", "nodeType": "SourceUnit", "nodes": [ { "id": 3625, "literals": [ "solidity", "^", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "820:23:6" }, { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/token/ERC20/IERC20.sol", "file": "../openzeppelin/contracts/token/ERC20/IERC20.sol", "id": 3626, "nodeType": "ImportDirective", "scope": 5491, "sourceUnit": 6708, "src": "845:58:6", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/access/AccessControl.sol", "file": "../openzeppelin/contracts/access/AccessControl.sol", "id": 3627, "nodeType": "ImportDirective", "scope": 5491, "sourceUnit": 5842, "src": "904:60:6", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Finance.sol", "file": "./Finance.sol", "id": 3628, "nodeType": "ImportDirective", "scope": 5491, "sourceUnit": 3319, "src": "965:23:6", "symbolAliases": [], "unitAlias": "" }, { "abstract": false, "baseContracts": [ { "arguments": null, "baseName": { "contractScope": null, "id": 3629, "name": "AccessControl", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 5841, "src": "1060:13:6", "typeDescriptions": { "typeIdentifier": "t_contract$_AccessControl_$5841", "typeString": "contract AccessControl" } }, "id": 3630, "nodeType": "InheritanceSpecifier", "src": "1060:13:6" } ], "contractDependencies": [ 5558, 5841 ], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 5490, "linearizedBaseContracts": [ 5490, 5841, 5558 ], "name": "Staking", "nodeType": "ContractDefinition", "nodes": [ { "id": 3633, "libraryName": { "contractScope": null, "id": 3631, "name": "SafeMath", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 6037, "src": "1086:8:6", "typeDescriptions": { "typeIdentifier": "t_contract$_SafeMath_$6037", "typeString": "library SafeMath" } }, "nodeType": "UsingForDirective", "src": "1080:27:6", "typeName": { "id": 3632, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1099:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } }, { "id": 3636, "libraryName": { "contractScope": null, "id": 3634, "name": "AssetLib", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3121, "src": "1118:8:6", "typeDescriptions": { "typeIdentifier": "t_contract$_AssetLib_$3121", "typeString": "library AssetLib" } }, "nodeType": "UsingForDirective", "src": "1112:34:6", "typeName": { "contractScope": null, "id": 3635, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1131:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } } }, { "canonicalName": "Staking.InterestRatePerBlock", "id": 3641, "members": [ { "constant": false, "id": 3638, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3641, "src": "1190:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3637, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1190:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3640, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3641, "src": "1294:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3639, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1294:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "name": "InterestRatePerBlock", "nodeType": "StructDefinition", "scope": 5490, "src": "1152:531:6", "visibility": "public" }, { "canonicalName": "Staking.Stake", "id": 3648, "members": [ { "constant": false, "id": 3643, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3648, "src": "1712:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3642, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1712:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3645, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3648, "src": "1740:30:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3644, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1740:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3647, "mutability": "mutable", "name": "asset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3648, "src": "1780:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3646, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1780:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "name": "Stake", "nodeType": "StructDefinition", "scope": 5490, "src": "1689:118:6", "visibility": "public" }, { "canonicalName": "Staking.LockedAsset", "id": 3653, "members": [ { "constant": false, "id": 3650, "mutability": "mutable", "name": "liquidSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3653, "src": "1842:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3649, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1842:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3652, "mutability": "mutable", "name": "asset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3653, "src": "1876:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3651, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1876:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "name": "LockedAsset", "nodeType": "StructDefinition", "scope": 5490, "src": "1813:90:6", "visibility": "public" }, { "canonicalName": "Staking.Locked", "id": 3659, "members": [ { "constant": false, "id": 3655, "mutability": "mutable", "name": "aggregate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3659, "src": "1933:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3654, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1933:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3658, "mutability": "mutable", "name": "assets", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3659, "src": "1967:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" }, "typeName": { "baseType": { "contractScope": null, "id": 3656, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "1967:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "id": 3657, "length": null, "nodeType": "ArrayTypeName", "src": "1967:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" } }, "value": null, "visibility": "internal" } ], "name": "Locked", "nodeType": "StructDefinition", "scope": 5490, "src": "1909:85:6", "visibility": "public" }, { "anonymous": false, "documentation": null, "id": 3669, "name": "BindStake", "nodeType": "EventDefinition", "parameters": { "id": 3668, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3661, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2064:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3660, "name": "address", "nodeType": "ElementaryTypeName", "src": "2064:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3663, "indexed": true, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2104:38:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3662, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2104:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3665, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2153:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3664, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2153:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3667, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2181:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3666, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2181:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "2052:159:6" }, "src": "2037:175:6" }, { "anonymous": false, "documentation": { "id": 3670, "nodeType": "StructuredDocumentation", "src": "2218:621:6", "text": "@dev This event is triggered exclusivelly to recalculate the compount interest of ALREADY staked asset\n for the poriod since it was calculated the last time. This means this event does *NOT* include *YET*\n any added (resp. removed) asset user is currently binding (resp. unbinding).\n The main motivation for this event is to give listener opportunity to get feedback what is the \n user's staked asset value with compound interrest recalculated to *CURRENT* block *BEFORE* user's\n action (binding resp. unbinding) affects user's staked asset value." }, "id": 3680, "name": "StakeCompoundInterest", "nodeType": "EventDefinition", "parameters": { "id": 3679, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3672, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "2883:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3671, "name": "address", "nodeType": "ElementaryTypeName", "src": "2883:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3674, "indexed": true, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "2923:38:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3673, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2923:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3676, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "2972:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3675, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2972:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3678, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "3024:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3677, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3024:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "2871:262:6" }, "src": "2844:290:6" }, { "anonymous": false, "documentation": null, "id": 3686, "name": "LiquidityDeposited", "nodeType": "EventDefinition", "parameters": { "id": 3685, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3682, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3686, "src": "3176:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3681, "name": "address", "nodeType": "ElementaryTypeName", "src": "3176:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3684, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3686, "src": "3216:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3683, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3216:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3164:72:6" }, "src": "3140:97:6" }, { "anonymous": false, "documentation": null, "id": 3694, "name": "LiquidityUnlocked", "nodeType": "EventDefinition", "parameters": { "id": 3693, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3688, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3694, "src": "3278:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3687, "name": "address", "nodeType": "ElementaryTypeName", "src": "3278:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3690, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3694, "src": "3318:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3689, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3318:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3692, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3694, "src": "3346:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3691, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3346:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3266:110:6" }, "src": "3243:134:6" }, { "anonymous": false, "documentation": null, "id": 3704, "name": "UnbindStake", "nodeType": "EventDefinition", "parameters": { "id": 3703, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3696, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3412:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3695, "name": "address", "nodeType": "ElementaryTypeName", "src": "3412:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3698, "indexed": true, "mutability": "mutable", "name": "liquidSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3452:32:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3697, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3452:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3700, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3495:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3699, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3495:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3702, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3523:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3701, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3523:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3400:153:6" }, "src": "3383:171:6" }, { "anonymous": false, "documentation": null, "id": 3710, "name": "NewInterestRate", "nodeType": "EventDefinition", "parameters": { "id": 3709, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3706, "indexed": true, "mutability": "mutable", "name": "index", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3710, "src": "3593:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3705, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3593:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3708, "indexed": false, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3710, "src": "3625:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3707, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3625:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3581:133:6" }, "src": "3560:155:6" }, { "anonymous": false, "documentation": null, "id": 3718, "name": "Withdraw", "nodeType": "EventDefinition", "parameters": { "id": 3717, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3712, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3718, "src": "3747:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3711, "name": "address", "nodeType": "ElementaryTypeName", "src": "3747:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3714, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3718, "src": "3787:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3713, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3787:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3716, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3718, "src": "3815:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3715, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3815:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3735:110:6" }, "src": "3721:125:6" }, { "anonymous": false, "documentation": null, "id": 3722, "name": "LockPeriod", "nodeType": "EventDefinition", "parameters": { "id": 3721, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3720, "indexed": false, "mutability": "mutable", "name": "numOfBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3722, "src": "3869:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 3719, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "3869:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" } ], "src": "3868:20:6" }, "src": "3852:37:6" }, { "anonymous": false, "documentation": null, "id": 3726, "name": "Pause", "nodeType": "EventDefinition", "parameters": { "id": 3725, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3724, "indexed": false, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3726, "src": "3906:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3723, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3906:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3905:20:6" }, "src": "3894:32:6" }, { "anonymous": false, "documentation": null, "id": 3732, "name": "TokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 3731, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3728, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3732, "src": "3953:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3727, "name": "address", "nodeType": "ElementaryTypeName", "src": "3953:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3730, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3732, "src": "3976:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3729, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3976:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3952:39:6" }, "src": "3931:61:6" }, { "anonymous": false, "documentation": null, "id": 3738, "name": "ExcessTokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 3737, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3734, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3738, "src": "4025:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3733, "name": "address", "nodeType": "ElementaryTypeName", "src": "4025:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3736, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3738, "src": "4048:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3735, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4048:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4024:39:6" }, "src": "3997:67:6" }, { "anonymous": false, "documentation": null, "id": 3744, "name": "RewardsPoolTokenTopUp", "nodeType": "EventDefinition", "parameters": { "id": 3743, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3740, "indexed": false, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3744, "src": "4097:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3739, "name": "address", "nodeType": "ElementaryTypeName", "src": "4097:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3742, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3744, "src": "4113:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3741, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4113:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4096:32:6" }, "src": "4069:60:6" }, { "anonymous": false, "documentation": null, "id": 3750, "name": "RewardsPoolTokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 3749, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3746, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3750, "src": "4167:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3745, "name": "address", "nodeType": "ElementaryTypeName", "src": "4167:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3748, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3750, "src": "4190:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3747, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4190:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4166:39:6" }, "src": "4134:72:6" }, { "anonymous": false, "documentation": null, "id": 3752, "name": "DeleteContract", "nodeType": "EventDefinition", "parameters": { "id": 3751, "nodeType": "ParameterList", "parameters": [], "src": "4231:2:6" }, "src": "4211:23:6" }, { "constant": true, "functionSelector": "c0ba241b", "id": 3757, "mutability": "constant", "name": "DELEGATE_ROLE", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "4241:66:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, "typeName": { "id": 3753, "name": "bytes32", "nodeType": "ElementaryTypeName", "src": "4241:7:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "value": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "44454c45474154455f524f4c45", "id": 3755, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "4291:15:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" }, "value": "DELEGATE_ROLE" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" } ], "id": 3754, "name": "keccak256", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -8, "src": "4281:9:6", "typeDescriptions": { "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", "typeString": "function (bytes memory) pure returns (bytes32)" } }, "id": 3756, "isConstant": false, "isLValue": false, "isPure": true, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4281:26:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "visibility": "public" }, { "constant": true, "functionSelector": "372646bb", "id": 3760, "mutability": "constant", "name": "DELETE_PROTECTION_PERIOD", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "4313:57:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3758, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4313:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": { "argumentTypes": null, "hexValue": "333730323835", "id": 3759, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "4364:6:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_370285_by_1", "typeString": "int_const 370285" }, "value": "370285" }, "visibility": "public" }, { "constant": false, "functionSelector": "ecd0c0c3", "id": 3762, "mutability": "mutable", "name": "_token", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "4427:20:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" }, "typeName": { "contractScope": null, "id": 3761, "name": "IERC20", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 6707, "src": "4427:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "32a1bd70", "id": 3764, "mutability": "mutable", "name": "_earliestDelete", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5201:30:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3763, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5201:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "f1209ef7", "id": 3766, "mutability": "mutable", "name": "_pausedSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5242:32:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3765, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5242:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "2514c50a", "id": 3768, "mutability": "mutable", "name": "_lockPeriodInBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5280:33:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 3767, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "5280:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "0c450f9d", "id": 3770, "mutability": "mutable", "name": "_rewardsPoolBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5441:34:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3769, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5441:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "292911fb", "id": 3772, "mutability": "mutable", "name": "_accruedGlobalPrincipal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5608:38:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3771, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5608:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "c8984ab2", "id": 3774, "mutability": "mutable", "name": "_accruedGlobalLiquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5652:45:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3773, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "5652:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "d9b202c9", "id": 3776, "mutability": "mutable", "name": "_accruedGlobalLocked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5712:42:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3775, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "5712:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "d9d8e783", "id": 3778, "mutability": "mutable", "name": "_interestRatesStartIdx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5770:37:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3777, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5770:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "4164b001", "id": 3780, "mutability": "mutable", "name": "_interestRatesNextIdx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5813:36:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3779, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5813:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "aa700ff2", "id": 3784, "mutability": "mutable", "name": "_interestRates", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5855:62:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock)" }, "typeName": { "id": 3783, "keyType": { "id": 3781, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5863:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Mapping", "src": "5855:40:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock)" }, "valueType": { "contractScope": null, "id": 3782, "name": "InterestRatePerBlock", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3641, "src": "5874:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" } } }, "value": null, "visibility": "public" }, { "constant": false, "id": 3788, "mutability": "mutable", "name": "_stakes", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5924:33:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake)" }, "typeName": { "id": 3787, "keyType": { "id": 3785, "name": "address", "nodeType": "ElementaryTypeName", "src": "5932:7:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Mapping", "src": "5924:25:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake)" }, "valueType": { "contractScope": null, "id": 3786, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "5943:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3792, "mutability": "mutable", "name": "_locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5963:34:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked)" }, "typeName": { "id": 3791, "keyType": { "id": 3789, "name": "address", "nodeType": "ElementaryTypeName", "src": "5971:7:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Mapping", "src": "5963:26:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked)" }, "valueType": { "contractScope": null, "id": 3790, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "5982:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } } }, "value": null, "visibility": "internal" }, { "constant": false, "functionSelector": "67ce50c0", "id": 3796, "mutability": "mutable", "name": "_liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "6003:52:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset)" }, "typeName": { "id": 3795, "keyType": { "id": 3793, "name": "address", "nodeType": "ElementaryTypeName", "src": "6011:7:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Mapping", "src": "6003:34:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset)" }, "valueType": { "contractScope": null, "id": 3794, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "6022:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } } }, "value": null, "visibility": "public" }, { "body": { "id": 3805, "nodeType": "Block", "src": "6117:73:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3799, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5031, "src": "6135:8:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 3800, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6135:10:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e6f7420616e206f776e6572", "id": 3801, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6147:24:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fba574a628f13337c8d554e12acf25c09c56b020b31ccfaf084049163a70a41b", "typeString": "literal_string \"Caller is not an owner\"" }, "value": "Caller is not an owner" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fba574a628f13337c8d554e12acf25c09c56b020b31ccfaf084049163a70a41b", "typeString": "literal_string \"Caller is not an owner\"" } ], "id": 3798, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6127:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3802, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6127:45:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3803, "nodeType": "ExpressionStatement", "src": "6127:45:6" }, { "id": 3804, "nodeType": "PlaceholderStatement", "src": "6182:1:6" } ] }, "documentation": null, "id": 3806, "name": "onlyOwner", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3797, "nodeType": "ParameterList", "parameters": [], "src": "6114:2:6" }, "src": "6096:94:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3821, "nodeType": "Block", "src": "6265:125:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_bool", "typeString": "bool" }, "id": 3816, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3809, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5031, "src": "6283:8:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 3810, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6283:10:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "BinaryOperation", "operator": "||", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3812, "name": "DELEGATE_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3757, "src": "6305:13:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3813, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "6320:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3814, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "6320:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 3811, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5630, "src": "6297:7:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 3815, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6297:34:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "6283:48:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465", "id": 3817, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6333:38:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" }, "value": "Caller is neither owner nor delegate" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" } ], "id": 3808, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6275:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3818, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6275:97:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3819, "nodeType": "ExpressionStatement", "src": "6275:97:6" }, { "id": 3820, "nodeType": "PlaceholderStatement", "src": "6382:1:6" } ] }, "documentation": null, "id": 3822, "name": "onlyDelegate", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3807, "nodeType": "ParameterList", "parameters": [], "src": "6262:2:6" }, "src": "6241:149:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3835, "nodeType": "Block", "src": "6449:96:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 3830, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3827, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "6467:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3828, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6467:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 3829, "name": "expirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3824, "src": "6488:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "6467:36:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73616374696f6e2065787069726564", "id": 3831, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6505:21:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" }, "value": "Transaction expired" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" } ], "id": 3826, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6459:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3832, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6459:68:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3833, "nodeType": "ExpressionStatement", "src": "6459:68:6" }, { "id": 3834, "nodeType": "PlaceholderStatement", "src": "6537:1:6" } ] }, "documentation": null, "id": 3836, "name": "verifyTxExpiration", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3825, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3824, "mutability": "mutable", "name": "expirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3836, "src": "6424:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3823, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6424:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "6423:25:6" }, "src": "6396:149:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3847, "nodeType": "Block", "src": "6578:102:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 3842, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 3839, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3766, "src": "6596:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3840, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "6616:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3841, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6616:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "6596:37:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "436f6e747261637420686173206265656e20706175736564", "id": 3843, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6635:26:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" }, "value": "Contract has been paused" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" } ], "id": 3838, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6588:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3844, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6588:74:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3845, "nodeType": "ExpressionStatement", "src": "6588:74:6" }, { "id": 3846, "nodeType": "PlaceholderStatement", "src": "6672:1:6" } ] }, "documentation": null, "id": 3848, "name": "verifyNotPaused", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3837, "nodeType": "ParameterList", "parameters": [], "src": "6575:2:6" }, "src": "6551:129:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3892, "nodeType": "Block", "src": "7002:640:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3861, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5584, "src": "7023:18:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3862, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "7043:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3863, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7043:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 3860, "name": "_setupRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5767, "src": "7012:10:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_bytes32_$_t_address_$returns$__$", "typeString": "function (bytes32,address)" } }, "id": 3864, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7012:42:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3865, "nodeType": "ExpressionStatement", "src": "7012:42:6" }, { "expression": { "argumentTypes": null, "id": 3870, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 3866, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "7065:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3868, "name": "ERC20Address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3851, "src": "7081:12:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 3867, "name": "IERC20", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 6707, "src": "7074:6:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_IERC20_$6707_$", "typeString": "type(contract IERC20)" } }, "id": 3869, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7074:20:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "src": "7065:29:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 3871, "nodeType": "ExpressionStatement", "src": "7065:29:6" }, { "expression": { "argumentTypes": null, "id": 3878, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 3872, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3764, "src": "7104:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3876, "name": "DELETE_PROTECTION_PERIOD", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3760, "src": "7144:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3873, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "7122:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3874, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7122:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3875, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "7122:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3877, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7122:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "7104:65:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3879, "nodeType": "ExpressionStatement", "src": "7104:65:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3881, "name": "lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3857, "src": "7512:18:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "id": 3880, "name": "_updateLockPeriod", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5079, "src": "7494:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint64_$returns$__$", "typeString": "function (uint64)" } }, "id": 3882, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7494:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3883, "nodeType": "ExpressionStatement", "src": "7494:37:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3885, "name": "interestRatePerBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3853, "src": "7558:20:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3884, "name": "_addInterestRate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5064, "src": "7541:16:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 3886, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7541:38:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3887, "nodeType": "ExpressionStatement", "src": "7541:38:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3889, "name": "pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3855, "src": "7601:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3888, "name": "_pauseSince", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5104, "src": "7589:11:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 3890, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7589:46:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3891, "nodeType": "ExpressionStatement", "src": "7589:46:6" } ] }, "documentation": { "id": 3849, "nodeType": "StructuredDocumentation", "src": "6756:68:6", "text": "@param ERC20Address address of the ERC20 contract" }, "id": 3893, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 3858, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3851, "mutability": "mutable", "name": "ERC20Address", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6852:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3850, "name": "address", "nodeType": "ElementaryTypeName", "src": "6852:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3853, "mutability": "mutable", "name": "interestRatePerBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6883:28:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3852, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6883:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3855, "mutability": "mutable", "name": "pausedSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6922:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3854, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6922:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3857, "mutability": "mutable", "name": "lockPeriodInBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6957:26:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 3856, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "6957:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" } ], "src": "6840:144:6" }, "returnParameters": { "id": 3859, "nodeType": "ParameterList", "parameters": [], "src": "7002:0:6" }, "scope": 5490, "src": "6829:813:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "body": { "id": 3910, "nodeType": "Block", "src": "8459:39:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3907, "name": "rate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3896, "src": "8486:4:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3906, "name": "_addInterestRate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5064, "src": "8469:16:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 3908, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8469:22:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3909, "nodeType": "ExpressionStatement", "src": "8469:22:6" } ] }, "documentation": { "id": 3894, "nodeType": "StructuredDocumentation", "src": "7649:627:6", "text": "@notice Add new interest rate in to the ordered container of previously added interest rates\n@param rate - signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18\n@param expirationBlock - block number beyond which is the carrier Tx considered expired, and so rejected.\n This is for protection of Tx sender to exactly define lifecycle length of the Tx,\n and so avoiding uncertainty of how long Tx sender needs to wait for Tx processing.\n Tx can be withheld\n@dev expiration period" }, "functionSelector": "13384039", "id": 3911, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [], "id": 3901, "modifierName": { "argumentTypes": null, "id": 3900, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3822, "src": "8396:12:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "8396:14:6" }, { "arguments": [ { "argumentTypes": null, "id": 3903, "name": "expirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3898, "src": "8438:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 3904, "modifierName": { "argumentTypes": null, "id": 3902, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "8419:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "8419:35:6" } ], "name": "addInterestRate", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 3899, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3896, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3911, "src": "8315:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3895, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8315:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3898, "mutability": "mutable", "name": "expirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3911, "src": "8337:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3897, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8337:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "8305:65:6" }, "returnParameters": { "id": 3905, "nodeType": "ParameterList", "parameters": [], "src": "8459:0:6" }, "scope": 5490, "src": "8281:217:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 3997, "nodeType": "Block", "src": "8682:654:6", "statements": [ { "assignments": [ 3924 ], "declarations": [ { "constant": false, "id": 3924, "mutability": "mutable", "name": "makeTransfer", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3997, "src": "8692:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 3923, "name": "bool", "nodeType": "ElementaryTypeName", "src": "8692:4:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "id": 3928, "initialValue": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 3927, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 3925, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "8712:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 3926, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "8722:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "8712:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "VariableDeclarationStatement", "src": "8692:31:6" }, { "condition": { "argumentTypes": null, "id": 3929, "name": "makeTransfer", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3924, "src": "8737:12:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 3968, "nodeType": "IfStatement", "src": "8733:352:6", "trueBody": { "id": 3967, "nodeType": "Block", "src": "8751:334:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3933, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "8793:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3934, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "8793:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3937, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "8813:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 3936, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "8805:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 3935, "name": "address", "nodeType": "ElementaryTypeName", "src": "8805:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 3938, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8805:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 3939, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "8820:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 3931, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "8773:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 3932, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transferFrom", "nodeType": "MemberAccess", "referencedDeclaration": 6688, "src": "8773:19:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,address,uint256) external returns (bool)" } }, "id": 3940, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8773:54:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73666572206661696c6564", "id": 3941, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "8829:17:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" }, "value": "Transfer failed" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" } ], "id": 3930, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "8765:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3942, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8765:82:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3943, "nodeType": "ExpressionStatement", "src": "8765:82:6" }, { "expression": { "argumentTypes": null, "id": 3949, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 3944, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "8861:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3947, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "8915:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 3945, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "8887:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3946, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "8887:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3948, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8887:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "8861:61:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3950, "nodeType": "ExpressionStatement", "src": "8861:61:6" }, { "expression": { "argumentTypes": null, "id": 3959, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3951, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "8936:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 3953, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "8936:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3957, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "9010:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3954, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "8972:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 3955, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "8972:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3956, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "8972:37:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3958, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8972:45:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "8936:81:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3960, "nodeType": "ExpressionStatement", "src": "8936:81:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3962, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9055:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3963, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9055:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 3964, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "9067:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3961, "name": "LiquidityDeposited", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3686, "src": "9036:18:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 3965, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9036:38:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3966, "nodeType": "EmitStatement", "src": "9031:43:6" } ] } }, { "assignments": [ 3970 ], "declarations": [ { "constant": false, "id": 3970, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3997, "src": "9095:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3969, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9095:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 3973, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3971, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "9116:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3972, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9116:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "9095:38:6" }, { "assignments": [ null, 3977, null ], "declarations": [ null, { "constant": false, "id": 3977, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3997, "src": "9146:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3976, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "9146:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 3983, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3979, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9201:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3980, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9201:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 3981, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3970, "src": "9213:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3978, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "9183:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 3982, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9183:41:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "9143:81:6" }, { "condition": { "argumentTypes": null, "id": 3984, "name": "makeTransfer", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3924, "src": "9239:12:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 3996, "nodeType": "IfStatement", "src": "9235:95:6", "trueBody": { "id": 3995, "nodeType": "Block", "src": "9253:77:6", "statements": [ { "expression": { "argumentTypes": null, "id": 3993, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3985, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3977, "src": "9267:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 3987, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "9267:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3991, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "9313:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3988, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3977, "src": "9289:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 3989, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "9289:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3990, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "9289:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3992, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9289:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "9267:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3994, "nodeType": "ExpressionStatement", "src": "9267:53:6" } ] } } ] }, "documentation": null, "functionSelector": "e2bbb158", "id": 3998, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 3918, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3915, "src": "8635:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 3919, "modifierName": { "argumentTypes": null, "id": 3917, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "8616:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "8616:37:6" }, { "arguments": null, "id": 3921, "modifierName": { "argumentTypes": null, "id": 3920, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "8662:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "8662:15:6" } ], "name": "deposit", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 3916, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3913, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3998, "src": "8531:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3912, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8531:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3915, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3998, "src": "8555:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3914, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8555:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "8521:69:6" }, "returnParameters": { "id": 3922, "nodeType": "ParameterList", "parameters": [], "src": "8682:0:6" }, "scope": 5490, "src": "8505:831:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4045, "nodeType": "Block", "src": "9796:317:6", "statements": [ { "assignments": [ 4012 ], "declarations": [ { "constant": false, "id": 4012, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9806:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4011, "name": "address", "nodeType": "ElementaryTypeName", "src": "9806:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4015, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4013, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9823:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4014, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9823:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "9806:27:6" }, { "assignments": [ 4017 ], "declarations": [ { "constant": false, "id": 4017, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9843:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4016, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9843:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4020, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4018, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "9864:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4019, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9864:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "9843:38:6" }, { "assignments": [ null, 4024, null ], "declarations": [ null, { "constant": false, "id": 4024, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9894:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4023, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "9894:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4029, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4026, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4012, "src": "9949:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4027, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4017, "src": "9957:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4025, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "9931:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4028, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9931:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "9891:77:6" }, { "assignments": [ 4033 ], "declarations": [ { "constant": false, "id": 4033, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9979:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4032, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "9979:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4038, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4036, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4001, "src": "10047:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4034, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4024, "src": "10011:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4035, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSubCompoundInterestFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3072, "src": "10011:35:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4037, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10011:43:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "VariableDeclarationStatement", "src": "9979:75:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4040, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4012, "src": "10082:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4041, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4033, "src": "10090:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, { "argumentTypes": null, "id": 4042, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4001, "src": "10099:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4039, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "10064:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4043, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10064:42:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4044, "nodeType": "ExpressionStatement", "src": "10064:42:6" } ] }, "documentation": { "id": 3999, "nodeType": "StructuredDocumentation", "src": "9343:270:6", "text": "@notice Withdraws amount from sender' available liquidity pool back to sender address,\n preferring withdrawal from compound interest dimension of liquidity.\n * @param amount - value to withdraw\n * @dev public access" }, "functionSelector": "441a3e70", "id": 4046, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4006, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4003, "src": "9749:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4007, "modifierName": { "argumentTypes": null, "id": 4005, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "9730:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "9730:37:6" }, { "arguments": null, "id": 4009, "modifierName": { "argumentTypes": null, "id": 4008, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "9776:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "9776:15:6" } ], "name": "withdraw", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4004, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4001, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4046, "src": "9645:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4000, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9645:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4003, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4046, "src": "9669:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4002, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9669:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "9635:69:6" }, "returnParameters": { "id": 4010, "nodeType": "ParameterList", "parameters": [], "src": "9796:0:6" }, "scope": 5490, "src": "9618:495:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4101, "nodeType": "Block", "src": "10411:366:6", "statements": [ { "assignments": [ 4058 ], "declarations": [ { "constant": false, "id": 4058, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10421:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4057, "name": "address", "nodeType": "ElementaryTypeName", "src": "10421:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4061, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4059, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10438:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4060, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10438:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "10421:27:6" }, { "assignments": [ 4063 ], "declarations": [ { "constant": false, "id": 4063, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10458:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4062, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10458:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4066, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4064, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "10479:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4065, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10479:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "10458:38:6" }, { "assignments": [ null, 4070, null ], "declarations": [ null, { "constant": false, "id": 4070, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10509:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4069, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "10509:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4075, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4072, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4058, "src": "10565:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4073, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4063, "src": "10573:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4071, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "10547:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4074, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10547:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "10506:78:6" }, { "assignments": [ 4079 ], "declarations": [ { "constant": false, "id": 4079, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10595:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4078, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "10595:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4080, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "10595:29:6" }, { "expression": { "argumentTypes": null, "id": 4086, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4081, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4079, "src": "10634:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4083, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10634:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4084, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4070, "src": "10654:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4085, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10654:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "10634:39:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4087, "nodeType": "ExpressionStatement", "src": "10634:39:6" }, { "expression": { "argumentTypes": null, "id": 4092, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4088, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4070, "src": "10683:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4090, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10683:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4091, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "10705:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "10683:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4093, "nodeType": "ExpressionStatement", "src": "10683:23:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4095, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4058, "src": "10735:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4096, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4079, "src": "10743:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4097, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4079, "src": "10752:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4098, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10752:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4094, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "10717:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4099, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10717:53:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4100, "nodeType": "ExpressionStatement", "src": "10717:53:6" } ] }, "documentation": { "id": 4047, "nodeType": "StructuredDocumentation", "src": "10120:123:6", "text": "@notice Withdraws *WHOLE* compound interest amount available to sender.\n * @dev public access" }, "functionSelector": "658e28a4", "id": 4102, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4052, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4049, "src": "10364:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4053, "modifierName": { "argumentTypes": null, "id": 4051, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "10345:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "10345:37:6" }, { "arguments": null, "id": 4055, "modifierName": { "argumentTypes": null, "id": 4054, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "10391:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "10391:15:6" } ], "name": "withdrawPrincipal", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4050, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4049, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4102, "src": "10284:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4048, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10284:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "10274:45:6" }, "returnParameters": { "id": 4056, "nodeType": "ParameterList", "parameters": [], "src": "10411:0:6" }, "scope": 5490, "src": "10248:529:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4157, "nodeType": "Block", "src": "11082:394:6", "statements": [ { "assignments": [ 4114 ], "declarations": [ { "constant": false, "id": 4114, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11092:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4113, "name": "address", "nodeType": "ElementaryTypeName", "src": "11092:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4117, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4115, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "11109:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4116, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11109:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "11092:27:6" }, { "assignments": [ 4119 ], "declarations": [ { "constant": false, "id": 4119, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11129:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4118, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "11129:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4122, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4120, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "11150:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4121, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11150:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "11129:38:6" }, { "assignments": [ null, 4126, null ], "declarations": [ null, { "constant": false, "id": 4126, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11180:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4125, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "11180:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4131, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4128, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4114, "src": "11236:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4129, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4119, "src": "11244:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4127, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "11218:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4130, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11218:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "11177:78:6" }, { "assignments": [ 4135 ], "declarations": [ { "constant": false, "id": 4135, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11266:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4134, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "11266:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4136, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "11266:29:6" }, { "expression": { "argumentTypes": null, "id": 4142, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4137, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4135, "src": "11305:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4139, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11305:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4140, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4126, "src": "11332:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4141, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11332:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "11305:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4143, "nodeType": "ExpressionStatement", "src": "11305:53:6" }, { "expression": { "argumentTypes": null, "id": 4148, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4144, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4126, "src": "11368:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4146, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11368:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4147, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "11397:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "11368:30:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4149, "nodeType": "ExpressionStatement", "src": "11368:30:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4151, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4114, "src": "11427:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4152, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4135, "src": "11435:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4153, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4135, "src": "11444:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4154, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11444:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4150, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "11409:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4155, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11409:60:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4156, "nodeType": "ExpressionStatement", "src": "11409:60:6" } ] }, "documentation": { "id": 4103, "nodeType": "StructuredDocumentation", "src": "10784:123:6", "text": "@notice Withdraws *WHOLE* compound interest amount available to sender.\n * @dev public access" }, "functionSelector": "d8cd3999", "id": 4158, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4108, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4105, "src": "11035:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4109, "modifierName": { "argumentTypes": null, "id": 4107, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "11016:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "11016:37:6" }, { "arguments": null, "id": 4111, "modifierName": { "argumentTypes": null, "id": 4110, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "11062:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "11062:15:6" } ], "name": "withdrawCompoundInterest", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4106, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4105, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4158, "src": "10955:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4104, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10955:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "10945:45:6" }, "returnParameters": { "id": 4112, "nodeType": "ParameterList", "parameters": [], "src": "11082:0:6" }, "scope": 5490, "src": "10912:564:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4208, "nodeType": "Block", "src": "11786:323:6", "statements": [ { "assignments": [ 4170 ], "declarations": [ { "constant": false, "id": 4170, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4208, "src": "11796:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4169, "name": "address", "nodeType": "ElementaryTypeName", "src": "11796:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4173, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4171, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "11813:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4172, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11813:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "11796:27:6" }, { "assignments": [ 4175 ], "declarations": [ { "constant": false, "id": 4175, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4208, "src": "11833:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4174, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "11833:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4178, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4176, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "11854:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4177, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11854:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "11833:38:6" }, { "assignments": [ null, 4182, null ], "declarations": [ null, { "constant": false, "id": 4182, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4208, "src": "11884:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4181, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "11884:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4187, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4184, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4170, "src": "11940:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4185, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4175, "src": "11948:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4183, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "11922:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4186, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11922:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "11881:78:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4189, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4170, "src": "11988:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4190, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "11996:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 4191, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "12007:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4192, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "composite", "nodeType": "MemberAccess", "referencedDeclaration": 2803, "src": "12007:19:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_struct$_Asset_$2788_storage_ptr_$returns$_t_uint256_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer) view returns (uint256)" } }, "id": 4193, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12007:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4188, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "11970:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4194, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11970:59:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4195, "nodeType": "ExpressionStatement", "src": "11970:59:6" }, { "expression": { "argumentTypes": null, "id": 4200, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4196, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "12039:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4198, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "12039:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4199, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "12068:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "12039:30:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4201, "nodeType": "ExpressionStatement", "src": "12039:30:6" }, { "expression": { "argumentTypes": null, "id": 4206, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4202, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "12079:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4204, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "12079:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4205, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "12101:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "12079:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4207, "nodeType": "ExpressionStatement", "src": "12079:23:6" } ] }, "documentation": { "id": 4159, "nodeType": "StructuredDocumentation", "src": "11483:130:6", "text": "@notice Withdraws whole liquidity available to sender back to sender' address,\n * @dev public access" }, "functionSelector": "dca2aa5c", "id": 4209, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4164, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4161, "src": "11739:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4165, "modifierName": { "argumentTypes": null, "id": 4163, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "11720:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "11720:37:6" }, { "arguments": null, "id": 4167, "modifierName": { "argumentTypes": null, "id": 4166, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "11766:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "11766:15:6" } ], "name": "withdrawWholeLiquidity", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4162, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4161, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4209, "src": "11659:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4160, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "11659:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "11649:45:6" }, "returnParameters": { "id": 4168, "nodeType": "ParameterList", "parameters": [], "src": "11786:0:6" }, "scope": 5490, "src": "11618:491:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4279, "nodeType": "Block", "src": "12295:1112:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4224, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4222, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4211, "src": "12313:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4223, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "12323:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "12313:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "416d6f756e74206d75737420626520686967686572207468616e207a65726f", "id": 4225, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "12326:33:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_8b5d38bfd9d56e06c511eeb946d76a1a45a72ed25ba1d513f75809a38de68bdb", "typeString": "literal_string \"Amount must be higher than zero\"" }, "value": "Amount must be higher than zero" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_8b5d38bfd9d56e06c511eeb946d76a1a45a72ed25ba1d513f75809a38de68bdb", "typeString": "literal_string \"Amount must be higher than zero\"" } ], "id": 4221, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "12305:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4226, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12305:55:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4227, "nodeType": "ExpressionStatement", "src": "12305:55:6" }, { "assignments": [ 4229 ], "declarations": [ { "constant": false, "id": 4229, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12371:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4228, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "12371:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4232, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4230, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "12392:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4231, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12392:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "12371:38:6" }, { "assignments": [ null, 4236, null ], "declarations": [ null, { "constant": false, "id": 4236, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12423:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4235, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "12423:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4242, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4238, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "12479:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4239, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "12479:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4240, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4229, "src": "12491:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4237, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "12461:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4241, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12461:41:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "12420:82:6" }, { "assignments": [ 4244 ], "declarations": [ { "constant": false, "id": 4244, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12848:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 4243, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "12848:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "id": 4250, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4246, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "12899:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4247, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "12899:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4248, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4229, "src": "12911:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4245, "name": "_updateStakeCompoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5315, "src": "12870:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Stake_$3648_storage_ptr_$", "typeString": "function (address,uint256) returns (struct Staking.Stake storage pointer)" } }, "id": 4249, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12870:52:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "nodeType": "VariableDeclarationStatement", "src": "12848:74:6" }, { "assignments": [ 4254 ], "declarations": [ { "constant": false, "id": 4254, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12932:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4253, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "12932:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4261, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4257, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4244, "src": "12998:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4258, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "12998:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, { "argumentTypes": null, "id": 4259, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4211, "src": "13011:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4255, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4236, "src": "12964:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4256, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iRelocatePrincipalFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3096, "src": "12964:33:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4260, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12964:54:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "VariableDeclarationStatement", "src": "12932:86:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4265, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4254, "src": "13057:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4262, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "13028:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4264, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "13028:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4266, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "13028:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4267, "nodeType": "ExpressionStatement", "src": "13028:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4269, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "13302:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4270, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "13302:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4271, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4244, "src": "13314:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4272, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "13314:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4273, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4254, "src": "13344:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4274, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "13344:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4275, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4254, "src": "13363:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4276, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "13363:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4268, "name": "BindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3669, "src": "13292:9:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4277, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "13292:96:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4278, "nodeType": "EmitStatement", "src": "13287:101:6" } ] }, "documentation": null, "functionSelector": "88cedd5e", "id": 4280, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4216, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4213, "src": "12248:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4217, "modifierName": { "argumentTypes": null, "id": 4215, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "12229:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "12229:37:6" }, { "arguments": null, "id": 4219, "modifierName": { "argumentTypes": null, "id": 4218, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "12275:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "12275:15:6" } ], "name": "bindStake", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4214, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4211, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4280, "src": "12144:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4210, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "12144:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4213, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4280, "src": "12168:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4212, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "12168:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "12134:69:6" }, "returnParameters": { "id": 4220, "nodeType": "ParameterList", "parameters": [], "src": "12295:0:6" }, "scope": 5490, "src": "12116:1291:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4549, "nodeType": "Block", "src": "14400:3414:6", "statements": [ { "assignments": [ 4294 ], "declarations": [ { "constant": false, "id": 4294, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14410:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4293, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14410:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4297, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4295, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "14431:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4296, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "14431:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "14410:38:6" }, { "assignments": [ 4299 ], "declarations": [ { "constant": false, "id": 4299, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14458:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4298, "name": "address", "nodeType": "ElementaryTypeName", "src": "14458:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4302, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4300, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "14475:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4301, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "14475:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "14458:27:6" }, { "assignments": [ 4304 ], "declarations": [ { "constant": false, "id": 4304, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14495:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 4303, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "14495:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "id": 4309, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4306, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "14546:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4307, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "14554:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4305, "name": "_updateStakeCompoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5315, "src": "14517:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Stake_$3648_storage_ptr_$", "typeString": "function (address,uint256) returns (struct Staking.Stake storage pointer)" } }, "id": 4308, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "14517:48:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "nodeType": "VariableDeclarationStatement", "src": "14495:70:6" }, { "assignments": [ 4311 ], "declarations": [ { "constant": false, "id": 4311, "mutability": "mutable", "name": "stake_composite", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14576:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4310, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14576:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4316, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4312, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "14602:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4313, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "14602:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4314, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "composite", "nodeType": "MemberAccess", "referencedDeclaration": 2803, "src": "14602:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_struct$_Asset_$2788_storage_ptr_$returns$_t_uint256_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer) view returns (uint256)" } }, "id": 4315, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "14602:23:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "14576:49:6" }, { "assignments": [ 4320 ], "declarations": [ { "constant": false, "id": 4320, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14635:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4319, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "14635:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4321, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "14635:29:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4324, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4322, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "14679:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4323, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "14688:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "14679:10:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4547, "nodeType": "Block", "src": "16562:1246:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4431, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4429, "name": "stake_composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4311, "src": "16580:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4430, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16599:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16580:20:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 4434, "nodeType": "IfStatement", "src": "16576:108:6", "trueBody": { "id": 4433, "nodeType": "Block", "src": "16602:82:6", "statements": [ { "expression": null, "functionReturnParameters": 4292, "id": 4432, "nodeType": "Return", "src": "16663:7:6" } ] } }, { "expression": { "argumentTypes": null, "id": 4438, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4435, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16698:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4436, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16708:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4437, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16708:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "src": "16698:21:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4439, "nodeType": "ExpressionStatement", "src": "16698:21:6" }, { "expression": { "argumentTypes": null, "id": 4446, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4440, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16733:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4443, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16733:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4444, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "16733:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4445, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16757:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16733:25:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4447, "nodeType": "ExpressionStatement", "src": "16733:25:6" }, { "expression": { "argumentTypes": null, "id": 4454, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4448, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16772:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4451, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16772:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4452, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "16772:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4453, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16803:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16772:32:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4455, "nodeType": "ExpressionStatement", "src": "16772:32:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "id": 4458, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4456, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "16823:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4457, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16846:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16823:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4545, "nodeType": "Block", "src": "17167:631:6", "statements": [ { "assignments": [ 4491 ], "declarations": [ { "constant": false, "id": 4491, "mutability": "mutable", "name": "locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4545, "src": "17185:21:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" }, "typeName": { "contractScope": null, "id": 4490, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "17185:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } }, "value": null, "visibility": "internal" } ], "id": 4495, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4492, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "17209:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4494, "indexExpression": { "argumentTypes": null, "id": 4493, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "17217:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "17209:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "17185:39:6" }, { "assignments": [ 4497 ], "declarations": [ { "constant": false, "id": 4497, "mutability": "mutable", "name": "newLockedAsset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4545, "src": "17242:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 4496, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "17242:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 4502, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4498, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4491, "src": "17279:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4499, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "17279:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "id": 4500, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "push", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "17279:18:6", "typeDescriptions": { "typeIdentifier": "t_function_arraypush_nonpayable$__$returns$_t_struct$_LockedAsset_$3653_storage_$", "typeString": "function () returns (struct Staking.LockedAsset storage ref)" } }, "id": 4501, "isConstant": false, "isLValue": true, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17279:20:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "17242:57:6" }, { "expression": { "argumentTypes": null, "id": 4510, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4503, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17317:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4505, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "17317:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4508, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "17366:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "expression": { "argumentTypes": null, "id": 4506, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "17351:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4507, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "17351:14:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4509, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17351:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "17317:69:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4511, "nodeType": "ExpressionStatement", "src": "17317:69:6" }, { "expression": { "argumentTypes": null, "id": 4516, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4512, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17404:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4514, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "17404:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 4515, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17427:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "src": "17404:30:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4517, "nodeType": "ExpressionStatement", "src": "17404:30:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4521, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17479:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4518, "name": "_accruedGlobalLocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3776, "src": "17453:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4520, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "17453:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4522, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17453:34:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4523, "nodeType": "ExpressionStatement", "src": "17453:34:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4529, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17527:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4524, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4491, "src": "17505:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4527, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "17505:16:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4528, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "17505:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4530, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17505:30:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4531, "nodeType": "ExpressionStatement", "src": "17505:30:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4533, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "17668:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4534, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "17668:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4535, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17680:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4536, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "17680:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4537, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17713:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4538, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "17713:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4539, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "17713:30:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4540, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17745:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4541, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "17745:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4542, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "17745:37:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4532, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "17656:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4543, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17656:127:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4544, "nodeType": "EmitStatement", "src": "17651:132:6" } ] }, "id": 4546, "nodeType": "IfStatement", "src": "16819:979:6", "trueBody": { "id": 4489, "nodeType": "Block", "src": "16849:312:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4463, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16891:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4459, "name": "_liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3796, "src": "16867:10:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset storage ref)" } }, "id": 4461, "indexExpression": { "argumentTypes": null, "id": 4460, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "16878:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "16867:18:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4462, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16867:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4464, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16867:32:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4465, "nodeType": "ExpressionStatement", "src": "16867:32:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4469, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16946:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4466, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "16917:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4468, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16917:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4470, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16917:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4471, "nodeType": "ExpressionStatement", "src": "16917:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4473, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "16989:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4474, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "16997:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4475, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17009:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4476, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "17009:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4477, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17028:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4478, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "17028:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4472, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "16977:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4479, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16977:76:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4480, "nodeType": "EmitStatement", "src": "16972:81:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4482, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "17094:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4483, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17102:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4484, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "17102:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4485, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17121:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4486, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "17121:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4481, "name": "LiquidityUnlocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3694, "src": "17076:17:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 4487, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17076:70:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4488, "nodeType": "EmitStatement", "src": "17071:75:6" } ] } } ] }, "id": 4548, "nodeType": "IfStatement", "src": "14675:3133:6", "trueBody": { "id": 4428, "nodeType": "Block", "src": "14691:1865:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4328, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4326, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "15425:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 4327, "name": "stake_composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4311, "src": "15435:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "15425:25:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "416d6f756e7420697320686967686572207468616e207374616b65", "id": 4329, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "15452:29:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_8bda5dc5d5057eb9094b795367834747986b50a0bac9d1f451ae725565808bcd", "typeString": "literal_string \"Amount is higher than stake\"" }, "value": "Amount is higher than stake" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_8bda5dc5d5057eb9094b795367834747986b50a0bac9d1f451ae725565808bcd", "typeString": "literal_string \"Amount is higher than stake\"" } ], "id": 4325, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "15417:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4330, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15417:65:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4331, "nodeType": "ExpressionStatement", "src": "15417:65:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "id": 4334, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4332, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "15501:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4333, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "15524:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "15501:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4426, "nodeType": "Block", "src": "15893:653:6", "statements": [ { "assignments": [ 4371 ], "declarations": [ { "constant": false, "id": 4371, "mutability": "mutable", "name": "locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4426, "src": "15911:21:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" }, "typeName": { "contractScope": null, "id": 4370, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "15911:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } }, "value": null, "visibility": "internal" } ], "id": 4375, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4372, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "15935:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4374, "indexExpression": { "argumentTypes": null, "id": 4373, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15943:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "15935:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "15911:39:6" }, { "assignments": [ 4377 ], "declarations": [ { "constant": false, "id": 4377, "mutability": "mutable", "name": "newLockedAsset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4426, "src": "15968:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 4376, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "15968:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 4382, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4378, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4371, "src": "16005:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4379, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "16005:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "id": 4380, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "push", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "16005:18:6", "typeDescriptions": { "typeIdentifier": "t_function_arraypush_nonpayable$__$returns$_t_struct$_LockedAsset_$3653_storage_$", "typeString": "function () returns (struct Staking.LockedAsset storage ref)" } }, "id": 4381, "isConstant": false, "isLValue": true, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16005:20:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "15968:57:6" }, { "expression": { "argumentTypes": null, "id": 4390, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4383, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4377, "src": "16043:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4385, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "16043:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4388, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "16092:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "expression": { "argumentTypes": null, "id": 4386, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "16077:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4387, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "16077:14:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4389, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16077:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "16043:69:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4391, "nodeType": "ExpressionStatement", "src": "16043:69:6" }, { "expression": { "argumentTypes": null, "id": 4400, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4392, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16130:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4396, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4377, "src": "16183:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4397, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "16183:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, { "argumentTypes": null, "id": 4398, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "16205:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4393, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16140:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4394, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16140:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4395, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iRelocateCompoundInterestFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3120, "src": "16140:42:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4399, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16140:72:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "src": "16130:82:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4401, "nodeType": "ExpressionStatement", "src": "16130:82:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4405, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16257:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4402, "name": "_accruedGlobalLocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3776, "src": "16231:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4404, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16231:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4406, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16231:34:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4407, "nodeType": "ExpressionStatement", "src": "16231:34:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4413, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16305:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4408, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4371, "src": "16283:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4411, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "16283:16:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4412, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16283:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4414, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16283:30:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4415, "nodeType": "ExpressionStatement", "src": "16283:30:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4417, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "16446:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4418, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4377, "src": "16454:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4419, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "16454:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4420, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16487:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4421, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "16487:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4422, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16506:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4423, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "16506:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4416, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "16434:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4424, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16434:97:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4425, "nodeType": "EmitStatement", "src": "16429:102:6" } ] }, "id": 4427, "nodeType": "IfStatement", "src": "15497:1049:6", "trueBody": { "id": 4369, "nodeType": "Block", "src": "15527:360:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4344, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4335, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15545:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4339, "name": "_liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3796, "src": "15598:10:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset storage ref)" } }, "id": 4341, "indexExpression": { "argumentTypes": null, "id": 4340, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15609:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "15598:18:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, { "argumentTypes": null, "id": 4342, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "15618:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4336, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "15555:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4337, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "15555:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4338, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iRelocateCompoundInterestFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3120, "src": "15555:42:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4343, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15555:70:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "src": "15545:80:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4345, "nodeType": "ExpressionStatement", "src": "15545:80:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4349, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15672:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4346, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "15643:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4348, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "15643:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4350, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15643:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4351, "nodeType": "ExpressionStatement", "src": "15643:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4353, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15715:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4354, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "15723:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4355, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15735:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4356, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "15735:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4357, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15754:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4358, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "15754:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4352, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "15703:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4359, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15703:76:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4360, "nodeType": "EmitStatement", "src": "15698:81:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4362, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15820:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4363, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15828:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4364, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "15828:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4365, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15847:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4366, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "15847:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4361, "name": "LiquidityUnlocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3694, "src": "15802:17:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 4367, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15802:70:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4368, "nodeType": "EmitStatement", "src": "15797:75:6" } ] } } ] } } ] }, "documentation": { "id": 4281, "nodeType": "StructuredDocumentation", "src": "13414:755:6", "text": "@notice Unbinds amount from the stake of sender of the transaction,\n and *LOCKS* it for number of blocks defined by value of the\n `_lockPeriodInBlocks` state of this contract at the point\n of this call.\n The locked amount can *NOT* be withdrawn from the contract\n *BEFORE* the lock period ends.\n * Unbinding (=calling this method) also means, that compound\n interest will be calculated for period since la.\n * @param amount - value to un-bind from the stake\n If `amount=0` then the **WHOLE** stake (including\n compound interest) will be unbound.\n * @dev public access" }, "functionSelector": "52885d8d", "id": 4550, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4288, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4285, "src": "14353:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4289, "modifierName": { "argumentTypes": null, "id": 4287, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "14334:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "14334:37:6" }, { "arguments": null, "id": 4291, "modifierName": { "argumentTypes": null, "id": 4290, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "14380:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "14380:15:6" } ], "name": "unbindStake", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4286, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4283, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4550, "src": "14204:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4282, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14204:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4285, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4550, "src": "14273:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4284, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14273:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "14194:114:6" }, "returnParameters": { "id": 4292, "nodeType": "ParameterList", "parameters": [], "src": "14400:0:6" }, "scope": 5490, "src": "14174:3640:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4557, "nodeType": "Block", "src": "17885:43:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4555, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "17902:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 4554, "id": 4556, "nodeType": "Return", "src": "17895:26:6" } ] }, "documentation": null, "functionSelector": "adf55101", "id": 4558, "implemented": true, "kind": "function", "modifiers": [], "name": "getRewardsPoolBalance", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4551, "nodeType": "ParameterList", "parameters": [], "src": "17851:2:6" }, "returnParameters": { "id": 4554, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4553, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4558, "src": "17876:7:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4552, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "17876:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "17875:9:6" }, "scope": 5490, "src": "17821:107:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4565, "nodeType": "Block", "src": "18000:39:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4563, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3764, "src": "18017:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 4562, "id": 4564, "nodeType": "Return", "src": "18010:22:6" } ] }, "documentation": null, "functionSelector": "085313ec", "id": 4566, "implemented": true, "kind": "function", "modifiers": [], "name": "getEarliestDeleteBlock", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4559, "nodeType": "ParameterList", "parameters": [], "src": "17966:2:6" }, "returnParameters": { "id": 4562, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4561, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4566, "src": "17991:7:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4560, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "17991:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "17990:9:6" }, "scope": 5490, "src": "17935:104:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4581, "nodeType": "Block", "src": "18144:59:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4579, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4573, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4571, "src": "18154:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4574, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "18163:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4576, "indexExpression": { "argumentTypes": null, "id": 4575, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4568, "src": "18171:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "18163:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "id": 4577, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "18163:26:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "id": 4578, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "18163:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "18154:42:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4580, "nodeType": "ExpressionStatement", "src": "18154:42:6" } ] }, "documentation": null, "functionSelector": "40e97903", "id": 4582, "implemented": true, "kind": "function", "modifiers": [], "name": "getNumberOfLockedAssetsForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4569, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4568, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4582, "src": "18086:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4567, "name": "address", "nodeType": "ElementaryTypeName", "src": "18086:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "18085:20:6" }, "returnParameters": { "id": 4572, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4571, "mutability": "mutable", "name": "length", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4582, "src": "18128:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4570, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18128:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "18127:16:6" }, "scope": 5490, "src": "18046:157:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4606, "nodeType": "Block", "src": "18338:147:6", "statements": [ { "assignments": [ 4594 ], "declarations": [ { "constant": false, "id": 4594, "mutability": "mutable", "name": "aggregate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4606, "src": "18348:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4593, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "18348:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4599, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4595, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "18383:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4597, "indexExpression": { "argumentTypes": null, "id": 4596, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4584, "src": "18391:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "18383:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "id": 4598, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "18383:29:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "18348:64:6" }, { "expression": { "argumentTypes": null, "components": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4600, "name": "aggregate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4594, "src": "18430:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4601, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "18430:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4602, "name": "aggregate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4594, "src": "18451:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4603, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "18451:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4604, "isConstant": false, "isInlineArray": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "TupleExpression", "src": "18429:49:6", "typeDescriptions": { "typeIdentifier": "t_tuple$_t_uint256_$_t_uint256_$", "typeString": "tuple(uint256,uint256)" } }, "functionReturnParameters": 4590, "id": 4605, "nodeType": "Return", "src": "18422:56:6" } ] }, "documentation": null, "functionSelector": "a8f1b4c4", "id": 4607, "implemented": true, "kind": "function", "modifiers": [], "name": "getLockedAssetsAggregateForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4585, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4584, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4607, "src": "18251:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4583, "name": "address", "nodeType": "ElementaryTypeName", "src": "18251:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "18250:20:6" }, "returnParameters": { "id": 4590, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4587, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4607, "src": "18293:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4586, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18293:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4589, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4607, "src": "18312:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4588, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18312:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "18292:45:6" }, "scope": 5490, "src": "18210:275:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4711, "nodeType": "Block", "src": "18915:654:6", "statements": [ { "assignments": [ 4625 ], "declarations": [ { "constant": false, "id": 4625, "mutability": "mutable", "name": "lockedAssets", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4711, "src": "18925:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" }, "typeName": { "baseType": { "contractScope": null, "id": 4623, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "18925:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "id": 4624, "length": null, "nodeType": "ArrayTypeName", "src": "18925:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" } }, "value": null, "visibility": "internal" } ], "id": 4630, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4626, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "18962:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4628, "indexExpression": { "argumentTypes": null, "id": 4627, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4610, "src": "18970:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "18962:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "id": 4629, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "18962:26:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "18925:63:6" }, { "assignments": [ 4632 ], "declarations": [ { "constant": false, "id": 4632, "mutability": "mutable", "name": "length", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4711, "src": "18998:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4631, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18998:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4635, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4633, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4625, "src": "19015:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 4634, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "19015:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "18998:36:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4638, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4636, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19048:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4637, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "19058:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "19048:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 4710, "nodeType": "IfStatement", "src": "19044:519:6", "trueBody": { "id": 4709, "nodeType": "Block", "src": "19061:502:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4645, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4639, "name": "principal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4614, "src": "19075:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4643, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19101:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4642, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "NewExpression", "src": "19087:13:6", "typeDescriptions": { "typeIdentifier": "t_function_objectcreation_pure$_t_uint256_$returns$_t_array$_t_uint256_$dyn_memory_ptr_$", "typeString": "function (uint256) pure returns (uint256[] memory)" }, "typeName": { "baseType": { "id": 4640, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19091:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4641, "length": null, "nodeType": "ArrayTypeName", "src": "19091:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } } }, "id": 4644, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "19087:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "src": "19075:33:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4646, "nodeType": "ExpressionStatement", "src": "19075:33:6" }, { "expression": { "argumentTypes": null, "id": 4653, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4647, "name": "compoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4617, "src": "19122:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4651, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19155:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4650, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "NewExpression", "src": "19141:13:6", "typeDescriptions": { "typeIdentifier": "t_function_objectcreation_pure$_t_uint256_$returns$_t_array$_t_uint256_$dyn_memory_ptr_$", "typeString": "function (uint256) pure returns (uint256[] memory)" }, "typeName": { "baseType": { "id": 4648, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19145:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4649, "length": null, "nodeType": "ArrayTypeName", "src": "19145:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } } }, "id": 4652, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "19141:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "src": "19122:40:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4654, "nodeType": "ExpressionStatement", "src": "19122:40:6" }, { "expression": { "argumentTypes": null, "id": 4661, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4655, "name": "liquidSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4620, "src": "19176:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4659, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19209:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4658, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "NewExpression", "src": "19195:13:6", "typeDescriptions": { "typeIdentifier": "t_function_objectcreation_pure$_t_uint256_$returns$_t_array$_t_uint256_$dyn_memory_ptr_$", "typeString": "function (uint256) pure returns (uint256[] memory)" }, "typeName": { "baseType": { "id": 4656, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19199:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4657, "length": null, "nodeType": "ArrayTypeName", "src": "19199:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } } }, "id": 4660, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "19195:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "src": "19176:40:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4662, "nodeType": "ExpressionStatement", "src": "19176:40:6" }, { "body": { "id": 4707, "nodeType": "Block", "src": "19266:287:6", "statements": [ { "assignments": [ 4674 ], "declarations": [ { "constant": false, "id": 4674, "mutability": "mutable", "name": "la", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4707, "src": "19284:22:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 4673, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "19284:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 4678, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4675, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4625, "src": "19309:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 4677, "indexExpression": { "argumentTypes": null, "id": 4676, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19322:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "19309:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "19284:40:6" }, { "assignments": [ 4682 ], "declarations": [ { "constant": false, "id": 4682, "mutability": "mutable", "name": "a", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4707, "src": "19342:24:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4681, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "19342:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4685, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4683, "name": "la", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4674, "src": "19369:2:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4684, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "19369:8:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "19342:35:6" }, { "expression": { "argumentTypes": null, "id": 4691, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4686, "name": "principal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4614, "src": "19395:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4688, "indexExpression": { "argumentTypes": null, "id": 4687, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19405:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "19395:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4689, "name": "a", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4682, "src": "19410:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4690, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "19410:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19395:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4692, "nodeType": "ExpressionStatement", "src": "19395:26:6" }, { "expression": { "argumentTypes": null, "id": 4698, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4693, "name": "compoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4617, "src": "19439:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4695, "indexExpression": { "argumentTypes": null, "id": 4694, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19456:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "19439:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4696, "name": "a", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4682, "src": "19461:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4697, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "19461:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19439:40:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4699, "nodeType": "ExpressionStatement", "src": "19439:40:6" }, { "expression": { "argumentTypes": null, "id": 4705, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4700, "name": "liquidSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4620, "src": "19497:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4702, "indexExpression": { "argumentTypes": null, "id": 4701, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19514:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "19497:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4703, "name": "la", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4674, "src": "19519:2:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4704, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "19519:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19497:41:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4706, "nodeType": "ExpressionStatement", "src": "19497:41:6" } ] }, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4669, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4667, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19249:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 4668, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19253:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19249:10:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 4708, "initializationExpression": { "assignments": [ 4664 ], "declarations": [ { "constant": false, "id": 4664, "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4708, "src": "19236:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4663, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19236:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4666, "initialValue": { "argumentTypes": null, "hexValue": "30", "id": 4665, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "19246:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "nodeType": "VariableDeclarationStatement", "src": "19236:11:6" }, "loopExpression": { "expression": { "argumentTypes": null, "id": 4671, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "++", "prefix": true, "src": "19261:3:6", "subExpression": { "argumentTypes": null, "id": 4670, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19263:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4672, "nodeType": "ExpressionStatement", "src": "19261:3:6" }, "nodeType": "ForStatement", "src": "19231:322:6" } ] } } ] }, "documentation": { "id": 4608, "nodeType": "StructuredDocumentation", "src": "18492:226:6", "text": "@dev Returns locked assets decomposed in to 3 separate arrays (principal, compound interest, liquid since block)\n NOTE(pb): This method might be quite expensive, depending on size of locked assets" }, "functionSelector": "5f208f34", "id": 4712, "implemented": true, "kind": "function", "modifiers": [], "name": "getLockedAssetsForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4611, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4610, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18755:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4609, "name": "address", "nodeType": "ElementaryTypeName", "src": "18755:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "18754:20:6" }, "returnParameters": { "id": 4621, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4614, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18813:26:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[]" }, "typeName": { "baseType": { "id": 4612, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18813:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4613, "length": null, "nodeType": "ArrayTypeName", "src": "18813:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4617, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18841:33:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[]" }, "typeName": { "baseType": { "id": 4615, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18841:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4616, "length": null, "nodeType": "ArrayTypeName", "src": "18841:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4620, "mutability": "mutable", "name": "liquidSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18876:33:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[]" }, "typeName": { "baseType": { "id": 4618, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18876:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4619, "length": null, "nodeType": "ArrayTypeName", "src": "18876:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } }, "value": null, "visibility": "internal" } ], "src": "18812:98:6" }, "scope": 5490, "src": "18723:846:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4753, "nodeType": "Block", "src": "19740:260:6", "statements": [ { "assignments": [ 4726 ], "declarations": [ { "constant": false, "id": 4726, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4753, "src": "19750:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 4725, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "19750:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "id": 4730, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4727, "name": "_stakes", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3788, "src": "19772:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake storage ref)" } }, "id": 4729, "indexExpression": { "argumentTypes": null, "id": 4728, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4714, "src": "19780:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "19772:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage", "typeString": "struct Staking.Stake storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "19750:41:6" }, { "expression": { "argumentTypes": null, "id": 4735, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4731, "name": "principal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4717, "src": "19801:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4732, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19813:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4733, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "19813:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4734, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "19813:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19801:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4736, "nodeType": "ExpressionStatement", "src": "19801:33:6" }, { "expression": { "argumentTypes": null, "id": 4741, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4737, "name": "compoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4719, "src": "19844:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4738, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19863:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4739, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "19863:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4740, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "19863:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19844:47:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4742, "nodeType": "ExpressionStatement", "src": "19844:47:6" }, { "expression": { "argumentTypes": null, "id": 4746, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4743, "name": "sinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4721, "src": "19901:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4744, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19914:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4745, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3643, "src": "19914:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19901:29:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4747, "nodeType": "ExpressionStatement", "src": "19901:29:6" }, { "expression": { "argumentTypes": null, "id": 4751, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4748, "name": "sinceInterestRateIndex", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4723, "src": "19940:22:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4749, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19965:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4750, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "19965:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19940:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4752, "nodeType": "ExpressionStatement", "src": "19940:53:6" } ] }, "documentation": null, "functionSelector": "c30f75cf", "id": 4754, "implemented": true, "kind": "function", "modifiers": [], "name": "getStakeForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4715, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4714, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19601:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4713, "name": "address", "nodeType": "ElementaryTypeName", "src": "19601:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "19600:20:6" }, "returnParameters": { "id": 4724, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4717, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19643:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4716, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19643:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4719, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19662:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4718, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19662:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4721, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19688:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4720, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19688:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4723, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19708:30:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4722, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19708:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "19642:97:6" }, "scope": 5490, "src": "19576:424:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4798, "nodeType": "Block", "src": "20599:276:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4767, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4765, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20613:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4766, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "20623:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "20613:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 4770, "nodeType": "IfStatement", "src": "20609:48:6", "trueBody": { "id": 4769, "nodeType": "Block", "src": "20626:31:6", "statements": [ { "expression": null, "functionReturnParameters": 4764, "id": 4768, "nodeType": "Return", "src": "20640:7:6" } ] } }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4774, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "20695:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4775, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "20695:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4778, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "20715:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4777, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "20707:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4776, "name": "address", "nodeType": "ElementaryTypeName", "src": "20707:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4779, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20707:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4780, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20722:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4772, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "20675:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4773, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transferFrom", "nodeType": "MemberAccess", "referencedDeclaration": 6688, "src": "20675:19:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,address,uint256) external returns (bool)" } }, "id": 4781, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20675:54:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73666572206661696c6564", "id": 4782, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "20731:17:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" }, "value": "Transfer failed" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" } ], "id": 4771, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "20667:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4783, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20667:82:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4784, "nodeType": "ExpressionStatement", "src": "20667:82:6" }, { "expression": { "argumentTypes": null, "id": 4790, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4785, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "20759:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4788, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20805:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4786, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "20781:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4787, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "20781:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4789, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20781:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "20759:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4791, "nodeType": "ExpressionStatement", "src": "20759:53:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4793, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "20849:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4794, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "20849:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4795, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20861:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4792, "name": "RewardsPoolTokenTopUp", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3744, "src": "20827:21:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 4796, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20827:41:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4797, "nodeType": "EmitStatement", "src": "20822:46:6" } ] }, "documentation": { "id": 4755, "nodeType": "StructuredDocumentation", "src": "20007:425:6", "text": "@dev Even though this is considered as administrative action (is not affected by\nby contract paused state, it can be executed by anyone who wishes to\ntop-up the rewards pool (funds are sent in to contract, *not* the other way around).\nThe Rewards Pool is exclusively dedicated to cover withdrawals of user' compound interest,\nwhich is effectively the reward." }, "functionSelector": "ba92a4c5", "id": 4799, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4762, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4759, "src": "20576:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4763, "modifierName": { "argumentTypes": null, "id": 4761, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "20557:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "20557:37:6" } ], "name": "topUpRewardsPool", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4760, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4757, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4799, "src": "20472:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4756, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "20472:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4759, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4799, "src": "20496:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4758, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "20496:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "20462:69:6" }, "returnParameters": { "id": 4764, "nodeType": "ParameterList", "parameters": [], "src": "20599:0:6" }, "scope": 5490, "src": "20437:438:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4816, "nodeType": "Block", "src": "21179:47:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4813, "name": "numOfBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4802, "src": "21207:11:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "id": 4812, "name": "_updateLockPeriod", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5079, "src": "21189:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint64_$returns$__$", "typeString": "function (uint64)" } }, "id": 4814, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "21189:30:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4815, "nodeType": "ExpressionStatement", "src": "21189:30:6" } ] }, "documentation": { "id": 4800, "nodeType": "StructuredDocumentation", "src": "20882:131:6", "text": "@notice Updates Lock Period value\n@param numOfBlocks length of the lock period\n@dev Delegate only" }, "functionSelector": "f0895e52", "id": 4817, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4807, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4804, "src": "21135:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4808, "modifierName": { "argumentTypes": null, "id": 4806, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "21116:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "21116:37:6" }, { "arguments": null, "id": 4810, "modifierName": { "argumentTypes": null, "id": 4809, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3822, "src": "21162:12:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "21162:12:6" } ], "name": "updateLockPeriod", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4805, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4802, "mutability": "mutable", "name": "numOfBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4817, "src": "21044:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 4801, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "21044:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4804, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4817, "src": "21064:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4803, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "21064:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "21043:47:6" }, "returnParameters": { "id": 4811, "nodeType": "ParameterList", "parameters": [], "src": "21179:0:6" }, "scope": 5490, "src": "21018:208:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4834, "nodeType": "Block", "src": "21671:41:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4831, "name": "blockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4820, "src": "21693:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4830, "name": "_pauseSince", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5104, "src": "21681:11:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 4832, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "21681:24:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4833, "nodeType": "ExpressionStatement", "src": "21681:24:6" } ] }, "documentation": { "id": 4818, "nodeType": "StructuredDocumentation", "src": "21233:277:6", "text": "@notice Pauses all NON-administrative interaction with the contract since the specidfed block number \n@param blockNumber block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)\n@dev Delegate only" }, "functionSelector": "347908df", "id": 4835, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4825, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4822, "src": "21627:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4826, "modifierName": { "argumentTypes": null, "id": 4824, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "21608:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "21608:37:6" }, { "arguments": null, "id": 4828, "modifierName": { "argumentTypes": null, "id": 4827, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3822, "src": "21654:12:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "21654:12:6" } ], "name": "pauseSince", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4823, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4820, "mutability": "mutable", "name": "blockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4835, "src": "21535:19:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4819, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "21535:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4822, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4835, "src": "21556:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4821, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "21556:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "21534:48:6" }, "returnParameters": { "id": 4829, "nodeType": "ParameterList", "parameters": [], "src": "21671:0:6" }, "scope": 5490, "src": "21515:197:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4909, "nodeType": "Block", "src": "22190:911:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4852, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4850, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22204:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4851, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "22214:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "22204:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4865, "nodeType": "Block", "src": "22276:98:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4861, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4859, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22298:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 4860, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "22308:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "22298:29:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "416d6f756e7420686967686572207468616e207265776172647320706f6f6c", "id": 4862, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "22329:33:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_587ce66cc8dcf85ee5804e9c2a7ad26da4041f2a6217e351e0d49635c54a34e6", "typeString": "literal_string \"Amount higher than rewards pool\"" }, "value": "Amount higher than rewards pool" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_587ce66cc8dcf85ee5804e9c2a7ad26da4041f2a6217e351e0d49635c54a34e6", "typeString": "literal_string \"Amount higher than rewards pool\"" } ], "id": 4858, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "22290:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4863, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22290:73:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4864, "nodeType": "ExpressionStatement", "src": "22290:73:6" } ] }, "id": 4866, "nodeType": "IfStatement", "src": "22200:174:6", "trueBody": { "id": 4857, "nodeType": "Block", "src": "22217:53:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4855, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4853, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22231:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 4854, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "22240:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "22231:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4856, "nodeType": "ExpressionStatement", "src": "22231:28:6" } ] } }, { "assignments": [ 4868 ], "declarations": [ { "constant": false, "id": 4868, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4909, "src": "22562:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4867, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22562:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4876, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4873, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "22613:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4872, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "22605:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4871, "name": "address", "nodeType": "ElementaryTypeName", "src": "22605:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4874, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22605:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 4869, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "22588:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4870, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 6646, "src": "22588:16:6", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 4875, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22588:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "22562:57:6" }, { "assignments": [ 4878 ], "declarations": [ { "constant": false, "id": 4878, "mutability": "mutable", "name": "expectedMinContractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4909, "src": "22629:34:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4877, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22629:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4883, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4881, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22694:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4879, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "22666:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4880, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "22666:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4882, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22666:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "22629:72:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4887, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4885, "name": "expectedMinContractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4878, "src": "22719:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 4886, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4868, "src": "22749:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "22719:45:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "436f6e747261637420696e636f6e73697374656e63792e", "id": 4888, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "22766:25:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_391f4d8f0a702da0afb416db97e6f601a29dc7e134644564cd5e78cc8b2ef1d5", "typeString": "literal_string \"Contract inconsistency.\"" }, "value": "Contract inconsistency." } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_391f4d8f0a702da0afb416db97e6f601a29dc7e134644564cd5e78cc8b2ef1d5", "typeString": "literal_string \"Contract inconsistency.\"" } ], "id": 4884, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "22711:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4889, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22711:81:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4890, "nodeType": "ExpressionStatement", "src": "22711:81:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4894, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4840, "src": "22827:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4895, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22842:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4892, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "22811:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4893, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "22811:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 4896, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22811:38:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e", "id": 4897, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "22851:34:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" }, "value": "Not enough funds on contr. addr." } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" } ], "id": 4891, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "22803:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4898, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22803:83:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4899, "nodeType": "ExpressionStatement", "src": "22803:83:6" }, { "expression": { "argumentTypes": null, "id": 4902, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4900, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "23000:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "-=", "rightHandSide": { "argumentTypes": null, "id": 4901, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "23023:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "23000:29:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4903, "nodeType": "ExpressionStatement", "src": "23000:29:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4905, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4840, "src": "23072:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4906, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "23087:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4904, "name": "RewardsPoolTokenWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3750, "src": "23045:26:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 4907, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "23045:49:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4908, "nodeType": "EmitStatement", "src": "23040:54:6" } ] }, "documentation": { "id": 4836, "nodeType": "StructuredDocumentation", "src": "21719:257:6", "text": "@dev Withdraw tokens from rewards pool.\n * @param amount : amount to withdraw.\n If `amount == 0` then whole amount in rewards pool will be withdrawn.\n@param targetAddress : address to send tokens to" }, "functionSelector": "68bccfcf", "id": 4910, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4845, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4842, "src": "22149:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4846, "modifierName": { "argumentTypes": null, "id": 4844, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "22130:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "22130:37:6" }, { "arguments": null, "id": 4848, "modifierName": { "argumentTypes": null, "id": 4847, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3806, "src": "22176:9:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "22176:9:6" } ], "name": "withdrawFromRewardsPool", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4843, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4838, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4910, "src": "22014:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4837, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22014:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4840, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4910, "src": "22030:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 4839, "name": "address", "nodeType": "ElementaryTypeName", "src": "22030:15:6", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4842, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4910, "src": "22069:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4841, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22069:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "22013:91:6" }, "returnParameters": { "id": 4849, "nodeType": "ParameterList", "parameters": [], "src": "22190:0:6" }, "scope": 5490, "src": "21981:1120:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4961, "nodeType": "Block", "src": "23907:609:6", "statements": [ { "assignments": [ 4924 ], "declarations": [ { "constant": false, "id": 4924, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4961, "src": "23917:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4923, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "23917:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4932, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4929, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "23968:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4928, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "23960:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4927, "name": "address", "nodeType": "ElementaryTypeName", "src": "23960:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4930, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "23960:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 4925, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "23943:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4926, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 6646, "src": "23943:16:6", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 4931, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "23943:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "23917:57:6" }, { "assignments": [ 4934 ], "declarations": [ { "constant": false, "id": 4934, "mutability": "mutable", "name": "expectedMinContractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4961, "src": "23984:34:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4933, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "23984:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4939, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4937, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "24049:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4935, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "24021:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4936, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "24021:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4938, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24021:48:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "23984:85:6" }, { "assignments": [ 4941 ], "declarations": [ { "constant": false, "id": 4941, "mutability": "mutable", "name": "excessAmount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4961, "src": "24275:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4940, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "24275:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4946, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4944, "name": "expectedMinContractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4934, "src": "24318:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4942, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4924, "src": "24298:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4943, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "24298:19:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4945, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24298:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "24275:70:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4950, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4913, "src": "24379:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4951, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4941, "src": "24394:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4948, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "24363:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4949, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "24363:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 4952, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24363:44:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e", "id": 4953, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "24409:34:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" }, "value": "Not enough funds on contr. addr." } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" } ], "id": 4947, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "24355:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4954, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24355:89:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4955, "nodeType": "ExpressionStatement", "src": "24355:89:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4957, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4913, "src": "24481:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4958, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4941, "src": "24496:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4956, "name": "ExcessTokenWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3738, "src": "24459:21:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 4959, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24459:50:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4960, "nodeType": "EmitStatement", "src": "24454:55:6" } ] }, "documentation": { "id": 4911, "nodeType": "StructuredDocumentation", "src": "23108:621:6", "text": "@dev Withdraw \"excess\" tokens, which were sent to contract directly via direct ERC20.transfer(...),\n without interacting with API of this (Staking) contract, what could be done only by mistake.\n Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such\n \"excess\" tokens out of contract.\n@param targetAddress : address to send tokens to\n@param txExpirationBlock : block number until which is the transaction valid (inclusive).\n When transaction is processed after this block, it fails." }, "functionSelector": "53e052ac", "id": 4962, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4918, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4915, "src": "23866:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4919, "modifierName": { "argumentTypes": null, "id": 4917, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "23847:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "23847:37:6" }, { "arguments": null, "id": 4921, "modifierName": { "argumentTypes": null, "id": 4920, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3806, "src": "23893:9:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "23893:9:6" } ], "name": "withdrawExcessTokens", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4916, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4913, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4962, "src": "23764:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 4912, "name": "address", "nodeType": "ElementaryTypeName", "src": "23764:15:6", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4915, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4962, "src": "23795:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4914, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "23795:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "23763:58:6" }, "returnParameters": { "id": 4922, "nodeType": "ParameterList", "parameters": [], "src": "23907:0:6" }, "scope": 5490, "src": "23734:782:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 5008, "nodeType": "Block", "src": "24995:294:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4979, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4976, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3764, "src": "25013:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">=", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4977, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "25032:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4978, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25032:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "25013:36:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4561726c696573742064656c657465206e6f742072656163686564", "id": 4980, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "25051:29:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" }, "value": "Earliest delete not reached" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" } ], "id": 4975, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "25005:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4981, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25005:76:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4982, "nodeType": "ExpressionStatement", "src": "25005:76:6" }, { "assignments": [ 4984 ], "declarations": [ { "constant": false, "id": 4984, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5008, "src": "25091:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4983, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "25091:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4992, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4989, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "25142:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4988, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "25134:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4987, "name": "address", "nodeType": "ElementaryTypeName", "src": "25134:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4990, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25134:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 4985, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "25117:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4986, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 6646, "src": "25117:16:6", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 4991, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25117:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "25091:57:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4996, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4965, "src": "25182:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4997, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4984, "src": "25197:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4994, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "25166:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4995, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "25166:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 4998, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25166:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" } ], "id": 4993, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "25158:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, "id": 4999, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25158:56:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5000, "nodeType": "ExpressionStatement", "src": "25158:56:6" }, { "eventCall": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 5001, "name": "DeleteContract", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3752, "src": "25229:14:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 5002, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25229:16:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5003, "nodeType": "EmitStatement", "src": "25224:21:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5005, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4965, "src": "25268:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 5004, "name": "selfdestruct", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -21, "src": "25255:12:6", "typeDescriptions": { "typeIdentifier": "t_function_selfdestruct_nonpayable$_t_address_payable_$returns$__$", "typeString": "function (address payable)" } }, "id": 5006, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25255:27:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5007, "nodeType": "ExpressionStatement", "src": "25255:27:6" } ] }, "documentation": { "id": 4963, "nodeType": "StructuredDocumentation", "src": "24523:312:6", "text": "@notice Delete the contract, transfers the remaining token and ether balance to the specified\npayoutAddress\n@param payoutAddress address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\n@dev owner only + only on or after `_earliestDelete` block" }, "functionSelector": "9608df4b", "id": 5009, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4970, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4967, "src": "24958:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4971, "modifierName": { "argumentTypes": null, "id": 4969, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "24939:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "24939:37:6" }, { "arguments": null, "id": 4973, "modifierName": { "argumentTypes": null, "id": 4972, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3806, "src": "24981:9:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "24981:9:6" } ], "name": "deleteContract", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4968, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4965, "mutability": "mutable", "name": "payoutAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5009, "src": "24864:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 4964, "name": "address", "nodeType": "ElementaryTypeName", "src": "24864:15:6", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4967, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5009, "src": "24895:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4966, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "24895:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "24863:58:6" }, "returnParameters": { "id": 4974, "nodeType": "ParameterList", "parameters": [], "src": "24995:0:6" }, "scope": 5490, "src": "24840:449:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 5018, "nodeType": "Block", "src": "25630:36:6", "statements": [ { "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5015, "name": "block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -4, "src": "25647:5:6", "typeDescriptions": { "typeIdentifier": "t_magic_block", "typeString": "block" } }, "id": 5016, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "number", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "25647:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 5014, "id": 5017, "nodeType": "Return", "src": "25640:19:6" } ] }, "documentation": { "id": 5010, "nodeType": "StructuredDocumentation", "src": "25431:124:6", "text": "@dev VIRTUAL Method returning bock number. Introduced for \n testing purposes (allows mocking)." }, "id": 5019, "implemented": true, "kind": "function", "modifiers": [], "name": "_getBlockNumber", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5011, "nodeType": "ParameterList", "parameters": [], "src": "25584:2:6" }, "returnParameters": { "id": 5014, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5013, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5019, "src": "25617:7:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5012, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "25617:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "25616:9:6" }, "scope": 5490, "src": "25560:106:6", "stateMutability": "view", "virtual": true, "visibility": "internal" }, { "body": { "id": 5030, "nodeType": "Block", "src": "25721:63:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5025, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5584, "src": "25746:18:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5026, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "25766:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 5027, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "25766:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 5024, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5630, "src": "25738:7:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 5028, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25738:39:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "functionReturnParameters": 5023, "id": 5029, "nodeType": "Return", "src": "25731:46:6" } ] }, "documentation": null, "id": 5031, "implemented": true, "kind": "function", "modifiers": [], "name": "_isOwner", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5020, "nodeType": "ParameterList", "parameters": [], "src": "25690:2:6" }, "returnParameters": { "id": 5023, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5022, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5031, "src": "25715:4:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 5021, "name": "bool", "nodeType": "ElementaryTypeName", "src": "25715:4:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "src": "25714:6:6" }, "scope": 5490, "src": "25673:111:6", "stateMutability": "view", "virtual": false, "visibility": "internal" }, { "body": { "id": 5063, "nodeType": "Block", "src": "26069:336:6", "statements": [ { "assignments": [ 5038 ], "declarations": [ { "constant": false, "id": 5038, "mutability": "mutable", "name": "idx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5063, "src": "26079:11:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5037, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26079:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5040, "initialValue": { "argumentTypes": null, "id": 5039, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "26093:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "26079:35:6" }, { "expression": { "argumentTypes": null, "id": 5049, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5041, "name": "_interestRates", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3784, "src": "26124:14:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock storage ref)" } }, "id": 5043, "indexExpression": { "argumentTypes": null, "id": 5042, "name": "idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5038, "src": "26139:3:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "26124:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 5045, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "26195:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 5046, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26195:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "id": 5047, "name": "rate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5034, "src": "26233:4:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5044, "name": "InterestRatePerBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3641, "src": "26146:20:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_struct$_InterestRatePerBlock_$3641_storage_ptr_$", "typeString": "type(struct Staking.InterestRatePerBlock storage pointer)" } }, "id": 5048, "isConstant": false, "isLValue": false, "isPure": false, "kind": "structConstructorCall", "lValueRequested": false, "names": [ "sinceBlock", "rate" ], "nodeType": "FunctionCall", "src": "26146:148:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_memory_ptr", "typeString": "struct Staking.InterestRatePerBlock memory" } }, "src": "26124:170:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "id": 5050, "nodeType": "ExpressionStatement", "src": "26124:170:6" }, { "expression": { "argumentTypes": null, "id": 5056, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5051, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "26304:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "31", "id": 5054, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "26354:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" } ], "expression": { "argumentTypes": null, "id": 5052, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "26328:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5053, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "26328:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5055, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26328:28:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "26304:52:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5057, "nodeType": "ExpressionStatement", "src": "26304:52:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5059, "name": "idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5038, "src": "26388:3:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "id": 5060, "name": "rate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5034, "src": "26393:4:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5058, "name": "NewInterestRate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3710, "src": "26372:15:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (uint256,uint256)" } }, "id": 5061, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26372:26:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5062, "nodeType": "EmitStatement", "src": "26367:31:6" } ] }, "documentation": { "id": 5032, "nodeType": "StructuredDocumentation", "src": "25791:219:6", "text": "@notice Add new interest rate in to the ordered container of previously added interest rates\n@param rate - signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18" }, "id": 5064, "implemented": true, "kind": "function", "modifiers": [], "name": "_addInterestRate", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5035, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5034, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5064, "src": "26041:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5033, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26041:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "26040:14:6" }, "returnParameters": { "id": 5036, "nodeType": "ParameterList", "parameters": [], "src": "26069:0:6" }, "scope": 5490, "src": "26015:390:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5078, "nodeType": "Block", "src": "26582:88:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5072, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5070, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "26592:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 5071, "name": "numOfBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5067, "src": "26614:11:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "src": "26592:33:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "id": 5073, "nodeType": "ExpressionStatement", "src": "26592:33:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5075, "name": "numOfBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5067, "src": "26651:11:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "id": 5074, "name": "LockPeriod", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3722, "src": "26640:10:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint64_$returns$__$", "typeString": "function (uint64)" } }, "id": 5076, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26640:23:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5077, "nodeType": "EmitStatement", "src": "26635:28:6" } ] }, "documentation": { "id": 5065, "nodeType": "StructuredDocumentation", "src": "26412:105:6", "text": "@notice Updates Lock Period value\n@param numOfBlocks length of the lock period" }, "id": 5079, "implemented": true, "kind": "function", "modifiers": [], "name": "_updateLockPeriod", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5068, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5067, "mutability": "mutable", "name": "numOfBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5079, "src": "26549:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 5066, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "26549:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" } ], "src": "26548:20:6" }, "returnParameters": { "id": 5069, "nodeType": "ParameterList", "parameters": [], "src": "26582:0:6" }, "scope": 5490, "src": "26522:148:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5103, "nodeType": "Block", "src": "26989:199:6", "statements": [ { "assignments": [ 5086 ], "declarations": [ { "constant": false, "id": 5086, "mutability": "mutable", "name": "currentBlockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5103, "src": "26999:26:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5085, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26999:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5089, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 5087, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "27028:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 5088, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "27028:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "26999:46:6" }, { "expression": { "argumentTypes": null, "id": 5097, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5090, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3766, "src": "27055:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5093, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5091, "name": "blockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5082, "src": "27075:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 5092, "name": "currentBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5086, "src": "27089:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "27075:32:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseExpression": { "argumentTypes": null, "id": 5095, "name": "blockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5082, "src": "27131:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5096, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "Conditional", "src": "27075:67:6", "trueExpression": { "argumentTypes": null, "id": 5094, "name": "currentBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5086, "src": "27110:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "27055:87:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5098, "nodeType": "ExpressionStatement", "src": "27055:87:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5100, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3766, "src": "27163:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5099, "name": "Pause", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3726, "src": "27157:5:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 5101, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "27157:24:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5102, "nodeType": "EmitStatement", "src": "27152:29:6" } ] }, "documentation": { "id": 5080, "nodeType": "StructuredDocumentation", "src": "26677:251:6", "text": "@notice Pauses all NON-administrative interaction with the contract since the specidfed block number \n@param blockNumber block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)" }, "id": 5104, "implemented": true, "kind": "function", "modifiers": [], "name": "_pauseSince", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5083, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5082, "mutability": "mutable", "name": "blockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5104, "src": "26954:19:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5081, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26954:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "26953:21:6" }, "returnParameters": { "id": 5084, "nodeType": "ParameterList", "parameters": [], "src": "26989:0:6" }, "scope": 5490, "src": "26933:255:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5167, "nodeType": "Block", "src": "27984:719:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5116, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5114, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5111, "src": "27999:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5115, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "28009:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "27999:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5166, "nodeType": "IfStatement", "src": "27995:702:6", "trueBody": { "id": 5165, "nodeType": "Block", "src": "28012:685:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5121, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5118, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "28034:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">=", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5119, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28057:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5120, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "28057:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "28034:47:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4e6f7420656e6f7567682066756e647320696e207265776172647320706f6f6c", "id": 5122, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "28083:34:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_eea74719e5ae8f4b294d08561dadf2343b6f65323137e057e863d77438841724", "typeString": "literal_string \"Not enough funds in rewards pool\"" }, "value": "Not enough funds in rewards pool" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_eea74719e5ae8f4b294d08561dadf2343b6f65323137e057e863d77438841724", "typeString": "literal_string \"Not enough funds in rewards pool\"" } ], "id": 5117, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "28026:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 5123, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28026:92:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5124, "nodeType": "ExpressionStatement", "src": "28026:92:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5128, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5107, "src": "28156:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 5129, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5111, "src": "28164:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5126, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "28140:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 5127, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "28140:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 5130, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28140:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73666572206661696c6564", "id": 5131, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "28173:17:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" }, "value": "Transfer failed" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" } ], "id": 5125, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "28132:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 5132, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28132:59:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5133, "nodeType": "ExpressionStatement", "src": "28132:59:6" }, { "expression": { "argumentTypes": null, "id": 5140, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5134, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "28206:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5137, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28252:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5138, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "28252:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5135, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "28228:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5136, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "28228:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5139, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28228:49:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "28206:71:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5141, "nodeType": "ExpressionStatement", "src": "28206:71:6" }, { "expression": { "argumentTypes": null, "id": 5148, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5142, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "28291:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5145, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28345:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5146, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "28345:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5143, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "28317:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5144, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "28317:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5147, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28317:46:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "28291:72:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5149, "nodeType": "ExpressionStatement", "src": "28291:72:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5153, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28406:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5150, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "28377:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5152, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "28377:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5154, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28377:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5155, "nodeType": "ExpressionStatement", "src": "28377:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5157, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "28629:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 5158, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "28629:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5159, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28641:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5160, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "28641:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5161, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28660:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5162, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "28660:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5156, "name": "Withdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3718, "src": "28620:8:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 5163, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28620:65:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5164, "nodeType": "EmitStatement", "src": "28615:70:6" } ] } } ] }, "documentation": { "id": 5105, "nodeType": "StructuredDocumentation", "src": "27195:685:6", "text": "@notice Withdraws amount from sender' available liquidity pool back to sender address,\n preferring withdrawal from compound interest dimension of liquidity.\n * @param amount - value to withdraw\n * @dev NOTE(pb): Passing redundant `uint256 amount` (on top of the `Asset _amount`) in the name\n of performance to avoid calculating it again from `_amount` (or the other way around).\n IMPLICATION: Caller **MUST** pass correct values, ensuring that `amount == _amount.composite()`,\n since this private method is **NOT** verifying this condition due to performance reasons." }, "id": 5168, "implemented": true, "kind": "function", "modifiers": [], "name": "_finaliseWithdraw", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5112, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5107, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5168, "src": "27912:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 5106, "name": "address", "nodeType": "ElementaryTypeName", "src": "27912:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5109, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5168, "src": "27928:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 5108, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "27928:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5111, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5168, "src": "27959:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5110, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "27959:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "27911:63:6" }, "returnParameters": { "id": 5113, "nodeType": "ParameterList", "parameters": [], "src": "27984:0:6" }, "scope": 5490, "src": "27885:818:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5314, "nodeType": "Block", "src": "28840:2765:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5181, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5177, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "28850:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5178, "name": "_stakes", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3788, "src": "28858:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake storage ref)" } }, "id": 5180, "indexExpression": { "argumentTypes": null, "id": 5179, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5170, "src": "28866:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "28858:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage", "typeString": "struct Staking.Stake storage ref" } }, "src": "28850:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5182, "nodeType": "ExpressionStatement", "src": "28850:23:6" }, { "assignments": [ 5184 ], "declarations": [ { "constant": false, "id": 5184, "mutability": "mutable", "name": "composite", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5314, "src": "28883:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5183, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "28883:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5189, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5185, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "28903:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5186, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "28903:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5187, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "composite", "nodeType": "MemberAccess", "referencedDeclaration": 2803, "src": "28903:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_struct$_Asset_$2788_storage_ptr_$returns$_t_uint256_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer) view returns (uint256)" } }, "id": 5188, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28903:23:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "28883:43:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5192, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5190, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "28940:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5191, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "28953:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "28940:14:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5281, "nodeType": "IfStatement", "src": "28936:1369:6", "trueBody": { "id": 5280, "nodeType": "Block", "src": "28964:1341:6", "statements": [ { "assignments": [ 5194 ], "declarations": [ { "constant": false, "id": 5194, "mutability": "mutable", "name": "start_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5280, "src": "29048:19:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5193, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29048:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5197, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5195, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "29070:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5196, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3643, "src": "29070:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29048:38:6" }, { "body": { "id": 5265, "nodeType": "Block", "src": "29411:802:6", "statements": [ { "assignments": [ 5210 ], "declarations": [ { "constant": false, "id": 5210, "mutability": "mutable", "name": "interest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5265, "src": "29429:37:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" }, "typeName": { "contractScope": null, "id": 5209, "name": "InterestRatePerBlock", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3641, "src": "29429:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" } }, "value": null, "visibility": "internal" } ], "id": 5214, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5211, "name": "_interestRates", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3784, "src": "29469:14:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock storage ref)" } }, "id": 5213, "indexExpression": { "argumentTypes": null, "id": 5212, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29484:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "29469:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "29429:57:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5219, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5216, "name": "interest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5210, "src": "29697:8:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock storage pointer" } }, "id": 5217, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3638, "src": "29697:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 5218, "name": "start_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5194, "src": "29720:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29697:34:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "73696e6365426c6f636b20696e636f6e73697374656e6379", "id": 5220, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "29733:26:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_0b30ca9f63e0c2596e072157a3416c4519a0da0c7a77930c294d44b40e819c8c", "typeString": "literal_string \"sinceBlock inconsistency\"" }, "value": "sinceBlock inconsistency" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_0b30ca9f63e0c2596e072157a3416c4519a0da0c7a77930c294d44b40e819c8c", "typeString": "literal_string \"sinceBlock inconsistency\"" } ], "id": 5215, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "29689:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 5221, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "29689:71:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5222, "nodeType": "ExpressionStatement", "src": "29689:71:6" }, { "assignments": [ 5224 ], "declarations": [ { "constant": false, "id": 5224, "mutability": "mutable", "name": "end_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5265, "src": "29778:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5223, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29778:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5226, "initialValue": { "argumentTypes": null, "id": 5225, "name": "at_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5172, "src": "29798:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29778:28:6" }, { "assignments": [ 5228 ], "declarations": [ { "constant": false, "id": 5228, "mutability": "mutable", "name": "j", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5265, "src": "29825:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5227, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29825:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5232, "initialValue": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5231, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5229, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29837:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "+", "rightExpression": { "argumentTypes": null, "hexValue": "31", "id": 5230, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "29841:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" }, "src": "29837:5:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29825:17:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5235, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5233, "name": "j", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5228, "src": "29864:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 5234, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "29868:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29864:25:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5248, "nodeType": "IfStatement", "src": "29860:192:6", "trueBody": { "id": 5247, "nodeType": "Block", "src": "29891:161:6", "statements": [ { "assignments": [ 5237 ], "declarations": [ { "constant": false, "id": 5237, "mutability": "mutable", "name": "next_interest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5247, "src": "29913:42:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" }, "typeName": { "contractScope": null, "id": 5236, "name": "InterestRatePerBlock", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3641, "src": "29913:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" } }, "value": null, "visibility": "internal" } ], "id": 5241, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5238, "name": "_interestRates", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3784, "src": "29958:14:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock storage ref)" } }, "id": 5240, "indexExpression": { "argumentTypes": null, "id": 5239, "name": "j", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5228, "src": "29973:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "29958:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "29913:62:6" }, { "expression": { "argumentTypes": null, "id": 5245, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5242, "name": "end_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5224, "src": "29997:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5243, "name": "next_interest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5237, "src": "30009:13:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock storage pointer" } }, "id": 5244, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3638, "src": "30009:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29997:36:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5246, "nodeType": "ExpressionStatement", "src": "29997:36:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5259, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5249, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "30070:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5252, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "30107:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5253, "name": "interest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5210, "src": "30118:8:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock storage pointer" } }, "id": 5254, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "rate", "nodeType": "MemberAccess", "referencedDeclaration": 3640, "src": "30118:13:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5257, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5255, "name": "end_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5224, "src": "30133:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "-", "rightExpression": { "argumentTypes": null, "id": 5256, "name": "start_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5194, "src": "30145:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30133:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5250, "name": "Finance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3318, "src": "30082:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_Finance_$3318_$", "typeString": "type(library Finance)" } }, "id": 5251, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 3249, "src": "30082:24:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$_t_uint256_$returns$_t_uint256_$", "typeString": "function (uint256,uint256,uint256) pure returns (uint256)" } }, "id": 5258, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "30082:75:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30070:87:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5260, "nodeType": "ExpressionStatement", "src": "30070:87:6" }, { "expression": { "argumentTypes": null, "id": 5263, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5261, "name": "start_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5194, "src": "30175:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 5262, "name": "end_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5224, "src": "30189:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30175:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5264, "nodeType": "ExpressionStatement", "src": "30175:23:6" } ] }, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5205, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5203, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29379:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 5204, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "29383:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29379:25:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 5266, "initializationExpression": { "assignments": [ 5199 ], "declarations": [ { "constant": false, "id": 5199, "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5266, "src": "29339:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5198, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29339:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5202, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5200, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "29349:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5201, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "29349:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29339:38:6" }, "loopExpression": { "expression": { "argumentTypes": null, "id": 5207, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "++", "prefix": true, "src": "29406:3:6", "subExpression": { "argumentTypes": null, "id": 5206, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29408:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5208, "nodeType": "ExpressionStatement", "src": "29406:3:6" }, "nodeType": "ForStatement", "src": "29334:879:6" }, { "expression": { "argumentTypes": null, "id": 5278, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5267, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30227:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5270, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "30227:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5271, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "30227:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5274, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30272:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5275, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "30272:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5276, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "30272:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5272, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "30258:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5273, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "30258:13:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5277, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "30258:36:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30227:67:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5279, "nodeType": "ExpressionStatement", "src": "30227:67:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5286, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5282, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30315:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5284, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3643, "src": "30315:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 5285, "name": "at_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5172, "src": "30334:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30315:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5287, "nodeType": "ExpressionStatement", "src": "30315:27:6" }, { "expression": { "argumentTypes": null, "id": 5300, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5288, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30352:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5290, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "30352:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "components": [ { "argumentTypes": null, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5293, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5291, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "30384:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5292, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "30409:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "30384:26:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseExpression": { "argumentTypes": null, "hexValue": "30", "id": 5297, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "30441:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "id": 5298, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "Conditional", "src": "30384:58:6", "trueExpression": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5296, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5294, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "30413:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "-", "rightExpression": { "argumentTypes": null, "hexValue": "31", "id": 5295, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "30437:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" }, "src": "30413:25:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 5299, "isConstant": false, "isInlineArray": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "TupleExpression", "src": "30383:60:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30352:91:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5301, "nodeType": "ExpressionStatement", "src": "30352:91:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5303, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5170, "src": "31508:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5304, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "31516:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5305, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "31516:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5306, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "31546:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5307, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "31546:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5308, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "31546:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5309, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "31569:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5310, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "31569:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5311, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "31569:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5302, "name": "StakeCompoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3680, "src": "31486:21:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 5312, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "31486:112:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5313, "nodeType": "EmitStatement", "src": "31481:117:6" } ] }, "documentation": null, "id": 5315, "implemented": true, "kind": "function", "modifiers": [], "name": "_updateStakeCompoundInterest", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5173, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5170, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5315, "src": "28748:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 5169, "name": "address", "nodeType": "ElementaryTypeName", "src": "28748:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5172, "mutability": "mutable", "name": "at_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5315, "src": "28764:16:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5171, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "28764:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "28747:34:6" }, "returnParameters": { "id": 5176, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5175, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5315, "src": "28815:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 5174, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "28815:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "src": "28814:21:6" }, "scope": 5490, "src": "28710:2895:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5488, "nodeType": "Block", "src": "31801:2505:6", "statements": [ { "assignments": [ 5329 ], "declarations": [ { "constant": false, "id": 5329, "mutability": "mutable", "name": "locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5488, "src": "31811:21:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" }, "typeName": { "contractScope": null, "id": 5328, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "31811:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } }, "value": null, "visibility": "internal" } ], "id": 5333, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5330, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "31835:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 5332, "indexExpression": { "argumentTypes": null, "id": 5331, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "31843:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "31835:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "31811:39:6" }, { "assignments": [ 5337 ], "declarations": [ { "constant": false, "id": 5337, "mutability": "mutable", "name": "lockedAssets", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5488, "src": "31860:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" }, "typeName": { "baseType": { "contractScope": null, "id": 5335, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "31860:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "id": 5336, "length": null, "nodeType": "ArrayTypeName", "src": "31860:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" } }, "value": null, "visibility": "internal" } ], "id": 5340, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5338, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5329, "src": "31897:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 5339, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "31897:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "31860:50:6" }, { "expression": { "argumentTypes": null, "id": 5345, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5341, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5324, "src": "31920:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5342, "name": "_liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3796, "src": "31932:10:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset storage ref)" } }, "id": 5344, "indexExpression": { "argumentTypes": null, "id": 5343, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "31943:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "31932:18:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "src": "31920:30:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 5346, "nodeType": "ExpressionStatement", "src": "31920:30:6" }, { "body": { "id": 5420, "nodeType": "Block", "src": "32006:1628:6", "statements": [ { "assignments": [ 5356 ], "declarations": [ { "constant": false, "id": 5356, "mutability": "mutable", "name": "l", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5420, "src": "32020:20:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 5355, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "32020:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 5360, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5357, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "32043:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5359, "indexExpression": { "argumentTypes": null, "id": 5358, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "32056:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "32043:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "32020:38:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5364, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5361, "name": "l", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5356, "src": "32077:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset memory" } }, "id": 5362, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "32077:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "id": 5363, "name": "at_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5319, "src": "32098:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "32077:29:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5370, "nodeType": "IfStatement", "src": "32073:191:6", "trueBody": { "id": 5369, "nodeType": "Block", "src": "32108:156:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5366, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "++", "prefix": true, "src": "32126:3:6", "subExpression": { "argumentTypes": null, "id": 5365, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "32128:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5367, "nodeType": "ExpressionStatement", "src": "32126:3:6" }, { "id": 5368, "nodeType": "Continue", "src": "32241:8:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5381, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5371, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32278:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5373, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "32278:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5377, "name": "l", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5356, "src": "32340:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset memory" } }, "id": 5378, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "32340:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5379, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "32340:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5374, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32308:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5375, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "32308:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5376, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "32308:31:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5380, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "32308:50:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "32278:80:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5382, "nodeType": "ExpressionStatement", "src": "32278:80:6" }, { "expression": { "argumentTypes": null, "id": 5393, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5383, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32576:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5385, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "32576:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5389, "name": "l", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5356, "src": "32652:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset memory" } }, "id": 5390, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "32652:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5391, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "32652:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5386, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32613:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5387, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "32613:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5388, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "32613:38:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5392, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "32613:64:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "32576:101:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5394, "nodeType": "ExpressionStatement", "src": "32576:101:6" }, { "assignments": [ 5396 ], "declarations": [ { "constant": false, "id": 5396, "mutability": "mutable", "name": "last_idx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5420, "src": "32959:16:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5395, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "32959:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5401, "initialValue": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5400, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5397, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "32978:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5398, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "32978:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "-", "rightExpression": { "argumentTypes": null, "hexValue": "31", "id": 5399, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33000:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" }, "src": "32978:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "32959:42:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5404, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5402, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "33019:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "id": 5403, "name": "last_idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5396, "src": "33024:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "33019:13:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5414, "nodeType": "IfStatement", "src": "33015:92:6", "trueBody": { "id": 5413, "nodeType": "Block", "src": "33034:73:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5411, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5405, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33052:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5407, "indexExpression": { "argumentTypes": null, "id": 5406, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "33065:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "33052:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5408, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33070:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5410, "indexExpression": { "argumentTypes": null, "id": 5409, "name": "last_idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5396, "src": "33083:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "33070:22:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "src": "33052:40:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "id": 5412, "nodeType": "ExpressionStatement", "src": "33052:40:6" } ] } }, { "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 5415, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33605:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5417, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "pop", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "33605:16:6", "typeDescriptions": { "typeIdentifier": "t_function_arraypop_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 5418, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "33605:18:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5419, "nodeType": "ExpressionStatement", "src": "33605:18:6" } ] }, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5354, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5351, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "31979:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5352, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "31983:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5353, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "31983:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "31979:23:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 5421, "initializationExpression": { "assignments": [ 5348 ], "declarations": [ { "constant": false, "id": 5348, "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5421, "src": "31966:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5347, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "31966:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5350, "initialValue": { "argumentTypes": null, "hexValue": "30", "id": 5349, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "31976:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "nodeType": "VariableDeclarationStatement", "src": "31966:11:6" }, "loopExpression": null, "nodeType": "ForStatement", "src": "31961:1673:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5425, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5422, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33699:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5423, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "33699:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5424, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33722:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "33699:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5432, "nodeType": "IfStatement", "src": "33695:77:6", "trueBody": { "id": 5431, "nodeType": "Block", "src": "33725:47:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5429, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "delete", "prefix": true, "src": "33739:22:6", "subExpression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5426, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "33746:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 5428, "indexExpression": { "argumentTypes": null, "id": 5427, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "33754:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "33746:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5430, "nodeType": "ExpressionStatement", "src": "33739:22:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5443, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5433, "name": "collected", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5326, "src": "33782:9:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_bool", "typeString": "bool" }, "id": 5442, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5437, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5434, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33794:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5435, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "33794:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5436, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33825:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "33794:32:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "BinaryOperation", "operator": "||", "rightExpression": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5441, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5438, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33830:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5439, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "33830:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5440, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33868:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "33830:39:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "33794:75:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "33782:87:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 5444, "nodeType": "ExpressionStatement", "src": "33782:87:6" }, { "condition": { "argumentTypes": null, "id": 5445, "name": "collected", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5326, "src": "33883:9:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5487, "nodeType": "IfStatement", "src": "33879:421:6", "trueBody": { "id": 5486, "nodeType": "Block", "src": "33894:406:6", "statements": [ { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5447, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "33932:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5448, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33940:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5449, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "33940:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5450, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33969:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5451, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "33969:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5446, "name": "LiquidityUnlocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3694, "src": "33914:17:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 5452, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "33914:90:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5453, "nodeType": "EmitStatement", "src": "33909:95:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5457, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34045:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5454, "name": "_accruedGlobalLocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3776, "src": "34019:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5456, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "34019:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5458, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34019:44:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5459, "nodeType": "ExpressionStatement", "src": "34019:44:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5463, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5460, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "34081:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5461, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "34081:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5462, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "34104:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "34081:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5473, "nodeType": "IfStatement", "src": "34077:103:6", "trueBody": { "id": 5472, "nodeType": "Block", "src": "34107:73:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5469, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34147:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5464, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5329, "src": "34125:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 5467, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "34125:16:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5468, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "34125:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5470, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34125:40:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5471, "nodeType": "ExpressionStatement", "src": "34125:40:6" } ] } }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5477, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34223:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5474, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "34194:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5476, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "34194:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5478, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34194:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5479, "nodeType": "ExpressionStatement", "src": "34194:47:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5483, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34271:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5480, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5324, "src": "34256:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 5482, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "34256:14:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5484, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34256:33:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5485, "nodeType": "ExpressionStatement", "src": "34256:33:6" } ] } } ] }, "documentation": null, "id": 5489, "implemented": true, "kind": "function", "modifiers": [], "name": "_collectLiquidity", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5320, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5317, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31639:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 5316, "name": "address", "nodeType": "ElementaryTypeName", "src": "31639:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5319, "mutability": "mutable", "name": "at_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31655:16:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5318, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "31655:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "31638:34:6" }, "returnParameters": { "id": 5327, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5322, "mutability": "mutable", "name": "unlockedLiquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31706:39:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 5321, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "31706:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5324, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31747:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 5323, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "31747:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5326, "mutability": "mutable", "name": "collected", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31781:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 5325, "name": "bool", "nodeType": "ElementaryTypeName", "src": "31781:4:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "src": "31705:91:6" }, "scope": 5490, "src": "31612:2694:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" } ], "scope": 5491, "src": "1040:33269:6" } ], "src": "820:33490:6" }, "legacyAST": { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Staking.sol", "exportedSymbols": { "Staking": [ 5490 ] }, "id": 5491, "license": "Apache-2.0", "nodeType": "SourceUnit", "nodes": [ { "id": 3625, "literals": [ "solidity", "^", "0.6", ".0" ], "nodeType": "PragmaDirective", "src": "820:23:6" }, { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/token/ERC20/IERC20.sol", "file": "../openzeppelin/contracts/token/ERC20/IERC20.sol", "id": 3626, "nodeType": "ImportDirective", "scope": 5491, "sourceUnit": 6708, "src": "845:58:6", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/openzeppelin/contracts/access/AccessControl.sol", "file": "../openzeppelin/contracts/access/AccessControl.sol", "id": 3627, "nodeType": "ImportDirective", "scope": 5491, "sourceUnit": 5842, "src": "904:60:6", "symbolAliases": [], "unitAlias": "" }, { "absolutePath": "/Users/pb/code/fetch/cosmos/ledger-staking-contract/contracts/Finance.sol", "file": "./Finance.sol", "id": 3628, "nodeType": "ImportDirective", "scope": 5491, "sourceUnit": 3319, "src": "965:23:6", "symbolAliases": [], "unitAlias": "" }, { "abstract": false, "baseContracts": [ { "arguments": null, "baseName": { "contractScope": null, "id": 3629, "name": "AccessControl", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 5841, "src": "1060:13:6", "typeDescriptions": { "typeIdentifier": "t_contract$_AccessControl_$5841", "typeString": "contract AccessControl" } }, "id": 3630, "nodeType": "InheritanceSpecifier", "src": "1060:13:6" } ], "contractDependencies": [ 5558, 5841 ], "contractKind": "contract", "documentation": null, "fullyImplemented": true, "id": 5490, "linearizedBaseContracts": [ 5490, 5841, 5558 ], "name": "Staking", "nodeType": "ContractDefinition", "nodes": [ { "id": 3633, "libraryName": { "contractScope": null, "id": 3631, "name": "SafeMath", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 6037, "src": "1086:8:6", "typeDescriptions": { "typeIdentifier": "t_contract$_SafeMath_$6037", "typeString": "library SafeMath" } }, "nodeType": "UsingForDirective", "src": "1080:27:6", "typeName": { "id": 3632, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1099:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } }, { "id": 3636, "libraryName": { "contractScope": null, "id": 3634, "name": "AssetLib", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3121, "src": "1118:8:6", "typeDescriptions": { "typeIdentifier": "t_contract$_AssetLib_$3121", "typeString": "library AssetLib" } }, "nodeType": "UsingForDirective", "src": "1112:34:6", "typeName": { "contractScope": null, "id": 3635, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1131:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } } }, { "canonicalName": "Staking.InterestRatePerBlock", "id": 3641, "members": [ { "constant": false, "id": 3638, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3641, "src": "1190:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3637, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1190:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3640, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3641, "src": "1294:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3639, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1294:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "name": "InterestRatePerBlock", "nodeType": "StructDefinition", "scope": 5490, "src": "1152:531:6", "visibility": "public" }, { "canonicalName": "Staking.Stake", "id": 3648, "members": [ { "constant": false, "id": 3643, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3648, "src": "1712:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3642, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1712:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3645, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3648, "src": "1740:30:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3644, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1740:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3647, "mutability": "mutable", "name": "asset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3648, "src": "1780:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3646, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1780:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "name": "Stake", "nodeType": "StructDefinition", "scope": 5490, "src": "1689:118:6", "visibility": "public" }, { "canonicalName": "Staking.LockedAsset", "id": 3653, "members": [ { "constant": false, "id": 3650, "mutability": "mutable", "name": "liquidSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3653, "src": "1842:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3649, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "1842:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3652, "mutability": "mutable", "name": "asset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3653, "src": "1876:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3651, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1876:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "name": "LockedAsset", "nodeType": "StructDefinition", "scope": 5490, "src": "1813:90:6", "visibility": "public" }, { "canonicalName": "Staking.Locked", "id": 3659, "members": [ { "constant": false, "id": 3655, "mutability": "mutable", "name": "aggregate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3659, "src": "1933:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3654, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "1933:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3658, "mutability": "mutable", "name": "assets", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3659, "src": "1967:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" }, "typeName": { "baseType": { "contractScope": null, "id": 3656, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "1967:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "id": 3657, "length": null, "nodeType": "ArrayTypeName", "src": "1967:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" } }, "value": null, "visibility": "internal" } ], "name": "Locked", "nodeType": "StructDefinition", "scope": 5490, "src": "1909:85:6", "visibility": "public" }, { "anonymous": false, "documentation": null, "id": 3669, "name": "BindStake", "nodeType": "EventDefinition", "parameters": { "id": 3668, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3661, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2064:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3660, "name": "address", "nodeType": "ElementaryTypeName", "src": "2064:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3663, "indexed": true, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2104:38:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3662, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2104:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3665, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2153:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3664, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2153:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3667, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3669, "src": "2181:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3666, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2181:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "2052:159:6" }, "src": "2037:175:6" }, { "anonymous": false, "documentation": { "id": 3670, "nodeType": "StructuredDocumentation", "src": "2218:621:6", "text": "@dev This event is triggered exclusivelly to recalculate the compount interest of ALREADY staked asset\n for the poriod since it was calculated the last time. This means this event does *NOT* include *YET*\n any added (resp. removed) asset user is currently binding (resp. unbinding).\n The main motivation for this event is to give listener opportunity to get feedback what is the \n user's staked asset value with compound interrest recalculated to *CURRENT* block *BEFORE* user's\n action (binding resp. unbinding) affects user's staked asset value." }, "id": 3680, "name": "StakeCompoundInterest", "nodeType": "EventDefinition", "parameters": { "id": 3679, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3672, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "2883:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3671, "name": "address", "nodeType": "ElementaryTypeName", "src": "2883:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3674, "indexed": true, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "2923:38:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3673, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2923:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3676, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "2972:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3675, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "2972:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3678, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3680, "src": "3024:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3677, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3024:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "2871:262:6" }, "src": "2844:290:6" }, { "anonymous": false, "documentation": null, "id": 3686, "name": "LiquidityDeposited", "nodeType": "EventDefinition", "parameters": { "id": 3685, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3682, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3686, "src": "3176:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3681, "name": "address", "nodeType": "ElementaryTypeName", "src": "3176:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3684, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3686, "src": "3216:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3683, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3216:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3164:72:6" }, "src": "3140:97:6" }, { "anonymous": false, "documentation": null, "id": 3694, "name": "LiquidityUnlocked", "nodeType": "EventDefinition", "parameters": { "id": 3693, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3688, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3694, "src": "3278:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3687, "name": "address", "nodeType": "ElementaryTypeName", "src": "3278:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3690, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3694, "src": "3318:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3689, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3318:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3692, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3694, "src": "3346:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3691, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3346:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3266:110:6" }, "src": "3243:134:6" }, { "anonymous": false, "documentation": null, "id": 3704, "name": "UnbindStake", "nodeType": "EventDefinition", "parameters": { "id": 3703, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3696, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3412:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3695, "name": "address", "nodeType": "ElementaryTypeName", "src": "3412:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3698, "indexed": true, "mutability": "mutable", "name": "liquidSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3452:32:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3697, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3452:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3700, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3495:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3699, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3495:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3702, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3704, "src": "3523:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3701, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3523:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3400:153:6" }, "src": "3383:171:6" }, { "anonymous": false, "documentation": null, "id": 3710, "name": "NewInterestRate", "nodeType": "EventDefinition", "parameters": { "id": 3709, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3706, "indexed": true, "mutability": "mutable", "name": "index", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3710, "src": "3593:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3705, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3593:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3708, "indexed": false, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3710, "src": "3625:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3707, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3625:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3581:133:6" }, "src": "3560:155:6" }, { "anonymous": false, "documentation": null, "id": 3718, "name": "Withdraw", "nodeType": "EventDefinition", "parameters": { "id": 3717, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3712, "indexed": true, "mutability": "mutable", "name": "stakerAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3718, "src": "3747:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3711, "name": "address", "nodeType": "ElementaryTypeName", "src": "3747:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3714, "indexed": false, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3718, "src": "3787:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3713, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3787:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3716, "indexed": false, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3718, "src": "3815:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3715, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3815:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3735:110:6" }, "src": "3721:125:6" }, { "anonymous": false, "documentation": null, "id": 3722, "name": "LockPeriod", "nodeType": "EventDefinition", "parameters": { "id": 3721, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3720, "indexed": false, "mutability": "mutable", "name": "numOfBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3722, "src": "3869:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 3719, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "3869:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" } ], "src": "3868:20:6" }, "src": "3852:37:6" }, { "anonymous": false, "documentation": null, "id": 3726, "name": "Pause", "nodeType": "EventDefinition", "parameters": { "id": 3725, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3724, "indexed": false, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3726, "src": "3906:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3723, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3906:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3905:20:6" }, "src": "3894:32:6" }, { "anonymous": false, "documentation": null, "id": 3732, "name": "TokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 3731, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3728, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3732, "src": "3953:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3727, "name": "address", "nodeType": "ElementaryTypeName", "src": "3953:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3730, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3732, "src": "3976:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3729, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "3976:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "3952:39:6" }, "src": "3931:61:6" }, { "anonymous": false, "documentation": null, "id": 3738, "name": "ExcessTokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 3737, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3734, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3738, "src": "4025:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3733, "name": "address", "nodeType": "ElementaryTypeName", "src": "4025:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3736, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3738, "src": "4048:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3735, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4048:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4024:39:6" }, "src": "3997:67:6" }, { "anonymous": false, "documentation": null, "id": 3744, "name": "RewardsPoolTokenTopUp", "nodeType": "EventDefinition", "parameters": { "id": 3743, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3740, "indexed": false, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3744, "src": "4097:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3739, "name": "address", "nodeType": "ElementaryTypeName", "src": "4097:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3742, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3744, "src": "4113:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3741, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4113:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4096:32:6" }, "src": "4069:60:6" }, { "anonymous": false, "documentation": null, "id": 3750, "name": "RewardsPoolTokenWithdrawal", "nodeType": "EventDefinition", "parameters": { "id": 3749, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3746, "indexed": false, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3750, "src": "4167:21:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3745, "name": "address", "nodeType": "ElementaryTypeName", "src": "4167:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3748, "indexed": false, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3750, "src": "4190:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3747, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4190:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "4166:39:6" }, "src": "4134:72:6" }, { "anonymous": false, "documentation": null, "id": 3752, "name": "DeleteContract", "nodeType": "EventDefinition", "parameters": { "id": 3751, "nodeType": "ParameterList", "parameters": [], "src": "4231:2:6" }, "src": "4211:23:6" }, { "constant": true, "functionSelector": "c0ba241b", "id": 3757, "mutability": "constant", "name": "DELEGATE_ROLE", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "4241:66:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, "typeName": { "id": 3753, "name": "bytes32", "nodeType": "ElementaryTypeName", "src": "4241:7:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "value": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "44454c45474154455f524f4c45", "id": 3755, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "4291:15:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" }, "value": "DELEGATE_ROLE" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_stringliteral_1a82baf2b928242f69f7147fb92490c6288d044f7257b88817e6284f1eec0f15", "typeString": "literal_string \"DELEGATE_ROLE\"" } ], "id": 3754, "name": "keccak256", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -8, "src": "4281:9:6", "typeDescriptions": { "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", "typeString": "function (bytes memory) pure returns (bytes32)" } }, "id": 3756, "isConstant": false, "isLValue": false, "isPure": true, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "4281:26:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, "visibility": "public" }, { "constant": true, "functionSelector": "372646bb", "id": 3760, "mutability": "constant", "name": "DELETE_PROTECTION_PERIOD", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "4313:57:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3758, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "4313:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": { "argumentTypes": null, "hexValue": "333730323835", "id": 3759, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "4364:6:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_370285_by_1", "typeString": "int_const 370285" }, "value": "370285" }, "visibility": "public" }, { "constant": false, "functionSelector": "ecd0c0c3", "id": 3762, "mutability": "mutable", "name": "_token", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "4427:20:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" }, "typeName": { "contractScope": null, "id": 3761, "name": "IERC20", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 6707, "src": "4427:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "32a1bd70", "id": 3764, "mutability": "mutable", "name": "_earliestDelete", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5201:30:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3763, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5201:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "f1209ef7", "id": 3766, "mutability": "mutable", "name": "_pausedSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5242:32:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3765, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5242:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "2514c50a", "id": 3768, "mutability": "mutable", "name": "_lockPeriodInBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5280:33:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 3767, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "5280:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "0c450f9d", "id": 3770, "mutability": "mutable", "name": "_rewardsPoolBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5441:34:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3769, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5441:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "292911fb", "id": 3772, "mutability": "mutable", "name": "_accruedGlobalPrincipal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5608:38:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3771, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5608:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "c8984ab2", "id": 3774, "mutability": "mutable", "name": "_accruedGlobalLiquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5652:45:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3773, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "5652:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "d9b202c9", "id": 3776, "mutability": "mutable", "name": "_accruedGlobalLocked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5712:42:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3775, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "5712:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "d9d8e783", "id": 3778, "mutability": "mutable", "name": "_interestRatesStartIdx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5770:37:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3777, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5770:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "4164b001", "id": 3780, "mutability": "mutable", "name": "_interestRatesNextIdx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5813:36:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3779, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5813:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "public" }, { "constant": false, "functionSelector": "aa700ff2", "id": 3784, "mutability": "mutable", "name": "_interestRates", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5855:62:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock)" }, "typeName": { "id": 3783, "keyType": { "id": 3781, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "5863:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Mapping", "src": "5855:40:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock)" }, "valueType": { "contractScope": null, "id": 3782, "name": "InterestRatePerBlock", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3641, "src": "5874:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" } } }, "value": null, "visibility": "public" }, { "constant": false, "id": 3788, "mutability": "mutable", "name": "_stakes", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5924:33:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake)" }, "typeName": { "id": 3787, "keyType": { "id": 3785, "name": "address", "nodeType": "ElementaryTypeName", "src": "5932:7:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Mapping", "src": "5924:25:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake)" }, "valueType": { "contractScope": null, "id": 3786, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "5943:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3792, "mutability": "mutable", "name": "_locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "5963:34:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked)" }, "typeName": { "id": 3791, "keyType": { "id": 3789, "name": "address", "nodeType": "ElementaryTypeName", "src": "5971:7:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Mapping", "src": "5963:26:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked)" }, "valueType": { "contractScope": null, "id": 3790, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "5982:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } } }, "value": null, "visibility": "internal" }, { "constant": false, "functionSelector": "67ce50c0", "id": 3796, "mutability": "mutable", "name": "_liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5490, "src": "6003:52:6", "stateVariable": true, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset)" }, "typeName": { "id": 3795, "keyType": { "id": 3793, "name": "address", "nodeType": "ElementaryTypeName", "src": "6011:7:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "nodeType": "Mapping", "src": "6003:34:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset)" }, "valueType": { "contractScope": null, "id": 3794, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "6022:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } } }, "value": null, "visibility": "public" }, { "body": { "id": 3805, "nodeType": "Block", "src": "6117:73:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3799, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5031, "src": "6135:8:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 3800, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6135:10:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e6f7420616e206f776e6572", "id": 3801, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6147:24:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fba574a628f13337c8d554e12acf25c09c56b020b31ccfaf084049163a70a41b", "typeString": "literal_string \"Caller is not an owner\"" }, "value": "Caller is not an owner" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fba574a628f13337c8d554e12acf25c09c56b020b31ccfaf084049163a70a41b", "typeString": "literal_string \"Caller is not an owner\"" } ], "id": 3798, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6127:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3802, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6127:45:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3803, "nodeType": "ExpressionStatement", "src": "6127:45:6" }, { "id": 3804, "nodeType": "PlaceholderStatement", "src": "6182:1:6" } ] }, "documentation": null, "id": 3806, "name": "onlyOwner", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3797, "nodeType": "ParameterList", "parameters": [], "src": "6114:2:6" }, "src": "6096:94:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3821, "nodeType": "Block", "src": "6265:125:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_bool", "typeString": "bool" }, "id": 3816, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3809, "name": "_isOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5031, "src": "6283:8:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", "typeString": "function () view returns (bool)" } }, "id": 3810, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6283:10:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "BinaryOperation", "operator": "||", "rightExpression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3812, "name": "DELEGATE_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3757, "src": "6305:13:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3813, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "6320:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3814, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "6320:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 3811, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5630, "src": "6297:7:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 3815, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6297:34:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "6283:48:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "43616c6c6572206973206e656974686572206f776e6572206e6f722064656c6567617465", "id": 3817, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6333:38:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" }, "value": "Caller is neither owner nor delegate" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_3c2cbf62f56025e2baf3b46dabe4ee1ab0f4be1432d0bef94b4d166423814999", "typeString": "literal_string \"Caller is neither owner nor delegate\"" } ], "id": 3808, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6275:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3818, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6275:97:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3819, "nodeType": "ExpressionStatement", "src": "6275:97:6" }, { "id": 3820, "nodeType": "PlaceholderStatement", "src": "6382:1:6" } ] }, "documentation": null, "id": 3822, "name": "onlyDelegate", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3807, "nodeType": "ParameterList", "parameters": [], "src": "6262:2:6" }, "src": "6241:149:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3835, "nodeType": "Block", "src": "6449:96:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 3830, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3827, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "6467:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3828, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6467:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 3829, "name": "expirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3824, "src": "6488:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "6467:36:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73616374696f6e2065787069726564", "id": 3831, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6505:21:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" }, "value": "Transaction expired" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_38b57334da13daffb65e2d9cfe97bc3051af86f72807115eae867384ed846551", "typeString": "literal_string \"Transaction expired\"" } ], "id": 3826, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6459:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3832, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6459:68:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3833, "nodeType": "ExpressionStatement", "src": "6459:68:6" }, { "id": 3834, "nodeType": "PlaceholderStatement", "src": "6537:1:6" } ] }, "documentation": null, "id": 3836, "name": "verifyTxExpiration", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3825, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3824, "mutability": "mutable", "name": "expirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3836, "src": "6424:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3823, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6424:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "6423:25:6" }, "src": "6396:149:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3847, "nodeType": "Block", "src": "6578:102:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 3842, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 3839, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3766, "src": "6596:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3840, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "6616:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3841, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6616:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "6596:37:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "436f6e747261637420686173206265656e20706175736564", "id": 3843, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "6635:26:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" }, "value": "Contract has been paused" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_6993ab713d4a8a857f0654d36ec91738ef7ba3b8999d96006199fec5b907c586", "typeString": "literal_string \"Contract has been paused\"" } ], "id": 3838, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "6588:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3844, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "6588:74:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3845, "nodeType": "ExpressionStatement", "src": "6588:74:6" }, { "id": 3846, "nodeType": "PlaceholderStatement", "src": "6672:1:6" } ] }, "documentation": null, "id": 3848, "name": "verifyNotPaused", "nodeType": "ModifierDefinition", "overrides": null, "parameters": { "id": 3837, "nodeType": "ParameterList", "parameters": [], "src": "6575:2:6" }, "src": "6551:129:6", "virtual": false, "visibility": "internal" }, { "body": { "id": 3892, "nodeType": "Block", "src": "7002:640:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3861, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5584, "src": "7023:18:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3862, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "7043:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3863, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "7043:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 3860, "name": "_setupRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5767, "src": "7012:10:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_bytes32_$_t_address_$returns$__$", "typeString": "function (bytes32,address)" } }, "id": 3864, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7012:42:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3865, "nodeType": "ExpressionStatement", "src": "7012:42:6" }, { "expression": { "argumentTypes": null, "id": 3870, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 3866, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "7065:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3868, "name": "ERC20Address", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3851, "src": "7081:12:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "id": 3867, "name": "IERC20", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 6707, "src": "7074:6:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_IERC20_$6707_$", "typeString": "type(contract IERC20)" } }, "id": 3869, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7074:20:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "src": "7065:29:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 3871, "nodeType": "ExpressionStatement", "src": "7065:29:6" }, { "expression": { "argumentTypes": null, "id": 3878, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 3872, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3764, "src": "7104:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3876, "name": "DELETE_PROTECTION_PERIOD", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3760, "src": "7144:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3873, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "7122:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3874, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7122:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3875, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "7122:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3877, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7122:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "7104:65:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3879, "nodeType": "ExpressionStatement", "src": "7104:65:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3881, "name": "lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3857, "src": "7512:18:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "id": 3880, "name": "_updateLockPeriod", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5079, "src": "7494:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint64_$returns$__$", "typeString": "function (uint64)" } }, "id": 3882, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7494:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3883, "nodeType": "ExpressionStatement", "src": "7494:37:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3885, "name": "interestRatePerBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3853, "src": "7558:20:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3884, "name": "_addInterestRate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5064, "src": "7541:16:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 3886, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7541:38:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3887, "nodeType": "ExpressionStatement", "src": "7541:38:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3889, "name": "pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3855, "src": "7601:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3888, "name": "_pauseSince", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5104, "src": "7589:11:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 3890, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "7589:46:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3891, "nodeType": "ExpressionStatement", "src": "7589:46:6" } ] }, "documentation": { "id": 3849, "nodeType": "StructuredDocumentation", "src": "6756:68:6", "text": "@param ERC20Address address of the ERC20 contract" }, "id": 3893, "implemented": true, "kind": "constructor", "modifiers": [], "name": "", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 3858, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3851, "mutability": "mutable", "name": "ERC20Address", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6852:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 3850, "name": "address", "nodeType": "ElementaryTypeName", "src": "6852:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3853, "mutability": "mutable", "name": "interestRatePerBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6883:28:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3852, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6883:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3855, "mutability": "mutable", "name": "pausedSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6922:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3854, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "6922:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3857, "mutability": "mutable", "name": "lockPeriodInBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3893, "src": "6957:26:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 3856, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "6957:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" } ], "src": "6840:144:6" }, "returnParameters": { "id": 3859, "nodeType": "ParameterList", "parameters": [], "src": "7002:0:6" }, "scope": 5490, "src": "6829:813:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "public" }, { "body": { "id": 3910, "nodeType": "Block", "src": "8459:39:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3907, "name": "rate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3896, "src": "8486:4:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3906, "name": "_addInterestRate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5064, "src": "8469:16:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 3908, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8469:22:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3909, "nodeType": "ExpressionStatement", "src": "8469:22:6" } ] }, "documentation": { "id": 3894, "nodeType": "StructuredDocumentation", "src": "7649:627:6", "text": "@notice Add new interest rate in to the ordered container of previously added interest rates\n@param rate - signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18\n@param expirationBlock - block number beyond which is the carrier Tx considered expired, and so rejected.\n This is for protection of Tx sender to exactly define lifecycle length of the Tx,\n and so avoiding uncertainty of how long Tx sender needs to wait for Tx processing.\n Tx can be withheld\n@dev expiration period" }, "functionSelector": "13384039", "id": 3911, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [], "id": 3901, "modifierName": { "argumentTypes": null, "id": 3900, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3822, "src": "8396:12:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "8396:14:6" }, { "arguments": [ { "argumentTypes": null, "id": 3903, "name": "expirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3898, "src": "8438:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 3904, "modifierName": { "argumentTypes": null, "id": 3902, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "8419:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "8419:35:6" } ], "name": "addInterestRate", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 3899, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3896, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3911, "src": "8315:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3895, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8315:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3898, "mutability": "mutable", "name": "expirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3911, "src": "8337:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3897, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8337:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "8305:65:6" }, "returnParameters": { "id": 3905, "nodeType": "ParameterList", "parameters": [], "src": "8459:0:6" }, "scope": 5490, "src": "8281:217:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 3997, "nodeType": "Block", "src": "8682:654:6", "statements": [ { "assignments": [ 3924 ], "declarations": [ { "constant": false, "id": 3924, "mutability": "mutable", "name": "makeTransfer", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3997, "src": "8692:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 3923, "name": "bool", "nodeType": "ElementaryTypeName", "src": "8692:4:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "id": 3928, "initialValue": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 3927, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 3925, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "8712:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 3926, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "8722:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "8712:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "VariableDeclarationStatement", "src": "8692:31:6" }, { "condition": { "argumentTypes": null, "id": 3929, "name": "makeTransfer", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3924, "src": "8737:12:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 3968, "nodeType": "IfStatement", "src": "8733:352:6", "trueBody": { "id": 3967, "nodeType": "Block", "src": "8751:334:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3933, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "8793:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3934, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "8793:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3937, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "8813:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 3936, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "8805:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 3935, "name": "address", "nodeType": "ElementaryTypeName", "src": "8805:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 3938, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8805:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 3939, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "8820:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 3931, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "8773:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 3932, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transferFrom", "nodeType": "MemberAccess", "referencedDeclaration": 6688, "src": "8773:19:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,address,uint256) external returns (bool)" } }, "id": 3940, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8773:54:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73666572206661696c6564", "id": 3941, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "8829:17:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" }, "value": "Transfer failed" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" } ], "id": 3930, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "8765:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 3942, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8765:82:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3943, "nodeType": "ExpressionStatement", "src": "8765:82:6" }, { "expression": { "argumentTypes": null, "id": 3949, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 3944, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "8861:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3947, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "8915:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 3945, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "8887:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3946, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "8887:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3948, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8887:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "8861:61:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3950, "nodeType": "ExpressionStatement", "src": "8861:61:6" }, { "expression": { "argumentTypes": null, "id": 3959, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3951, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "8936:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 3953, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "8936:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3957, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "9010:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3954, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "8972:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 3955, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "8972:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3956, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "8972:37:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3958, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "8972:45:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "8936:81:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3960, "nodeType": "ExpressionStatement", "src": "8936:81:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3962, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9055:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3963, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9055:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 3964, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "9067:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3961, "name": "LiquidityDeposited", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3686, "src": "9036:18:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 3965, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9036:38:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 3966, "nodeType": "EmitStatement", "src": "9031:43:6" } ] } }, { "assignments": [ 3970 ], "declarations": [ { "constant": false, "id": 3970, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3997, "src": "9095:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3969, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9095:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 3973, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 3971, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "9116:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 3972, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9116:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "9095:38:6" }, { "assignments": [ null, 3977, null ], "declarations": [ null, { "constant": false, "id": 3977, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3997, "src": "9146:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 3976, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "9146:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 3983, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3979, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9201:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 3980, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9201:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 3981, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3970, "src": "9213:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 3978, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "9183:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 3982, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9183:41:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "9143:81:6" }, { "condition": { "argumentTypes": null, "id": 3984, "name": "makeTransfer", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3924, "src": "9239:12:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 3996, "nodeType": "IfStatement", "src": "9235:95:6", "trueBody": { "id": 3995, "nodeType": "Block", "src": "9253:77:6", "statements": [ { "expression": { "argumentTypes": null, "id": 3993, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3985, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3977, "src": "9267:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 3987, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "9267:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 3991, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3913, "src": "9313:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 3988, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3977, "src": "9289:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 3989, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "9289:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3990, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "9289:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 3992, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9289:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "9267:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 3994, "nodeType": "ExpressionStatement", "src": "9267:53:6" } ] } } ] }, "documentation": null, "functionSelector": "e2bbb158", "id": 3998, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 3918, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3915, "src": "8635:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 3919, "modifierName": { "argumentTypes": null, "id": 3917, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "8616:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "8616:37:6" }, { "arguments": null, "id": 3921, "modifierName": { "argumentTypes": null, "id": 3920, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "8662:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "8662:15:6" } ], "name": "deposit", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 3916, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 3913, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3998, "src": "8531:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3912, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8531:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 3915, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 3998, "src": "8555:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 3914, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "8555:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "8521:69:6" }, "returnParameters": { "id": 3922, "nodeType": "ParameterList", "parameters": [], "src": "8682:0:6" }, "scope": 5490, "src": "8505:831:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4045, "nodeType": "Block", "src": "9796:317:6", "statements": [ { "assignments": [ 4012 ], "declarations": [ { "constant": false, "id": 4012, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9806:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4011, "name": "address", "nodeType": "ElementaryTypeName", "src": "9806:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4015, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4013, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "9823:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4014, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "9823:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "9806:27:6" }, { "assignments": [ 4017 ], "declarations": [ { "constant": false, "id": 4017, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9843:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4016, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9843:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4020, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4018, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "9864:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4019, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9864:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "9843:38:6" }, { "assignments": [ null, 4024, null ], "declarations": [ null, { "constant": false, "id": 4024, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9894:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4023, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "9894:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4029, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4026, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4012, "src": "9949:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4027, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4017, "src": "9957:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4025, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "9931:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4028, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "9931:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "9891:77:6" }, { "assignments": [ 4033 ], "declarations": [ { "constant": false, "id": 4033, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4045, "src": "9979:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4032, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "9979:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4038, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4036, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4001, "src": "10047:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4034, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4024, "src": "10011:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4035, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSubCompoundInterestFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3072, "src": "10011:35:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4037, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10011:43:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "VariableDeclarationStatement", "src": "9979:75:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4040, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4012, "src": "10082:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4041, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4033, "src": "10090:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, { "argumentTypes": null, "id": 4042, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4001, "src": "10099:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4039, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "10064:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4043, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10064:42:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4044, "nodeType": "ExpressionStatement", "src": "10064:42:6" } ] }, "documentation": { "id": 3999, "nodeType": "StructuredDocumentation", "src": "9343:270:6", "text": "@notice Withdraws amount from sender' available liquidity pool back to sender address,\n preferring withdrawal from compound interest dimension of liquidity.\n * @param amount - value to withdraw\n * @dev public access" }, "functionSelector": "441a3e70", "id": 4046, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4006, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4003, "src": "9749:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4007, "modifierName": { "argumentTypes": null, "id": 4005, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "9730:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "9730:37:6" }, { "arguments": null, "id": 4009, "modifierName": { "argumentTypes": null, "id": 4008, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "9776:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "9776:15:6" } ], "name": "withdraw", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4004, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4001, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4046, "src": "9645:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4000, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9645:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4003, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4046, "src": "9669:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4002, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "9669:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "9635:69:6" }, "returnParameters": { "id": 4010, "nodeType": "ParameterList", "parameters": [], "src": "9796:0:6" }, "scope": 5490, "src": "9618:495:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4101, "nodeType": "Block", "src": "10411:366:6", "statements": [ { "assignments": [ 4058 ], "declarations": [ { "constant": false, "id": 4058, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10421:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4057, "name": "address", "nodeType": "ElementaryTypeName", "src": "10421:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4061, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4059, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "10438:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4060, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "10438:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "10421:27:6" }, { "assignments": [ 4063 ], "declarations": [ { "constant": false, "id": 4063, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10458:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4062, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10458:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4066, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4064, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "10479:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4065, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10479:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "10458:38:6" }, { "assignments": [ null, 4070, null ], "declarations": [ null, { "constant": false, "id": 4070, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10509:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4069, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "10509:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4075, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4072, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4058, "src": "10565:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4073, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4063, "src": "10573:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4071, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "10547:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4074, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10547:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "10506:78:6" }, { "assignments": [ 4079 ], "declarations": [ { "constant": false, "id": 4079, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4101, "src": "10595:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4078, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "10595:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4080, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "10595:29:6" }, { "expression": { "argumentTypes": null, "id": 4086, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4081, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4079, "src": "10634:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4083, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10634:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4084, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4070, "src": "10654:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4085, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10654:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "10634:39:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4087, "nodeType": "ExpressionStatement", "src": "10634:39:6" }, { "expression": { "argumentTypes": null, "id": 4092, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4088, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4070, "src": "10683:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4090, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10683:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4091, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "10705:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "10683:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4093, "nodeType": "ExpressionStatement", "src": "10683:23:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4095, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4058, "src": "10735:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4096, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4079, "src": "10743:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4097, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4079, "src": "10752:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4098, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "10752:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4094, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "10717:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4099, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "10717:53:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4100, "nodeType": "ExpressionStatement", "src": "10717:53:6" } ] }, "documentation": { "id": 4047, "nodeType": "StructuredDocumentation", "src": "10120:123:6", "text": "@notice Withdraws *WHOLE* compound interest amount available to sender.\n * @dev public access" }, "functionSelector": "658e28a4", "id": 4102, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4052, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4049, "src": "10364:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4053, "modifierName": { "argumentTypes": null, "id": 4051, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "10345:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "10345:37:6" }, { "arguments": null, "id": 4055, "modifierName": { "argumentTypes": null, "id": 4054, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "10391:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "10391:15:6" } ], "name": "withdrawPrincipal", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4050, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4049, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4102, "src": "10284:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4048, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10284:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "10274:45:6" }, "returnParameters": { "id": 4056, "nodeType": "ParameterList", "parameters": [], "src": "10411:0:6" }, "scope": 5490, "src": "10248:529:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4157, "nodeType": "Block", "src": "11082:394:6", "statements": [ { "assignments": [ 4114 ], "declarations": [ { "constant": false, "id": 4114, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11092:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4113, "name": "address", "nodeType": "ElementaryTypeName", "src": "11092:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4117, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4115, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "11109:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4116, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11109:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "11092:27:6" }, { "assignments": [ 4119 ], "declarations": [ { "constant": false, "id": 4119, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11129:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4118, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "11129:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4122, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4120, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "11150:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4121, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11150:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "11129:38:6" }, { "assignments": [ null, 4126, null ], "declarations": [ null, { "constant": false, "id": 4126, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11180:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4125, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "11180:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4131, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4128, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4114, "src": "11236:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4129, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4119, "src": "11244:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4127, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "11218:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4130, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11218:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "11177:78:6" }, { "assignments": [ 4135 ], "declarations": [ { "constant": false, "id": 4135, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4157, "src": "11266:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4134, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "11266:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4136, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "11266:29:6" }, { "expression": { "argumentTypes": null, "id": 4142, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4137, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4135, "src": "11305:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4139, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11305:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4140, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4126, "src": "11332:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4141, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11332:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "11305:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4143, "nodeType": "ExpressionStatement", "src": "11305:53:6" }, { "expression": { "argumentTypes": null, "id": 4148, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4144, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4126, "src": "11368:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4146, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11368:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4147, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "11397:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "11368:30:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4149, "nodeType": "ExpressionStatement", "src": "11368:30:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4151, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4114, "src": "11427:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4152, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4135, "src": "11435:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4153, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4135, "src": "11444:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4154, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "11444:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4150, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "11409:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4155, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11409:60:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4156, "nodeType": "ExpressionStatement", "src": "11409:60:6" } ] }, "documentation": { "id": 4103, "nodeType": "StructuredDocumentation", "src": "10784:123:6", "text": "@notice Withdraws *WHOLE* compound interest amount available to sender.\n * @dev public access" }, "functionSelector": "d8cd3999", "id": 4158, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4108, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4105, "src": "11035:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4109, "modifierName": { "argumentTypes": null, "id": 4107, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "11016:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "11016:37:6" }, { "arguments": null, "id": 4111, "modifierName": { "argumentTypes": null, "id": 4110, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "11062:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "11062:15:6" } ], "name": "withdrawCompoundInterest", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4106, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4105, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4158, "src": "10955:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4104, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "10955:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "10945:45:6" }, "returnParameters": { "id": 4112, "nodeType": "ParameterList", "parameters": [], "src": "11082:0:6" }, "scope": 5490, "src": "10912:564:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4208, "nodeType": "Block", "src": "11786:323:6", "statements": [ { "assignments": [ 4170 ], "declarations": [ { "constant": false, "id": 4170, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4208, "src": "11796:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4169, "name": "address", "nodeType": "ElementaryTypeName", "src": "11796:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4173, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4171, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "11813:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4172, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "11813:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "11796:27:6" }, { "assignments": [ 4175 ], "declarations": [ { "constant": false, "id": 4175, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4208, "src": "11833:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4174, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "11833:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4178, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4176, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "11854:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4177, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11854:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "11833:38:6" }, { "assignments": [ null, 4182, null ], "declarations": [ null, { "constant": false, "id": 4182, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4208, "src": "11884:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4181, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "11884:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4187, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4184, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4170, "src": "11940:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4185, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4175, "src": "11948:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4183, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "11922:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4186, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11922:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "11881:78:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4189, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4170, "src": "11988:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4190, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "11996:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 4191, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "12007:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4192, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "composite", "nodeType": "MemberAccess", "referencedDeclaration": 2803, "src": "12007:19:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_struct$_Asset_$2788_storage_ptr_$returns$_t_uint256_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer) view returns (uint256)" } }, "id": 4193, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12007:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4188, "name": "_finaliseWithdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5168, "src": "11970:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_struct$_Asset_$2788_memory_ptr_$_t_uint256_$returns$__$", "typeString": "function (address,struct AssetLib.Asset memory,uint256)" } }, "id": 4194, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "11970:59:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4195, "nodeType": "ExpressionStatement", "src": "11970:59:6" }, { "expression": { "argumentTypes": null, "id": 4200, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4196, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "12039:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4198, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "12039:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4199, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "12068:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "12039:30:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4201, "nodeType": "ExpressionStatement", "src": "12039:30:6" }, { "expression": { "argumentTypes": null, "id": 4206, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4202, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4182, "src": "12079:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4204, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "12079:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4205, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "12101:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "12079:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4207, "nodeType": "ExpressionStatement", "src": "12079:23:6" } ] }, "documentation": { "id": 4159, "nodeType": "StructuredDocumentation", "src": "11483:130:6", "text": "@notice Withdraws whole liquidity available to sender back to sender' address,\n * @dev public access" }, "functionSelector": "dca2aa5c", "id": 4209, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4164, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4161, "src": "11739:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4165, "modifierName": { "argumentTypes": null, "id": 4163, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "11720:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "11720:37:6" }, { "arguments": null, "id": 4167, "modifierName": { "argumentTypes": null, "id": 4166, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "11766:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "11766:15:6" } ], "name": "withdrawWholeLiquidity", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4162, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4161, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4209, "src": "11659:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4160, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "11659:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "11649:45:6" }, "returnParameters": { "id": 4168, "nodeType": "ParameterList", "parameters": [], "src": "11786:0:6" }, "scope": 5490, "src": "11618:491:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4279, "nodeType": "Block", "src": "12295:1112:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4224, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4222, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4211, "src": "12313:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4223, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "12323:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "12313:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "416d6f756e74206d75737420626520686967686572207468616e207a65726f", "id": 4225, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "12326:33:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_8b5d38bfd9d56e06c511eeb946d76a1a45a72ed25ba1d513f75809a38de68bdb", "typeString": "literal_string \"Amount must be higher than zero\"" }, "value": "Amount must be higher than zero" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_8b5d38bfd9d56e06c511eeb946d76a1a45a72ed25ba1d513f75809a38de68bdb", "typeString": "literal_string \"Amount must be higher than zero\"" } ], "id": 4221, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "12305:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4226, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12305:55:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4227, "nodeType": "ExpressionStatement", "src": "12305:55:6" }, { "assignments": [ 4229 ], "declarations": [ { "constant": false, "id": 4229, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12371:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4228, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "12371:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4232, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4230, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "12392:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4231, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12392:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "12371:38:6" }, { "assignments": [ null, 4236, null ], "declarations": [ null, { "constant": false, "id": 4236, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12423:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4235, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "12423:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, null ], "id": 4242, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4238, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "12479:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4239, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "12479:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4240, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4229, "src": "12491:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4237, "name": "_collectLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5489, "src": "12461:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "function (address,uint256) returns (struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "id": 4241, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12461:41:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$_t_struct$_Asset_$2788_memory_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_bool_$", "typeString": "tuple(struct AssetLib.Asset memory,struct AssetLib.Asset storage pointer,bool)" } }, "nodeType": "VariableDeclarationStatement", "src": "12420:82:6" }, { "assignments": [ 4244 ], "declarations": [ { "constant": false, "id": 4244, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12848:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 4243, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "12848:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "id": 4250, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4246, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "12899:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4247, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "12899:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4248, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4229, "src": "12911:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4245, "name": "_updateStakeCompoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5315, "src": "12870:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Stake_$3648_storage_ptr_$", "typeString": "function (address,uint256) returns (struct Staking.Stake storage pointer)" } }, "id": 4249, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12870:52:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "nodeType": "VariableDeclarationStatement", "src": "12848:74:6" }, { "assignments": [ 4254 ], "declarations": [ { "constant": false, "id": 4254, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4279, "src": "12932:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4253, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "12932:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4261, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4257, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4244, "src": "12998:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4258, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "12998:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, { "argumentTypes": null, "id": 4259, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4211, "src": "13011:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4255, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4236, "src": "12964:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4256, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iRelocatePrincipalFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3096, "src": "12964:33:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4260, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "12964:54:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "VariableDeclarationStatement", "src": "12932:86:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4265, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4254, "src": "13057:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4262, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "13028:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4264, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "13028:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4266, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "13028:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4267, "nodeType": "ExpressionStatement", "src": "13028:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4269, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "13302:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4270, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "13302:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4271, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4244, "src": "13314:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4272, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "13314:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4273, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4254, "src": "13344:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4274, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "13344:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4275, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4254, "src": "13363:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4276, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "13363:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4268, "name": "BindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3669, "src": "13292:9:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4277, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "13292:96:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4278, "nodeType": "EmitStatement", "src": "13287:101:6" } ] }, "documentation": null, "functionSelector": "88cedd5e", "id": 4280, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4216, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4213, "src": "12248:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4217, "modifierName": { "argumentTypes": null, "id": 4215, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "12229:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "12229:37:6" }, { "arguments": null, "id": 4219, "modifierName": { "argumentTypes": null, "id": 4218, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "12275:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "12275:15:6" } ], "name": "bindStake", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4214, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4211, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4280, "src": "12144:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4210, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "12144:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4213, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4280, "src": "12168:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4212, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "12168:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "12134:69:6" }, "returnParameters": { "id": 4220, "nodeType": "ParameterList", "parameters": [], "src": "12295:0:6" }, "scope": 5490, "src": "12116:1291:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4549, "nodeType": "Block", "src": "14400:3414:6", "statements": [ { "assignments": [ 4294 ], "declarations": [ { "constant": false, "id": 4294, "mutability": "mutable", "name": "curr_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14410:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4293, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14410:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4297, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4295, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "14431:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4296, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "14431:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "14410:38:6" }, { "assignments": [ 4299 ], "declarations": [ { "constant": false, "id": 4299, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14458:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4298, "name": "address", "nodeType": "ElementaryTypeName", "src": "14458:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "id": 4302, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4300, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "14475:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4301, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "14475:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "nodeType": "VariableDeclarationStatement", "src": "14458:27:6" }, { "assignments": [ 4304 ], "declarations": [ { "constant": false, "id": 4304, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14495:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 4303, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "14495:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "id": 4309, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4306, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "14546:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4307, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "14554:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4305, "name": "_updateStakeCompoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5315, "src": "14517:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$_t_struct$_Stake_$3648_storage_ptr_$", "typeString": "function (address,uint256) returns (struct Staking.Stake storage pointer)" } }, "id": 4308, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "14517:48:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "nodeType": "VariableDeclarationStatement", "src": "14495:70:6" }, { "assignments": [ 4311 ], "declarations": [ { "constant": false, "id": 4311, "mutability": "mutable", "name": "stake_composite", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14576:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4310, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14576:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4316, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4312, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "14602:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4313, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "14602:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4314, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "composite", "nodeType": "MemberAccess", "referencedDeclaration": 2803, "src": "14602:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_struct$_Asset_$2788_storage_ptr_$returns$_t_uint256_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer) view returns (uint256)" } }, "id": 4315, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "14602:23:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "14576:49:6" }, { "assignments": [ 4320 ], "declarations": [ { "constant": false, "id": 4320, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4549, "src": "14635:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4319, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "14635:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4321, "initialValue": null, "nodeType": "VariableDeclarationStatement", "src": "14635:29:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4324, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4322, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "14679:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4323, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "14688:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "14679:10:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4547, "nodeType": "Block", "src": "16562:1246:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4431, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4429, "name": "stake_composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4311, "src": "16580:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4430, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16599:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16580:20:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 4434, "nodeType": "IfStatement", "src": "16576:108:6", "trueBody": { "id": 4433, "nodeType": "Block", "src": "16602:82:6", "statements": [ { "expression": null, "functionReturnParameters": 4292, "id": 4432, "nodeType": "Return", "src": "16663:7:6" } ] } }, { "expression": { "argumentTypes": null, "id": 4438, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4435, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16698:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4436, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16708:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4437, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16708:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "src": "16698:21:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4439, "nodeType": "ExpressionStatement", "src": "16698:21:6" }, { "expression": { "argumentTypes": null, "id": 4446, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4440, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16733:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4443, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16733:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4444, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "16733:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4445, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16757:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16733:25:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4447, "nodeType": "ExpressionStatement", "src": "16733:25:6" }, { "expression": { "argumentTypes": null, "id": 4454, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4448, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16772:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4451, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16772:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4452, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "16772:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "hexValue": "30", "id": 4453, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16803:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16772:32:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4455, "nodeType": "ExpressionStatement", "src": "16772:32:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "id": 4458, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4456, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "16823:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4457, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "16846:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "16823:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4545, "nodeType": "Block", "src": "17167:631:6", "statements": [ { "assignments": [ 4491 ], "declarations": [ { "constant": false, "id": 4491, "mutability": "mutable", "name": "locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4545, "src": "17185:21:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" }, "typeName": { "contractScope": null, "id": 4490, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "17185:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } }, "value": null, "visibility": "internal" } ], "id": 4495, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4492, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "17209:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4494, "indexExpression": { "argumentTypes": null, "id": 4493, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "17217:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "17209:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "17185:39:6" }, { "assignments": [ 4497 ], "declarations": [ { "constant": false, "id": 4497, "mutability": "mutable", "name": "newLockedAsset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4545, "src": "17242:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 4496, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "17242:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 4502, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4498, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4491, "src": "17279:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4499, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "17279:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "id": 4500, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "push", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "17279:18:6", "typeDescriptions": { "typeIdentifier": "t_function_arraypush_nonpayable$__$returns$_t_struct$_LockedAsset_$3653_storage_$", "typeString": "function () returns (struct Staking.LockedAsset storage ref)" } }, "id": 4501, "isConstant": false, "isLValue": true, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17279:20:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "17242:57:6" }, { "expression": { "argumentTypes": null, "id": 4510, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4503, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17317:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4505, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "17317:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4508, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "17366:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "expression": { "argumentTypes": null, "id": 4506, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "17351:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4507, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "17351:14:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4509, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17351:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "17317:69:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4511, "nodeType": "ExpressionStatement", "src": "17317:69:6" }, { "expression": { "argumentTypes": null, "id": 4516, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4512, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17404:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4514, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "17404:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 4515, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17427:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "src": "17404:30:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4517, "nodeType": "ExpressionStatement", "src": "17404:30:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4521, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17479:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4518, "name": "_accruedGlobalLocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3776, "src": "17453:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4520, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "17453:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4522, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17453:34:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4523, "nodeType": "ExpressionStatement", "src": "17453:34:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4529, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17527:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4524, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4491, "src": "17505:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4527, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "17505:16:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4528, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "17505:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4530, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17505:30:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4531, "nodeType": "ExpressionStatement", "src": "17505:30:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4533, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "17668:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4534, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "17668:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4535, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17680:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4536, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "17680:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4537, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17713:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4538, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "17713:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4539, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "17713:30:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4540, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4497, "src": "17745:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4541, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "17745:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4542, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "17745:37:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4532, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "17656:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4543, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17656:127:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4544, "nodeType": "EmitStatement", "src": "17651:132:6" } ] }, "id": 4546, "nodeType": "IfStatement", "src": "16819:979:6", "trueBody": { "id": 4489, "nodeType": "Block", "src": "16849:312:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4463, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16891:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4459, "name": "_liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3796, "src": "16867:10:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset storage ref)" } }, "id": 4461, "indexExpression": { "argumentTypes": null, "id": 4460, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "16878:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "16867:18:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4462, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16867:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4464, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16867:32:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4465, "nodeType": "ExpressionStatement", "src": "16867:32:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4469, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16946:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4466, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "16917:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4468, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16917:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4470, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16917:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4471, "nodeType": "ExpressionStatement", "src": "16917:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4473, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "16989:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4474, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "16997:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4475, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17009:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4476, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "17009:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4477, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17028:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4478, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "17028:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4472, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "16977:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4479, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16977:76:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4480, "nodeType": "EmitStatement", "src": "16972:81:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4482, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "17094:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4483, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17102:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4484, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "17102:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4485, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "17121:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4486, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "17121:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4481, "name": "LiquidityUnlocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3694, "src": "17076:17:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 4487, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "17076:70:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4488, "nodeType": "EmitStatement", "src": "17071:75:6" } ] } } ] }, "id": 4548, "nodeType": "IfStatement", "src": "14675:3133:6", "trueBody": { "id": 4428, "nodeType": "Block", "src": "14691:1865:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4328, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4326, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "15425:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 4327, "name": "stake_composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4311, "src": "15435:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "15425:25:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "416d6f756e7420697320686967686572207468616e207374616b65", "id": 4329, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "15452:29:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_8bda5dc5d5057eb9094b795367834747986b50a0bac9d1f451ae725565808bcd", "typeString": "literal_string \"Amount is higher than stake\"" }, "value": "Amount is higher than stake" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_8bda5dc5d5057eb9094b795367834747986b50a0bac9d1f451ae725565808bcd", "typeString": "literal_string \"Amount is higher than stake\"" } ], "id": 4325, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "15417:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4330, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15417:65:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4331, "nodeType": "ExpressionStatement", "src": "15417:65:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "id": 4334, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4332, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "15501:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4333, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "15524:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "15501:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4426, "nodeType": "Block", "src": "15893:653:6", "statements": [ { "assignments": [ 4371 ], "declarations": [ { "constant": false, "id": 4371, "mutability": "mutable", "name": "locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4426, "src": "15911:21:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" }, "typeName": { "contractScope": null, "id": 4370, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "15911:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } }, "value": null, "visibility": "internal" } ], "id": 4375, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4372, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "15935:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4374, "indexExpression": { "argumentTypes": null, "id": 4373, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15943:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "15935:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "15911:39:6" }, { "assignments": [ 4377 ], "declarations": [ { "constant": false, "id": 4377, "mutability": "mutable", "name": "newLockedAsset", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4426, "src": "15968:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 4376, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "15968:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 4382, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4378, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4371, "src": "16005:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4379, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "16005:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "id": 4380, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "push", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "16005:18:6", "typeDescriptions": { "typeIdentifier": "t_function_arraypush_nonpayable$__$returns$_t_struct$_LockedAsset_$3653_storage_$", "typeString": "function () returns (struct Staking.LockedAsset storage ref)" } }, "id": 4381, "isConstant": false, "isLValue": true, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16005:20:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "15968:57:6" }, { "expression": { "argumentTypes": null, "id": 4390, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4383, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4377, "src": "16043:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4385, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "16043:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4388, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "16092:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "expression": { "argumentTypes": null, "id": 4386, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "16077:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4387, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "16077:14:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4389, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16077:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "16043:69:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4391, "nodeType": "ExpressionStatement", "src": "16043:69:6" }, { "expression": { "argumentTypes": null, "id": 4400, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4392, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16130:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4396, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4377, "src": "16183:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4397, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "16183:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, { "argumentTypes": null, "id": 4398, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "16205:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4393, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "16140:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4394, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "16140:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4395, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iRelocateCompoundInterestFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3120, "src": "16140:42:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4399, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16140:72:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "src": "16130:82:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4401, "nodeType": "ExpressionStatement", "src": "16130:82:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4405, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16257:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4402, "name": "_accruedGlobalLocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3776, "src": "16231:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4404, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16231:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4406, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16231:34:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4407, "nodeType": "ExpressionStatement", "src": "16231:34:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4413, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16305:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4408, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4371, "src": "16283:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 4411, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "16283:16:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4412, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "16283:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4414, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16283:30:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4415, "nodeType": "ExpressionStatement", "src": "16283:30:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4417, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "16446:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4418, "name": "newLockedAsset", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4377, "src": "16454:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4419, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "16454:31:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4420, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16487:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4421, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "16487:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4422, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "16506:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4423, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "16506:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4416, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "16434:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4424, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "16434:97:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4425, "nodeType": "EmitStatement", "src": "16429:102:6" } ] }, "id": 4427, "nodeType": "IfStatement", "src": "15497:1049:6", "trueBody": { "id": 4369, "nodeType": "Block", "src": "15527:360:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4344, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4335, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15545:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4339, "name": "_liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3796, "src": "15598:10:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset storage ref)" } }, "id": 4341, "indexExpression": { "argumentTypes": null, "id": 4340, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15609:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "15598:18:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, { "argumentTypes": null, "id": 4342, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4283, "src": "15618:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4336, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4304, "src": "15555:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4337, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "15555:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4338, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iRelocateCompoundInterestFirst", "nodeType": "MemberAccess", "referencedDeclaration": 3120, "src": "15555:42:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_storage_ptr_$_t_uint256_$returns$_t_struct$_Asset_$2788_memory_ptr_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset storage pointer,uint256) returns (struct AssetLib.Asset memory)" } }, "id": 4343, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15555:70:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "src": "15545:80:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4345, "nodeType": "ExpressionStatement", "src": "15545:80:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4349, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15672:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 4346, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "15643:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4348, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "15643:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 4350, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15643:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4351, "nodeType": "ExpressionStatement", "src": "15643:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4353, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15715:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4354, "name": "curr_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4294, "src": "15723:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4355, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15735:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4356, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "15735:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4357, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15754:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4358, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "15754:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4352, "name": "UnbindStake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3704, "src": "15703:11:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 4359, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15703:76:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4360, "nodeType": "EmitStatement", "src": "15698:81:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4362, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4299, "src": "15820:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4363, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15828:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4364, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "15828:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4365, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4320, "src": "15847:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 4366, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "15847:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4361, "name": "LiquidityUnlocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3694, "src": "15802:17:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 4367, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "15802:70:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4368, "nodeType": "EmitStatement", "src": "15797:75:6" } ] } } ] } } ] }, "documentation": { "id": 4281, "nodeType": "StructuredDocumentation", "src": "13414:755:6", "text": "@notice Unbinds amount from the stake of sender of the transaction,\n and *LOCKS* it for number of blocks defined by value of the\n `_lockPeriodInBlocks` state of this contract at the point\n of this call.\n The locked amount can *NOT* be withdrawn from the contract\n *BEFORE* the lock period ends.\n * Unbinding (=calling this method) also means, that compound\n interest will be calculated for period since la.\n * @param amount - value to un-bind from the stake\n If `amount=0` then the **WHOLE** stake (including\n compound interest) will be unbound.\n * @dev public access" }, "functionSelector": "52885d8d", "id": 4550, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4288, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4285, "src": "14353:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4289, "modifierName": { "argumentTypes": null, "id": 4287, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "14334:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "14334:37:6" }, { "arguments": null, "id": 4291, "modifierName": { "argumentTypes": null, "id": 4290, "name": "verifyNotPaused", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3848, "src": "14380:15:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "14380:15:6" } ], "name": "unbindStake", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4286, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4283, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4550, "src": "14204:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4282, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14204:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4285, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4550, "src": "14273:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4284, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "14273:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "14194:114:6" }, "returnParameters": { "id": 4292, "nodeType": "ParameterList", "parameters": [], "src": "14400:0:6" }, "scope": 5490, "src": "14174:3640:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4557, "nodeType": "Block", "src": "17885:43:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4555, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "17902:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 4554, "id": 4556, "nodeType": "Return", "src": "17895:26:6" } ] }, "documentation": null, "functionSelector": "adf55101", "id": 4558, "implemented": true, "kind": "function", "modifiers": [], "name": "getRewardsPoolBalance", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4551, "nodeType": "ParameterList", "parameters": [], "src": "17851:2:6" }, "returnParameters": { "id": 4554, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4553, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4558, "src": "17876:7:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4552, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "17876:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "17875:9:6" }, "scope": 5490, "src": "17821:107:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4565, "nodeType": "Block", "src": "18000:39:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4563, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3764, "src": "18017:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 4562, "id": 4564, "nodeType": "Return", "src": "18010:22:6" } ] }, "documentation": null, "functionSelector": "085313ec", "id": 4566, "implemented": true, "kind": "function", "modifiers": [], "name": "getEarliestDeleteBlock", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4559, "nodeType": "ParameterList", "parameters": [], "src": "17966:2:6" }, "returnParameters": { "id": 4562, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4561, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4566, "src": "17991:7:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4560, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "17991:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "17990:9:6" }, "scope": 5490, "src": "17935:104:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4581, "nodeType": "Block", "src": "18144:59:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4579, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4573, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4571, "src": "18154:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4574, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "18163:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4576, "indexExpression": { "argumentTypes": null, "id": 4575, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4568, "src": "18171:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "18163:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "id": 4577, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "18163:26:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "id": 4578, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "18163:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "18154:42:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4580, "nodeType": "ExpressionStatement", "src": "18154:42:6" } ] }, "documentation": null, "functionSelector": "40e97903", "id": 4582, "implemented": true, "kind": "function", "modifiers": [], "name": "getNumberOfLockedAssetsForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4569, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4568, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4582, "src": "18086:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4567, "name": "address", "nodeType": "ElementaryTypeName", "src": "18086:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "18085:20:6" }, "returnParameters": { "id": 4572, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4571, "mutability": "mutable", "name": "length", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4582, "src": "18128:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4570, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18128:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "18127:16:6" }, "scope": 5490, "src": "18046:157:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4606, "nodeType": "Block", "src": "18338:147:6", "statements": [ { "assignments": [ 4594 ], "declarations": [ { "constant": false, "id": 4594, "mutability": "mutable", "name": "aggregate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4606, "src": "18348:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4593, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "18348:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4599, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4595, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "18383:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4597, "indexExpression": { "argumentTypes": null, "id": 4596, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4584, "src": "18391:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "18383:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "id": 4598, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "18383:29:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "18348:64:6" }, { "expression": { "argumentTypes": null, "components": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4600, "name": "aggregate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4594, "src": "18430:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4601, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "18430:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4602, "name": "aggregate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4594, "src": "18451:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4603, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "18451:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4604, "isConstant": false, "isInlineArray": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "TupleExpression", "src": "18429:49:6", "typeDescriptions": { "typeIdentifier": "t_tuple$_t_uint256_$_t_uint256_$", "typeString": "tuple(uint256,uint256)" } }, "functionReturnParameters": 4590, "id": 4605, "nodeType": "Return", "src": "18422:56:6" } ] }, "documentation": null, "functionSelector": "a8f1b4c4", "id": 4607, "implemented": true, "kind": "function", "modifiers": [], "name": "getLockedAssetsAggregateForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4585, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4584, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4607, "src": "18251:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4583, "name": "address", "nodeType": "ElementaryTypeName", "src": "18251:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "18250:20:6" }, "returnParameters": { "id": 4590, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4587, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4607, "src": "18293:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4586, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18293:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4589, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4607, "src": "18312:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4588, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18312:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "18292:45:6" }, "scope": 5490, "src": "18210:275:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4711, "nodeType": "Block", "src": "18915:654:6", "statements": [ { "assignments": [ 4625 ], "declarations": [ { "constant": false, "id": 4625, "mutability": "mutable", "name": "lockedAssets", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4711, "src": "18925:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" }, "typeName": { "baseType": { "contractScope": null, "id": 4623, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "18925:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "id": 4624, "length": null, "nodeType": "ArrayTypeName", "src": "18925:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" } }, "value": null, "visibility": "internal" } ], "id": 4630, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4626, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "18962:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 4628, "indexExpression": { "argumentTypes": null, "id": 4627, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4610, "src": "18970:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "18962:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "id": 4629, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "18962:26:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "18925:63:6" }, { "assignments": [ 4632 ], "declarations": [ { "constant": false, "id": 4632, "mutability": "mutable", "name": "length", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4711, "src": "18998:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4631, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18998:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4635, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4633, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4625, "src": "19015:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 4634, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "19015:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "18998:36:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4638, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4636, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19048:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4637, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "19058:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "19048:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 4710, "nodeType": "IfStatement", "src": "19044:519:6", "trueBody": { "id": 4709, "nodeType": "Block", "src": "19061:502:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4645, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4639, "name": "principal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4614, "src": "19075:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4643, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19101:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4642, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "NewExpression", "src": "19087:13:6", "typeDescriptions": { "typeIdentifier": "t_function_objectcreation_pure$_t_uint256_$returns$_t_array$_t_uint256_$dyn_memory_ptr_$", "typeString": "function (uint256) pure returns (uint256[] memory)" }, "typeName": { "baseType": { "id": 4640, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19091:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4641, "length": null, "nodeType": "ArrayTypeName", "src": "19091:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } } }, "id": 4644, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "19087:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "src": "19075:33:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4646, "nodeType": "ExpressionStatement", "src": "19075:33:6" }, { "expression": { "argumentTypes": null, "id": 4653, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4647, "name": "compoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4617, "src": "19122:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4651, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19155:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4650, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "NewExpression", "src": "19141:13:6", "typeDescriptions": { "typeIdentifier": "t_function_objectcreation_pure$_t_uint256_$returns$_t_array$_t_uint256_$dyn_memory_ptr_$", "typeString": "function (uint256) pure returns (uint256[] memory)" }, "typeName": { "baseType": { "id": 4648, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19145:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4649, "length": null, "nodeType": "ArrayTypeName", "src": "19145:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } } }, "id": 4652, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "19141:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "src": "19122:40:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4654, "nodeType": "ExpressionStatement", "src": "19122:40:6" }, { "expression": { "argumentTypes": null, "id": 4661, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4655, "name": "liquidSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4620, "src": "19176:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4659, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19209:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4658, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "NewExpression", "src": "19195:13:6", "typeDescriptions": { "typeIdentifier": "t_function_objectcreation_pure$_t_uint256_$returns$_t_array$_t_uint256_$dyn_memory_ptr_$", "typeString": "function (uint256) pure returns (uint256[] memory)" }, "typeName": { "baseType": { "id": 4656, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19199:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4657, "length": null, "nodeType": "ArrayTypeName", "src": "19199:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } } }, "id": 4660, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "19195:21:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "src": "19176:40:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4662, "nodeType": "ExpressionStatement", "src": "19176:40:6" }, { "body": { "id": 4707, "nodeType": "Block", "src": "19266:287:6", "statements": [ { "assignments": [ 4674 ], "declarations": [ { "constant": false, "id": 4674, "mutability": "mutable", "name": "la", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4707, "src": "19284:22:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 4673, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "19284:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 4678, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4675, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4625, "src": "19309:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 4677, "indexExpression": { "argumentTypes": null, "id": 4676, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19322:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "19309:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "19284:40:6" }, { "assignments": [ 4682 ], "declarations": [ { "constant": false, "id": 4682, "mutability": "mutable", "name": "a", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4707, "src": "19342:24:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 4681, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "19342:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" } ], "id": 4685, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4683, "name": "la", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4674, "src": "19369:2:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4684, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "19369:8:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "19342:35:6" }, { "expression": { "argumentTypes": null, "id": 4691, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4686, "name": "principal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4614, "src": "19395:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4688, "indexExpression": { "argumentTypes": null, "id": 4687, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19405:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "19395:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4689, "name": "a", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4682, "src": "19410:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4690, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "19410:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19395:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4692, "nodeType": "ExpressionStatement", "src": "19395:26:6" }, { "expression": { "argumentTypes": null, "id": 4698, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4693, "name": "compoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4617, "src": "19439:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4695, "indexExpression": { "argumentTypes": null, "id": 4694, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19456:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "19439:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4696, "name": "a", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4682, "src": "19461:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 4697, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "19461:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19439:40:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4699, "nodeType": "ExpressionStatement", "src": "19439:40:6" }, { "expression": { "argumentTypes": null, "id": 4705, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4700, "name": "liquidSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4620, "src": "19497:16:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[] memory" } }, "id": 4702, "indexExpression": { "argumentTypes": null, "id": 4701, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19514:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "19497:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4703, "name": "la", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4674, "src": "19519:2:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset storage pointer" } }, "id": 4704, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "19519:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19497:41:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4706, "nodeType": "ExpressionStatement", "src": "19497:41:6" } ] }, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4669, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4667, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19249:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 4668, "name": "length", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4632, "src": "19253:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19249:10:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 4708, "initializationExpression": { "assignments": [ 4664 ], "declarations": [ { "constant": false, "id": 4664, "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4708, "src": "19236:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4663, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19236:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4666, "initialValue": { "argumentTypes": null, "hexValue": "30", "id": 4665, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "19246:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "nodeType": "VariableDeclarationStatement", "src": "19236:11:6" }, "loopExpression": { "expression": { "argumentTypes": null, "id": 4671, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "++", "prefix": true, "src": "19261:3:6", "subExpression": { "argumentTypes": null, "id": 4670, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4664, "src": "19263:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4672, "nodeType": "ExpressionStatement", "src": "19261:3:6" }, "nodeType": "ForStatement", "src": "19231:322:6" } ] } } ] }, "documentation": { "id": 4608, "nodeType": "StructuredDocumentation", "src": "18492:226:6", "text": "@dev Returns locked assets decomposed in to 3 separate arrays (principal, compound interest, liquid since block)\n NOTE(pb): This method might be quite expensive, depending on size of locked assets" }, "functionSelector": "5f208f34", "id": 4712, "implemented": true, "kind": "function", "modifiers": [], "name": "getLockedAssetsForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4611, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4610, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18755:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4609, "name": "address", "nodeType": "ElementaryTypeName", "src": "18755:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "18754:20:6" }, "returnParameters": { "id": 4621, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4614, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18813:26:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[]" }, "typeName": { "baseType": { "id": 4612, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18813:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4613, "length": null, "nodeType": "ArrayTypeName", "src": "18813:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4617, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18841:33:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[]" }, "typeName": { "baseType": { "id": 4615, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18841:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4616, "length": null, "nodeType": "ArrayTypeName", "src": "18841:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4620, "mutability": "mutable", "name": "liquidSinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4712, "src": "18876:33:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_memory_ptr", "typeString": "uint256[]" }, "typeName": { "baseType": { "id": 4618, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "18876:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4619, "length": null, "nodeType": "ArrayTypeName", "src": "18876:9:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_uint256_$dyn_storage_ptr", "typeString": "uint256[]" } }, "value": null, "visibility": "internal" } ], "src": "18812:98:6" }, "scope": 5490, "src": "18723:846:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4753, "nodeType": "Block", "src": "19740:260:6", "statements": [ { "assignments": [ 4726 ], "declarations": [ { "constant": false, "id": 4726, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4753, "src": "19750:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 4725, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "19750:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "id": 4730, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 4727, "name": "_stakes", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3788, "src": "19772:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake storage ref)" } }, "id": 4729, "indexExpression": { "argumentTypes": null, "id": 4728, "name": "forAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4714, "src": "19780:10:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "19772:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage", "typeString": "struct Staking.Stake storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "19750:41:6" }, { "expression": { "argumentTypes": null, "id": 4735, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4731, "name": "principal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4717, "src": "19801:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4732, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19813:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4733, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "19813:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4734, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "19813:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19801:33:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4736, "nodeType": "ExpressionStatement", "src": "19801:33:6" }, { "expression": { "argumentTypes": null, "id": 4741, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4737, "name": "compoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4719, "src": "19844:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4738, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19863:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4739, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "19863:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 4740, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "19863:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19844:47:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4742, "nodeType": "ExpressionStatement", "src": "19844:47:6" }, { "expression": { "argumentTypes": null, "id": 4746, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4743, "name": "sinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4721, "src": "19901:10:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4744, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19914:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4745, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3643, "src": "19914:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19901:29:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4747, "nodeType": "ExpressionStatement", "src": "19901:29:6" }, { "expression": { "argumentTypes": null, "id": 4751, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4748, "name": "sinceInterestRateIndex", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4723, "src": "19940:22:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4749, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4726, "src": "19965:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 4750, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "19965:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "19940:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4752, "nodeType": "ExpressionStatement", "src": "19940:53:6" } ] }, "documentation": null, "functionSelector": "c30f75cf", "id": 4754, "implemented": true, "kind": "function", "modifiers": [], "name": "getStakeForUser", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4715, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4714, "mutability": "mutable", "name": "forAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19601:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 4713, "name": "address", "nodeType": "ElementaryTypeName", "src": "19601:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" } ], "src": "19600:20:6" }, "returnParameters": { "id": 4724, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4717, "mutability": "mutable", "name": "principal", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19643:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4716, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19643:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4719, "mutability": "mutable", "name": "compoundInterest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19662:24:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4718, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19662:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4721, "mutability": "mutable", "name": "sinceBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19688:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4720, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19688:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4723, "mutability": "mutable", "name": "sinceInterestRateIndex", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4754, "src": "19708:30:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4722, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "19708:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "19642:97:6" }, "scope": 5490, "src": "19576:424:6", "stateMutability": "view", "virtual": false, "visibility": "external" }, { "body": { "id": 4798, "nodeType": "Block", "src": "20599:276:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4767, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4765, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20613:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4766, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "20623:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "20613:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 4770, "nodeType": "IfStatement", "src": "20609:48:6", "trueBody": { "id": 4769, "nodeType": "Block", "src": "20626:31:6", "statements": [ { "expression": null, "functionReturnParameters": 4764, "id": 4768, "nodeType": "Return", "src": "20640:7:6" } ] } }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4774, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "20695:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4775, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "20695:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4778, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "20715:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4777, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "20707:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4776, "name": "address", "nodeType": "ElementaryTypeName", "src": "20707:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4779, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20707:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 4780, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20722:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4772, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "20675:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4773, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transferFrom", "nodeType": "MemberAccess", "referencedDeclaration": 6688, "src": "20675:19:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,address,uint256) external returns (bool)" } }, "id": 4781, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20675:54:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73666572206661696c6564", "id": 4782, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "20731:17:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" }, "value": "Transfer failed" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" } ], "id": 4771, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "20667:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4783, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20667:82:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4784, "nodeType": "ExpressionStatement", "src": "20667:82:6" }, { "expression": { "argumentTypes": null, "id": 4790, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4785, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "20759:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4788, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20805:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4786, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "20781:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4787, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "20781:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4789, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20781:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "20759:53:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4791, "nodeType": "ExpressionStatement", "src": "20759:53:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 4793, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "20849:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 4794, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "20849:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4795, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4757, "src": "20861:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4792, "name": "RewardsPoolTokenTopUp", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3744, "src": "20827:21:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 4796, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "20827:41:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4797, "nodeType": "EmitStatement", "src": "20822:46:6" } ] }, "documentation": { "id": 4755, "nodeType": "StructuredDocumentation", "src": "20007:425:6", "text": "@dev Even though this is considered as administrative action (is not affected by\nby contract paused state, it can be executed by anyone who wishes to\ntop-up the rewards pool (funds are sent in to contract, *not* the other way around).\nThe Rewards Pool is exclusively dedicated to cover withdrawals of user' compound interest,\nwhich is effectively the reward." }, "functionSelector": "ba92a4c5", "id": 4799, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4762, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4759, "src": "20576:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4763, "modifierName": { "argumentTypes": null, "id": 4761, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "20557:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "20557:37:6" } ], "name": "topUpRewardsPool", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4760, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4757, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4799, "src": "20472:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4756, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "20472:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4759, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4799, "src": "20496:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4758, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "20496:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "20462:69:6" }, "returnParameters": { "id": 4764, "nodeType": "ParameterList", "parameters": [], "src": "20599:0:6" }, "scope": 5490, "src": "20437:438:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4816, "nodeType": "Block", "src": "21179:47:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4813, "name": "numOfBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4802, "src": "21207:11:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "id": 4812, "name": "_updateLockPeriod", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5079, "src": "21189:17:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint64_$returns$__$", "typeString": "function (uint64)" } }, "id": 4814, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "21189:30:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4815, "nodeType": "ExpressionStatement", "src": "21189:30:6" } ] }, "documentation": { "id": 4800, "nodeType": "StructuredDocumentation", "src": "20882:131:6", "text": "@notice Updates Lock Period value\n@param numOfBlocks length of the lock period\n@dev Delegate only" }, "functionSelector": "f0895e52", "id": 4817, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4807, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4804, "src": "21135:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4808, "modifierName": { "argumentTypes": null, "id": 4806, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "21116:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "21116:37:6" }, { "arguments": null, "id": 4810, "modifierName": { "argumentTypes": null, "id": 4809, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3822, "src": "21162:12:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "21162:12:6" } ], "name": "updateLockPeriod", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4805, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4802, "mutability": "mutable", "name": "numOfBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4817, "src": "21044:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 4801, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "21044:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4804, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4817, "src": "21064:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4803, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "21064:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "21043:47:6" }, "returnParameters": { "id": 4811, "nodeType": "ParameterList", "parameters": [], "src": "21179:0:6" }, "scope": 5490, "src": "21018:208:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4834, "nodeType": "Block", "src": "21671:41:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4831, "name": "blockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4820, "src": "21693:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4830, "name": "_pauseSince", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5104, "src": "21681:11:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 4832, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "21681:24:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4833, "nodeType": "ExpressionStatement", "src": "21681:24:6" } ] }, "documentation": { "id": 4818, "nodeType": "StructuredDocumentation", "src": "21233:277:6", "text": "@notice Pauses all NON-administrative interaction with the contract since the specidfed block number \n@param blockNumber block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)\n@dev Delegate only" }, "functionSelector": "347908df", "id": 4835, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4825, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4822, "src": "21627:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4826, "modifierName": { "argumentTypes": null, "id": 4824, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "21608:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "21608:37:6" }, { "arguments": null, "id": 4828, "modifierName": { "argumentTypes": null, "id": 4827, "name": "onlyDelegate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3822, "src": "21654:12:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "21654:12:6" } ], "name": "pauseSince", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4823, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4820, "mutability": "mutable", "name": "blockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4835, "src": "21535:19:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4819, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "21535:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4822, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4835, "src": "21556:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4821, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "21556:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "21534:48:6" }, "returnParameters": { "id": 4829, "nodeType": "ParameterList", "parameters": [], "src": "21671:0:6" }, "scope": 5490, "src": "21515:197:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4909, "nodeType": "Block", "src": "22190:911:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4852, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4850, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22204:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 4851, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "22214:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "22204:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": { "id": 4865, "nodeType": "Block", "src": "22276:98:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4861, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4859, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22298:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 4860, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "22308:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "22298:29:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "416d6f756e7420686967686572207468616e207265776172647320706f6f6c", "id": 4862, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "22329:33:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_587ce66cc8dcf85ee5804e9c2a7ad26da4041f2a6217e351e0d49635c54a34e6", "typeString": "literal_string \"Amount higher than rewards pool\"" }, "value": "Amount higher than rewards pool" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_587ce66cc8dcf85ee5804e9c2a7ad26da4041f2a6217e351e0d49635c54a34e6", "typeString": "literal_string \"Amount higher than rewards pool\"" } ], "id": 4858, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "22290:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4863, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22290:73:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4864, "nodeType": "ExpressionStatement", "src": "22290:73:6" } ] }, "id": 4866, "nodeType": "IfStatement", "src": "22200:174:6", "trueBody": { "id": 4857, "nodeType": "Block", "src": "22217:53:6", "statements": [ { "expression": { "argumentTypes": null, "id": 4855, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4853, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22231:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 4854, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "22240:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "22231:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4856, "nodeType": "ExpressionStatement", "src": "22231:28:6" } ] } }, { "assignments": [ 4868 ], "declarations": [ { "constant": false, "id": 4868, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4909, "src": "22562:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4867, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22562:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4876, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4873, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "22613:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4872, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "22605:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4871, "name": "address", "nodeType": "ElementaryTypeName", "src": "22605:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4874, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22605:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 4869, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "22588:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4870, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 6646, "src": "22588:16:6", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 4875, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22588:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "22562:57:6" }, { "assignments": [ 4878 ], "declarations": [ { "constant": false, "id": 4878, "mutability": "mutable", "name": "expectedMinContractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4909, "src": "22629:34:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4877, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22629:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4883, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4881, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22694:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4879, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "22666:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4880, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "22666:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4882, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22666:35:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "22629:72:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4887, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4885, "name": "expectedMinContractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4878, "src": "22719:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 4886, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4868, "src": "22749:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "22719:45:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "436f6e747261637420696e636f6e73697374656e63792e", "id": 4888, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "22766:25:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_391f4d8f0a702da0afb416db97e6f601a29dc7e134644564cd5e78cc8b2ef1d5", "typeString": "literal_string \"Contract inconsistency.\"" }, "value": "Contract inconsistency." } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_391f4d8f0a702da0afb416db97e6f601a29dc7e134644564cd5e78cc8b2ef1d5", "typeString": "literal_string \"Contract inconsistency.\"" } ], "id": 4884, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "22711:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4889, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22711:81:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4890, "nodeType": "ExpressionStatement", "src": "22711:81:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4894, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4840, "src": "22827:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4895, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "22842:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4892, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "22811:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4893, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "22811:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 4896, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22811:38:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e", "id": 4897, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "22851:34:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" }, "value": "Not enough funds on contr. addr." } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" } ], "id": 4891, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "22803:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4898, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "22803:83:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4899, "nodeType": "ExpressionStatement", "src": "22803:83:6" }, { "expression": { "argumentTypes": null, "id": 4902, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 4900, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "23000:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "-=", "rightHandSide": { "argumentTypes": null, "id": 4901, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "23023:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "23000:29:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4903, "nodeType": "ExpressionStatement", "src": "23000:29:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4905, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4840, "src": "23072:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4906, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4838, "src": "23087:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4904, "name": "RewardsPoolTokenWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3750, "src": "23045:26:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 4907, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "23045:49:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4908, "nodeType": "EmitStatement", "src": "23040:54:6" } ] }, "documentation": { "id": 4836, "nodeType": "StructuredDocumentation", "src": "21719:257:6", "text": "@dev Withdraw tokens from rewards pool.\n * @param amount : amount to withdraw.\n If `amount == 0` then whole amount in rewards pool will be withdrawn.\n@param targetAddress : address to send tokens to" }, "functionSelector": "68bccfcf", "id": 4910, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4845, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4842, "src": "22149:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4846, "modifierName": { "argumentTypes": null, "id": 4844, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "22130:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "22130:37:6" }, { "arguments": null, "id": 4848, "modifierName": { "argumentTypes": null, "id": 4847, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3806, "src": "22176:9:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "22176:9:6" } ], "name": "withdrawFromRewardsPool", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4843, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4838, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4910, "src": "22014:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4837, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22014:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4840, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4910, "src": "22030:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 4839, "name": "address", "nodeType": "ElementaryTypeName", "src": "22030:15:6", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4842, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4910, "src": "22069:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4841, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "22069:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "22013:91:6" }, "returnParameters": { "id": 4849, "nodeType": "ParameterList", "parameters": [], "src": "22190:0:6" }, "scope": 5490, "src": "21981:1120:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 4961, "nodeType": "Block", "src": "23907:609:6", "statements": [ { "assignments": [ 4924 ], "declarations": [ { "constant": false, "id": 4924, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4961, "src": "23917:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4923, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "23917:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4932, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4929, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "23968:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4928, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "23960:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4927, "name": "address", "nodeType": "ElementaryTypeName", "src": "23960:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4930, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "23960:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 4925, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "23943:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4926, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 6646, "src": "23943:16:6", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 4931, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "23943:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "23917:57:6" }, { "assignments": [ 4934 ], "declarations": [ { "constant": false, "id": 4934, "mutability": "mutable", "name": "expectedMinContractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4961, "src": "23984:34:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4933, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "23984:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4939, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4937, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "24049:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4935, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "24021:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4936, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "24021:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4938, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24021:48:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "23984:85:6" }, { "assignments": [ 4941 ], "declarations": [ { "constant": false, "id": 4941, "mutability": "mutable", "name": "excessAmount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4961, "src": "24275:20:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4940, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "24275:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4946, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4944, "name": "expectedMinContractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4934, "src": "24318:26:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4942, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4924, "src": "24298:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 4943, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "24298:19:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 4945, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24298:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "24275:70:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4950, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4913, "src": "24379:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4951, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4941, "src": "24394:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4948, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "24363:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4949, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "24363:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 4952, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24363:44:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4e6f7420656e6f7567682066756e6473206f6e20636f6e74722e20616464722e", "id": 4953, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "24409:34:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" }, "value": "Not enough funds on contr. addr." } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_fd3fa1113e097b10bbbc2a9e78e5896bda814123efe5dcd9c1d96e852dae5f82", "typeString": "literal_string \"Not enough funds on contr. addr.\"" } ], "id": 4947, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "24355:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4954, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24355:89:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4955, "nodeType": "ExpressionStatement", "src": "24355:89:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4957, "name": "targetAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4913, "src": "24481:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4958, "name": "excessAmount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4941, "src": "24496:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 4956, "name": "ExcessTokenWithdrawal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3738, "src": "24459:21:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", "typeString": "function (address,uint256)" } }, "id": 4959, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "24459:50:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4960, "nodeType": "EmitStatement", "src": "24454:55:6" } ] }, "documentation": { "id": 4911, "nodeType": "StructuredDocumentation", "src": "23108:621:6", "text": "@dev Withdraw \"excess\" tokens, which were sent to contract directly via direct ERC20.transfer(...),\n without interacting with API of this (Staking) contract, what could be done only by mistake.\n Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such\n \"excess\" tokens out of contract.\n@param targetAddress : address to send tokens to\n@param txExpirationBlock : block number until which is the transaction valid (inclusive).\n When transaction is processed after this block, it fails." }, "functionSelector": "53e052ac", "id": 4962, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4918, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4915, "src": "23866:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4919, "modifierName": { "argumentTypes": null, "id": 4917, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "23847:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "23847:37:6" }, { "arguments": null, "id": 4921, "modifierName": { "argumentTypes": null, "id": 4920, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3806, "src": "23893:9:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "23893:9:6" } ], "name": "withdrawExcessTokens", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4916, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4913, "mutability": "mutable", "name": "targetAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4962, "src": "23764:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 4912, "name": "address", "nodeType": "ElementaryTypeName", "src": "23764:15:6", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4915, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 4962, "src": "23795:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4914, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "23795:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "23763:58:6" }, "returnParameters": { "id": 4922, "nodeType": "ParameterList", "parameters": [], "src": "23907:0:6" }, "scope": 5490, "src": "23734:782:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 5008, "nodeType": "Block", "src": "24995:294:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 4979, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 4976, "name": "_earliestDelete", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3764, "src": "25013:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">=", "rightExpression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 4977, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "25032:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 4978, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25032:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "25013:36:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4561726c696573742064656c657465206e6f742072656163686564", "id": 4980, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "25051:29:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" }, "value": "Earliest delete not reached" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_5f181249ab5034a5754d3db6b5f7492a28b118cbf69c6f93d3cfe542886db22a", "typeString": "literal_string \"Earliest delete not reached\"" } ], "id": 4975, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "25005:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 4981, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25005:76:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 4982, "nodeType": "ExpressionStatement", "src": "25005:76:6" }, { "assignments": [ 4984 ], "declarations": [ { "constant": false, "id": 4984, "mutability": "mutable", "name": "contractBalance", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5008, "src": "25091:23:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4983, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "25091:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 4992, "initialValue": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4989, "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -28, "src": "25142:4:6", "typeDescriptions": { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_contract$_Staking_$5490", "typeString": "contract Staking" } ], "id": 4988, "isConstant": false, "isLValue": false, "isPure": true, "lValueRequested": false, "nodeType": "ElementaryTypeNameExpression", "src": "25134:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_address_$", "typeString": "type(address)" }, "typeName": { "id": 4987, "name": "address", "nodeType": "ElementaryTypeName", "src": "25134:7:6", "typeDescriptions": { "typeIdentifier": null, "typeString": null } } }, "id": 4990, "isConstant": false, "isLValue": false, "isPure": false, "kind": "typeConversion", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25134:13:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" } ], "expression": { "argumentTypes": null, "id": 4985, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "25117:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4986, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "balanceOf", "nodeType": "MemberAccess", "referencedDeclaration": 6646, "src": "25117:16:6", "typeDescriptions": { "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", "typeString": "function (address) view external returns (uint256)" } }, "id": 4991, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25117:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "25091:57:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 4996, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4965, "src": "25182:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "id": 4997, "name": "contractBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4984, "src": "25197:15:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 4994, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "25166:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 4995, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "25166:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 4998, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25166:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" } ], "id": 4993, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "25158:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", "typeString": "function (bool) pure" } }, "id": 4999, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25158:56:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5000, "nodeType": "ExpressionStatement", "src": "25158:56:6" }, { "eventCall": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 5001, "name": "DeleteContract", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3752, "src": "25229:14:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 5002, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25229:16:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5003, "nodeType": "EmitStatement", "src": "25224:21:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5005, "name": "payoutAddress", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4965, "src": "25268:13:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 5004, "name": "selfdestruct", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -21, "src": "25255:12:6", "typeDescriptions": { "typeIdentifier": "t_function_selfdestruct_nonpayable$_t_address_payable_$returns$__$", "typeString": "function (address payable)" } }, "id": 5006, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25255:27:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5007, "nodeType": "ExpressionStatement", "src": "25255:27:6" } ] }, "documentation": { "id": 4963, "nodeType": "StructuredDocumentation", "src": "24523:312:6", "text": "@notice Delete the contract, transfers the remaining token and ether balance to the specified\npayoutAddress\n@param payoutAddress address to transfer the balances to. Ensure that this is able to handle ERC20 tokens\n@dev owner only + only on or after `_earliestDelete` block" }, "functionSelector": "9608df4b", "id": 5009, "implemented": true, "kind": "function", "modifiers": [ { "arguments": [ { "argumentTypes": null, "id": 4970, "name": "txExpirationBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 4967, "src": "24958:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 4971, "modifierName": { "argumentTypes": null, "id": 4969, "name": "verifyTxExpiration", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3836, "src": "24939:18:6", "typeDescriptions": { "typeIdentifier": "t_modifier$_t_uint256_$", "typeString": "modifier (uint256)" } }, "nodeType": "ModifierInvocation", "src": "24939:37:6" }, { "arguments": null, "id": 4973, "modifierName": { "argumentTypes": null, "id": 4972, "name": "onlyOwner", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3806, "src": "24981:9:6", "typeDescriptions": { "typeIdentifier": "t_modifier$__$", "typeString": "modifier ()" } }, "nodeType": "ModifierInvocation", "src": "24981:9:6" } ], "name": "deleteContract", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 4968, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 4965, "mutability": "mutable", "name": "payoutAddress", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5009, "src": "24864:29:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, "typeName": { "id": 4964, "name": "address", "nodeType": "ElementaryTypeName", "src": "24864:15:6", "stateMutability": "payable", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 4967, "mutability": "mutable", "name": "txExpirationBlock", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5009, "src": "24895:25:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 4966, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "24895:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "24863:58:6" }, "returnParameters": { "id": 4974, "nodeType": "ParameterList", "parameters": [], "src": "24995:0:6" }, "scope": 5490, "src": "24840:449:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "external" }, { "body": { "id": 5018, "nodeType": "Block", "src": "25630:36:6", "statements": [ { "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5015, "name": "block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -4, "src": "25647:5:6", "typeDescriptions": { "typeIdentifier": "t_magic_block", "typeString": "block" } }, "id": 5016, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "number", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "25647:12:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "functionReturnParameters": 5014, "id": 5017, "nodeType": "Return", "src": "25640:19:6" } ] }, "documentation": { "id": 5010, "nodeType": "StructuredDocumentation", "src": "25431:124:6", "text": "@dev VIRTUAL Method returning bock number. Introduced for \n testing purposes (allows mocking)." }, "id": 5019, "implemented": true, "kind": "function", "modifiers": [], "name": "_getBlockNumber", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5011, "nodeType": "ParameterList", "parameters": [], "src": "25584:2:6" }, "returnParameters": { "id": 5014, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5013, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5019, "src": "25617:7:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5012, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "25617:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "25616:9:6" }, "scope": 5490, "src": "25560:106:6", "stateMutability": "view", "virtual": true, "visibility": "internal" }, { "body": { "id": 5030, "nodeType": "Block", "src": "25721:63:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5025, "name": "DEFAULT_ADMIN_ROLE", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5584, "src": "25746:18:6", "typeDescriptions": { "typeIdentifier": "t_bytes32", "typeString": "bytes32" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5026, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "25766:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 5027, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "25766:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bytes32", "typeString": "bytes32" }, { "typeIdentifier": "t_address_payable", "typeString": "address payable" } ], "id": 5024, "name": "hasRole", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5630, "src": "25738:7:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_bytes32_$_t_address_$returns$_t_bool_$", "typeString": "function (bytes32,address) view returns (bool)" } }, "id": 5028, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "25738:39:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "functionReturnParameters": 5023, "id": 5029, "nodeType": "Return", "src": "25731:46:6" } ] }, "documentation": null, "id": 5031, "implemented": true, "kind": "function", "modifiers": [], "name": "_isOwner", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5020, "nodeType": "ParameterList", "parameters": [], "src": "25690:2:6" }, "returnParameters": { "id": 5023, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5022, "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5031, "src": "25715:4:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 5021, "name": "bool", "nodeType": "ElementaryTypeName", "src": "25715:4:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "src": "25714:6:6" }, "scope": 5490, "src": "25673:111:6", "stateMutability": "view", "virtual": false, "visibility": "internal" }, { "body": { "id": 5063, "nodeType": "Block", "src": "26069:336:6", "statements": [ { "assignments": [ 5038 ], "declarations": [ { "constant": false, "id": 5038, "mutability": "mutable", "name": "idx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5063, "src": "26079:11:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5037, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26079:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5040, "initialValue": { "argumentTypes": null, "id": 5039, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "26093:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "26079:35:6" }, { "expression": { "argumentTypes": null, "id": 5049, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5041, "name": "_interestRates", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3784, "src": "26124:14:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock storage ref)" } }, "id": 5043, "indexExpression": { "argumentTypes": null, "id": 5042, "name": "idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5038, "src": "26139:3:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "26124:19:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 5045, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "26195:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 5046, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26195:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "id": 5047, "name": "rate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5034, "src": "26233:4:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5044, "name": "InterestRatePerBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3641, "src": "26146:20:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_struct$_InterestRatePerBlock_$3641_storage_ptr_$", "typeString": "type(struct Staking.InterestRatePerBlock storage pointer)" } }, "id": 5048, "isConstant": false, "isLValue": false, "isPure": false, "kind": "structConstructorCall", "lValueRequested": false, "names": [ "sinceBlock", "rate" ], "nodeType": "FunctionCall", "src": "26146:148:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_memory_ptr", "typeString": "struct Staking.InterestRatePerBlock memory" } }, "src": "26124:170:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "id": 5050, "nodeType": "ExpressionStatement", "src": "26124:170:6" }, { "expression": { "argumentTypes": null, "id": 5056, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5051, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "26304:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "hexValue": "31", "id": 5054, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "26354:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" } ], "expression": { "argumentTypes": null, "id": 5052, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "26328:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5053, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "26328:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5055, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26328:28:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "26304:52:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5057, "nodeType": "ExpressionStatement", "src": "26304:52:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5059, "name": "idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5038, "src": "26388:3:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "id": 5060, "name": "rate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5034, "src": "26393:4:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5058, "name": "NewInterestRate", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3710, "src": "26372:15:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (uint256,uint256)" } }, "id": 5061, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26372:26:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5062, "nodeType": "EmitStatement", "src": "26367:31:6" } ] }, "documentation": { "id": 5032, "nodeType": "StructuredDocumentation", "src": "25791:219:6", "text": "@notice Add new interest rate in to the ordered container of previously added interest rates\n@param rate - signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18" }, "id": 5064, "implemented": true, "kind": "function", "modifiers": [], "name": "_addInterestRate", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5035, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5034, "mutability": "mutable", "name": "rate", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5064, "src": "26041:12:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5033, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26041:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "26040:14:6" }, "returnParameters": { "id": 5036, "nodeType": "ParameterList", "parameters": [], "src": "26069:0:6" }, "scope": 5490, "src": "26015:390:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5078, "nodeType": "Block", "src": "26582:88:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5072, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5070, "name": "_lockPeriodInBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3768, "src": "26592:19:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 5071, "name": "numOfBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5067, "src": "26614:11:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "src": "26592:33:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "id": 5073, "nodeType": "ExpressionStatement", "src": "26592:33:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5075, "name": "numOfBlocks", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5067, "src": "26651:11:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint64", "typeString": "uint64" } ], "id": 5074, "name": "LockPeriod", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3722, "src": "26640:10:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint64_$returns$__$", "typeString": "function (uint64)" } }, "id": 5076, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "26640:23:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5077, "nodeType": "EmitStatement", "src": "26635:28:6" } ] }, "documentation": { "id": 5065, "nodeType": "StructuredDocumentation", "src": "26412:105:6", "text": "@notice Updates Lock Period value\n@param numOfBlocks length of the lock period" }, "id": 5079, "implemented": true, "kind": "function", "modifiers": [], "name": "_updateLockPeriod", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5068, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5067, "mutability": "mutable", "name": "numOfBlocks", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5079, "src": "26549:18:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" }, "typeName": { "id": 5066, "name": "uint64", "nodeType": "ElementaryTypeName", "src": "26549:6:6", "typeDescriptions": { "typeIdentifier": "t_uint64", "typeString": "uint64" } }, "value": null, "visibility": "internal" } ], "src": "26548:20:6" }, "returnParameters": { "id": 5069, "nodeType": "ParameterList", "parameters": [], "src": "26582:0:6" }, "scope": 5490, "src": "26522:148:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5103, "nodeType": "Block", "src": "26989:199:6", "statements": [ { "assignments": [ 5086 ], "declarations": [ { "constant": false, "id": 5086, "mutability": "mutable", "name": "currentBlockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5103, "src": "26999:26:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5085, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26999:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5089, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "id": 5087, "name": "_getBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5019, "src": "27028:15:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", "typeString": "function () view returns (uint256)" } }, "id": 5088, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "27028:17:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "26999:46:6" }, { "expression": { "argumentTypes": null, "id": 5097, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5090, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3766, "src": "27055:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5093, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5091, "name": "blockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5082, "src": "27075:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 5092, "name": "currentBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5086, "src": "27089:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "27075:32:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseExpression": { "argumentTypes": null, "id": 5095, "name": "blockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5082, "src": "27131:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5096, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "Conditional", "src": "27075:67:6", "trueExpression": { "argumentTypes": null, "id": 5094, "name": "currentBlockNumber", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5086, "src": "27110:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "27055:87:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5098, "nodeType": "ExpressionStatement", "src": "27055:87:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5100, "name": "_pausedSinceBlock", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3766, "src": "27163:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5099, "name": "Pause", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3726, "src": "27157:5:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", "typeString": "function (uint256)" } }, "id": 5101, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "27157:24:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5102, "nodeType": "EmitStatement", "src": "27152:29:6" } ] }, "documentation": { "id": 5080, "nodeType": "StructuredDocumentation", "src": "26677:251:6", "text": "@notice Pauses all NON-administrative interaction with the contract since the specidfed block number \n@param blockNumber block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)" }, "id": 5104, "implemented": true, "kind": "function", "modifiers": [], "name": "_pauseSince", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5083, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5082, "mutability": "mutable", "name": "blockNumber", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5104, "src": "26954:19:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5081, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "26954:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "26953:21:6" }, "returnParameters": { "id": 5084, "nodeType": "ParameterList", "parameters": [], "src": "26989:0:6" }, "scope": 5490, "src": "26933:255:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5167, "nodeType": "Block", "src": "27984:719:6", "statements": [ { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5116, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5114, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5111, "src": "27999:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5115, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "28009:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "27999:11:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5166, "nodeType": "IfStatement", "src": "27995:702:6", "trueBody": { "id": 5165, "nodeType": "Block", "src": "28012:685:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5121, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5118, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "28034:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">=", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5119, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28057:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5120, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "28057:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "28034:47:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "4e6f7420656e6f7567682066756e647320696e207265776172647320706f6f6c", "id": 5122, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "28083:34:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_eea74719e5ae8f4b294d08561dadf2343b6f65323137e057e863d77438841724", "typeString": "literal_string \"Not enough funds in rewards pool\"" }, "value": "Not enough funds in rewards pool" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_eea74719e5ae8f4b294d08561dadf2343b6f65323137e057e863d77438841724", "typeString": "literal_string \"Not enough funds in rewards pool\"" } ], "id": 5117, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "28026:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 5123, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28026:92:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5124, "nodeType": "ExpressionStatement", "src": "28026:92:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5128, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5107, "src": "28156:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "id": 5129, "name": "amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5111, "src": "28164:6:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5126, "name": "_token", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3762, "src": "28140:6:6", "typeDescriptions": { "typeIdentifier": "t_contract$_IERC20_$6707", "typeString": "contract IERC20" } }, "id": 5127, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "transfer", "nodeType": "MemberAccess", "referencedDeclaration": 6656, "src": "28140:15:6", "typeDescriptions": { "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", "typeString": "function (address,uint256) external returns (bool)" } }, "id": 5130, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28140:31:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "5472616e73666572206661696c6564", "id": 5131, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "28173:17:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" }, "value": "Transfer failed" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_25adaa6d082ce15f901e0d8a3d393e7462ef9edf2e6bc8321fa14d1615b6fc51", "typeString": "literal_string \"Transfer failed\"" } ], "id": 5125, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "28132:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 5132, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28132:59:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5133, "nodeType": "ExpressionStatement", "src": "28132:59:6" }, { "expression": { "argumentTypes": null, "id": 5140, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5134, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "28206:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5137, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28252:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5138, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "28252:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5135, "name": "_rewardsPoolBalance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3770, "src": "28228:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5136, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "28228:23:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5139, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28228:49:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "28206:71:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5141, "nodeType": "ExpressionStatement", "src": "28206:71:6" }, { "expression": { "argumentTypes": null, "id": 5148, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5142, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "28291:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5145, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28345:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5146, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "28345:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5143, "name": "_accruedGlobalPrincipal", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3772, "src": "28317:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5144, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "28317:27:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5147, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28317:46:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "28291:72:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5149, "nodeType": "ExpressionStatement", "src": "28291:72:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5153, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28406:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5150, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "28377:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5152, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "28377:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5154, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28377:37:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5155, "nodeType": "ExpressionStatement", "src": "28377:37:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5157, "name": "msg", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": -15, "src": "28629:3:6", "typeDescriptions": { "typeIdentifier": "t_magic_message", "typeString": "msg" } }, "id": 5158, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sender", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "28629:10:6", "typeDescriptions": { "typeIdentifier": "t_address_payable", "typeString": "address payable" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5159, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28641:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5160, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "28641:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5161, "name": "_amount", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5109, "src": "28660:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5162, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "28660:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address_payable", "typeString": "address payable" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5156, "name": "Withdraw", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3718, "src": "28620:8:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 5163, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28620:65:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5164, "nodeType": "EmitStatement", "src": "28615:70:6" } ] } } ] }, "documentation": { "id": 5105, "nodeType": "StructuredDocumentation", "src": "27195:685:6", "text": "@notice Withdraws amount from sender' available liquidity pool back to sender address,\n preferring withdrawal from compound interest dimension of liquidity.\n * @param amount - value to withdraw\n * @dev NOTE(pb): Passing redundant `uint256 amount` (on top of the `Asset _amount`) in the name\n of performance to avoid calculating it again from `_amount` (or the other way around).\n IMPLICATION: Caller **MUST** pass correct values, ensuring that `amount == _amount.composite()`,\n since this private method is **NOT** verifying this condition due to performance reasons." }, "id": 5168, "implemented": true, "kind": "function", "modifiers": [], "name": "_finaliseWithdraw", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5112, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5107, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5168, "src": "27912:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 5106, "name": "address", "nodeType": "ElementaryTypeName", "src": "27912:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5109, "mutability": "mutable", "name": "_amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5168, "src": "27928:29:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 5108, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "27928:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5111, "mutability": "mutable", "name": "amount", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5168, "src": "27959:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5110, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "27959:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "27911:63:6" }, "returnParameters": { "id": 5113, "nodeType": "ParameterList", "parameters": [], "src": "27984:0:6" }, "scope": 5490, "src": "27885:818:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5314, "nodeType": "Block", "src": "28840:2765:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5181, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5177, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "28850:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5178, "name": "_stakes", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3788, "src": "28858:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Stake_$3648_storage_$", "typeString": "mapping(address => struct Staking.Stake storage ref)" } }, "id": 5180, "indexExpression": { "argumentTypes": null, "id": 5179, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5170, "src": "28866:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "28858:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage", "typeString": "struct Staking.Stake storage ref" } }, "src": "28850:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5182, "nodeType": "ExpressionStatement", "src": "28850:23:6" }, { "assignments": [ 5184 ], "declarations": [ { "constant": false, "id": 5184, "mutability": "mutable", "name": "composite", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5314, "src": "28883:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5183, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "28883:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5189, "initialValue": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5185, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "28903:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5186, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "28903:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5187, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "composite", "nodeType": "MemberAccess", "referencedDeclaration": 2803, "src": "28903:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_view$_t_struct$_Asset_$2788_storage_ptr_$returns$_t_uint256_$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer) view returns (uint256)" } }, "id": 5188, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "28903:23:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "28883:43:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5192, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5190, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "28940:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5191, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "28953:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "28940:14:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5281, "nodeType": "IfStatement", "src": "28936:1369:6", "trueBody": { "id": 5280, "nodeType": "Block", "src": "28964:1341:6", "statements": [ { "assignments": [ 5194 ], "declarations": [ { "constant": false, "id": 5194, "mutability": "mutable", "name": "start_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5280, "src": "29048:19:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5193, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29048:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5197, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5195, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "29070:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5196, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3643, "src": "29070:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29048:38:6" }, { "body": { "id": 5265, "nodeType": "Block", "src": "29411:802:6", "statements": [ { "assignments": [ 5210 ], "declarations": [ { "constant": false, "id": 5210, "mutability": "mutable", "name": "interest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5265, "src": "29429:37:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" }, "typeName": { "contractScope": null, "id": 5209, "name": "InterestRatePerBlock", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3641, "src": "29429:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" } }, "value": null, "visibility": "internal" } ], "id": 5214, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5211, "name": "_interestRates", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3784, "src": "29469:14:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock storage ref)" } }, "id": 5213, "indexExpression": { "argumentTypes": null, "id": 5212, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29484:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "29469:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "29429:57:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5219, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5216, "name": "interest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5210, "src": "29697:8:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock storage pointer" } }, "id": 5217, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3638, "src": "29697:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<=", "rightExpression": { "argumentTypes": null, "id": 5218, "name": "start_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5194, "src": "29720:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29697:34:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, { "argumentTypes": null, "hexValue": "73696e6365426c6f636b20696e636f6e73697374656e6379", "id": 5220, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", "src": "29733:26:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_stringliteral_0b30ca9f63e0c2596e072157a3416c4519a0da0c7a77930c294d44b40e819c8c", "typeString": "literal_string \"sinceBlock inconsistency\"" }, "value": "sinceBlock inconsistency" } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_bool", "typeString": "bool" }, { "typeIdentifier": "t_stringliteral_0b30ca9f63e0c2596e072157a3416c4519a0da0c7a77930c294d44b40e819c8c", "typeString": "literal_string \"sinceBlock inconsistency\"" } ], "id": 5215, "name": "require", "nodeType": "Identifier", "overloadedDeclarations": [ -18, -18 ], "referencedDeclaration": -18, "src": "29689:7:6", "typeDescriptions": { "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", "typeString": "function (bool,string memory) pure" } }, "id": 5221, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "29689:71:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5222, "nodeType": "ExpressionStatement", "src": "29689:71:6" }, { "assignments": [ 5224 ], "declarations": [ { "constant": false, "id": 5224, "mutability": "mutable", "name": "end_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5265, "src": "29778:17:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5223, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29778:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5226, "initialValue": { "argumentTypes": null, "id": 5225, "name": "at_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5172, "src": "29798:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29778:28:6" }, { "assignments": [ 5228 ], "declarations": [ { "constant": false, "id": 5228, "mutability": "mutable", "name": "j", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5265, "src": "29825:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5227, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29825:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5232, "initialValue": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5231, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5229, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29837:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "+", "rightExpression": { "argumentTypes": null, "hexValue": "31", "id": 5230, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "29841:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" }, "src": "29837:5:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29825:17:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5235, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5233, "name": "j", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5228, "src": "29864:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 5234, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "29868:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29864:25:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5248, "nodeType": "IfStatement", "src": "29860:192:6", "trueBody": { "id": 5247, "nodeType": "Block", "src": "29891:161:6", "statements": [ { "assignments": [ 5237 ], "declarations": [ { "constant": false, "id": 5237, "mutability": "mutable", "name": "next_interest", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5247, "src": "29913:42:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" }, "typeName": { "contractScope": null, "id": 5236, "name": "InterestRatePerBlock", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3641, "src": "29913:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock" } }, "value": null, "visibility": "internal" } ], "id": 5241, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5238, "name": "_interestRates", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3784, "src": "29958:14:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_uint256_$_t_struct$_InterestRatePerBlock_$3641_storage_$", "typeString": "mapping(uint256 => struct Staking.InterestRatePerBlock storage ref)" } }, "id": 5240, "indexExpression": { "argumentTypes": null, "id": 5239, "name": "j", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5228, "src": "29973:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "29958:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage", "typeString": "struct Staking.InterestRatePerBlock storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "29913:62:6" }, { "expression": { "argumentTypes": null, "id": 5245, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5242, "name": "end_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5224, "src": "29997:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5243, "name": "next_interest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5237, "src": "30009:13:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock storage pointer" } }, "id": 5244, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3638, "src": "30009:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29997:36:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5246, "nodeType": "ExpressionStatement", "src": "29997:36:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5259, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5249, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "30070:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5252, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "30107:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5253, "name": "interest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5210, "src": "30118:8:6", "typeDescriptions": { "typeIdentifier": "t_struct$_InterestRatePerBlock_$3641_storage_ptr", "typeString": "struct Staking.InterestRatePerBlock storage pointer" } }, "id": 5254, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "rate", "nodeType": "MemberAccess", "referencedDeclaration": 3640, "src": "30118:13:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5257, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5255, "name": "end_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5224, "src": "30133:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "-", "rightExpression": { "argumentTypes": null, "id": 5256, "name": "start_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5194, "src": "30145:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30133:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5250, "name": "Finance", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3318, "src": "30082:7:6", "typeDescriptions": { "typeIdentifier": "t_type$_t_contract$_Finance_$3318_$", "typeString": "type(library Finance)" } }, "id": 5251, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 3249, "src": "30082:24:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$_t_uint256_$returns$_t_uint256_$", "typeString": "function (uint256,uint256,uint256) pure returns (uint256)" } }, "id": 5258, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "30082:75:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30070:87:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5260, "nodeType": "ExpressionStatement", "src": "30070:87:6" }, { "expression": { "argumentTypes": null, "id": 5263, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5261, "name": "start_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5194, "src": "30175:11:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 5262, "name": "end_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5224, "src": "30189:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30175:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5264, "nodeType": "ExpressionStatement", "src": "30175:23:6" } ] }, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5205, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5203, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29379:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "id": 5204, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "29383:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "29379:25:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 5266, "initializationExpression": { "assignments": [ 5199 ], "declarations": [ { "constant": false, "id": 5199, "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5266, "src": "29339:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5198, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "29339:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5202, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5200, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "29349:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5201, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "29349:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "29339:38:6" }, "loopExpression": { "expression": { "argumentTypes": null, "id": 5207, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "++", "prefix": true, "src": "29406:3:6", "subExpression": { "argumentTypes": null, "id": 5206, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5199, "src": "29408:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5208, "nodeType": "ExpressionStatement", "src": "29406:3:6" }, "nodeType": "ForStatement", "src": "29334:879:6" }, { "expression": { "argumentTypes": null, "id": 5278, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5267, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30227:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5270, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "30227:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5271, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "30227:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5274, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30272:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5275, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "30272:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5276, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "30272:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "id": 5272, "name": "composite", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5184, "src": "30258:9:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5273, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "sub", "nodeType": "MemberAccess", "referencedDeclaration": 5887, "src": "30258:13:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5277, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "30258:36:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30227:67:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5279, "nodeType": "ExpressionStatement", "src": "30227:67:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5286, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5282, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30315:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5284, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "sinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3643, "src": "30315:16:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "id": 5285, "name": "at_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5172, "src": "30334:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30315:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5287, "nodeType": "ExpressionStatement", "src": "30315:27:6" }, { "expression": { "argumentTypes": null, "id": 5300, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5288, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "30352:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5290, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "30352:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "components": [ { "argumentTypes": null, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5293, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5291, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "30384:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5292, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "30409:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "30384:26:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseExpression": { "argumentTypes": null, "hexValue": "30", "id": 5297, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "30441:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "id": 5298, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "Conditional", "src": "30384:58:6", "trueExpression": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5296, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5294, "name": "_interestRatesNextIdx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3780, "src": "30413:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "-", "rightExpression": { "argumentTypes": null, "hexValue": "31", "id": 5295, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "30437:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" }, "src": "30413:25:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "id": 5299, "isConstant": false, "isInlineArray": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "TupleExpression", "src": "30383:60:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "30352:91:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5301, "nodeType": "ExpressionStatement", "src": "30352:91:6" }, { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5303, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5170, "src": "31508:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5304, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "31516:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5305, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "sinceInterestRateIndex", "nodeType": "MemberAccess", "referencedDeclaration": 3645, "src": "31516:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5306, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "31546:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5307, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "31546:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5308, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "31546:21:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5309, "name": "stake", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5175, "src": "31569:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake storage pointer" } }, "id": 5310, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3647, "src": "31569:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5311, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "31569:28:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5302, "name": "StakeCompoundInterest", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3680, "src": "31486:21:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256,uint256)" } }, "id": 5312, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "31486:112:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5313, "nodeType": "EmitStatement", "src": "31481:117:6" } ] }, "documentation": null, "id": 5315, "implemented": true, "kind": "function", "modifiers": [], "name": "_updateStakeCompoundInterest", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5173, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5170, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5315, "src": "28748:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 5169, "name": "address", "nodeType": "ElementaryTypeName", "src": "28748:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5172, "mutability": "mutable", "name": "at_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5315, "src": "28764:16:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5171, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "28764:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "28747:34:6" }, "returnParameters": { "id": 5176, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5175, "mutability": "mutable", "name": "stake", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5315, "src": "28815:19:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" }, "typeName": { "contractScope": null, "id": 5174, "name": "Stake", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3648, "src": "28815:5:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Stake_$3648_storage_ptr", "typeString": "struct Staking.Stake" } }, "value": null, "visibility": "internal" } ], "src": "28814:21:6" }, "scope": 5490, "src": "28710:2895:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" }, { "body": { "id": 5488, "nodeType": "Block", "src": "31801:2505:6", "statements": [ { "assignments": [ 5329 ], "declarations": [ { "constant": false, "id": 5329, "mutability": "mutable", "name": "locked", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5488, "src": "31811:21:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" }, "typeName": { "contractScope": null, "id": 5328, "name": "Locked", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3659, "src": "31811:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked" } }, "value": null, "visibility": "internal" } ], "id": 5333, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5330, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "31835:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 5332, "indexExpression": { "argumentTypes": null, "id": 5331, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "31843:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "31835:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "31811:39:6" }, { "assignments": [ 5337 ], "declarations": [ { "constant": false, "id": 5337, "mutability": "mutable", "name": "lockedAssets", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5488, "src": "31860:34:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" }, "typeName": { "baseType": { "contractScope": null, "id": 5335, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "31860:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "id": 5336, "length": null, "nodeType": "ArrayTypeName", "src": "31860:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset[]" } }, "value": null, "visibility": "internal" } ], "id": 5340, "initialValue": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5338, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5329, "src": "31897:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 5339, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "assets", "nodeType": "MemberAccess", "referencedDeclaration": 3658, "src": "31897:13:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage", "typeString": "struct Staking.LockedAsset storage ref[] storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "31860:50:6" }, { "expression": { "argumentTypes": null, "id": 5345, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5341, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5324, "src": "31920:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5342, "name": "_liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3796, "src": "31932:10:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Asset_$2788_storage_$", "typeString": "mapping(address => struct AssetLib.Asset storage ref)" } }, "id": 5344, "indexExpression": { "argumentTypes": null, "id": 5343, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "31943:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "31932:18:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "src": "31920:30:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 5346, "nodeType": "ExpressionStatement", "src": "31920:30:6" }, { "body": { "id": 5420, "nodeType": "Block", "src": "32006:1628:6", "statements": [ { "assignments": [ 5356 ], "declarations": [ { "constant": false, "id": 5356, "mutability": "mutable", "name": "l", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5420, "src": "32020:20:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset" }, "typeName": { "contractScope": null, "id": 5355, "name": "LockedAsset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 3653, "src": "32020:11:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage_ptr", "typeString": "struct Staking.LockedAsset" } }, "value": null, "visibility": "internal" } ], "id": 5360, "initialValue": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5357, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "32043:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5359, "indexExpression": { "argumentTypes": null, "id": 5358, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "32056:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "32043:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "VariableDeclarationStatement", "src": "32020:38:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5364, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5361, "name": "l", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5356, "src": "32077:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset memory" } }, "id": 5362, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "liquidSinceBlock", "nodeType": "MemberAccess", "referencedDeclaration": 3650, "src": "32077:18:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": ">", "rightExpression": { "argumentTypes": null, "id": 5363, "name": "at_block", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5319, "src": "32098:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "32077:29:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5370, "nodeType": "IfStatement", "src": "32073:191:6", "trueBody": { "id": 5369, "nodeType": "Block", "src": "32108:156:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5366, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "++", "prefix": true, "src": "32126:3:6", "subExpression": { "argumentTypes": null, "id": 5365, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "32128:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5367, "nodeType": "ExpressionStatement", "src": "32126:3:6" }, { "id": 5368, "nodeType": "Continue", "src": "32241:8:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5381, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5371, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32278:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5373, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "32278:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5377, "name": "l", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5356, "src": "32340:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset memory" } }, "id": 5378, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "32340:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5379, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "32340:17:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5374, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32308:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5375, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "32308:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5376, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "32308:31:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5380, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "32308:50:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "32278:80:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5382, "nodeType": "ExpressionStatement", "src": "32278:80:6" }, { "expression": { "argumentTypes": null, "id": 5393, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5383, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32576:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5385, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "32576:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5389, "name": "l", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5356, "src": "32652:1:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_memory_ptr", "typeString": "struct Staking.LockedAsset memory" } }, "id": 5390, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "asset", "nodeType": "MemberAccess", "referencedDeclaration": 3652, "src": "32652:7:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5391, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "32652:24:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5386, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "32613:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5387, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "32613:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5388, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "add", "nodeType": "MemberAccess", "referencedDeclaration": 5870, "src": "32613:38:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", "typeString": "function (uint256,uint256) pure returns (uint256)" } }, "id": 5392, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "32613:64:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "32576:101:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "id": 5394, "nodeType": "ExpressionStatement", "src": "32576:101:6" }, { "assignments": [ 5396 ], "declarations": [ { "constant": false, "id": 5396, "mutability": "mutable", "name": "last_idx", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5420, "src": "32959:16:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5395, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "32959:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5401, "initialValue": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5400, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5397, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "32978:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5398, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "32978:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "-", "rightExpression": { "argumentTypes": null, "hexValue": "31", "id": 5399, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33000:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_1_by_1", "typeString": "int_const 1" }, "value": "1" }, "src": "32978:23:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "VariableDeclarationStatement", "src": "32959:42:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5404, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5402, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "33019:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "id": 5403, "name": "last_idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5396, "src": "33024:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "33019:13:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5414, "nodeType": "IfStatement", "src": "33015:92:6", "trueBody": { "id": 5413, "nodeType": "Block", "src": "33034:73:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5411, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5405, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33052:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5407, "indexExpression": { "argumentTypes": null, "id": 5406, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "33065:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "33052:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5408, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33070:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5410, "indexExpression": { "argumentTypes": null, "id": 5409, "name": "last_idx", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5396, "src": "33083:8:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "nodeType": "IndexAccess", "src": "33070:22:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "src": "33052:40:6", "typeDescriptions": { "typeIdentifier": "t_struct$_LockedAsset_$3653_storage", "typeString": "struct Staking.LockedAsset storage ref" } }, "id": 5412, "nodeType": "ExpressionStatement", "src": "33052:40:6" } ] } }, { "expression": { "argumentTypes": null, "arguments": [], "expression": { "argumentTypes": [], "expression": { "argumentTypes": null, "id": 5415, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33605:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5417, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "pop", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "33605:16:6", "typeDescriptions": { "typeIdentifier": "t_function_arraypop_nonpayable$__$returns$__$", "typeString": "function ()" } }, "id": 5418, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "33605:18:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5419, "nodeType": "ExpressionStatement", "src": "33605:18:6" } ] }, "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5354, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "id": 5351, "name": "i", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5348, "src": "31979:1:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "<", "rightExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5352, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "31983:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5353, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "31983:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "src": "31979:23:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 5421, "initializationExpression": { "assignments": [ 5348 ], "declarations": [ { "constant": false, "id": 5348, "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5421, "src": "31966:9:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5347, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "31966:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "id": 5350, "initialValue": { "argumentTypes": null, "hexValue": "30", "id": 5349, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "31976:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "nodeType": "VariableDeclarationStatement", "src": "31966:11:6" }, "loopExpression": null, "nodeType": "ForStatement", "src": "31961:1673:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5425, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5422, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "33699:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5423, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "33699:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "==", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5424, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33722:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "33699:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5432, "nodeType": "IfStatement", "src": "33695:77:6", "trueBody": { "id": 5431, "nodeType": "Block", "src": "33725:47:6", "statements": [ { "expression": { "argumentTypes": null, "id": 5429, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "nodeType": "UnaryOperation", "operator": "delete", "prefix": true, "src": "33739:22:6", "subExpression": { "argumentTypes": null, "baseExpression": { "argumentTypes": null, "id": 5426, "name": "_locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3792, "src": "33746:7:6", "typeDescriptions": { "typeIdentifier": "t_mapping$_t_address_$_t_struct$_Locked_$3659_storage_$", "typeString": "mapping(address => struct Staking.Locked storage ref)" } }, "id": 5428, "indexExpression": { "argumentTypes": null, "id": 5427, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "33754:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": true, "nodeType": "IndexAccess", "src": "33746:15:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage", "typeString": "struct Staking.Locked storage ref" } }, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5430, "nodeType": "ExpressionStatement", "src": "33739:22:6" } ] } }, { "expression": { "argumentTypes": null, "id": 5443, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftHandSide": { "argumentTypes": null, "id": 5433, "name": "collected", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5326, "src": "33782:9:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "Assignment", "operator": "=", "rightHandSide": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_bool", "typeString": "bool" }, "id": 5442, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5437, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5434, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33794:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5435, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "33794:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5436, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33825:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "33794:32:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "nodeType": "BinaryOperation", "operator": "||", "rightExpression": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5441, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5438, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33830:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5439, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "33830:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5440, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "33868:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "33830:39:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "33794:75:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "src": "33782:87:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "id": 5444, "nodeType": "ExpressionStatement", "src": "33782:87:6" }, { "condition": { "argumentTypes": null, "id": 5445, "name": "collected", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5326, "src": "33883:9:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5487, "nodeType": "IfStatement", "src": "33879:421:6", "trueBody": { "id": 5486, "nodeType": "Block", "src": "33894:406:6", "statements": [ { "eventCall": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5447, "name": "sender", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5317, "src": "33932:6:6", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5448, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33940:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5449, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "principal", "nodeType": "MemberAccess", "referencedDeclaration": 2785, "src": "33940:27:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5450, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "33969:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } }, "id": 5451, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "compoundInterest", "nodeType": "MemberAccess", "referencedDeclaration": 2787, "src": "33969:34:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_address", "typeString": "address" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" }, { "typeIdentifier": "t_uint256", "typeString": "uint256" } ], "id": 5446, "name": "LiquidityUnlocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3694, "src": "33914:17:6", "typeDescriptions": { "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$_t_uint256_$returns$__$", "typeString": "function (address,uint256,uint256)" } }, "id": 5452, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "33914:90:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5453, "nodeType": "EmitStatement", "src": "33909:95:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5457, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34045:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5454, "name": "_accruedGlobalLocked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3776, "src": "34019:20:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5456, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "34019:25:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5458, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34019:44:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5459, "nodeType": "ExpressionStatement", "src": "34019:44:6" }, { "condition": { "argumentTypes": null, "commonType": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "id": 5463, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "leftExpression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5460, "name": "lockedAssets", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5337, "src": "34081:12:6", "typeDescriptions": { "typeIdentifier": "t_array$_t_struct$_LockedAsset_$3653_storage_$dyn_storage_ptr", "typeString": "struct Staking.LockedAsset storage ref[] storage pointer" } }, "id": 5461, "isConstant": false, "isLValue": false, "isPure": false, "lValueRequested": false, "memberName": "length", "nodeType": "MemberAccess", "referencedDeclaration": null, "src": "34081:19:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "nodeType": "BinaryOperation", "operator": "!=", "rightExpression": { "argumentTypes": null, "hexValue": "30", "id": 5462, "isConstant": false, "isLValue": false, "isPure": true, "kind": "number", "lValueRequested": false, "nodeType": "Literal", "src": "34104:1:6", "subdenomination": null, "typeDescriptions": { "typeIdentifier": "t_rational_0_by_1", "typeString": "int_const 0" }, "value": "0" }, "src": "34081:24:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "falseBody": null, "id": 5473, "nodeType": "IfStatement", "src": "34077:103:6", "trueBody": { "id": 5472, "nodeType": "Block", "src": "34107:73:6", "statements": [ { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5469, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34147:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "expression": { "argumentTypes": null, "id": 5464, "name": "locked", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5329, "src": "34125:6:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Locked_$3659_storage_ptr", "typeString": "struct Staking.Locked storage pointer" } }, "id": 5467, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "aggregate", "nodeType": "MemberAccess", "referencedDeclaration": 3655, "src": "34125:16:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5468, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iSub", "nodeType": "MemberAccess", "referencedDeclaration": 2938, "src": "34125:21:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5470, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34125:40:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5471, "nodeType": "ExpressionStatement", "src": "34125:40:6" } ] } }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5477, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34223:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5474, "name": "_accruedGlobalLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 3774, "src": "34194:23:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage", "typeString": "struct AssetLib.Asset storage ref" } }, "id": 5476, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "34194:28:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5478, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34194:47:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5479, "nodeType": "ExpressionStatement", "src": "34194:47:6" }, { "expression": { "argumentTypes": null, "arguments": [ { "argumentTypes": null, "id": 5483, "name": "unlockedLiquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5322, "src": "34271:17:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } } ], "expression": { "argumentTypes": [ { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset memory" } ], "expression": { "argumentTypes": null, "id": 5480, "name": "liquidity", "nodeType": "Identifier", "overloadedDeclarations": [], "referencedDeclaration": 5324, "src": "34256:9:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset storage pointer" } }, "id": 5482, "isConstant": false, "isLValue": true, "isPure": false, "lValueRequested": false, "memberName": "iAdd", "nodeType": "MemberAccess", "referencedDeclaration": 2878, "src": "34256:14:6", "typeDescriptions": { "typeIdentifier": "t_function_internal_nonpayable$_t_struct$_Asset_$2788_storage_ptr_$_t_struct$_Asset_$2788_memory_ptr_$returns$__$bound_to$_t_struct$_Asset_$2788_storage_ptr_$", "typeString": "function (struct AssetLib.Asset storage pointer,struct AssetLib.Asset memory)" } }, "id": 5484, "isConstant": false, "isLValue": false, "isPure": false, "kind": "functionCall", "lValueRequested": false, "names": [], "nodeType": "FunctionCall", "src": "34256:33:6", "tryCall": false, "typeDescriptions": { "typeIdentifier": "t_tuple$__$", "typeString": "tuple()" } }, "id": 5485, "nodeType": "ExpressionStatement", "src": "34256:33:6" } ] } } ] }, "documentation": null, "id": 5489, "implemented": true, "kind": "function", "modifiers": [], "name": "_collectLiquidity", "nodeType": "FunctionDefinition", "overrides": null, "parameters": { "id": 5320, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5317, "mutability": "mutable", "name": "sender", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31639:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" }, "typeName": { "id": 5316, "name": "address", "nodeType": "ElementaryTypeName", "src": "31639:7:6", "stateMutability": "nonpayable", "typeDescriptions": { "typeIdentifier": "t_address", "typeString": "address" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5319, "mutability": "mutable", "name": "at_block", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31655:16:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" }, "typeName": { "id": 5318, "name": "uint256", "nodeType": "ElementaryTypeName", "src": "31655:7:6", "typeDescriptions": { "typeIdentifier": "t_uint256", "typeString": "uint256" } }, "value": null, "visibility": "internal" } ], "src": "31638:34:6" }, "returnParameters": { "id": 5327, "nodeType": "ParameterList", "parameters": [ { "constant": false, "id": 5322, "mutability": "mutable", "name": "unlockedLiquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31706:39:6", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_memory_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 5321, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "31706:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5324, "mutability": "mutable", "name": "liquidity", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31747:32:6", "stateVariable": false, "storageLocation": "storage", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" }, "typeName": { "contractScope": null, "id": 5323, "name": "AssetLib.Asset", "nodeType": "UserDefinedTypeName", "referencedDeclaration": 2788, "src": "31747:14:6", "typeDescriptions": { "typeIdentifier": "t_struct$_Asset_$2788_storage_ptr", "typeString": "struct AssetLib.Asset" } }, "value": null, "visibility": "internal" }, { "constant": false, "id": 5326, "mutability": "mutable", "name": "collected", "nodeType": "VariableDeclaration", "overrides": null, "scope": 5489, "src": "31781:14:6", "stateVariable": false, "storageLocation": "default", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" }, "typeName": { "id": 5325, "name": "bool", "nodeType": "ElementaryTypeName", "src": "31781:4:6", "typeDescriptions": { "typeIdentifier": "t_bool", "typeString": "bool" } }, "value": null, "visibility": "internal" } ], "src": "31705:91:6" }, "scope": 5490, "src": "31612:2694:6", "stateMutability": "nonpayable", "virtual": false, "visibility": "internal" } ], "scope": 5491, "src": "1040:33269:6" } ], "src": "820:33490:6" }, "compiler": { "name": "solc", "version": "0.6.8+commit.0bbfe453.Emscripten.clang" }, "networks": {}, "schemaVersion": "3.1.0", "updatedAt": "2020-10-13T17:26:53.190Z", "devdoc": { "methods": { "addInterestRate(uint256,uint256)": { "details": "expiration period", "params": { "expirationBlock": "- block number beyond which is the carrier Tx considered expired, and so rejected. This is for protection of Tx sender to exactly define lifecycle length of the Tx, and so avoiding uncertainty of how long Tx sender needs to wait for Tx processing. Tx can be withheld", "rate": "- signed interest rate value in [10**18] units => real_rate [1] = rate [10**18] / 10**18" } }, "constructor": { "params": { "ERC20Address": "address of the ERC20 contract" } }, "deleteContract(address,uint256)": { "details": "owner only + only on or after `_earliestDelete` block", "params": { "payoutAddress": "address to transfer the balances to. Ensure that this is able to handle ERC20 tokens" } }, "getLockedAssetsForUser(address)": { "details": "Returns locked assets decomposed in to 3 separate arrays (principal, compound interest, liquid since block) NOTE(pb): This method might be quite expensive, depending on size of locked assets" }, "getRoleAdmin(bytes32)": { "details": "Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. * To change a role's admin, use {_setRoleAdmin}." }, "getRoleMember(bytes32,uint256)": { "details": "Returns one of the accounts that have `role`. `index` must be a value between 0 and {getRoleMemberCount}, non-inclusive. * Role bearers are not sorted in any particular way, and their ordering may change at any point. * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure you perform all queries on the same block. See the following https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] for more information." }, "getRoleMemberCount(bytes32)": { "details": "Returns the number of accounts that have `role`. Can be used together with {getRoleMember} to enumerate all bearers of a role." }, "grantRole(bytes32,address)": { "details": "Grants `role` to `account`. * If `account` had not been already granted `role`, emits a {RoleGranted} event. * Requirements: * - the caller must have ``role``'s admin role." }, "hasRole(bytes32,address)": { "details": "Returns `true` if `account` has been granted `role`." }, "pauseSince(uint256,uint256)": { "details": "Delegate only", "params": { "blockNumber": "block number since which non-admin interaction will be paused (for all _getBlockNumber() >= blockNumber)" } }, "renounceRole(bytes32,address)": { "details": "Revokes `role` from the calling account. * Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). * If the calling account had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must be `account`." }, "revokeRole(bytes32,address)": { "details": "Revokes `role` from `account`. * If `account` had been granted `role`, emits a {RoleRevoked} event. * Requirements: * - the caller must have ``role``'s admin role." }, "topUpRewardsPool(uint256,uint256)": { "details": "Even though this is considered as administrative action (is not affected by by contract paused state, it can be executed by anyone who wishes to top-up the rewards pool (funds are sent in to contract, *not* the other way around). The Rewards Pool is exclusively dedicated to cover withdrawals of user' compound interest, which is effectively the reward." }, "unbindStake(uint256,uint256)": { "details": "public access", "params": { "amount": "- value to un-bind from the stake If `amount=0` then the **WHOLE** stake (including compound interest) will be unbound." } }, "updateLockPeriod(uint64,uint256)": { "details": "Delegate only", "params": { "numOfBlocks": "length of the lock period" } }, "withdraw(uint256,uint256)": { "details": "public access", "params": { "amount": "- value to withdraw" } }, "withdrawCompoundInterest(uint256)": { "details": "public access" }, "withdrawExcessTokens(address,uint256)": { "details": "Withdraw \"excess\" tokens, which were sent to contract directly via direct ERC20.transfer(...), without interacting with API of this (Staking) contract, what could be done only by mistake. Thus this method is meant to be used primarily for rescue purposes, enabling withdrawal of such \"excess\" tokens out of contract.", "params": { "targetAddress": ": address to send tokens to", "txExpirationBlock": ": block number until which is the transaction valid (inclusive). When transaction is processed after this block, it fails." } }, "withdrawFromRewardsPool(uint256,address,uint256)": { "details": "Withdraw tokens from rewards pool.", "params": { "amount": ": amount to withdraw. If `amount == 0` then whole amount in rewards pool will be withdrawn.", "targetAddress": ": address to send tokens to" } }, "withdrawPrincipal(uint256)": { "details": "public access" }, "withdrawWholeLiquidity(uint256)": { "details": "public access" } } }, "userdoc": { "methods": { "addInterestRate(uint256,uint256)": { "notice": "Add new interest rate in to the ordered container of previously added interest rates" }, "deleteContract(address,uint256)": { "notice": "Delete the contract, transfers the remaining token and ether balance to the specified payoutAddress" }, "pauseSince(uint256,uint256)": { "notice": "Pauses all NON-administrative interaction with the contract since the specidfed block number " }, "unbindStake(uint256,uint256)": { "notice": "Unbinds amount from the stake of sender of the transaction, and *LOCKS* it for number of blocks defined by value of the `_lockPeriodInBlocks` state of this contract at the point of this call. The locked amount can *NOT* be withdrawn from the contract *BEFORE* the lock period ends. * Unbinding (=calling this method) also means, that compound interest will be calculated for period since la." }, "updateLockPeriod(uint64,uint256)": { "notice": "Updates Lock Period value" }, "withdraw(uint256,uint256)": { "notice": "Withdraws amount from sender' available liquidity pool back to sender address, preferring withdrawal from compound interest dimension of liquidity." }, "withdrawCompoundInterest(uint256)": { "notice": "Withdraws *WHOLE* compound interest amount available to sender." }, "withdrawPrincipal(uint256)": { "notice": "Withdraws *WHOLE* compound interest amount available to sender." }, "withdrawWholeLiquidity(uint256)": { "notice": "Withdraws whole liquidity available to sender back to sender' address," } } } } ================================================ FILE: packages/fetchai/contracts/staking_erc20/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the erc1155 contract definition.""" import logging from aea_ledger_ethereum import EthereumApi from aea.common import Address, JSONLike from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi _default_logger = logging.getLogger( "aea.packages.fetchai.contracts.staking_erc20.contract" ) PUBLIC_ID = PublicId.from_str("fetchai/staking_erc20:0.10.3") class StakingERC20(Contract): """The ERC1155 contract class which acts as a bridge between AEA framework and ERC1155 ABI.""" contract_id = PUBLIC_ID @classmethod def get_stake( cls, ledger_api: LedgerApi, contract_address: Address, address: Address, ) -> JSONLike: """ Get the balance for a specific token id. :param ledger_api: the ledger API :param contract_address: the address of the contract :param address: the address :return: the balance in a dictionary - {"balance": {token_id: int, balance: int}} """ if ledger_api.identifier == EthereumApi.identifier: instance = cls.get_instance(ledger_api, contract_address) result = instance.functions.getStakeForUser(address).call() return {"stake": result[0]} raise NotImplementedError ================================================ FILE: packages/fetchai/contracts/staking_erc20/contract.yaml ================================================ name: staking_erc20 author: fetchai version: 0.10.3 type: contract description: The staking_erc20 contract contains the main staking contract of Fetch.ai for Ethereum mainnet. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmXcVNZrAEX5Ja2fPKBYDVV77s5JKXtyGP19pgZfJNerrw __init__.py: QmdKarQHWsEyedE8E3X6my3zLZkdTGGrbmyJaidti3Sqyx build/Migrations.json: QmfFYYWoq1L1Ni6YPBWWoRPvCZKBLZ7qzN3UDX537mCeuE build/staking_erc20.json: QmVf2BuDDJPoCHDwEQKHdgfph4CbTFpqJJvcDLaM9tyoQq contract.py: QmWgc5hyfnxZ5R5YTo6HZpTsqBuL3WuhJzQdkjqgFnZNyh fingerprint_ignore_patterns: [] class_name: StakingERC20 contract_interface_paths: ethereum: build/staking_erc20.json dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 ================================================ FILE: packages/fetchai/protocols/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the protocol packages authored by Fetch.ai.""" ================================================ FILE: packages/fetchai/protocols/acn/README.md ================================================ # ACN Protocol ## Description This is a protocol for ACN (agent communication network) envelope delivery. ## Specification ```yaml --- name: acn author: fetchai version: 1.1.7 description: The protocol used for envelope delivery on the ACN. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: aea/acn:1.0.0 speech_acts: register: record: ct:AgentRecord lookup_request: agent_address: pt:str lookup_response: record: ct:AgentRecord aea_envelope: envelope: pt:bytes record: ct:AgentRecord status: body: ct:StatusBody ... --- ct:AgentRecord: string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; ct:StatusBody: | enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; ... --- initiation: [register, lookup_request, aea_envelope] reply: register: [status] lookup_request: [lookup_response, status] aea_envelope: [status] status: [] lookup_response: [] termination: [status, lookup_response] roles: {node} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/acn/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the acn protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.acn.message import AcnMessage from packages.fetchai.protocols.acn.serialization import AcnSerializer AcnMessage.serializer = AcnSerializer ================================================ FILE: packages/fetchai/protocols/acn/acn.proto ================================================ syntax = "proto3"; package aea.aea.acn.v1_0_0; message AcnMessage{ // Custom Types message AgentRecord{ string service_id = 1; string ledger_id = 2; string address = 3; string public_key = 4; string peer_public_key = 5; string signature = 6; string not_before = 7; string not_after = 8; } message StatusBody{ enum StatusCodeEnum { // common (0x) SUCCESS = 0; ERROR_UNSUPPORTED_VERSION = 1; ERROR_UNEXPECTED_PAYLOAD = 2; ERROR_GENERIC = 3; ERROR_DECODE = 4; // register (1x) ERROR_WRONG_AGENT_ADDRESS = 10; ERROR_WRONG_PUBLIC_KEY = 11; ERROR_INVALID_PROOF = 12; ERROR_UNSUPPORTED_LEDGER = 13; // lookup & delivery (2x) ERROR_UNKNOWN_AGENT_ADDRESS = 20; ERROR_AGENT_NOT_READY = 21; } StatusCodeEnum code = 1; repeated string msgs = 2; } // Performatives and contents message Register_Performative{ AgentRecord record = 1; } message Lookup_Request_Performative{ string agent_address = 1; } message Lookup_Response_Performative{ AgentRecord record = 1; } message Aea_Envelope_Performative{ bytes envelope = 1; AgentRecord record = 2; } message Status_Performative{ StatusBody body = 1; } oneof performative{ Aea_Envelope_Performative aea_envelope = 5; Lookup_Request_Performative lookup_request = 6; Lookup_Response_Performative lookup_response = 7; Register_Performative register = 8; Status_Performative status = 9; } } ================================================ FILE: packages/fetchai/protocols/acn/acn_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: acn.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\tacn.proto\x12\x12\x61\x65\x61.aea.acn.v1_0_0"\x92\x0b\n\nAcnMessage\x12P\n\x0c\x61\x65\x61_envelope\x18\x05 \x01(\x0b\x32\x38.aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_PerformativeH\x00\x12T\n\x0elookup_request\x18\x06 \x01(\x0b\x32:.aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_PerformativeH\x00\x12V\n\x0flookup_response\x18\x07 \x01(\x0b\x32;.aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_PerformativeH\x00\x12H\n\x08register\x18\x08 \x01(\x0b\x32\x34.aea.aea.acn.v1_0_0.AcnMessage.Register_PerformativeH\x00\x12\x44\n\x06status\x18\t \x01(\x0b\x32\x32.aea.aea.acn.v1_0_0.AcnMessage.Status_PerformativeH\x00\x1a\xac\x01\n\x0b\x41gentRecord\x12\x12\n\nservice_id\x18\x01 \x01(\t\x12\x11\n\tledger_id\x18\x02 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\t\x12\x12\n\npublic_key\x18\x04 \x01(\t\x12\x17\n\x0fpeer_public_key\x18\x05 \x01(\t\x12\x11\n\tsignature\x18\x06 \x01(\t\x12\x12\n\nnot_before\x18\x07 \x01(\t\x12\x11\n\tnot_after\x18\x08 \x01(\t\x1a\x92\x03\n\nStatusBody\x12\x46\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x38.aea.aea.acn.v1_0_0.AcnMessage.StatusBody.StatusCodeEnum\x12\x0c\n\x04msgs\x18\x02 \x03(\t"\xad\x02\n\x0eStatusCodeEnum\x12\x0b\n\x07SUCCESS\x10\x00\x12\x1d\n\x19\x45RROR_UNSUPPORTED_VERSION\x10\x01\x12\x1c\n\x18\x45RROR_UNEXPECTED_PAYLOAD\x10\x02\x12\x11\n\rERROR_GENERIC\x10\x03\x12\x10\n\x0c\x45RROR_DECODE\x10\x04\x12\x1d\n\x19\x45RROR_WRONG_AGENT_ADDRESS\x10\n\x12\x1a\n\x16\x45RROR_WRONG_PUBLIC_KEY\x10\x0b\x12\x17\n\x13\x45RROR_INVALID_PROOF\x10\x0c\x12\x1c\n\x18\x45RROR_UNSUPPORTED_LEDGER\x10\r\x12\x1f\n\x1b\x45RROR_UNKNOWN_AGENT_ADDRESS\x10\x14\x12\x19\n\x15\x45RROR_AGENT_NOT_READY\x10\x15\x1aS\n\x15Register_Performative\x12:\n\x06record\x18\x01 \x01(\x0b\x32*.aea.aea.acn.v1_0_0.AcnMessage.AgentRecord\x1a\x34\n\x1bLookup_Request_Performative\x12\x15\n\ragent_address\x18\x01 \x01(\t\x1aZ\n\x1cLookup_Response_Performative\x12:\n\x06record\x18\x01 \x01(\x0b\x32*.aea.aea.acn.v1_0_0.AcnMessage.AgentRecord\x1ai\n\x19\x41\x65\x61_Envelope_Performative\x12\x10\n\x08\x65nvelope\x18\x01 \x01(\x0c\x12:\n\x06record\x18\x02 \x01(\x0b\x32*.aea.aea.acn.v1_0_0.AcnMessage.AgentRecord\x1aN\n\x13Status_Performative\x12\x37\n\x04\x62ody\x18\x01 \x01(\x0b\x32).aea.aea.acn.v1_0_0.AcnMessage.StatusBodyB\x0e\n\x0cperformativeb\x06proto3' ) _ACNMESSAGE = DESCRIPTOR.message_types_by_name["AcnMessage"] _ACNMESSAGE_AGENTRECORD = _ACNMESSAGE.nested_types_by_name["AgentRecord"] _ACNMESSAGE_STATUSBODY = _ACNMESSAGE.nested_types_by_name["StatusBody"] _ACNMESSAGE_REGISTER_PERFORMATIVE = _ACNMESSAGE.nested_types_by_name[ "Register_Performative" ] _ACNMESSAGE_LOOKUP_REQUEST_PERFORMATIVE = _ACNMESSAGE.nested_types_by_name[ "Lookup_Request_Performative" ] _ACNMESSAGE_LOOKUP_RESPONSE_PERFORMATIVE = _ACNMESSAGE.nested_types_by_name[ "Lookup_Response_Performative" ] _ACNMESSAGE_AEA_ENVELOPE_PERFORMATIVE = _ACNMESSAGE.nested_types_by_name[ "Aea_Envelope_Performative" ] _ACNMESSAGE_STATUS_PERFORMATIVE = _ACNMESSAGE.nested_types_by_name[ "Status_Performative" ] _ACNMESSAGE_STATUSBODY_STATUSCODEENUM = _ACNMESSAGE_STATUSBODY.enum_types_by_name[ "StatusCodeEnum" ] AcnMessage = _reflection.GeneratedProtocolMessageType( "AcnMessage", (_message.Message,), { "AgentRecord": _reflection.GeneratedProtocolMessageType( "AgentRecord", (_message.Message,), { "DESCRIPTOR": _ACNMESSAGE_AGENTRECORD, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage.AgentRecord) }, ), "StatusBody": _reflection.GeneratedProtocolMessageType( "StatusBody", (_message.Message,), { "DESCRIPTOR": _ACNMESSAGE_STATUSBODY, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage.StatusBody) }, ), "Register_Performative": _reflection.GeneratedProtocolMessageType( "Register_Performative", (_message.Message,), { "DESCRIPTOR": _ACNMESSAGE_REGISTER_PERFORMATIVE, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage.Register_Performative) }, ), "Lookup_Request_Performative": _reflection.GeneratedProtocolMessageType( "Lookup_Request_Performative", (_message.Message,), { "DESCRIPTOR": _ACNMESSAGE_LOOKUP_REQUEST_PERFORMATIVE, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage.Lookup_Request_Performative) }, ), "Lookup_Response_Performative": _reflection.GeneratedProtocolMessageType( "Lookup_Response_Performative", (_message.Message,), { "DESCRIPTOR": _ACNMESSAGE_LOOKUP_RESPONSE_PERFORMATIVE, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage.Lookup_Response_Performative) }, ), "Aea_Envelope_Performative": _reflection.GeneratedProtocolMessageType( "Aea_Envelope_Performative", (_message.Message,), { "DESCRIPTOR": _ACNMESSAGE_AEA_ENVELOPE_PERFORMATIVE, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage.Aea_Envelope_Performative) }, ), "Status_Performative": _reflection.GeneratedProtocolMessageType( "Status_Performative", (_message.Message,), { "DESCRIPTOR": _ACNMESSAGE_STATUS_PERFORMATIVE, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage.Status_Performative) }, ), "DESCRIPTOR": _ACNMESSAGE, "__module__": "acn_pb2" # @@protoc_insertion_point(class_scope:aea.aea.acn.v1_0_0.AcnMessage) }, ) _sym_db.RegisterMessage(AcnMessage) _sym_db.RegisterMessage(AcnMessage.AgentRecord) _sym_db.RegisterMessage(AcnMessage.StatusBody) _sym_db.RegisterMessage(AcnMessage.Register_Performative) _sym_db.RegisterMessage(AcnMessage.Lookup_Request_Performative) _sym_db.RegisterMessage(AcnMessage.Lookup_Response_Performative) _sym_db.RegisterMessage(AcnMessage.Aea_Envelope_Performative) _sym_db.RegisterMessage(AcnMessage.Status_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _ACNMESSAGE._serialized_start = 34 _ACNMESSAGE._serialized_end = 1460 _ACNMESSAGE_AGENTRECORD._serialized_start = 449 _ACNMESSAGE_AGENTRECORD._serialized_end = 621 _ACNMESSAGE_STATUSBODY._serialized_start = 624 _ACNMESSAGE_STATUSBODY._serialized_end = 1026 _ACNMESSAGE_STATUSBODY_STATUSCODEENUM._serialized_start = 725 _ACNMESSAGE_STATUSBODY_STATUSCODEENUM._serialized_end = 1026 _ACNMESSAGE_REGISTER_PERFORMATIVE._serialized_start = 1028 _ACNMESSAGE_REGISTER_PERFORMATIVE._serialized_end = 1111 _ACNMESSAGE_LOOKUP_REQUEST_PERFORMATIVE._serialized_start = 1113 _ACNMESSAGE_LOOKUP_REQUEST_PERFORMATIVE._serialized_end = 1165 _ACNMESSAGE_LOOKUP_RESPONSE_PERFORMATIVE._serialized_start = 1167 _ACNMESSAGE_LOOKUP_RESPONSE_PERFORMATIVE._serialized_end = 1257 _ACNMESSAGE_AEA_ENVELOPE_PERFORMATIVE._serialized_start = 1259 _ACNMESSAGE_AEA_ENVELOPE_PERFORMATIVE._serialized_end = 1364 _ACNMESSAGE_STATUS_PERFORMATIVE._serialized_start = 1366 _ACNMESSAGE_STATUS_PERFORMATIVE._serialized_end = 1444 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/acn/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from enum import Enum from typing import Any, List from aea.helpers.base import SimpleId, SimpleIdOrStr class AgentRecord: """ This class represents an instance of AgentRecord. Eventually needs to be unified with `aea.helpers.acn.agent_record`. """ __slots__ = ( "_address", "_public_key", "_peer_public_key", "_signature", "_service_id", "_ledger_id", "_message_format", "_message", ) def __init__( self, address: str, public_key: str, peer_public_key: str, signature: str, service_id: SimpleIdOrStr, ledger_id: SimpleIdOrStr, ) -> None: """Initialise an instance of AgentRecord.""" self._address = address self._public_key = public_key self._peer_public_key = peer_public_key self._signature = signature self._service_id = str(SimpleId(service_id)) self._ledger_id = str(SimpleId(ledger_id)) @property def address(self) -> str: """Get agent address""" return self._address @property def public_key(self) -> str: """Get agent public key""" return self._public_key @property def peer_public_key(self) -> str: """Get agent representative's public key""" return self._peer_public_key @property def signature(self) -> str: """Get record signature""" return self._signature @property def service_id(self) -> SimpleIdOrStr: """Get the identifier.""" return self._service_id @property def ledger_id(self) -> SimpleIdOrStr: """Get ledger id.""" return self._ledger_id @staticmethod def encode( agent_record_protobuf_object: Any, agent_record_object: "AgentRecord" ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the agent_record_protobuf_object argument is matched with the instance of this class in the 'agent_record_object' argument. :param agent_record_protobuf_object: the protocol buffer object whose type corresponds with this class. :param agent_record_object: an instance of this class to be encoded in the protocol buffer object. """ agent_record_protobuf_object.address = agent_record_object.address agent_record_protobuf_object.public_key = agent_record_object.public_key agent_record_protobuf_object.peer_public_key = ( agent_record_object.peer_public_key ) agent_record_protobuf_object.signature = agent_record_object.signature agent_record_protobuf_object.service_id = agent_record_object.service_id agent_record_protobuf_object.ledger_id = agent_record_object.ledger_id @classmethod def decode(cls, agent_record_protobuf_object: Any) -> "AgentRecord": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'agent_record_protobuf_object' argument. :param agent_record_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'agent_record_protobuf_object' argument. """ record = cls( address=agent_record_protobuf_object.address, public_key=agent_record_protobuf_object.public_key, peer_public_key=agent_record_protobuf_object.peer_public_key, signature=agent_record_protobuf_object.signature, service_id=agent_record_protobuf_object.service_id, ledger_id=agent_record_protobuf_object.ledger_id, ) return record def __eq__(self, other: Any) -> bool: """Compare to objects of this class.""" return ( isinstance(other, AgentRecord) and self.address == other.address and self.public_key == other.public_key ) class StatusBody: """This class represents an instance of StatusBody.""" __slots__ = ( "_status_code", "_msgs", ) class StatusCode(Enum): """Status code enum.""" SUCCESS = 0 ERROR_UNSUPPORTED_VERSION = 1 ERROR_UNEXPECTED_PAYLOAD = 2 ERROR_GENERIC = 3 ERROR_DECODE = 4 ERROR_WRONG_AGENT_ADDRESS = 10 ERROR_WRONG_PUBLIC_KEY = 11 ERROR_INVALID_PROOF = 12 ERROR_UNSUPPORTED_LEDGER = 13 ERROR_UNKNOWN_AGENT_ADDRESS = 20 ERROR_AGENT_NOT_READY = 21 def __int__(self) -> int: """Get string representation.""" return self.value def __init__(self, status_code: StatusCode, msgs: List[str]) -> None: """Initialise an instance of StatusBody.""" self._status_code = status_code self._msgs = msgs @property def status_code(self) -> "StatusCode": """Get the status code.""" return self._status_code @property def msgs(self) -> List[str]: """Get the list of messages.""" return self._msgs @staticmethod def encode( status_body_protobuf_object: Any, status_body_object: "StatusBody" ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the status_body_protobuf_object argument is matched with the instance of this class in the 'status_body_object' argument. :param status_body_protobuf_object: the protocol buffer object whose type corresponds with this class. :param status_body_object: an instance of this class to be encoded in the protocol buffer object. """ status_body_protobuf_object.code = int(status_body_object.status_code) status_body_protobuf_object.msgs.extend(status_body_object.msgs) @classmethod def decode(cls, status_body_protobuf_object: Any) -> "StatusBody": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'status_body_protobuf_object' argument. :param status_body_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'status_body_protobuf_object' argument. """ status_body = cls( status_code=cls.StatusCode(status_body_protobuf_object.code), msgs=status_body_protobuf_object.msgs, ) return status_body def __eq__(self, other: Any) -> bool: """Compare to objects of this class.""" return ( isinstance(other, StatusBody) and self.status_code == other.status_code and self.msgs == other.msgs ) ================================================ FILE: packages/fetchai/protocols/acn/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for acn dialogue management. - AcnDialogue: The dialogue class maintains state of a dialogue and manages it. - AcnDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.acn.message import AcnMessage class AcnDialogue(Dialogue): """The acn dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { AcnMessage.Performative.REGISTER, AcnMessage.Performative.LOOKUP_REQUEST, AcnMessage.Performative.AEA_ENVELOPE, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {AcnMessage.Performative.STATUS, AcnMessage.Performative.LOOKUP_RESPONSE} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { AcnMessage.Performative.AEA_ENVELOPE: frozenset( {AcnMessage.Performative.STATUS} ), AcnMessage.Performative.LOOKUP_REQUEST: frozenset( {AcnMessage.Performative.LOOKUP_RESPONSE, AcnMessage.Performative.STATUS} ), AcnMessage.Performative.LOOKUP_RESPONSE: frozenset(), AcnMessage.Performative.REGISTER: frozenset({AcnMessage.Performative.STATUS}), AcnMessage.Performative.STATUS: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a acn dialogue.""" NODE = "node" class EndState(Dialogue.EndState): """This class defines the end states of a acn dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[AcnMessage] = AcnMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class AcnDialogues(Dialogues, ABC): """This class keeps track of all acn dialogues.""" END_STATES = frozenset( {AcnDialogue.EndState.SUCCESSFUL, AcnDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[AcnDialogue] = AcnDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=AcnMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/acn/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains acn's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.acn.custom_types import AgentRecord as CustomAgentRecord from packages.fetchai.protocols.acn.custom_types import StatusBody as CustomStatusBody _default_logger = logging.getLogger("aea.packages.fetchai.protocols.acn.message") DEFAULT_BODY_SIZE = 4 class AcnMessage(Message): """The protocol used for envelope delivery on the ACN.""" protocol_id = PublicId.from_str("fetchai/acn:1.1.7") protocol_specification_id = PublicId.from_str("aea/acn:1.0.0") AgentRecord = CustomAgentRecord StatusBody = CustomStatusBody class Performative(Message.Performative): """Performatives for the acn protocol.""" AEA_ENVELOPE = "aea_envelope" LOOKUP_REQUEST = "lookup_request" LOOKUP_RESPONSE = "lookup_response" REGISTER = "register" STATUS = "status" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "aea_envelope", "lookup_request", "lookup_response", "register", "status", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "agent_address", "body", "dialogue_reference", "envelope", "message_id", "performative", "record", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of AcnMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=AcnMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(AcnMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def agent_address(self) -> str: """Get the 'agent_address' content from the message.""" enforce(self.is_set("agent_address"), "'agent_address' content is not set.") return cast(str, self.get("agent_address")) @property def body(self) -> CustomStatusBody: """Get the 'body' content from the message.""" enforce(self.is_set("body"), "'body' content is not set.") return cast(CustomStatusBody, self.get("body")) @property def envelope(self) -> bytes: """Get the 'envelope' content from the message.""" enforce(self.is_set("envelope"), "'envelope' content is not set.") return cast(bytes, self.get("envelope")) @property def record(self) -> CustomAgentRecord: """Get the 'record' content from the message.""" enforce(self.is_set("record"), "'record' content is not set.") return cast(CustomAgentRecord, self.get("record")) def _is_consistent(self) -> bool: """Check that the message follows the acn protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, AcnMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == AcnMessage.Performative.REGISTER: expected_nb_of_contents = 1 enforce( isinstance(self.record, CustomAgentRecord), "Invalid type for content 'record'. Expected 'AgentRecord'. Found '{}'.".format( type(self.record) ), ) elif self.performative == AcnMessage.Performative.LOOKUP_REQUEST: expected_nb_of_contents = 1 enforce( isinstance(self.agent_address, str), "Invalid type for content 'agent_address'. Expected 'str'. Found '{}'.".format( type(self.agent_address) ), ) elif self.performative == AcnMessage.Performative.LOOKUP_RESPONSE: expected_nb_of_contents = 1 enforce( isinstance(self.record, CustomAgentRecord), "Invalid type for content 'record'. Expected 'AgentRecord'. Found '{}'.".format( type(self.record) ), ) elif self.performative == AcnMessage.Performative.AEA_ENVELOPE: expected_nb_of_contents = 2 enforce( isinstance(self.envelope, bytes), "Invalid type for content 'envelope'. Expected 'bytes'. Found '{}'.".format( type(self.envelope) ), ) enforce( isinstance(self.record, CustomAgentRecord), "Invalid type for content 'record'. Expected 'AgentRecord'. Found '{}'.".format( type(self.record) ), ) elif self.performative == AcnMessage.Performative.STATUS: expected_nb_of_contents = 1 enforce( isinstance(self.body, CustomStatusBody), "Invalid type for content 'body'. Expected 'StatusBody'. Found '{}'.".format( type(self.body) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/acn/protocol.yaml ================================================ name: acn author: fetchai version: 1.1.7 protocol_specification_id: aea/acn:1.0.0 type: protocol description: The protocol used for envelope delivery on the ACN. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmfMfnMxXNv96uBwMk9EcFE9vKTLwNn7afLTdMNKUBLgX7 __init__.py: QmXYL7CpbddU18HUG1Jf334np9yiWjYDsQYg7qBQjBrL9o acn.proto: QmVWvXETUNe7QZTvBgzwpofNP3suFthwyxbTVUqSx8mdnE acn_pb2.py: Qmf3HYmJtGwugpCuWSmJEDVHwZcnCF6BWmeEouUSpBRs45 custom_types.py: QmQNMAGx2JGSM3CKXCos2oWpzSoCa7vsrmQc57j5QY8NFz dialogues.py: QmUbvA3jgzdGMseRtPyDoMApVrVZ5h4pspt5UY77EM9kR9 message.py: QmdrjeM3ShmDKLsB46TbDzigsW7bPZCdLKEdtxUiZenkn9 serialization.py: QmQ8QfHCvKNg2D3Fcb7u4tJmRw4gce16YUsbwvGfBMEk38 fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/acn/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for acn protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.acn import acn_pb2 from packages.fetchai.protocols.acn.custom_types import AgentRecord, StatusBody from packages.fetchai.protocols.acn.message import AcnMessage class AcnSerializer(Serializer): """Serialization for the 'acn' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Acn' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(AcnMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() acn_msg = acn_pb2.AcnMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == AcnMessage.Performative.REGISTER: performative = acn_pb2.AcnMessage.Register_Performative() # type: ignore record = msg.record AgentRecord.encode(performative.record, record) acn_msg.register.CopyFrom(performative) elif performative_id == AcnMessage.Performative.LOOKUP_REQUEST: performative = acn_pb2.AcnMessage.Lookup_Request_Performative() # type: ignore agent_address = msg.agent_address performative.agent_address = agent_address acn_msg.lookup_request.CopyFrom(performative) elif performative_id == AcnMessage.Performative.LOOKUP_RESPONSE: performative = acn_pb2.AcnMessage.Lookup_Response_Performative() # type: ignore record = msg.record AgentRecord.encode(performative.record, record) acn_msg.lookup_response.CopyFrom(performative) elif performative_id == AcnMessage.Performative.AEA_ENVELOPE: performative = acn_pb2.AcnMessage.Aea_Envelope_Performative() # type: ignore envelope = msg.envelope performative.envelope = envelope record = msg.record AgentRecord.encode(performative.record, record) acn_msg.aea_envelope.CopyFrom(performative) elif performative_id == AcnMessage.Performative.STATUS: performative = acn_pb2.AcnMessage.Status_Performative() # type: ignore body = msg.body StatusBody.encode(performative.body, body) acn_msg.status.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = acn_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Acn' message. :param obj: the bytes object. :return: the 'Acn' message. """ message_pb = ProtobufMessage() acn_pb = acn_pb2.AcnMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target acn_pb.ParseFromString(message_pb.dialogue_message.content) performative = acn_pb.WhichOneof("performative") performative_id = AcnMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == AcnMessage.Performative.REGISTER: pb2_record = acn_pb.register.record record = AgentRecord.decode(pb2_record) performative_content["record"] = record elif performative_id == AcnMessage.Performative.LOOKUP_REQUEST: agent_address = acn_pb.lookup_request.agent_address performative_content["agent_address"] = agent_address elif performative_id == AcnMessage.Performative.LOOKUP_RESPONSE: pb2_record = acn_pb.lookup_response.record record = AgentRecord.decode(pb2_record) performative_content["record"] = record elif performative_id == AcnMessage.Performative.AEA_ENVELOPE: envelope = acn_pb.aea_envelope.envelope performative_content["envelope"] = envelope pb2_record = acn_pb.aea_envelope.record record = AgentRecord.decode(pb2_record) performative_content["record"] = record elif performative_id == AcnMessage.Performative.STATUS: pb2_body = acn_pb.status.body body = StatusBody.decode(pb2_body) performative_content["body"] = body else: raise ValueError("Performative not valid: {}.".format(performative_id)) return AcnMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/aggregation/README.md ================================================ # Aggregation Protocol ## Description This is an aggregation protocol for aggregating observations. ## Specification ```yaml --- name: aggregation author: fetchai version: 0.2.7 description: A protocol for agents to aggregate individual observations license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/aggregation:0.2.7 speech_acts: observation: value: pt:int time: pt:str source: pt:str signature: pt:str aggregation: value: pt:int time: pt:str contributors: pt:list[pt:str] signature: pt:str ... --- initiation: [observation, aggregation] reply: observation: [] aggregation: [] termination: [observation, aggregation] roles: {agent} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/aggregation/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the aggregation protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.aggregation.message import AggregationMessage from packages.fetchai.protocols.aggregation.serialization import AggregationSerializer AggregationMessage.serializer = AggregationSerializer ================================================ FILE: packages/fetchai/protocols/aggregation/aggregation.proto ================================================ syntax = "proto3"; package aea.fetchai.aggregation.v0_2_7; message AggregationMessage{ // Performatives and contents message Observation_Performative{ int64 value = 1; string time = 2; string source = 3; string signature = 4; } message Aggregation_Performative{ int64 value = 1; string time = 2; repeated string contributors = 3; string signature = 4; } oneof performative{ Aggregation_Performative aggregation = 5; Observation_Performative observation = 6; } } ================================================ FILE: packages/fetchai/protocols/aggregation/aggregation_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: aggregation.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x11\x61ggregation.proto\x12\x1e\x61\x65\x61.fetchai.aggregation.v0_2_7"\xaa\x03\n\x12\x41ggregationMessage\x12\x62\n\x0b\x61ggregation\x18\x05 \x01(\x0b\x32K.aea.fetchai.aggregation.v0_2_7.AggregationMessage.Aggregation_PerformativeH\x00\x12\x62\n\x0bobservation\x18\x06 \x01(\x0b\x32K.aea.fetchai.aggregation.v0_2_7.AggregationMessage.Observation_PerformativeH\x00\x1aZ\n\x18Observation_Performative\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x0c\n\x04time\x18\x02 \x01(\t\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x11\n\tsignature\x18\x04 \x01(\t\x1a`\n\x18\x41ggregation_Performative\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x0c\n\x04time\x18\x02 \x01(\t\x12\x14\n\x0c\x63ontributors\x18\x03 \x03(\t\x12\x11\n\tsignature\x18\x04 \x01(\tB\x0e\n\x0cperformativeb\x06proto3' ) _AGGREGATIONMESSAGE = DESCRIPTOR.message_types_by_name["AggregationMessage"] _AGGREGATIONMESSAGE_OBSERVATION_PERFORMATIVE = _AGGREGATIONMESSAGE.nested_types_by_name[ "Observation_Performative" ] _AGGREGATIONMESSAGE_AGGREGATION_PERFORMATIVE = _AGGREGATIONMESSAGE.nested_types_by_name[ "Aggregation_Performative" ] AggregationMessage = _reflection.GeneratedProtocolMessageType( "AggregationMessage", (_message.Message,), { "Observation_Performative": _reflection.GeneratedProtocolMessageType( "Observation_Performative", (_message.Message,), { "DESCRIPTOR": _AGGREGATIONMESSAGE_OBSERVATION_PERFORMATIVE, "__module__": "aggregation_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.aggregation.v0_2_7.AggregationMessage.Observation_Performative) }, ), "Aggregation_Performative": _reflection.GeneratedProtocolMessageType( "Aggregation_Performative", (_message.Message,), { "DESCRIPTOR": _AGGREGATIONMESSAGE_AGGREGATION_PERFORMATIVE, "__module__": "aggregation_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.aggregation.v0_2_7.AggregationMessage.Aggregation_Performative) }, ), "DESCRIPTOR": _AGGREGATIONMESSAGE, "__module__": "aggregation_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.aggregation.v0_2_7.AggregationMessage) }, ) _sym_db.RegisterMessage(AggregationMessage) _sym_db.RegisterMessage(AggregationMessage.Observation_Performative) _sym_db.RegisterMessage(AggregationMessage.Aggregation_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _AGGREGATIONMESSAGE._serialized_start = 54 _AGGREGATIONMESSAGE._serialized_end = 480 _AGGREGATIONMESSAGE_OBSERVATION_PERFORMATIVE._serialized_start = 276 _AGGREGATIONMESSAGE_OBSERVATION_PERFORMATIVE._serialized_end = 366 _AGGREGATIONMESSAGE_AGGREGATION_PERFORMATIVE._serialized_start = 368 _AGGREGATIONMESSAGE_AGGREGATION_PERFORMATIVE._serialized_end = 464 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/aggregation/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for aggregation dialogue management. - AggregationDialogue: The dialogue class maintains state of a dialogue and manages it. - AggregationDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.aggregation.message import AggregationMessage class AggregationDialogue(Dialogue): """The aggregation dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { AggregationMessage.Performative.OBSERVATION, AggregationMessage.Performative.AGGREGATION, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { AggregationMessage.Performative.OBSERVATION, AggregationMessage.Performative.AGGREGATION, } ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { AggregationMessage.Performative.AGGREGATION: frozenset(), AggregationMessage.Performative.OBSERVATION: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a aggregation dialogue.""" AGENT = "agent" class EndState(Dialogue.EndState): """This class defines the end states of a aggregation dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[AggregationMessage] = AggregationMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class AggregationDialogues(Dialogues, ABC): """This class keeps track of all aggregation dialogues.""" END_STATES = frozenset( {AggregationDialogue.EndState.SUCCESSFUL, AggregationDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[AggregationDialogue] = AggregationDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=AggregationMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/aggregation/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains aggregation's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message _default_logger = logging.getLogger( "aea.packages.fetchai.protocols.aggregation.message" ) DEFAULT_BODY_SIZE = 4 class AggregationMessage(Message): """A protocol for agents to aggregate individual observations""" protocol_id = PublicId.from_str("fetchai/aggregation:0.2.7") protocol_specification_id = PublicId.from_str("fetchai/aggregation:0.2.7") class Performative(Message.Performative): """Performatives for the aggregation protocol.""" AGGREGATION = "aggregation" OBSERVATION = "observation" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"aggregation", "observation"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "contributors", "dialogue_reference", "message_id", "performative", "signature", "source", "target", "time", "value", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of AggregationMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=AggregationMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(AggregationMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def contributors(self) -> Tuple[str, ...]: """Get the 'contributors' content from the message.""" enforce(self.is_set("contributors"), "'contributors' content is not set.") return cast(Tuple[str, ...], self.get("contributors")) @property def signature(self) -> str: """Get the 'signature' content from the message.""" enforce(self.is_set("signature"), "'signature' content is not set.") return cast(str, self.get("signature")) @property def source(self) -> str: """Get the 'source' content from the message.""" enforce(self.is_set("source"), "'source' content is not set.") return cast(str, self.get("source")) @property def time(self) -> str: """Get the 'time' content from the message.""" enforce(self.is_set("time"), "'time' content is not set.") return cast(str, self.get("time")) @property def value(self) -> int: """Get the 'value' content from the message.""" enforce(self.is_set("value"), "'value' content is not set.") return cast(int, self.get("value")) def _is_consistent(self) -> bool: """Check that the message follows the aggregation protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, AggregationMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == AggregationMessage.Performative.OBSERVATION: expected_nb_of_contents = 4 enforce( type(self.value) is int, "Invalid type for content 'value'. Expected 'int'. Found '{}'.".format( type(self.value) ), ) enforce( isinstance(self.time, str), "Invalid type for content 'time'. Expected 'str'. Found '{}'.".format( type(self.time) ), ) enforce( isinstance(self.source, str), "Invalid type for content 'source'. Expected 'str'. Found '{}'.".format( type(self.source) ), ) enforce( isinstance(self.signature, str), "Invalid type for content 'signature'. Expected 'str'. Found '{}'.".format( type(self.signature) ), ) elif self.performative == AggregationMessage.Performative.AGGREGATION: expected_nb_of_contents = 4 enforce( type(self.value) is int, "Invalid type for content 'value'. Expected 'int'. Found '{}'.".format( type(self.value) ), ) enforce( isinstance(self.time, str), "Invalid type for content 'time'. Expected 'str'. Found '{}'.".format( type(self.time) ), ) enforce( isinstance(self.contributors, tuple), "Invalid type for content 'contributors'. Expected 'tuple'. Found '{}'.".format( type(self.contributors) ), ) enforce( all(isinstance(element, str) for element in self.contributors), "Invalid type for tuple elements in content 'contributors'. Expected 'str'.", ) enforce( isinstance(self.signature, str), "Invalid type for content 'signature'. Expected 'str'. Found '{}'.".format( type(self.signature) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/aggregation/protocol.yaml ================================================ name: aggregation author: fetchai version: 0.2.7 protocol_specification_id: fetchai/aggregation:0.2.7 type: protocol description: A protocol for agents to aggregate individual observations license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qmbab8fKDdNxLUP1i4Mszem1Z5sTknSkgEmMqMftGySPw2 __init__.py: QmPLeQvFJqhZ39qb2TchPVMUNqh7Y8qb11MRdJXVr34Bqw aggregation.proto: QmNpnRdmU4sMxxCkuJj1fz3QkEkwAmoP4vHk5ZdeTffDup aggregation_pb2.py: QmVyjn8ySVvya7oacMCB9ibhhHESud414YSbEag7Fat6Hw dialogues.py: QmbvTvfkrmWn7WoNg5AHjSQfC2ofv1Z7MJPXjk67YRq9A5 message.py: QmZXRaozSHTD1ABstrjA2xxb46u6iEzRHiB56X4DpF61j6 serialization.py: QmbHocoXnB9h6HPwBoYZ1N2ULF4FAuuZKs6uHF7jHw85eM fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/aggregation/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for aggregation protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.aggregation import aggregation_pb2 from packages.fetchai.protocols.aggregation.message import AggregationMessage class AggregationSerializer(Serializer): """Serialization for the 'aggregation' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Aggregation' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(AggregationMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() aggregation_msg = aggregation_pb2.AggregationMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == AggregationMessage.Performative.OBSERVATION: performative = aggregation_pb2.AggregationMessage.Observation_Performative() # type: ignore value = msg.value performative.value = value time = msg.time performative.time = time source = msg.source performative.source = source signature = msg.signature performative.signature = signature aggregation_msg.observation.CopyFrom(performative) elif performative_id == AggregationMessage.Performative.AGGREGATION: performative = aggregation_pb2.AggregationMessage.Aggregation_Performative() # type: ignore value = msg.value performative.value = value time = msg.time performative.time = time contributors = msg.contributors performative.contributors.extend(contributors) signature = msg.signature performative.signature = signature aggregation_msg.aggregation.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = aggregation_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Aggregation' message. :param obj: the bytes object. :return: the 'Aggregation' message. """ message_pb = ProtobufMessage() aggregation_pb = aggregation_pb2.AggregationMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target aggregation_pb.ParseFromString(message_pb.dialogue_message.content) performative = aggregation_pb.WhichOneof("performative") performative_id = AggregationMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == AggregationMessage.Performative.OBSERVATION: value = aggregation_pb.observation.value performative_content["value"] = value time = aggregation_pb.observation.time performative_content["time"] = time source = aggregation_pb.observation.source performative_content["source"] = source signature = aggregation_pb.observation.signature performative_content["signature"] = signature elif performative_id == AggregationMessage.Performative.AGGREGATION: value = aggregation_pb.aggregation.value performative_content["value"] = value time = aggregation_pb.aggregation.time performative_content["time"] = time contributors = aggregation_pb.aggregation.contributors contributors_tuple = tuple(contributors) performative_content["contributors"] = contributors_tuple signature = aggregation_pb.aggregation.signature performative_content["signature"] = signature else: raise ValueError("Performative not valid: {}.".format(performative_id)) return AggregationMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/contract_api/README.md ================================================ # Contract API Protocol ## Description This is a protocol for contract APIs' requests and responses. ## Specification ```yaml --- name: contract_api author: fetchai version: 1.1.7 description: A protocol for contract APIs requests and responses. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/contract_api:1.0.0 speech_acts: get_deploy_transaction: ledger_id: pt:str contract_id: pt:str callable: pt:str kwargs: ct:Kwargs get_raw_transaction: ledger_id: pt:str contract_id: pt:str contract_address: pt:str callable: pt:str kwargs: ct:Kwargs get_raw_message: ledger_id: pt:str contract_id: pt:str contract_address: pt:str callable: pt:str kwargs: ct:Kwargs get_state: ledger_id: pt:str contract_id: pt:str contract_address: pt:str callable: pt:str kwargs: ct:Kwargs state: state: ct:State raw_transaction: raw_transaction: ct:RawTransaction raw_message: raw_message: ct:RawMessage error: code: pt:optional[pt:int] message: pt:optional[pt:str] data: pt:bytes ... --- ct:Kwargs: bytes kwargs = 1; ct:State: bytes state = 1; ct:RawTransaction: bytes raw_transaction = 1; ct:RawMessage: bytes raw_message = 1; ... --- initiation: [get_deploy_transaction, get_raw_transaction, get_raw_message, get_state] reply: get_deploy_transaction: [raw_transaction, error] get_raw_transaction: [raw_transaction, error] get_raw_message: [raw_message, error] get_state: [state, error] raw_transaction: [] raw_message: [] state: [] error: [] termination: [state, raw_transaction, raw_message, error] roles: {agent, ledger} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/contract_api/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the contract_api protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.contract_api.serialization import ContractApiSerializer ContractApiMessage.serializer = ContractApiSerializer ================================================ FILE: packages/fetchai/protocols/contract_api/contract_api.proto ================================================ syntax = "proto3"; package aea.fetchai.contract_api.v1_0_0; message ContractApiMessage{ // Custom Types message Kwargs{ bytes kwargs = 1; } message RawMessage{ bytes raw_message = 1; } message RawTransaction{ bytes raw_transaction = 1; } message State{ bytes state = 1; } // Performatives and contents message Get_Deploy_Transaction_Performative{ string ledger_id = 1; string contract_id = 2; string callable = 3; Kwargs kwargs = 4; } message Get_Raw_Transaction_Performative{ string ledger_id = 1; string contract_id = 2; string contract_address = 3; string callable = 4; Kwargs kwargs = 5; } message Get_Raw_Message_Performative{ string ledger_id = 1; string contract_id = 2; string contract_address = 3; string callable = 4; Kwargs kwargs = 5; } message Get_State_Performative{ string ledger_id = 1; string contract_id = 2; string contract_address = 3; string callable = 4; Kwargs kwargs = 5; } message State_Performative{ State state = 1; } message Raw_Transaction_Performative{ RawTransaction raw_transaction = 1; } message Raw_Message_Performative{ RawMessage raw_message = 1; } message Error_Performative{ int64 code = 1; bool code_is_set = 2; string message = 3; bool message_is_set = 4; bytes data = 5; } oneof performative{ Error_Performative error = 5; Get_Deploy_Transaction_Performative get_deploy_transaction = 6; Get_Raw_Message_Performative get_raw_message = 7; Get_Raw_Transaction_Performative get_raw_transaction = 8; Get_State_Performative get_state = 9; Raw_Message_Performative raw_message = 10; Raw_Transaction_Performative raw_transaction = 11; State_Performative state = 12; } } ================================================ FILE: packages/fetchai/protocols/contract_api/contract_api_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: contract_api.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x12\x63ontract_api.proto\x12\x1f\x61\x65\x61.fetchai.contract_api.v1_0_0"\x93\x11\n\x12\x43ontractApiMessage\x12W\n\x05\x65rror\x18\x05 \x01(\x0b\x32\x46.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Error_PerformativeH\x00\x12y\n\x16get_deploy_transaction\x18\x06 \x01(\x0b\x32W.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_Deploy_Transaction_PerformativeH\x00\x12k\n\x0fget_raw_message\x18\x07 \x01(\x0b\x32P.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_Raw_Message_PerformativeH\x00\x12s\n\x13get_raw_transaction\x18\x08 \x01(\x0b\x32T.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_Raw_Transaction_PerformativeH\x00\x12_\n\tget_state\x18\t \x01(\x0b\x32J.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_State_PerformativeH\x00\x12\x63\n\x0braw_message\x18\n \x01(\x0b\x32L.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Raw_Message_PerformativeH\x00\x12k\n\x0fraw_transaction\x18\x0b \x01(\x0b\x32P.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Raw_Transaction_PerformativeH\x00\x12W\n\x05state\x18\x0c \x01(\x0b\x32\x46.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.State_PerformativeH\x00\x1a\x18\n\x06Kwargs\x12\x0e\n\x06kwargs\x18\x01 \x01(\x0c\x1a!\n\nRawMessage\x12\x13\n\x0braw_message\x18\x01 \x01(\x0c\x1a)\n\x0eRawTransaction\x12\x17\n\x0fraw_transaction\x18\x01 \x01(\x0c\x1a\x16\n\x05State\x12\r\n\x05state\x18\x01 \x01(\x0c\x1a\xab\x01\n#Get_Deploy_Transaction_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x03 \x01(\t\x12J\n\x06kwargs\x18\x04 \x01(\x0b\x32:.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Kwargs\x1a\xc2\x01\n Get_Raw_Transaction_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\t\x12\x18\n\x10\x63ontract_address\x18\x03 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x04 \x01(\t\x12J\n\x06kwargs\x18\x05 \x01(\x0b\x32:.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Kwargs\x1a\xbe\x01\n\x1cGet_Raw_Message_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\t\x12\x18\n\x10\x63ontract_address\x18\x03 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x04 \x01(\t\x12J\n\x06kwargs\x18\x05 \x01(\x0b\x32:.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Kwargs\x1a\xb8\x01\n\x16Get_State_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x13\n\x0b\x63ontract_id\x18\x02 \x01(\t\x12\x18\n\x10\x63ontract_address\x18\x03 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x04 \x01(\t\x12J\n\x06kwargs\x18\x05 \x01(\x0b\x32:.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Kwargs\x1a^\n\x12State_Performative\x12H\n\x05state\x18\x01 \x01(\x0b\x32\x39.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.State\x1a{\n\x1cRaw_Transaction_Performative\x12[\n\x0fraw_transaction\x18\x01 \x01(\x0b\x32\x42.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.RawTransaction\x1ao\n\x18Raw_Message_Performative\x12S\n\x0braw_message\x18\x01 \x01(\x0b\x32>.aea.fetchai.contract_api.v1_0_0.ContractApiMessage.RawMessage\x1an\n\x12\x45rror_Performative\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x03\x12\x13\n\x0b\x63ode_is_set\x18\x02 \x01(\x08\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x16\n\x0emessage_is_set\x18\x04 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\x42\x0e\n\x0cperformativeb\x06proto3' ) _CONTRACTAPIMESSAGE = DESCRIPTOR.message_types_by_name["ContractApiMessage"] _CONTRACTAPIMESSAGE_KWARGS = _CONTRACTAPIMESSAGE.nested_types_by_name["Kwargs"] _CONTRACTAPIMESSAGE_RAWMESSAGE = _CONTRACTAPIMESSAGE.nested_types_by_name["RawMessage"] _CONTRACTAPIMESSAGE_RAWTRANSACTION = _CONTRACTAPIMESSAGE.nested_types_by_name[ "RawTransaction" ] _CONTRACTAPIMESSAGE_STATE = _CONTRACTAPIMESSAGE.nested_types_by_name["State"] _CONTRACTAPIMESSAGE_GET_DEPLOY_TRANSACTION_PERFORMATIVE = ( _CONTRACTAPIMESSAGE.nested_types_by_name["Get_Deploy_Transaction_Performative"] ) _CONTRACTAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE = ( _CONTRACTAPIMESSAGE.nested_types_by_name["Get_Raw_Transaction_Performative"] ) _CONTRACTAPIMESSAGE_GET_RAW_MESSAGE_PERFORMATIVE = ( _CONTRACTAPIMESSAGE.nested_types_by_name["Get_Raw_Message_Performative"] ) _CONTRACTAPIMESSAGE_GET_STATE_PERFORMATIVE = _CONTRACTAPIMESSAGE.nested_types_by_name[ "Get_State_Performative" ] _CONTRACTAPIMESSAGE_STATE_PERFORMATIVE = _CONTRACTAPIMESSAGE.nested_types_by_name[ "State_Performative" ] _CONTRACTAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE = ( _CONTRACTAPIMESSAGE.nested_types_by_name["Raw_Transaction_Performative"] ) _CONTRACTAPIMESSAGE_RAW_MESSAGE_PERFORMATIVE = _CONTRACTAPIMESSAGE.nested_types_by_name[ "Raw_Message_Performative" ] _CONTRACTAPIMESSAGE_ERROR_PERFORMATIVE = _CONTRACTAPIMESSAGE.nested_types_by_name[ "Error_Performative" ] ContractApiMessage = _reflection.GeneratedProtocolMessageType( "ContractApiMessage", (_message.Message,), { "Kwargs": _reflection.GeneratedProtocolMessageType( "Kwargs", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_KWARGS, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Kwargs) }, ), "RawMessage": _reflection.GeneratedProtocolMessageType( "RawMessage", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_RAWMESSAGE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.RawMessage) }, ), "RawTransaction": _reflection.GeneratedProtocolMessageType( "RawTransaction", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_RAWTRANSACTION, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.RawTransaction) }, ), "State": _reflection.GeneratedProtocolMessageType( "State", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_STATE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.State) }, ), "Get_Deploy_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Get_Deploy_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_GET_DEPLOY_TRANSACTION_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_Deploy_Transaction_Performative) }, ), "Get_Raw_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Get_Raw_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_Raw_Transaction_Performative) }, ), "Get_Raw_Message_Performative": _reflection.GeneratedProtocolMessageType( "Get_Raw_Message_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_GET_RAW_MESSAGE_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_Raw_Message_Performative) }, ), "Get_State_Performative": _reflection.GeneratedProtocolMessageType( "Get_State_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_GET_STATE_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Get_State_Performative) }, ), "State_Performative": _reflection.GeneratedProtocolMessageType( "State_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_STATE_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.State_Performative) }, ), "Raw_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Raw_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Raw_Transaction_Performative) }, ), "Raw_Message_Performative": _reflection.GeneratedProtocolMessageType( "Raw_Message_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_RAW_MESSAGE_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Raw_Message_Performative) }, ), "Error_Performative": _reflection.GeneratedProtocolMessageType( "Error_Performative", (_message.Message,), { "DESCRIPTOR": _CONTRACTAPIMESSAGE_ERROR_PERFORMATIVE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage.Error_Performative) }, ), "DESCRIPTOR": _CONTRACTAPIMESSAGE, "__module__": "contract_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.contract_api.v1_0_0.ContractApiMessage) }, ) _sym_db.RegisterMessage(ContractApiMessage) _sym_db.RegisterMessage(ContractApiMessage.Kwargs) _sym_db.RegisterMessage(ContractApiMessage.RawMessage) _sym_db.RegisterMessage(ContractApiMessage.RawTransaction) _sym_db.RegisterMessage(ContractApiMessage.State) _sym_db.RegisterMessage(ContractApiMessage.Get_Deploy_Transaction_Performative) _sym_db.RegisterMessage(ContractApiMessage.Get_Raw_Transaction_Performative) _sym_db.RegisterMessage(ContractApiMessage.Get_Raw_Message_Performative) _sym_db.RegisterMessage(ContractApiMessage.Get_State_Performative) _sym_db.RegisterMessage(ContractApiMessage.State_Performative) _sym_db.RegisterMessage(ContractApiMessage.Raw_Transaction_Performative) _sym_db.RegisterMessage(ContractApiMessage.Raw_Message_Performative) _sym_db.RegisterMessage(ContractApiMessage.Error_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _CONTRACTAPIMESSAGE._serialized_start = 56 _CONTRACTAPIMESSAGE._serialized_end = 2251 _CONTRACTAPIMESSAGE_KWARGS._serialized_start = 912 _CONTRACTAPIMESSAGE_KWARGS._serialized_end = 936 _CONTRACTAPIMESSAGE_RAWMESSAGE._serialized_start = 938 _CONTRACTAPIMESSAGE_RAWMESSAGE._serialized_end = 971 _CONTRACTAPIMESSAGE_RAWTRANSACTION._serialized_start = 973 _CONTRACTAPIMESSAGE_RAWTRANSACTION._serialized_end = 1014 _CONTRACTAPIMESSAGE_STATE._serialized_start = 1016 _CONTRACTAPIMESSAGE_STATE._serialized_end = 1038 _CONTRACTAPIMESSAGE_GET_DEPLOY_TRANSACTION_PERFORMATIVE._serialized_start = 1041 _CONTRACTAPIMESSAGE_GET_DEPLOY_TRANSACTION_PERFORMATIVE._serialized_end = 1212 _CONTRACTAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE._serialized_start = 1215 _CONTRACTAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE._serialized_end = 1409 _CONTRACTAPIMESSAGE_GET_RAW_MESSAGE_PERFORMATIVE._serialized_start = 1412 _CONTRACTAPIMESSAGE_GET_RAW_MESSAGE_PERFORMATIVE._serialized_end = 1602 _CONTRACTAPIMESSAGE_GET_STATE_PERFORMATIVE._serialized_start = 1605 _CONTRACTAPIMESSAGE_GET_STATE_PERFORMATIVE._serialized_end = 1789 _CONTRACTAPIMESSAGE_STATE_PERFORMATIVE._serialized_start = 1791 _CONTRACTAPIMESSAGE_STATE_PERFORMATIVE._serialized_end = 1885 _CONTRACTAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE._serialized_start = 1887 _CONTRACTAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE._serialized_end = 2010 _CONTRACTAPIMESSAGE_RAW_MESSAGE_PERFORMATIVE._serialized_start = 2012 _CONTRACTAPIMESSAGE_RAW_MESSAGE_PERFORMATIVE._serialized_end = 2123 _CONTRACTAPIMESSAGE_ERROR_PERFORMATIVE._serialized_start = 2125 _CONTRACTAPIMESSAGE_ERROR_PERFORMATIVE._serialized_end = 2235 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/contract_api/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from typing import Any from aea.common import JSONLike from aea.exceptions import enforce from aea.helpers.serializers import DictProtobufStructSerializer from aea.helpers.transaction.base import RawMessage as BaseRawMessage from aea.helpers.transaction.base import RawTransaction as BaseRawTransaction from aea.helpers.transaction.base import State as BaseState RawMessage = BaseRawMessage RawTransaction = BaseRawTransaction State = BaseState class Kwargs: """This class represents an instance of Kwargs.""" __slots__ = ("_body",) def __init__( self, body: JSONLike, ): """Initialise an instance of RawTransaction.""" self._body = body self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce( isinstance(self._body, dict) and all(isinstance(key, str) for key in self._body.keys()), "Body must be dict and keys must be str.", ) @property def body(self) -> JSONLike: """Get the body.""" return self._body @staticmethod def encode(kwargs_protobuf_object: Any, kwargs_object: "Kwargs") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the kwargs_protobuf_object argument is matched with the instance of this class in the 'kwargs_object' argument. :param kwargs_protobuf_object: the protocol buffer object whose type corresponds with this class. :param kwargs_object: an instance of this class to be encoded in the protocol buffer object. """ kwargs_protobuf_object.kwargs = DictProtobufStructSerializer.encode( kwargs_object.body ) @classmethod def decode(cls, kwargs_protobuf_object: Any) -> "Kwargs": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'kwargs_protobuf_object' argument. :param kwargs_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'kwargs_protobuf_object' argument. """ kwargs = DictProtobufStructSerializer.decode(kwargs_protobuf_object.kwargs) return cls(kwargs) def __eq__(self, other: Any) -> bool: """Check equality.""" return isinstance(other, Kwargs) and self.body == other.body def __str__(self) -> str: """Get string representation.""" return "Kwargs: body={}".format(self.body) ================================================ FILE: packages/fetchai/protocols/contract_api/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for contract_api dialogue management. - ContractApiDialogue: The dialogue class maintains state of a dialogue and manages it. - ContractApiDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.contract_api.message import ContractApiMessage class ContractApiDialogue(Dialogue): """The contract_api dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ContractApiMessage.Performative.GET_RAW_TRANSACTION, ContractApiMessage.Performative.GET_RAW_MESSAGE, ContractApiMessage.Performative.GET_STATE, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { ContractApiMessage.Performative.STATE, ContractApiMessage.Performative.RAW_TRANSACTION, ContractApiMessage.Performative.RAW_MESSAGE, ContractApiMessage.Performative.ERROR, } ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { ContractApiMessage.Performative.ERROR: frozenset(), ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION: frozenset( { ContractApiMessage.Performative.RAW_TRANSACTION, ContractApiMessage.Performative.ERROR, } ), ContractApiMessage.Performative.GET_RAW_MESSAGE: frozenset( { ContractApiMessage.Performative.RAW_MESSAGE, ContractApiMessage.Performative.ERROR, } ), ContractApiMessage.Performative.GET_RAW_TRANSACTION: frozenset( { ContractApiMessage.Performative.RAW_TRANSACTION, ContractApiMessage.Performative.ERROR, } ), ContractApiMessage.Performative.GET_STATE: frozenset( { ContractApiMessage.Performative.STATE, ContractApiMessage.Performative.ERROR, } ), ContractApiMessage.Performative.RAW_MESSAGE: frozenset(), ContractApiMessage.Performative.RAW_TRANSACTION: frozenset(), ContractApiMessage.Performative.STATE: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a contract_api dialogue.""" AGENT = "agent" LEDGER = "ledger" class EndState(Dialogue.EndState): """This class defines the end states of a contract_api dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class ContractApiDialogues(Dialogues, ABC): """This class keeps track of all contract_api dialogues.""" END_STATES = frozenset( {ContractApiDialogue.EndState.SUCCESSFUL, ContractApiDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[ContractApiDialogue] = ContractApiDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=ContractApiMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/contract_api/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains contract_api's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Optional, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.contract_api.custom_types import Kwargs as CustomKwargs from packages.fetchai.protocols.contract_api.custom_types import ( RawMessage as CustomRawMessage, ) from packages.fetchai.protocols.contract_api.custom_types import ( RawTransaction as CustomRawTransaction, ) from packages.fetchai.protocols.contract_api.custom_types import State as CustomState _default_logger = logging.getLogger( "aea.packages.fetchai.protocols.contract_api.message" ) DEFAULT_BODY_SIZE = 4 class ContractApiMessage(Message): """A protocol for contract APIs requests and responses.""" protocol_id = PublicId.from_str("fetchai/contract_api:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/contract_api:1.0.0") Kwargs = CustomKwargs RawMessage = CustomRawMessage RawTransaction = CustomRawTransaction State = CustomState class Performative(Message.Performative): """Performatives for the contract_api protocol.""" ERROR = "error" GET_DEPLOY_TRANSACTION = "get_deploy_transaction" GET_RAW_MESSAGE = "get_raw_message" GET_RAW_TRANSACTION = "get_raw_transaction" GET_STATE = "get_state" RAW_MESSAGE = "raw_message" RAW_TRANSACTION = "raw_transaction" STATE = "state" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "error", "get_deploy_transaction", "get_raw_message", "get_raw_transaction", "get_state", "raw_message", "raw_transaction", "state", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "callable", "code", "contract_address", "contract_id", "data", "dialogue_reference", "kwargs", "ledger_id", "message", "message_id", "performative", "raw_message", "raw_transaction", "state", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of ContractApiMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=ContractApiMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(ContractApiMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def callable(self) -> str: """Get the 'callable' content from the message.""" enforce(self.is_set("callable"), "'callable' content is not set.") return cast(str, self.get("callable")) @property def code(self) -> Optional[int]: """Get the 'code' content from the message.""" return cast(Optional[int], self.get("code")) @property def contract_address(self) -> str: """Get the 'contract_address' content from the message.""" enforce( self.is_set("contract_address"), "'contract_address' content is not set." ) return cast(str, self.get("contract_address")) @property def contract_id(self) -> str: """Get the 'contract_id' content from the message.""" enforce(self.is_set("contract_id"), "'contract_id' content is not set.") return cast(str, self.get("contract_id")) @property def data(self) -> bytes: """Get the 'data' content from the message.""" enforce(self.is_set("data"), "'data' content is not set.") return cast(bytes, self.get("data")) @property def kwargs(self) -> CustomKwargs: """Get the 'kwargs' content from the message.""" enforce(self.is_set("kwargs"), "'kwargs' content is not set.") return cast(CustomKwargs, self.get("kwargs")) @property def ledger_id(self) -> str: """Get the 'ledger_id' content from the message.""" enforce(self.is_set("ledger_id"), "'ledger_id' content is not set.") return cast(str, self.get("ledger_id")) @property def message(self) -> Optional[str]: """Get the 'message' content from the message.""" return cast(Optional[str], self.get("message")) @property def raw_message(self) -> CustomRawMessage: """Get the 'raw_message' content from the message.""" enforce(self.is_set("raw_message"), "'raw_message' content is not set.") return cast(CustomRawMessage, self.get("raw_message")) @property def raw_transaction(self) -> CustomRawTransaction: """Get the 'raw_transaction' content from the message.""" enforce(self.is_set("raw_transaction"), "'raw_transaction' content is not set.") return cast(CustomRawTransaction, self.get("raw_transaction")) @property def state(self) -> CustomState: """Get the 'state' content from the message.""" enforce(self.is_set("state"), "'state' content is not set.") return cast(CustomState, self.get("state")) def _is_consistent(self) -> bool: """Check that the message follows the contract_api protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, ContractApiMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if ( self.performative == ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION ): expected_nb_of_contents = 4 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.contract_id, str), "Invalid type for content 'contract_id'. Expected 'str'. Found '{}'.".format( type(self.contract_id) ), ) enforce( isinstance(self.callable, str), "Invalid type for content 'callable'. Expected 'str'. Found '{}'.".format( type(self.callable) ), ) enforce( isinstance(self.kwargs, CustomKwargs), "Invalid type for content 'kwargs'. Expected 'Kwargs'. Found '{}'.".format( type(self.kwargs) ), ) elif ( self.performative == ContractApiMessage.Performative.GET_RAW_TRANSACTION ): expected_nb_of_contents = 5 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.contract_id, str), "Invalid type for content 'contract_id'. Expected 'str'. Found '{}'.".format( type(self.contract_id) ), ) enforce( isinstance(self.contract_address, str), "Invalid type for content 'contract_address'. Expected 'str'. Found '{}'.".format( type(self.contract_address) ), ) enforce( isinstance(self.callable, str), "Invalid type for content 'callable'. Expected 'str'. Found '{}'.".format( type(self.callable) ), ) enforce( isinstance(self.kwargs, CustomKwargs), "Invalid type for content 'kwargs'. Expected 'Kwargs'. Found '{}'.".format( type(self.kwargs) ), ) elif self.performative == ContractApiMessage.Performative.GET_RAW_MESSAGE: expected_nb_of_contents = 5 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.contract_id, str), "Invalid type for content 'contract_id'. Expected 'str'. Found '{}'.".format( type(self.contract_id) ), ) enforce( isinstance(self.contract_address, str), "Invalid type for content 'contract_address'. Expected 'str'. Found '{}'.".format( type(self.contract_address) ), ) enforce( isinstance(self.callable, str), "Invalid type for content 'callable'. Expected 'str'. Found '{}'.".format( type(self.callable) ), ) enforce( isinstance(self.kwargs, CustomKwargs), "Invalid type for content 'kwargs'. Expected 'Kwargs'. Found '{}'.".format( type(self.kwargs) ), ) elif self.performative == ContractApiMessage.Performative.GET_STATE: expected_nb_of_contents = 5 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.contract_id, str), "Invalid type for content 'contract_id'. Expected 'str'. Found '{}'.".format( type(self.contract_id) ), ) enforce( isinstance(self.contract_address, str), "Invalid type for content 'contract_address'. Expected 'str'. Found '{}'.".format( type(self.contract_address) ), ) enforce( isinstance(self.callable, str), "Invalid type for content 'callable'. Expected 'str'. Found '{}'.".format( type(self.callable) ), ) enforce( isinstance(self.kwargs, CustomKwargs), "Invalid type for content 'kwargs'. Expected 'Kwargs'. Found '{}'.".format( type(self.kwargs) ), ) elif self.performative == ContractApiMessage.Performative.STATE: expected_nb_of_contents = 1 enforce( isinstance(self.state, CustomState), "Invalid type for content 'state'. Expected 'State'. Found '{}'.".format( type(self.state) ), ) elif self.performative == ContractApiMessage.Performative.RAW_TRANSACTION: expected_nb_of_contents = 1 enforce( isinstance(self.raw_transaction, CustomRawTransaction), "Invalid type for content 'raw_transaction'. Expected 'RawTransaction'. Found '{}'.".format( type(self.raw_transaction) ), ) elif self.performative == ContractApiMessage.Performative.RAW_MESSAGE: expected_nb_of_contents = 1 enforce( isinstance(self.raw_message, CustomRawMessage), "Invalid type for content 'raw_message'. Expected 'RawMessage'. Found '{}'.".format( type(self.raw_message) ), ) elif self.performative == ContractApiMessage.Performative.ERROR: expected_nb_of_contents = 1 if self.is_set("code"): expected_nb_of_contents += 1 code = cast(int, self.code) enforce( type(code) is int, "Invalid type for content 'code'. Expected 'int'. Found '{}'.".format( type(code) ), ) if self.is_set("message"): expected_nb_of_contents += 1 message = cast(str, self.message) enforce( isinstance(message, str), "Invalid type for content 'message'. Expected 'str'. Found '{}'.".format( type(message) ), ) enforce( isinstance(self.data, bytes), "Invalid type for content 'data'. Expected 'bytes'. Found '{}'.".format( type(self.data) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/contract_api/protocol.yaml ================================================ name: contract_api author: fetchai version: 1.1.7 protocol_specification_id: fetchai/contract_api:1.0.0 type: protocol description: A protocol for contract APIs requests and responses. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qmbbumi4uwtWACodswSRbf6YZkXBgzhByLkY9ZTz846aKQ __init__.py: QmSU7kcQHDrWyFvCZpLnN2qH5CE29JrWnpTBp9tiSrhCkB contract_api.proto: QmXP6TkLcQcsFWE66Z7ffgNcW3p6xAB63GXh4UsnEbHd9D contract_api_pb2.py: QmXqZFRTtRZeLytmmWKRxExKGua1Be3Ux5Eq1qLabsbe2Y custom_types.py: QmXbRNjRpLvV4qhoGPHndQZfgTC5BMDoUDW8ZP9MrWKCG4 dialogues.py: QmXCtZ8beuCEXueFoJRHAEP7Y7UHfBhMCFSjtQWmCQGVzP message.py: QmfQSKdcr5CcpPC9apArdoqgdKcAsFVNnTGBPs4dXLUWka serialization.py: QmcVXMQWnw76k5iwMnvWnQfCgJBUoTXBpnGDFqnkNRyfhZ fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/contract_api/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for contract_api protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.contract_api import contract_api_pb2 from packages.fetchai.protocols.contract_api.custom_types import ( Kwargs, RawMessage, RawTransaction, State, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage class ContractApiSerializer(Serializer): """Serialization for the 'contract_api' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'ContractApi' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(ContractApiMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() contract_api_msg = contract_api_pb2.ContractApiMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION: performative = contract_api_pb2.ContractApiMessage.Get_Deploy_Transaction_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id contract_id = msg.contract_id performative.contract_id = contract_id callable = msg.callable performative.callable = callable kwargs = msg.kwargs Kwargs.encode(performative.kwargs, kwargs) contract_api_msg.get_deploy_transaction.CopyFrom(performative) elif performative_id == ContractApiMessage.Performative.GET_RAW_TRANSACTION: performative = contract_api_pb2.ContractApiMessage.Get_Raw_Transaction_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id contract_id = msg.contract_id performative.contract_id = contract_id contract_address = msg.contract_address performative.contract_address = contract_address callable = msg.callable performative.callable = callable kwargs = msg.kwargs Kwargs.encode(performative.kwargs, kwargs) contract_api_msg.get_raw_transaction.CopyFrom(performative) elif performative_id == ContractApiMessage.Performative.GET_RAW_MESSAGE: performative = contract_api_pb2.ContractApiMessage.Get_Raw_Message_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id contract_id = msg.contract_id performative.contract_id = contract_id contract_address = msg.contract_address performative.contract_address = contract_address callable = msg.callable performative.callable = callable kwargs = msg.kwargs Kwargs.encode(performative.kwargs, kwargs) contract_api_msg.get_raw_message.CopyFrom(performative) elif performative_id == ContractApiMessage.Performative.GET_STATE: performative = contract_api_pb2.ContractApiMessage.Get_State_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id contract_id = msg.contract_id performative.contract_id = contract_id contract_address = msg.contract_address performative.contract_address = contract_address callable = msg.callable performative.callable = callable kwargs = msg.kwargs Kwargs.encode(performative.kwargs, kwargs) contract_api_msg.get_state.CopyFrom(performative) elif performative_id == ContractApiMessage.Performative.STATE: performative = contract_api_pb2.ContractApiMessage.State_Performative() # type: ignore state = msg.state State.encode(performative.state, state) contract_api_msg.state.CopyFrom(performative) elif performative_id == ContractApiMessage.Performative.RAW_TRANSACTION: performative = contract_api_pb2.ContractApiMessage.Raw_Transaction_Performative() # type: ignore raw_transaction = msg.raw_transaction RawTransaction.encode(performative.raw_transaction, raw_transaction) contract_api_msg.raw_transaction.CopyFrom(performative) elif performative_id == ContractApiMessage.Performative.RAW_MESSAGE: performative = contract_api_pb2.ContractApiMessage.Raw_Message_Performative() # type: ignore raw_message = msg.raw_message RawMessage.encode(performative.raw_message, raw_message) contract_api_msg.raw_message.CopyFrom(performative) elif performative_id == ContractApiMessage.Performative.ERROR: performative = contract_api_pb2.ContractApiMessage.Error_Performative() # type: ignore if msg.is_set("code"): performative.code_is_set = True code = msg.code performative.code = code if msg.is_set("message"): performative.message_is_set = True message = msg.message performative.message = message data = msg.data performative.data = data contract_api_msg.error.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = contract_api_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'ContractApi' message. :param obj: the bytes object. :return: the 'ContractApi' message. """ message_pb = ProtobufMessage() contract_api_pb = contract_api_pb2.ContractApiMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target contract_api_pb.ParseFromString(message_pb.dialogue_message.content) performative = contract_api_pb.WhichOneof("performative") performative_id = ContractApiMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION: ledger_id = contract_api_pb.get_deploy_transaction.ledger_id performative_content["ledger_id"] = ledger_id contract_id = contract_api_pb.get_deploy_transaction.contract_id performative_content["contract_id"] = contract_id callable = contract_api_pb.get_deploy_transaction.callable performative_content["callable"] = callable pb2_kwargs = contract_api_pb.get_deploy_transaction.kwargs kwargs = Kwargs.decode(pb2_kwargs) performative_content["kwargs"] = kwargs elif performative_id == ContractApiMessage.Performative.GET_RAW_TRANSACTION: ledger_id = contract_api_pb.get_raw_transaction.ledger_id performative_content["ledger_id"] = ledger_id contract_id = contract_api_pb.get_raw_transaction.contract_id performative_content["contract_id"] = contract_id contract_address = contract_api_pb.get_raw_transaction.contract_address performative_content["contract_address"] = contract_address callable = contract_api_pb.get_raw_transaction.callable performative_content["callable"] = callable pb2_kwargs = contract_api_pb.get_raw_transaction.kwargs kwargs = Kwargs.decode(pb2_kwargs) performative_content["kwargs"] = kwargs elif performative_id == ContractApiMessage.Performative.GET_RAW_MESSAGE: ledger_id = contract_api_pb.get_raw_message.ledger_id performative_content["ledger_id"] = ledger_id contract_id = contract_api_pb.get_raw_message.contract_id performative_content["contract_id"] = contract_id contract_address = contract_api_pb.get_raw_message.contract_address performative_content["contract_address"] = contract_address callable = contract_api_pb.get_raw_message.callable performative_content["callable"] = callable pb2_kwargs = contract_api_pb.get_raw_message.kwargs kwargs = Kwargs.decode(pb2_kwargs) performative_content["kwargs"] = kwargs elif performative_id == ContractApiMessage.Performative.GET_STATE: ledger_id = contract_api_pb.get_state.ledger_id performative_content["ledger_id"] = ledger_id contract_id = contract_api_pb.get_state.contract_id performative_content["contract_id"] = contract_id contract_address = contract_api_pb.get_state.contract_address performative_content["contract_address"] = contract_address callable = contract_api_pb.get_state.callable performative_content["callable"] = callable pb2_kwargs = contract_api_pb.get_state.kwargs kwargs = Kwargs.decode(pb2_kwargs) performative_content["kwargs"] = kwargs elif performative_id == ContractApiMessage.Performative.STATE: pb2_state = contract_api_pb.state.state state = State.decode(pb2_state) performative_content["state"] = state elif performative_id == ContractApiMessage.Performative.RAW_TRANSACTION: pb2_raw_transaction = contract_api_pb.raw_transaction.raw_transaction raw_transaction = RawTransaction.decode(pb2_raw_transaction) performative_content["raw_transaction"] = raw_transaction elif performative_id == ContractApiMessage.Performative.RAW_MESSAGE: pb2_raw_message = contract_api_pb.raw_message.raw_message raw_message = RawMessage.decode(pb2_raw_message) performative_content["raw_message"] = raw_message elif performative_id == ContractApiMessage.Performative.ERROR: if contract_api_pb.error.code_is_set: code = contract_api_pb.error.code performative_content["code"] = code if contract_api_pb.error.message_is_set: message = contract_api_pb.error.message performative_content["message"] = message data = contract_api_pb.error.data performative_content["data"] = data else: raise ValueError("Performative not valid: {}.".format(performative_id)) return ContractApiMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/cosm_trade/README.md ================================================ # Ledger API Protocol ## Description This is a protocol for preparing an atomic swap bilateral transaction for cosmos-based ledgers, including fetchai's. For two parties A and B to atomically swap tokens on cosmos-based networks, A has to send its public key to B. Then B constructs the transaction using both his public key and A's, signs the transaction, adds its signature to the list of signatures in the transaction and sends this to A. After receiving, A signs the transaction, adds its signature to the list of signatures in the transaction and broadcasts it to the network for processing. ## Specification ```yaml --- name: cosm_trade author: fetchai version: 0.2.7 description: A protocol for preparing an atomic swap bilateral transaction for cosmos-based ledgers, including fetchai's. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/cosm_trade:1.0.0 speech_acts: inform_public_key: public_key: pt:str inform_signed_transaction: signed_transaction: ct:SignedTransaction fipa_dialogue_id: pt:optional[pt:list[pt:str]] error: code: pt:int message: pt:optional[pt:str] data: pt:optional[pt:bytes] end: {} ... --- ct:SignedTransaction: | bytes signed_transaction = 1; ... --- initiation: [inform_public_key, inform_signed_transaction] reply: inform_public_key: [inform_signed_transaction, error] inform_signed_transaction: [error, end] error: [] end: [] termination: [error, end] roles: {agent} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/cosm_trade/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the cosm_trade protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.cosm_trade.message import CosmTradeMessage from packages.fetchai.protocols.cosm_trade.serialization import CosmTradeSerializer CosmTradeMessage.serializer = CosmTradeSerializer ================================================ FILE: packages/fetchai/protocols/cosm_trade/cosm_trade.proto ================================================ syntax = "proto3"; package aea.fetchai.cosm_trade.v1_0_0; message CosmTradeMessage{ // Custom Types message SignedTransaction{ bytes signed_transaction = 1; } // Performatives and contents message Inform_Public_Key_Performative{ string public_key = 1; } message Inform_Signed_Transaction_Performative{ SignedTransaction signed_transaction = 1; repeated string fipa_dialogue_id = 2; bool fipa_dialogue_id_is_set = 3; } message Error_Performative{ int64 code = 1; string message = 2; bool message_is_set = 3; bytes data = 4; bool data_is_set = 5; } message End_Performative{ } oneof performative{ End_Performative end = 5; Error_Performative error = 6; Inform_Public_Key_Performative inform_public_key = 7; Inform_Signed_Transaction_Performative inform_signed_transaction = 8; } } ================================================ FILE: packages/fetchai/protocols/cosm_trade/cosm_trade_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: cosm_trade.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x10\x63osm_trade.proto\x12\x1d\x61\x65\x61.fetchai.cosm_trade.v1_0_0"\xe2\x06\n\x10\x43osmTradeMessage\x12O\n\x03\x65nd\x18\x05 \x01(\x0b\x32@.aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.End_PerformativeH\x00\x12S\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x42.aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.Error_PerformativeH\x00\x12k\n\x11inform_public_key\x18\x07 \x01(\x0b\x32N.aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.Inform_Public_Key_PerformativeH\x00\x12{\n\x19inform_signed_transaction\x18\x08 \x01(\x0b\x32V.aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.Inform_Signed_Transaction_PerformativeH\x00\x1a/\n\x11SignedTransaction\x12\x1a\n\x12signed_transaction\x18\x01 \x01(\x0c\x1a\x34\n\x1eInform_Public_Key_Performative\x12\x12\n\npublic_key\x18\x01 \x01(\t\x1a\xc2\x01\n&Inform_Signed_Transaction_Performative\x12]\n\x12signed_transaction\x18\x01 \x01(\x0b\x32\x41.aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.SignedTransaction\x12\x18\n\x10\x66ipa_dialogue_id\x18\x02 \x03(\t\x12\x1f\n\x17\x66ipa_dialogue_id_is_set\x18\x03 \x01(\x08\x1an\n\x12\x45rror_Performative\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x03\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x16\n\x0emessage_is_set\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x13\n\x0b\x64\x61ta_is_set\x18\x05 \x01(\x08\x1a\x12\n\x10\x45nd_PerformativeB\x0e\n\x0cperformativeb\x06proto3' ) _COSMTRADEMESSAGE = DESCRIPTOR.message_types_by_name["CosmTradeMessage"] _COSMTRADEMESSAGE_SIGNEDTRANSACTION = _COSMTRADEMESSAGE.nested_types_by_name[ "SignedTransaction" ] _COSMTRADEMESSAGE_INFORM_PUBLIC_KEY_PERFORMATIVE = ( _COSMTRADEMESSAGE.nested_types_by_name["Inform_Public_Key_Performative"] ) _COSMTRADEMESSAGE_INFORM_SIGNED_TRANSACTION_PERFORMATIVE = ( _COSMTRADEMESSAGE.nested_types_by_name["Inform_Signed_Transaction_Performative"] ) _COSMTRADEMESSAGE_ERROR_PERFORMATIVE = _COSMTRADEMESSAGE.nested_types_by_name[ "Error_Performative" ] _COSMTRADEMESSAGE_END_PERFORMATIVE = _COSMTRADEMESSAGE.nested_types_by_name[ "End_Performative" ] CosmTradeMessage = _reflection.GeneratedProtocolMessageType( "CosmTradeMessage", (_message.Message,), { "SignedTransaction": _reflection.GeneratedProtocolMessageType( "SignedTransaction", (_message.Message,), { "DESCRIPTOR": _COSMTRADEMESSAGE_SIGNEDTRANSACTION, "__module__": "cosm_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.SignedTransaction) }, ), "Inform_Public_Key_Performative": _reflection.GeneratedProtocolMessageType( "Inform_Public_Key_Performative", (_message.Message,), { "DESCRIPTOR": _COSMTRADEMESSAGE_INFORM_PUBLIC_KEY_PERFORMATIVE, "__module__": "cosm_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.Inform_Public_Key_Performative) }, ), "Inform_Signed_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Inform_Signed_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _COSMTRADEMESSAGE_INFORM_SIGNED_TRANSACTION_PERFORMATIVE, "__module__": "cosm_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.Inform_Signed_Transaction_Performative) }, ), "Error_Performative": _reflection.GeneratedProtocolMessageType( "Error_Performative", (_message.Message,), { "DESCRIPTOR": _COSMTRADEMESSAGE_ERROR_PERFORMATIVE, "__module__": "cosm_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.Error_Performative) }, ), "End_Performative": _reflection.GeneratedProtocolMessageType( "End_Performative", (_message.Message,), { "DESCRIPTOR": _COSMTRADEMESSAGE_END_PERFORMATIVE, "__module__": "cosm_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage.End_Performative) }, ), "DESCRIPTOR": _COSMTRADEMESSAGE, "__module__": "cosm_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.cosm_trade.v1_0_0.CosmTradeMessage) }, ) _sym_db.RegisterMessage(CosmTradeMessage) _sym_db.RegisterMessage(CosmTradeMessage.SignedTransaction) _sym_db.RegisterMessage(CosmTradeMessage.Inform_Public_Key_Performative) _sym_db.RegisterMessage(CosmTradeMessage.Inform_Signed_Transaction_Performative) _sym_db.RegisterMessage(CosmTradeMessage.Error_Performative) _sym_db.RegisterMessage(CosmTradeMessage.End_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _COSMTRADEMESSAGE._serialized_start = 52 _COSMTRADEMESSAGE._serialized_end = 918 _COSMTRADEMESSAGE_SIGNEDTRANSACTION._serialized_start = 472 _COSMTRADEMESSAGE_SIGNEDTRANSACTION._serialized_end = 519 _COSMTRADEMESSAGE_INFORM_PUBLIC_KEY_PERFORMATIVE._serialized_start = 521 _COSMTRADEMESSAGE_INFORM_PUBLIC_KEY_PERFORMATIVE._serialized_end = 573 _COSMTRADEMESSAGE_INFORM_SIGNED_TRANSACTION_PERFORMATIVE._serialized_start = 576 _COSMTRADEMESSAGE_INFORM_SIGNED_TRANSACTION_PERFORMATIVE._serialized_end = 770 _COSMTRADEMESSAGE_ERROR_PERFORMATIVE._serialized_start = 772 _COSMTRADEMESSAGE_ERROR_PERFORMATIVE._serialized_end = 882 _COSMTRADEMESSAGE_END_PERFORMATIVE._serialized_start = 884 _COSMTRADEMESSAGE_END_PERFORMATIVE._serialized_end = 902 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/cosm_trade/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from aea.helpers.transaction.base import SignedTransaction as BaseSignedTransaction SignedTransaction = BaseSignedTransaction ================================================ FILE: packages/fetchai/protocols/cosm_trade/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for cosm_trade dialogue management. - CosmTradeDialogue: The dialogue class maintains state of a dialogue and manages it. - CosmTradeDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.cosm_trade.message import CosmTradeMessage class CosmTradeDialogue(Dialogue): """The cosm_trade dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { CosmTradeMessage.Performative.INFORM_PUBLIC_KEY, CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {CosmTradeMessage.Performative.ERROR, CosmTradeMessage.Performative.END} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { CosmTradeMessage.Performative.END: frozenset(), CosmTradeMessage.Performative.ERROR: frozenset(), CosmTradeMessage.Performative.INFORM_PUBLIC_KEY: frozenset( { CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, CosmTradeMessage.Performative.ERROR, } ), CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION: frozenset( {CosmTradeMessage.Performative.ERROR, CosmTradeMessage.Performative.END} ), } class Role(Dialogue.Role): """This class defines the agent's role in a cosm_trade dialogue.""" AGENT = "agent" class EndState(Dialogue.EndState): """This class defines the end states of a cosm_trade dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[CosmTradeMessage] = CosmTradeMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class CosmTradeDialogues(Dialogues, ABC): """This class keeps track of all cosm_trade dialogues.""" END_STATES = frozenset( {CosmTradeDialogue.EndState.SUCCESSFUL, CosmTradeDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[CosmTradeDialogue] = CosmTradeDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=CosmTradeMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/cosm_trade/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains cosm_trade's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Optional, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.cosm_trade.custom_types import ( SignedTransaction as CustomSignedTransaction, ) _default_logger = logging.getLogger("aea.packages.fetchai.protocols.cosm_trade.message") DEFAULT_BODY_SIZE = 4 class CosmTradeMessage(Message): """A protocol for preparing an atomic swap bilateral transaction for cosmos-based ledgers, including fetchai's.""" protocol_id = PublicId.from_str("fetchai/cosm_trade:0.2.7") protocol_specification_id = PublicId.from_str("fetchai/cosm_trade:1.0.0") SignedTransaction = CustomSignedTransaction class Performative(Message.Performative): """Performatives for the cosm_trade protocol.""" END = "end" ERROR = "error" INFORM_PUBLIC_KEY = "inform_public_key" INFORM_SIGNED_TRANSACTION = "inform_signed_transaction" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"end", "error", "inform_public_key", "inform_signed_transaction"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "code", "data", "dialogue_reference", "fipa_dialogue_id", "message", "message_id", "performative", "public_key", "signed_transaction", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of CosmTradeMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=CosmTradeMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(CosmTradeMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def code(self) -> int: """Get the 'code' content from the message.""" enforce(self.is_set("code"), "'code' content is not set.") return cast(int, self.get("code")) @property def data(self) -> Optional[bytes]: """Get the 'data' content from the message.""" return cast(Optional[bytes], self.get("data")) @property def fipa_dialogue_id(self) -> Optional[Tuple[str, ...]]: """Get the 'fipa_dialogue_id' content from the message.""" return cast(Optional[Tuple[str, ...]], self.get("fipa_dialogue_id")) @property def message(self) -> Optional[str]: """Get the 'message' content from the message.""" return cast(Optional[str], self.get("message")) @property def public_key(self) -> str: """Get the 'public_key' content from the message.""" enforce(self.is_set("public_key"), "'public_key' content is not set.") return cast(str, self.get("public_key")) @property def signed_transaction(self) -> CustomSignedTransaction: """Get the 'signed_transaction' content from the message.""" enforce( self.is_set("signed_transaction"), "'signed_transaction' content is not set.", ) return cast(CustomSignedTransaction, self.get("signed_transaction")) def _is_consistent(self) -> bool: """Check that the message follows the cosm_trade protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, CosmTradeMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == CosmTradeMessage.Performative.INFORM_PUBLIC_KEY: expected_nb_of_contents = 1 enforce( isinstance(self.public_key, str), "Invalid type for content 'public_key'. Expected 'str'. Found '{}'.".format( type(self.public_key) ), ) elif ( self.performative == CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION ): expected_nb_of_contents = 1 enforce( isinstance(self.signed_transaction, CustomSignedTransaction), "Invalid type for content 'signed_transaction'. Expected 'SignedTransaction'. Found '{}'.".format( type(self.signed_transaction) ), ) if self.is_set("fipa_dialogue_id"): expected_nb_of_contents += 1 fipa_dialogue_id = cast(Tuple[str, ...], self.fipa_dialogue_id) enforce( isinstance(fipa_dialogue_id, tuple), "Invalid type for content 'fipa_dialogue_id'. Expected 'tuple'. Found '{}'.".format( type(fipa_dialogue_id) ), ) enforce( all(isinstance(element, str) for element in fipa_dialogue_id), "Invalid type for tuple elements in content 'fipa_dialogue_id'. Expected 'str'.", ) elif self.performative == CosmTradeMessage.Performative.ERROR: expected_nb_of_contents = 1 enforce( type(self.code) is int, "Invalid type for content 'code'. Expected 'int'. Found '{}'.".format( type(self.code) ), ) if self.is_set("message"): expected_nb_of_contents += 1 message = cast(str, self.message) enforce( isinstance(message, str), "Invalid type for content 'message'. Expected 'str'. Found '{}'.".format( type(message) ), ) if self.is_set("data"): expected_nb_of_contents += 1 data = cast(bytes, self.data) enforce( isinstance(data, bytes), "Invalid type for content 'data'. Expected 'bytes'. Found '{}'.".format( type(data) ), ) elif self.performative == CosmTradeMessage.Performative.END: expected_nb_of_contents = 0 # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/cosm_trade/protocol.yaml ================================================ name: cosm_trade author: fetchai version: 0.2.7 protocol_specification_id: fetchai/cosm_trade:1.0.0 type: protocol description: A protocol for preparing an atomic swap bilateral transaction for cosmos-based ledgers, including fetchai's. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmU2AsQPL4v5qPsPxh17pbNQCFV5qgqbXbsUe5kMJPCyum __init__.py: QmcBn4zeHdnsdkMKwbiWaKpPKELmzKSrv4aFPPSp3EgUqb cosm_trade.proto: QmWJZbNWJD8Qz1A1ju89wWoxFjMhC7RHn1pkeVPW3s3CPT cosm_trade_pb2.py: Qma3GnRA8g9t6RADbYyagW747CcDmuNUEqahpjHvsroTTx custom_types.py: QmZPHKmNPKwPGQu6cswi7MsfWGUHm8YG1XWowtnxVBstXS dialogues.py: QmThJHxUPEeiaVqNJz3QgNvJXAVxwxppM5VHK8TSEuPdxk message.py: QmdbUwDBj5v6DkcvjtHgHz6HPHjZndDTEQH12RwL4eQCR6 serialization.py: QmT2faXciRfykDBg3kXUvma2wjNgjHZ37nDfxiGD6mMtrB fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/cosm_trade/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for cosm_trade protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.cosm_trade import cosm_trade_pb2 from packages.fetchai.protocols.cosm_trade.custom_types import SignedTransaction from packages.fetchai.protocols.cosm_trade.message import CosmTradeMessage class CosmTradeSerializer(Serializer): """Serialization for the 'cosm_trade' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'CosmTrade' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(CosmTradeMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() cosm_trade_msg = cosm_trade_pb2.CosmTradeMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == CosmTradeMessage.Performative.INFORM_PUBLIC_KEY: performative = cosm_trade_pb2.CosmTradeMessage.Inform_Public_Key_Performative() # type: ignore public_key = msg.public_key performative.public_key = public_key cosm_trade_msg.inform_public_key.CopyFrom(performative) elif performative_id == CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION: performative = cosm_trade_pb2.CosmTradeMessage.Inform_Signed_Transaction_Performative() # type: ignore signed_transaction = msg.signed_transaction SignedTransaction.encode( performative.signed_transaction, signed_transaction ) if msg.is_set("fipa_dialogue_id"): performative.fipa_dialogue_id_is_set = True fipa_dialogue_id = msg.fipa_dialogue_id performative.fipa_dialogue_id.extend(fipa_dialogue_id) cosm_trade_msg.inform_signed_transaction.CopyFrom(performative) elif performative_id == CosmTradeMessage.Performative.ERROR: performative = cosm_trade_pb2.CosmTradeMessage.Error_Performative() # type: ignore code = msg.code performative.code = code if msg.is_set("message"): performative.message_is_set = True message = msg.message performative.message = message if msg.is_set("data"): performative.data_is_set = True data = msg.data performative.data = data cosm_trade_msg.error.CopyFrom(performative) elif performative_id == CosmTradeMessage.Performative.END: performative = cosm_trade_pb2.CosmTradeMessage.End_Performative() # type: ignore cosm_trade_msg.end.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = cosm_trade_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'CosmTrade' message. :param obj: the bytes object. :return: the 'CosmTrade' message. """ message_pb = ProtobufMessage() cosm_trade_pb = cosm_trade_pb2.CosmTradeMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target cosm_trade_pb.ParseFromString(message_pb.dialogue_message.content) performative = cosm_trade_pb.WhichOneof("performative") performative_id = CosmTradeMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == CosmTradeMessage.Performative.INFORM_PUBLIC_KEY: public_key = cosm_trade_pb.inform_public_key.public_key performative_content["public_key"] = public_key elif performative_id == CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION: pb2_signed_transaction = ( cosm_trade_pb.inform_signed_transaction.signed_transaction ) signed_transaction = SignedTransaction.decode(pb2_signed_transaction) performative_content["signed_transaction"] = signed_transaction if cosm_trade_pb.inform_signed_transaction.fipa_dialogue_id_is_set: fipa_dialogue_id = ( cosm_trade_pb.inform_signed_transaction.fipa_dialogue_id ) fipa_dialogue_id_tuple = tuple(fipa_dialogue_id) performative_content["fipa_dialogue_id"] = fipa_dialogue_id_tuple elif performative_id == CosmTradeMessage.Performative.ERROR: code = cosm_trade_pb.error.code performative_content["code"] = code if cosm_trade_pb.error.message_is_set: message = cosm_trade_pb.error.message performative_content["message"] = message if cosm_trade_pb.error.data_is_set: data = cosm_trade_pb.error.data performative_content["data"] = data elif performative_id == CosmTradeMessage.Performative.END: pass else: raise ValueError("Performative not valid: {}.".format(performative_id)) return CosmTradeMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/default/README.md ================================================ # Default Protocol ## Description This is a protocol for two agents exchanging any bytes messages. ## Specification ```yaml --- name: default author: fetchai version: 1.1.7 description: A protocol for exchanging any bytes message. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/default:1.0.0 speech_acts: bytes: content: pt:bytes error: error_code: ct:ErrorCode error_msg: pt:str error_data: pt:dict[pt:str, pt:bytes] end: {} ... --- ct:ErrorCode: | enum ErrorCodeEnum { UNSUPPORTED_PROTOCOL = 0; DECODING_ERROR = 1; INVALID_MESSAGE = 2; UNSUPPORTED_SKILL = 3; INVALID_DIALOGUE = 4; } ErrorCodeEnum error_code = 1; ... --- initiation: [bytes, error] reply: bytes: [bytes, error, end] error: [] end: [] termination: [end, error] roles: {agent} end_states: [successful, failed] keep_terminal_state_dialogues: true ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/default/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the default protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.default.serialization import DefaultSerializer DefaultMessage.serializer = DefaultSerializer ================================================ FILE: packages/fetchai/protocols/default/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from enum import Enum from typing import Any class ErrorCode(Enum): """This class represents an instance of ErrorCode.""" UNSUPPORTED_PROTOCOL = 0 DECODING_ERROR = 1 INVALID_MESSAGE = 2 UNSUPPORTED_SKILL = 3 INVALID_DIALOGUE = 4 @staticmethod def encode(error_code_protobuf_object: Any, error_code_object: "ErrorCode") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the error_code_protobuf_object argument is matched with the instance of this class in the 'error_code_object' argument. :param error_code_protobuf_object: the protocol buffer object whose type corresponds with this class. :param error_code_object: an instance of this class to be encoded in the protocol buffer object. """ error_code_protobuf_object.error_code = error_code_object.value @classmethod def decode(cls, error_code_protobuf_object: Any) -> "ErrorCode": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'error_code_protobuf_object' argument. :param error_code_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'error_code_protobuf_object' argument. """ enum_value_from_pb2 = error_code_protobuf_object.error_code return ErrorCode(enum_value_from_pb2) ================================================ FILE: packages/fetchai/protocols/default/default.proto ================================================ syntax = "proto3"; package aea.fetchai.default.v1_0_0; message DefaultMessage{ // Custom Types message ErrorCode{ enum ErrorCodeEnum { UNSUPPORTED_PROTOCOL = 0; DECODING_ERROR = 1; INVALID_MESSAGE = 2; UNSUPPORTED_SKILL = 3; INVALID_DIALOGUE = 4; } ErrorCodeEnum error_code = 1; } // Performatives and contents message Bytes_Performative{ bytes content = 1; } message Error_Performative{ ErrorCode error_code = 1; string error_msg = 2; map error_data = 3; } message End_Performative{ } oneof performative{ Bytes_Performative bytes = 5; End_Performative end = 6; Error_Performative error = 7; } } ================================================ FILE: packages/fetchai/protocols/default/default_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: default.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\rdefault.proto\x12\x1a\x61\x65\x61.fetchai.default.v1_0_0"\xb6\x06\n\x0e\x44\x65\x66\x61ultMessage\x12N\n\x05\x62ytes\x18\x05 \x01(\x0b\x32=.aea.fetchai.default.v1_0_0.DefaultMessage.Bytes_PerformativeH\x00\x12J\n\x03\x65nd\x18\x06 \x01(\x0b\x32;.aea.fetchai.default.v1_0_0.DefaultMessage.End_PerformativeH\x00\x12N\n\x05\x65rror\x18\x07 \x01(\x0b\x32=.aea.fetchai.default.v1_0_0.DefaultMessage.Error_PerformativeH\x00\x1a\xe4\x01\n\tErrorCode\x12V\n\nerror_code\x18\x01 \x01(\x0e\x32\x42.aea.fetchai.default.v1_0_0.DefaultMessage.ErrorCode.ErrorCodeEnum"\x7f\n\rErrorCodeEnum\x12\x18\n\x14UNSUPPORTED_PROTOCOL\x10\x00\x12\x12\n\x0e\x44\x45\x43ODING_ERROR\x10\x01\x12\x13\n\x0fINVALID_MESSAGE\x10\x02\x12\x15\n\x11UNSUPPORTED_SKILL\x10\x03\x12\x14\n\x10INVALID_DIALOGUE\x10\x04\x1a%\n\x12\x42ytes_Performative\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\x0c\x1a\x85\x02\n\x12\x45rror_Performative\x12H\n\nerror_code\x18\x01 \x01(\x0b\x32\x34.aea.fetchai.default.v1_0_0.DefaultMessage.ErrorCode\x12\x11\n\terror_msg\x18\x02 \x01(\t\x12`\n\nerror_data\x18\x03 \x03(\x0b\x32L.aea.fetchai.default.v1_0_0.DefaultMessage.Error_Performative.ErrorDataEntry\x1a\x30\n\x0e\x45rrorDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x12\n\x10\x45nd_PerformativeB\x0e\n\x0cperformativeb\x06proto3' ) _DEFAULTMESSAGE = DESCRIPTOR.message_types_by_name["DefaultMessage"] _DEFAULTMESSAGE_ERRORCODE = _DEFAULTMESSAGE.nested_types_by_name["ErrorCode"] _DEFAULTMESSAGE_BYTES_PERFORMATIVE = _DEFAULTMESSAGE.nested_types_by_name[ "Bytes_Performative" ] _DEFAULTMESSAGE_ERROR_PERFORMATIVE = _DEFAULTMESSAGE.nested_types_by_name[ "Error_Performative" ] _DEFAULTMESSAGE_ERROR_PERFORMATIVE_ERRORDATAENTRY = ( _DEFAULTMESSAGE_ERROR_PERFORMATIVE.nested_types_by_name["ErrorDataEntry"] ) _DEFAULTMESSAGE_END_PERFORMATIVE = _DEFAULTMESSAGE.nested_types_by_name[ "End_Performative" ] _DEFAULTMESSAGE_ERRORCODE_ERRORCODEENUM = _DEFAULTMESSAGE_ERRORCODE.enum_types_by_name[ "ErrorCodeEnum" ] DefaultMessage = _reflection.GeneratedProtocolMessageType( "DefaultMessage", (_message.Message,), { "ErrorCode": _reflection.GeneratedProtocolMessageType( "ErrorCode", (_message.Message,), { "DESCRIPTOR": _DEFAULTMESSAGE_ERRORCODE, "__module__": "default_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.default.v1_0_0.DefaultMessage.ErrorCode) }, ), "Bytes_Performative": _reflection.GeneratedProtocolMessageType( "Bytes_Performative", (_message.Message,), { "DESCRIPTOR": _DEFAULTMESSAGE_BYTES_PERFORMATIVE, "__module__": "default_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.default.v1_0_0.DefaultMessage.Bytes_Performative) }, ), "Error_Performative": _reflection.GeneratedProtocolMessageType( "Error_Performative", (_message.Message,), { "ErrorDataEntry": _reflection.GeneratedProtocolMessageType( "ErrorDataEntry", (_message.Message,), { "DESCRIPTOR": _DEFAULTMESSAGE_ERROR_PERFORMATIVE_ERRORDATAENTRY, "__module__": "default_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.default.v1_0_0.DefaultMessage.Error_Performative.ErrorDataEntry) }, ), "DESCRIPTOR": _DEFAULTMESSAGE_ERROR_PERFORMATIVE, "__module__": "default_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.default.v1_0_0.DefaultMessage.Error_Performative) }, ), "End_Performative": _reflection.GeneratedProtocolMessageType( "End_Performative", (_message.Message,), { "DESCRIPTOR": _DEFAULTMESSAGE_END_PERFORMATIVE, "__module__": "default_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.default.v1_0_0.DefaultMessage.End_Performative) }, ), "DESCRIPTOR": _DEFAULTMESSAGE, "__module__": "default_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.default.v1_0_0.DefaultMessage) }, ) _sym_db.RegisterMessage(DefaultMessage) _sym_db.RegisterMessage(DefaultMessage.ErrorCode) _sym_db.RegisterMessage(DefaultMessage.Bytes_Performative) _sym_db.RegisterMessage(DefaultMessage.Error_Performative) _sym_db.RegisterMessage(DefaultMessage.Error_Performative.ErrorDataEntry) _sym_db.RegisterMessage(DefaultMessage.End_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _DEFAULTMESSAGE_ERROR_PERFORMATIVE_ERRORDATAENTRY._options = None _DEFAULTMESSAGE_ERROR_PERFORMATIVE_ERRORDATAENTRY._serialized_options = b"8\001" _DEFAULTMESSAGE._serialized_start = 46 _DEFAULTMESSAGE._serialized_end = 868 _DEFAULTMESSAGE_ERRORCODE._serialized_start = 301 _DEFAULTMESSAGE_ERRORCODE._serialized_end = 529 _DEFAULTMESSAGE_ERRORCODE_ERRORCODEENUM._serialized_start = 402 _DEFAULTMESSAGE_ERRORCODE_ERRORCODEENUM._serialized_end = 529 _DEFAULTMESSAGE_BYTES_PERFORMATIVE._serialized_start = 531 _DEFAULTMESSAGE_BYTES_PERFORMATIVE._serialized_end = 568 _DEFAULTMESSAGE_ERROR_PERFORMATIVE._serialized_start = 571 _DEFAULTMESSAGE_ERROR_PERFORMATIVE._serialized_end = 832 _DEFAULTMESSAGE_ERROR_PERFORMATIVE_ERRORDATAENTRY._serialized_start = 784 _DEFAULTMESSAGE_ERROR_PERFORMATIVE_ERRORDATAENTRY._serialized_end = 832 _DEFAULTMESSAGE_END_PERFORMATIVE._serialized_start = 834 _DEFAULTMESSAGE_END_PERFORMATIVE._serialized_end = 852 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/default/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for default dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.default.message import DefaultMessage class DefaultDialogue(Dialogue): """The default dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {DefaultMessage.Performative.END, DefaultMessage.Performative.ERROR} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { DefaultMessage.Performative.BYTES: frozenset( { DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR, DefaultMessage.Performative.END, } ), DefaultMessage.Performative.END: frozenset(), DefaultMessage.Performative.ERROR: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a default dialogue.""" AGENT = "agent" class EndState(Dialogue.EndState): """This class defines the end states of a default dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[DefaultMessage] = DefaultMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class DefaultDialogues(Dialogues, ABC): """This class keeps track of all default dialogues.""" END_STATES = frozenset( {DefaultDialogue.EndState.SUCCESSFUL, DefaultDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = True def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[DefaultDialogue] = DefaultDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=DefaultMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/default/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains default's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.default.custom_types import ErrorCode as CustomErrorCode _default_logger = logging.getLogger("aea.packages.fetchai.protocols.default.message") DEFAULT_BODY_SIZE = 4 class DefaultMessage(Message): """A protocol for exchanging any bytes message.""" protocol_id = PublicId.from_str("fetchai/default:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/default:1.0.0") ErrorCode = CustomErrorCode class Performative(Message.Performative): """Performatives for the default protocol.""" BYTES = "bytes" END = "end" ERROR = "error" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"bytes", "end", "error"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "content", "dialogue_reference", "error_code", "error_data", "error_msg", "message_id", "performative", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of DefaultMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=DefaultMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(DefaultMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def content(self) -> bytes: """Get the 'content' content from the message.""" enforce(self.is_set("content"), "'content' content is not set.") return cast(bytes, self.get("content")) @property def error_code(self) -> CustomErrorCode: """Get the 'error_code' content from the message.""" enforce(self.is_set("error_code"), "'error_code' content is not set.") return cast(CustomErrorCode, self.get("error_code")) @property def error_data(self) -> Dict[str, bytes]: """Get the 'error_data' content from the message.""" enforce(self.is_set("error_data"), "'error_data' content is not set.") return cast(Dict[str, bytes], self.get("error_data")) @property def error_msg(self) -> str: """Get the 'error_msg' content from the message.""" enforce(self.is_set("error_msg"), "'error_msg' content is not set.") return cast(str, self.get("error_msg")) def _is_consistent(self) -> bool: """Check that the message follows the default protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, DefaultMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == DefaultMessage.Performative.BYTES: expected_nb_of_contents = 1 enforce( isinstance(self.content, bytes), "Invalid type for content 'content'. Expected 'bytes'. Found '{}'.".format( type(self.content) ), ) elif self.performative == DefaultMessage.Performative.ERROR: expected_nb_of_contents = 3 enforce( isinstance(self.error_code, CustomErrorCode), "Invalid type for content 'error_code'. Expected 'ErrorCode'. Found '{}'.".format( type(self.error_code) ), ) enforce( isinstance(self.error_msg, str), "Invalid type for content 'error_msg'. Expected 'str'. Found '{}'.".format( type(self.error_msg) ), ) enforce( isinstance(self.error_data, dict), "Invalid type for content 'error_data'. Expected 'dict'. Found '{}'.".format( type(self.error_data) ), ) for key_of_error_data, value_of_error_data in self.error_data.items(): enforce( isinstance(key_of_error_data, str), "Invalid type for dictionary keys in content 'error_data'. Expected 'str'. Found '{}'.".format( type(key_of_error_data) ), ) enforce( isinstance(value_of_error_data, bytes), "Invalid type for dictionary values in content 'error_data'. Expected 'bytes'. Found '{}'.".format( type(value_of_error_data) ), ) elif self.performative == DefaultMessage.Performative.END: expected_nb_of_contents = 0 # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/default/protocol.yaml ================================================ name: default author: fetchai version: 1.1.7 protocol_specification_id: fetchai/default:1.0.0 type: protocol description: A protocol for exchanging any bytes message. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qma4UhkBrAwxD1qdNwiMwTVYYQB7cBHd7UXJQ1SQWYi8Zs __init__.py: QmQgKC4pQPaoCXwfSkJimbqpyMN1qGUq7RfBAU2591yTap custom_types.py: QmQUFTE8WVj8V1PyrhuCvmPbTLoTuFWjiK97XJKavDQfyC default.proto: QmWYzTSHVbz7FBS84iKFMhGSXPxay2mss29vY7ufz2BFJ8 default_pb2.py: QmPX9tm18ddM5Q928JLd1HmdUZKp2ssKhCJzhZ53FJmjxM dialogues.py: QmPbCt78gFSSPbmBu87R6REMc2gD3JU9UWMkVNF9mP9A7x message.py: QmRL8PiNCoCMsHmrR4LnbphEtm4oEPR8AAhSgoADHV29D7 serialization.py: QmepsqanTZV4Wg3Dg9qDtSqwe5AyRS2BUgURndk1h9kjNt fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/default/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for default protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.default import default_pb2 from packages.fetchai.protocols.default.custom_types import ErrorCode from packages.fetchai.protocols.default.message import DefaultMessage class DefaultSerializer(Serializer): """Serialization for the 'default' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Default' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(DefaultMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() default_msg = default_pb2.DefaultMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == DefaultMessage.Performative.BYTES: performative = default_pb2.DefaultMessage.Bytes_Performative() # type: ignore content = msg.content performative.content = content default_msg.bytes.CopyFrom(performative) elif performative_id == DefaultMessage.Performative.ERROR: performative = default_pb2.DefaultMessage.Error_Performative() # type: ignore error_code = msg.error_code ErrorCode.encode(performative.error_code, error_code) error_msg = msg.error_msg performative.error_msg = error_msg error_data = msg.error_data performative.error_data.update(error_data) default_msg.error.CopyFrom(performative) elif performative_id == DefaultMessage.Performative.END: performative = default_pb2.DefaultMessage.End_Performative() # type: ignore default_msg.end.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = default_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Default' message. :param obj: the bytes object. :return: the 'Default' message. """ message_pb = ProtobufMessage() default_pb = default_pb2.DefaultMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target default_pb.ParseFromString(message_pb.dialogue_message.content) performative = default_pb.WhichOneof("performative") performative_id = DefaultMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == DefaultMessage.Performative.BYTES: content = default_pb.bytes.content performative_content["content"] = content elif performative_id == DefaultMessage.Performative.ERROR: pb2_error_code = default_pb.error.error_code error_code = ErrorCode.decode(pb2_error_code) performative_content["error_code"] = error_code error_msg = default_pb.error.error_msg performative_content["error_msg"] = error_msg error_data = default_pb.error.error_data error_data_dict = dict(error_data) performative_content["error_data"] = error_data_dict elif performative_id == DefaultMessage.Performative.END: pass else: raise ValueError("Performative not valid: {}.".format(performative_id)) return DefaultMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/fipa/README.md ================================================ # Fipa Protocol ## Description This is a protocol for two agents to negotiate over a fixed set of resources. ## Specification ```yaml --- name: fipa author: fetchai version: 1.1.7 description: A protocol for FIPA ACL. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/fipa:1.0.0 speech_acts: cfp: query: ct:Query propose: proposal: ct:Description accept_w_inform: info: pt:dict[pt:str, pt:str] match_accept_w_inform: info: pt:dict[pt:str, pt:str] inform: info: pt:dict[pt:str, pt:str] accept: {} decline: {} match_accept: {} end: {} ... --- ct:Query: | bytes query_bytes = 1; ct:Description: | bytes description_bytes = 1; ... --- initiation: [cfp] reply: cfp: [propose, decline] propose: [accept, accept_w_inform, decline, propose] accept: [decline, match_accept, match_accept_w_inform] accept_w_inform: [decline, match_accept, match_accept_w_inform] decline: [] match_accept: [inform, end] match_accept_w_inform: [inform, end] inform: [inform, end] end: [] termination: [decline, end] roles: {seller, buyer} end_states: [successful, declined_cfp, declined_propose, declined_accept] keep_terminal_state_dialogues: true ... ``` ## Links - FIPA Foundation ================================================ FILE: packages/fetchai/protocols/fipa/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the fipa protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.fipa.serialization import FipaSerializer FipaMessage.serializer = FipaSerializer ================================================ FILE: packages/fetchai/protocols/fipa/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from aea.helpers.search.models import Description as BaseDescription from aea.helpers.search.models import Query as BaseQuery Description = BaseDescription Query = BaseQuery ================================================ FILE: packages/fetchai/protocols/fipa/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for fipa dialogue management. - FipaDialogue: The dialogue class maintains state of a dialogue and manages it. - FipaDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.fipa.message import FipaMessage class FipaDialogue(Dialogue): """The fipa dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {FipaMessage.Performative.CFP} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {FipaMessage.Performative.DECLINE, FipaMessage.Performative.END} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { FipaMessage.Performative.ACCEPT: frozenset( { FipaMessage.Performative.DECLINE, FipaMessage.Performative.MATCH_ACCEPT, FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, } ), FipaMessage.Performative.ACCEPT_W_INFORM: frozenset( { FipaMessage.Performative.DECLINE, FipaMessage.Performative.MATCH_ACCEPT, FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, } ), FipaMessage.Performative.CFP: frozenset( {FipaMessage.Performative.PROPOSE, FipaMessage.Performative.DECLINE} ), FipaMessage.Performative.DECLINE: frozenset(), FipaMessage.Performative.END: frozenset(), FipaMessage.Performative.INFORM: frozenset( {FipaMessage.Performative.INFORM, FipaMessage.Performative.END} ), FipaMessage.Performative.MATCH_ACCEPT: frozenset( {FipaMessage.Performative.INFORM, FipaMessage.Performative.END} ), FipaMessage.Performative.MATCH_ACCEPT_W_INFORM: frozenset( {FipaMessage.Performative.INFORM, FipaMessage.Performative.END} ), FipaMessage.Performative.PROPOSE: frozenset( { FipaMessage.Performative.ACCEPT, FipaMessage.Performative.ACCEPT_W_INFORM, FipaMessage.Performative.DECLINE, FipaMessage.Performative.PROPOSE, } ), } class Role(Dialogue.Role): """This class defines the agent's role in a fipa dialogue.""" BUYER = "buyer" SELLER = "seller" class EndState(Dialogue.EndState): """This class defines the end states of a fipa dialogue.""" SUCCESSFUL = 0 DECLINED_CFP = 1 DECLINED_PROPOSE = 2 DECLINED_ACCEPT = 3 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class FipaDialogues(Dialogues, ABC): """This class keeps track of all fipa dialogues.""" END_STATES = frozenset( { FipaDialogue.EndState.SUCCESSFUL, FipaDialogue.EndState.DECLINED_CFP, FipaDialogue.EndState.DECLINED_PROPOSE, FipaDialogue.EndState.DECLINED_ACCEPT, } ) _keep_terminal_state_dialogues = True def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[FipaDialogue] = FipaDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=FipaMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/fipa/fipa.proto ================================================ syntax = "proto3"; package aea.fetchai.fipa.v1_0_0; message FipaMessage{ // Custom Types message Description{ bytes description_bytes = 1; } message Query{ bytes query_bytes = 1; } // Performatives and contents message Cfp_Performative{ Query query = 1; } message Propose_Performative{ Description proposal = 1; } message Accept_W_Inform_Performative{ map info = 1; } message Match_Accept_W_Inform_Performative{ map info = 1; } message Inform_Performative{ map info = 1; } message Accept_Performative{ } message Decline_Performative{ } message Match_Accept_Performative{ } message End_Performative{ } oneof performative{ Accept_Performative accept = 5; Accept_W_Inform_Performative accept_w_inform = 6; Cfp_Performative cfp = 7; Decline_Performative decline = 8; End_Performative end = 9; Inform_Performative inform = 10; Match_Accept_Performative match_accept = 11; Match_Accept_W_Inform_Performative match_accept_w_inform = 12; Propose_Performative propose = 13; } } ================================================ FILE: packages/fetchai/protocols/fipa/fipa_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: fipa.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\nfipa.proto\x12\x17\x61\x65\x61.fetchai.fipa.v1_0_0"\xc5\x0c\n\x0b\x46ipaMessage\x12J\n\x06\x61\x63\x63\x65pt\x18\x05 \x01(\x0b\x32\x38.aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_PerformativeH\x00\x12\\\n\x0f\x61\x63\x63\x65pt_w_inform\x18\x06 \x01(\x0b\x32\x41.aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_PerformativeH\x00\x12\x44\n\x03\x63\x66p\x18\x07 \x01(\x0b\x32\x35.aea.fetchai.fipa.v1_0_0.FipaMessage.Cfp_PerformativeH\x00\x12L\n\x07\x64\x65\x63line\x18\x08 \x01(\x0b\x32\x39.aea.fetchai.fipa.v1_0_0.FipaMessage.Decline_PerformativeH\x00\x12\x44\n\x03\x65nd\x18\t \x01(\x0b\x32\x35.aea.fetchai.fipa.v1_0_0.FipaMessage.End_PerformativeH\x00\x12J\n\x06inform\x18\n \x01(\x0b\x32\x38.aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_PerformativeH\x00\x12V\n\x0cmatch_accept\x18\x0b \x01(\x0b\x32>.aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_PerformativeH\x00\x12h\n\x15match_accept_w_inform\x18\x0c \x01(\x0b\x32G.aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_PerformativeH\x00\x12L\n\x07propose\x18\r \x01(\x0b\x32\x39.aea.fetchai.fipa.v1_0_0.FipaMessage.Propose_PerformativeH\x00\x1a(\n\x0b\x44\x65scription\x12\x19\n\x11\x64\x65scription_bytes\x18\x01 \x01(\x0c\x1a\x1c\n\x05Query\x12\x13\n\x0bquery_bytes\x18\x01 \x01(\x0c\x1aM\n\x10\x43\x66p_Performative\x12\x39\n\x05query\x18\x01 \x01(\x0b\x32*.aea.fetchai.fipa.v1_0_0.FipaMessage.Query\x1aZ\n\x14Propose_Performative\x12\x42\n\x08proposal\x18\x01 \x01(\x0b\x32\x30.aea.fetchai.fipa.v1_0_0.FipaMessage.Description\x1a\xa6\x01\n\x1c\x41\x63\x63\x65pt_W_Inform_Performative\x12Y\n\x04info\x18\x01 \x03(\x0b\x32K.aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative.InfoEntry\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xb2\x01\n"Match_Accept_W_Inform_Performative\x12_\n\x04info\x18\x01 \x03(\x0b\x32Q.aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative.InfoEntry\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x94\x01\n\x13Inform_Performative\x12P\n\x04info\x18\x01 \x03(\x0b\x32\x42.aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative.InfoEntry\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x15\n\x13\x41\x63\x63\x65pt_Performative\x1a\x16\n\x14\x44\x65\x63line_Performative\x1a\x1b\n\x19Match_Accept_Performative\x1a\x12\n\x10\x45nd_PerformativeB\x0e\n\x0cperformativeb\x06proto3' ) _FIPAMESSAGE = DESCRIPTOR.message_types_by_name["FipaMessage"] _FIPAMESSAGE_DESCRIPTION = _FIPAMESSAGE.nested_types_by_name["Description"] _FIPAMESSAGE_QUERY = _FIPAMESSAGE.nested_types_by_name["Query"] _FIPAMESSAGE_CFP_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name["Cfp_Performative"] _FIPAMESSAGE_PROPOSE_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name[ "Propose_Performative" ] _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name[ "Accept_W_Inform_Performative" ] _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY = ( _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name[ "Match_Accept_W_Inform_Performative" ] _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY = ( _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) _FIPAMESSAGE_INFORM_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name[ "Inform_Performative" ] _FIPAMESSAGE_INFORM_PERFORMATIVE_INFOENTRY = ( _FIPAMESSAGE_INFORM_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) _FIPAMESSAGE_ACCEPT_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name[ "Accept_Performative" ] _FIPAMESSAGE_DECLINE_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name[ "Decline_Performative" ] _FIPAMESSAGE_MATCH_ACCEPT_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name[ "Match_Accept_Performative" ] _FIPAMESSAGE_END_PERFORMATIVE = _FIPAMESSAGE.nested_types_by_name["End_Performative"] FipaMessage = _reflection.GeneratedProtocolMessageType( "FipaMessage", (_message.Message,), { "Description": _reflection.GeneratedProtocolMessageType( "Description", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_DESCRIPTION, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Description) }, ), "Query": _reflection.GeneratedProtocolMessageType( "Query", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_QUERY, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Query) }, ), "Cfp_Performative": _reflection.GeneratedProtocolMessageType( "Cfp_Performative", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_CFP_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Cfp_Performative) }, ), "Propose_Performative": _reflection.GeneratedProtocolMessageType( "Propose_Performative", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_PROPOSE_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Propose_Performative) }, ), "Accept_W_Inform_Performative": _reflection.GeneratedProtocolMessageType( "Accept_W_Inform_Performative", (_message.Message,), { "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative.InfoEntry) }, ), "DESCRIPTOR": _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_W_Inform_Performative) }, ), "Match_Accept_W_Inform_Performative": _reflection.GeneratedProtocolMessageType( "Match_Accept_W_Inform_Performative", (_message.Message,), { "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative.InfoEntry) }, ), "DESCRIPTOR": _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_W_Inform_Performative) }, ), "Inform_Performative": _reflection.GeneratedProtocolMessageType( "Inform_Performative", (_message.Message,), { "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_INFORM_PERFORMATIVE_INFOENTRY, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative.InfoEntry) }, ), "DESCRIPTOR": _FIPAMESSAGE_INFORM_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Inform_Performative) }, ), "Accept_Performative": _reflection.GeneratedProtocolMessageType( "Accept_Performative", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_ACCEPT_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Accept_Performative) }, ), "Decline_Performative": _reflection.GeneratedProtocolMessageType( "Decline_Performative", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_DECLINE_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Decline_Performative) }, ), "Match_Accept_Performative": _reflection.GeneratedProtocolMessageType( "Match_Accept_Performative", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_MATCH_ACCEPT_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.Match_Accept_Performative) }, ), "End_Performative": _reflection.GeneratedProtocolMessageType( "End_Performative", (_message.Message,), { "DESCRIPTOR": _FIPAMESSAGE_END_PERFORMATIVE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage.End_Performative) }, ), "DESCRIPTOR": _FIPAMESSAGE, "__module__": "fipa_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.fipa.v1_0_0.FipaMessage) }, ) _sym_db.RegisterMessage(FipaMessage) _sym_db.RegisterMessage(FipaMessage.Description) _sym_db.RegisterMessage(FipaMessage.Query) _sym_db.RegisterMessage(FipaMessage.Cfp_Performative) _sym_db.RegisterMessage(FipaMessage.Propose_Performative) _sym_db.RegisterMessage(FipaMessage.Accept_W_Inform_Performative) _sym_db.RegisterMessage(FipaMessage.Accept_W_Inform_Performative.InfoEntry) _sym_db.RegisterMessage(FipaMessage.Match_Accept_W_Inform_Performative) _sym_db.RegisterMessage(FipaMessage.Match_Accept_W_Inform_Performative.InfoEntry) _sym_db.RegisterMessage(FipaMessage.Inform_Performative) _sym_db.RegisterMessage(FipaMessage.Inform_Performative.InfoEntry) _sym_db.RegisterMessage(FipaMessage.Accept_Performative) _sym_db.RegisterMessage(FipaMessage.Decline_Performative) _sym_db.RegisterMessage(FipaMessage.Match_Accept_Performative) _sym_db.RegisterMessage(FipaMessage.End_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._options = None _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._serialized_options = b"8\001" _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._options = None _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._serialized_options = ( b"8\001" ) _FIPAMESSAGE_INFORM_PERFORMATIVE_INFOENTRY._options = None _FIPAMESSAGE_INFORM_PERFORMATIVE_INFOENTRY._serialized_options = b"8\001" _FIPAMESSAGE._serialized_start = 40 _FIPAMESSAGE._serialized_end = 1645 _FIPAMESSAGE_DESCRIPTION._serialized_start = 791 _FIPAMESSAGE_DESCRIPTION._serialized_end = 831 _FIPAMESSAGE_QUERY._serialized_start = 833 _FIPAMESSAGE_QUERY._serialized_end = 861 _FIPAMESSAGE_CFP_PERFORMATIVE._serialized_start = 863 _FIPAMESSAGE_CFP_PERFORMATIVE._serialized_end = 940 _FIPAMESSAGE_PROPOSE_PERFORMATIVE._serialized_start = 942 _FIPAMESSAGE_PROPOSE_PERFORMATIVE._serialized_end = 1032 _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE._serialized_start = 1035 _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE._serialized_end = 1201 _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._serialized_start = 1158 _FIPAMESSAGE_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._serialized_end = 1201 _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE._serialized_start = 1204 _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE._serialized_end = 1382 _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._serialized_start = 1158 _FIPAMESSAGE_MATCH_ACCEPT_W_INFORM_PERFORMATIVE_INFOENTRY._serialized_end = 1201 _FIPAMESSAGE_INFORM_PERFORMATIVE._serialized_start = 1385 _FIPAMESSAGE_INFORM_PERFORMATIVE._serialized_end = 1533 _FIPAMESSAGE_INFORM_PERFORMATIVE_INFOENTRY._serialized_start = 1158 _FIPAMESSAGE_INFORM_PERFORMATIVE_INFOENTRY._serialized_end = 1201 _FIPAMESSAGE_ACCEPT_PERFORMATIVE._serialized_start = 1535 _FIPAMESSAGE_ACCEPT_PERFORMATIVE._serialized_end = 1556 _FIPAMESSAGE_DECLINE_PERFORMATIVE._serialized_start = 1558 _FIPAMESSAGE_DECLINE_PERFORMATIVE._serialized_end = 1580 _FIPAMESSAGE_MATCH_ACCEPT_PERFORMATIVE._serialized_start = 1582 _FIPAMESSAGE_MATCH_ACCEPT_PERFORMATIVE._serialized_end = 1609 _FIPAMESSAGE_END_PERFORMATIVE._serialized_start = 1611 _FIPAMESSAGE_END_PERFORMATIVE._serialized_end = 1629 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/fipa/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains fipa's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.fipa.custom_types import ( Description as CustomDescription, ) from packages.fetchai.protocols.fipa.custom_types import Query as CustomQuery _default_logger = logging.getLogger("aea.packages.fetchai.protocols.fipa.message") DEFAULT_BODY_SIZE = 4 class FipaMessage(Message): """A protocol for FIPA ACL.""" protocol_id = PublicId.from_str("fetchai/fipa:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/fipa:1.0.0") Description = CustomDescription Query = CustomQuery class Performative(Message.Performative): """Performatives for the fipa protocol.""" ACCEPT = "accept" ACCEPT_W_INFORM = "accept_w_inform" CFP = "cfp" DECLINE = "decline" END = "end" INFORM = "inform" MATCH_ACCEPT = "match_accept" MATCH_ACCEPT_W_INFORM = "match_accept_w_inform" PROPOSE = "propose" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "accept", "accept_w_inform", "cfp", "decline", "end", "inform", "match_accept", "match_accept_w_inform", "propose", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "dialogue_reference", "info", "message_id", "performative", "proposal", "query", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of FipaMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=FipaMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(FipaMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def info(self) -> Dict[str, str]: """Get the 'info' content from the message.""" enforce(self.is_set("info"), "'info' content is not set.") return cast(Dict[str, str], self.get("info")) @property def proposal(self) -> CustomDescription: """Get the 'proposal' content from the message.""" enforce(self.is_set("proposal"), "'proposal' content is not set.") return cast(CustomDescription, self.get("proposal")) @property def query(self) -> CustomQuery: """Get the 'query' content from the message.""" enforce(self.is_set("query"), "'query' content is not set.") return cast(CustomQuery, self.get("query")) def _is_consistent(self) -> bool: """Check that the message follows the fipa protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, FipaMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == FipaMessage.Performative.CFP: expected_nb_of_contents = 1 enforce( isinstance(self.query, CustomQuery), "Invalid type for content 'query'. Expected 'Query'. Found '{}'.".format( type(self.query) ), ) elif self.performative == FipaMessage.Performative.PROPOSE: expected_nb_of_contents = 1 enforce( isinstance(self.proposal, CustomDescription), "Invalid type for content 'proposal'. Expected 'Description'. Found '{}'.".format( type(self.proposal) ), ) elif self.performative == FipaMessage.Performative.ACCEPT_W_INFORM: expected_nb_of_contents = 1 enforce( isinstance(self.info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(self.info) ), ) for key_of_info, value_of_info in self.info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) elif self.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM: expected_nb_of_contents = 1 enforce( isinstance(self.info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(self.info) ), ) for key_of_info, value_of_info in self.info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) elif self.performative == FipaMessage.Performative.INFORM: expected_nb_of_contents = 1 enforce( isinstance(self.info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(self.info) ), ) for key_of_info, value_of_info in self.info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) elif self.performative == FipaMessage.Performative.ACCEPT: expected_nb_of_contents = 0 elif self.performative == FipaMessage.Performative.DECLINE: expected_nb_of_contents = 0 elif self.performative == FipaMessage.Performative.MATCH_ACCEPT: expected_nb_of_contents = 0 elif self.performative == FipaMessage.Performative.END: expected_nb_of_contents = 0 # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/fipa/protocol.yaml ================================================ name: fipa author: fetchai version: 1.1.7 protocol_specification_id: fetchai/fipa:1.0.0 type: protocol description: A protocol for FIPA ACL. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qmb3nuPdF326cWYMVYJjqxdpGMkdR4ENFdCXbMEv9t7Doj __init__.py: QmcKvopHYdkJwfnAYmgtPbUZZvcnP3bffhKjFWMXJFwno9 custom_types.py: QmNibpgi18PbM4drwbYxw18GwMkBAToh21EhKimnF3j7Sg dialogues.py: QmS2o9G3AyW7n7R6Ec9n3KSSntMx5MrGARD3Ya67FDAa2s fipa.proto: QmS7aXZ2JoG3oyMHWiPYoP9RJ7iChsoTC9KQLsj6vi3ejR fipa_pb2.py: QmT6CxDiwyz3ucsNxZSxtNZXE9NThshV68zvXEYtiWjEUP message.py: QmZKYP3yu7KRRxJsip9FKbuuW91FUwnDuVsFTaxqeuccuT serialization.py: QmUHydPjLVt5B4JAoN3XccNieB3EFiUoMzvs2N9UQmZXJZ fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/fipa/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for fipa protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.fipa import fipa_pb2 from packages.fetchai.protocols.fipa.custom_types import Description, Query from packages.fetchai.protocols.fipa.message import FipaMessage class FipaSerializer(Serializer): """Serialization for the 'fipa' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Fipa' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(FipaMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() fipa_msg = fipa_pb2.FipaMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == FipaMessage.Performative.CFP: performative = fipa_pb2.FipaMessage.Cfp_Performative() # type: ignore query = msg.query Query.encode(performative.query, query) fipa_msg.cfp.CopyFrom(performative) elif performative_id == FipaMessage.Performative.PROPOSE: performative = fipa_pb2.FipaMessage.Propose_Performative() # type: ignore proposal = msg.proposal Description.encode(performative.proposal, proposal) fipa_msg.propose.CopyFrom(performative) elif performative_id == FipaMessage.Performative.ACCEPT_W_INFORM: performative = fipa_pb2.FipaMessage.Accept_W_Inform_Performative() # type: ignore info = msg.info performative.info.update(info) fipa_msg.accept_w_inform.CopyFrom(performative) elif performative_id == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM: performative = fipa_pb2.FipaMessage.Match_Accept_W_Inform_Performative() # type: ignore info = msg.info performative.info.update(info) fipa_msg.match_accept_w_inform.CopyFrom(performative) elif performative_id == FipaMessage.Performative.INFORM: performative = fipa_pb2.FipaMessage.Inform_Performative() # type: ignore info = msg.info performative.info.update(info) fipa_msg.inform.CopyFrom(performative) elif performative_id == FipaMessage.Performative.ACCEPT: performative = fipa_pb2.FipaMessage.Accept_Performative() # type: ignore fipa_msg.accept.CopyFrom(performative) elif performative_id == FipaMessage.Performative.DECLINE: performative = fipa_pb2.FipaMessage.Decline_Performative() # type: ignore fipa_msg.decline.CopyFrom(performative) elif performative_id == FipaMessage.Performative.MATCH_ACCEPT: performative = fipa_pb2.FipaMessage.Match_Accept_Performative() # type: ignore fipa_msg.match_accept.CopyFrom(performative) elif performative_id == FipaMessage.Performative.END: performative = fipa_pb2.FipaMessage.End_Performative() # type: ignore fipa_msg.end.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = fipa_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Fipa' message. :param obj: the bytes object. :return: the 'Fipa' message. """ message_pb = ProtobufMessage() fipa_pb = fipa_pb2.FipaMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target fipa_pb.ParseFromString(message_pb.dialogue_message.content) performative = fipa_pb.WhichOneof("performative") performative_id = FipaMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == FipaMessage.Performative.CFP: pb2_query = fipa_pb.cfp.query query = Query.decode(pb2_query) performative_content["query"] = query elif performative_id == FipaMessage.Performative.PROPOSE: pb2_proposal = fipa_pb.propose.proposal proposal = Description.decode(pb2_proposal) performative_content["proposal"] = proposal elif performative_id == FipaMessage.Performative.ACCEPT_W_INFORM: info = fipa_pb.accept_w_inform.info info_dict = dict(info) performative_content["info"] = info_dict elif performative_id == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM: info = fipa_pb.match_accept_w_inform.info info_dict = dict(info) performative_content["info"] = info_dict elif performative_id == FipaMessage.Performative.INFORM: info = fipa_pb.inform.info info_dict = dict(info) performative_content["info"] = info_dict elif performative_id == FipaMessage.Performative.ACCEPT: pass elif performative_id == FipaMessage.Performative.DECLINE: pass elif performative_id == FipaMessage.Performative.MATCH_ACCEPT: pass elif performative_id == FipaMessage.Performative.END: pass else: raise ValueError("Performative not valid: {}.".format(performative_id)) return FipaMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/gym/README.md ================================================ # Gym Protocol ## Description This is a protocol for interacting with a gym connection. ## Specification ```yaml --- name: gym author: fetchai version: 1.1.7 description: A protocol for interacting with a gym connection. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/gym:1.0.0 speech_acts: act: action: ct:AnyObject step_id: pt:int percept: step_id: pt:int observation: ct:AnyObject reward: pt:float done: pt:bool info: ct:AnyObject status: content: pt:dict[pt:str, pt:str] reset: {} close: {} ... --- ct:AnyObject: | bytes any = 1; ... --- initiation: [reset] reply: reset: [status] status: [act, close, reset] act: [percept] percept: [act, close, reset] close: [] termination: [close] roles: {agent, environment} end_states: [successful] keep_terminal_state_dialogues: false ... ``` ## Links - OpenAI Gym ================================================ FILE: packages/fetchai/protocols/gym/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the gym protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.protocols.gym.serialization import GymSerializer GymMessage.serializer = GymSerializer ================================================ FILE: packages/fetchai/protocols/gym/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" import pickle # nosec from typing import Any class AnyObject: """This class represents an instance of AnyObject.""" __slots__ = ("any",) def __init__(self, _any: Any): """Initialise an instance of AnyObject.""" self.any = _any @staticmethod def encode(any_object_protobuf_object: Any, any_object_object: "AnyObject") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the any_object_protobuf_object argument is matched with the instance of this class in the 'any_object_object' argument. :param any_object_protobuf_object: the protocol buffer object whose type corresponds with this class. :param any_object_object: an instance of this class to be encoded in the protocol buffer object. """ any_object_protobuf_object.any = pickle.dumps(any_object_object) # nosec @classmethod def decode(cls, any_object_protobuf_object: Any) -> "AnyObject": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'any_object_protobuf_object' argument. :param any_object_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'any_object_protobuf_object' argument. """ return pickle.loads(any_object_protobuf_object.any) # nosec def __eq__(self, other: Any) -> bool: """Check equality.""" return self.any == other.any ================================================ FILE: packages/fetchai/protocols/gym/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for gym dialogue management. - GymDialogue: The dialogue class maintains state of a dialogue and manages it. - GymDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.gym.message import GymMessage class GymDialogue(Dialogue): """The gym dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {GymMessage.Performative.RESET} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {GymMessage.Performative.CLOSE} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { GymMessage.Performative.ACT: frozenset({GymMessage.Performative.PERCEPT}), GymMessage.Performative.CLOSE: frozenset(), GymMessage.Performative.PERCEPT: frozenset( { GymMessage.Performative.ACT, GymMessage.Performative.CLOSE, GymMessage.Performative.RESET, } ), GymMessage.Performative.RESET: frozenset({GymMessage.Performative.STATUS}), GymMessage.Performative.STATUS: frozenset( { GymMessage.Performative.ACT, GymMessage.Performative.CLOSE, GymMessage.Performative.RESET, } ), } class Role(Dialogue.Role): """This class defines the agent's role in a gym dialogue.""" AGENT = "agent" ENVIRONMENT = "environment" class EndState(Dialogue.EndState): """This class defines the end states of a gym dialogue.""" SUCCESSFUL = 0 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[GymMessage] = GymMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class GymDialogues(Dialogues, ABC): """This class keeps track of all gym dialogues.""" END_STATES = frozenset({GymDialogue.EndState.SUCCESSFUL}) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[GymDialogue] = GymDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=GymMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/gym/gym.proto ================================================ syntax = "proto3"; package aea.fetchai.gym.v1_0_0; message GymMessage{ // Custom Types message AnyObject{ bytes any = 1; } // Performatives and contents message Act_Performative{ AnyObject action = 1; int64 step_id = 2; } message Percept_Performative{ int64 step_id = 1; AnyObject observation = 2; float reward = 3; bool done = 4; AnyObject info = 5; } message Status_Performative{ map content = 1; } message Reset_Performative{ } message Close_Performative{ } oneof performative{ Act_Performative act = 5; Close_Performative close = 6; Percept_Performative percept = 7; Reset_Performative reset = 8; Status_Performative status = 9; } } ================================================ FILE: packages/fetchai/protocols/gym/gym_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: gym.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\tgym.proto\x12\x16\x61\x65\x61.fetchai.gym.v1_0_0"\x94\x07\n\nGymMessage\x12\x42\n\x03\x61\x63t\x18\x05 \x01(\x0b\x32\x33.aea.fetchai.gym.v1_0_0.GymMessage.Act_PerformativeH\x00\x12\x46\n\x05\x63lose\x18\x06 \x01(\x0b\x32\x35.aea.fetchai.gym.v1_0_0.GymMessage.Close_PerformativeH\x00\x12J\n\x07percept\x18\x07 \x01(\x0b\x32\x37.aea.fetchai.gym.v1_0_0.GymMessage.Percept_PerformativeH\x00\x12\x46\n\x05reset\x18\x08 \x01(\x0b\x32\x35.aea.fetchai.gym.v1_0_0.GymMessage.Reset_PerformativeH\x00\x12H\n\x06status\x18\t \x01(\x0b\x32\x36.aea.fetchai.gym.v1_0_0.GymMessage.Status_PerformativeH\x00\x1a\x18\n\tAnyObject\x12\x0b\n\x03\x61ny\x18\x01 \x01(\x0c\x1a\x61\n\x10\x41\x63t_Performative\x12<\n\x06\x61\x63tion\x18\x01 \x01(\x0b\x32,.aea.fetchai.gym.v1_0_0.GymMessage.AnyObject\x12\x0f\n\x07step_id\x18\x02 \x01(\x03\x1a\xc4\x01\n\x14Percept_Performative\x12\x0f\n\x07step_id\x18\x01 \x01(\x03\x12\x41\n\x0bobservation\x18\x02 \x01(\x0b\x32,.aea.fetchai.gym.v1_0_0.GymMessage.AnyObject\x12\x0e\n\x06reward\x18\x03 \x01(\x02\x12\x0c\n\x04\x64one\x18\x04 \x01(\x08\x12:\n\x04info\x18\x05 \x01(\x0b\x32,.aea.fetchai.gym.v1_0_0.GymMessage.AnyObject\x1a\x9b\x01\n\x13Status_Performative\x12T\n\x07\x63ontent\x18\x01 \x03(\x0b\x32\x43.aea.fetchai.gym.v1_0_0.GymMessage.Status_Performative.ContentEntry\x1a.\n\x0c\x43ontentEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x14\n\x12Reset_Performative\x1a\x14\n\x12\x43lose_PerformativeB\x0e\n\x0cperformativeb\x06proto3' ) _GYMMESSAGE = DESCRIPTOR.message_types_by_name["GymMessage"] _GYMMESSAGE_ANYOBJECT = _GYMMESSAGE.nested_types_by_name["AnyObject"] _GYMMESSAGE_ACT_PERFORMATIVE = _GYMMESSAGE.nested_types_by_name["Act_Performative"] _GYMMESSAGE_PERCEPT_PERFORMATIVE = _GYMMESSAGE.nested_types_by_name[ "Percept_Performative" ] _GYMMESSAGE_STATUS_PERFORMATIVE = _GYMMESSAGE.nested_types_by_name[ "Status_Performative" ] _GYMMESSAGE_STATUS_PERFORMATIVE_CONTENTENTRY = ( _GYMMESSAGE_STATUS_PERFORMATIVE.nested_types_by_name["ContentEntry"] ) _GYMMESSAGE_RESET_PERFORMATIVE = _GYMMESSAGE.nested_types_by_name["Reset_Performative"] _GYMMESSAGE_CLOSE_PERFORMATIVE = _GYMMESSAGE.nested_types_by_name["Close_Performative"] GymMessage = _reflection.GeneratedProtocolMessageType( "GymMessage", (_message.Message,), { "AnyObject": _reflection.GeneratedProtocolMessageType( "AnyObject", (_message.Message,), { "DESCRIPTOR": _GYMMESSAGE_ANYOBJECT, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage.AnyObject) }, ), "Act_Performative": _reflection.GeneratedProtocolMessageType( "Act_Performative", (_message.Message,), { "DESCRIPTOR": _GYMMESSAGE_ACT_PERFORMATIVE, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage.Act_Performative) }, ), "Percept_Performative": _reflection.GeneratedProtocolMessageType( "Percept_Performative", (_message.Message,), { "DESCRIPTOR": _GYMMESSAGE_PERCEPT_PERFORMATIVE, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage.Percept_Performative) }, ), "Status_Performative": _reflection.GeneratedProtocolMessageType( "Status_Performative", (_message.Message,), { "ContentEntry": _reflection.GeneratedProtocolMessageType( "ContentEntry", (_message.Message,), { "DESCRIPTOR": _GYMMESSAGE_STATUS_PERFORMATIVE_CONTENTENTRY, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage.Status_Performative.ContentEntry) }, ), "DESCRIPTOR": _GYMMESSAGE_STATUS_PERFORMATIVE, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage.Status_Performative) }, ), "Reset_Performative": _reflection.GeneratedProtocolMessageType( "Reset_Performative", (_message.Message,), { "DESCRIPTOR": _GYMMESSAGE_RESET_PERFORMATIVE, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage.Reset_Performative) }, ), "Close_Performative": _reflection.GeneratedProtocolMessageType( "Close_Performative", (_message.Message,), { "DESCRIPTOR": _GYMMESSAGE_CLOSE_PERFORMATIVE, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage.Close_Performative) }, ), "DESCRIPTOR": _GYMMESSAGE, "__module__": "gym_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.gym.v1_0_0.GymMessage) }, ) _sym_db.RegisterMessage(GymMessage) _sym_db.RegisterMessage(GymMessage.AnyObject) _sym_db.RegisterMessage(GymMessage.Act_Performative) _sym_db.RegisterMessage(GymMessage.Percept_Performative) _sym_db.RegisterMessage(GymMessage.Status_Performative) _sym_db.RegisterMessage(GymMessage.Status_Performative.ContentEntry) _sym_db.RegisterMessage(GymMessage.Reset_Performative) _sym_db.RegisterMessage(GymMessage.Close_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _GYMMESSAGE_STATUS_PERFORMATIVE_CONTENTENTRY._options = None _GYMMESSAGE_STATUS_PERFORMATIVE_CONTENTENTRY._serialized_options = b"8\001" _GYMMESSAGE._serialized_start = 38 _GYMMESSAGE._serialized_end = 954 _GYMMESSAGE_ANYOBJECT._serialized_start = 414 _GYMMESSAGE_ANYOBJECT._serialized_end = 438 _GYMMESSAGE_ACT_PERFORMATIVE._serialized_start = 440 _GYMMESSAGE_ACT_PERFORMATIVE._serialized_end = 537 _GYMMESSAGE_PERCEPT_PERFORMATIVE._serialized_start = 540 _GYMMESSAGE_PERCEPT_PERFORMATIVE._serialized_end = 736 _GYMMESSAGE_STATUS_PERFORMATIVE._serialized_start = 739 _GYMMESSAGE_STATUS_PERFORMATIVE._serialized_end = 894 _GYMMESSAGE_STATUS_PERFORMATIVE_CONTENTENTRY._serialized_start = 848 _GYMMESSAGE_STATUS_PERFORMATIVE_CONTENTENTRY._serialized_end = 894 _GYMMESSAGE_RESET_PERFORMATIVE._serialized_start = 896 _GYMMESSAGE_RESET_PERFORMATIVE._serialized_end = 916 _GYMMESSAGE_CLOSE_PERFORMATIVE._serialized_start = 918 _GYMMESSAGE_CLOSE_PERFORMATIVE._serialized_end = 938 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/gym/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains gym's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.gym.custom_types import AnyObject as CustomAnyObject _default_logger = logging.getLogger("aea.packages.fetchai.protocols.gym.message") DEFAULT_BODY_SIZE = 4 class GymMessage(Message): """A protocol for interacting with a gym connection.""" protocol_id = PublicId.from_str("fetchai/gym:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/gym:1.0.0") AnyObject = CustomAnyObject class Performative(Message.Performative): """Performatives for the gym protocol.""" ACT = "act" CLOSE = "close" PERCEPT = "percept" RESET = "reset" STATUS = "status" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"act", "close", "percept", "reset", "status"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "action", "content", "dialogue_reference", "done", "info", "message_id", "observation", "performative", "reward", "step_id", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of GymMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=GymMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(GymMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def action(self) -> CustomAnyObject: """Get the 'action' content from the message.""" enforce(self.is_set("action"), "'action' content is not set.") return cast(CustomAnyObject, self.get("action")) @property def content(self) -> Dict[str, str]: """Get the 'content' content from the message.""" enforce(self.is_set("content"), "'content' content is not set.") return cast(Dict[str, str], self.get("content")) @property def done(self) -> bool: """Get the 'done' content from the message.""" enforce(self.is_set("done"), "'done' content is not set.") return cast(bool, self.get("done")) @property def info(self) -> CustomAnyObject: """Get the 'info' content from the message.""" enforce(self.is_set("info"), "'info' content is not set.") return cast(CustomAnyObject, self.get("info")) @property def observation(self) -> CustomAnyObject: """Get the 'observation' content from the message.""" enforce(self.is_set("observation"), "'observation' content is not set.") return cast(CustomAnyObject, self.get("observation")) @property def reward(self) -> float: """Get the 'reward' content from the message.""" enforce(self.is_set("reward"), "'reward' content is not set.") return cast(float, self.get("reward")) @property def step_id(self) -> int: """Get the 'step_id' content from the message.""" enforce(self.is_set("step_id"), "'step_id' content is not set.") return cast(int, self.get("step_id")) def _is_consistent(self) -> bool: """Check that the message follows the gym protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, GymMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == GymMessage.Performative.ACT: expected_nb_of_contents = 2 enforce( isinstance(self.action, CustomAnyObject), "Invalid type for content 'action'. Expected 'AnyObject'. Found '{}'.".format( type(self.action) ), ) enforce( type(self.step_id) is int, "Invalid type for content 'step_id'. Expected 'int'. Found '{}'.".format( type(self.step_id) ), ) elif self.performative == GymMessage.Performative.PERCEPT: expected_nb_of_contents = 5 enforce( type(self.step_id) is int, "Invalid type for content 'step_id'. Expected 'int'. Found '{}'.".format( type(self.step_id) ), ) enforce( isinstance(self.observation, CustomAnyObject), "Invalid type for content 'observation'. Expected 'AnyObject'. Found '{}'.".format( type(self.observation) ), ) enforce( isinstance(self.reward, float), "Invalid type for content 'reward'. Expected 'float'. Found '{}'.".format( type(self.reward) ), ) enforce( isinstance(self.done, bool), "Invalid type for content 'done'. Expected 'bool'. Found '{}'.".format( type(self.done) ), ) enforce( isinstance(self.info, CustomAnyObject), "Invalid type for content 'info'. Expected 'AnyObject'. Found '{}'.".format( type(self.info) ), ) elif self.performative == GymMessage.Performative.STATUS: expected_nb_of_contents = 1 enforce( isinstance(self.content, dict), "Invalid type for content 'content'. Expected 'dict'. Found '{}'.".format( type(self.content) ), ) for key_of_content, value_of_content in self.content.items(): enforce( isinstance(key_of_content, str), "Invalid type for dictionary keys in content 'content'. Expected 'str'. Found '{}'.".format( type(key_of_content) ), ) enforce( isinstance(value_of_content, str), "Invalid type for dictionary values in content 'content'. Expected 'str'. Found '{}'.".format( type(value_of_content) ), ) elif self.performative == GymMessage.Performative.RESET: expected_nb_of_contents = 0 elif self.performative == GymMessage.Performative.CLOSE: expected_nb_of_contents = 0 # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/gym/protocol.yaml ================================================ name: gym author: fetchai version: 1.1.7 protocol_specification_id: fetchai/gym:1.0.0 type: protocol description: A protocol for interacting with a gym connection. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmVpaTeAZL4HN37449d3KAPMQw9uY1gH1Apm9XDhZkfGmE __init__.py: QmVpAS5VMF6ABFdnmthK6fbcuaLLEx94hebtVgaosTePTU custom_types.py: QmX11ojHWTmPcEVWk8fzDiHdKwDQDccTLPYx8KoPiKiPPS dialogues.py: QmTjYmokn1ayZYPSosarwJMpTPU4jAHfy4rftDgzcvNGLb gym.proto: QmdCRYrHpG1AGzGfGAisbDZEJA2gdgJvhivtHqttTsQeYE gym_pb2.py: QmXhaUyFbsLoKbHBK83SR6a9zvAGAsvWGu7tA2BTTyw26W message.py: QmcCwF8uJpPACDTzcNS5SmgJV86uudxfNpvk4bHHoSvSqS serialization.py: QmYf3hsqiLZ9NQcoSXaVypD11NPUFczXw9zi1EinRzv7hV fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/gym/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for gym protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.gym import gym_pb2 from packages.fetchai.protocols.gym.custom_types import AnyObject from packages.fetchai.protocols.gym.message import GymMessage class GymSerializer(Serializer): """Serialization for the 'gym' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Gym' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(GymMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() gym_msg = gym_pb2.GymMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == GymMessage.Performative.ACT: performative = gym_pb2.GymMessage.Act_Performative() # type: ignore action = msg.action AnyObject.encode(performative.action, action) step_id = msg.step_id performative.step_id = step_id gym_msg.act.CopyFrom(performative) elif performative_id == GymMessage.Performative.PERCEPT: performative = gym_pb2.GymMessage.Percept_Performative() # type: ignore step_id = msg.step_id performative.step_id = step_id observation = msg.observation AnyObject.encode(performative.observation, observation) reward = msg.reward performative.reward = reward done = msg.done performative.done = done info = msg.info AnyObject.encode(performative.info, info) gym_msg.percept.CopyFrom(performative) elif performative_id == GymMessage.Performative.STATUS: performative = gym_pb2.GymMessage.Status_Performative() # type: ignore content = msg.content performative.content.update(content) gym_msg.status.CopyFrom(performative) elif performative_id == GymMessage.Performative.RESET: performative = gym_pb2.GymMessage.Reset_Performative() # type: ignore gym_msg.reset.CopyFrom(performative) elif performative_id == GymMessage.Performative.CLOSE: performative = gym_pb2.GymMessage.Close_Performative() # type: ignore gym_msg.close.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = gym_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Gym' message. :param obj: the bytes object. :return: the 'Gym' message. """ message_pb = ProtobufMessage() gym_pb = gym_pb2.GymMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target gym_pb.ParseFromString(message_pb.dialogue_message.content) performative = gym_pb.WhichOneof("performative") performative_id = GymMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == GymMessage.Performative.ACT: pb2_action = gym_pb.act.action action = AnyObject.decode(pb2_action) performative_content["action"] = action step_id = gym_pb.act.step_id performative_content["step_id"] = step_id elif performative_id == GymMessage.Performative.PERCEPT: step_id = gym_pb.percept.step_id performative_content["step_id"] = step_id pb2_observation = gym_pb.percept.observation observation = AnyObject.decode(pb2_observation) performative_content["observation"] = observation reward = gym_pb.percept.reward performative_content["reward"] = reward done = gym_pb.percept.done performative_content["done"] = done pb2_info = gym_pb.percept.info info = AnyObject.decode(pb2_info) performative_content["info"] = info elif performative_id == GymMessage.Performative.STATUS: content = gym_pb.status.content content_dict = dict(content) performative_content["content"] = content_dict elif performative_id == GymMessage.Performative.RESET: pass elif performative_id == GymMessage.Performative.CLOSE: pass else: raise ValueError("Performative not valid: {}.".format(performative_id)) return GymMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/http/README.md ================================================ # HTTP Protocol ## Description This is a protocol for interacting with a client/server via HTTP requests and responses. ## Specification ```yaml --- name: http author: fetchai version: 1.1.7 description: A protocol for HTTP requests and responses. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/http:1.0.0 speech_acts: request: method: pt:str url: pt:str version: pt:str headers: pt:str body: pt:bytes response: version: pt:str status_code: pt:int status_text: pt:str headers: pt:str body: pt:bytes ... --- initiation: [request] reply: request: [response] response: [] termination: [response] roles: {client, server} end_states: [successful] keep_terminal_state_dialogues: false ... ``` ## Links - HTTP Specification ================================================ FILE: packages/fetchai/protocols/http/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the http protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.http.serialization import HttpSerializer HttpMessage.serializer = HttpSerializer ================================================ FILE: packages/fetchai/protocols/http/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for http dialogue management. - HttpDialogue: The dialogue class maintains state of a dialogue and manages it. - HttpDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.http.message import HttpMessage class HttpDialogue(Dialogue): """The http dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {HttpMessage.Performative.REQUEST} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {HttpMessage.Performative.RESPONSE} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { HttpMessage.Performative.REQUEST: frozenset( {HttpMessage.Performative.RESPONSE} ), HttpMessage.Performative.RESPONSE: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a http dialogue.""" CLIENT = "client" SERVER = "server" class EndState(Dialogue.EndState): """This class defines the end states of a http dialogue.""" SUCCESSFUL = 0 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[HttpMessage] = HttpMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class HttpDialogues(Dialogues, ABC): """This class keeps track of all http dialogues.""" END_STATES = frozenset({HttpDialogue.EndState.SUCCESSFUL}) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[HttpDialogue] = HttpDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=HttpMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/http/http.proto ================================================ syntax = "proto3"; package aea.fetchai.http.v1_0_0; message HttpMessage{ // Performatives and contents message Request_Performative{ string method = 1; string url = 2; string version = 3; string headers = 4; bytes body = 5; } message Response_Performative{ string version = 1; int64 status_code = 2; string status_text = 3; string headers = 4; bytes body = 5; } oneof performative{ Request_Performative request = 5; Response_Performative response = 6; } } ================================================ FILE: packages/fetchai/protocols/http/http_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: http.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\nhttp.proto\x12\x17\x61\x65\x61.fetchai.http.v1_0_0"\x93\x03\n\x0bHttpMessage\x12L\n\x07request\x18\x05 \x01(\x0b\x32\x39.aea.fetchai.http.v1_0_0.HttpMessage.Request_PerformativeH\x00\x12N\n\x08response\x18\x06 \x01(\x0b\x32:.aea.fetchai.http.v1_0_0.HttpMessage.Response_PerformativeH\x00\x1a\x63\n\x14Request_Performative\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x0f\n\x07headers\x18\x04 \x01(\t\x12\x0c\n\x04\x62ody\x18\x05 \x01(\x0c\x1aq\n\x15Response_Performative\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x13\n\x0bstatus_code\x18\x02 \x01(\x03\x12\x13\n\x0bstatus_text\x18\x03 \x01(\t\x12\x0f\n\x07headers\x18\x04 \x01(\t\x12\x0c\n\x04\x62ody\x18\x05 \x01(\x0c\x42\x0e\n\x0cperformativeb\x06proto3' ) _HTTPMESSAGE = DESCRIPTOR.message_types_by_name["HttpMessage"] _HTTPMESSAGE_REQUEST_PERFORMATIVE = _HTTPMESSAGE.nested_types_by_name[ "Request_Performative" ] _HTTPMESSAGE_RESPONSE_PERFORMATIVE = _HTTPMESSAGE.nested_types_by_name[ "Response_Performative" ] HttpMessage = _reflection.GeneratedProtocolMessageType( "HttpMessage", (_message.Message,), { "Request_Performative": _reflection.GeneratedProtocolMessageType( "Request_Performative", (_message.Message,), { "DESCRIPTOR": _HTTPMESSAGE_REQUEST_PERFORMATIVE, "__module__": "http_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.http.v1_0_0.HttpMessage.Request_Performative) }, ), "Response_Performative": _reflection.GeneratedProtocolMessageType( "Response_Performative", (_message.Message,), { "DESCRIPTOR": _HTTPMESSAGE_RESPONSE_PERFORMATIVE, "__module__": "http_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.http.v1_0_0.HttpMessage.Response_Performative) }, ), "DESCRIPTOR": _HTTPMESSAGE, "__module__": "http_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.http.v1_0_0.HttpMessage) }, ) _sym_db.RegisterMessage(HttpMessage) _sym_db.RegisterMessage(HttpMessage.Request_Performative) _sym_db.RegisterMessage(HttpMessage.Response_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _HTTPMESSAGE._serialized_start = 40 _HTTPMESSAGE._serialized_end = 443 _HTTPMESSAGE_REQUEST_PERFORMATIVE._serialized_start = 213 _HTTPMESSAGE_REQUEST_PERFORMATIVE._serialized_end = 312 _HTTPMESSAGE_RESPONSE_PERFORMATIVE._serialized_start = 314 _HTTPMESSAGE_RESPONSE_PERFORMATIVE._serialized_end = 427 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/http/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains http's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message _default_logger = logging.getLogger("aea.packages.fetchai.protocols.http.message") DEFAULT_BODY_SIZE = 4 class HttpMessage(Message): """A protocol for HTTP requests and responses.""" protocol_id = PublicId.from_str("fetchai/http:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/http:1.0.0") class Performative(Message.Performative): """Performatives for the http protocol.""" REQUEST = "request" RESPONSE = "response" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"request", "response"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "body", "dialogue_reference", "headers", "message_id", "method", "performative", "status_code", "status_text", "target", "url", "version", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of HttpMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=HttpMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(HttpMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def body(self) -> bytes: """Get the 'body' content from the message.""" enforce(self.is_set("body"), "'body' content is not set.") return cast(bytes, self.get("body")) @property def headers(self) -> str: """Get the 'headers' content from the message.""" enforce(self.is_set("headers"), "'headers' content is not set.") return cast(str, self.get("headers")) @property def method(self) -> str: """Get the 'method' content from the message.""" enforce(self.is_set("method"), "'method' content is not set.") return cast(str, self.get("method")) @property def status_code(self) -> int: """Get the 'status_code' content from the message.""" enforce(self.is_set("status_code"), "'status_code' content is not set.") return cast(int, self.get("status_code")) @property def status_text(self) -> str: """Get the 'status_text' content from the message.""" enforce(self.is_set("status_text"), "'status_text' content is not set.") return cast(str, self.get("status_text")) @property def url(self) -> str: """Get the 'url' content from the message.""" enforce(self.is_set("url"), "'url' content is not set.") return cast(str, self.get("url")) @property def version(self) -> str: """Get the 'version' content from the message.""" enforce(self.is_set("version"), "'version' content is not set.") return cast(str, self.get("version")) def _is_consistent(self) -> bool: """Check that the message follows the http protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, HttpMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == HttpMessage.Performative.REQUEST: expected_nb_of_contents = 5 enforce( isinstance(self.method, str), "Invalid type for content 'method'. Expected 'str'. Found '{}'.".format( type(self.method) ), ) enforce( isinstance(self.url, str), "Invalid type for content 'url'. Expected 'str'. Found '{}'.".format( type(self.url) ), ) enforce( isinstance(self.version, str), "Invalid type for content 'version'. Expected 'str'. Found '{}'.".format( type(self.version) ), ) enforce( isinstance(self.headers, str), "Invalid type for content 'headers'. Expected 'str'. Found '{}'.".format( type(self.headers) ), ) enforce( isinstance(self.body, bytes), "Invalid type for content 'body'. Expected 'bytes'. Found '{}'.".format( type(self.body) ), ) elif self.performative == HttpMessage.Performative.RESPONSE: expected_nb_of_contents = 5 enforce( isinstance(self.version, str), "Invalid type for content 'version'. Expected 'str'. Found '{}'.".format( type(self.version) ), ) enforce( type(self.status_code) is int, "Invalid type for content 'status_code'. Expected 'int'. Found '{}'.".format( type(self.status_code) ), ) enforce( isinstance(self.status_text, str), "Invalid type for content 'status_text'. Expected 'str'. Found '{}'.".format( type(self.status_text) ), ) enforce( isinstance(self.headers, str), "Invalid type for content 'headers'. Expected 'str'. Found '{}'.".format( type(self.headers) ), ) enforce( isinstance(self.body, bytes), "Invalid type for content 'body'. Expected 'bytes'. Found '{}'.".format( type(self.body) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/http/protocol.yaml ================================================ name: http author: fetchai version: 1.1.7 protocol_specification_id: fetchai/http:1.0.0 type: protocol description: A protocol for HTTP requests and responses. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmWqoWa2LrKVLYdmURDriMkUYSUxoWXL1wgatFshQMUzha __init__.py: QmPXrzMrmAGjAqvjXEKC9WvdrVgng9viYX8WuEJYDcfVCv dialogues.py: QmUcjazcTHwvVMQk2vPjsJQjscbm5mE1mKBHYVUrNJbTsR http.proto: Qmag9uQYVPQwsdZfH1GEaBX5xgikoYuphQpXnWP2xob6Ys http_pb2.py: QmPck55KUSn1KfGQ3jGTq6eh2Fhh6Kdn5HPotrpFJeJ8u3 message.py: Qmf545etJNhTDu4YoL3EcNFqrB6WCrXGFvZN2WLJozB3Dk serialization.py: QmaPKtCLFJSV6Jxtft5k4vz2CuuG5eWz8GsMSbTtdrFxyx fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/http/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for http protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.http import http_pb2 from packages.fetchai.protocols.http.message import HttpMessage class HttpSerializer(Serializer): """Serialization for the 'http' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Http' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(HttpMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() http_msg = http_pb2.HttpMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == HttpMessage.Performative.REQUEST: performative = http_pb2.HttpMessage.Request_Performative() # type: ignore method = msg.method performative.method = method url = msg.url performative.url = url version = msg.version performative.version = version headers = msg.headers performative.headers = headers body = msg.body performative.body = body http_msg.request.CopyFrom(performative) elif performative_id == HttpMessage.Performative.RESPONSE: performative = http_pb2.HttpMessage.Response_Performative() # type: ignore version = msg.version performative.version = version status_code = msg.status_code performative.status_code = status_code status_text = msg.status_text performative.status_text = status_text headers = msg.headers performative.headers = headers body = msg.body performative.body = body http_msg.response.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = http_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Http' message. :param obj: the bytes object. :return: the 'Http' message. """ message_pb = ProtobufMessage() http_pb = http_pb2.HttpMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target http_pb.ParseFromString(message_pb.dialogue_message.content) performative = http_pb.WhichOneof("performative") performative_id = HttpMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == HttpMessage.Performative.REQUEST: method = http_pb.request.method performative_content["method"] = method url = http_pb.request.url performative_content["url"] = url version = http_pb.request.version performative_content["version"] = version headers = http_pb.request.headers performative_content["headers"] = headers body = http_pb.request.body performative_content["body"] = body elif performative_id == HttpMessage.Performative.RESPONSE: version = http_pb.response.version performative_content["version"] = version status_code = http_pb.response.status_code performative_content["status_code"] = status_code status_text = http_pb.response.status_text performative_content["status_text"] = status_text headers = http_pb.response.headers performative_content["headers"] = headers body = http_pb.response.body performative_content["body"] = body else: raise ValueError("Performative not valid: {}.".format(performative_id)) return HttpMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/ledger_api/README.md ================================================ # Ledger API Protocol ## Description This is a protocol for interacting with ledger APIs. ## Specification ```yaml --- name: ledger_api author: fetchai version: 1.1.7 description: A protocol for ledger APIs requests and responses. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/ledger_api:1.0.0 speech_acts: get_balance: ledger_id: pt:str address: pt:str get_raw_transaction: terms: ct:Terms send_signed_transaction: signed_transaction: ct:SignedTransaction get_transaction_receipt: transaction_digest: ct:TransactionDigest balance: ledger_id: pt:str balance: pt:int raw_transaction: raw_transaction: ct:RawTransaction transaction_digest: transaction_digest: ct:TransactionDigest transaction_receipt: transaction_receipt: ct:TransactionReceipt get_state: ledger_id: pt:str callable: pt:str args: pt:list[pt:str] kwargs: ct:Kwargs state: ledger_id: pt:str state: ct:State error: code: pt:int message: pt:optional[pt:str] data: pt:optional[pt:bytes] ... --- ct:Terms: | bytes terms = 1; ct:Kwargs: | bytes kwargs = 1; ct:State: | bytes state = 1; ct:SignedTransaction: | bytes signed_transaction = 1; ct:RawTransaction: | bytes raw_transaction = 1; ct:TransactionDigest: | bytes transaction_digest = 1; ct:TransactionReceipt: | bytes transaction_receipt = 1; ... --- initiation: [get_balance, get_state, get_raw_transaction, send_signed_transaction, get_transaction_receipt] reply: get_balance: [balance, error] balance: [] get_state: [state, error] state: [] get_raw_transaction: [raw_transaction, error] raw_transaction: [send_signed_transaction] send_signed_transaction: [transaction_digest, error] transaction_digest: [get_transaction_receipt] get_transaction_receipt: [transaction_receipt, error] transaction_receipt: [] error: [] termination: [balance, state, transaction_receipt, error] roles: {agent, ledger} end_states: [successful] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/ledger_api/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the ledger_api protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ledger_api.serialization import LedgerApiSerializer LedgerApiMessage.serializer = LedgerApiSerializer ================================================ FILE: packages/fetchai/protocols/ledger_api/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from typing import Any from aea.common import JSONLike from aea.exceptions import enforce from aea.helpers.serializers import DictProtobufStructSerializer from aea.helpers.transaction.base import RawTransaction as BaseRawTransaction from aea.helpers.transaction.base import SignedTransaction as BaseSignedTransaction from aea.helpers.transaction.base import State as BaseState from aea.helpers.transaction.base import Terms as BaseTerms from aea.helpers.transaction.base import TransactionDigest as BaseTransactionDigest from aea.helpers.transaction.base import TransactionReceipt as BaseTransactionReceipt RawTransaction = BaseRawTransaction SignedTransaction = BaseSignedTransaction State = BaseState Terms = BaseTerms TransactionDigest = BaseTransactionDigest TransactionReceipt = BaseTransactionReceipt class Kwargs: """This class represents an instance of Kwargs.""" __slots__ = ("_body",) def __init__( self, body: JSONLike, ): """Initialise an instance of RawTransaction.""" self._body = body self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" enforce( isinstance(self._body, dict) and all(isinstance(key, str) for key in self._body.keys()), "Body must be dict and keys must be str.", ) @property def body(self) -> JSONLike: """Get the body.""" return self._body @staticmethod def encode(kwargs_protobuf_object: Any, kwargs_object: "Kwargs") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the kwargs_protobuf_object argument is matched with the instance of this class in the 'kwargs_object' argument. :param kwargs_protobuf_object: the protocol buffer object whose type corresponds with this class. :param kwargs_object: an instance of this class to be encoded in the protocol buffer object. """ kwargs_protobuf_object.kwargs = DictProtobufStructSerializer.encode( kwargs_object.body ) @classmethod def decode(cls, kwargs_protobuf_object: Any) -> "Kwargs": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'kwargs_protobuf_object' argument. :param kwargs_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'kwargs_protobuf_object' argument. """ kwargs = DictProtobufStructSerializer.decode(kwargs_protobuf_object.kwargs) return cls(kwargs) def __eq__(self, other: Any) -> bool: """Check equality.""" return isinstance(other, Kwargs) and self.body == other.body def __str__(self) -> str: """Get string representation.""" return "Kwargs: body={}".format(self.body) ================================================ FILE: packages/fetchai/protocols/ledger_api/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for ledger_api dialogue management. - LedgerApiDialogue: The dialogue class maintains state of a dialogue and manages it. - LedgerApiDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage class LedgerApiDialogue(Dialogue): """The ledger_api dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { LedgerApiMessage.Performative.GET_BALANCE, LedgerApiMessage.Performative.GET_STATE, LedgerApiMessage.Performative.GET_RAW_TRANSACTION, LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { LedgerApiMessage.Performative.BALANCE, LedgerApiMessage.Performative.STATE, LedgerApiMessage.Performative.TRANSACTION_RECEIPT, LedgerApiMessage.Performative.ERROR, } ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { LedgerApiMessage.Performative.BALANCE: frozenset(), LedgerApiMessage.Performative.ERROR: frozenset(), LedgerApiMessage.Performative.GET_BALANCE: frozenset( {LedgerApiMessage.Performative.BALANCE, LedgerApiMessage.Performative.ERROR} ), LedgerApiMessage.Performative.GET_RAW_TRANSACTION: frozenset( { LedgerApiMessage.Performative.RAW_TRANSACTION, LedgerApiMessage.Performative.ERROR, } ), LedgerApiMessage.Performative.GET_STATE: frozenset( {LedgerApiMessage.Performative.STATE, LedgerApiMessage.Performative.ERROR} ), LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT: frozenset( { LedgerApiMessage.Performative.TRANSACTION_RECEIPT, LedgerApiMessage.Performative.ERROR, } ), LedgerApiMessage.Performative.RAW_TRANSACTION: frozenset( {LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION} ), LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION: frozenset( { LedgerApiMessage.Performative.TRANSACTION_DIGEST, LedgerApiMessage.Performative.ERROR, } ), LedgerApiMessage.Performative.STATE: frozenset(), LedgerApiMessage.Performative.TRANSACTION_DIGEST: frozenset( {LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT} ), LedgerApiMessage.Performative.TRANSACTION_RECEIPT: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a ledger_api dialogue.""" AGENT = "agent" LEDGER = "ledger" class EndState(Dialogue.EndState): """This class defines the end states of a ledger_api dialogue.""" SUCCESSFUL = 0 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class LedgerApiDialogues(Dialogues, ABC): """This class keeps track of all ledger_api dialogues.""" END_STATES = frozenset({LedgerApiDialogue.EndState.SUCCESSFUL}) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[LedgerApiDialogue] = LedgerApiDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=LedgerApiMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/ledger_api/ledger_api.proto ================================================ syntax = "proto3"; package aea.fetchai.ledger_api.v1_0_0; message LedgerApiMessage{ // Custom Types message Kwargs{ bytes kwargs = 1; } message RawTransaction{ bytes raw_transaction = 1; } message SignedTransaction{ bytes signed_transaction = 1; } message State{ bytes state = 1; } message Terms{ bytes terms = 1; } message TransactionDigest{ bytes transaction_digest = 1; } message TransactionReceipt{ bytes transaction_receipt = 1; } // Performatives and contents message Get_Balance_Performative{ string ledger_id = 1; string address = 2; } message Get_Raw_Transaction_Performative{ Terms terms = 1; } message Send_Signed_Transaction_Performative{ SignedTransaction signed_transaction = 1; } message Get_Transaction_Receipt_Performative{ TransactionDigest transaction_digest = 1; } message Balance_Performative{ string ledger_id = 1; int64 balance = 2; } message Raw_Transaction_Performative{ RawTransaction raw_transaction = 1; } message Transaction_Digest_Performative{ TransactionDigest transaction_digest = 1; } message Transaction_Receipt_Performative{ TransactionReceipt transaction_receipt = 1; } message Get_State_Performative{ string ledger_id = 1; string callable = 2; repeated string args = 3; Kwargs kwargs = 4; } message State_Performative{ string ledger_id = 1; State state = 2; } message Error_Performative{ int64 code = 1; string message = 2; bool message_is_set = 3; bytes data = 4; bool data_is_set = 5; } oneof performative{ Balance_Performative balance = 5; Error_Performative error = 6; Get_Balance_Performative get_balance = 7; Get_Raw_Transaction_Performative get_raw_transaction = 8; Get_State_Performative get_state = 9; Get_Transaction_Receipt_Performative get_transaction_receipt = 10; Raw_Transaction_Performative raw_transaction = 11; Send_Signed_Transaction_Performative send_signed_transaction = 12; State_Performative state = 13; Transaction_Digest_Performative transaction_digest = 14; Transaction_Receipt_Performative transaction_receipt = 15; } } ================================================ FILE: packages/fetchai/protocols/ledger_api/ledger_api_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: ledger_api.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x10ledger_api.proto\x12\x1d\x61\x65\x61.fetchai.ledger_api.v1_0_0"\x86\x15\n\x10LedgerApiMessage\x12W\n\x07\x62\x61lance\x18\x05 \x01(\x0b\x32\x44.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Balance_PerformativeH\x00\x12S\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x42.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Error_PerformativeH\x00\x12_\n\x0bget_balance\x18\x07 \x01(\x0b\x32H.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_Balance_PerformativeH\x00\x12o\n\x13get_raw_transaction\x18\x08 \x01(\x0b\x32P.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_Raw_Transaction_PerformativeH\x00\x12[\n\tget_state\x18\t \x01(\x0b\x32\x46.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_State_PerformativeH\x00\x12w\n\x17get_transaction_receipt\x18\n \x01(\x0b\x32T.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_Transaction_Receipt_PerformativeH\x00\x12g\n\x0fraw_transaction\x18\x0b \x01(\x0b\x32L.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Raw_Transaction_PerformativeH\x00\x12w\n\x17send_signed_transaction\x18\x0c \x01(\x0b\x32T.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Send_Signed_Transaction_PerformativeH\x00\x12S\n\x05state\x18\r \x01(\x0b\x32\x42.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.State_PerformativeH\x00\x12m\n\x12transaction_digest\x18\x0e \x01(\x0b\x32O.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Transaction_Digest_PerformativeH\x00\x12o\n\x13transaction_receipt\x18\x0f \x01(\x0b\x32P.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Transaction_Receipt_PerformativeH\x00\x1a\x18\n\x06Kwargs\x12\x0e\n\x06kwargs\x18\x01 \x01(\x0c\x1a)\n\x0eRawTransaction\x12\x17\n\x0fraw_transaction\x18\x01 \x01(\x0c\x1a/\n\x11SignedTransaction\x12\x1a\n\x12signed_transaction\x18\x01 \x01(\x0c\x1a\x16\n\x05State\x12\r\n\x05state\x18\x01 \x01(\x0c\x1a\x16\n\x05Terms\x12\r\n\x05terms\x18\x01 \x01(\x0c\x1a/\n\x11TransactionDigest\x12\x1a\n\x12transaction_digest\x18\x01 \x01(\x0c\x1a\x31\n\x12TransactionReceipt\x12\x1b\n\x13transaction_receipt\x18\x01 \x01(\x0c\x1a>\n\x18Get_Balance_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\x1ah\n Get_Raw_Transaction_Performative\x12\x44\n\x05terms\x18\x01 \x01(\x0b\x32\x35.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Terms\x1a\x85\x01\n$Send_Signed_Transaction_Performative\x12]\n\x12signed_transaction\x18\x01 \x01(\x0b\x32\x41.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.SignedTransaction\x1a\x85\x01\n$Get_Transaction_Receipt_Performative\x12]\n\x12transaction_digest\x18\x01 \x01(\x0b\x32\x41.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.TransactionDigest\x1a:\n\x14\x42\x61lance_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\x02 \x01(\x03\x1aw\n\x1cRaw_Transaction_Performative\x12W\n\x0fraw_transaction\x18\x01 \x01(\x0b\x32>.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.RawTransaction\x1a\x80\x01\n\x1fTransaction_Digest_Performative\x12]\n\x12transaction_digest\x18\x01 \x01(\x0b\x32\x41.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.TransactionDigest\x1a\x83\x01\n Transaction_Receipt_Performative\x12_\n\x13transaction_receipt\x18\x01 \x01(\x0b\x32\x42.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.TransactionReceipt\x1a\x93\x01\n\x16Get_State_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x03 \x03(\t\x12\x46\n\x06kwargs\x18\x04 \x01(\x0b\x32\x36.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Kwargs\x1am\n\x12State_Performative\x12\x11\n\tledger_id\x18\x01 \x01(\t\x12\x44\n\x05state\x18\x02 \x01(\x0b\x32\x35.aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.State\x1an\n\x12\x45rror_Performative\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x03\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x16\n\x0emessage_is_set\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x13\n\x0b\x64\x61ta_is_set\x18\x05 \x01(\x08\x42\x0e\n\x0cperformativeb\x06proto3' ) _LEDGERAPIMESSAGE = DESCRIPTOR.message_types_by_name["LedgerApiMessage"] _LEDGERAPIMESSAGE_KWARGS = _LEDGERAPIMESSAGE.nested_types_by_name["Kwargs"] _LEDGERAPIMESSAGE_RAWTRANSACTION = _LEDGERAPIMESSAGE.nested_types_by_name[ "RawTransaction" ] _LEDGERAPIMESSAGE_SIGNEDTRANSACTION = _LEDGERAPIMESSAGE.nested_types_by_name[ "SignedTransaction" ] _LEDGERAPIMESSAGE_STATE = _LEDGERAPIMESSAGE.nested_types_by_name["State"] _LEDGERAPIMESSAGE_TERMS = _LEDGERAPIMESSAGE.nested_types_by_name["Terms"] _LEDGERAPIMESSAGE_TRANSACTIONDIGEST = _LEDGERAPIMESSAGE.nested_types_by_name[ "TransactionDigest" ] _LEDGERAPIMESSAGE_TRANSACTIONRECEIPT = _LEDGERAPIMESSAGE.nested_types_by_name[ "TransactionReceipt" ] _LEDGERAPIMESSAGE_GET_BALANCE_PERFORMATIVE = _LEDGERAPIMESSAGE.nested_types_by_name[ "Get_Balance_Performative" ] _LEDGERAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE = ( _LEDGERAPIMESSAGE.nested_types_by_name["Get_Raw_Transaction_Performative"] ) _LEDGERAPIMESSAGE_SEND_SIGNED_TRANSACTION_PERFORMATIVE = ( _LEDGERAPIMESSAGE.nested_types_by_name["Send_Signed_Transaction_Performative"] ) _LEDGERAPIMESSAGE_GET_TRANSACTION_RECEIPT_PERFORMATIVE = ( _LEDGERAPIMESSAGE.nested_types_by_name["Get_Transaction_Receipt_Performative"] ) _LEDGERAPIMESSAGE_BALANCE_PERFORMATIVE = _LEDGERAPIMESSAGE.nested_types_by_name[ "Balance_Performative" ] _LEDGERAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE = _LEDGERAPIMESSAGE.nested_types_by_name[ "Raw_Transaction_Performative" ] _LEDGERAPIMESSAGE_TRANSACTION_DIGEST_PERFORMATIVE = ( _LEDGERAPIMESSAGE.nested_types_by_name["Transaction_Digest_Performative"] ) _LEDGERAPIMESSAGE_TRANSACTION_RECEIPT_PERFORMATIVE = ( _LEDGERAPIMESSAGE.nested_types_by_name["Transaction_Receipt_Performative"] ) _LEDGERAPIMESSAGE_GET_STATE_PERFORMATIVE = _LEDGERAPIMESSAGE.nested_types_by_name[ "Get_State_Performative" ] _LEDGERAPIMESSAGE_STATE_PERFORMATIVE = _LEDGERAPIMESSAGE.nested_types_by_name[ "State_Performative" ] _LEDGERAPIMESSAGE_ERROR_PERFORMATIVE = _LEDGERAPIMESSAGE.nested_types_by_name[ "Error_Performative" ] LedgerApiMessage = _reflection.GeneratedProtocolMessageType( "LedgerApiMessage", (_message.Message,), { "Kwargs": _reflection.GeneratedProtocolMessageType( "Kwargs", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_KWARGS, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Kwargs) }, ), "RawTransaction": _reflection.GeneratedProtocolMessageType( "RawTransaction", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_RAWTRANSACTION, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.RawTransaction) }, ), "SignedTransaction": _reflection.GeneratedProtocolMessageType( "SignedTransaction", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_SIGNEDTRANSACTION, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.SignedTransaction) }, ), "State": _reflection.GeneratedProtocolMessageType( "State", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_STATE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.State) }, ), "Terms": _reflection.GeneratedProtocolMessageType( "Terms", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_TERMS, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Terms) }, ), "TransactionDigest": _reflection.GeneratedProtocolMessageType( "TransactionDigest", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_TRANSACTIONDIGEST, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.TransactionDigest) }, ), "TransactionReceipt": _reflection.GeneratedProtocolMessageType( "TransactionReceipt", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_TRANSACTIONRECEIPT, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.TransactionReceipt) }, ), "Get_Balance_Performative": _reflection.GeneratedProtocolMessageType( "Get_Balance_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_GET_BALANCE_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_Balance_Performative) }, ), "Get_Raw_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Get_Raw_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_Raw_Transaction_Performative) }, ), "Send_Signed_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Send_Signed_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_SEND_SIGNED_TRANSACTION_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Send_Signed_Transaction_Performative) }, ), "Get_Transaction_Receipt_Performative": _reflection.GeneratedProtocolMessageType( "Get_Transaction_Receipt_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_GET_TRANSACTION_RECEIPT_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_Transaction_Receipt_Performative) }, ), "Balance_Performative": _reflection.GeneratedProtocolMessageType( "Balance_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_BALANCE_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Balance_Performative) }, ), "Raw_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Raw_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Raw_Transaction_Performative) }, ), "Transaction_Digest_Performative": _reflection.GeneratedProtocolMessageType( "Transaction_Digest_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_TRANSACTION_DIGEST_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Transaction_Digest_Performative) }, ), "Transaction_Receipt_Performative": _reflection.GeneratedProtocolMessageType( "Transaction_Receipt_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_TRANSACTION_RECEIPT_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Transaction_Receipt_Performative) }, ), "Get_State_Performative": _reflection.GeneratedProtocolMessageType( "Get_State_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_GET_STATE_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Get_State_Performative) }, ), "State_Performative": _reflection.GeneratedProtocolMessageType( "State_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_STATE_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.State_Performative) }, ), "Error_Performative": _reflection.GeneratedProtocolMessageType( "Error_Performative", (_message.Message,), { "DESCRIPTOR": _LEDGERAPIMESSAGE_ERROR_PERFORMATIVE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage.Error_Performative) }, ), "DESCRIPTOR": _LEDGERAPIMESSAGE, "__module__": "ledger_api_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ledger_api.v1_0_0.LedgerApiMessage) }, ) _sym_db.RegisterMessage(LedgerApiMessage) _sym_db.RegisterMessage(LedgerApiMessage.Kwargs) _sym_db.RegisterMessage(LedgerApiMessage.RawTransaction) _sym_db.RegisterMessage(LedgerApiMessage.SignedTransaction) _sym_db.RegisterMessage(LedgerApiMessage.State) _sym_db.RegisterMessage(LedgerApiMessage.Terms) _sym_db.RegisterMessage(LedgerApiMessage.TransactionDigest) _sym_db.RegisterMessage(LedgerApiMessage.TransactionReceipt) _sym_db.RegisterMessage(LedgerApiMessage.Get_Balance_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Get_Raw_Transaction_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Send_Signed_Transaction_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Get_Transaction_Receipt_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Balance_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Raw_Transaction_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Transaction_Digest_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Transaction_Receipt_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Get_State_Performative) _sym_db.RegisterMessage(LedgerApiMessage.State_Performative) _sym_db.RegisterMessage(LedgerApiMessage.Error_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _LEDGERAPIMESSAGE._serialized_start = 52 _LEDGERAPIMESSAGE._serialized_end = 2746 _LEDGERAPIMESSAGE_KWARGS._serialized_start = 1205 _LEDGERAPIMESSAGE_KWARGS._serialized_end = 1229 _LEDGERAPIMESSAGE_RAWTRANSACTION._serialized_start = 1231 _LEDGERAPIMESSAGE_RAWTRANSACTION._serialized_end = 1272 _LEDGERAPIMESSAGE_SIGNEDTRANSACTION._serialized_start = 1274 _LEDGERAPIMESSAGE_SIGNEDTRANSACTION._serialized_end = 1321 _LEDGERAPIMESSAGE_STATE._serialized_start = 1323 _LEDGERAPIMESSAGE_STATE._serialized_end = 1345 _LEDGERAPIMESSAGE_TERMS._serialized_start = 1347 _LEDGERAPIMESSAGE_TERMS._serialized_end = 1369 _LEDGERAPIMESSAGE_TRANSACTIONDIGEST._serialized_start = 1371 _LEDGERAPIMESSAGE_TRANSACTIONDIGEST._serialized_end = 1418 _LEDGERAPIMESSAGE_TRANSACTIONRECEIPT._serialized_start = 1420 _LEDGERAPIMESSAGE_TRANSACTIONRECEIPT._serialized_end = 1469 _LEDGERAPIMESSAGE_GET_BALANCE_PERFORMATIVE._serialized_start = 1471 _LEDGERAPIMESSAGE_GET_BALANCE_PERFORMATIVE._serialized_end = 1533 _LEDGERAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE._serialized_start = 1535 _LEDGERAPIMESSAGE_GET_RAW_TRANSACTION_PERFORMATIVE._serialized_end = 1639 _LEDGERAPIMESSAGE_SEND_SIGNED_TRANSACTION_PERFORMATIVE._serialized_start = 1642 _LEDGERAPIMESSAGE_SEND_SIGNED_TRANSACTION_PERFORMATIVE._serialized_end = 1775 _LEDGERAPIMESSAGE_GET_TRANSACTION_RECEIPT_PERFORMATIVE._serialized_start = 1778 _LEDGERAPIMESSAGE_GET_TRANSACTION_RECEIPT_PERFORMATIVE._serialized_end = 1911 _LEDGERAPIMESSAGE_BALANCE_PERFORMATIVE._serialized_start = 1913 _LEDGERAPIMESSAGE_BALANCE_PERFORMATIVE._serialized_end = 1971 _LEDGERAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE._serialized_start = 1973 _LEDGERAPIMESSAGE_RAW_TRANSACTION_PERFORMATIVE._serialized_end = 2092 _LEDGERAPIMESSAGE_TRANSACTION_DIGEST_PERFORMATIVE._serialized_start = 2095 _LEDGERAPIMESSAGE_TRANSACTION_DIGEST_PERFORMATIVE._serialized_end = 2223 _LEDGERAPIMESSAGE_TRANSACTION_RECEIPT_PERFORMATIVE._serialized_start = 2226 _LEDGERAPIMESSAGE_TRANSACTION_RECEIPT_PERFORMATIVE._serialized_end = 2357 _LEDGERAPIMESSAGE_GET_STATE_PERFORMATIVE._serialized_start = 2360 _LEDGERAPIMESSAGE_GET_STATE_PERFORMATIVE._serialized_end = 2507 _LEDGERAPIMESSAGE_STATE_PERFORMATIVE._serialized_start = 2509 _LEDGERAPIMESSAGE_STATE_PERFORMATIVE._serialized_end = 2618 _LEDGERAPIMESSAGE_ERROR_PERFORMATIVE._serialized_start = 2620 _LEDGERAPIMESSAGE_ERROR_PERFORMATIVE._serialized_end = 2730 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/ledger_api/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains ledger_api's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Optional, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.ledger_api.custom_types import Kwargs as CustomKwargs from packages.fetchai.protocols.ledger_api.custom_types import ( RawTransaction as CustomRawTransaction, ) from packages.fetchai.protocols.ledger_api.custom_types import ( SignedTransaction as CustomSignedTransaction, ) from packages.fetchai.protocols.ledger_api.custom_types import State as CustomState from packages.fetchai.protocols.ledger_api.custom_types import Terms as CustomTerms from packages.fetchai.protocols.ledger_api.custom_types import ( TransactionDigest as CustomTransactionDigest, ) from packages.fetchai.protocols.ledger_api.custom_types import ( TransactionReceipt as CustomTransactionReceipt, ) _default_logger = logging.getLogger("aea.packages.fetchai.protocols.ledger_api.message") DEFAULT_BODY_SIZE = 4 class LedgerApiMessage(Message): """A protocol for ledger APIs requests and responses.""" protocol_id = PublicId.from_str("fetchai/ledger_api:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/ledger_api:1.0.0") Kwargs = CustomKwargs RawTransaction = CustomRawTransaction SignedTransaction = CustomSignedTransaction State = CustomState Terms = CustomTerms TransactionDigest = CustomTransactionDigest TransactionReceipt = CustomTransactionReceipt class Performative(Message.Performative): """Performatives for the ledger_api protocol.""" BALANCE = "balance" ERROR = "error" GET_BALANCE = "get_balance" GET_RAW_TRANSACTION = "get_raw_transaction" GET_STATE = "get_state" GET_TRANSACTION_RECEIPT = "get_transaction_receipt" RAW_TRANSACTION = "raw_transaction" SEND_SIGNED_TRANSACTION = "send_signed_transaction" STATE = "state" TRANSACTION_DIGEST = "transaction_digest" TRANSACTION_RECEIPT = "transaction_receipt" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "balance", "error", "get_balance", "get_raw_transaction", "get_state", "get_transaction_receipt", "raw_transaction", "send_signed_transaction", "state", "transaction_digest", "transaction_receipt", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "address", "args", "balance", "callable", "code", "data", "dialogue_reference", "kwargs", "ledger_id", "message", "message_id", "performative", "raw_transaction", "signed_transaction", "state", "target", "terms", "transaction_digest", "transaction_receipt", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of LedgerApiMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=LedgerApiMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(LedgerApiMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def address(self) -> str: """Get the 'address' content from the message.""" enforce(self.is_set("address"), "'address' content is not set.") return cast(str, self.get("address")) @property def args(self) -> Tuple[str, ...]: """Get the 'args' content from the message.""" enforce(self.is_set("args"), "'args' content is not set.") return cast(Tuple[str, ...], self.get("args")) @property def balance(self) -> int: """Get the 'balance' content from the message.""" enforce(self.is_set("balance"), "'balance' content is not set.") return cast(int, self.get("balance")) @property def callable(self) -> str: """Get the 'callable' content from the message.""" enforce(self.is_set("callable"), "'callable' content is not set.") return cast(str, self.get("callable")) @property def code(self) -> int: """Get the 'code' content from the message.""" enforce(self.is_set("code"), "'code' content is not set.") return cast(int, self.get("code")) @property def data(self) -> Optional[bytes]: """Get the 'data' content from the message.""" return cast(Optional[bytes], self.get("data")) @property def kwargs(self) -> CustomKwargs: """Get the 'kwargs' content from the message.""" enforce(self.is_set("kwargs"), "'kwargs' content is not set.") return cast(CustomKwargs, self.get("kwargs")) @property def ledger_id(self) -> str: """Get the 'ledger_id' content from the message.""" enforce(self.is_set("ledger_id"), "'ledger_id' content is not set.") return cast(str, self.get("ledger_id")) @property def message(self) -> Optional[str]: """Get the 'message' content from the message.""" return cast(Optional[str], self.get("message")) @property def raw_transaction(self) -> CustomRawTransaction: """Get the 'raw_transaction' content from the message.""" enforce(self.is_set("raw_transaction"), "'raw_transaction' content is not set.") return cast(CustomRawTransaction, self.get("raw_transaction")) @property def signed_transaction(self) -> CustomSignedTransaction: """Get the 'signed_transaction' content from the message.""" enforce( self.is_set("signed_transaction"), "'signed_transaction' content is not set.", ) return cast(CustomSignedTransaction, self.get("signed_transaction")) @property def state(self) -> CustomState: """Get the 'state' content from the message.""" enforce(self.is_set("state"), "'state' content is not set.") return cast(CustomState, self.get("state")) @property def terms(self) -> CustomTerms: """Get the 'terms' content from the message.""" enforce(self.is_set("terms"), "'terms' content is not set.") return cast(CustomTerms, self.get("terms")) @property def transaction_digest(self) -> CustomTransactionDigest: """Get the 'transaction_digest' content from the message.""" enforce( self.is_set("transaction_digest"), "'transaction_digest' content is not set.", ) return cast(CustomTransactionDigest, self.get("transaction_digest")) @property def transaction_receipt(self) -> CustomTransactionReceipt: """Get the 'transaction_receipt' content from the message.""" enforce( self.is_set("transaction_receipt"), "'transaction_receipt' content is not set.", ) return cast(CustomTransactionReceipt, self.get("transaction_receipt")) def _is_consistent(self) -> bool: """Check that the message follows the ledger_api protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, LedgerApiMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == LedgerApiMessage.Performative.GET_BALANCE: expected_nb_of_contents = 2 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.address, str), "Invalid type for content 'address'. Expected 'str'. Found '{}'.".format( type(self.address) ), ) elif self.performative == LedgerApiMessage.Performative.GET_RAW_TRANSACTION: expected_nb_of_contents = 1 enforce( isinstance(self.terms, CustomTerms), "Invalid type for content 'terms'. Expected 'Terms'. Found '{}'.".format( type(self.terms) ), ) elif ( self.performative == LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION ): expected_nb_of_contents = 1 enforce( isinstance(self.signed_transaction, CustomSignedTransaction), "Invalid type for content 'signed_transaction'. Expected 'SignedTransaction'. Found '{}'.".format( type(self.signed_transaction) ), ) elif ( self.performative == LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT ): expected_nb_of_contents = 1 enforce( isinstance(self.transaction_digest, CustomTransactionDigest), "Invalid type for content 'transaction_digest'. Expected 'TransactionDigest'. Found '{}'.".format( type(self.transaction_digest) ), ) elif self.performative == LedgerApiMessage.Performative.BALANCE: expected_nb_of_contents = 2 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( type(self.balance) is int, "Invalid type for content 'balance'. Expected 'int'. Found '{}'.".format( type(self.balance) ), ) elif self.performative == LedgerApiMessage.Performative.RAW_TRANSACTION: expected_nb_of_contents = 1 enforce( isinstance(self.raw_transaction, CustomRawTransaction), "Invalid type for content 'raw_transaction'. Expected 'RawTransaction'. Found '{}'.".format( type(self.raw_transaction) ), ) elif self.performative == LedgerApiMessage.Performative.TRANSACTION_DIGEST: expected_nb_of_contents = 1 enforce( isinstance(self.transaction_digest, CustomTransactionDigest), "Invalid type for content 'transaction_digest'. Expected 'TransactionDigest'. Found '{}'.".format( type(self.transaction_digest) ), ) elif self.performative == LedgerApiMessage.Performative.TRANSACTION_RECEIPT: expected_nb_of_contents = 1 enforce( isinstance(self.transaction_receipt, CustomTransactionReceipt), "Invalid type for content 'transaction_receipt'. Expected 'TransactionReceipt'. Found '{}'.".format( type(self.transaction_receipt) ), ) elif self.performative == LedgerApiMessage.Performative.GET_STATE: expected_nb_of_contents = 4 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.callable, str), "Invalid type for content 'callable'. Expected 'str'. Found '{}'.".format( type(self.callable) ), ) enforce( isinstance(self.args, tuple), "Invalid type for content 'args'. Expected 'tuple'. Found '{}'.".format( type(self.args) ), ) enforce( all(isinstance(element, str) for element in self.args), "Invalid type for tuple elements in content 'args'. Expected 'str'.", ) enforce( isinstance(self.kwargs, CustomKwargs), "Invalid type for content 'kwargs'. Expected 'Kwargs'. Found '{}'.".format( type(self.kwargs) ), ) elif self.performative == LedgerApiMessage.Performative.STATE: expected_nb_of_contents = 2 enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.state, CustomState), "Invalid type for content 'state'. Expected 'State'. Found '{}'.".format( type(self.state) ), ) elif self.performative == LedgerApiMessage.Performative.ERROR: expected_nb_of_contents = 1 enforce( type(self.code) is int, "Invalid type for content 'code'. Expected 'int'. Found '{}'.".format( type(self.code) ), ) if self.is_set("message"): expected_nb_of_contents += 1 message = cast(str, self.message) enforce( isinstance(message, str), "Invalid type for content 'message'. Expected 'str'. Found '{}'.".format( type(message) ), ) if self.is_set("data"): expected_nb_of_contents += 1 data = cast(bytes, self.data) enforce( isinstance(data, bytes), "Invalid type for content 'data'. Expected 'bytes'. Found '{}'.".format( type(data) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/ledger_api/protocol.yaml ================================================ name: ledger_api author: fetchai version: 1.1.7 protocol_specification_id: fetchai/ledger_api:1.0.0 type: protocol description: A protocol for ledger APIs requests and responses. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmPh9s795kuU8tCggTPTuAcHC95dkf6CNPUwRCZvZnQ7ty __init__.py: QmUTxKefDQ2Pek9zDDFkSBG79vJnbPftX7mLvoEWg8Qw2w custom_types.py: QmVHe1LBaErJseoa5QbhpvbEpFZXx4vaaBveREGNmwZs91 dialogues.py: QmZ7iDRuQs32KxGEutUrHqTeVHa8UTTje2tvVa8ELu3kDy ledger_api.proto: QmR92cmoxSxKANTvCmm9skftvgzYobNwcWCUanNkduJjyh ledger_api_pb2.py: QmNt9mSa71PcXDHFDwEWb3ay4RAE11KURX8hzZmFj8voEo message.py: QmbeVWQTAkLybFVLyemoMtotXaU7DyjByXz2JW5pxXob1M serialization.py: QmbYMuLC59Emc8hwW8ELcFRaT8xiXakdL2p5vCyBE8PnCg fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/ledger_api/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for ledger_api protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.ledger_api import ledger_api_pb2 from packages.fetchai.protocols.ledger_api.custom_types import ( Kwargs, RawTransaction, SignedTransaction, State, Terms, TransactionDigest, TransactionReceipt, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage class LedgerApiSerializer(Serializer): """Serialization for the 'ledger_api' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'LedgerApi' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(LedgerApiMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() ledger_api_msg = ledger_api_pb2.LedgerApiMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == LedgerApiMessage.Performative.GET_BALANCE: performative = ledger_api_pb2.LedgerApiMessage.Get_Balance_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id address = msg.address performative.address = address ledger_api_msg.get_balance.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.GET_RAW_TRANSACTION: performative = ledger_api_pb2.LedgerApiMessage.Get_Raw_Transaction_Performative() # type: ignore terms = msg.terms Terms.encode(performative.terms, terms) ledger_api_msg.get_raw_transaction.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION: performative = ledger_api_pb2.LedgerApiMessage.Send_Signed_Transaction_Performative() # type: ignore signed_transaction = msg.signed_transaction SignedTransaction.encode( performative.signed_transaction, signed_transaction ) ledger_api_msg.send_signed_transaction.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT: performative = ledger_api_pb2.LedgerApiMessage.Get_Transaction_Receipt_Performative() # type: ignore transaction_digest = msg.transaction_digest TransactionDigest.encode( performative.transaction_digest, transaction_digest ) ledger_api_msg.get_transaction_receipt.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.BALANCE: performative = ledger_api_pb2.LedgerApiMessage.Balance_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id balance = msg.balance performative.balance = balance ledger_api_msg.balance.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.RAW_TRANSACTION: performative = ledger_api_pb2.LedgerApiMessage.Raw_Transaction_Performative() # type: ignore raw_transaction = msg.raw_transaction RawTransaction.encode(performative.raw_transaction, raw_transaction) ledger_api_msg.raw_transaction.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.TRANSACTION_DIGEST: performative = ledger_api_pb2.LedgerApiMessage.Transaction_Digest_Performative() # type: ignore transaction_digest = msg.transaction_digest TransactionDigest.encode( performative.transaction_digest, transaction_digest ) ledger_api_msg.transaction_digest.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.TRANSACTION_RECEIPT: performative = ledger_api_pb2.LedgerApiMessage.Transaction_Receipt_Performative() # type: ignore transaction_receipt = msg.transaction_receipt TransactionReceipt.encode( performative.transaction_receipt, transaction_receipt ) ledger_api_msg.transaction_receipt.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.GET_STATE: performative = ledger_api_pb2.LedgerApiMessage.Get_State_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id callable = msg.callable performative.callable = callable args = msg.args performative.args.extend(args) kwargs = msg.kwargs Kwargs.encode(performative.kwargs, kwargs) ledger_api_msg.get_state.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.STATE: performative = ledger_api_pb2.LedgerApiMessage.State_Performative() # type: ignore ledger_id = msg.ledger_id performative.ledger_id = ledger_id state = msg.state State.encode(performative.state, state) ledger_api_msg.state.CopyFrom(performative) elif performative_id == LedgerApiMessage.Performative.ERROR: performative = ledger_api_pb2.LedgerApiMessage.Error_Performative() # type: ignore code = msg.code performative.code = code if msg.is_set("message"): performative.message_is_set = True message = msg.message performative.message = message if msg.is_set("data"): performative.data_is_set = True data = msg.data performative.data = data ledger_api_msg.error.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = ledger_api_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'LedgerApi' message. :param obj: the bytes object. :return: the 'LedgerApi' message. """ message_pb = ProtobufMessage() ledger_api_pb = ledger_api_pb2.LedgerApiMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target ledger_api_pb.ParseFromString(message_pb.dialogue_message.content) performative = ledger_api_pb.WhichOneof("performative") performative_id = LedgerApiMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == LedgerApiMessage.Performative.GET_BALANCE: ledger_id = ledger_api_pb.get_balance.ledger_id performative_content["ledger_id"] = ledger_id address = ledger_api_pb.get_balance.address performative_content["address"] = address elif performative_id == LedgerApiMessage.Performative.GET_RAW_TRANSACTION: pb2_terms = ledger_api_pb.get_raw_transaction.terms terms = Terms.decode(pb2_terms) performative_content["terms"] = terms elif performative_id == LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION: pb2_signed_transaction = ( ledger_api_pb.send_signed_transaction.signed_transaction ) signed_transaction = SignedTransaction.decode(pb2_signed_transaction) performative_content["signed_transaction"] = signed_transaction elif performative_id == LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT: pb2_transaction_digest = ( ledger_api_pb.get_transaction_receipt.transaction_digest ) transaction_digest = TransactionDigest.decode(pb2_transaction_digest) performative_content["transaction_digest"] = transaction_digest elif performative_id == LedgerApiMessage.Performative.BALANCE: ledger_id = ledger_api_pb.balance.ledger_id performative_content["ledger_id"] = ledger_id balance = ledger_api_pb.balance.balance performative_content["balance"] = balance elif performative_id == LedgerApiMessage.Performative.RAW_TRANSACTION: pb2_raw_transaction = ledger_api_pb.raw_transaction.raw_transaction raw_transaction = RawTransaction.decode(pb2_raw_transaction) performative_content["raw_transaction"] = raw_transaction elif performative_id == LedgerApiMessage.Performative.TRANSACTION_DIGEST: pb2_transaction_digest = ledger_api_pb.transaction_digest.transaction_digest transaction_digest = TransactionDigest.decode(pb2_transaction_digest) performative_content["transaction_digest"] = transaction_digest elif performative_id == LedgerApiMessage.Performative.TRANSACTION_RECEIPT: pb2_transaction_receipt = ( ledger_api_pb.transaction_receipt.transaction_receipt ) transaction_receipt = TransactionReceipt.decode(pb2_transaction_receipt) performative_content["transaction_receipt"] = transaction_receipt elif performative_id == LedgerApiMessage.Performative.GET_STATE: ledger_id = ledger_api_pb.get_state.ledger_id performative_content["ledger_id"] = ledger_id callable = ledger_api_pb.get_state.callable performative_content["callable"] = callable args = ledger_api_pb.get_state.args args_tuple = tuple(args) performative_content["args"] = args_tuple pb2_kwargs = ledger_api_pb.get_state.kwargs kwargs = Kwargs.decode(pb2_kwargs) performative_content["kwargs"] = kwargs elif performative_id == LedgerApiMessage.Performative.STATE: ledger_id = ledger_api_pb.state.ledger_id performative_content["ledger_id"] = ledger_id pb2_state = ledger_api_pb.state.state state = State.decode(pb2_state) performative_content["state"] = state elif performative_id == LedgerApiMessage.Performative.ERROR: code = ledger_api_pb.error.code performative_content["code"] = code if ledger_api_pb.error.message_is_set: message = ledger_api_pb.error.message performative_content["message"] = message if ledger_api_pb.error.data_is_set: data = ledger_api_pb.error.data performative_content["data"] = data else: raise ValueError("Performative not valid: {}.".format(performative_id)) return LedgerApiMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/ml_trade/README.md ================================================ # ML Trade Protocol ## Description This is a protocol for trading data for training and prediction purposes. ## Specification ```yaml --- name: ml_trade author: fetchai version: 1.1.7 description: A protocol for trading data for training and prediction purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/ml_trade:1.0.0 speech_acts: cfp: query: ct:Query terms: terms: ct:Description accept: terms: ct:Description tx_digest: pt:str data: terms: ct:Description payload: pt:bytes ... --- ct:Query: | bytes query_bytes = 1; ct:Description: | bytes description_bytes = 1; ... --- initiation: [cfp] reply: cfp: [terms] terms: [accept] accept: [data] data: [] termination: [data] roles: {seller, buyer} end_states: [successful] keep_terminal_state_dialogues: true ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/ml_trade/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the ml_trade protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.ml_trade.serialization import MlTradeSerializer MlTradeMessage.serializer = MlTradeSerializer ================================================ FILE: packages/fetchai/protocols/ml_trade/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from aea.helpers.search.models import Description as BaseDescription from aea.helpers.search.models import Query as BaseQuery Description = BaseDescription Query = BaseQuery ================================================ FILE: packages/fetchai/protocols/ml_trade/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for ml_trade dialogue management. - MlTradeDialogue: The dialogue class maintains state of a dialogue and manages it. - MlTradeDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.ml_trade.message import MlTradeMessage class MlTradeDialogue(Dialogue): """The ml_trade dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {MlTradeMessage.Performative.CFP} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {MlTradeMessage.Performative.DATA} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { MlTradeMessage.Performative.ACCEPT: frozenset( {MlTradeMessage.Performative.DATA} ), MlTradeMessage.Performative.CFP: frozenset({MlTradeMessage.Performative.TERMS}), MlTradeMessage.Performative.DATA: frozenset(), MlTradeMessage.Performative.TERMS: frozenset( {MlTradeMessage.Performative.ACCEPT} ), } class Role(Dialogue.Role): """This class defines the agent's role in a ml_trade dialogue.""" BUYER = "buyer" SELLER = "seller" class EndState(Dialogue.EndState): """This class defines the end states of a ml_trade dialogue.""" SUCCESSFUL = 0 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[MlTradeMessage] = MlTradeMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class MlTradeDialogues(Dialogues, ABC): """This class keeps track of all ml_trade dialogues.""" END_STATES = frozenset({MlTradeDialogue.EndState.SUCCESSFUL}) _keep_terminal_state_dialogues = True def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[MlTradeDialogue] = MlTradeDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=MlTradeMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/ml_trade/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains ml_trade's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.ml_trade.custom_types import ( Description as CustomDescription, ) from packages.fetchai.protocols.ml_trade.custom_types import Query as CustomQuery _default_logger = logging.getLogger("aea.packages.fetchai.protocols.ml_trade.message") DEFAULT_BODY_SIZE = 4 class MlTradeMessage(Message): """A protocol for trading data for training and prediction purposes.""" protocol_id = PublicId.from_str("fetchai/ml_trade:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/ml_trade:1.0.0") Description = CustomDescription Query = CustomQuery class Performative(Message.Performative): """Performatives for the ml_trade protocol.""" ACCEPT = "accept" CFP = "cfp" DATA = "data" TERMS = "terms" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"accept", "cfp", "data", "terms"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "dialogue_reference", "message_id", "payload", "performative", "query", "target", "terms", "tx_digest", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of MlTradeMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=MlTradeMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(MlTradeMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def payload(self) -> bytes: """Get the 'payload' content from the message.""" enforce(self.is_set("payload"), "'payload' content is not set.") return cast(bytes, self.get("payload")) @property def query(self) -> CustomQuery: """Get the 'query' content from the message.""" enforce(self.is_set("query"), "'query' content is not set.") return cast(CustomQuery, self.get("query")) @property def terms(self) -> CustomDescription: """Get the 'terms' content from the message.""" enforce(self.is_set("terms"), "'terms' content is not set.") return cast(CustomDescription, self.get("terms")) @property def tx_digest(self) -> str: """Get the 'tx_digest' content from the message.""" enforce(self.is_set("tx_digest"), "'tx_digest' content is not set.") return cast(str, self.get("tx_digest")) def _is_consistent(self) -> bool: """Check that the message follows the ml_trade protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, MlTradeMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == MlTradeMessage.Performative.CFP: expected_nb_of_contents = 1 enforce( isinstance(self.query, CustomQuery), "Invalid type for content 'query'. Expected 'Query'. Found '{}'.".format( type(self.query) ), ) elif self.performative == MlTradeMessage.Performative.TERMS: expected_nb_of_contents = 1 enforce( isinstance(self.terms, CustomDescription), "Invalid type for content 'terms'. Expected 'Description'. Found '{}'.".format( type(self.terms) ), ) elif self.performative == MlTradeMessage.Performative.ACCEPT: expected_nb_of_contents = 2 enforce( isinstance(self.terms, CustomDescription), "Invalid type for content 'terms'. Expected 'Description'. Found '{}'.".format( type(self.terms) ), ) enforce( isinstance(self.tx_digest, str), "Invalid type for content 'tx_digest'. Expected 'str'. Found '{}'.".format( type(self.tx_digest) ), ) elif self.performative == MlTradeMessage.Performative.DATA: expected_nb_of_contents = 2 enforce( isinstance(self.terms, CustomDescription), "Invalid type for content 'terms'. Expected 'Description'. Found '{}'.".format( type(self.terms) ), ) enforce( isinstance(self.payload, bytes), "Invalid type for content 'payload'. Expected 'bytes'. Found '{}'.".format( type(self.payload) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/ml_trade/ml_trade.proto ================================================ syntax = "proto3"; package aea.fetchai.ml_trade.v1_0_0; message MlTradeMessage{ // Custom Types message Description{ bytes description_bytes = 1; } message Query{ bytes query_bytes = 1; } // Performatives and contents message Cfp_Performative{ Query query = 1; } message Terms_Performative{ Description terms = 1; } message Accept_Performative{ Description terms = 1; string tx_digest = 2; } message Data_Performative{ Description terms = 1; bytes payload = 2; } oneof performative{ Accept_Performative accept = 5; Cfp_Performative cfp = 6; Data_Performative data = 7; Terms_Performative terms = 8; } } ================================================ FILE: packages/fetchai/protocols/ml_trade/ml_trade_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: ml_trade.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0eml_trade.proto\x12\x1b\x61\x65\x61.fetchai.ml_trade.v1_0_0"\xbc\x06\n\x0eMlTradeMessage\x12Q\n\x06\x61\x63\x63\x65pt\x18\x05 \x01(\x0b\x32?.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Accept_PerformativeH\x00\x12K\n\x03\x63\x66p\x18\x06 \x01(\x0b\x32<.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Cfp_PerformativeH\x00\x12M\n\x04\x64\x61ta\x18\x07 \x01(\x0b\x32=.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Data_PerformativeH\x00\x12O\n\x05terms\x18\x08 \x01(\x0b\x32>.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Terms_PerformativeH\x00\x1a(\n\x0b\x44\x65scription\x12\x19\n\x11\x64\x65scription_bytes\x18\x01 \x01(\x0c\x1a\x1c\n\x05Query\x12\x13\n\x0bquery_bytes\x18\x01 \x01(\x0c\x1aT\n\x10\x43\x66p_Performative\x12@\n\x05query\x18\x01 \x01(\x0b\x32\x31.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Query\x1a\\\n\x12Terms_Performative\x12\x46\n\x05terms\x18\x01 \x01(\x0b\x32\x37.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Description\x1ap\n\x13\x41\x63\x63\x65pt_Performative\x12\x46\n\x05terms\x18\x01 \x01(\x0b\x32\x37.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Description\x12\x11\n\ttx_digest\x18\x02 \x01(\t\x1al\n\x11\x44\x61ta_Performative\x12\x46\n\x05terms\x18\x01 \x01(\x0b\x32\x37.aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Description\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x42\x0e\n\x0cperformativeb\x06proto3' ) _MLTRADEMESSAGE = DESCRIPTOR.message_types_by_name["MlTradeMessage"] _MLTRADEMESSAGE_DESCRIPTION = _MLTRADEMESSAGE.nested_types_by_name["Description"] _MLTRADEMESSAGE_QUERY = _MLTRADEMESSAGE.nested_types_by_name["Query"] _MLTRADEMESSAGE_CFP_PERFORMATIVE = _MLTRADEMESSAGE.nested_types_by_name[ "Cfp_Performative" ] _MLTRADEMESSAGE_TERMS_PERFORMATIVE = _MLTRADEMESSAGE.nested_types_by_name[ "Terms_Performative" ] _MLTRADEMESSAGE_ACCEPT_PERFORMATIVE = _MLTRADEMESSAGE.nested_types_by_name[ "Accept_Performative" ] _MLTRADEMESSAGE_DATA_PERFORMATIVE = _MLTRADEMESSAGE.nested_types_by_name[ "Data_Performative" ] MlTradeMessage = _reflection.GeneratedProtocolMessageType( "MlTradeMessage", (_message.Message,), { "Description": _reflection.GeneratedProtocolMessageType( "Description", (_message.Message,), { "DESCRIPTOR": _MLTRADEMESSAGE_DESCRIPTION, "__module__": "ml_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Description) }, ), "Query": _reflection.GeneratedProtocolMessageType( "Query", (_message.Message,), { "DESCRIPTOR": _MLTRADEMESSAGE_QUERY, "__module__": "ml_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Query) }, ), "Cfp_Performative": _reflection.GeneratedProtocolMessageType( "Cfp_Performative", (_message.Message,), { "DESCRIPTOR": _MLTRADEMESSAGE_CFP_PERFORMATIVE, "__module__": "ml_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Cfp_Performative) }, ), "Terms_Performative": _reflection.GeneratedProtocolMessageType( "Terms_Performative", (_message.Message,), { "DESCRIPTOR": _MLTRADEMESSAGE_TERMS_PERFORMATIVE, "__module__": "ml_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Terms_Performative) }, ), "Accept_Performative": _reflection.GeneratedProtocolMessageType( "Accept_Performative", (_message.Message,), { "DESCRIPTOR": _MLTRADEMESSAGE_ACCEPT_PERFORMATIVE, "__module__": "ml_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Accept_Performative) }, ), "Data_Performative": _reflection.GeneratedProtocolMessageType( "Data_Performative", (_message.Message,), { "DESCRIPTOR": _MLTRADEMESSAGE_DATA_PERFORMATIVE, "__module__": "ml_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ml_trade.v1_0_0.MlTradeMessage.Data_Performative) }, ), "DESCRIPTOR": _MLTRADEMESSAGE, "__module__": "ml_trade_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.ml_trade.v1_0_0.MlTradeMessage) }, ) _sym_db.RegisterMessage(MlTradeMessage) _sym_db.RegisterMessage(MlTradeMessage.Description) _sym_db.RegisterMessage(MlTradeMessage.Query) _sym_db.RegisterMessage(MlTradeMessage.Cfp_Performative) _sym_db.RegisterMessage(MlTradeMessage.Terms_Performative) _sym_db.RegisterMessage(MlTradeMessage.Accept_Performative) _sym_db.RegisterMessage(MlTradeMessage.Data_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _MLTRADEMESSAGE._serialized_start = 48 _MLTRADEMESSAGE._serialized_end = 876 _MLTRADEMESSAGE_DESCRIPTION._serialized_start = 386 _MLTRADEMESSAGE_DESCRIPTION._serialized_end = 426 _MLTRADEMESSAGE_QUERY._serialized_start = 428 _MLTRADEMESSAGE_QUERY._serialized_end = 456 _MLTRADEMESSAGE_CFP_PERFORMATIVE._serialized_start = 458 _MLTRADEMESSAGE_CFP_PERFORMATIVE._serialized_end = 542 _MLTRADEMESSAGE_TERMS_PERFORMATIVE._serialized_start = 544 _MLTRADEMESSAGE_TERMS_PERFORMATIVE._serialized_end = 636 _MLTRADEMESSAGE_ACCEPT_PERFORMATIVE._serialized_start = 638 _MLTRADEMESSAGE_ACCEPT_PERFORMATIVE._serialized_end = 750 _MLTRADEMESSAGE_DATA_PERFORMATIVE._serialized_start = 752 _MLTRADEMESSAGE_DATA_PERFORMATIVE._serialized_end = 860 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/ml_trade/protocol.yaml ================================================ name: ml_trade author: fetchai version: 1.1.7 protocol_specification_id: fetchai/ml_trade:1.0.0 type: protocol description: A protocol for trading data for training and prediction purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmTDDRfEdNV55C5Z34PLoUqn5LKd8p1p96GbrWa5G2S65J __init__.py: QmSE8icQPrhs32YjKAxzz7BZjFaLWEKsZpouXshP516GzA custom_types.py: QmPnyZAfuH2myvv47BaN7hsRDo7E4rmjvzDYWoMCwXwkg8 dialogues.py: Qmc63JhVebdh1Y4QxLjDxxLf4eYtaj62pLw9eVqULqFM64 message.py: Qmd9923RVn5ABHb9MytPVqPvqC9GKwTp6cfm58EnqYaFUr ml_trade.proto: QmbW2f4qNJJeY8YVgrawHjroqYcTviY5BevCBYVUMVVoH9 ml_trade_pb2.py: QmTF6TseznjZxVoJH99vxW9GDz4LrSFhqBwNsfF6s8cv9H serialization.py: QmTAULUySkgNgvXeCmC21gE7AU2a3V8dD4jAbDqAWokcMC fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/ml_trade/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for ml_trade protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.ml_trade import ml_trade_pb2 from packages.fetchai.protocols.ml_trade.custom_types import Description, Query from packages.fetchai.protocols.ml_trade.message import MlTradeMessage class MlTradeSerializer(Serializer): """Serialization for the 'ml_trade' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'MlTrade' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(MlTradeMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() ml_trade_msg = ml_trade_pb2.MlTradeMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == MlTradeMessage.Performative.CFP: performative = ml_trade_pb2.MlTradeMessage.Cfp_Performative() # type: ignore query = msg.query Query.encode(performative.query, query) ml_trade_msg.cfp.CopyFrom(performative) elif performative_id == MlTradeMessage.Performative.TERMS: performative = ml_trade_pb2.MlTradeMessage.Terms_Performative() # type: ignore terms = msg.terms Description.encode(performative.terms, terms) ml_trade_msg.terms.CopyFrom(performative) elif performative_id == MlTradeMessage.Performative.ACCEPT: performative = ml_trade_pb2.MlTradeMessage.Accept_Performative() # type: ignore terms = msg.terms Description.encode(performative.terms, terms) tx_digest = msg.tx_digest performative.tx_digest = tx_digest ml_trade_msg.accept.CopyFrom(performative) elif performative_id == MlTradeMessage.Performative.DATA: performative = ml_trade_pb2.MlTradeMessage.Data_Performative() # type: ignore terms = msg.terms Description.encode(performative.terms, terms) payload = msg.payload performative.payload = payload ml_trade_msg.data.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = ml_trade_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'MlTrade' message. :param obj: the bytes object. :return: the 'MlTrade' message. """ message_pb = ProtobufMessage() ml_trade_pb = ml_trade_pb2.MlTradeMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target ml_trade_pb.ParseFromString(message_pb.dialogue_message.content) performative = ml_trade_pb.WhichOneof("performative") performative_id = MlTradeMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == MlTradeMessage.Performative.CFP: pb2_query = ml_trade_pb.cfp.query query = Query.decode(pb2_query) performative_content["query"] = query elif performative_id == MlTradeMessage.Performative.TERMS: pb2_terms = ml_trade_pb.terms.terms terms = Description.decode(pb2_terms) performative_content["terms"] = terms elif performative_id == MlTradeMessage.Performative.ACCEPT: pb2_terms = ml_trade_pb.accept.terms terms = Description.decode(pb2_terms) performative_content["terms"] = terms tx_digest = ml_trade_pb.accept.tx_digest performative_content["tx_digest"] = tx_digest elif performative_id == MlTradeMessage.Performative.DATA: pb2_terms = ml_trade_pb.data.terms terms = Description.decode(pb2_terms) performative_content["terms"] = terms payload = ml_trade_pb.data.payload performative_content["payload"] = payload else: raise ValueError("Performative not valid: {}.".format(performative_id)) return MlTradeMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/oef_search/README.md ================================================ # OEF Search Protocol ## Description This is a protocol for interacting with an OEF search service. It allows for registering of agents and services, and searching of agents and services using a query language. ## Specification ```yaml --- name: oef_search author: fetchai version: 1.1.7 description: A protocol for interacting with an OEF search service. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/oef_search:1.0.0 speech_acts: register_service: service_description: ct:Description unregister_service: service_description: ct:Description search_services: query: ct:Query search_result: agents: pt:list[pt:str] agents_info: ct:AgentsInfo success: agents_info: ct:AgentsInfo oef_error: oef_error_operation: ct:OefErrorOperation ... --- ct:Query: | bytes query_bytes = 1; ct:Description: | bytes description_bytes = 1; ct:AgentsInfo: | bytes agents_info = 1; ct:OefErrorOperation: | enum OefErrorEnum { REGISTER_SERVICE = 0; UNREGISTER_SERVICE = 1; SEARCH_SERVICES = 2; SEND_MESSAGE = 3; } OefErrorEnum oef_error = 1; ... --- initiation: [register_service, unregister_service, search_services] reply: register_service: [success, oef_error] unregister_service: [success, oef_error] search_services: [search_result, oef_error] search_result: [] oef_error: [] success: [] termination: [oef_error, search_result, success] roles: {agent, oef_node} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/oef_search/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the oef_search protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.oef_search.serialization import OefSearchSerializer OefSearchMessage.serializer = OefSearchSerializer ================================================ FILE: packages/fetchai/protocols/oef_search/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from enum import Enum from typing import Any, Dict from aea.exceptions import enforce from aea.helpers.search.models import Description as BaseDescription from aea.helpers.search.models import Query as BaseQuery from aea.helpers.serializers import DictProtobufStructSerializer Description = BaseDescription class AgentsInfo: """This class represents an instance of AgentsInfo.""" __slots__ = ("_body",) def __init__(self, body: Dict[str, Dict[str, Any]]): """Initialise an instance of AgentsInfo.""" self._body = body self._check_consistency() def _check_consistency(self) -> None: """Check consistency of the object.""" if self._body is None: raise ValueError("body must not be None") enforce( isinstance(self._body, dict) and all(isinstance(key, str) for key in self._body.keys()) and all(isinstance(value, dict) for value in self._body.values()), "Body must be dict and keys must be str and values must be dict.", ) @property def body(self) -> Dict[str, Dict[str, Any]]: """Get the body.""" return self._body def get_info_for_agent(self, agent_address: str) -> Dict[str, Any]: """Get the info for the agent address.""" return self._body.get(agent_address, {}) @staticmethod def encode( agents_info_protobuf_object: Any, agents_info_object: "AgentsInfo" ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the agents_info_protobuf_object argument is matched with the instance of this class in the 'agents_info_object' argument. :param agents_info_protobuf_object: the protocol buffer object whose type corresponds with this class. :param agents_info_object: an instance of this class to be encoded in the protocol buffer object. """ agents_info_protobuf_object.agents_info = DictProtobufStructSerializer.encode( agents_info_object.body ) @classmethod def decode(cls, agents_info_protobuf_object: Any) -> "AgentsInfo": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'agents_info_protobuf_object' argument. :param agents_info_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'agents_info_protobuf_object' argument. """ body = DictProtobufStructSerializer.decode( agents_info_protobuf_object.agents_info ) return cls(body) def __eq__(self, other: Any) -> bool: """Compare with another object.""" if not isinstance(other, AgentsInfo): return False # pragma: nocover return self.body == other.body class OefErrorOperation(Enum): """This class represents an instance of OefErrorOperation.""" REGISTER_SERVICE = 0 UNREGISTER_SERVICE = 1 SEARCH_SERVICES = 2 SEND_MESSAGE = 3 OTHER = 10000 def __str__(self) -> str: """Get string representation.""" return str(self.value) @staticmethod def encode( oef_error_operation_protobuf_object: Any, oef_error_operation_object: "OefErrorOperation", ) -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the oef_error_operation_protobuf_object argument is matched with the instance of this class in the 'oef_error_operation_object' argument. :param oef_error_operation_protobuf_object: the protocol buffer object whose type corresponds with this class. :param oef_error_operation_object: an instance of this class to be encoded in the protocol buffer object. """ oef_error_operation_protobuf_object.oef_error = oef_error_operation_object.value @classmethod def decode(cls, oef_error_operation_protobuf_object: Any) -> "OefErrorOperation": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'oef_error_operation_protobuf_object' argument. :param oef_error_operation_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'oef_error_operation_protobuf_object' argument. """ enum_value_from_pb2 = oef_error_operation_protobuf_object.oef_error return OefErrorOperation(enum_value_from_pb2) Query = BaseQuery ================================================ FILE: packages/fetchai/protocols/oef_search/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for oef_search dialogue management. - OefSearchDialogue: The dialogue class maintains state of a dialogue and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.oef_search.message import OefSearchMessage class OefSearchDialogue(Dialogue): """The oef_search dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { OefSearchMessage.Performative.REGISTER_SERVICE, OefSearchMessage.Performative.UNREGISTER_SERVICE, OefSearchMessage.Performative.SEARCH_SERVICES, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { OefSearchMessage.Performative.OEF_ERROR, OefSearchMessage.Performative.SEARCH_RESULT, OefSearchMessage.Performative.SUCCESS, } ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { OefSearchMessage.Performative.OEF_ERROR: frozenset(), OefSearchMessage.Performative.REGISTER_SERVICE: frozenset( { OefSearchMessage.Performative.SUCCESS, OefSearchMessage.Performative.OEF_ERROR, } ), OefSearchMessage.Performative.SEARCH_RESULT: frozenset(), OefSearchMessage.Performative.SEARCH_SERVICES: frozenset( { OefSearchMessage.Performative.SEARCH_RESULT, OefSearchMessage.Performative.OEF_ERROR, } ), OefSearchMessage.Performative.SUCCESS: frozenset(), OefSearchMessage.Performative.UNREGISTER_SERVICE: frozenset( { OefSearchMessage.Performative.SUCCESS, OefSearchMessage.Performative.OEF_ERROR, } ), } class Role(Dialogue.Role): """This class defines the agent's role in a oef_search dialogue.""" AGENT = "agent" OEF_NODE = "oef_node" class EndState(Dialogue.EndState): """This class defines the end states of a oef_search dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[OefSearchMessage] = OefSearchMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class OefSearchDialogues(Dialogues, ABC): """This class keeps track of all oef_search dialogues.""" END_STATES = frozenset( {OefSearchDialogue.EndState.SUCCESSFUL, OefSearchDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[OefSearchDialogue] = OefSearchDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=OefSearchMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/oef_search/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains oef_search's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.oef_search.custom_types import ( AgentsInfo as CustomAgentsInfo, ) from packages.fetchai.protocols.oef_search.custom_types import ( Description as CustomDescription, ) from packages.fetchai.protocols.oef_search.custom_types import ( OefErrorOperation as CustomOefErrorOperation, ) from packages.fetchai.protocols.oef_search.custom_types import Query as CustomQuery _default_logger = logging.getLogger("aea.packages.fetchai.protocols.oef_search.message") DEFAULT_BODY_SIZE = 4 class OefSearchMessage(Message): """A protocol for interacting with an OEF search service.""" protocol_id = PublicId.from_str("fetchai/oef_search:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/oef_search:1.0.0") AgentsInfo = CustomAgentsInfo Description = CustomDescription OefErrorOperation = CustomOefErrorOperation Query = CustomQuery class Performative(Message.Performative): """Performatives for the oef_search protocol.""" OEF_ERROR = "oef_error" REGISTER_SERVICE = "register_service" SEARCH_RESULT = "search_result" SEARCH_SERVICES = "search_services" SUCCESS = "success" UNREGISTER_SERVICE = "unregister_service" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "oef_error", "register_service", "search_result", "search_services", "success", "unregister_service", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "agents", "agents_info", "dialogue_reference", "message_id", "oef_error_operation", "performative", "query", "service_description", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of OefSearchMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=OefSearchMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(OefSearchMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def agents(self) -> Tuple[str, ...]: """Get the 'agents' content from the message.""" enforce(self.is_set("agents"), "'agents' content is not set.") return cast(Tuple[str, ...], self.get("agents")) @property def agents_info(self) -> CustomAgentsInfo: """Get the 'agents_info' content from the message.""" enforce(self.is_set("agents_info"), "'agents_info' content is not set.") return cast(CustomAgentsInfo, self.get("agents_info")) @property def oef_error_operation(self) -> CustomOefErrorOperation: """Get the 'oef_error_operation' content from the message.""" enforce( self.is_set("oef_error_operation"), "'oef_error_operation' content is not set.", ) return cast(CustomOefErrorOperation, self.get("oef_error_operation")) @property def query(self) -> CustomQuery: """Get the 'query' content from the message.""" enforce(self.is_set("query"), "'query' content is not set.") return cast(CustomQuery, self.get("query")) @property def service_description(self) -> CustomDescription: """Get the 'service_description' content from the message.""" enforce( self.is_set("service_description"), "'service_description' content is not set.", ) return cast(CustomDescription, self.get("service_description")) def _is_consistent(self) -> bool: """Check that the message follows the oef_search protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, OefSearchMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == OefSearchMessage.Performative.REGISTER_SERVICE: expected_nb_of_contents = 1 enforce( isinstance(self.service_description, CustomDescription), "Invalid type for content 'service_description'. Expected 'Description'. Found '{}'.".format( type(self.service_description) ), ) elif self.performative == OefSearchMessage.Performative.UNREGISTER_SERVICE: expected_nb_of_contents = 1 enforce( isinstance(self.service_description, CustomDescription), "Invalid type for content 'service_description'. Expected 'Description'. Found '{}'.".format( type(self.service_description) ), ) elif self.performative == OefSearchMessage.Performative.SEARCH_SERVICES: expected_nb_of_contents = 1 enforce( isinstance(self.query, CustomQuery), "Invalid type for content 'query'. Expected 'Query'. Found '{}'.".format( type(self.query) ), ) elif self.performative == OefSearchMessage.Performative.SEARCH_RESULT: expected_nb_of_contents = 2 enforce( isinstance(self.agents, tuple), "Invalid type for content 'agents'. Expected 'tuple'. Found '{}'.".format( type(self.agents) ), ) enforce( all(isinstance(element, str) for element in self.agents), "Invalid type for tuple elements in content 'agents'. Expected 'str'.", ) enforce( isinstance(self.agents_info, CustomAgentsInfo), "Invalid type for content 'agents_info'. Expected 'AgentsInfo'. Found '{}'.".format( type(self.agents_info) ), ) elif self.performative == OefSearchMessage.Performative.SUCCESS: expected_nb_of_contents = 1 enforce( isinstance(self.agents_info, CustomAgentsInfo), "Invalid type for content 'agents_info'. Expected 'AgentsInfo'. Found '{}'.".format( type(self.agents_info) ), ) elif self.performative == OefSearchMessage.Performative.OEF_ERROR: expected_nb_of_contents = 1 enforce( isinstance(self.oef_error_operation, CustomOefErrorOperation), "Invalid type for content 'oef_error_operation'. Expected 'OefErrorOperation'. Found '{}'.".format( type(self.oef_error_operation) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/oef_search/oef_search.proto ================================================ syntax = "proto3"; package aea.fetchai.oef_search.v1_0_0; message OefSearchMessage{ // Custom Types message AgentsInfo{ bytes agents_info = 1; } message Description{ bytes description_bytes = 1; } message OefErrorOperation{ enum OefErrorEnum { REGISTER_SERVICE = 0; UNREGISTER_SERVICE = 1; SEARCH_SERVICES = 2; SEND_MESSAGE = 3; } OefErrorEnum oef_error = 1; } message Query{ bytes query_bytes = 1; } // Performatives and contents message Register_Service_Performative{ Description service_description = 1; } message Unregister_Service_Performative{ Description service_description = 1; } message Search_Services_Performative{ Query query = 1; } message Search_Result_Performative{ repeated string agents = 1; AgentsInfo agents_info = 2; } message Success_Performative{ AgentsInfo agents_info = 1; } message Oef_Error_Performative{ OefErrorOperation oef_error_operation = 1; } oneof performative{ Oef_Error_Performative oef_error = 5; Register_Service_Performative register_service = 6; Search_Result_Performative search_result = 7; Search_Services_Performative search_services = 8; Success_Performative success = 9; Unregister_Service_Performative unregister_service = 10; } } ================================================ FILE: packages/fetchai/protocols/oef_search/oef_search_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: oef_search.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x10oef_search.proto\x12\x1d\x61\x65\x61.fetchai.oef_search.v1_0_0"\x89\r\n\x10OefSearchMessage\x12[\n\toef_error\x18\x05 \x01(\x0b\x32\x46.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Oef_Error_PerformativeH\x00\x12i\n\x10register_service\x18\x06 \x01(\x0b\x32M.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Register_Service_PerformativeH\x00\x12\x63\n\rsearch_result\x18\x07 \x01(\x0b\x32J.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Search_Result_PerformativeH\x00\x12g\n\x0fsearch_services\x18\x08 \x01(\x0b\x32L.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Search_Services_PerformativeH\x00\x12W\n\x07success\x18\t \x01(\x0b\x32\x44.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Success_PerformativeH\x00\x12m\n\x12unregister_service\x18\n \x01(\x0b\x32O.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Unregister_Service_PerformativeH\x00\x1a!\n\nAgentsInfo\x12\x13\n\x0b\x61gents_info\x18\x01 \x01(\x0c\x1a(\n\x0b\x44\x65scription\x12\x19\n\x11\x64\x65scription_bytes\x18\x01 \x01(\x0c\x1a\xdb\x01\n\x11OefErrorOperation\x12\x61\n\toef_error\x18\x01 \x01(\x0e\x32N.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.OefErrorOperation.OefErrorEnum"c\n\x0cOefErrorEnum\x12\x14\n\x10REGISTER_SERVICE\x10\x00\x12\x16\n\x12UNREGISTER_SERVICE\x10\x01\x12\x13\n\x0fSEARCH_SERVICES\x10\x02\x12\x10\n\x0cSEND_MESSAGE\x10\x03\x1a\x1c\n\x05Query\x12\x13\n\x0bquery_bytes\x18\x01 \x01(\x0c\x1ay\n\x1dRegister_Service_Performative\x12X\n\x13service_description\x18\x01 \x01(\x0b\x32;.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Description\x1a{\n\x1fUnregister_Service_Performative\x12X\n\x13service_description\x18\x01 \x01(\x0b\x32;.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Description\x1a\x64\n\x1cSearch_Services_Performative\x12\x44\n\x05query\x18\x01 \x01(\x0b\x32\x35.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Query\x1a}\n\x1aSearch_Result_Performative\x12\x0e\n\x06\x61gents\x18\x01 \x03(\t\x12O\n\x0b\x61gents_info\x18\x02 \x01(\x0b\x32:.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.AgentsInfo\x1ag\n\x14Success_Performative\x12O\n\x0b\x61gents_info\x18\x01 \x01(\x0b\x32:.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.AgentsInfo\x1ax\n\x16Oef_Error_Performative\x12^\n\x13oef_error_operation\x18\x01 \x01(\x0b\x32\x41.aea.fetchai.oef_search.v1_0_0.OefSearchMessage.OefErrorOperationB\x0e\n\x0cperformativeb\x06proto3' ) _OEFSEARCHMESSAGE = DESCRIPTOR.message_types_by_name["OefSearchMessage"] _OEFSEARCHMESSAGE_AGENTSINFO = _OEFSEARCHMESSAGE.nested_types_by_name["AgentsInfo"] _OEFSEARCHMESSAGE_DESCRIPTION = _OEFSEARCHMESSAGE.nested_types_by_name["Description"] _OEFSEARCHMESSAGE_OEFERROROPERATION = _OEFSEARCHMESSAGE.nested_types_by_name[ "OefErrorOperation" ] _OEFSEARCHMESSAGE_QUERY = _OEFSEARCHMESSAGE.nested_types_by_name["Query"] _OEFSEARCHMESSAGE_REGISTER_SERVICE_PERFORMATIVE = ( _OEFSEARCHMESSAGE.nested_types_by_name["Register_Service_Performative"] ) _OEFSEARCHMESSAGE_UNREGISTER_SERVICE_PERFORMATIVE = ( _OEFSEARCHMESSAGE.nested_types_by_name["Unregister_Service_Performative"] ) _OEFSEARCHMESSAGE_SEARCH_SERVICES_PERFORMATIVE = _OEFSEARCHMESSAGE.nested_types_by_name[ "Search_Services_Performative" ] _OEFSEARCHMESSAGE_SEARCH_RESULT_PERFORMATIVE = _OEFSEARCHMESSAGE.nested_types_by_name[ "Search_Result_Performative" ] _OEFSEARCHMESSAGE_SUCCESS_PERFORMATIVE = _OEFSEARCHMESSAGE.nested_types_by_name[ "Success_Performative" ] _OEFSEARCHMESSAGE_OEF_ERROR_PERFORMATIVE = _OEFSEARCHMESSAGE.nested_types_by_name[ "Oef_Error_Performative" ] _OEFSEARCHMESSAGE_OEFERROROPERATION_OEFERRORENUM = ( _OEFSEARCHMESSAGE_OEFERROROPERATION.enum_types_by_name["OefErrorEnum"] ) OefSearchMessage = _reflection.GeneratedProtocolMessageType( "OefSearchMessage", (_message.Message,), { "AgentsInfo": _reflection.GeneratedProtocolMessageType( "AgentsInfo", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_AGENTSINFO, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.AgentsInfo) }, ), "Description": _reflection.GeneratedProtocolMessageType( "Description", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_DESCRIPTION, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Description) }, ), "OefErrorOperation": _reflection.GeneratedProtocolMessageType( "OefErrorOperation", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_OEFERROROPERATION, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.OefErrorOperation) }, ), "Query": _reflection.GeneratedProtocolMessageType( "Query", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_QUERY, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Query) }, ), "Register_Service_Performative": _reflection.GeneratedProtocolMessageType( "Register_Service_Performative", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_REGISTER_SERVICE_PERFORMATIVE, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Register_Service_Performative) }, ), "Unregister_Service_Performative": _reflection.GeneratedProtocolMessageType( "Unregister_Service_Performative", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_UNREGISTER_SERVICE_PERFORMATIVE, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Unregister_Service_Performative) }, ), "Search_Services_Performative": _reflection.GeneratedProtocolMessageType( "Search_Services_Performative", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_SEARCH_SERVICES_PERFORMATIVE, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Search_Services_Performative) }, ), "Search_Result_Performative": _reflection.GeneratedProtocolMessageType( "Search_Result_Performative", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_SEARCH_RESULT_PERFORMATIVE, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Search_Result_Performative) }, ), "Success_Performative": _reflection.GeneratedProtocolMessageType( "Success_Performative", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_SUCCESS_PERFORMATIVE, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Success_Performative) }, ), "Oef_Error_Performative": _reflection.GeneratedProtocolMessageType( "Oef_Error_Performative", (_message.Message,), { "DESCRIPTOR": _OEFSEARCHMESSAGE_OEF_ERROR_PERFORMATIVE, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage.Oef_Error_Performative) }, ), "DESCRIPTOR": _OEFSEARCHMESSAGE, "__module__": "oef_search_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.oef_search.v1_0_0.OefSearchMessage) }, ) _sym_db.RegisterMessage(OefSearchMessage) _sym_db.RegisterMessage(OefSearchMessage.AgentsInfo) _sym_db.RegisterMessage(OefSearchMessage.Description) _sym_db.RegisterMessage(OefSearchMessage.OefErrorOperation) _sym_db.RegisterMessage(OefSearchMessage.Query) _sym_db.RegisterMessage(OefSearchMessage.Register_Service_Performative) _sym_db.RegisterMessage(OefSearchMessage.Unregister_Service_Performative) _sym_db.RegisterMessage(OefSearchMessage.Search_Services_Performative) _sym_db.RegisterMessage(OefSearchMessage.Search_Result_Performative) _sym_db.RegisterMessage(OefSearchMessage.Success_Performative) _sym_db.RegisterMessage(OefSearchMessage.Oef_Error_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _OEFSEARCHMESSAGE._serialized_start = 52 _OEFSEARCHMESSAGE._serialized_end = 1725 _OEFSEARCHMESSAGE_AGENTSINFO._serialized_start = 678 _OEFSEARCHMESSAGE_AGENTSINFO._serialized_end = 711 _OEFSEARCHMESSAGE_DESCRIPTION._serialized_start = 713 _OEFSEARCHMESSAGE_DESCRIPTION._serialized_end = 753 _OEFSEARCHMESSAGE_OEFERROROPERATION._serialized_start = 756 _OEFSEARCHMESSAGE_OEFERROROPERATION._serialized_end = 975 _OEFSEARCHMESSAGE_OEFERROROPERATION_OEFERRORENUM._serialized_start = 876 _OEFSEARCHMESSAGE_OEFERROROPERATION_OEFERRORENUM._serialized_end = 975 _OEFSEARCHMESSAGE_QUERY._serialized_start = 977 _OEFSEARCHMESSAGE_QUERY._serialized_end = 1005 _OEFSEARCHMESSAGE_REGISTER_SERVICE_PERFORMATIVE._serialized_start = 1007 _OEFSEARCHMESSAGE_REGISTER_SERVICE_PERFORMATIVE._serialized_end = 1128 _OEFSEARCHMESSAGE_UNREGISTER_SERVICE_PERFORMATIVE._serialized_start = 1130 _OEFSEARCHMESSAGE_UNREGISTER_SERVICE_PERFORMATIVE._serialized_end = 1253 _OEFSEARCHMESSAGE_SEARCH_SERVICES_PERFORMATIVE._serialized_start = 1255 _OEFSEARCHMESSAGE_SEARCH_SERVICES_PERFORMATIVE._serialized_end = 1355 _OEFSEARCHMESSAGE_SEARCH_RESULT_PERFORMATIVE._serialized_start = 1357 _OEFSEARCHMESSAGE_SEARCH_RESULT_PERFORMATIVE._serialized_end = 1482 _OEFSEARCHMESSAGE_SUCCESS_PERFORMATIVE._serialized_start = 1484 _OEFSEARCHMESSAGE_SUCCESS_PERFORMATIVE._serialized_end = 1587 _OEFSEARCHMESSAGE_OEF_ERROR_PERFORMATIVE._serialized_start = 1589 _OEFSEARCHMESSAGE_OEF_ERROR_PERFORMATIVE._serialized_end = 1709 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/oef_search/protocol.yaml ================================================ name: oef_search author: fetchai version: 1.1.7 protocol_specification_id: fetchai/oef_search:1.0.0 type: protocol description: A protocol for interacting with an OEF search service. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmZvdgb8Ns3YG1yVHEUHkmW7aLJRtKhSBRjcjLCZbfouGh __init__.py: QmWPibhs94rCa3Vi4kUQNm9Gw2DcWFaihifkz6FJamasB2 custom_types.py: QmX9ynYP53vEmENbextDTw9NRkrvbeS5PjnStqLUEDQLS2 dialogues.py: Qmf7epXX4QZqLEYKQdAieXpwCYKZCdRthDdZxfmpHsryaq message.py: QmYPaQXBTSSTrk8HUABxbTfFLQRca2pcVqYzicUvTks6gG oef_search.proto: QmaYkawAXEeeNuCcjmwcvdsttnE3owtuP9ouAYVyRu7M2J oef_search_pb2.py: QmSCvcwkLmwWESqiAs2Vj2yioUwLmdzMGaCDRo3sHT1ByL serialization.py: QmRNFL8Gf5pTQdALxkB6YBQumsoJDb9hiQ1CVApoU3qdJ6 fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/oef_search/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for oef_search protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.oef_search import oef_search_pb2 from packages.fetchai.protocols.oef_search.custom_types import ( AgentsInfo, Description, OefErrorOperation, Query, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage class OefSearchSerializer(Serializer): """Serialization for the 'oef_search' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'OefSearch' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(OefSearchMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() oef_search_msg = oef_search_pb2.OefSearchMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == OefSearchMessage.Performative.REGISTER_SERVICE: performative = oef_search_pb2.OefSearchMessage.Register_Service_Performative() # type: ignore service_description = msg.service_description Description.encode(performative.service_description, service_description) oef_search_msg.register_service.CopyFrom(performative) elif performative_id == OefSearchMessage.Performative.UNREGISTER_SERVICE: performative = oef_search_pb2.OefSearchMessage.Unregister_Service_Performative() # type: ignore service_description = msg.service_description Description.encode(performative.service_description, service_description) oef_search_msg.unregister_service.CopyFrom(performative) elif performative_id == OefSearchMessage.Performative.SEARCH_SERVICES: performative = oef_search_pb2.OefSearchMessage.Search_Services_Performative() # type: ignore query = msg.query Query.encode(performative.query, query) oef_search_msg.search_services.CopyFrom(performative) elif performative_id == OefSearchMessage.Performative.SEARCH_RESULT: performative = oef_search_pb2.OefSearchMessage.Search_Result_Performative() # type: ignore agents = msg.agents performative.agents.extend(agents) agents_info = msg.agents_info AgentsInfo.encode(performative.agents_info, agents_info) oef_search_msg.search_result.CopyFrom(performative) elif performative_id == OefSearchMessage.Performative.SUCCESS: performative = oef_search_pb2.OefSearchMessage.Success_Performative() # type: ignore agents_info = msg.agents_info AgentsInfo.encode(performative.agents_info, agents_info) oef_search_msg.success.CopyFrom(performative) elif performative_id == OefSearchMessage.Performative.OEF_ERROR: performative = oef_search_pb2.OefSearchMessage.Oef_Error_Performative() # type: ignore oef_error_operation = msg.oef_error_operation OefErrorOperation.encode( performative.oef_error_operation, oef_error_operation ) oef_search_msg.oef_error.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = oef_search_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'OefSearch' message. :param obj: the bytes object. :return: the 'OefSearch' message. """ message_pb = ProtobufMessage() oef_search_pb = oef_search_pb2.OefSearchMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target oef_search_pb.ParseFromString(message_pb.dialogue_message.content) performative = oef_search_pb.WhichOneof("performative") performative_id = OefSearchMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == OefSearchMessage.Performative.REGISTER_SERVICE: pb2_service_description = oef_search_pb.register_service.service_description service_description = Description.decode(pb2_service_description) performative_content["service_description"] = service_description elif performative_id == OefSearchMessage.Performative.UNREGISTER_SERVICE: pb2_service_description = ( oef_search_pb.unregister_service.service_description ) service_description = Description.decode(pb2_service_description) performative_content["service_description"] = service_description elif performative_id == OefSearchMessage.Performative.SEARCH_SERVICES: pb2_query = oef_search_pb.search_services.query query = Query.decode(pb2_query) performative_content["query"] = query elif performative_id == OefSearchMessage.Performative.SEARCH_RESULT: agents = oef_search_pb.search_result.agents agents_tuple = tuple(agents) performative_content["agents"] = agents_tuple pb2_agents_info = oef_search_pb.search_result.agents_info agents_info = AgentsInfo.decode(pb2_agents_info) performative_content["agents_info"] = agents_info elif performative_id == OefSearchMessage.Performative.SUCCESS: pb2_agents_info = oef_search_pb.success.agents_info agents_info = AgentsInfo.decode(pb2_agents_info) performative_content["agents_info"] = agents_info elif performative_id == OefSearchMessage.Performative.OEF_ERROR: pb2_oef_error_operation = oef_search_pb.oef_error.oef_error_operation oef_error_operation = OefErrorOperation.decode(pb2_oef_error_operation) performative_content["oef_error_operation"] = oef_error_operation else: raise ValueError("Performative not valid: {}.".format(performative_id)) return OefSearchMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/prometheus/README.md ================================================ # Prometheus Protocol ## Description This is a protocol for interacting with prometheus connection. ## Specification ```yaml --- name: prometheus author: fetchai version: 1.1.7 description: A protocol for adding and updating metrics to a prometheus server. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/prometheus:1.0.0 speech_acts: add_metric: type: pt:str title: pt:str description: pt:str labels: pt:dict[pt:str, pt:str] update_metric: title: pt:str callable: pt:str value: pt:float labels: pt:dict[pt:str, pt:str] response: code: pt:int message: pt:optional[pt:str] ... --- initiation: [add_metric, update_metric] reply: add_metric: [response] update_metric: [response] response: [] termination: [response] roles: {agent, server} end_states: [successful] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/prometheus/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the prometheus protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.protocols.prometheus.serialization import PrometheusSerializer PrometheusMessage.serializer = PrometheusSerializer ================================================ FILE: packages/fetchai/protocols/prometheus/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for prometheus dialogue management. - PrometheusDialogue: The dialogue class maintains state of a dialogue and manages it. - PrometheusDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.prometheus.message import PrometheusMessage class PrometheusDialogue(Dialogue): """The prometheus dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { PrometheusMessage.Performative.ADD_METRIC, PrometheusMessage.Performative.UPDATE_METRIC, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {PrometheusMessage.Performative.RESPONSE} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { PrometheusMessage.Performative.ADD_METRIC: frozenset( {PrometheusMessage.Performative.RESPONSE} ), PrometheusMessage.Performative.RESPONSE: frozenset(), PrometheusMessage.Performative.UPDATE_METRIC: frozenset( {PrometheusMessage.Performative.RESPONSE} ), } class Role(Dialogue.Role): """This class defines the agent's role in a prometheus dialogue.""" AGENT = "agent" SERVER = "server" class EndState(Dialogue.EndState): """This class defines the end states of a prometheus dialogue.""" SUCCESSFUL = 0 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[PrometheusMessage] = PrometheusMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class PrometheusDialogues(Dialogues, ABC): """This class keeps track of all prometheus dialogues.""" END_STATES = frozenset({PrometheusDialogue.EndState.SUCCESSFUL}) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[PrometheusDialogue] = PrometheusDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=PrometheusMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/prometheus/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains prometheus's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, Optional, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message _default_logger = logging.getLogger("aea.packages.fetchai.protocols.prometheus.message") DEFAULT_BODY_SIZE = 4 class PrometheusMessage(Message): """A protocol for adding and updating metrics to a prometheus server.""" protocol_id = PublicId.from_str("fetchai/prometheus:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/prometheus:1.0.0") class Performative(Message.Performative): """Performatives for the prometheus protocol.""" ADD_METRIC = "add_metric" RESPONSE = "response" UPDATE_METRIC = "update_metric" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"add_metric", "response", "update_metric"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "callable", "code", "description", "dialogue_reference", "labels", "message", "message_id", "performative", "target", "title", "type", "value", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of PrometheusMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=PrometheusMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(PrometheusMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def callable(self) -> str: """Get the 'callable' content from the message.""" enforce(self.is_set("callable"), "'callable' content is not set.") return cast(str, self.get("callable")) @property def code(self) -> int: """Get the 'code' content from the message.""" enforce(self.is_set("code"), "'code' content is not set.") return cast(int, self.get("code")) @property def description(self) -> str: """Get the 'description' content from the message.""" enforce(self.is_set("description"), "'description' content is not set.") return cast(str, self.get("description")) @property def labels(self) -> Dict[str, str]: """Get the 'labels' content from the message.""" enforce(self.is_set("labels"), "'labels' content is not set.") return cast(Dict[str, str], self.get("labels")) @property def message(self) -> Optional[str]: """Get the 'message' content from the message.""" return cast(Optional[str], self.get("message")) @property def title(self) -> str: """Get the 'title' content from the message.""" enforce(self.is_set("title"), "'title' content is not set.") return cast(str, self.get("title")) @property def type(self) -> str: """Get the 'type' content from the message.""" enforce(self.is_set("type"), "'type' content is not set.") return cast(str, self.get("type")) @property def value(self) -> float: """Get the 'value' content from the message.""" enforce(self.is_set("value"), "'value' content is not set.") return cast(float, self.get("value")) def _is_consistent(self) -> bool: """Check that the message follows the prometheus protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, PrometheusMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == PrometheusMessage.Performative.ADD_METRIC: expected_nb_of_contents = 4 enforce( isinstance(self.type, str), "Invalid type for content 'type'. Expected 'str'. Found '{}'.".format( type(self.type) ), ) enforce( isinstance(self.title, str), "Invalid type for content 'title'. Expected 'str'. Found '{}'.".format( type(self.title) ), ) enforce( isinstance(self.description, str), "Invalid type for content 'description'. Expected 'str'. Found '{}'.".format( type(self.description) ), ) enforce( isinstance(self.labels, dict), "Invalid type for content 'labels'. Expected 'dict'. Found '{}'.".format( type(self.labels) ), ) for key_of_labels, value_of_labels in self.labels.items(): enforce( isinstance(key_of_labels, str), "Invalid type for dictionary keys in content 'labels'. Expected 'str'. Found '{}'.".format( type(key_of_labels) ), ) enforce( isinstance(value_of_labels, str), "Invalid type for dictionary values in content 'labels'. Expected 'str'. Found '{}'.".format( type(value_of_labels) ), ) elif self.performative == PrometheusMessage.Performative.UPDATE_METRIC: expected_nb_of_contents = 4 enforce( isinstance(self.title, str), "Invalid type for content 'title'. Expected 'str'. Found '{}'.".format( type(self.title) ), ) enforce( isinstance(self.callable, str), "Invalid type for content 'callable'. Expected 'str'. Found '{}'.".format( type(self.callable) ), ) enforce( isinstance(self.value, float), "Invalid type for content 'value'. Expected 'float'. Found '{}'.".format( type(self.value) ), ) enforce( isinstance(self.labels, dict), "Invalid type for content 'labels'. Expected 'dict'. Found '{}'.".format( type(self.labels) ), ) for key_of_labels, value_of_labels in self.labels.items(): enforce( isinstance(key_of_labels, str), "Invalid type for dictionary keys in content 'labels'. Expected 'str'. Found '{}'.".format( type(key_of_labels) ), ) enforce( isinstance(value_of_labels, str), "Invalid type for dictionary values in content 'labels'. Expected 'str'. Found '{}'.".format( type(value_of_labels) ), ) elif self.performative == PrometheusMessage.Performative.RESPONSE: expected_nb_of_contents = 1 enforce( type(self.code) is int, "Invalid type for content 'code'. Expected 'int'. Found '{}'.".format( type(self.code) ), ) if self.is_set("message"): expected_nb_of_contents += 1 message = cast(str, self.message) enforce( isinstance(message, str), "Invalid type for content 'message'. Expected 'str'. Found '{}'.".format( type(message) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/prometheus/prometheus.proto ================================================ syntax = "proto3"; package aea.fetchai.prometheus.v1_0_0; message PrometheusMessage{ // Performatives and contents message Add_Metric_Performative{ string type = 1; string title = 2; string description = 3; map labels = 4; } message Update_Metric_Performative{ string title = 1; string callable = 2; float value = 3; map labels = 4; } message Response_Performative{ int64 code = 1; string message = 2; bool message_is_set = 3; } oneof performative{ Add_Metric_Performative add_metric = 5; Response_Performative response = 6; Update_Metric_Performative update_metric = 7; } } ================================================ FILE: packages/fetchai/protocols/prometheus/prometheus_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: prometheus.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x10prometheus.proto\x12\x1d\x61\x65\x61.fetchai.prometheus.v1_0_0"\xdf\x06\n\x11PrometheusMessage\x12^\n\nadd_metric\x18\x05 \x01(\x0b\x32H.aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Add_Metric_PerformativeH\x00\x12Z\n\x08response\x18\x06 \x01(\x0b\x32\x46.aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Response_PerformativeH\x00\x12\x64\n\rupdate_metric\x18\x07 \x01(\x0b\x32K.aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Update_Metric_PerformativeH\x00\x1a\xe0\x01\n\x17\x41\x64\x64_Metric_Performative\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x64\n\x06labels\x18\x04 \x03(\x0b\x32T.aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Add_Metric_Performative.LabelsEntry\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xe4\x01\n\x1aUpdate_Metric_Performative\x12\r\n\x05title\x18\x01 \x01(\t\x12\x10\n\x08\x63\x61llable\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\x02\x12g\n\x06labels\x18\x04 \x03(\x0b\x32W.aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Update_Metric_Performative.LabelsEntry\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1aN\n\x15Response_Performative\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x03\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x16\n\x0emessage_is_set\x18\x03 \x01(\x08\x42\x0e\n\x0cperformativeb\x06proto3' ) _PROMETHEUSMESSAGE = DESCRIPTOR.message_types_by_name["PrometheusMessage"] _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE = _PROMETHEUSMESSAGE.nested_types_by_name[ "Add_Metric_Performative" ] _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE_LABELSENTRY = ( _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE.nested_types_by_name["LabelsEntry"] ) _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE = _PROMETHEUSMESSAGE.nested_types_by_name[ "Update_Metric_Performative" ] _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE_LABELSENTRY = ( _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE.nested_types_by_name["LabelsEntry"] ) _PROMETHEUSMESSAGE_RESPONSE_PERFORMATIVE = _PROMETHEUSMESSAGE.nested_types_by_name[ "Response_Performative" ] PrometheusMessage = _reflection.GeneratedProtocolMessageType( "PrometheusMessage", (_message.Message,), { "Add_Metric_Performative": _reflection.GeneratedProtocolMessageType( "Add_Metric_Performative", (_message.Message,), { "LabelsEntry": _reflection.GeneratedProtocolMessageType( "LabelsEntry", (_message.Message,), { "DESCRIPTOR": _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE_LABELSENTRY, "__module__": "prometheus_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Add_Metric_Performative.LabelsEntry) }, ), "DESCRIPTOR": _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE, "__module__": "prometheus_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Add_Metric_Performative) }, ), "Update_Metric_Performative": _reflection.GeneratedProtocolMessageType( "Update_Metric_Performative", (_message.Message,), { "LabelsEntry": _reflection.GeneratedProtocolMessageType( "LabelsEntry", (_message.Message,), { "DESCRIPTOR": _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE_LABELSENTRY, "__module__": "prometheus_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Update_Metric_Performative.LabelsEntry) }, ), "DESCRIPTOR": _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE, "__module__": "prometheus_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Update_Metric_Performative) }, ), "Response_Performative": _reflection.GeneratedProtocolMessageType( "Response_Performative", (_message.Message,), { "DESCRIPTOR": _PROMETHEUSMESSAGE_RESPONSE_PERFORMATIVE, "__module__": "prometheus_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.prometheus.v1_0_0.PrometheusMessage.Response_Performative) }, ), "DESCRIPTOR": _PROMETHEUSMESSAGE, "__module__": "prometheus_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.prometheus.v1_0_0.PrometheusMessage) }, ) _sym_db.RegisterMessage(PrometheusMessage) _sym_db.RegisterMessage(PrometheusMessage.Add_Metric_Performative) _sym_db.RegisterMessage(PrometheusMessage.Add_Metric_Performative.LabelsEntry) _sym_db.RegisterMessage(PrometheusMessage.Update_Metric_Performative) _sym_db.RegisterMessage(PrometheusMessage.Update_Metric_Performative.LabelsEntry) _sym_db.RegisterMessage(PrometheusMessage.Response_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE_LABELSENTRY._options = None _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE_LABELSENTRY._serialized_options = ( b"8\001" ) _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE_LABELSENTRY._options = None _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE_LABELSENTRY._serialized_options = ( b"8\001" ) _PROMETHEUSMESSAGE._serialized_start = 52 _PROMETHEUSMESSAGE._serialized_end = 915 _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE._serialized_start = 364 _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE._serialized_end = 588 _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE_LABELSENTRY._serialized_start = 543 _PROMETHEUSMESSAGE_ADD_METRIC_PERFORMATIVE_LABELSENTRY._serialized_end = 588 _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE._serialized_start = 591 _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE._serialized_end = 819 _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE_LABELSENTRY._serialized_start = 543 _PROMETHEUSMESSAGE_UPDATE_METRIC_PERFORMATIVE_LABELSENTRY._serialized_end = 588 _PROMETHEUSMESSAGE_RESPONSE_PERFORMATIVE._serialized_start = 821 _PROMETHEUSMESSAGE_RESPONSE_PERFORMATIVE._serialized_end = 899 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/prometheus/protocol.yaml ================================================ name: prometheus author: fetchai version: 1.1.7 protocol_specification_id: fetchai/prometheus:1.0.0 type: protocol description: A protocol for adding and updating metrics to a prometheus server. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmQXXjGuq8W29ZJSSRrVyFHEn943hdoiWzQBvtwUufN1eH __init__.py: QmTa48qrS3hbw1t1VEXieZ5pRpCTpR2gRWY92ecNyfRtFy dialogues.py: QmNwiRofM3HPmxs3SSEsr8ZHuyXEPJVkYTApMSgczisZ9H message.py: QmPbTxtc4ctk5h8Yk3KCkT2uHpezsmgLYkjCPQDNAxm8x1 prometheus.proto: QmXzFWmrWVqQuxtVgaZwuMgbrEvSRrRVU63htURUsFJ1wv prometheus_pb2.py: QmNuDYT7RWNRmRKSMgsLqD76gmmNSNm88uTCSPcvoxTdHr serialization.py: Qmf7WvXADqkDpQbjD6nZ3n7iKg5jsiqGTR91AWRqSKhiAq fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/prometheus/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for prometheus protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.prometheus import prometheus_pb2 from packages.fetchai.protocols.prometheus.message import PrometheusMessage class PrometheusSerializer(Serializer): """Serialization for the 'prometheus' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Prometheus' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(PrometheusMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() prometheus_msg = prometheus_pb2.PrometheusMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == PrometheusMessage.Performative.ADD_METRIC: performative = prometheus_pb2.PrometheusMessage.Add_Metric_Performative() # type: ignore type = msg.type performative.type = type title = msg.title performative.title = title description = msg.description performative.description = description labels = msg.labels performative.labels.update(labels) prometheus_msg.add_metric.CopyFrom(performative) elif performative_id == PrometheusMessage.Performative.UPDATE_METRIC: performative = prometheus_pb2.PrometheusMessage.Update_Metric_Performative() # type: ignore title = msg.title performative.title = title callable = msg.callable performative.callable = callable value = msg.value performative.value = value labels = msg.labels performative.labels.update(labels) prometheus_msg.update_metric.CopyFrom(performative) elif performative_id == PrometheusMessage.Performative.RESPONSE: performative = prometheus_pb2.PrometheusMessage.Response_Performative() # type: ignore code = msg.code performative.code = code if msg.is_set("message"): performative.message_is_set = True message = msg.message performative.message = message prometheus_msg.response.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = prometheus_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Prometheus' message. :param obj: the bytes object. :return: the 'Prometheus' message. """ message_pb = ProtobufMessage() prometheus_pb = prometheus_pb2.PrometheusMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target prometheus_pb.ParseFromString(message_pb.dialogue_message.content) performative = prometheus_pb.WhichOneof("performative") performative_id = PrometheusMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == PrometheusMessage.Performative.ADD_METRIC: type = prometheus_pb.add_metric.type performative_content["type"] = type title = prometheus_pb.add_metric.title performative_content["title"] = title description = prometheus_pb.add_metric.description performative_content["description"] = description labels = prometheus_pb.add_metric.labels labels_dict = dict(labels) performative_content["labels"] = labels_dict elif performative_id == PrometheusMessage.Performative.UPDATE_METRIC: title = prometheus_pb.update_metric.title performative_content["title"] = title callable = prometheus_pb.update_metric.callable performative_content["callable"] = callable value = prometheus_pb.update_metric.value performative_content["value"] = value labels = prometheus_pb.update_metric.labels labels_dict = dict(labels) performative_content["labels"] = labels_dict elif performative_id == PrometheusMessage.Performative.RESPONSE: code = prometheus_pb.response.code performative_content["code"] = code if prometheus_pb.response.message_is_set: message = prometheus_pb.response.message performative_content["message"] = message else: raise ValueError("Performative not valid: {}.".format(performative_id)) return PrometheusMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/register/README.md ================================================ # Register Protocol ## Description This is a protocol for communication between two AEAs for registration. ## Specification ```yaml --- name: register author: fetchai version: 1.1.7 description: A protocol for communication between two AEAs for registration. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/register:1.0.0 speech_acts: register: info: pt:dict[pt:str, pt:str] success: info: pt:dict[pt:str, pt:str] error: error_code: pt:int error_msg: pt:str info: pt:dict[pt:str, pt:str] ... --- initiation: [register] reply: register: [success, error] success: [] error: [] termination: [success, error] roles: {agent} end_states: [successful, failed] keep_terminal_state_dialogues: true ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/register/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the register protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.register.serialization import RegisterSerializer RegisterMessage.serializer = RegisterSerializer ================================================ FILE: packages/fetchai/protocols/register/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for register dialogue management. - RegisterDialogue: The dialogue class maintains state of a dialogue and manages it. - RegisterDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.register.message import RegisterMessage class RegisterDialogue(Dialogue): """The register dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {RegisterMessage.Performative.REGISTER} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {RegisterMessage.Performative.SUCCESS, RegisterMessage.Performative.ERROR} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { RegisterMessage.Performative.ERROR: frozenset(), RegisterMessage.Performative.REGISTER: frozenset( {RegisterMessage.Performative.SUCCESS, RegisterMessage.Performative.ERROR} ), RegisterMessage.Performative.SUCCESS: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a register dialogue.""" AGENT = "agent" class EndState(Dialogue.EndState): """This class defines the end states of a register dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[RegisterMessage] = RegisterMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class RegisterDialogues(Dialogues, ABC): """This class keeps track of all register dialogues.""" END_STATES = frozenset( {RegisterDialogue.EndState.SUCCESSFUL, RegisterDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = True def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[RegisterDialogue] = RegisterDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=RegisterMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/register/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains register's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message _default_logger = logging.getLogger("aea.packages.fetchai.protocols.register.message") DEFAULT_BODY_SIZE = 4 class RegisterMessage(Message): """A protocol for communication between two AEAs for registration.""" protocol_id = PublicId.from_str("fetchai/register:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/register:1.0.0") class Performative(Message.Performative): """Performatives for the register protocol.""" ERROR = "error" REGISTER = "register" SUCCESS = "success" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"error", "register", "success"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "dialogue_reference", "error_code", "error_msg", "info", "message_id", "performative", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of RegisterMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=RegisterMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(RegisterMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def error_code(self) -> int: """Get the 'error_code' content from the message.""" enforce(self.is_set("error_code"), "'error_code' content is not set.") return cast(int, self.get("error_code")) @property def error_msg(self) -> str: """Get the 'error_msg' content from the message.""" enforce(self.is_set("error_msg"), "'error_msg' content is not set.") return cast(str, self.get("error_msg")) @property def info(self) -> Dict[str, str]: """Get the 'info' content from the message.""" enforce(self.is_set("info"), "'info' content is not set.") return cast(Dict[str, str], self.get("info")) def _is_consistent(self) -> bool: """Check that the message follows the register protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, RegisterMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == RegisterMessage.Performative.REGISTER: expected_nb_of_contents = 1 enforce( isinstance(self.info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(self.info) ), ) for key_of_info, value_of_info in self.info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) elif self.performative == RegisterMessage.Performative.SUCCESS: expected_nb_of_contents = 1 enforce( isinstance(self.info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(self.info) ), ) for key_of_info, value_of_info in self.info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) elif self.performative == RegisterMessage.Performative.ERROR: expected_nb_of_contents = 3 enforce( type(self.error_code) is int, "Invalid type for content 'error_code'. Expected 'int'. Found '{}'.".format( type(self.error_code) ), ) enforce( isinstance(self.error_msg, str), "Invalid type for content 'error_msg'. Expected 'str'. Found '{}'.".format( type(self.error_msg) ), ) enforce( isinstance(self.info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(self.info) ), ) for key_of_info, value_of_info in self.info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/register/protocol.yaml ================================================ name: register author: fetchai version: 1.1.7 protocol_specification_id: fetchai/register:1.0.0 type: protocol description: A protocol for communication between two AEAs for registration. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmVrcrKFvVrTDLiYDMrcEeTrujcg7r1SXyRAa1H1FbWTLa __init__.py: QmWc6YpAxjsvoQSqhzREAijdZyp1VLgsE5uyp9NXU3H8v1 dialogues.py: QmVgE32GY8hLMnZ7xeceBL4zUo9eGb6eD1f4Le6Rx7wnes message.py: QmZwiWBzBF4PHj4j5A9WXYhkhKUTH96azMdVs3WRm26f46 register.proto: QmarDZqqxezQX3XLLxWigC32S5Aiu367XjHVMKCjXcpfeN register_pb2.py: QmRisHAzYZK38NFaW4kfhYQx6VpTQnKN7hepxV83LgFW91 serialization.py: QmQQPj3hBPTBLJUFVdgvaev4ezxsFrm5G47uGfPpfvzywK fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/register/register.proto ================================================ syntax = "proto3"; package aea.fetchai.register.v1_0_0; message RegisterMessage{ // Performatives and contents message Register_Performative{ map info = 1; } message Success_Performative{ map info = 1; } message Error_Performative{ int64 error_code = 1; string error_msg = 2; map info = 3; } oneof performative{ Error_Performative error = 5; Register_Performative register = 6; Success_Performative success = 7; } } ================================================ FILE: packages/fetchai/protocols/register/register_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: register.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0eregister.proto\x12\x1b\x61\x65\x61.fetchai.register.v1_0_0"\xa9\x06\n\x0fRegisterMessage\x12P\n\x05\x65rror\x18\x05 \x01(\x0b\x32?.aea.fetchai.register.v1_0_0.RegisterMessage.Error_PerformativeH\x00\x12V\n\x08register\x18\x06 \x01(\x0b\x32\x42.aea.fetchai.register.v1_0_0.RegisterMessage.Register_PerformativeH\x00\x12T\n\x07success\x18\x07 \x01(\x0b\x32\x41.aea.fetchai.register.v1_0_0.RegisterMessage.Success_PerformativeH\x00\x1a\xa0\x01\n\x15Register_Performative\x12Z\n\x04info\x18\x01 \x03(\x0b\x32L.aea.fetchai.register.v1_0_0.RegisterMessage.Register_Performative.InfoEntry\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x9e\x01\n\x14Success_Performative\x12Y\n\x04info\x18\x01 \x03(\x0b\x32K.aea.fetchai.register.v1_0_0.RegisterMessage.Success_Performative.InfoEntry\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xc1\x01\n\x12\x45rror_Performative\x12\x12\n\nerror_code\x18\x01 \x01(\x03\x12\x11\n\terror_msg\x18\x02 \x01(\t\x12W\n\x04info\x18\x03 \x03(\x0b\x32I.aea.fetchai.register.v1_0_0.RegisterMessage.Error_Performative.InfoEntry\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0cperformativeb\x06proto3' ) _REGISTERMESSAGE = DESCRIPTOR.message_types_by_name["RegisterMessage"] _REGISTERMESSAGE_REGISTER_PERFORMATIVE = _REGISTERMESSAGE.nested_types_by_name[ "Register_Performative" ] _REGISTERMESSAGE_REGISTER_PERFORMATIVE_INFOENTRY = ( _REGISTERMESSAGE_REGISTER_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) _REGISTERMESSAGE_SUCCESS_PERFORMATIVE = _REGISTERMESSAGE.nested_types_by_name[ "Success_Performative" ] _REGISTERMESSAGE_SUCCESS_PERFORMATIVE_INFOENTRY = ( _REGISTERMESSAGE_SUCCESS_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) _REGISTERMESSAGE_ERROR_PERFORMATIVE = _REGISTERMESSAGE.nested_types_by_name[ "Error_Performative" ] _REGISTERMESSAGE_ERROR_PERFORMATIVE_INFOENTRY = ( _REGISTERMESSAGE_ERROR_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) RegisterMessage = _reflection.GeneratedProtocolMessageType( "RegisterMessage", (_message.Message,), { "Register_Performative": _reflection.GeneratedProtocolMessageType( "Register_Performative", (_message.Message,), { "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _REGISTERMESSAGE_REGISTER_PERFORMATIVE_INFOENTRY, "__module__": "register_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.register.v1_0_0.RegisterMessage.Register_Performative.InfoEntry) }, ), "DESCRIPTOR": _REGISTERMESSAGE_REGISTER_PERFORMATIVE, "__module__": "register_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.register.v1_0_0.RegisterMessage.Register_Performative) }, ), "Success_Performative": _reflection.GeneratedProtocolMessageType( "Success_Performative", (_message.Message,), { "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _REGISTERMESSAGE_SUCCESS_PERFORMATIVE_INFOENTRY, "__module__": "register_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.register.v1_0_0.RegisterMessage.Success_Performative.InfoEntry) }, ), "DESCRIPTOR": _REGISTERMESSAGE_SUCCESS_PERFORMATIVE, "__module__": "register_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.register.v1_0_0.RegisterMessage.Success_Performative) }, ), "Error_Performative": _reflection.GeneratedProtocolMessageType( "Error_Performative", (_message.Message,), { "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _REGISTERMESSAGE_ERROR_PERFORMATIVE_INFOENTRY, "__module__": "register_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.register.v1_0_0.RegisterMessage.Error_Performative.InfoEntry) }, ), "DESCRIPTOR": _REGISTERMESSAGE_ERROR_PERFORMATIVE, "__module__": "register_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.register.v1_0_0.RegisterMessage.Error_Performative) }, ), "DESCRIPTOR": _REGISTERMESSAGE, "__module__": "register_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.register.v1_0_0.RegisterMessage) }, ) _sym_db.RegisterMessage(RegisterMessage) _sym_db.RegisterMessage(RegisterMessage.Register_Performative) _sym_db.RegisterMessage(RegisterMessage.Register_Performative.InfoEntry) _sym_db.RegisterMessage(RegisterMessage.Success_Performative) _sym_db.RegisterMessage(RegisterMessage.Success_Performative.InfoEntry) _sym_db.RegisterMessage(RegisterMessage.Error_Performative) _sym_db.RegisterMessage(RegisterMessage.Error_Performative.InfoEntry) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _REGISTERMESSAGE_REGISTER_PERFORMATIVE_INFOENTRY._options = None _REGISTERMESSAGE_REGISTER_PERFORMATIVE_INFOENTRY._serialized_options = b"8\001" _REGISTERMESSAGE_SUCCESS_PERFORMATIVE_INFOENTRY._options = None _REGISTERMESSAGE_SUCCESS_PERFORMATIVE_INFOENTRY._serialized_options = b"8\001" _REGISTERMESSAGE_ERROR_PERFORMATIVE_INFOENTRY._options = None _REGISTERMESSAGE_ERROR_PERFORMATIVE_INFOENTRY._serialized_options = b"8\001" _REGISTERMESSAGE._serialized_start = 48 _REGISTERMESSAGE._serialized_end = 857 _REGISTERMESSAGE_REGISTER_PERFORMATIVE._serialized_start = 324 _REGISTERMESSAGE_REGISTER_PERFORMATIVE._serialized_end = 484 _REGISTERMESSAGE_REGISTER_PERFORMATIVE_INFOENTRY._serialized_start = 441 _REGISTERMESSAGE_REGISTER_PERFORMATIVE_INFOENTRY._serialized_end = 484 _REGISTERMESSAGE_SUCCESS_PERFORMATIVE._serialized_start = 487 _REGISTERMESSAGE_SUCCESS_PERFORMATIVE._serialized_end = 645 _REGISTERMESSAGE_SUCCESS_PERFORMATIVE_INFOENTRY._serialized_start = 441 _REGISTERMESSAGE_SUCCESS_PERFORMATIVE_INFOENTRY._serialized_end = 484 _REGISTERMESSAGE_ERROR_PERFORMATIVE._serialized_start = 648 _REGISTERMESSAGE_ERROR_PERFORMATIVE._serialized_end = 841 _REGISTERMESSAGE_ERROR_PERFORMATIVE_INFOENTRY._serialized_start = 441 _REGISTERMESSAGE_ERROR_PERFORMATIVE_INFOENTRY._serialized_end = 484 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/register/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for register protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.register import register_pb2 from packages.fetchai.protocols.register.message import RegisterMessage class RegisterSerializer(Serializer): """Serialization for the 'register' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Register' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(RegisterMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() register_msg = register_pb2.RegisterMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == RegisterMessage.Performative.REGISTER: performative = register_pb2.RegisterMessage.Register_Performative() # type: ignore info = msg.info performative.info.update(info) register_msg.register.CopyFrom(performative) elif performative_id == RegisterMessage.Performative.SUCCESS: performative = register_pb2.RegisterMessage.Success_Performative() # type: ignore info = msg.info performative.info.update(info) register_msg.success.CopyFrom(performative) elif performative_id == RegisterMessage.Performative.ERROR: performative = register_pb2.RegisterMessage.Error_Performative() # type: ignore error_code = msg.error_code performative.error_code = error_code error_msg = msg.error_msg performative.error_msg = error_msg info = msg.info performative.info.update(info) register_msg.error.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = register_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Register' message. :param obj: the bytes object. :return: the 'Register' message. """ message_pb = ProtobufMessage() register_pb = register_pb2.RegisterMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target register_pb.ParseFromString(message_pb.dialogue_message.content) performative = register_pb.WhichOneof("performative") performative_id = RegisterMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == RegisterMessage.Performative.REGISTER: info = register_pb.register.info info_dict = dict(info) performative_content["info"] = info_dict elif performative_id == RegisterMessage.Performative.SUCCESS: info = register_pb.success.info info_dict = dict(info) performative_content["info"] = info_dict elif performative_id == RegisterMessage.Performative.ERROR: error_code = register_pb.error.error_code performative_content["error_code"] = error_code error_msg = register_pb.error.error_msg performative_content["error_msg"] = error_msg info = register_pb.error.info info_dict = dict(info) performative_content["info"] = info_dict else: raise ValueError("Performative not valid: {}.".format(performative_id)) return RegisterMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/signing/README.md ================================================ # Signing Protocol ## Description This is a protocol for communication between a skill and a decision maker. ## Specification ```yaml --- name: signing author: fetchai version: 1.1.7 description: A protocol for communication between skills and decision maker. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/signing:1.0.0 speech_acts: sign_transaction: terms: ct:Terms raw_transaction: ct:RawTransaction sign_message: terms: ct:Terms raw_message: ct:RawMessage signed_transaction: signed_transaction: ct:SignedTransaction signed_message: signed_message: ct:SignedMessage error: error_code: ct:ErrorCode ... --- ct:ErrorCode: | enum ErrorCodeEnum { UNSUCCESSFUL_MESSAGE_SIGNING = 0; UNSUCCESSFUL_TRANSACTION_SIGNING = 1; } ErrorCodeEnum error_code = 1; ct:RawMessage: | bytes raw_message = 1; ct:RawTransaction: | bytes raw_transaction = 1; ct:SignedMessage: | bytes signed_message = 1; ct:SignedTransaction: | bytes signed_transaction = 1; ct:Terms: | bytes terms = 1; ... --- initiation: [sign_transaction, sign_message] reply: sign_transaction: [signed_transaction, error] sign_message: [signed_message, error] signed_transaction: [] signed_message: [] error: [] termination: [signed_transaction, signed_message, error] roles: {skill, decision_maker} end_states: [successful, failed] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/signing/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the signing protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.protocols.signing.serialization import SigningSerializer SigningMessage.serializer = SigningSerializer ================================================ FILE: packages/fetchai/protocols/signing/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from enum import Enum from typing import Any from aea.helpers.transaction.base import RawMessage as BaseRawMessage from aea.helpers.transaction.base import RawTransaction as BaseRawTransaction from aea.helpers.transaction.base import SignedMessage as BaseSignedMessage from aea.helpers.transaction.base import SignedTransaction as BaseSignedTransaction from aea.helpers.transaction.base import Terms as BaseTerms class ErrorCode(Enum): """This class represents an instance of ErrorCode.""" UNSUCCESSFUL_MESSAGE_SIGNING = 0 UNSUCCESSFUL_TRANSACTION_SIGNING = 1 @staticmethod def encode(error_code_protobuf_object: Any, error_code_object: "ErrorCode") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the error_code_protobuf_object argument is matched with the instance of this class in the 'error_code_object' argument. :param error_code_protobuf_object: the protocol buffer object whose type corresponds with this class. :param error_code_object: an instance of this class to be encoded in the protocol buffer object. """ error_code_protobuf_object.error_code = error_code_object.value @classmethod def decode(cls, error_code_protobuf_object: Any) -> "ErrorCode": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'error_code_protobuf_object' argument. :param error_code_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'error_code_protobuf_object' argument. """ enum_value_from_pb2 = error_code_protobuf_object.error_code return ErrorCode(enum_value_from_pb2) RawMessage = BaseRawMessage RawTransaction = BaseRawTransaction SignedMessage = BaseSignedMessage SignedTransaction = BaseSignedTransaction Terms = BaseTerms ================================================ FILE: packages/fetchai/protocols/signing/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for signing dialogue management. - SigningDialogue: The dialogue class maintains state of a dialogue and manages it. - SigningDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.signing.message import SigningMessage class SigningDialogue(Dialogue): """The signing dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { SigningMessage.Performative.SIGN_TRANSACTION, SigningMessage.Performative.SIGN_MESSAGE, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { SigningMessage.Performative.SIGNED_TRANSACTION, SigningMessage.Performative.SIGNED_MESSAGE, SigningMessage.Performative.ERROR, } ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { SigningMessage.Performative.ERROR: frozenset(), SigningMessage.Performative.SIGN_MESSAGE: frozenset( { SigningMessage.Performative.SIGNED_MESSAGE, SigningMessage.Performative.ERROR, } ), SigningMessage.Performative.SIGN_TRANSACTION: frozenset( { SigningMessage.Performative.SIGNED_TRANSACTION, SigningMessage.Performative.ERROR, } ), SigningMessage.Performative.SIGNED_MESSAGE: frozenset(), SigningMessage.Performative.SIGNED_TRANSACTION: frozenset(), } class Role(Dialogue.Role): """This class defines the agent's role in a signing dialogue.""" DECISION_MAKER = "decision_maker" SKILL = "skill" class EndState(Dialogue.EndState): """This class defines the end states of a signing dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class SigningDialogues(Dialogues, ABC): """This class keeps track of all signing dialogues.""" END_STATES = frozenset( {SigningDialogue.EndState.SUCCESSFUL, SigningDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[SigningDialogue] = SigningDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=SigningMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/signing/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains signing's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.signing.custom_types import ErrorCode as CustomErrorCode from packages.fetchai.protocols.signing.custom_types import ( RawMessage as CustomRawMessage, ) from packages.fetchai.protocols.signing.custom_types import ( RawTransaction as CustomRawTransaction, ) from packages.fetchai.protocols.signing.custom_types import ( SignedMessage as CustomSignedMessage, ) from packages.fetchai.protocols.signing.custom_types import ( SignedTransaction as CustomSignedTransaction, ) from packages.fetchai.protocols.signing.custom_types import Terms as CustomTerms _default_logger = logging.getLogger("aea.packages.fetchai.protocols.signing.message") DEFAULT_BODY_SIZE = 4 class SigningMessage(Message): """A protocol for communication between skills and decision maker.""" protocol_id = PublicId.from_str("fetchai/signing:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/signing:1.0.0") ErrorCode = CustomErrorCode RawMessage = CustomRawMessage RawTransaction = CustomRawTransaction SignedMessage = CustomSignedMessage SignedTransaction = CustomSignedTransaction Terms = CustomTerms class Performative(Message.Performative): """Performatives for the signing protocol.""" ERROR = "error" SIGN_MESSAGE = "sign_message" SIGN_TRANSACTION = "sign_transaction" SIGNED_MESSAGE = "signed_message" SIGNED_TRANSACTION = "signed_transaction" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "error", "sign_message", "sign_transaction", "signed_message", "signed_transaction", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "dialogue_reference", "error_code", "message_id", "performative", "raw_message", "raw_transaction", "signed_message", "signed_transaction", "target", "terms", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of SigningMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=SigningMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(SigningMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def error_code(self) -> CustomErrorCode: """Get the 'error_code' content from the message.""" enforce(self.is_set("error_code"), "'error_code' content is not set.") return cast(CustomErrorCode, self.get("error_code")) @property def raw_message(self) -> CustomRawMessage: """Get the 'raw_message' content from the message.""" enforce(self.is_set("raw_message"), "'raw_message' content is not set.") return cast(CustomRawMessage, self.get("raw_message")) @property def raw_transaction(self) -> CustomRawTransaction: """Get the 'raw_transaction' content from the message.""" enforce(self.is_set("raw_transaction"), "'raw_transaction' content is not set.") return cast(CustomRawTransaction, self.get("raw_transaction")) @property def signed_message(self) -> CustomSignedMessage: """Get the 'signed_message' content from the message.""" enforce(self.is_set("signed_message"), "'signed_message' content is not set.") return cast(CustomSignedMessage, self.get("signed_message")) @property def signed_transaction(self) -> CustomSignedTransaction: """Get the 'signed_transaction' content from the message.""" enforce( self.is_set("signed_transaction"), "'signed_transaction' content is not set.", ) return cast(CustomSignedTransaction, self.get("signed_transaction")) @property def terms(self) -> CustomTerms: """Get the 'terms' content from the message.""" enforce(self.is_set("terms"), "'terms' content is not set.") return cast(CustomTerms, self.get("terms")) def _is_consistent(self) -> bool: """Check that the message follows the signing protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, SigningMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == SigningMessage.Performative.SIGN_TRANSACTION: expected_nb_of_contents = 2 enforce( isinstance(self.terms, CustomTerms), "Invalid type for content 'terms'. Expected 'Terms'. Found '{}'.".format( type(self.terms) ), ) enforce( isinstance(self.raw_transaction, CustomRawTransaction), "Invalid type for content 'raw_transaction'. Expected 'RawTransaction'. Found '{}'.".format( type(self.raw_transaction) ), ) elif self.performative == SigningMessage.Performative.SIGN_MESSAGE: expected_nb_of_contents = 2 enforce( isinstance(self.terms, CustomTerms), "Invalid type for content 'terms'. Expected 'Terms'. Found '{}'.".format( type(self.terms) ), ) enforce( isinstance(self.raw_message, CustomRawMessage), "Invalid type for content 'raw_message'. Expected 'RawMessage'. Found '{}'.".format( type(self.raw_message) ), ) elif self.performative == SigningMessage.Performative.SIGNED_TRANSACTION: expected_nb_of_contents = 1 enforce( isinstance(self.signed_transaction, CustomSignedTransaction), "Invalid type for content 'signed_transaction'. Expected 'SignedTransaction'. Found '{}'.".format( type(self.signed_transaction) ), ) elif self.performative == SigningMessage.Performative.SIGNED_MESSAGE: expected_nb_of_contents = 1 enforce( isinstance(self.signed_message, CustomSignedMessage), "Invalid type for content 'signed_message'. Expected 'SignedMessage'. Found '{}'.".format( type(self.signed_message) ), ) elif self.performative == SigningMessage.Performative.ERROR: expected_nb_of_contents = 1 enforce( isinstance(self.error_code, CustomErrorCode), "Invalid type for content 'error_code'. Expected 'ErrorCode'. Found '{}'.".format( type(self.error_code) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/signing/protocol.yaml ================================================ name: signing author: fetchai version: 1.1.7 protocol_specification_id: fetchai/signing:1.0.0 type: protocol description: A protocol for communication between skills and decision maker. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmWWjq6ppoaiGG46yUQWbgrN78fF3iKcuk5Zn6L7pkVdtA __init__.py: QmQwA8M3CvBeWabUAWUC6Mmt5bUgqQK8TAHt62JpZzsSt1 custom_types.py: QmbYjhUzvTMv6kU9UbuGLyVGfyurZPgX8PBGZGj58jDHv3 dialogues.py: QmQ9LyN1FamvMZ1o7Pt9H69bZZVY1Yxgm7HhyeN4fSXEUa message.py: QmeugK5PynXtgXgRbvjVyQCBXWwVdvnQvNgWLYScqLhFn1 serialization.py: QmPeHDs5eXVApHLj6Xecc6jqBC8LDrmUJcKRgVYDNQ9W2q signing.proto: QmbHQYswu1d5JTq8QD3WY9Trw7CwCFbv4c1wmgwiZC5756 signing_pb2.py: QmaYoSC2uxSATBzNY9utPp7J78drWgyYocr4cPjEhyLf7y fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/signing/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for signing protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.signing import signing_pb2 from packages.fetchai.protocols.signing.custom_types import ( ErrorCode, RawMessage, RawTransaction, SignedMessage, SignedTransaction, Terms, ) from packages.fetchai.protocols.signing.message import SigningMessage class SigningSerializer(Serializer): """Serialization for the 'signing' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Signing' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(SigningMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() signing_msg = signing_pb2.SigningMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == SigningMessage.Performative.SIGN_TRANSACTION: performative = signing_pb2.SigningMessage.Sign_Transaction_Performative() # type: ignore terms = msg.terms Terms.encode(performative.terms, terms) raw_transaction = msg.raw_transaction RawTransaction.encode(performative.raw_transaction, raw_transaction) signing_msg.sign_transaction.CopyFrom(performative) elif performative_id == SigningMessage.Performative.SIGN_MESSAGE: performative = signing_pb2.SigningMessage.Sign_Message_Performative() # type: ignore terms = msg.terms Terms.encode(performative.terms, terms) raw_message = msg.raw_message RawMessage.encode(performative.raw_message, raw_message) signing_msg.sign_message.CopyFrom(performative) elif performative_id == SigningMessage.Performative.SIGNED_TRANSACTION: performative = signing_pb2.SigningMessage.Signed_Transaction_Performative() # type: ignore signed_transaction = msg.signed_transaction SignedTransaction.encode( performative.signed_transaction, signed_transaction ) signing_msg.signed_transaction.CopyFrom(performative) elif performative_id == SigningMessage.Performative.SIGNED_MESSAGE: performative = signing_pb2.SigningMessage.Signed_Message_Performative() # type: ignore signed_message = msg.signed_message SignedMessage.encode(performative.signed_message, signed_message) signing_msg.signed_message.CopyFrom(performative) elif performative_id == SigningMessage.Performative.ERROR: performative = signing_pb2.SigningMessage.Error_Performative() # type: ignore error_code = msg.error_code ErrorCode.encode(performative.error_code, error_code) signing_msg.error.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = signing_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Signing' message. :param obj: the bytes object. :return: the 'Signing' message. """ message_pb = ProtobufMessage() signing_pb = signing_pb2.SigningMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target signing_pb.ParseFromString(message_pb.dialogue_message.content) performative = signing_pb.WhichOneof("performative") performative_id = SigningMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == SigningMessage.Performative.SIGN_TRANSACTION: pb2_terms = signing_pb.sign_transaction.terms terms = Terms.decode(pb2_terms) performative_content["terms"] = terms pb2_raw_transaction = signing_pb.sign_transaction.raw_transaction raw_transaction = RawTransaction.decode(pb2_raw_transaction) performative_content["raw_transaction"] = raw_transaction elif performative_id == SigningMessage.Performative.SIGN_MESSAGE: pb2_terms = signing_pb.sign_message.terms terms = Terms.decode(pb2_terms) performative_content["terms"] = terms pb2_raw_message = signing_pb.sign_message.raw_message raw_message = RawMessage.decode(pb2_raw_message) performative_content["raw_message"] = raw_message elif performative_id == SigningMessage.Performative.SIGNED_TRANSACTION: pb2_signed_transaction = signing_pb.signed_transaction.signed_transaction signed_transaction = SignedTransaction.decode(pb2_signed_transaction) performative_content["signed_transaction"] = signed_transaction elif performative_id == SigningMessage.Performative.SIGNED_MESSAGE: pb2_signed_message = signing_pb.signed_message.signed_message signed_message = SignedMessage.decode(pb2_signed_message) performative_content["signed_message"] = signed_message elif performative_id == SigningMessage.Performative.ERROR: pb2_error_code = signing_pb.error.error_code error_code = ErrorCode.decode(pb2_error_code) performative_content["error_code"] = error_code else: raise ValueError("Performative not valid: {}.".format(performative_id)) return SigningMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/signing/signing.proto ================================================ syntax = "proto3"; package aea.fetchai.signing.v1_0_0; message SigningMessage{ // Custom Types message ErrorCode{ enum ErrorCodeEnum { UNSUCCESSFUL_MESSAGE_SIGNING = 0; UNSUCCESSFUL_TRANSACTION_SIGNING = 1; } ErrorCodeEnum error_code = 1; } message RawMessage{ bytes raw_message = 1; } message RawTransaction{ bytes raw_transaction = 1; } message SignedMessage{ bytes signed_message = 1; } message SignedTransaction{ bytes signed_transaction = 1; } message Terms{ bytes terms = 1; } // Performatives and contents message Sign_Transaction_Performative{ Terms terms = 1; RawTransaction raw_transaction = 2; } message Sign_Message_Performative{ Terms terms = 1; RawMessage raw_message = 2; } message Signed_Transaction_Performative{ SignedTransaction signed_transaction = 1; } message Signed_Message_Performative{ SignedMessage signed_message = 1; } message Error_Performative{ ErrorCode error_code = 1; } oneof performative{ Error_Performative error = 5; Sign_Message_Performative sign_message = 6; Sign_Transaction_Performative sign_transaction = 7; Signed_Message_Performative signed_message = 8; Signed_Transaction_Performative signed_transaction = 9; } } ================================================ FILE: packages/fetchai/protocols/signing/signing_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: signing.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\rsigning.proto\x12\x1a\x61\x65\x61.fetchai.signing.v1_0_0"\xaf\x0c\n\x0eSigningMessage\x12N\n\x05\x65rror\x18\x05 \x01(\x0b\x32=.aea.fetchai.signing.v1_0_0.SigningMessage.Error_PerformativeH\x00\x12\\\n\x0csign_message\x18\x06 \x01(\x0b\x32\x44.aea.fetchai.signing.v1_0_0.SigningMessage.Sign_Message_PerformativeH\x00\x12\x64\n\x10sign_transaction\x18\x07 \x01(\x0b\x32H.aea.fetchai.signing.v1_0_0.SigningMessage.Sign_Transaction_PerformativeH\x00\x12`\n\x0esigned_message\x18\x08 \x01(\x0b\x32\x46.aea.fetchai.signing.v1_0_0.SigningMessage.Signed_Message_PerformativeH\x00\x12h\n\x12signed_transaction\x18\t \x01(\x0b\x32J.aea.fetchai.signing.v1_0_0.SigningMessage.Signed_Transaction_PerformativeH\x00\x1a\xbc\x01\n\tErrorCode\x12V\n\nerror_code\x18\x01 \x01(\x0e\x32\x42.aea.fetchai.signing.v1_0_0.SigningMessage.ErrorCode.ErrorCodeEnum"W\n\rErrorCodeEnum\x12 \n\x1cUNSUCCESSFUL_MESSAGE_SIGNING\x10\x00\x12$\n UNSUCCESSFUL_TRANSACTION_SIGNING\x10\x01\x1a!\n\nRawMessage\x12\x13\n\x0braw_message\x18\x01 \x01(\x0c\x1a)\n\x0eRawTransaction\x12\x17\n\x0fraw_transaction\x18\x01 \x01(\x0c\x1a\'\n\rSignedMessage\x12\x16\n\x0esigned_message\x18\x01 \x01(\x0c\x1a/\n\x11SignedTransaction\x12\x1a\n\x12signed_transaction\x18\x01 \x01(\x0c\x1a\x16\n\x05Terms\x12\r\n\x05terms\x18\x01 \x01(\x0c\x1a\xb4\x01\n\x1dSign_Transaction_Performative\x12?\n\x05terms\x18\x01 \x01(\x0b\x32\x30.aea.fetchai.signing.v1_0_0.SigningMessage.Terms\x12R\n\x0fraw_transaction\x18\x02 \x01(\x0b\x32\x39.aea.fetchai.signing.v1_0_0.SigningMessage.RawTransaction\x1a\xa8\x01\n\x19Sign_Message_Performative\x12?\n\x05terms\x18\x01 \x01(\x0b\x32\x30.aea.fetchai.signing.v1_0_0.SigningMessage.Terms\x12J\n\x0braw_message\x18\x02 \x01(\x0b\x32\x35.aea.fetchai.signing.v1_0_0.SigningMessage.RawMessage\x1a{\n\x1fSigned_Transaction_Performative\x12X\n\x12signed_transaction\x18\x01 \x01(\x0b\x32<.aea.fetchai.signing.v1_0_0.SigningMessage.SignedTransaction\x1ao\n\x1bSigned_Message_Performative\x12P\n\x0esigned_message\x18\x01 \x01(\x0b\x32\x38.aea.fetchai.signing.v1_0_0.SigningMessage.SignedMessage\x1a^\n\x12\x45rror_Performative\x12H\n\nerror_code\x18\x01 \x01(\x0b\x32\x34.aea.fetchai.signing.v1_0_0.SigningMessage.ErrorCodeB\x0e\n\x0cperformativeb\x06proto3' ) _SIGNINGMESSAGE = DESCRIPTOR.message_types_by_name["SigningMessage"] _SIGNINGMESSAGE_ERRORCODE = _SIGNINGMESSAGE.nested_types_by_name["ErrorCode"] _SIGNINGMESSAGE_RAWMESSAGE = _SIGNINGMESSAGE.nested_types_by_name["RawMessage"] _SIGNINGMESSAGE_RAWTRANSACTION = _SIGNINGMESSAGE.nested_types_by_name["RawTransaction"] _SIGNINGMESSAGE_SIGNEDMESSAGE = _SIGNINGMESSAGE.nested_types_by_name["SignedMessage"] _SIGNINGMESSAGE_SIGNEDTRANSACTION = _SIGNINGMESSAGE.nested_types_by_name[ "SignedTransaction" ] _SIGNINGMESSAGE_TERMS = _SIGNINGMESSAGE.nested_types_by_name["Terms"] _SIGNINGMESSAGE_SIGN_TRANSACTION_PERFORMATIVE = _SIGNINGMESSAGE.nested_types_by_name[ "Sign_Transaction_Performative" ] _SIGNINGMESSAGE_SIGN_MESSAGE_PERFORMATIVE = _SIGNINGMESSAGE.nested_types_by_name[ "Sign_Message_Performative" ] _SIGNINGMESSAGE_SIGNED_TRANSACTION_PERFORMATIVE = _SIGNINGMESSAGE.nested_types_by_name[ "Signed_Transaction_Performative" ] _SIGNINGMESSAGE_SIGNED_MESSAGE_PERFORMATIVE = _SIGNINGMESSAGE.nested_types_by_name[ "Signed_Message_Performative" ] _SIGNINGMESSAGE_ERROR_PERFORMATIVE = _SIGNINGMESSAGE.nested_types_by_name[ "Error_Performative" ] _SIGNINGMESSAGE_ERRORCODE_ERRORCODEENUM = _SIGNINGMESSAGE_ERRORCODE.enum_types_by_name[ "ErrorCodeEnum" ] SigningMessage = _reflection.GeneratedProtocolMessageType( "SigningMessage", (_message.Message,), { "ErrorCode": _reflection.GeneratedProtocolMessageType( "ErrorCode", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_ERRORCODE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.ErrorCode) }, ), "RawMessage": _reflection.GeneratedProtocolMessageType( "RawMessage", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_RAWMESSAGE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.RawMessage) }, ), "RawTransaction": _reflection.GeneratedProtocolMessageType( "RawTransaction", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_RAWTRANSACTION, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.RawTransaction) }, ), "SignedMessage": _reflection.GeneratedProtocolMessageType( "SignedMessage", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_SIGNEDMESSAGE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.SignedMessage) }, ), "SignedTransaction": _reflection.GeneratedProtocolMessageType( "SignedTransaction", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_SIGNEDTRANSACTION, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.SignedTransaction) }, ), "Terms": _reflection.GeneratedProtocolMessageType( "Terms", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_TERMS, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.Terms) }, ), "Sign_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Sign_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_SIGN_TRANSACTION_PERFORMATIVE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.Sign_Transaction_Performative) }, ), "Sign_Message_Performative": _reflection.GeneratedProtocolMessageType( "Sign_Message_Performative", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_SIGN_MESSAGE_PERFORMATIVE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.Sign_Message_Performative) }, ), "Signed_Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Signed_Transaction_Performative", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_SIGNED_TRANSACTION_PERFORMATIVE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.Signed_Transaction_Performative) }, ), "Signed_Message_Performative": _reflection.GeneratedProtocolMessageType( "Signed_Message_Performative", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_SIGNED_MESSAGE_PERFORMATIVE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.Signed_Message_Performative) }, ), "Error_Performative": _reflection.GeneratedProtocolMessageType( "Error_Performative", (_message.Message,), { "DESCRIPTOR": _SIGNINGMESSAGE_ERROR_PERFORMATIVE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage.Error_Performative) }, ), "DESCRIPTOR": _SIGNINGMESSAGE, "__module__": "signing_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.signing.v1_0_0.SigningMessage) }, ) _sym_db.RegisterMessage(SigningMessage) _sym_db.RegisterMessage(SigningMessage.ErrorCode) _sym_db.RegisterMessage(SigningMessage.RawMessage) _sym_db.RegisterMessage(SigningMessage.RawTransaction) _sym_db.RegisterMessage(SigningMessage.SignedMessage) _sym_db.RegisterMessage(SigningMessage.SignedTransaction) _sym_db.RegisterMessage(SigningMessage.Terms) _sym_db.RegisterMessage(SigningMessage.Sign_Transaction_Performative) _sym_db.RegisterMessage(SigningMessage.Sign_Message_Performative) _sym_db.RegisterMessage(SigningMessage.Signed_Transaction_Performative) _sym_db.RegisterMessage(SigningMessage.Signed_Message_Performative) _sym_db.RegisterMessage(SigningMessage.Error_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _SIGNINGMESSAGE._serialized_start = 46 _SIGNINGMESSAGE._serialized_end = 1629 _SIGNINGMESSAGE_ERRORCODE._serialized_start = 545 _SIGNINGMESSAGE_ERRORCODE._serialized_end = 733 _SIGNINGMESSAGE_ERRORCODE_ERRORCODEENUM._serialized_start = 646 _SIGNINGMESSAGE_ERRORCODE_ERRORCODEENUM._serialized_end = 733 _SIGNINGMESSAGE_RAWMESSAGE._serialized_start = 735 _SIGNINGMESSAGE_RAWMESSAGE._serialized_end = 768 _SIGNINGMESSAGE_RAWTRANSACTION._serialized_start = 770 _SIGNINGMESSAGE_RAWTRANSACTION._serialized_end = 811 _SIGNINGMESSAGE_SIGNEDMESSAGE._serialized_start = 813 _SIGNINGMESSAGE_SIGNEDMESSAGE._serialized_end = 852 _SIGNINGMESSAGE_SIGNEDTRANSACTION._serialized_start = 854 _SIGNINGMESSAGE_SIGNEDTRANSACTION._serialized_end = 901 _SIGNINGMESSAGE_TERMS._serialized_start = 903 _SIGNINGMESSAGE_TERMS._serialized_end = 925 _SIGNINGMESSAGE_SIGN_TRANSACTION_PERFORMATIVE._serialized_start = 928 _SIGNINGMESSAGE_SIGN_TRANSACTION_PERFORMATIVE._serialized_end = 1108 _SIGNINGMESSAGE_SIGN_MESSAGE_PERFORMATIVE._serialized_start = 1111 _SIGNINGMESSAGE_SIGN_MESSAGE_PERFORMATIVE._serialized_end = 1279 _SIGNINGMESSAGE_SIGNED_TRANSACTION_PERFORMATIVE._serialized_start = 1281 _SIGNINGMESSAGE_SIGNED_TRANSACTION_PERFORMATIVE._serialized_end = 1404 _SIGNINGMESSAGE_SIGNED_MESSAGE_PERFORMATIVE._serialized_start = 1406 _SIGNINGMESSAGE_SIGNED_MESSAGE_PERFORMATIVE._serialized_end = 1517 _SIGNINGMESSAGE_ERROR_PERFORMATIVE._serialized_start = 1519 _SIGNINGMESSAGE_ERROR_PERFORMATIVE._serialized_end = 1613 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/state_update/README.md ================================================ # State Update Protocol ## Description This is a protocol for updating the state of a decision maker. ## Specification ```yaml --- name: state_update author: fetchai version: 1.1.7 description: A protocol for state updates to the decision maker state. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/state_update:1.0.0 speech_acts: initialize: exchange_params_by_currency_id: pt:dict[pt:str, pt:float] utility_params_by_good_id: pt:dict[pt:str, pt:float] amount_by_currency_id: pt:dict[pt:str, pt:int] quantities_by_good_id: pt:dict[pt:str, pt:int] apply: amount_by_currency_id: pt:dict[pt:str, pt:int] quantities_by_good_id: pt:dict[pt:str, pt:int] end: {} ... --- initiation: [initialize] reply: initialize: [apply] apply: [apply, end] end: [] termination: [end] roles: {skill, decision_maker} end_states: [successful] keep_terminal_state_dialogues: false ... ``` ## Links ================================================ FILE: packages/fetchai/protocols/state_update/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the state_update protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.state_update.message import StateUpdateMessage from packages.fetchai.protocols.state_update.serialization import StateUpdateSerializer StateUpdateMessage.serializer = StateUpdateSerializer ================================================ FILE: packages/fetchai/protocols/state_update/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for state_update dialogue management. - StateUpdateDialogue: The dialogue class maintains state of a dialogue and manages it. - StateUpdateDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.state_update.message import StateUpdateMessage class StateUpdateDialogue(Dialogue): """The state_update dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {StateUpdateMessage.Performative.INITIALIZE} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {StateUpdateMessage.Performative.END} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { StateUpdateMessage.Performative.APPLY: frozenset( {StateUpdateMessage.Performative.APPLY, StateUpdateMessage.Performative.END} ), StateUpdateMessage.Performative.END: frozenset(), StateUpdateMessage.Performative.INITIALIZE: frozenset( {StateUpdateMessage.Performative.APPLY} ), } class Role(Dialogue.Role): """This class defines the agent's role in a state_update dialogue.""" DECISION_MAKER = "decision_maker" SKILL = "skill" class EndState(Dialogue.EndState): """This class defines the end states of a state_update dialogue.""" SUCCESSFUL = 0 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[StateUpdateMessage] = StateUpdateMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class StateUpdateDialogues(Dialogues, ABC): """This class keeps track of all state_update dialogues.""" END_STATES = frozenset({StateUpdateDialogue.EndState.SUCCESSFUL}) _keep_terminal_state_dialogues = False def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[StateUpdateDialogue] = StateUpdateDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=StateUpdateMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/state_update/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains state_update's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message _default_logger = logging.getLogger( "aea.packages.fetchai.protocols.state_update.message" ) DEFAULT_BODY_SIZE = 4 class StateUpdateMessage(Message): """A protocol for state updates to the decision maker state.""" protocol_id = PublicId.from_str("fetchai/state_update:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/state_update:1.0.0") class Performative(Message.Performative): """Performatives for the state_update protocol.""" APPLY = "apply" END = "end" INITIALIZE = "initialize" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = {"apply", "end", "initialize"} __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "amount_by_currency_id", "dialogue_reference", "exchange_params_by_currency_id", "message_id", "performative", "quantities_by_good_id", "target", "utility_params_by_good_id", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of StateUpdateMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=StateUpdateMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(StateUpdateMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def amount_by_currency_id(self) -> Dict[str, int]: """Get the 'amount_by_currency_id' content from the message.""" enforce( self.is_set("amount_by_currency_id"), "'amount_by_currency_id' content is not set.", ) return cast(Dict[str, int], self.get("amount_by_currency_id")) @property def exchange_params_by_currency_id(self) -> Dict[str, float]: """Get the 'exchange_params_by_currency_id' content from the message.""" enforce( self.is_set("exchange_params_by_currency_id"), "'exchange_params_by_currency_id' content is not set.", ) return cast(Dict[str, float], self.get("exchange_params_by_currency_id")) @property def quantities_by_good_id(self) -> Dict[str, int]: """Get the 'quantities_by_good_id' content from the message.""" enforce( self.is_set("quantities_by_good_id"), "'quantities_by_good_id' content is not set.", ) return cast(Dict[str, int], self.get("quantities_by_good_id")) @property def utility_params_by_good_id(self) -> Dict[str, float]: """Get the 'utility_params_by_good_id' content from the message.""" enforce( self.is_set("utility_params_by_good_id"), "'utility_params_by_good_id' content is not set.", ) return cast(Dict[str, float], self.get("utility_params_by_good_id")) def _is_consistent(self) -> bool: """Check that the message follows the state_update protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, StateUpdateMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == StateUpdateMessage.Performative.INITIALIZE: expected_nb_of_contents = 4 enforce( isinstance(self.exchange_params_by_currency_id, dict), "Invalid type for content 'exchange_params_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.exchange_params_by_currency_id) ), ) for ( key_of_exchange_params_by_currency_id, value_of_exchange_params_by_currency_id, ) in self.exchange_params_by_currency_id.items(): enforce( isinstance(key_of_exchange_params_by_currency_id, str), "Invalid type for dictionary keys in content 'exchange_params_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_exchange_params_by_currency_id) ), ) enforce( isinstance(value_of_exchange_params_by_currency_id, float), "Invalid type for dictionary values in content 'exchange_params_by_currency_id'. Expected 'float'. Found '{}'.".format( type(value_of_exchange_params_by_currency_id) ), ) enforce( isinstance(self.utility_params_by_good_id, dict), "Invalid type for content 'utility_params_by_good_id'. Expected 'dict'. Found '{}'.".format( type(self.utility_params_by_good_id) ), ) for ( key_of_utility_params_by_good_id, value_of_utility_params_by_good_id, ) in self.utility_params_by_good_id.items(): enforce( isinstance(key_of_utility_params_by_good_id, str), "Invalid type for dictionary keys in content 'utility_params_by_good_id'. Expected 'str'. Found '{}'.".format( type(key_of_utility_params_by_good_id) ), ) enforce( isinstance(value_of_utility_params_by_good_id, float), "Invalid type for dictionary values in content 'utility_params_by_good_id'. Expected 'float'. Found '{}'.".format( type(value_of_utility_params_by_good_id) ), ) enforce( isinstance(self.amount_by_currency_id, dict), "Invalid type for content 'amount_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.amount_by_currency_id) ), ) for ( key_of_amount_by_currency_id, value_of_amount_by_currency_id, ) in self.amount_by_currency_id.items(): enforce( isinstance(key_of_amount_by_currency_id, str), "Invalid type for dictionary keys in content 'amount_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_amount_by_currency_id) ), ) enforce( type(value_of_amount_by_currency_id) is int, "Invalid type for dictionary values in content 'amount_by_currency_id'. Expected 'int'. Found '{}'.".format( type(value_of_amount_by_currency_id) ), ) enforce( isinstance(self.quantities_by_good_id, dict), "Invalid type for content 'quantities_by_good_id'. Expected 'dict'. Found '{}'.".format( type(self.quantities_by_good_id) ), ) for ( key_of_quantities_by_good_id, value_of_quantities_by_good_id, ) in self.quantities_by_good_id.items(): enforce( isinstance(key_of_quantities_by_good_id, str), "Invalid type for dictionary keys in content 'quantities_by_good_id'. Expected 'str'. Found '{}'.".format( type(key_of_quantities_by_good_id) ), ) enforce( type(value_of_quantities_by_good_id) is int, "Invalid type for dictionary values in content 'quantities_by_good_id'. Expected 'int'. Found '{}'.".format( type(value_of_quantities_by_good_id) ), ) elif self.performative == StateUpdateMessage.Performative.APPLY: expected_nb_of_contents = 2 enforce( isinstance(self.amount_by_currency_id, dict), "Invalid type for content 'amount_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.amount_by_currency_id) ), ) for ( key_of_amount_by_currency_id, value_of_amount_by_currency_id, ) in self.amount_by_currency_id.items(): enforce( isinstance(key_of_amount_by_currency_id, str), "Invalid type for dictionary keys in content 'amount_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_amount_by_currency_id) ), ) enforce( type(value_of_amount_by_currency_id) is int, "Invalid type for dictionary values in content 'amount_by_currency_id'. Expected 'int'. Found '{}'.".format( type(value_of_amount_by_currency_id) ), ) enforce( isinstance(self.quantities_by_good_id, dict), "Invalid type for content 'quantities_by_good_id'. Expected 'dict'. Found '{}'.".format( type(self.quantities_by_good_id) ), ) for ( key_of_quantities_by_good_id, value_of_quantities_by_good_id, ) in self.quantities_by_good_id.items(): enforce( isinstance(key_of_quantities_by_good_id, str), "Invalid type for dictionary keys in content 'quantities_by_good_id'. Expected 'str'. Found '{}'.".format( type(key_of_quantities_by_good_id) ), ) enforce( type(value_of_quantities_by_good_id) is int, "Invalid type for dictionary values in content 'quantities_by_good_id'. Expected 'int'. Found '{}'.".format( type(value_of_quantities_by_good_id) ), ) elif self.performative == StateUpdateMessage.Performative.END: expected_nb_of_contents = 0 # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/state_update/protocol.yaml ================================================ name: state_update author: fetchai version: 1.1.7 protocol_specification_id: fetchai/state_update:1.0.0 type: protocol description: A protocol for state updates to the decision maker state. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmbFH2ySjB5FnvLhKCm68jJPVeasHbGBSTUyEZP4P17mKi __init__.py: Qmboa7WunXM34apxH2fu4z1996CrfxcCaV2HxhvXsjnkxK dialogues.py: QmUugwuLbuHr8zkGRkd7Dimwc7LzbCHYvdNHC6cn3TS7XP message.py: QmPKeMCSAcxDeEXUMzAjRvVvWJ5AUMYoDyGPGjUEoHkKff serialization.py: QmRxkqWUB7EVGp1SnsMXeDssCcsiirvPPJAaeN5TREPBBm state_update.proto: QmdLQpu2jpJUuUFhF34hBeh64Gfv5V1JxLTKCTgY93qduR state_update_pb2.py: QmXmaUALXJ4zV2CuADrGsFpNmy8RhWLSYpm7dXXogfgrw3 fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/state_update/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for state_update protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.state_update import state_update_pb2 from packages.fetchai.protocols.state_update.message import StateUpdateMessage class StateUpdateSerializer(Serializer): """Serialization for the 'state_update' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'StateUpdate' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(StateUpdateMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() state_update_msg = state_update_pb2.StateUpdateMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == StateUpdateMessage.Performative.INITIALIZE: performative = state_update_pb2.StateUpdateMessage.Initialize_Performative() # type: ignore exchange_params_by_currency_id = msg.exchange_params_by_currency_id performative.exchange_params_by_currency_id.update( exchange_params_by_currency_id ) utility_params_by_good_id = msg.utility_params_by_good_id performative.utility_params_by_good_id.update(utility_params_by_good_id) amount_by_currency_id = msg.amount_by_currency_id performative.amount_by_currency_id.update(amount_by_currency_id) quantities_by_good_id = msg.quantities_by_good_id performative.quantities_by_good_id.update(quantities_by_good_id) state_update_msg.initialize.CopyFrom(performative) elif performative_id == StateUpdateMessage.Performative.APPLY: performative = state_update_pb2.StateUpdateMessage.Apply_Performative() # type: ignore amount_by_currency_id = msg.amount_by_currency_id performative.amount_by_currency_id.update(amount_by_currency_id) quantities_by_good_id = msg.quantities_by_good_id performative.quantities_by_good_id.update(quantities_by_good_id) state_update_msg.apply.CopyFrom(performative) elif performative_id == StateUpdateMessage.Performative.END: performative = state_update_pb2.StateUpdateMessage.End_Performative() # type: ignore state_update_msg.end.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = state_update_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'StateUpdate' message. :param obj: the bytes object. :return: the 'StateUpdate' message. """ message_pb = ProtobufMessage() state_update_pb = state_update_pb2.StateUpdateMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target state_update_pb.ParseFromString(message_pb.dialogue_message.content) performative = state_update_pb.WhichOneof("performative") performative_id = StateUpdateMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == StateUpdateMessage.Performative.INITIALIZE: exchange_params_by_currency_id = ( state_update_pb.initialize.exchange_params_by_currency_id ) exchange_params_by_currency_id_dict = dict(exchange_params_by_currency_id) performative_content[ "exchange_params_by_currency_id" ] = exchange_params_by_currency_id_dict utility_params_by_good_id = ( state_update_pb.initialize.utility_params_by_good_id ) utility_params_by_good_id_dict = dict(utility_params_by_good_id) performative_content[ "utility_params_by_good_id" ] = utility_params_by_good_id_dict amount_by_currency_id = state_update_pb.initialize.amount_by_currency_id amount_by_currency_id_dict = dict(amount_by_currency_id) performative_content["amount_by_currency_id"] = amount_by_currency_id_dict quantities_by_good_id = state_update_pb.initialize.quantities_by_good_id quantities_by_good_id_dict = dict(quantities_by_good_id) performative_content["quantities_by_good_id"] = quantities_by_good_id_dict elif performative_id == StateUpdateMessage.Performative.APPLY: amount_by_currency_id = state_update_pb.apply.amount_by_currency_id amount_by_currency_id_dict = dict(amount_by_currency_id) performative_content["amount_by_currency_id"] = amount_by_currency_id_dict quantities_by_good_id = state_update_pb.apply.quantities_by_good_id quantities_by_good_id_dict = dict(quantities_by_good_id) performative_content["quantities_by_good_id"] = quantities_by_good_id_dict elif performative_id == StateUpdateMessage.Performative.END: pass else: raise ValueError("Performative not valid: {}.".format(performative_id)) return StateUpdateMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/state_update/state_update.proto ================================================ syntax = "proto3"; package aea.fetchai.state_update.v1_0_0; message StateUpdateMessage{ // Performatives and contents message Initialize_Performative{ map exchange_params_by_currency_id = 1; map utility_params_by_good_id = 2; map amount_by_currency_id = 3; map quantities_by_good_id = 4; } message Apply_Performative{ map amount_by_currency_id = 1; map quantities_by_good_id = 2; } message End_Performative{ } oneof performative{ Apply_Performative apply = 5; End_Performative end = 6; Initialize_Performative initialize = 7; } } ================================================ FILE: packages/fetchai/protocols/state_update/state_update_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: state_update.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x12state_update.proto\x12\x1f\x61\x65\x61.fetchai.state_update.v1_0_0"\x93\x0c\n\x12StateUpdateMessage\x12W\n\x05\x61pply\x18\x05 \x01(\x0b\x32\x46.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Apply_PerformativeH\x00\x12S\n\x03\x65nd\x18\x06 \x01(\x0b\x32\x44.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.End_PerformativeH\x00\x12\x61\n\ninitialize\x18\x07 \x01(\x0b\x32K.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_PerformativeH\x00\x1a\xbc\x06\n\x17Initialize_Performative\x12\x93\x01\n\x1e\x65xchange_params_by_currency_id\x18\x01 \x03(\x0b\x32k.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.ExchangeParamsByCurrencyIdEntry\x12\x89\x01\n\x19utility_params_by_good_id\x18\x02 \x03(\x0b\x32\x66.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.UtilityParamsByGoodIdEntry\x12\x82\x01\n\x15\x61mount_by_currency_id\x18\x03 \x03(\x0b\x32\x63.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.AmountByCurrencyIdEntry\x12\x82\x01\n\x15quantities_by_good_id\x18\x04 \x03(\x0b\x32\x63.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.QuantitiesByGoodIdEntry\x1a\x41\n\x1f\x45xchangeParamsByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a<\n\x1aUtilityParamsByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x88\x03\n\x12\x41pply_Performative\x12}\n\x15\x61mount_by_currency_id\x18\x01 \x03(\x0b\x32^.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Apply_Performative.AmountByCurrencyIdEntry\x12}\n\x15quantities_by_good_id\x18\x02 \x03(\x0b\x32^.aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Apply_Performative.QuantitiesByGoodIdEntry\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x12\n\x10\x45nd_PerformativeB\x0e\n\x0cperformativeb\x06proto3' ) _STATEUPDATEMESSAGE = DESCRIPTOR.message_types_by_name["StateUpdateMessage"] _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE = _STATEUPDATEMESSAGE.nested_types_by_name[ "Initialize_Performative" ] _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY = ( _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE.nested_types_by_name[ "ExchangeParamsByCurrencyIdEntry" ] ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY = ( _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE.nested_types_by_name[ "UtilityParamsByGoodIdEntry" ] ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY = ( _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE.nested_types_by_name[ "AmountByCurrencyIdEntry" ] ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = ( _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE.nested_types_by_name[ "QuantitiesByGoodIdEntry" ] ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE = _STATEUPDATEMESSAGE.nested_types_by_name[ "Apply_Performative" ] _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY = ( _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE.nested_types_by_name[ "AmountByCurrencyIdEntry" ] ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = ( _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE.nested_types_by_name[ "QuantitiesByGoodIdEntry" ] ) _STATEUPDATEMESSAGE_END_PERFORMATIVE = _STATEUPDATEMESSAGE.nested_types_by_name[ "End_Performative" ] StateUpdateMessage = _reflection.GeneratedProtocolMessageType( "StateUpdateMessage", (_message.Message,), { "Initialize_Performative": _reflection.GeneratedProtocolMessageType( "Initialize_Performative", (_message.Message,), { "ExchangeParamsByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "ExchangeParamsByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.ExchangeParamsByCurrencyIdEntry) }, ), "UtilityParamsByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "UtilityParamsByGoodIdEntry", (_message.Message,), { "DESCRIPTOR": _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.UtilityParamsByGoodIdEntry) }, ), "AmountByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "AmountByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.AmountByCurrencyIdEntry) }, ), "QuantitiesByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "QuantitiesByGoodIdEntry", (_message.Message,), { "DESCRIPTOR": _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_QUANTITIESBYGOODIDENTRY, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative.QuantitiesByGoodIdEntry) }, ), "DESCRIPTOR": _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Initialize_Performative) }, ), "Apply_Performative": _reflection.GeneratedProtocolMessageType( "Apply_Performative", (_message.Message,), { "AmountByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "AmountByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Apply_Performative.AmountByCurrencyIdEntry) }, ), "QuantitiesByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "QuantitiesByGoodIdEntry", (_message.Message,), { "DESCRIPTOR": _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_QUANTITIESBYGOODIDENTRY, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Apply_Performative.QuantitiesByGoodIdEntry) }, ), "DESCRIPTOR": _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.Apply_Performative) }, ), "End_Performative": _reflection.GeneratedProtocolMessageType( "End_Performative", (_message.Message,), { "DESCRIPTOR": _STATEUPDATEMESSAGE_END_PERFORMATIVE, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage.End_Performative) }, ), "DESCRIPTOR": _STATEUPDATEMESSAGE, "__module__": "state_update_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.state_update.v1_0_0.StateUpdateMessage) }, ) _sym_db.RegisterMessage(StateUpdateMessage) _sym_db.RegisterMessage(StateUpdateMessage.Initialize_Performative) _sym_db.RegisterMessage( StateUpdateMessage.Initialize_Performative.ExchangeParamsByCurrencyIdEntry ) _sym_db.RegisterMessage( StateUpdateMessage.Initialize_Performative.UtilityParamsByGoodIdEntry ) _sym_db.RegisterMessage( StateUpdateMessage.Initialize_Performative.AmountByCurrencyIdEntry ) _sym_db.RegisterMessage( StateUpdateMessage.Initialize_Performative.QuantitiesByGoodIdEntry ) _sym_db.RegisterMessage(StateUpdateMessage.Apply_Performative) _sym_db.RegisterMessage(StateUpdateMessage.Apply_Performative.AmountByCurrencyIdEntry) _sym_db.RegisterMessage(StateUpdateMessage.Apply_Performative.QuantitiesByGoodIdEntry) _sym_db.RegisterMessage(StateUpdateMessage.End_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._options = ( None ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._options = ( None ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._serialized_options = ( b"8\001" ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._options = None _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._options = None _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_options = ( b"8\001" ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._options = None _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._options = None _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_options = ( b"8\001" ) _STATEUPDATEMESSAGE._serialized_start = 56 _STATEUPDATEMESSAGE._serialized_end = 1611 _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE._serialized_start = 352 _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE._serialized_end = 1180 _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._serialized_start = ( 935 ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._serialized_end = ( 1000 ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._serialized_start = ( 1002 ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._serialized_end = ( 1062 ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_start = ( 1064 ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_end = ( 1121 ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_start = ( 1123 ) _STATEUPDATEMESSAGE_INITIALIZE_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_end = ( 1180 ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE._serialized_start = 1183 _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE._serialized_end = 1575 _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_start = ( 1064 ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_end = ( 1121 ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_start = ( 1123 ) _STATEUPDATEMESSAGE_APPLY_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_end = ( 1180 ) _STATEUPDATEMESSAGE_END_PERFORMATIVE._serialized_start = 1577 _STATEUPDATEMESSAGE_END_PERFORMATIVE._serialized_end = 1595 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/protocols/tac/README.md ================================================ # TAC Protocol ## Description This is a protocol for participating in a Trading Agent Competition (TAC). ## Specification ```yaml --- name: tac author: fetchai version: 1.1.7 description: The tac protocol implements the messages an AEA needs to participate in the TAC. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: fetchai/tac:1.0.0 speech_acts: register: agent_name: pt:str unregister: {} transaction: transaction_id: pt:str ledger_id: pt:str sender_address: pt:str counterparty_address: pt:str amount_by_currency_id: pt:dict[pt:str, pt:int] fee_by_currency_id: pt:dict[pt:str, pt:int] quantities_by_good_id: pt:dict[pt:str, pt:int] nonce: pt:str sender_signature: pt:str counterparty_signature: pt:str cancelled: {} game_data: amount_by_currency_id: pt:dict[pt:str, pt:int] exchange_params_by_currency_id: pt:dict[pt:str, pt:float] quantities_by_good_id: pt:dict[pt:str, pt:int] utility_params_by_good_id: pt:dict[pt:str, pt:float] fee_by_currency_id: pt:dict[pt:str, pt:int] agent_addr_to_name: pt:dict[pt:str, pt:str] currency_id_to_name: pt:dict[pt:str, pt:str] good_id_to_name: pt:dict[pt:str, pt:str] version_id: pt:str info: pt:optional[pt:dict[pt:str, pt:str]] transaction_confirmation: transaction_id: pt:str amount_by_currency_id: pt:dict[pt:str, pt:int] quantities_by_good_id: pt:dict[pt:str, pt:int] tac_error: error_code: ct:ErrorCode info: pt:optional[pt:dict[pt:str, pt:str]] ... --- ct:ErrorCode: | enum ErrorCodeEnum { GENERIC_ERROR = 0; REQUEST_NOT_VALID = 1; AGENT_ADDR_ALREADY_REGISTERED = 2; AGENT_NAME_ALREADY_REGISTERED = 3; AGENT_NOT_REGISTERED = 4; TRANSACTION_NOT_VALID = 5; TRANSACTION_NOT_MATCHING = 6; AGENT_NAME_NOT_IN_WHITELIST = 7; COMPETITION_NOT_RUNNING = 8; DIALOGUE_INCONSISTENT = 9; } ErrorCodeEnum error_code = 1; ... --- initiation: [register] reply: register: [tac_error, game_data, cancelled, unregister] unregister: [tac_error] transaction: [transaction, transaction_confirmation, tac_error, cancelled] cancelled: [] game_data: [transaction, transaction_confirmation, cancelled] transaction_confirmation: [transaction, transaction_confirmation, cancelled] tac_error: [transaction, transaction_confirmation, cancelled] termination: [cancelled] roles: {participant, controller} end_states: [successful, failed] keep_terminal_state_dialogues: true ... ``` ## Links - TAC skill in the AEA framework ================================================ FILE: packages/fetchai/protocols/tac/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the tac protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.protocols.tac.serialization import TacSerializer TacMessage.serializer = TacSerializer ================================================ FILE: packages/fetchai/protocols/tac/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from enum import Enum from typing import Any, Dict CODE_TO_MSG = { 0: "Unexpected error.", 1: "Request not recognized", 2: "Agent addr already registered.", 3: "Agent name already registered.", 4: "Agent not registered.", 5: "Error in checking transaction", 6: "The transaction request does not match with a previous transaction request with the same id.", 7: "Agent name not in whitelist.", 8: "The competition is not running yet.", 9: "The message is inconsistent with the dialogue.", } # type: Dict[int, str] class ErrorCode(Enum): """This class represents an instance of ErrorCode.""" GENERIC_ERROR = 0 REQUEST_NOT_VALID = 1 AGENT_ADDR_ALREADY_REGISTERED = 2 AGENT_NAME_ALREADY_REGISTERED = 3 AGENT_NOT_REGISTERED = 4 TRANSACTION_NOT_VALID = 5 TRANSACTION_NOT_MATCHING = 6 AGENT_NAME_NOT_IN_WHITELIST = 7 COMPETITION_NOT_RUNNING = 8 DIALOGUE_INCONSISTENT = 9 @staticmethod def to_msg(error_code: int) -> str: """Get the error code.""" return CODE_TO_MSG[error_code] @staticmethod def encode(error_code_protobuf_object: Any, error_code_object: "ErrorCode") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the error_code_protobuf_object argument is matched with the instance of this class in the 'error_code_object' argument. :param error_code_protobuf_object: the protocol buffer object whose type corresponds with this class. :param error_code_object: an instance of this class to be encoded in the protocol buffer object. """ error_code_protobuf_object.error_code = error_code_object.value @classmethod def decode(cls, error_code_protobuf_object: Any) -> "ErrorCode": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'error_code_protobuf_object' argument. :param error_code_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'error_code_protobuf_object' argument. """ enum_value_from_pb2 = error_code_protobuf_object.error_code return ErrorCode(enum_value_from_pb2) ================================================ FILE: packages/fetchai/protocols/tac/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for tac dialogue management. - TacDialogue: The dialogue class maintains state of a dialogue and manages it. - TacDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from packages.fetchai.protocols.tac.message import TacMessage class TacDialogue(Dialogue): """The tac dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {TacMessage.Performative.REGISTER} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {TacMessage.Performative.CANCELLED} ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { TacMessage.Performative.CANCELLED: frozenset(), TacMessage.Performative.GAME_DATA: frozenset( { TacMessage.Performative.TRANSACTION, TacMessage.Performative.TRANSACTION_CONFIRMATION, TacMessage.Performative.CANCELLED, } ), TacMessage.Performative.REGISTER: frozenset( { TacMessage.Performative.TAC_ERROR, TacMessage.Performative.GAME_DATA, TacMessage.Performative.CANCELLED, TacMessage.Performative.UNREGISTER, } ), TacMessage.Performative.TAC_ERROR: frozenset( { TacMessage.Performative.TRANSACTION, TacMessage.Performative.TRANSACTION_CONFIRMATION, TacMessage.Performative.CANCELLED, } ), TacMessage.Performative.TRANSACTION: frozenset( { TacMessage.Performative.TRANSACTION, TacMessage.Performative.TRANSACTION_CONFIRMATION, TacMessage.Performative.TAC_ERROR, TacMessage.Performative.CANCELLED, } ), TacMessage.Performative.TRANSACTION_CONFIRMATION: frozenset( { TacMessage.Performative.TRANSACTION, TacMessage.Performative.TRANSACTION_CONFIRMATION, TacMessage.Performative.CANCELLED, } ), TacMessage.Performative.UNREGISTER: frozenset( {TacMessage.Performative.TAC_ERROR} ), } class Role(Dialogue.Role): """This class defines the agent's role in a tac dialogue.""" CONTROLLER = "controller" PARTICIPANT = "participant" class EndState(Dialogue.EndState): """This class defines the end states of a tac dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[TacMessage] = TacMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class TacDialogues(Dialogues, ABC): """This class keeps track of all tac dialogues.""" END_STATES = frozenset( {TacDialogue.EndState.SUCCESSFUL, TacDialogue.EndState.FAILED} ) _keep_terminal_state_dialogues = True def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[TacDialogue] = TacDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=TacMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/protocols/tac/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tac's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, Optional, Set, Tuple, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from packages.fetchai.protocols.tac.custom_types import ErrorCode as CustomErrorCode _default_logger = logging.getLogger("aea.packages.fetchai.protocols.tac.message") DEFAULT_BODY_SIZE = 4 class TacMessage(Message): """The tac protocol implements the messages an AEA needs to participate in the TAC.""" protocol_id = PublicId.from_str("fetchai/tac:1.1.7") protocol_specification_id = PublicId.from_str("fetchai/tac:1.0.0") ErrorCode = CustomErrorCode class Performative(Message.Performative): """Performatives for the tac protocol.""" CANCELLED = "cancelled" GAME_DATA = "game_data" REGISTER = "register" TAC_ERROR = "tac_error" TRANSACTION = "transaction" TRANSACTION_CONFIRMATION = "transaction_confirmation" UNREGISTER = "unregister" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "cancelled", "game_data", "register", "tac_error", "transaction", "transaction_confirmation", "unregister", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "agent_addr_to_name", "agent_name", "amount_by_currency_id", "counterparty_address", "counterparty_signature", "currency_id_to_name", "dialogue_reference", "error_code", "exchange_params_by_currency_id", "fee_by_currency_id", "good_id_to_name", "info", "ledger_id", "message_id", "nonce", "performative", "quantities_by_good_id", "sender_address", "sender_signature", "target", "transaction_id", "utility_params_by_good_id", "version_id", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of TacMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=TacMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(TacMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def agent_addr_to_name(self) -> Dict[str, str]: """Get the 'agent_addr_to_name' content from the message.""" enforce( self.is_set("agent_addr_to_name"), "'agent_addr_to_name' content is not set.", ) return cast(Dict[str, str], self.get("agent_addr_to_name")) @property def agent_name(self) -> str: """Get the 'agent_name' content from the message.""" enforce(self.is_set("agent_name"), "'agent_name' content is not set.") return cast(str, self.get("agent_name")) @property def amount_by_currency_id(self) -> Dict[str, int]: """Get the 'amount_by_currency_id' content from the message.""" enforce( self.is_set("amount_by_currency_id"), "'amount_by_currency_id' content is not set.", ) return cast(Dict[str, int], self.get("amount_by_currency_id")) @property def counterparty_address(self) -> str: """Get the 'counterparty_address' content from the message.""" enforce( self.is_set("counterparty_address"), "'counterparty_address' content is not set.", ) return cast(str, self.get("counterparty_address")) @property def counterparty_signature(self) -> str: """Get the 'counterparty_signature' content from the message.""" enforce( self.is_set("counterparty_signature"), "'counterparty_signature' content is not set.", ) return cast(str, self.get("counterparty_signature")) @property def currency_id_to_name(self) -> Dict[str, str]: """Get the 'currency_id_to_name' content from the message.""" enforce( self.is_set("currency_id_to_name"), "'currency_id_to_name' content is not set.", ) return cast(Dict[str, str], self.get("currency_id_to_name")) @property def error_code(self) -> CustomErrorCode: """Get the 'error_code' content from the message.""" enforce(self.is_set("error_code"), "'error_code' content is not set.") return cast(CustomErrorCode, self.get("error_code")) @property def exchange_params_by_currency_id(self) -> Dict[str, float]: """Get the 'exchange_params_by_currency_id' content from the message.""" enforce( self.is_set("exchange_params_by_currency_id"), "'exchange_params_by_currency_id' content is not set.", ) return cast(Dict[str, float], self.get("exchange_params_by_currency_id")) @property def fee_by_currency_id(self) -> Dict[str, int]: """Get the 'fee_by_currency_id' content from the message.""" enforce( self.is_set("fee_by_currency_id"), "'fee_by_currency_id' content is not set.", ) return cast(Dict[str, int], self.get("fee_by_currency_id")) @property def good_id_to_name(self) -> Dict[str, str]: """Get the 'good_id_to_name' content from the message.""" enforce(self.is_set("good_id_to_name"), "'good_id_to_name' content is not set.") return cast(Dict[str, str], self.get("good_id_to_name")) @property def info(self) -> Optional[Dict[str, str]]: """Get the 'info' content from the message.""" return cast(Optional[Dict[str, str]], self.get("info")) @property def ledger_id(self) -> str: """Get the 'ledger_id' content from the message.""" enforce(self.is_set("ledger_id"), "'ledger_id' content is not set.") return cast(str, self.get("ledger_id")) @property def nonce(self) -> str: """Get the 'nonce' content from the message.""" enforce(self.is_set("nonce"), "'nonce' content is not set.") return cast(str, self.get("nonce")) @property def quantities_by_good_id(self) -> Dict[str, int]: """Get the 'quantities_by_good_id' content from the message.""" enforce( self.is_set("quantities_by_good_id"), "'quantities_by_good_id' content is not set.", ) return cast(Dict[str, int], self.get("quantities_by_good_id")) @property def sender_address(self) -> str: """Get the 'sender_address' content from the message.""" enforce(self.is_set("sender_address"), "'sender_address' content is not set.") return cast(str, self.get("sender_address")) @property def sender_signature(self) -> str: """Get the 'sender_signature' content from the message.""" enforce( self.is_set("sender_signature"), "'sender_signature' content is not set." ) return cast(str, self.get("sender_signature")) @property def transaction_id(self) -> str: """Get the 'transaction_id' content from the message.""" enforce(self.is_set("transaction_id"), "'transaction_id' content is not set.") return cast(str, self.get("transaction_id")) @property def utility_params_by_good_id(self) -> Dict[str, float]: """Get the 'utility_params_by_good_id' content from the message.""" enforce( self.is_set("utility_params_by_good_id"), "'utility_params_by_good_id' content is not set.", ) return cast(Dict[str, float], self.get("utility_params_by_good_id")) @property def version_id(self) -> str: """Get the 'version_id' content from the message.""" enforce(self.is_set("version_id"), "'version_id' content is not set.") return cast(str, self.get("version_id")) def _is_consistent(self) -> bool: """Check that the message follows the tac protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, TacMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == TacMessage.Performative.REGISTER: expected_nb_of_contents = 1 enforce( isinstance(self.agent_name, str), "Invalid type for content 'agent_name'. Expected 'str'. Found '{}'.".format( type(self.agent_name) ), ) elif self.performative == TacMessage.Performative.UNREGISTER: expected_nb_of_contents = 0 elif self.performative == TacMessage.Performative.TRANSACTION: expected_nb_of_contents = 10 enforce( isinstance(self.transaction_id, str), "Invalid type for content 'transaction_id'. Expected 'str'. Found '{}'.".format( type(self.transaction_id) ), ) enforce( isinstance(self.ledger_id, str), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( type(self.ledger_id) ), ) enforce( isinstance(self.sender_address, str), "Invalid type for content 'sender_address'. Expected 'str'. Found '{}'.".format( type(self.sender_address) ), ) enforce( isinstance(self.counterparty_address, str), "Invalid type for content 'counterparty_address'. Expected 'str'. Found '{}'.".format( type(self.counterparty_address) ), ) enforce( isinstance(self.amount_by_currency_id, dict), "Invalid type for content 'amount_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.amount_by_currency_id) ), ) for ( key_of_amount_by_currency_id, value_of_amount_by_currency_id, ) in self.amount_by_currency_id.items(): enforce( isinstance(key_of_amount_by_currency_id, str), "Invalid type for dictionary keys in content 'amount_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_amount_by_currency_id) ), ) enforce( type(value_of_amount_by_currency_id) is int, "Invalid type for dictionary values in content 'amount_by_currency_id'. Expected 'int'. Found '{}'.".format( type(value_of_amount_by_currency_id) ), ) enforce( isinstance(self.fee_by_currency_id, dict), "Invalid type for content 'fee_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.fee_by_currency_id) ), ) for ( key_of_fee_by_currency_id, value_of_fee_by_currency_id, ) in self.fee_by_currency_id.items(): enforce( isinstance(key_of_fee_by_currency_id, str), "Invalid type for dictionary keys in content 'fee_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_fee_by_currency_id) ), ) enforce( type(value_of_fee_by_currency_id) is int, "Invalid type for dictionary values in content 'fee_by_currency_id'. Expected 'int'. Found '{}'.".format( type(value_of_fee_by_currency_id) ), ) enforce( isinstance(self.quantities_by_good_id, dict), "Invalid type for content 'quantities_by_good_id'. Expected 'dict'. Found '{}'.".format( type(self.quantities_by_good_id) ), ) for ( key_of_quantities_by_good_id, value_of_quantities_by_good_id, ) in self.quantities_by_good_id.items(): enforce( isinstance(key_of_quantities_by_good_id, str), "Invalid type for dictionary keys in content 'quantities_by_good_id'. Expected 'str'. Found '{}'.".format( type(key_of_quantities_by_good_id) ), ) enforce( type(value_of_quantities_by_good_id) is int, "Invalid type for dictionary values in content 'quantities_by_good_id'. Expected 'int'. Found '{}'.".format( type(value_of_quantities_by_good_id) ), ) enforce( isinstance(self.nonce, str), "Invalid type for content 'nonce'. Expected 'str'. Found '{}'.".format( type(self.nonce) ), ) enforce( isinstance(self.sender_signature, str), "Invalid type for content 'sender_signature'. Expected 'str'. Found '{}'.".format( type(self.sender_signature) ), ) enforce( isinstance(self.counterparty_signature, str), "Invalid type for content 'counterparty_signature'. Expected 'str'. Found '{}'.".format( type(self.counterparty_signature) ), ) elif self.performative == TacMessage.Performative.CANCELLED: expected_nb_of_contents = 0 elif self.performative == TacMessage.Performative.GAME_DATA: expected_nb_of_contents = 9 enforce( isinstance(self.amount_by_currency_id, dict), "Invalid type for content 'amount_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.amount_by_currency_id) ), ) for ( key_of_amount_by_currency_id, value_of_amount_by_currency_id, ) in self.amount_by_currency_id.items(): enforce( isinstance(key_of_amount_by_currency_id, str), "Invalid type for dictionary keys in content 'amount_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_amount_by_currency_id) ), ) enforce( type(value_of_amount_by_currency_id) is int, "Invalid type for dictionary values in content 'amount_by_currency_id'. Expected 'int'. Found '{}'.".format( type(value_of_amount_by_currency_id) ), ) enforce( isinstance(self.exchange_params_by_currency_id, dict), "Invalid type for content 'exchange_params_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.exchange_params_by_currency_id) ), ) for ( key_of_exchange_params_by_currency_id, value_of_exchange_params_by_currency_id, ) in self.exchange_params_by_currency_id.items(): enforce( isinstance(key_of_exchange_params_by_currency_id, str), "Invalid type for dictionary keys in content 'exchange_params_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_exchange_params_by_currency_id) ), ) enforce( isinstance(value_of_exchange_params_by_currency_id, float), "Invalid type for dictionary values in content 'exchange_params_by_currency_id'. Expected 'float'. Found '{}'.".format( type(value_of_exchange_params_by_currency_id) ), ) enforce( isinstance(self.quantities_by_good_id, dict), "Invalid type for content 'quantities_by_good_id'. Expected 'dict'. Found '{}'.".format( type(self.quantities_by_good_id) ), ) for ( key_of_quantities_by_good_id, value_of_quantities_by_good_id, ) in self.quantities_by_good_id.items(): enforce( isinstance(key_of_quantities_by_good_id, str), "Invalid type for dictionary keys in content 'quantities_by_good_id'. Expected 'str'. Found '{}'.".format( type(key_of_quantities_by_good_id) ), ) enforce( type(value_of_quantities_by_good_id) is int, "Invalid type for dictionary values in content 'quantities_by_good_id'. Expected 'int'. Found '{}'.".format( type(value_of_quantities_by_good_id) ), ) enforce( isinstance(self.utility_params_by_good_id, dict), "Invalid type for content 'utility_params_by_good_id'. Expected 'dict'. Found '{}'.".format( type(self.utility_params_by_good_id) ), ) for ( key_of_utility_params_by_good_id, value_of_utility_params_by_good_id, ) in self.utility_params_by_good_id.items(): enforce( isinstance(key_of_utility_params_by_good_id, str), "Invalid type for dictionary keys in content 'utility_params_by_good_id'. Expected 'str'. Found '{}'.".format( type(key_of_utility_params_by_good_id) ), ) enforce( isinstance(value_of_utility_params_by_good_id, float), "Invalid type for dictionary values in content 'utility_params_by_good_id'. Expected 'float'. Found '{}'.".format( type(value_of_utility_params_by_good_id) ), ) enforce( isinstance(self.fee_by_currency_id, dict), "Invalid type for content 'fee_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.fee_by_currency_id) ), ) for ( key_of_fee_by_currency_id, value_of_fee_by_currency_id, ) in self.fee_by_currency_id.items(): enforce( isinstance(key_of_fee_by_currency_id, str), "Invalid type for dictionary keys in content 'fee_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_fee_by_currency_id) ), ) enforce( type(value_of_fee_by_currency_id) is int, "Invalid type for dictionary values in content 'fee_by_currency_id'. Expected 'int'. Found '{}'.".format( type(value_of_fee_by_currency_id) ), ) enforce( isinstance(self.agent_addr_to_name, dict), "Invalid type for content 'agent_addr_to_name'. Expected 'dict'. Found '{}'.".format( type(self.agent_addr_to_name) ), ) for ( key_of_agent_addr_to_name, value_of_agent_addr_to_name, ) in self.agent_addr_to_name.items(): enforce( isinstance(key_of_agent_addr_to_name, str), "Invalid type for dictionary keys in content 'agent_addr_to_name'. Expected 'str'. Found '{}'.".format( type(key_of_agent_addr_to_name) ), ) enforce( isinstance(value_of_agent_addr_to_name, str), "Invalid type for dictionary values in content 'agent_addr_to_name'. Expected 'str'. Found '{}'.".format( type(value_of_agent_addr_to_name) ), ) enforce( isinstance(self.currency_id_to_name, dict), "Invalid type for content 'currency_id_to_name'. Expected 'dict'. Found '{}'.".format( type(self.currency_id_to_name) ), ) for ( key_of_currency_id_to_name, value_of_currency_id_to_name, ) in self.currency_id_to_name.items(): enforce( isinstance(key_of_currency_id_to_name, str), "Invalid type for dictionary keys in content 'currency_id_to_name'. Expected 'str'. Found '{}'.".format( type(key_of_currency_id_to_name) ), ) enforce( isinstance(value_of_currency_id_to_name, str), "Invalid type for dictionary values in content 'currency_id_to_name'. Expected 'str'. Found '{}'.".format( type(value_of_currency_id_to_name) ), ) enforce( isinstance(self.good_id_to_name, dict), "Invalid type for content 'good_id_to_name'. Expected 'dict'. Found '{}'.".format( type(self.good_id_to_name) ), ) for ( key_of_good_id_to_name, value_of_good_id_to_name, ) in self.good_id_to_name.items(): enforce( isinstance(key_of_good_id_to_name, str), "Invalid type for dictionary keys in content 'good_id_to_name'. Expected 'str'. Found '{}'.".format( type(key_of_good_id_to_name) ), ) enforce( isinstance(value_of_good_id_to_name, str), "Invalid type for dictionary values in content 'good_id_to_name'. Expected 'str'. Found '{}'.".format( type(value_of_good_id_to_name) ), ) enforce( isinstance(self.version_id, str), "Invalid type for content 'version_id'. Expected 'str'. Found '{}'.".format( type(self.version_id) ), ) if self.is_set("info"): expected_nb_of_contents += 1 info = cast(Dict[str, str], self.info) enforce( isinstance(info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(info) ), ) for key_of_info, value_of_info in info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) elif self.performative == TacMessage.Performative.TRANSACTION_CONFIRMATION: expected_nb_of_contents = 3 enforce( isinstance(self.transaction_id, str), "Invalid type for content 'transaction_id'. Expected 'str'. Found '{}'.".format( type(self.transaction_id) ), ) enforce( isinstance(self.amount_by_currency_id, dict), "Invalid type for content 'amount_by_currency_id'. Expected 'dict'. Found '{}'.".format( type(self.amount_by_currency_id) ), ) for ( key_of_amount_by_currency_id, value_of_amount_by_currency_id, ) in self.amount_by_currency_id.items(): enforce( isinstance(key_of_amount_by_currency_id, str), "Invalid type for dictionary keys in content 'amount_by_currency_id'. Expected 'str'. Found '{}'.".format( type(key_of_amount_by_currency_id) ), ) enforce( type(value_of_amount_by_currency_id) is int, "Invalid type for dictionary values in content 'amount_by_currency_id'. Expected 'int'. Found '{}'.".format( type(value_of_amount_by_currency_id) ), ) enforce( isinstance(self.quantities_by_good_id, dict), "Invalid type for content 'quantities_by_good_id'. Expected 'dict'. Found '{}'.".format( type(self.quantities_by_good_id) ), ) for ( key_of_quantities_by_good_id, value_of_quantities_by_good_id, ) in self.quantities_by_good_id.items(): enforce( isinstance(key_of_quantities_by_good_id, str), "Invalid type for dictionary keys in content 'quantities_by_good_id'. Expected 'str'. Found '{}'.".format( type(key_of_quantities_by_good_id) ), ) enforce( type(value_of_quantities_by_good_id) is int, "Invalid type for dictionary values in content 'quantities_by_good_id'. Expected 'int'. Found '{}'.".format( type(value_of_quantities_by_good_id) ), ) elif self.performative == TacMessage.Performative.TAC_ERROR: expected_nb_of_contents = 1 enforce( isinstance(self.error_code, CustomErrorCode), "Invalid type for content 'error_code'. Expected 'ErrorCode'. Found '{}'.".format( type(self.error_code) ), ) if self.is_set("info"): expected_nb_of_contents += 1 info = cast(Dict[str, str], self.info) enforce( isinstance(info, dict), "Invalid type for content 'info'. Expected 'dict'. Found '{}'.".format( type(info) ), ) for key_of_info, value_of_info in info.items(): enforce( isinstance(key_of_info, str), "Invalid type for dictionary keys in content 'info'. Expected 'str'. Found '{}'.".format( type(key_of_info) ), ) enforce( isinstance(value_of_info, str), "Invalid type for dictionary values in content 'info'. Expected 'str'. Found '{}'.".format( type(value_of_info) ), ) # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: packages/fetchai/protocols/tac/protocol.yaml ================================================ name: tac author: fetchai version: 1.1.7 protocol_specification_id: fetchai/tac:1.0.0 type: protocol description: The tac protocol implements the messages an AEA needs to participate in the TAC. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmZdoSoTZHxvDbwdxm6d7xgRjCqvzNDcvhpUKS2inKUAaV __init__.py: QmNxUNMKkhimqesJ52WFyG1jZqXC9CNo7ftsao9EKwAqay custom_types.py: QmWUg194r4jqm6U2SY4B9CADn2bNE56qrpWJrVnGPvSz8v dialogues.py: QmRhZrnyT4YaXsPXAr1ota3ndp64MAcc3XNcGF2sCN4z4Q message.py: QmT3aDoBkR7VYoC9BEnMqNLJFZycSjcPGjG4ThUJNMjRgG serialization.py: QmdHc6cF7GM6eMD3VqNjAwibvY9Bnd98SxvTrzDJW5tsAM tac.proto: QmVLyb1hc9SmouyhBXT2g8RYHsyitqxqDMssnMnPj5AhCx tac_pb2.py: QmdzW9DyYGaEA6evnb4mVHQRkxRyzmgiN1LdiFrck9T6Ai fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: packages/fetchai/protocols/tac/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for tac protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from packages.fetchai.protocols.tac import tac_pb2 from packages.fetchai.protocols.tac.custom_types import ErrorCode from packages.fetchai.protocols.tac.message import TacMessage class TacSerializer(Serializer): """Serialization for the 'tac' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'Tac' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(TacMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() tac_msg = tac_pb2.TacMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == TacMessage.Performative.REGISTER: performative = tac_pb2.TacMessage.Register_Performative() # type: ignore agent_name = msg.agent_name performative.agent_name = agent_name tac_msg.register.CopyFrom(performative) elif performative_id == TacMessage.Performative.UNREGISTER: performative = tac_pb2.TacMessage.Unregister_Performative() # type: ignore tac_msg.unregister.CopyFrom(performative) elif performative_id == TacMessage.Performative.TRANSACTION: performative = tac_pb2.TacMessage.Transaction_Performative() # type: ignore transaction_id = msg.transaction_id performative.transaction_id = transaction_id ledger_id = msg.ledger_id performative.ledger_id = ledger_id sender_address = msg.sender_address performative.sender_address = sender_address counterparty_address = msg.counterparty_address performative.counterparty_address = counterparty_address amount_by_currency_id = msg.amount_by_currency_id performative.amount_by_currency_id.update(amount_by_currency_id) fee_by_currency_id = msg.fee_by_currency_id performative.fee_by_currency_id.update(fee_by_currency_id) quantities_by_good_id = msg.quantities_by_good_id performative.quantities_by_good_id.update(quantities_by_good_id) nonce = msg.nonce performative.nonce = nonce sender_signature = msg.sender_signature performative.sender_signature = sender_signature counterparty_signature = msg.counterparty_signature performative.counterparty_signature = counterparty_signature tac_msg.transaction.CopyFrom(performative) elif performative_id == TacMessage.Performative.CANCELLED: performative = tac_pb2.TacMessage.Cancelled_Performative() # type: ignore tac_msg.cancelled.CopyFrom(performative) elif performative_id == TacMessage.Performative.GAME_DATA: performative = tac_pb2.TacMessage.Game_Data_Performative() # type: ignore amount_by_currency_id = msg.amount_by_currency_id performative.amount_by_currency_id.update(amount_by_currency_id) exchange_params_by_currency_id = msg.exchange_params_by_currency_id performative.exchange_params_by_currency_id.update( exchange_params_by_currency_id ) quantities_by_good_id = msg.quantities_by_good_id performative.quantities_by_good_id.update(quantities_by_good_id) utility_params_by_good_id = msg.utility_params_by_good_id performative.utility_params_by_good_id.update(utility_params_by_good_id) fee_by_currency_id = msg.fee_by_currency_id performative.fee_by_currency_id.update(fee_by_currency_id) agent_addr_to_name = msg.agent_addr_to_name performative.agent_addr_to_name.update(agent_addr_to_name) currency_id_to_name = msg.currency_id_to_name performative.currency_id_to_name.update(currency_id_to_name) good_id_to_name = msg.good_id_to_name performative.good_id_to_name.update(good_id_to_name) version_id = msg.version_id performative.version_id = version_id if msg.is_set("info"): performative.info_is_set = True info = msg.info performative.info.update(info) tac_msg.game_data.CopyFrom(performative) elif performative_id == TacMessage.Performative.TRANSACTION_CONFIRMATION: performative = tac_pb2.TacMessage.Transaction_Confirmation_Performative() # type: ignore transaction_id = msg.transaction_id performative.transaction_id = transaction_id amount_by_currency_id = msg.amount_by_currency_id performative.amount_by_currency_id.update(amount_by_currency_id) quantities_by_good_id = msg.quantities_by_good_id performative.quantities_by_good_id.update(quantities_by_good_id) tac_msg.transaction_confirmation.CopyFrom(performative) elif performative_id == TacMessage.Performative.TAC_ERROR: performative = tac_pb2.TacMessage.Tac_Error_Performative() # type: ignore error_code = msg.error_code ErrorCode.encode(performative.error_code, error_code) if msg.is_set("info"): performative.info_is_set = True info = msg.info performative.info.update(info) tac_msg.tac_error.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = tac_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'Tac' message. :param obj: the bytes object. :return: the 'Tac' message. """ message_pb = ProtobufMessage() tac_pb = tac_pb2.TacMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target tac_pb.ParseFromString(message_pb.dialogue_message.content) performative = tac_pb.WhichOneof("performative") performative_id = TacMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == TacMessage.Performative.REGISTER: agent_name = tac_pb.register.agent_name performative_content["agent_name"] = agent_name elif performative_id == TacMessage.Performative.UNREGISTER: pass elif performative_id == TacMessage.Performative.TRANSACTION: transaction_id = tac_pb.transaction.transaction_id performative_content["transaction_id"] = transaction_id ledger_id = tac_pb.transaction.ledger_id performative_content["ledger_id"] = ledger_id sender_address = tac_pb.transaction.sender_address performative_content["sender_address"] = sender_address counterparty_address = tac_pb.transaction.counterparty_address performative_content["counterparty_address"] = counterparty_address amount_by_currency_id = tac_pb.transaction.amount_by_currency_id amount_by_currency_id_dict = dict(amount_by_currency_id) performative_content["amount_by_currency_id"] = amount_by_currency_id_dict fee_by_currency_id = tac_pb.transaction.fee_by_currency_id fee_by_currency_id_dict = dict(fee_by_currency_id) performative_content["fee_by_currency_id"] = fee_by_currency_id_dict quantities_by_good_id = tac_pb.transaction.quantities_by_good_id quantities_by_good_id_dict = dict(quantities_by_good_id) performative_content["quantities_by_good_id"] = quantities_by_good_id_dict nonce = tac_pb.transaction.nonce performative_content["nonce"] = nonce sender_signature = tac_pb.transaction.sender_signature performative_content["sender_signature"] = sender_signature counterparty_signature = tac_pb.transaction.counterparty_signature performative_content["counterparty_signature"] = counterparty_signature elif performative_id == TacMessage.Performative.CANCELLED: pass elif performative_id == TacMessage.Performative.GAME_DATA: amount_by_currency_id = tac_pb.game_data.amount_by_currency_id amount_by_currency_id_dict = dict(amount_by_currency_id) performative_content["amount_by_currency_id"] = amount_by_currency_id_dict exchange_params_by_currency_id = ( tac_pb.game_data.exchange_params_by_currency_id ) exchange_params_by_currency_id_dict = dict(exchange_params_by_currency_id) performative_content[ "exchange_params_by_currency_id" ] = exchange_params_by_currency_id_dict quantities_by_good_id = tac_pb.game_data.quantities_by_good_id quantities_by_good_id_dict = dict(quantities_by_good_id) performative_content["quantities_by_good_id"] = quantities_by_good_id_dict utility_params_by_good_id = tac_pb.game_data.utility_params_by_good_id utility_params_by_good_id_dict = dict(utility_params_by_good_id) performative_content[ "utility_params_by_good_id" ] = utility_params_by_good_id_dict fee_by_currency_id = tac_pb.game_data.fee_by_currency_id fee_by_currency_id_dict = dict(fee_by_currency_id) performative_content["fee_by_currency_id"] = fee_by_currency_id_dict agent_addr_to_name = tac_pb.game_data.agent_addr_to_name agent_addr_to_name_dict = dict(agent_addr_to_name) performative_content["agent_addr_to_name"] = agent_addr_to_name_dict currency_id_to_name = tac_pb.game_data.currency_id_to_name currency_id_to_name_dict = dict(currency_id_to_name) performative_content["currency_id_to_name"] = currency_id_to_name_dict good_id_to_name = tac_pb.game_data.good_id_to_name good_id_to_name_dict = dict(good_id_to_name) performative_content["good_id_to_name"] = good_id_to_name_dict version_id = tac_pb.game_data.version_id performative_content["version_id"] = version_id if tac_pb.game_data.info_is_set: info = tac_pb.game_data.info info_dict = dict(info) performative_content["info"] = info_dict elif performative_id == TacMessage.Performative.TRANSACTION_CONFIRMATION: transaction_id = tac_pb.transaction_confirmation.transaction_id performative_content["transaction_id"] = transaction_id amount_by_currency_id = ( tac_pb.transaction_confirmation.amount_by_currency_id ) amount_by_currency_id_dict = dict(amount_by_currency_id) performative_content["amount_by_currency_id"] = amount_by_currency_id_dict quantities_by_good_id = ( tac_pb.transaction_confirmation.quantities_by_good_id ) quantities_by_good_id_dict = dict(quantities_by_good_id) performative_content["quantities_by_good_id"] = quantities_by_good_id_dict elif performative_id == TacMessage.Performative.TAC_ERROR: pb2_error_code = tac_pb.tac_error.error_code error_code = ErrorCode.decode(pb2_error_code) performative_content["error_code"] = error_code if tac_pb.tac_error.info_is_set: info = tac_pb.tac_error.info info_dict = dict(info) performative_content["info"] = info_dict else: raise ValueError("Performative not valid: {}.".format(performative_id)) return TacMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content ) ================================================ FILE: packages/fetchai/protocols/tac/tac.proto ================================================ syntax = "proto3"; package aea.fetchai.tac.v1_0_0; message TacMessage{ // Custom Types message ErrorCode{ enum ErrorCodeEnum { GENERIC_ERROR = 0; REQUEST_NOT_VALID = 1; AGENT_ADDR_ALREADY_REGISTERED = 2; AGENT_NAME_ALREADY_REGISTERED = 3; AGENT_NOT_REGISTERED = 4; TRANSACTION_NOT_VALID = 5; TRANSACTION_NOT_MATCHING = 6; AGENT_NAME_NOT_IN_WHITELIST = 7; COMPETITION_NOT_RUNNING = 8; DIALOGUE_INCONSISTENT = 9; } ErrorCodeEnum error_code = 1; } // Performatives and contents message Register_Performative{ string agent_name = 1; } message Unregister_Performative{ } message Transaction_Performative{ string transaction_id = 1; string ledger_id = 2; string sender_address = 3; string counterparty_address = 4; map amount_by_currency_id = 5; map fee_by_currency_id = 6; map quantities_by_good_id = 7; string nonce = 8; string sender_signature = 9; string counterparty_signature = 10; } message Cancelled_Performative{ } message Game_Data_Performative{ map amount_by_currency_id = 1; map exchange_params_by_currency_id = 2; map quantities_by_good_id = 3; map utility_params_by_good_id = 4; map fee_by_currency_id = 5; map agent_addr_to_name = 6; map currency_id_to_name = 7; map good_id_to_name = 8; string version_id = 9; map info = 10; bool info_is_set = 11; } message Transaction_Confirmation_Performative{ string transaction_id = 1; map amount_by_currency_id = 2; map quantities_by_good_id = 3; } message Tac_Error_Performative{ ErrorCode error_code = 1; map info = 2; bool info_is_set = 3; } oneof performative{ Cancelled_Performative cancelled = 5; Game_Data_Performative game_data = 6; Register_Performative register = 7; Tac_Error_Performative tac_error = 8; Transaction_Performative transaction = 9; Transaction_Confirmation_Performative transaction_confirmation = 10; Unregister_Performative unregister = 11; } } ================================================ FILE: packages/fetchai/protocols/tac/tac_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: tac.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\ttac.proto\x12\x16\x61\x65\x61.fetchai.tac.v1_0_0"\xf9\x1f\n\nTacMessage\x12N\n\tcancelled\x18\x05 \x01(\x0b\x32\x39.aea.fetchai.tac.v1_0_0.TacMessage.Cancelled_PerformativeH\x00\x12N\n\tgame_data\x18\x06 \x01(\x0b\x32\x39.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_PerformativeH\x00\x12L\n\x08register\x18\x07 \x01(\x0b\x32\x38.aea.fetchai.tac.v1_0_0.TacMessage.Register_PerformativeH\x00\x12N\n\ttac_error\x18\x08 \x01(\x0b\x32\x39.aea.fetchai.tac.v1_0_0.TacMessage.Tac_Error_PerformativeH\x00\x12R\n\x0btransaction\x18\t \x01(\x0b\x32;.aea.fetchai.tac.v1_0_0.TacMessage.Transaction_PerformativeH\x00\x12l\n\x18transaction_confirmation\x18\n \x01(\x0b\x32H.aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Confirmation_PerformativeH\x00\x12P\n\nunregister\x18\x0b \x01(\x0b\x32:.aea.fetchai.tac.v1_0_0.TacMessage.Unregister_PerformativeH\x00\x1a\x89\x03\n\tErrorCode\x12N\n\nerror_code\x18\x01 \x01(\x0e\x32:.aea.fetchai.tac.v1_0_0.TacMessage.ErrorCode.ErrorCodeEnum"\xab\x02\n\rErrorCodeEnum\x12\x11\n\rGENERIC_ERROR\x10\x00\x12\x15\n\x11REQUEST_NOT_VALID\x10\x01\x12!\n\x1d\x41GENT_ADDR_ALREADY_REGISTERED\x10\x02\x12!\n\x1d\x41GENT_NAME_ALREADY_REGISTERED\x10\x03\x12\x18\n\x14\x41GENT_NOT_REGISTERED\x10\x04\x12\x19\n\x15TRANSACTION_NOT_VALID\x10\x05\x12\x1c\n\x18TRANSACTION_NOT_MATCHING\x10\x06\x12\x1f\n\x1b\x41GENT_NAME_NOT_IN_WHITELIST\x10\x07\x12\x1b\n\x17\x43OMPETITION_NOT_RUNNING\x10\x08\x12\x19\n\x15\x44IALOGUE_INCONSISTENT\x10\t\x1a+\n\x15Register_Performative\x12\x12\n\nagent_name\x18\x01 \x01(\t\x1a\x19\n\x17Unregister_Performative\x1a\xc8\x05\n\x18Transaction_Performative\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\x11\n\tledger_id\x18\x02 \x01(\t\x12\x16\n\x0esender_address\x18\x03 \x01(\t\x12\x1c\n\x14\x63ounterparty_address\x18\x04 \x01(\t\x12r\n\x15\x61mount_by_currency_id\x18\x05 \x03(\x0b\x32S.aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Performative.AmountByCurrencyIdEntry\x12l\n\x12\x66\x65\x65_by_currency_id\x18\x06 \x03(\x0b\x32P.aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Performative.FeeByCurrencyIdEntry\x12r\n\x15quantities_by_good_id\x18\x07 \x03(\x0b\x32S.aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Performative.QuantitiesByGoodIdEntry\x12\r\n\x05nonce\x18\x08 \x01(\t\x12\x18\n\x10sender_signature\x18\t \x01(\t\x12\x1e\n\x16\x63ounterparty_signature\x18\n \x01(\t\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x36\n\x14\x46\x65\x65\x42yCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x18\n\x16\x43\x61ncelled_Performative\x1a\xa3\x0c\n\x16Game_Data_Performative\x12p\n\x15\x61mount_by_currency_id\x18\x01 \x03(\x0b\x32Q.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.AmountByCurrencyIdEntry\x12\x81\x01\n\x1e\x65xchange_params_by_currency_id\x18\x02 \x03(\x0b\x32Y.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.ExchangeParamsByCurrencyIdEntry\x12p\n\x15quantities_by_good_id\x18\x03 \x03(\x0b\x32Q.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.QuantitiesByGoodIdEntry\x12w\n\x19utility_params_by_good_id\x18\x04 \x03(\x0b\x32T.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.UtilityParamsByGoodIdEntry\x12j\n\x12\x66\x65\x65_by_currency_id\x18\x05 \x03(\x0b\x32N.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry\x12j\n\x12\x61gent_addr_to_name\x18\x06 \x03(\x0b\x32N.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.AgentAddrToNameEntry\x12l\n\x13\x63urrency_id_to_name\x18\x07 \x03(\x0b\x32O.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.CurrencyIdToNameEntry\x12\x64\n\x0fgood_id_to_name\x18\x08 \x03(\x0b\x32K.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.GoodIdToNameEntry\x12\x12\n\nversion_id\x18\t \x01(\t\x12Q\n\x04info\x18\n \x03(\x0b\x32\x43.aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.InfoEntry\x12\x13\n\x0binfo_is_set\x18\x0b \x01(\x08\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x41\n\x1f\x45xchangeParamsByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a<\n\x1aUtilityParamsByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x36\n\x14\x46\x65\x65\x42yCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x36\n\x14\x41gentAddrToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x37\n\x15\x43urrencyIdToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11GoodIdToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xb7\x03\n%Transaction_Confirmation_Performative\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\x7f\n\x15\x61mount_by_currency_id\x18\x02 \x03(\x0b\x32`.aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Confirmation_Performative.AmountByCurrencyIdEntry\x12\x7f\n\x15quantities_by_good_id\x18\x03 \x03(\x0b\x32`.aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Confirmation_Performative.QuantitiesByGoodIdEntry\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\xef\x01\n\x16Tac_Error_Performative\x12@\n\nerror_code\x18\x01 \x01(\x0b\x32,.aea.fetchai.tac.v1_0_0.TacMessage.ErrorCode\x12Q\n\x04info\x18\x02 \x03(\x0b\x32\x43.aea.fetchai.tac.v1_0_0.TacMessage.Tac_Error_Performative.InfoEntry\x12\x13\n\x0binfo_is_set\x18\x03 \x01(\x08\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0cperformativeb\x06proto3' ) _TACMESSAGE = DESCRIPTOR.message_types_by_name["TacMessage"] _TACMESSAGE_ERRORCODE = _TACMESSAGE.nested_types_by_name["ErrorCode"] _TACMESSAGE_REGISTER_PERFORMATIVE = _TACMESSAGE.nested_types_by_name[ "Register_Performative" ] _TACMESSAGE_UNREGISTER_PERFORMATIVE = _TACMESSAGE.nested_types_by_name[ "Unregister_Performative" ] _TACMESSAGE_TRANSACTION_PERFORMATIVE = _TACMESSAGE.nested_types_by_name[ "Transaction_Performative" ] _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY = ( _TACMESSAGE_TRANSACTION_PERFORMATIVE.nested_types_by_name["AmountByCurrencyIdEntry"] ) _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY = ( _TACMESSAGE_TRANSACTION_PERFORMATIVE.nested_types_by_name["FeeByCurrencyIdEntry"] ) _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = ( _TACMESSAGE_TRANSACTION_PERFORMATIVE.nested_types_by_name["QuantitiesByGoodIdEntry"] ) _TACMESSAGE_CANCELLED_PERFORMATIVE = _TACMESSAGE.nested_types_by_name[ "Cancelled_Performative" ] _TACMESSAGE_GAME_DATA_PERFORMATIVE = _TACMESSAGE.nested_types_by_name[ "Game_Data_Performative" ] _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name["AmountByCurrencyIdEntry"] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name[ "ExchangeParamsByCurrencyIdEntry" ] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name["QuantitiesByGoodIdEntry"] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name[ "UtilityParamsByGoodIdEntry" ] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name["FeeByCurrencyIdEntry"] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name["AgentAddrToNameEntry"] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name["CurrencyIdToNameEntry"] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name["GoodIdToNameEntry"] ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_INFOENTRY = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE = _TACMESSAGE.nested_types_by_name[ "Transaction_Confirmation_Performative" ] _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY = ( _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE.nested_types_by_name[ "AmountByCurrencyIdEntry" ] ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = ( _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE.nested_types_by_name[ "QuantitiesByGoodIdEntry" ] ) _TACMESSAGE_TAC_ERROR_PERFORMATIVE = _TACMESSAGE.nested_types_by_name[ "Tac_Error_Performative" ] _TACMESSAGE_TAC_ERROR_PERFORMATIVE_INFOENTRY = ( _TACMESSAGE_TAC_ERROR_PERFORMATIVE.nested_types_by_name["InfoEntry"] ) _TACMESSAGE_ERRORCODE_ERRORCODEENUM = _TACMESSAGE_ERRORCODE.enum_types_by_name[ "ErrorCodeEnum" ] TacMessage = _reflection.GeneratedProtocolMessageType( "TacMessage", (_message.Message,), { "ErrorCode": _reflection.GeneratedProtocolMessageType( "ErrorCode", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_ERRORCODE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.ErrorCode) }, ), "Register_Performative": _reflection.GeneratedProtocolMessageType( "Register_Performative", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_REGISTER_PERFORMATIVE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Register_Performative) }, ), "Unregister_Performative": _reflection.GeneratedProtocolMessageType( "Unregister_Performative", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_UNREGISTER_PERFORMATIVE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Unregister_Performative) }, ), "Transaction_Performative": _reflection.GeneratedProtocolMessageType( "Transaction_Performative", (_message.Message,), { "AmountByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "AmountByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Performative.AmountByCurrencyIdEntry) }, ), "FeeByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "FeeByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Performative.FeeByCurrencyIdEntry) }, ), "QuantitiesByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "QuantitiesByGoodIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Performative.QuantitiesByGoodIdEntry) }, ), "DESCRIPTOR": _TACMESSAGE_TRANSACTION_PERFORMATIVE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Performative) }, ), "Cancelled_Performative": _reflection.GeneratedProtocolMessageType( "Cancelled_Performative", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_CANCELLED_PERFORMATIVE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Cancelled_Performative) }, ), "Game_Data_Performative": _reflection.GeneratedProtocolMessageType( "Game_Data_Performative", (_message.Message,), { "AmountByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "AmountByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.AmountByCurrencyIdEntry) }, ), "ExchangeParamsByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "ExchangeParamsByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.ExchangeParamsByCurrencyIdEntry) }, ), "QuantitiesByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "QuantitiesByGoodIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.QuantitiesByGoodIdEntry) }, ), "UtilityParamsByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "UtilityParamsByGoodIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.UtilityParamsByGoodIdEntry) }, ), "FeeByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "FeeByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry) }, ), "AgentAddrToNameEntry": _reflection.GeneratedProtocolMessageType( "AgentAddrToNameEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.AgentAddrToNameEntry) }, ), "CurrencyIdToNameEntry": _reflection.GeneratedProtocolMessageType( "CurrencyIdToNameEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.CurrencyIdToNameEntry) }, ), "GoodIdToNameEntry": _reflection.GeneratedProtocolMessageType( "GoodIdToNameEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.GoodIdToNameEntry) }, ), "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_INFOENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative.InfoEntry) }, ), "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Game_Data_Performative) }, ), "Transaction_Confirmation_Performative": _reflection.GeneratedProtocolMessageType( "Transaction_Confirmation_Performative", (_message.Message,), { "AmountByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( "AmountByCurrencyIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Confirmation_Performative.AmountByCurrencyIdEntry) }, ), "QuantitiesByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "QuantitiesByGoodIdEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Confirmation_Performative.QuantitiesByGoodIdEntry) }, ), "DESCRIPTOR": _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Transaction_Confirmation_Performative) }, ), "Tac_Error_Performative": _reflection.GeneratedProtocolMessageType( "Tac_Error_Performative", (_message.Message,), { "InfoEntry": _reflection.GeneratedProtocolMessageType( "InfoEntry", (_message.Message,), { "DESCRIPTOR": _TACMESSAGE_TAC_ERROR_PERFORMATIVE_INFOENTRY, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Tac_Error_Performative.InfoEntry) }, ), "DESCRIPTOR": _TACMESSAGE_TAC_ERROR_PERFORMATIVE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage.Tac_Error_Performative) }, ), "DESCRIPTOR": _TACMESSAGE, "__module__": "tac_pb2" # @@protoc_insertion_point(class_scope:aea.fetchai.tac.v1_0_0.TacMessage) }, ) _sym_db.RegisterMessage(TacMessage) _sym_db.RegisterMessage(TacMessage.ErrorCode) _sym_db.RegisterMessage(TacMessage.Register_Performative) _sym_db.RegisterMessage(TacMessage.Unregister_Performative) _sym_db.RegisterMessage(TacMessage.Transaction_Performative) _sym_db.RegisterMessage(TacMessage.Transaction_Performative.AmountByCurrencyIdEntry) _sym_db.RegisterMessage(TacMessage.Transaction_Performative.FeeByCurrencyIdEntry) _sym_db.RegisterMessage(TacMessage.Transaction_Performative.QuantitiesByGoodIdEntry) _sym_db.RegisterMessage(TacMessage.Cancelled_Performative) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.AmountByCurrencyIdEntry) _sym_db.RegisterMessage( TacMessage.Game_Data_Performative.ExchangeParamsByCurrencyIdEntry ) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.QuantitiesByGoodIdEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.UtilityParamsByGoodIdEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.AgentAddrToNameEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.CurrencyIdToNameEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.GoodIdToNameEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.InfoEntry) _sym_db.RegisterMessage(TacMessage.Transaction_Confirmation_Performative) _sym_db.RegisterMessage( TacMessage.Transaction_Confirmation_Performative.AmountByCurrencyIdEntry ) _sym_db.RegisterMessage( TacMessage.Transaction_Confirmation_Performative.QuantitiesByGoodIdEntry ) _sym_db.RegisterMessage(TacMessage.Tac_Error_Performative) _sym_db.RegisterMessage(TacMessage.Tac_Error_Performative.InfoEntry) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._options = None _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY._options = None _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._options = None _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY._serialized_options = b"8\001" _TACMESSAGE_GAME_DATA_PERFORMATIVE_INFOENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_INFOENTRY._serialized_options = b"8\001" _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._options = ( None ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._options = ( None ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_options = ( b"8\001" ) _TACMESSAGE_TAC_ERROR_PERFORMATIVE_INFOENTRY._options = None _TACMESSAGE_TAC_ERROR_PERFORMATIVE_INFOENTRY._serialized_options = b"8\001" _TACMESSAGE._serialized_start = 38 _TACMESSAGE._serialized_end = 4127 _TACMESSAGE_ERRORCODE._serialized_start = 647 _TACMESSAGE_ERRORCODE._serialized_end = 1040 _TACMESSAGE_ERRORCODE_ERRORCODEENUM._serialized_start = 741 _TACMESSAGE_ERRORCODE_ERRORCODEENUM._serialized_end = 1040 _TACMESSAGE_REGISTER_PERFORMATIVE._serialized_start = 1042 _TACMESSAGE_REGISTER_PERFORMATIVE._serialized_end = 1085 _TACMESSAGE_UNREGISTER_PERFORMATIVE._serialized_start = 1087 _TACMESSAGE_UNREGISTER_PERFORMATIVE._serialized_end = 1112 _TACMESSAGE_TRANSACTION_PERFORMATIVE._serialized_start = 1115 _TACMESSAGE_TRANSACTION_PERFORMATIVE._serialized_end = 1827 _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_start = ( 1655 ) _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_end = 1712 _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY._serialized_start = 1714 _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY._serialized_end = 1768 _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_start = ( 1770 ) _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_end = 1827 _TACMESSAGE_CANCELLED_PERFORMATIVE._serialized_start = 1829 _TACMESSAGE_CANCELLED_PERFORMATIVE._serialized_end = 1853 _TACMESSAGE_GAME_DATA_PERFORMATIVE._serialized_start = 1856 _TACMESSAGE_GAME_DATA_PERFORMATIVE._serialized_end = 3427 _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_start = 1655 _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_end = 1712 _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._serialized_start = ( 2974 ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._serialized_end = ( 3039 ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_start = 1770 _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_end = 1827 _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._serialized_start = ( 3100 ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._serialized_end = 3160 _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY._serialized_start = 1714 _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY._serialized_end = 1768 _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY._serialized_start = 3218 _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY._serialized_end = 3272 _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY._serialized_start = 3274 _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY._serialized_end = 3329 _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY._serialized_start = 3331 _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY._serialized_end = 3382 _TACMESSAGE_GAME_DATA_PERFORMATIVE_INFOENTRY._serialized_start = 3384 _TACMESSAGE_GAME_DATA_PERFORMATIVE_INFOENTRY._serialized_end = 3427 _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE._serialized_start = 3430 _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE._serialized_end = 3869 _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_start = ( 1655 ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._serialized_end = ( 1712 ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_start = ( 1770 ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._serialized_end = ( 1827 ) _TACMESSAGE_TAC_ERROR_PERFORMATIVE._serialized_start = 3872 _TACMESSAGE_TAC_ERROR_PERFORMATIVE._serialized_end = 4111 _TACMESSAGE_TAC_ERROR_PERFORMATIVE_INFOENTRY._serialized_start = 3384 _TACMESSAGE_TAC_ERROR_PERFORMATIVE_INFOENTRY._serialized_end = 3427 # @@protoc_insertion_point(module_scope) ================================================ FILE: packages/fetchai/skills/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the skill packages authored by Fetch.ai.""" ================================================ FILE: packages/fetchai/skills/advanced_data_request/README.md ================================================ # Advanced Data Request ## Description This skill is used to get specific data from an API, which can either be shared with other agent skills or made available by http request. ## Behaviours - `advanced_data_request_behaviour`: requests data from specified source every `tick_interval` seconds from the API endpoint `url` specified in the skill configuration. ## Handlers - `http`: handles incoming `http` messages, retrieves the data from the appropriate response, stores it in shared state under the key: `observation`, and responds to requests satisfying the API specification listed in `api_spec.yaml`. ================================================ FILE: packages/fetchai/skills/advanced_data_request/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the AdvancedDataRequest skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/advanced_data_request:0.7.6") ================================================ FILE: packages/fetchai/skills/advanced_data_request/api_spec.yaml ================================================ openapi: "3.0.0" info: version: 0.1.0 title: advanced_data_request_api paths: /data: get: summary: Advanced data request operationId: data responses: '200': description: A JSON response including the data content: application/json: schema: type: object '404': description: The specified data was not found. default: description: Unexpected error ================================================ FILE: packages/fetchai/skills/advanced_data_request/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a behaviour for fetching data from an API.""" import json from typing import Any, Dict, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_ID, ) from packages.fetchai.connections.prometheus.connection import ( PUBLIC_ID as PROM_CONNECTION_ID, ) from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.skills.advanced_data_request.dialogues import ( HttpDialogues, PrometheusDialogues, ) from packages.fetchai.skills.advanced_data_request.models import ( AdvancedDataRequestModel, ) class AdvancedDataRequestBehaviour(TickerBehaviour): """This class provides a simple behaviour to fetch data.""" def __init__(self, **kwargs: Any): """Initialize the advanced data request behaviour.""" super().__init__(**kwargs) def send_http_request_message(self) -> None: """Send an http request message.""" # context http_dialogues = cast(HttpDialogues, self.context.http_dialogues) model = cast(AdvancedDataRequestModel, self.context.advanced_data_request_model) content = model.body # http request message request_http_message, _ = http_dialogues.create( counterparty=str(HTTP_CLIENT_ID), performative=HttpMessage.Performative.REQUEST, method=model.method, url=model.url, headers="Content-Type: application/json", version="", body=b"" if content is None else json.dumps(content).encode("utf-8"), ) # send message self.context.outbox.put_message(message=request_http_message) def add_prometheus_metric( self, metric_name: str, metric_type: str, description: str, labels: Dict[str, str], ) -> None: """ Add a prometheus metric. :param metric_name: the name of the metric to add. :param metric_type: the type of the metric. :param description: a description of the metric. :param labels: the metric labels. """ # context prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) # prometheus update message message, _ = prom_dialogues.create( counterparty=str(PROM_CONNECTION_ID), performative=PrometheusMessage.Performative.ADD_METRIC, type=metric_type, title=metric_name, description=description, labels=labels, ) # send message self.context.outbox.put_message(message=message) def update_prometheus_metric( self, metric_name: str, update_func: str, value: float, labels: Dict[str, str], ) -> None: """ Update a prometheus metric. :param metric_name: the name of the metric. :param update_func: the name of the update function (e.g. inc, dec, set, ...). :param value: the value to provide to the update function. :param labels: the metric labels. """ # context prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) # prometheus update message message, _ = prom_dialogues.create( counterparty=str(PROM_CONNECTION_ID), performative=PrometheusMessage.Performative.UPDATE_METRIC, title=metric_name, callable=update_func, value=value, labels=labels, ) # send message self.context.outbox.put_message(message=message) def setup(self) -> None: """Implement the setup.""" self.context.logger.info("setting up AdvancedDataRequestBehaviour") prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) if prom_dialogues.enabled: for metric in prom_dialogues.metrics: metric_name = metric["name"] self.context.logger.info("Adding Prometheus metric: " + metric_name) self.add_prometheus_metric( metric_name, metric["type"], metric["description"], dict(metric["labels"]), ) def act(self) -> None: """Implement the act.""" model = cast(AdvancedDataRequestModel, self.context.advanced_data_request_model) self.context.logger.info(f"Fetching data from {model.url}") self.send_http_request_message() def teardown(self) -> None: """Implement the task teardown.""" self.context.logger.info("tearing down AdvancedDataRequestBehaviour") ================================================ FILE: packages/fetchai/skills/advanced_data_request/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains dialogues used by the advanced_data_request skill.""" from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.prometheus.dialogues import ( PrometheusDialogue as BasePrometheusDialogue, ) from packages.fetchai.protocols.prometheus.dialogues import ( PrometheusDialogues as BasePrometheusDialogues, ) HttpDialogue = BaseHttpDialogue PrometheusDialogue = BasePrometheusDialogue class HttpDialogues(Model, BaseHttpDialogues): """This class keeps track of all http dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent in this dialogue """ if ( message.performative == HttpMessage.Performative.REQUEST and message.sender != receiver_address ) or ( message.performative == HttpMessage.Performative.RESPONSE and message.sender == receiver_address ): return BaseHttpDialogue.Role.SERVER return BaseHttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) class PrometheusDialogues(Model, BasePrometheusDialogues): """The dialogues class keeps track of all prometheus dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ self.enabled = kwargs.pop("enabled", False) self.metrics = kwargs.pop("metrics", []) Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return PrometheusDialogue.Role.AGENT BasePrometheusDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/advanced_data_request/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains handlers for the advanced_data_request skill.""" import json from typing import Any, Dict, Optional, SupportsFloat, cast from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.skills.advanced_data_request.dialogues import ( HttpDialogue, HttpDialogues, PrometheusDialogue, PrometheusDialogues, ) def find(dotted_path: str, data: Dict[str, Any]) -> Optional[Any]: """Find entry at dotted_path in data""" keys = dotted_path.split(".") value = data for key in keys: value = value.get(key, {}) return None if value == {} else value def is_number(value: SupportsFloat) -> bool: """Test if value is a number""" if value is None: return False try: float(value) return True except ValueError: return False class HttpHandler(Handler): """This class provides a simple http handler.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id def __init__(self, **kwargs: Any): """Initialize the handler.""" super().__init__(**kwargs) self._http_server_id = None # type: Optional[PublicId] def setup(self) -> None: """Set up the handler.""" self.context.logger.info("setting up HttpHandler") # skill can be used with or without http server if ( self.context.advanced_data_request_model.use_http_server ): # pylint: disable=import-outside-toplevel from packages.fetchai.connections.http_server.connection import ( PUBLIC_ID as HTTP_SERVER_ID, ) self._http_server_id = HTTP_SERVER_ID def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ message = cast(HttpMessage, message) # recover dialogue http_dialogues = cast(HttpDialogues, self.context.http_dialogues) http_dialogue = cast(HttpDialogue, http_dialogues.update(message)) if http_dialogue is None: self._handle_unidentified_dialogue(message) return if ( message.performative == HttpMessage.Performative.RESPONSE and message.status_code == 200 ): self._handle_response(message) elif message.performative == HttpMessage.Performative.REQUEST: self._handle_request(message, http_dialogue) else: self.context.logger.info( f"got unexpected http message: code = {message.status_code}" ) def _handle_response(self, http_msg: HttpMessage) -> None: """ Handle an Http response. :param http_msg: the http message """ model = self.context.advanced_data_request_model msg_body = json.loads(http_msg.body) success = False for output in model.outputs: json_path = output["json_path"] # find desired output data in msg_body value = cast(SupportsFloat, find(json_path, msg_body)) # if value is a numeric type, store it as fixed-point with number of decimals if is_number(value): float_value = float(value) int_value = int(float_value * 10**model.decimals) observation = { output["name"]: {"value": int_value, "decimals": model.decimals} } elif isinstance(value, str): observation = {output["name"]: {"value": value}} else: self.context.logger.warning( f"No valid output for {output['name']} found in response." ) continue success = True self.context.shared_state.update(observation) self.context.logger.info(f"Observation: {observation}") if success and self.context.prometheus_dialogues.enabled: metric_name = "num_retrievals" self.context.behaviours.advanced_data_request_behaviour.update_prometheus_metric( metric_name, "inc", 1.0, {} ) def _handle_request( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle a Http request. :param http_msg: the http message :param http_dialogue: the http dialogue """ self.context.logger.info( "received http request with method={}, url={} and body={!r}".format( http_msg.method, http_msg.url, http_msg.body, ) ) if self._http_server_id: if http_msg.method == "get": self._handle_get(http_msg, http_dialogue) elif http_msg.method == "post": self.context.logger.info("method 'post' is not supported.") else: self.context.logger.info("http server is not enabled.") def _handle_get(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb GET. :param http_msg: the http message :param http_dialogue: the http dialogue """ model = self.context.advanced_data_request_model outputs = [output["name"] for output in model.outputs] data = { key: value for (key, value) in self.context.shared_state.items() if key in outputs } http_response = http_dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_msg, version=http_msg.version, status_code=200, status_text="Success", headers=http_msg.headers, body=json.dumps(data).encode("utf-8"), ) self.context.logger.info("responding with: {}".format(http_response)) self.context.outbox.put_message(message=http_response) if self.context.prometheus_dialogues.enabled: metric_name = "num_requests" self.context.behaviours.advanced_data_request_behaviour.update_prometheus_metric( metric_name, "inc", 1.0, {} ) def _handle_unidentified_dialogue(self, msg: Message) -> None: """ Handle an unidentified dialogue. :param msg: the unidentified message to be handled """ self.context.logger.info( "received invalid message={}, unidentified dialogue.".format(msg) ) def teardown(self) -> None: """Teardown the handler.""" class PrometheusHandler(Handler): """This class handles responses from the prometheus server.""" SUPPORTED_PROTOCOL = PrometheusMessage.protocol_id def setup(self) -> None: """Set up the handler.""" self.context.logger.info("setting up PrometheusHandler") def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ message = cast(PrometheusMessage, message) # recover dialogue prometheus_dialogues = cast( PrometheusDialogues, self.context.prometheus_dialogues ) prometheus_dialogue = cast( PrometheusDialogue, prometheus_dialogues.update(message) ) if prometheus_dialogue is None: self._handle_unidentified_dialogue(message) return if message.performative == PrometheusMessage.Performative.RESPONSE: self.context.logger.debug( f"Prometheus response ({message.code}): {message.message}" ) else: # pragma: nocover self.context.logger.debug( f"got unexpected prometheus message: Performative = {PrometheusMessage.Performative}" ) def _handle_unidentified_dialogue(self, msg: Message) -> None: """ Handle an unidentified dialogue. :param msg: the unidentified message to be handled """ self.context.logger.info( "received invalid message={}, unidentified dialogue.".format(msg) ) def teardown(self) -> None: """Teardown the handler.""" ================================================ FILE: packages/fetchai/skills/advanced_data_request/models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a model for the AdvancedDataRequest skill""" from typing import Any from aea.skills.base import Model DEFAULT_URL = "" DEFAULT_METHOD = "GET" DEFAULT_BODY = "" DEFAULT_OUTPUTS = None DEFAULT_DECIMALS = 5 DEFAULT_USE_HTTP_SERVER = False HTTP_REQUEST_METHODS = {"GET", "PUT", "POST", "PATCH", "DELETE"} class AdvancedDataRequestModel(Model): """This class models the AdvancedDataRequest skill.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ self.url = kwargs.pop("url", DEFAULT_URL) self.method = kwargs.pop("method", DEFAULT_METHOD) self.body = kwargs.pop("body", DEFAULT_BODY) self.outputs = kwargs.pop("outputs", DEFAULT_OUTPUTS) self.decimals = kwargs.pop("decimals", DEFAULT_DECIMALS) self.use_http_server = kwargs.pop("use_http_server", DEFAULT_USE_HTTP_SERVER) Model.__init__(self, **kwargs) self._validate_config() def _validate_config(self) -> None: # pragma: nocover """Ensure the configuration settings are all valid.""" msg = [] if not isinstance(self.url, str): msg.append("'url' must be provided as a string") if self.method not in HTTP_REQUEST_METHODS: msg.append(f"'method' must be one of {HTTP_REQUEST_METHODS}") if not isinstance(self.body, str): msg.append("'body' must be provided as a string") if not isinstance(self.outputs, list): msg.append("outputs must be provided as a list") else: for (ind, output) in enumerate(self.outputs): if not isinstance(output, dict): msg.append(f"output {ind} must be a dict") else: if "name" not in output: msg.append(f"output {ind} must include key 'name'") if "json_path" not in output: msg.append(f"output {ind} must include key 'json_path'") if not isinstance(self.decimals, int): msg.append("'decimals' must be provided as an integer") if not isinstance(self.use_http_server, bool): msg.append("'use_http_server' must be provided as a bool") if msg: raise ValueError("Invalid skill configuration: " + ",".join(msg)) ================================================ FILE: packages/fetchai/skills/advanced_data_request/skill.yaml ================================================ name: advanced_data_request author: fetchai version: 0.7.6 type: skill description: Retrieve data from an API license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmaibnVqk95FVuhSXKCsYvUQFAkLCwYtPyWDmaNVDSFWvp __init__.py: QmenhXvYML3HKHzYufiHqxCKqyaFLxdyDvAzQGdEF31ZSy api_spec.yaml: QmUPhCYr6tWDMysdMCQxT67oAKRdMbGpgqDfAA5wpei12s behaviours.py: QmNubn8GCjV2d12FQu2Zv3FjXyoxoEsKzAg3fbskhtL3S2 dialogues.py: QmQxtJ6xqwGkbTEJX2rn8YDDhdvSCe3HhbyQYTaz9FknrG handlers.py: QmP3hpp4LmW3ZX63LScByXKojaa9CVpTKocWf8eLu978nw models.py: QmV7PcnNvogzj4PwAMZ8CQvsTaxEKJMKVgkatSHba1RJxt fingerprint_ignore_patterns: [] contracts: [] protocols: - fetchai/http:1.1.7 - fetchai/prometheus:1.1.7 skills: [] behaviours: advanced_data_request_behaviour: args: tick_interval: 5 class_name: AdvancedDataRequestBehaviour handlers: http: args: {} class_name: HttpHandler prometheus: args: {} class_name: PrometheusHandler models: advanced_data_request_model: args: body: '' decimals: 5 method: GET outputs: [] url: '' use_http_server: false class_name: AdvancedDataRequestModel http_dialogues: args: {} class_name: HttpDialogues prometheus_dialogues: args: enabled: true metrics: - name: num_retrievals type: Gauge description: Number of data retrievals labels: {} - name: num_requests type: Gauge description: Number of data requests served labels: {} class_name: PrometheusDialogues dependencies: {} is_abstract: false connections: [] ================================================ FILE: packages/fetchai/skills/aries_alice/README.md ================================================ # Aries Alice ## Description This skill emulates the Alice actor in this demo. This skill is part of the Fetch.ai Aries demo. It simulates the Alice actor of the demo linked above. It first registers Alice on the sOEF. It then receives invitation details from Faber AEA. Then it connects with an underlying Aries cloud agent (ACA) instance and executes an `accept-invitation` command. ## Behaviours - `alice`: registers and unregisters Alice AEA on the sOEF ## Handlers - `default`: handles `default` messages for the invitation detail it receives from the Faber AEA - `http`: handles `http` messages for communicating with Alice ACA - `oef_search`: handles `oef_search` messages if registration on the sOEF was erratic ## Links - AEA Aries Demo - Hyperledger Demo ================================================ FILE: packages/fetchai/skills/aries_alice/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the aries_alice skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/aries_alice:0.26.6") ================================================ FILE: packages/fetchai/skills/aries_alice/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour of a generic seller AEA.""" import json from typing import Any, Dict, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_PUBLIC_ID, ) from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_alice.dialogues import ( HttpDialogues, OefSearchDialogues, ) from packages.fetchai.skills.aries_alice.strategy import Strategy DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 DEFAULT_SERVICES_INTERVAL = 60.0 class AliceBehaviour(TickerBehaviour): """This class implements a behaviour.""" def __init__(self, **kwargs: Any) -> None: """Initialise the behaviour.""" services_interval = kwargs.pop( "services_interval", DEFAULT_SERVICES_INTERVAL ) # type: int self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=services_interval, **kwargs) self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def send_http_request_message( self, method: str, url: str, content: Dict = None ) -> None: """ Send an http request message. :param method: the http request method (i.e. 'GET' or 'POST'). :param url: the url to send the message to. :param content: the payload. """ # context http_dialogues = cast(HttpDialogues, self.context.http_dialogues) # http request message request_http_message, _ = http_dialogues.create( counterparty=str(HTTP_CLIENT_PUBLIC_ID), performative=HttpMessage.Performative.REQUEST, method=method, url=url, headers="Content-Type: application/json", version="", body=b"" if content is None else json.dumps(content).encode("utf-8"), ) # send self.context.outbox.put_message(message=request_http_message) def setup(self) -> None: """Implement the setup.""" self.context.logger.info("My address is: " + self.context.agent_address) self._register_agent() def act(self) -> None: """Implement the act.""" self._retry_failed_registration() self.perform_agents_search() def perform_agents_search(self) -> None: """Perform agents search to query proofs from.""" strategy = cast(Strategy, self.context.strategy) if not strategy.is_searching: return query = strategy.get_location_and_service_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("Searching for agents on SOEF...") def teardown(self) -> None: """Implement the task teardown.""" self._unregister_service() self._unregister_agent() def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """Register the agent's service.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_service_description() self._register(description, "registering agent's service on the SOEF.") def register_genus(self) -> None: """Register the agent's personality genus.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """Unregister service from the SOEF.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering service from SOEF.") def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") ================================================ FILE: packages/fetchai/skills/aries_alice/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - Dialogue: The dialogue class maintains state of a dialogue and manages it. - Dialogues: The dialogues class keeps track of all dialogues. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) HttpDialogue = BaseHttpDialogue class HttpDialogues(Model, BaseHttpDialogues): """This class keeps track of all http dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseHttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/aries_alice/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers for the aries_alice skill.""" import json from typing import Any, Dict, List, Optional, cast from aea.common import Address from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_alice.behaviours import AliceBehaviour from packages.fetchai.skills.aries_alice.dialogues import ( DefaultDialogue, DefaultDialogues, HttpDialogue, HttpDialogues, OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.aries_alice.strategy import ( ADMIN_COMMAND_CREATE_INVITATION, ADMIN_COMMAND_RECEIVE_INVITE, Strategy, ) class DefaultHandler(Handler): """This class represents alice's handler for default messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ message = cast(DefaultMessage, message) # recover dialogue default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_dialogue = cast( Optional[DefaultDialogue], default_dialogues.update(message) ) if default_dialogue is None: self.context.logger.error( "alice -> default_handler -> handle(): something went wrong when adding the incoming default message to the dialogue." ) return if message.performative == DefaultMessage.Performative.BYTES: content_bytes = message.content content = json.loads(content_bytes) self.context.logger.info("Received message content:" + repr(content)) # accept invite if "@type" in content: strategy = cast(Strategy, self.context.strategy) strategy.invitations[content["@id"]] = message.sender self.context.behaviours.alice.send_http_request_message( method="POST", url=strategy.admin_url + ADMIN_COMMAND_RECEIVE_INVITE + "?auto_accept=true", content=content, ) def teardown(self) -> None: """Implement the handler teardown.""" class HttpHandler(Handler): """This class represents alice's handler for HTTP messages.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id # type: Optional[PublicId] def __init__(self, **kwargs: Any) -> None: """Initialize the handler.""" super().__init__(**kwargs) self.connected: Dict[str, Address] = {} # conn_id: agent addr self.addr_names: Dict[Address, str] = {} # agent addr: agent name self.connections_sent: Dict[str, Address] = {} # conn_id: agent addr self.cred_def_id: Optional[str] = None self.presentation_requests: List[Dict] = [] @property def invitations(self) -> Dict[str, str]: """Get list of invitation sent from the strategy object.""" strategy = cast(Strategy, self.context.strategy) return strategy.invitations def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ message = cast(HttpMessage, message) strategy = cast(Strategy, self.context.strategy) # recover dialogue http_dialogues = cast(HttpDialogues, self.context.http_dialogues) http_dialogue = cast(Optional[HttpDialogue], http_dialogues.update(message)) if http_dialogue is None: self.context.logger.error( "alice -> http_handler -> handle() -> REQUEST: something went wrong when adding the incoming HTTP webhook request message to the dialogue." ) return if message.performative == HttpMessage.Performative.REQUEST: # webhook content_bytes = message.body self.context.logger.info( "Received webhook message content:" + str(content_bytes) ) content = json.loads(content_bytes) if "invitation_msg_id" in content: if content["invitation_msg_id"] in self.invitations: if ( content["state"] == "active" and content["connection_id"] not in self.connected ): self.context.logger.info( f"Connected to {content['their_label']}" ) self.connected[content["connection_id"]] = self.invitations[ content["invitation_msg_id"] ] name = content["their_label"] self.addr_names[ self.invitations[content["invitation_msg_id"]] ] = name if name != "faber": body = { "connection_id": content["connection_id"], "proof_request": { "name": "Proof of Education", "version": "1.0", "requested_attributes": { "0_name_uuid": { "name": "name", "restrictions": [ {"cred_def_id": self.cred_def_id} ], }, "0_date_uuid": { "name": "date", "restrictions": [ {"cred_def_id": self.cred_def_id} ], }, "0_degree_uuid": { "name": "degree", "restrictions": [ {"cred_def_id": self.cred_def_id} ], }, "0_self_attested_thing_uuid": { "name": "self_attested_thing" }, }, "requested_predicates": {}, }, } self.context.behaviours.alice.send_http_request_message( method="POST", url=strategy.admin_url + "/present-proof/send-request", content=body, ) self.context.logger.info( f"Sent credentials proof request to {name}" ) elif "presentation_request_dict" in content: if content["role"] == "prover": if content["state"] == "request_received": self.context.behaviours.alice.send_http_request_message( method="GET", url=strategy.admin_url + f"/present-proof/records/{content['presentation_exchange_id']}/credentials", ) self.presentation_requests.append(content) self.context.logger.info("Got credentials proof request") elif ( content["role"] == "verifier" and content["state"] == "presentation_received" ): name = self.addr_names[self.connected[content["connection_id"]]] self.context.logger.info(f"Got credentials proof from {name}") elif "credential_proposal_dict" in content: if content["state"] == "credential_acked": self.cred_def_id = content["raw_credential"]["cred_def_id"] self.context.logger.info( f"Got crendetials from faber: schema:{self.cred_def_id} {content['credential_proposal_dict']['credential_proposal']}" ) strategy.is_searching = True self.context.behaviours.alice.perform_agents_search() else: self.context.logger.warning(f"unknown message {content}") elif ( message.performative == HttpMessage.Performative.RESPONSE ): # response to http_client request content_bytes = message.body content = json.loads(content_bytes) self.context.logger.info(f"Got response {content}") if "Error" in content: self.context.logger.error( "Something went wrong after I sent the administrative command of 'invitation receive'" ) elif "presentation_referents" in str(content): self._handle_creds_for_proof_request(content) else: self.context.logger.info( f"Received http response message content:{str(content)}" ) if "invitation" in content: self._send_invitation_message(content) def _handle_creds_for_proof_request(self, credentials: Dict) -> None: self.context.logger.info("start proof generating") strategy = cast(Strategy, self.context.strategy) credentials_by_ref = {} # type: ignore revealed = {} self_attested = {} predicates = {} if not self.presentation_requests: self.context.logger.warning("No presentastion requests pending") return presentation_request = self.presentation_requests.pop() if credentials: for row in credentials: for referent in row["presentation_referents"]: if referent not in credentials_by_ref: credentials_by_ref[referent] = row for referent in presentation_request["presentation_request"][ "requested_attributes" ]: if referent in credentials_by_ref: revealed[referent] = { "cred_id": credentials_by_ref[referent]["cred_info"]["referent"], "revealed": True, } else: self_attested[referent] = "my self-attested value" for referent in presentation_request["presentation_request"][ "requested_predicates" ]: if referent in credentials_by_ref: predicates[referent] = { "cred_id": credentials_by_ref[referent]["cred_info"]["referent"], "revealed": True, } request = { "requested_predicates": predicates, "requested_attributes": revealed, "self_attested_attributes": self_attested, } presentation_exchange_id = presentation_request["presentation_exchange_id"] self.context.behaviours.alice.send_http_request_message( method="POST", url=strategy.admin_url + "/present-proof/records/" + f"{presentation_exchange_id}/send-presentation", content=request, ) self.context.logger.info("proof generated and sent") def _send_invitation_message(self, connection: Dict) -> None: """ Send a default message to Alice. :param connection: the content of the connection message. """ strategy = cast(Strategy, self.context.strategy) # context connections_unsent = list( set(strategy.aea_addresses) - set(self.connections_sent.values()) ) if not connections_unsent: self.context.logger.info( "Every invitation pushed, skip this new connection" ) return target = connections_unsent[0] invitation = connection["invitation"] self.connections_sent[connection["connection_id"]] = target default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) message, _ = default_dialogues.create( counterparty=target, performative=DefaultMessage.Performative.BYTES, content=json.dumps(invitation).encode("utf-8"), ) # send self.context.outbox.put_message(message=message) self.context.logger.info(f"connection: {str(connection)}") self.context.logger.info(f"connection id: {connection['connection_id']}") # type: ignore self.context.logger.info(f"invitation: {str(invitation)}") self.context.logger.info( f"Sent invitation to {target}. Waiting for the invitation from agent {target} to finalise the connection..." ) def teardown(self) -> None: """Implement the handler teardown.""" class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the oef search message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( AliceBehaviour, self.context.behaviours.alice, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_search(self, oef_search_msg: OefSearchMessage) -> None: """ Handle the search response. :param oef_search_msg: the oef search message to be handled """ if len(oef_search_msg.agents) == 0: self.context.logger.info("No agents found. Keep searching") return self.context.logger.info( f"found agents {', '.join(oef_search_msg.agents)}, stopping search." ) strategy = cast(Strategy, self.context.strategy) strategy.is_searching = False strategy.aea_addresses = list(oef_search_msg.agents) # send invitations for addr in strategy.aea_addresses: self.context.behaviours.alice.send_http_request_message( method="POST", url=strategy.admin_url + ADMIN_COMMAND_CREATE_INVITATION, ) self.context.logger.info(f"created an invitation for {addr}.") def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): alice_behaviour = cast( AliceBehaviour, self.context.behaviours.alice, ) alice_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/aries_alice/skill.yaml ================================================ name: aries_alice author: fetchai version: 0.26.6 type: skill description: The aries_alice skill implements the alice player in the aries cloud agent demo license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmdtgELuz8SmSp3xdHdktd1SvmzSFsavwVYXjZxPMke5ij __init__.py: QmRdtdKnWntg3VjdGRQq4Yc2SErjD5k3epVVPmthAbZJXj behaviours.py: QmYc2UCizXg86mBBjS43K73Bq3URdnsW5BefnzfzA7fz4k dialogues.py: QmQvsX9sx5bDf588rrXmT2kzZSZapVpix2TkX4x422iTiu handlers.py: QmZ4fc49cqp92ApccgyE27WtkSkYbPj3CTendnEgYyVVuL strategy.py: QmP63YEBF9tQKSDYENn26D6x8fLqFtpU2npNK4mysdxsBz fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/oef_search:1.1.7 skills: [] behaviours: alice: args: max_soef_registration_retries: 5 services_interval: 20 class_name: AliceBehaviour handlers: default: args: {} class_name: DefaultHandler http: args: {} class_name: HttpHandler oef_search: args: {} class_name: OefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues http_dialogues: args: {} class_name: HttpDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: admin_host: 127.0.0.1 admin_port: 8031 classification: piece: classification value: identity.aries.alice location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data search_query: constraint_type: == search_key: intro_service search_value: intro_alice seed: null service_data: key: intro_service value: intro_alice class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/aries_alice/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import random from typing import Any, Dict, List from aea.common import Address from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.skills.base import Model # Search DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "intro_service", "search_value": "intro_alice", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 # default configs DEFAULT_ADMIN_HOST = "127.0.0.1" DEFAULT_ADMIN_PORT = 8031 # commands ADMIN_COMMAND_RECEIVE_INVITE = "/connections/receive-invitation" ADMIN_COMMAND_CREATE_INVITATION = "/connections/create-invitation" # convenience ALICE_ACA_IDENTITY = "Alice_ACA" HTTP_COUNTERPARTY = "HTTP Server" # search DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_DATA = {"key": "intro_service", "value": "intro_alice"} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "identity.aries.alice"} class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ # config self._admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) self._admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) self._admin_url = f"http://{self.admin_host}:{self.admin_port}" self._seed = ( kwargs.pop( "seed", None, ) or ( "my_seed_000000000000000000000000" + str(random.randint(100_000, 999_999)) # nosec )[-32:] ) # search location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} self.is_searching = False # Search self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self.invitations: Dict[str, Address] = {} self.connections: Dict[str, Address] = {} self.aea_addresses: List[Address] = [] super().__init__(**kwargs) @property def admin_host(self) -> str: """Get the admin host.""" return self._admin_host @property def admin_port(self) -> str: """Get the admin port.""" return self._admin_port @property def admin_url(self) -> str: """Get the admin URL.""" return self._admin_url @property def seed(self) -> str: """Get the wallet seed.""" return self._seed def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType( "distance", (self._agent_location["location"], self._radius) ), ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description ================================================ FILE: packages/fetchai/skills/aries_faber/README.md ================================================ # Aries Faber ## Description This skill emulates the Faber actor in this demo. This skill is part of the Fetch.ai Aries demo. It simulates the Faber actor of the demo linked above. It first registers a decentralised ID on an underlying ledger. It then connects with an underlying Aries cloud agent (ACA) instance, and forwards the following instructions: - register schema definition - register credential definition - create an invitation It then sends the invitation detail to an Alice agent that it finds via the sOEF. ## Behaviours - `faber`: searches for Alice AEA ## Handlers - `http`: handles `http` messages for communicating with the ledger and Faber ACA - `oef_search`: handles `oef_search` messages of finding Alice on the sOEF ## Links - AEA Aries Demo - Hyperledger Demo ================================================ FILE: packages/fetchai/skills/aries_faber/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the aries_faber skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/aries_faber:0.24.5") ================================================ FILE: packages/fetchai/skills/aries_faber/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour for the aries_faber skill.""" import json from typing import Any, Dict, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_PUBLIC_ID, ) from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_faber.dialogues import ( HttpDialogues, OefSearchDialogues, ) from packages.fetchai.skills.aries_faber.strategy import Strategy DEFAULT_SEARCH_INTERVAL = 5.0 class FaberBehaviour(TickerBehaviour): """This class represents the behaviour of faber.""" def __init__(self, **kwargs: Any): """Initialize the handler.""" search_interval = cast( float, kwargs.pop("search_interval", DEFAULT_SEARCH_INTERVAL) ) super().__init__(tick_interval=search_interval, **kwargs) def send_http_request_message( self, method: str, url: str, content: Dict = None ) -> None: """ Send an http request message. :param method: the http request method (i.e. 'GET' or 'POST'). :param url: the url to send the message to. :param content: the payload. """ # context http_dialogues = cast(HttpDialogues, self.context.http_dialogues) # http request message request_http_message, _ = http_dialogues.create( counterparty=str(HTTP_CLIENT_PUBLIC_ID), performative=HttpMessage.Performative.REQUEST, method=method, url=url, headers="Content-Type: application/json", version="", body=b"" if content is None else json.dumps(content).encode("utf-8"), ) # send self.context.outbox.put_message(message=request_http_message) def setup(self) -> None: """Implement the setup.""" strategy = cast(Strategy, self.context.strategy) strategy.is_searching = True def act(self) -> None: """Implement the act.""" strategy = cast(Strategy, self.context.strategy) if strategy.is_searching: query = strategy.get_location_and_service_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("Searching for Alice on SOEF...") def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/aries_faber/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - OefSearchDialogue: The dialogue class maintains state of a dialogue of type oef search and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef search. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) HttpDialogue = BaseHttpDialogue class HttpDialogues(Model, BaseHttpDialogues): """This class keeps track of all http dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseHttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.context.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/aries_faber/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers for the faber_alice skill.""" import json import random from typing import Any, Dict, List, Optional, cast from aea.common import Address from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_faber.dialogues import ( DefaultDialogues, HttpDialogue, HttpDialogues, OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.aries_faber.strategy import ( ADMIN_COMMAND_CREATE_INVITATION, ADMIN_COMMAND_CREDDEF, ADMIN_COMMAND_REGISTGER_PUBLIC_DID, ADMIN_COMMAND_SCEHMAS, ADMIN_COMMAND_STATUS, FABER_ACA_IDENTITY, LEDGER_COMMAND_REGISTER_DID, Strategy, ) DEFAULT_SEARCH_INTERVAL = 5.0 SUPPORT_REVOCATION = False class HttpHandler(Handler): """This class represents faber's handler for default messages.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id # type: Optional[PublicId] def __init__(self, **kwargs: Any): """Initialize the handler.""" super().__init__(**kwargs) # ACA stuff self.faber_identity = FABER_ACA_IDENTITY self.did = None # type: Optional[str] self._schema_id = None # type: Optional[str] self.credential_definition_id = None # type: Optional[str] # connections self.connections_sent: Dict[str, Address] = {} self.connections_set: Dict[str, Address] = {} self.counterparts_names: Dict[Address, str] = {} @property def schema_id(self) -> str: """Get schema id.""" if self._schema_id is None: raise ValueError("schema_id not set") return self._schema_id def _send_invitation_message(self, connection: Dict) -> None: """ Send a default message to Alice. :param connection: the content of the connection message. """ strategy = cast(Strategy, self.context.strategy) # context connections_unsent = list( set(strategy.aea_addresses) - set(self.connections_sent.values()) ) if not connections_unsent: self.context.logger.info( "Every invitation pushed, skip this new connection" ) return target = connections_unsent[0] invitation = connection["invitation"] self.connections_sent[connection["connection_id"]] = target default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) message, _ = default_dialogues.create( counterparty=target, performative=DefaultMessage.Performative.BYTES, content=json.dumps(invitation).encode("utf-8"), ) # send self.context.outbox.put_message(message=message) self.context.logger.info(f"connection: {str(connection)}") self.context.logger.info(f"connection id: {connection['connection_id']}") # type: ignore self.context.logger.info(f"invitation: {str(invitation)}") self.context.logger.info( f"Sent invitation to {target}. Waiting for the invitation from agent {target} to finalise the connection..." ) def _register_public_did_on_acapy(self) -> None: """Register DID on the ACA PY.""" strategy = cast(Strategy, self.context.strategy) self.context.behaviours.faber.send_http_request_message( method="POST", url=strategy.admin_url + ADMIN_COMMAND_REGISTGER_PUBLIC_DID + f"?did={self.did}", content="", ) def _register_did(self) -> None: """Register DID on the ledger.""" strategy = cast(Strategy, self.context.strategy) self.context.logger.info( f"Registering Faber_ACA with seed {str(strategy.seed)}" ) data = { "alias": self.faber_identity, "seed": strategy.seed, "role": "TRUST_ANCHOR", } self.context.behaviours.faber.send_http_request_message( method="POST", url=strategy.ledger_url + LEDGER_COMMAND_REGISTER_DID, content=data, ) def _register_schema( self, schema_name: str, version: str, schema_attrs: List[str] ) -> None: """ Register schema definition. :param schema_name: the name of the schema :param version: the version of the schema :param schema_attrs: the attributes of the schema """ strategy = cast(Strategy, self.context.strategy) schema_body = { "schema_name": schema_name, "schema_version": version, "attributes": schema_attrs, } self.context.logger.info(f"Registering schema {str(schema_body)}") # The following call isn't responded to. This is most probably because of missing options when running the accompanying ACA. # The accompanying ACA is not properly connected to the von network ledger (missing pointer to genesis file/wallet type) self.context.behaviours.faber.send_http_request_message( method="POST", url=strategy.admin_url + ADMIN_COMMAND_SCEHMAS, content=schema_body, ) def _register_creddef(self, schema_id: str) -> None: """ Register credential definition. :param schema_id: the id of the schema definition registered on the ledger """ strategy = cast(Strategy, self.context.strategy) credential_definition_body = { "schema_id": schema_id, "support_revocation": SUPPORT_REVOCATION, } self.context.behaviours.faber.send_http_request_message( method="POST", url=strategy.admin_url + ADMIN_COMMAND_CREDDEF, content=credential_definition_body, ) def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ message = cast(HttpMessage, message) # recover dialogue http_dialogues = cast(HttpDialogues, self.context.http_dialogues) http_dialogue = cast(Optional[HttpDialogue], http_dialogues.update(message)) if http_dialogue is None: self.context.logger.error( "something went wrong when adding the incoming HTTP message to the dialogue." ) return strategy = cast(Strategy, self.context.strategy) if ( message.performative == HttpMessage.Performative.RESPONSE and message.status_code == 200 ): # response to http request content_bytes = message.body # type: ignore content = json.loads(content_bytes) self.context.logger.info(f"Received message: {str(content)}") if "version" in content: # response to /status self._register_did() elif "did" in content: self.did = content["did"] self.context.logger.info(f"Received DID: {self.did}") self._register_public_did_on_acapy() elif "result" in content and "posture" in content["result"]: self.context.logger.info(f"Registered public DID: {content}") self._register_schema( schema_name="degree schema", version="0.0.1", schema_attrs=["average", "date", "degree", "name"], ) elif "schema_id" in content: self._schema_id = content["schema_id"] self._register_creddef(self.schema_id) elif "credential_definition_id" in content: self.credential_definition_id = content["credential_definition_id"] for _ in strategy.aea_addresses: # issue invitation for every agent self.context.behaviours.faber.send_http_request_message( method="POST", url=strategy.admin_url + ADMIN_COMMAND_CREATE_INVITATION, ) elif "invitation" in content: connection = content self._send_invitation_message(connection) elif "credential_proposal_dict" in content: connection_id = content["connection_id"] addr = self.connections_set[connection_id] name = self.counterparts_names[addr] self.context.logger.info( f"Credential issued for {name}({addr}): {content['credential_offer_dict']['credential_preview']}" ) else: self.context.logger.warning("UNKNOWN HTTP MESSAGE RESPONSE", message) elif ( message.performative == HttpMessage.Performative.REQUEST ): # webhook request content_bytes = message.body content = json.loads(content_bytes) self.context.logger.info(f"Received webhook message content:{str(content)}") if "connection_id" in content: connection_id = content["connection_id"] if connection_id not in self.connections_sent: return if not ( content["state"] == "active" and connection_id not in self.connections_set ): return addr = self.connections_sent[connection_id] name = content["their_label"] self.counterparts_names[addr] = name self.connections_set[connection_id] = addr self.context.logger.info(f"Connected to {name}({addr})") self.issue_crendetials_for(name, addr, connection_id) def issue_crendetials_for( self, name: str, address: Address, connection_id: str ) -> None: """Issue credentials for the agent.""" cred = { "connection_id": connection_id, "cred_def_id": self.credential_definition_id, "credential_proposal": { "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/1.0/credential-preview", "attributes": [ {"name": "name", "value": name}, {"name": "date", "value": "2022-01-01"}, { "name": "degree", "value": random.choice( # nosec ["Physics", "Chemistry", "Mathematics", "History"] ), }, { "name": "average", "value": str(random.choice([3, 4, 5])), # nosec }, ], }, } strategy = cast(Strategy, self.context.strategy) self.context.behaviours.faber.send_http_request_message( method="POST", url=strategy.admin_url + "/issue-credential/send", content=cred, ) self.context.logger.info( f"Credentials issue requested for {name}({address}) {cred}" ) def teardown(self) -> None: """Implement the handler teardown.""" class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ self.context.logger.info("Handling SOEF message...") oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the oef search message to be handled """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message to be handled :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_msg, oef_search_dialogue ) ) def _handle_search(self, oef_search_msg: OefSearchMessage) -> None: """ Handle the search response. :param oef_search_msg: the oef search message to be handled """ if len(oef_search_msg.agents) <= 1: self.context.logger.info("Waiting for more agents.") return self.context.logger.info( f"found agents {', '.join(oef_search_msg.agents)}, stopping search." ) strategy = cast(Strategy, self.context.strategy) strategy.is_searching = False # stopping search # set alice address strategy.aea_addresses = list(oef_search_msg.agents) # check ACA is running self.context.behaviours.faber.send_http_request_message( "GET", strategy.admin_url + ADMIN_COMMAND_STATUS ) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/aries_faber/skill.yaml ================================================ name: aries_faber author: fetchai version: 0.24.5 type: skill description: The aries_faber skill implements the faber player in the aries cloud agent demo license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmYKuXNqtkUvz4esnYnmg2iUvN3cNKTUzF92NmwPbD66L3 __init__.py: QmYToaj574eCcrzJ71BKJW4AZFGZ7SyT3Z7RLw3q2AVSiJ behaviours.py: QmZRkMM7UMa7x6jdUkHyDtGq1kBXVTHDwszcDna3HNQsdE dialogues.py: QmRXXA2vT1r65yb8iKAdhCbys7s2wy2M9Cjpf6jfEX6KZB handlers.py: QmfLxGW9MZkA4rkRFrZRLDSyWktrHa42mFRLZ1YPRcWMEi strategy.py: QmXeDdFngwnNb3F3TYusLX4j2DCCDdbkXTRcHHCa6xV6Wr fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/http:1.1.7 - fetchai/oef_search:1.1.7 skills: [] behaviours: faber: args: search_interval: 5 class_name: FaberBehaviour handlers: http: args: {} class_name: HttpHandler oef_search: args: {} class_name: OefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues http_dialogues: args: {} class_name: HttpDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: admin_host: 127.0.0.1 admin_port: 8021 ledger_url: http://127.0.0.1:9000 location: latitude: 51.5194 longitude: 0.127 search_query: constraint_type: == search_key: intro_service search_value: intro_alice search_radius: 5.0 seed: null class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/aries_faber/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import random from typing import Any, List from aea.common import Address from aea.exceptions import enforce from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.skills.base import Model # Default Config DEFAULT_ADMIN_HOST = "127.0.0.1" DEFAULT_ADMIN_PORT = 8021 DEFAULT_LEDGER_URL = "http://127.0.0.1:9000" # Commands ADMIN_COMMAND_CREATE_INVITATION = "/connections/create-invitation" ADMIN_COMMAND_STATUS = "/status" ADMIN_COMMAND_SCEHMAS = "/schemas" ADMIN_COMMAND_CREDDEF = "/credential-definitions" ADMIN_COMMAND_REGISTGER_PUBLIC_DID = "/wallet/did/public" LEDGER_COMMAND_REGISTER_DID = "/register" # Convenience FABER_ACA_IDENTITY = "Faber_ACA" # Search DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "intro_service", "search_value": "intro_alice", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ # config self._admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) self._admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) self._ledger_url = kwargs.pop("ledger_url", DEFAULT_LEDGER_URL) self._seed = ( kwargs.pop( "seed", None, ) or ( "my_seed_000000000000000000000000" + str(random.randint(100_000, 999_999)) # nosec )[-32:] ) # derived config self._admin_url = f"http://{self.admin_host}:{self.admin_port}" self._aea_addresses: List[Address] = [] # Search self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) super().__init__(**kwargs) self._is_searching = False @property def admin_host(self) -> str: """Get the admin host.""" return self._admin_host @property def admin_port(self) -> str: """Get the admin port.""" return self._admin_port @property def ledger_url(self) -> str: """Get the ledger URL.""" return self._ledger_url @property def seed(self) -> str: """Get the wallet seed.""" return self._seed @property def admin_url(self) -> str: """Get the admin URL.""" return self._admin_url @property def aea_addresses(self) -> List[Address]: """Get Alice's address.""" return self._aea_addresses @aea_addresses.setter def aea_addresses(self, addresses: List[Address]) -> None: self._aea_addresses = addresses @property def is_searching(self) -> bool: """Check if the agent is searching.""" return self._is_searching @is_searching.setter def is_searching(self, is_searching: bool) -> None: """Check if the agent is searching.""" enforce(isinstance(is_searching, bool), "Can only set bool on is_searching!") self._is_searching = is_searching def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType("distance", (self._agent_location, self._radius)) ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query ================================================ FILE: packages/fetchai/skills/carpark_client/README.md ================================================ # Car Park Client ## Description This skill purchases information on available car parking spaces in a vicinity. This skill finds an agent on the sOEF which sells car park availability data in a vicinity, requests this data, negotiates the price, pays the proposed amount if agreement is reach, and receives the data bought. ## Behaviours - `search`: searches for car park data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - Car Park Demo ================================================ FILE: packages/fetchai/skills/carpark_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the carpark client skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/carpark_client:0.27.6") ================================================ FILE: packages/fetchai/skills/carpark_client/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours of the agent.""" from packages.fetchai.skills.generic_buyer.behaviours import ( GenericSearchBehaviour, GenericTransactionBehaviour, ) SearchBehaviour = GenericSearchBehaviour TransactionBehaviour = GenericTransactionBehaviour ================================================ FILE: packages/fetchai/skills/carpark_client/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - SigningDialogues: The dialogues class keeps track of all dialogues of type signing. """ from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( SigningDialogues as GenericSigningDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues SigningDialogues = GenericSigningDialogues ================================================ FILE: packages/fetchai/skills/carpark_client/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of the agent.""" from packages.fetchai.skills.generic_buyer.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, GenericSigningHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler SigningHandler = GenericSigningHandler ================================================ FILE: packages/fetchai/skills/carpark_client/skill.yaml ================================================ name: carpark_client author: fetchai version: 0.27.6 type: skill description: The carpark client skill implements the functionality to run a client for carpark data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmdrvGfoAncV3e76Aiq5irJSKYXXwmhJTv7w8hPoadcFgt __init__.py: QmVsQFQFm8gBa2cKGmERfJYkq1aufHahgv2p1dF5eRwXhb behaviours.py: QmSr6fB3N7dhVo1cLY1TGd2q8usjGwNmTCNjBBbtgtVf9j dialogues.py: QmXgXcs25v9ob9a9XwT49wvK788vbUdZyYxUgG3ndHjrix handlers.py: QmP3Q6x3NMcWgRi6H5GtDtvnLWSoB1HeG8vTd4zcRZUgNj strategy.py: QmdHPLehqRr1dxuCbp4ENYmVMb8Ykvzg2Uzfos5kFJSr3D fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/generic_buyer:0.27.6 behaviours: search: args: search_interval: 5 class_name: SearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_quantity: 100 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_query: constraint_type: == search_key: seller_service search_value: car_park_service search_radius: 5.0 service_id: car_park_service class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/carpark_client/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy Strategy = GenericStrategy ================================================ FILE: packages/fetchai/skills/carpark_detection/README.md ================================================ # Car Park Detection ## Description This skill sells information on the number of car parking spaces available in a given vicinity. This skill is part of the Fetch.ai car park demo. It registers the "car park availability selling service" on the sOEF. It can be requested (for example by an agent with the `carpark_client` skill) to provide its data. It then negotiates the price and delivers the data after it receives payment. ## Behaviours - `service_registration`: registers car park info selling service on the sOEF ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages if service registration on the sOEF is unsuccessful ## Links - Car Park Demo ================================================ FILE: packages/fetchai/skills/carpark_detection/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the car park detection skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/carpark_detection:0.27.6") ================================================ FILE: packages/fetchai/skills/carpark_detection/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a behaviour.""" from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) ServiceRegistrationBehaviour = GenericServiceRegistrationBehaviour ================================================ FILE: packages/fetchai/skills/carpark_detection/database.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Communicate between the database and the python objects.""" import logging import os import shutil import sqlite3 import time from typing import Dict, List, Optional, Tuple, Union import skimage # type: ignore _default_logger = logging.getLogger( "aea.packages.fetchai.skills.carpark_detection.detection_database" ) class DetectionDatabase: # pylint: disable=too-many-public-methods """Communicate between the database and the python objects.""" def __init__( self, temp_dir: str, create_if_not_present: bool = True, logger: Optional[logging.Logger] = None, ) -> None: """Initialise the Detection Database Communication class.""" self.this_dir = os.path.dirname(__file__) self.temp_dir = temp_dir self.mask_image_path = self.temp_dir + "/mask.tiff" self.mask_ref_image_path = self.temp_dir + "/mask_ref.tiff" self.raw_image_dir = self.temp_dir + "/db_raw_images/" self.processed_image_dir = self.temp_dir + "/db_processed_images/" # Note that this path should be under source control self.default_mask_ref_path = self.this_dir + "/default_mask_ref.png" self.num_digits_time = ( 12 # need to match this up with the generate functions below ) self.image_file_ext = ".png" self.database_path = self.temp_dir + "/" + "detection_results.db" if create_if_not_present: self.initialise_backend() self.logger = logger if logger is not None else _default_logger def is_db_exits(self) -> bool: """Return true if database exists and is set up.""" if not os.path.isfile(self.database_path): return False ret = self.get_system_status("db", False) == "Exists" return ret def reset_database(self) -> None: # pragma: nocover """Reset the database and remove all data.""" # If we need to reset the database, then remove the table and any stored images self.logger.info("Database being reset.") # Remove the actual database file if os.path.isfile(self.database_path): os.remove(self.database_path) # Clear stored images shutil.rmtree(self.raw_image_dir) shutil.rmtree(self.processed_image_dir) # Recreate them self.logger.info("Initialising backend ...") self.initialise_backend() self.logger.info("Finished initialising backend!") def reset_mask(self) -> None: # pragma: nocover """Just reset the detection mask.""" # If we need to reset the database, then remove the table and any stored images self.logger.info("Mask being reset.") # Remove the actual database file if os.path.isfile(self.mask_image_path): os.remove(self.mask_image_path) if os.path.isfile(self.mask_ref_image_path): os.remove(self.mask_ref_image_path) self.ensure_dirs_exist() def initialise_backend(self) -> None: """Set up database and initialise the tables.""" self.ensure_dirs_exist() self.execute_single_sql( "CREATE TABLE IF NOT EXISTS images (epoch INTEGER, raw_image_path TEXT, " "processed_image_path TEXT, total_count INTEGER, " "moving_count INTEGER, free_spaces INTEGER, lat TEXT, lon TEXT)" ) self.execute_single_sql( "CREATE TABLE IF NOT EXISTS fet_table (id INTEGER PRIMARY KEY, amount BIGINT, last_updated TEXT)" ) self.execute_single_sql( "CREATE TABLE IF NOT EXISTS status_table (system_name TEXT PRIMARY KEY, status TEXT)" ) self.execute_single_sql( "CREATE TABLE IF NOT EXISTS name_lookup2 (oef_key TEXT PRIMARY KEY, friendly_name TEXT, epoch INT, is_self BIT)" ) self.execute_single_sql( "CREATE TABLE IF NOT EXISTS transaction_history (tx TEXT PRIMARY KEY, epoch INT, oef_key_payer TEXT, oef_key_payee TEXT, amount BIGINT, status TEXT)" ) self.execute_single_sql( "CREATE TABLE IF NOT EXISTS dialogue_statuses (dialogue_id TEXT, epoch DECIMAL, other_agent_key TEXT, received_msg TEXT, sent_msg TEXT)" ) if not self.is_db_exits(): self.set_system_status("lat", "UNKNOWN") self.set_system_status("lon", "UNKNOWN") self.set_system_status("db", "Exists") def set_fet(self, amount: int, t: str) -> None: # pragma: nocover """Record how much FET we have and when we last read it from the ledger.""" command = ( "INSERT OR REPLACE INTO fet_table(id, amount, last_updated) values(0, ?, ?)" ) variables = (str(amount), str(t)) self.execute_single_sql(command, variables) def get_fet(self) -> int: # pragma: nocover """Read how much FET we have.""" result = self.execute_single_sql("SELECT amount FROM fet_table WHERE id=0") if len(result) != 0: return result[0][0] return -99 def save_max_capacity(self, max_capacity: int) -> None: # pragma: nocover """Record the maximum number of spaces we can report on.""" self.set_system_status("max_capacity", str(max_capacity)) def get_max_capacity(self) -> Optional[int]: # pragma: nocover """Read the maximum number of spaces we can report on.""" max_capacity = self.get_system_status("max_capacity") if max_capacity == "UNKNOWN": return None return int(max_capacity) def save_lat_lon(self, lat: float, lon: float) -> None: # pragma: nocover """Record the longitude and latitude of our device.""" self.set_system_status("lat", str(lat)) self.set_system_status("lon", str(lon)) def get_lat_lon(self) -> Tuple[Optional[float], Optional[float]]: """Read the longitude and latitude of our device.""" lat = self.get_system_status("lat") lon = self.get_system_status("lon") if lat == "UNKNOWN" or lon == "UNKNOWN": return None, None return float(lat), float(lon) def set_system_status(self, system_name: str, status: str) -> None: """Record the status of one of the systems.""" command = ( "INSERT OR REPLACE INTO status_table(system_name, status) values(?, ?)" ) variables = (str(system_name), str(status)) self.execute_single_sql(command, variables) def get_system_status(self, system_name: str, print_exceptions: bool = True) -> str: """Read the status of one of the systems.""" command = "SELECT status FROM status_table WHERE system_name=?" variables = (str(system_name),) result = self.execute_single_sql(command, variables, print_exceptions) if len(result) != 0: return result[0][0] return "UNKNOWN" def set_dialogue_status( self, dialogue_id: int, other_agent_key: str, received_msg: str, sent_msg: str ) -> None: # pragma: nocover """Record the status of a dialog we are having.""" t = time.time() command = "INSERT INTO dialogue_statuses(dialogue_id, epoch, other_agent_key, received_msg, sent_msg) VALUES(?,?,?,?,?)" variables = ( str(dialogue_id), str(t), str(other_agent_key), str(received_msg), str(sent_msg), ) self.execute_single_sql(command, variables) def get_dialogue_statuses(self) -> List[Dict]: # pragma: nocover """Read the statuses of all the dialog we are having.""" data = self.execute_single_sql( "SELECT * FROM dialogue_statuses ORDER BY epoch DESC LIMIT 100" ) results = [] for datum in data: result = {} result["dialog_id"] = datum[0] result["epoch"] = datum[1] result["other_agent_key"] = datum[2] result["received_msg"] = datum[3] result["sent_msg"] = datum[4] results.append(result) return results def calc_uncleared_fet(self) -> int: # pragma: nocover """Calc our uncleared fet.""" cleared_fet_result = self.execute_single_sql( "SELECT amount FROM fet_table WHERE id=0" ) if len(cleared_fet_result) != 0: uncleared_fet_result = self.execute_single_sql( "SELECT SUM(amount) FROM transaction_history WHERE status = 'in_progress'" ) if len(uncleared_fet_result) == 0 or uncleared_fet_result[0][0] is None: return cleared_fet_result[0][0] return cleared_fet_result[0][0] + uncleared_fet_result[0][0] return -99 def add_friendly_name( self, oef_key: str, friendly_name: str, is_self: bool = False ) -> None: # pragma: nocover """Record the friendly name of one the agents we are dealing with (including ourselves).""" t = int(time.time()) command = "INSERT OR REPLACE INTO name_lookup2(oef_key, friendly_name, epoch, is_self) VALUES(?, ?, ?, ?)" variables = (str(oef_key), str(friendly_name), t, 1 if is_self else 0) self.execute_single_sql(command, variables) def add_in_progress_transaction( self, tx: str, oef_key_payer: str, oef_key_payee: str, amount: int ) -> None: # pragma: nocover """Record that a transaction in underway.""" t = int(time.time()) command = "INSERT OR REPLACE INTO transaction_history(tx, epoch, oef_key_payer, oef_key_payee, amount, status) VALUES(?, ?, ?, ?, ?, 'in_progress')" variables = (str(tx), t, str(oef_key_payer), str(oef_key_payee), amount) self.execute_single_sql(command, variables) def get_in_progress_transactions(self) -> List[Dict]: # pragma: nocover """Read all in-progress transactions.""" return self.get_transactions_with_status("in_progress") def get_complete_transactions(self) -> List[Dict]: # pragma: nocover """Read all complete transactions.""" return self.get_transactions_with_status("complete") def get_transactions_with_status( self, status: str ) -> List[Dict]: # pragma: nocover """Read all transactions with a given status.""" command = ( "SELECT * from transaction_history WHERE status = ? ORDER BY epoch DESC" ) variables = (str(status),) data = self.execute_single_sql(command, variables) results = [] for datum in data: result = {} result["tx_hash"] = datum[0] result["epoch"] = datum[1] result["oef_key_payer"] = datum[2] result["oef_key_payee"] = datum[3] result["amount"] = datum[4] result["status"] = datum[5] results.append(result) return results def get_n_transactions(self, count: int) -> List[Dict]: # pragma: nocover """Get the most resent N transactions.""" command = "SELECT * from transaction_history ORDER BY epoch DESC LIMIT ?" variables = (count,) data = self.execute_single_sql(command, variables) results = [] for datum in data: result = {} result["tx_hash"] = datum[0] result["epoch"] = datum[1] result["oef_key_payer"] = datum[2] result["oef_key_payee"] = datum[3] result["amount"] = datum[4] result["status"] = datum[5] results.append(result) return results def set_transaction_complete(self, tx: str) -> None: # pragma: nocover """Set a specific transaction as complete.""" command = "UPDATE transaction_history SET status ='complete' WHERE tx = ?" variables = (str(tx),) self.execute_single_sql(command, variables) def lookup_friendly_name(self, oef_key: str) -> Optional[str]: # pragma: nocover """Look up friendly name given the OEF key.""" command = "SELECT * FROM name_lookup2 WHERE oef_key = ? ORDER BY epoch DESC" variables = (str(oef_key),) results = self.execute_single_sql(command, variables) if len(results) == 0: return None return results[0][1] def lookup_self_names( self, ) -> Tuple[Optional[str], Optional[str]]: # pragma: nocover """Return out own name and key.""" results = self.execute_single_sql( "SELECT oef_key, friendly_name FROM name_lookup2 WHERE is_self = 1 ORDER BY epoch DESC" ) if len(results) == 0: return None, None return results[0][0], results[0][1] def add_entry_no_save( self, raw_path: str, processed_path: str, total_count: int, moving_count: int, free_spaces: int, lat: float, lon: float, ) -> None: # pragma: nocover """Add an entry into the detection database but do not save anything to disk.""" # need to extract the time! t = self.extract_time_from_raw_path(raw_path) command = "INSERT INTO images VALUES (?, ?, ?, ?, ?, ?, ?, ?)" variables = ( t, raw_path, processed_path, total_count, moving_count, free_spaces, lat, lon, ) self.execute_single_sql(command, variables) def add_entry( self, raw_image: str, processed_image: str, total_count: int, moving_count: int, free_spaces: int, lat: float, lon: float, ) -> None: # pragma: nocover """Add an entry into the detection database and record images to disk.""" t = int(time.time()) raw_path = self.generate_raw_image_path(t) processed_path = self.generate_processed_path(t) skimage.io.imsave(raw_path, raw_image) skimage.io.imsave(processed_path, processed_image) command = "INSERT INTO images VALUES (?, ?, ?, ?, ?, ?, ?, ?)" variables = ( t, raw_path, processed_path, total_count, moving_count, free_spaces, lat, lon, ) self.execute_single_sql(command, variables) def execute_single_sql( self, command: str, variables: Tuple[Union[str, int, float], ...] = (), print_exceptions: bool = True, ) -> List: """Query the database - all the other functions use this under the hood.""" conn = None ret = [] try: conn = sqlite3.connect(self.database_path, timeout=300) # 5 mins c = conn.cursor() c.execute(command, variables) ret = c.fetchall() conn.commit() except Exception as e: # pragma: nocover # pylint: disable=broad-except if print_exceptions: self.logger.warning("Exception in database: {}".format(e)) finally: if conn is not None: conn.close() return ret def get_latest_detection_data(self, max_num_rows: int) -> List[Dict]: """Return the most recent detection data.""" command = """SELECT * FROM images ORDER BY epoch DESC LIMIT ?""" variables = (max_num_rows,) results = self.execute_single_sql(command, variables) if results is None: return None ret_data = [] for r in results: this_data = {} this_data["epoch"] = r[0] this_data["raw_image_path"] = r[1] this_data["processed_image_path"] = r[2] this_data["total_count"] = r[3] this_data["moving_count"] = r[4] this_data["free_spaces"] = r[5] this_data["lat"] = r[6] this_data["lon"] = r[7] ret_data.append(this_data) return ret_data def prune_image_table(self, max_entries: int) -> None: # pragma: nocover """Remove image data if table longer than max_entries.""" self.prune_table("images", max_entries) def prune_transaction_table(self, max_entries: int) -> None: # pragma: nocover """Remove transaction data if table longer than max_entries.""" self.prune_table("transaction_history", max_entries) def prune_table(self, table_name: str, max_entries: int) -> None: # pragma: nocover """Remove any data if table longer than max_entries.""" command = "SELECT epoch FROM ? ORDER BY epoch DESC LIMIT 1 OFFSET ?" variables = ( table_name, max_entries - 1, ) results = self.execute_single_sql(command=command, variables=variables) if len(results) != 0: last_epoch = results[0][0] command = """DELETE FROM ? WHERE epoch None: """Test if we have our temp directories, and if we don't create them.""" if not os.path.isdir(self.temp_dir): os.mkdir(self.temp_dir) if not os.path.isdir(self.raw_image_dir): os.mkdir(self.raw_image_dir) if not os.path.isdir(self.processed_image_dir): os.mkdir(self.processed_image_dir) def generate_raw_image_path(self, t: int) -> str: # pragma: nocover """Return path where we store raw images.""" return ( self.raw_image_dir + "{0:012d}".format(t) + "_raw_image" + self.image_file_ext ) def generate_processed_path(self, t: int) -> str: # pragma: nocover """Return path where we store processed images.""" return ( self.processed_image_dir + "{0:012d}".format(t) + "_processed_image" + self.image_file_ext ) def generate_processed_from_raw_path(self, raw_name: str) -> str: # pragma: nocover """Given the raw path, return the processes path.""" return raw_name.replace("_raw_image.", "_processed_image.").replace( self.raw_image_dir, self.processed_image_dir ) def extract_time_from_raw_path(self, raw_name: str) -> int: # pragma: nocover """Given the raw path name, return the time the detection happened.""" start_index = len(self.raw_image_dir) extracted_num = raw_name[start_index : start_index + self.num_digits_time] return int(extracted_num) ================================================ FILE: packages/fetchai/skills/carpark_detection/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. """ from packages.fetchai.skills.generic_seller.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues ================================================ FILE: packages/fetchai/skills/carpark_detection/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers.""" from packages.fetchai.skills.generic_seller.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler ================================================ FILE: packages/fetchai/skills/carpark_detection/skill.yaml ================================================ name: carpark_detection author: fetchai version: 0.27.6 type: skill description: The carpark detection skill implements the detection and trading functionality for a carpark agent. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmZYxDqNu3RnTkhNCgp7VPBMpYxaqRT4Hb64ipgYvgJ5wn __init__.py: QmSiwGgkdvRNCiyF4EBEvoDpBFa5hBGLQ1F93Sd4byZ9bi behaviours.py: QmYgNwz5EA4yhEnMyiV3oe16g1MAKpFPJsTENfkVMySfr8 database.py: QmQ2Gh58YtC1eHAu1bavLPh6D9xeDaAGgye8vG2Z1SYjgA dialogues.py: QmZckK3x2oPgXmnP4XaEBJQoaPp8Gh4ojDHnxzeNsTf4tC handlers.py: QmecBakMGUcam9w9hzTv5Tdi3DrABqsoDYHgk9XojEv8Ay strategy.py: QmTHYX6BzXApbJnrYUMmtPPf7Ye388mQEhJ1gtURU9UV5c fingerprint_ignore_patterns: - temp_files_placeholder/* connections: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 skills: - fetchai/generic_seller:0.28.6 behaviours: service_registration: args: services_interval: 20 class_name: ServiceRegistrationBehaviour handlers: fipa: args: {} class_name: GenericFipaHandler ledger_api: args: {} class_name: GenericLedgerApiHandler oef_search: args: {} class_name: GenericOefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller data_for_sale: free_spaces: 0 has_data_source: false is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: car_park_service service_id: car_park_service unit_price: 10 class_name: Strategy dependencies: scikit-image: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/carpark_detection/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import os from typing import Any, Dict from aea.exceptions import enforce from packages.fetchai.skills.carpark_detection.database import DetectionDatabase from packages.fetchai.skills.generic_seller.strategy import GenericStrategy DEFAULT_DB_IS_REL_TO_CWD = False DEFAULT_DB_REL_DIR = "temp_files_placeholder" class Strategy(GenericStrategy): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ db_is_rel_to_cwd = kwargs.pop("db_is_rel_to_cwd", DEFAULT_DB_IS_REL_TO_CWD) db_rel_dir = kwargs.pop("db_rel_dir", DEFAULT_DB_REL_DIR) if db_is_rel_to_cwd: db_dir = os.path.join(os.getcwd(), db_rel_dir) else: db_dir = os.path.join(os.path.dirname(__file__), db_rel_dir) if not os.path.isdir(db_dir): raise ValueError("Database directory does not exist!") super().__init__(**kwargs) self.db = DetectionDatabase(db_dir, False, logger=self.context.logger) self._update_service_data() def collect_from_data_source(self) -> Dict[str, str]: """ Build the data payload. :return: the data """ enforce(self.db.is_db_exits(), "Db doesn't exist.") data = self.db.get_latest_detection_data(1) enforce(len(data) > 0, "Did not find any data.") free_spaces = data[0]["free_spaces"] return {"free_spaces": str(free_spaces)} def _update_service_data(self) -> None: """Update lat and long in service data if db present.""" if self.db.is_db_exits() and len(self.db.get_latest_detection_data(1)) > 0: lat, lon = self.db.get_lat_lon() if lat is not None and lon is not None: data = { "latitude": lat, "longitude": lon, } self._service_data = data ================================================ FILE: packages/fetchai/skills/confirmation_aw1/README.md ================================================ # Confirmation AW1 ## Description The `confirmation_aw1` skill is for handling registrations in Agent World 1. ## Behaviours - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `contract_api`: handles `contract_api` messages for communication with a staking contract - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `registration`: handles `register` messages for registration requests - `signing`: handles `signing` messages for transaction signing by the decision maker ## Models - `strategy`: contains the confirmation configuration ================================================ FILE: packages/fetchai/skills/confirmation_aw1/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/confirmation_aw1:0.15.6") ================================================ FILE: packages/fetchai/skills/confirmation_aw1/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour for the confirmation aw1 skill.""" from typing import Any, List, Optional, Set, cast from aea.protocols.dialogue.base import DialogueLabel from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.skills.confirmation_aw1.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, RegisterDialogue, ) DEFAULT_MAX_PROCESSING = 120 DEFAULT_TX_INTERVAL = 2.0 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class TransactionBehaviour(TickerBehaviour): """A behaviour to sequentially submit transactions to the blockchain.""" def __init__(self, **kwargs: Any): """Initialize the transaction behaviour.""" tx_interval = cast( float, kwargs.pop("transaction_interval", DEFAULT_TX_INTERVAL) ) self.max_processing = cast( float, kwargs.pop("max_processing", DEFAULT_MAX_PROCESSING) ) self.processing_time = 0.0 self.waiting: List[RegisterDialogue] = [] self.processing: Optional[LedgerApiDialogue] = None self.timedout: Set[DialogueLabel] = set() super().__init__(tick_interval=tx_interval, **kwargs) def setup(self) -> None: """Setup behaviour.""" def act(self) -> None: """Implement the act.""" if self.processing is not None: if self.processing_time <= self.max_processing: # already processing self.processing_time += self.tick_interval return self._timeout_processing() if len(self.waiting) == 0: # nothing to process return self._start_processing() def _start_processing(self) -> None: """Process the next transaction.""" register_dialogue = self.waiting.pop(0) self.context.logger.info( f"Processing transaction, {len(self.waiting)} transactions remaining" ) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, terms=register_dialogue.terms, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_register_dialogue = register_dialogue self.processing_time = 0.0 self.processing = ledger_api_dialogue self.context.logger.info( f"requesting transfer transaction from ledger api for message={ledger_api_msg}..." ) self.context.outbox.put_message(message=ledger_api_msg) def teardown(self) -> None: """Teardown behaviour.""" def _timeout_processing(self) -> None: """Timeout processing.""" if self.processing is None: return self.timedout.add(self.processing.dialogue_label) self.waiting.append(self.processing.associated_register_dialogue) self.processing_time = 0.0 self.processing = None def finish_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Finish processing. :param ledger_api_dialogue: the ledger api dialogue """ if self.processing == ledger_api_dialogue: self.processing_time = 0.0 self.processing = None return if ledger_api_dialogue.dialogue_label not in self.timedout: raise ValueError( f"Non-matching dialogues in transaction behaviour: {self.processing} and {ledger_api_dialogue}" ) self.timedout.remove(ledger_api_dialogue.dialogue_label) self.context.logger.debug( f"Timeout dialogue in transaction processing: {ledger_api_dialogue}" ) # don't reset, as another might be processing def failed_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Failed processing. Currently, we retry processing indefinitely. :param ledger_api_dialogue: the ledger api dialogue """ self.finish_processing(ledger_api_dialogue) self.waiting.append(ledger_api_dialogue.associated_register_dialogue) ================================================ FILE: packages/fetchai/skills/confirmation_aw1/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - OefSearchDialogue: The dialogue class maintains state of a dialogue and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues. """ from typing import Any, Optional, Type from aea.exceptions import AEAEnforceError, enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Address, Model from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue as BaseContractApiDialogue, ) from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.register.dialogues import ( RegisterDialogue as BaseRegisterDialogue, ) from packages.fetchai.protocols.register.dialogues import ( RegisterDialogues as BaseRegisterDialogues, ) from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class RegisterDialogue(BaseRegisterDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_terms",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[RegisterMessage] = RegisterMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseRegisterDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get terms.""" if self._terms is None: raise AEAEnforceError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class RegisterDialogues(Model, BaseRegisterDialogues): """This class keeps track of all register dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseRegisterDialogue.Role.AGENT BaseRegisterDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=RegisterDialogue, ) class ContractApiDialogue(BaseContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_terms", "_associated_register_dialogue") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] self._associated_register_dialogue = None # type: Optional[RegisterDialogue] @property def terms(self) -> Terms: """Get the terms.""" if self._terms is None: raise ValueError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set the terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms @property def associated_register_dialogue(self) -> RegisterDialogue: """Get the associated register dialogue.""" if self._associated_register_dialogue is None: raise ValueError("Associated register dialogue not set!") return self._associated_register_dialogue @associated_register_dialogue.setter def associated_register_dialogue( self, associated_register_dialogue: RegisterDialogue ) -> None: """Set the associated register dialogue.""" enforce( self._associated_register_dialogue is None, "Associated register dialogue already set!", ) self._associated_register_dialogue = associated_register_dialogue class ContractApiDialogues(Model, BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=ContractApiDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_register_dialogue", "_terms") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_register_dialogue = None # type: Optional[RegisterDialogue] self._terms = None # type: Optional[Terms] @property def associated_register_dialogue(self) -> RegisterDialogue: """Get associated_register_dialogue.""" if self._associated_register_dialogue is None: raise AEAEnforceError("RegisterDialogue not set!") return self._associated_register_dialogue @associated_register_dialogue.setter def associated_register_dialogue(self, register_dialogue: RegisterDialogue) -> None: """Set associated_fipa_dialogue""" enforce( self._associated_register_dialogue is None, "RegisterDialogue already set!" ) self._associated_register_dialogue = register_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_ledger_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_ledger_api_dialogue = None # type: Optional[LedgerApiDialogue] @property def associated_ledger_api_dialogue(self) -> LedgerApiDialogue: """Get associated_ledger_dialogue.""" if self._associated_ledger_api_dialogue is None: raise AEAEnforceError("LedgerApiDialogue not set!") return self._associated_ledger_api_dialogue @associated_ledger_api_dialogue.setter def associated_ledger_api_dialogue( self, ledger_api_dialogue: LedgerApiDialogue ) -> None: """Set associated_ledger_dialogue""" enforce( self._associated_ledger_api_dialogue is None, "LedgerApiDialogue already set!", ) self._associated_ledger_api_dialogue = ledger_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all signing dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: packages/fetchai/skills/confirmation_aw1/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a handler.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.confirmation_aw1.behaviours import TransactionBehaviour from packages.fetchai.skills.confirmation_aw1.dialogues import ( ContractApiDialogue, ContractApiDialogues, DefaultDialogues, LedgerApiDialogue, LedgerApiDialogues, RegisterDialogue, RegisterDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.confirmation_aw1.strategy import Strategy LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class AW1RegistrationHandler(Handler): """This class handles register messages.""" SUPPORTED_PROTOCOL = RegisterMessage.protocol_id def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ register_msg = cast(RegisterMessage, message) # recover dialogue register_dialogues = cast(RegisterDialogues, self.context.register_dialogues) register_dialogue = cast( Optional[RegisterDialogue], register_dialogues.update(register_msg) ) if register_dialogue is None: self._handle_unidentified_dialogue(register_msg) return # handle message if register_msg.performative is RegisterMessage.Performative.REGISTER: self._handle_register(register_msg, register_dialogue) else: self._handle_invalid(register_msg, register_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, register_msg: RegisterMessage) -> None: """ Handle an unidentified dialogue. :param register_msg: the register message """ self.context.logger.info( f"received invalid register_msg message={register_msg}, unidentified dialogue." ) def _handle_register( self, register_msg: RegisterMessage, register_dialogue: RegisterDialogue ) -> None: """ Handle an register message. :param register_msg: the register message :param register_dialogue: the dialogue """ self.context.logger.info( f"received register_msg register message={register_msg} in dialogue={register_dialogue}." ) strategy = cast(Strategy, self.context.strategy) is_valid, error_code, error_msg = strategy.valid_registration( register_msg.info, register_msg.sender ) if is_valid: strategy.lock_registration_temporarily( register_msg.sender, register_msg.info ) self.context.logger.info( f"valid registration={register_msg.info}. Verifying if tokens staked." ) terms = strategy.get_terms(register_msg.sender) if not strategy.developer_handle_only: contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) kwargs = strategy.get_kwargs(register_msg.info) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_STATE, ledger_id=strategy.contract_ledger_id, contract_id=strategy.contract_id, contract_address=strategy.contract_address, callable=strategy.contract_callable, kwargs=ContractApiMessage.Kwargs(kwargs), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = terms contract_api_dialogue.associated_register_dialogue = register_dialogue self.context.outbox.put_message(contract_api_msg) else: strategy.finalize_registration(register_msg.sender) register_dialogue.terms = terms tx_behaviour = cast( TransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.waiting.append(register_dialogue) else: self.context.logger.info( f"invalid registration={register_msg.info}. Rejecting." ) reply = register_dialogue.reply( performative=RegisterMessage.Performative.ERROR, error_code=error_code, error_msg=error_msg, info={}, ) self.context.outbox.put_message(reply) def _handle_invalid( self, register_msg: RegisterMessage, register_dialogue: RegisterDialogue ) -> None: """ Handle an register message. :param register_msg: the register message :param register_dialogue: the dialogue """ self.context.logger.warning( f"cannot handle register_msg message of performative={register_msg.performative} in dialogue={register_dialogue}." ) class ContractApiHandler(Handler): """Implement the contract api handler.""" SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ contract_api_msg = cast(ContractApiMessage, message) # recover dialogue contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_dialogue = cast( Optional[ContractApiDialogue], contract_api_dialogues.update(contract_api_msg), ) if contract_api_dialogue is None: self._handle_unidentified_dialogue(contract_api_msg) return # handle message if contract_api_msg.performative is ContractApiMessage.Performative.STATE: self._handle_state(contract_api_msg, contract_api_dialogue) elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: self._handle_error(contract_api_msg, contract_api_dialogue) else: self._handle_invalid(contract_api_msg, contract_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue( self, contract_api_msg: ContractApiMessage ) -> None: """ Handle an unidentified dialogue. :param contract_api_msg: the contract api message """ self.context.logger.info( f"received invalid contract_api message={contract_api_msg}, unidentified dialogue." ) def _handle_state( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_message performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info(f"received state message={contract_api_msg}") register_dialogue = contract_api_dialogue.associated_register_dialogue register_msg = cast( Optional[RegisterMessage], register_dialogue.last_incoming_message ) if register_msg is None: raise ValueError("Could not retrieve fipa message") strategy = cast(Strategy, self.context.strategy) if strategy.has_staked(contract_api_msg.state.body): self.context.logger.info("Has staked! Requesting funds release.") strategy.finalize_registration(register_msg.sender) register_dialogue.terms = contract_api_dialogue.terms tx_behaviour = cast( TransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.waiting.append(register_dialogue) else: strategy.unlock_registration(register_msg.sender) self.context.logger.info( f"invalid registration={register_msg.info}. Rejecting." ) reply = register_dialogue.reply( performative=RegisterMessage.Performative.ERROR, error_code=1, error_msg="No funds staked!", info={}, ) self.context.outbox.put_message(reply) def _handle_error( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of error performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( f"received ledger_api error message={contract_api_msg} in dialogue={contract_api_dialogue}." ) register_dialogue = contract_api_dialogue.associated_register_dialogue register_msg = cast( Optional[RegisterMessage], register_dialogue.last_incoming_message ) if register_msg is None: raise ValueError("Could not retrieve fipa message") strategy = cast(Strategy, self.context.strategy) strategy.unlock_registration(register_msg.sender) def _handle_invalid( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of invalid performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( f"cannot handle contract_api message of performative={contract_api_msg.performative} in dialogue={contract_api_dialogue}." ) class LedgerApiHandler(Handler): """Implement the ledger handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" strategy = cast(Strategy, self.context.strategy) for registered_aea in strategy.all_registered_aeas: self._send_confirmation_details_to_awx_aeas(registered_aea) def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.RAW_TRANSACTION: self._handle_raw_transaction(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the ledger api message """ self.context.logger.info( f"received invalid ledger_api message={ledger_api_msg}, unidentified dialogue." ) def _handle_raw_transaction( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of raw_transaction performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info(f"received raw transaction={ledger_api_msg}") signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=ledger_api_msg.raw_transaction, terms=ledger_api_dialogue.associated_register_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) ledger_api_msg_ = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.logger.info("checking transaction is settled.") self.context.outbox.put_message(message=ledger_api_msg_) def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ register_dialogue = ledger_api_dialogue.associated_register_dialogue is_settled = LedgerApis.is_transaction_settled( register_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.receipt, ) tx_behaviour = cast(TransactionBehaviour, self.context.behaviours.transaction) if is_settled: tx_behaviour.finish_processing(ledger_api_dialogue) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ledger_api_msg_ is None: raise ValueError( # pragma: nocover "Could not retrieve last ledger_api message" ) register_msg = cast( Optional[RegisterMessage], register_dialogue.last_incoming_message ) if register_msg is None: raise ValueError("Could not retrieve last register message") response = register_dialogue.reply( performative=RegisterMessage.Performative.SUCCESS, target_message=register_msg, info={"transaction_digest": ledger_api_msg_.transaction_digest.body}, ) self.context.outbox.put_message(message=response) self.context.logger.info( f"informing counterparty={response.to} of registration success." ) self._send_confirmation_details_to_awx_aeas(response.to) else: tx_behaviour.failed_processing(ledger_api_dialogue) self.context.logger.info( "transaction_receipt={} not settled or not valid, aborting".format( ledger_api_msg.transaction_receipt ) ) def _send_confirmation_details_to_awx_aeas(self, confirmed_aea: str) -> None: """ Send a confirmation of registration to aw2 AEAs. :param confirmed_aea: the confirmed aea's address """ strategy = cast(Strategy, self.context.strategy) if strategy.awx_aeas != []: developer_handle = strategy.get_developer_handle(confirmed_aea) self.context.logger.info( f"informing awx_aeas={strategy.awx_aeas} of registration success of confirmed aea={confirmed_aea} of developer={developer_handle}." ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) for awx_aea in strategy.awx_aeas: msg, _ = default_dialogues.create( counterparty=awx_aea, performative=DefaultMessage.Performative.BYTES, content=f"{confirmed_aea}_{developer_handle}".encode("utf-8"), ) self.context.outbox.put_message(message=msg) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( f"received ledger_api error message={ledger_api_msg} in dialogue={ledger_api_dialogue}." ) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ( ledger_api_msg_ is not None and ledger_api_msg_.performative != LedgerApiMessage.Performative.GET_BALANCE ): tx_behaviour = cast( TransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( f"cannot handle ledger_api message of performative={ledger_api_msg.performative} in dialogue={ledger_api_dialogue}." ) class SigningHandler(Handler): """Implement the signing handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( f"received invalid signing message={signing_msg}, unidentified dialogue." ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue last_ledger_api_msg = ledger_api_dialogue.last_incoming_message if last_ledger_api_msg is None: raise ValueError("Could not retrieve last message in ledger api dialogue") ledger_api_msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, target_message=last_ledger_api_msg, signed_transaction=signing_msg.signed_transaction, ) self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( f"transaction signing was not successful. Error_code={signing_msg.error_code} in dialogue={signing_dialogue}" ) signing_msg_ = cast( Optional[SigningMessage], signing_dialogue.last_outgoing_message ) if ( signing_msg_ is not None and signing_msg_.performative == SigningMessage.Performative.SIGN_TRANSACTION ): tx_behaviour = cast( TransactionBehaviour, self.context.behaviours.transaction ) ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( f"cannot handle signing message of performative={signing_msg.performative} in dialogue={signing_dialogue}." ) ================================================ FILE: packages/fetchai/skills/confirmation_aw1/registration_db.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a the registration db model.""" import logging import os import sqlite3 from typing import Any, List, Tuple from aea.skills.base import Model _default_logger = logging.getLogger( "aea.packages.fetchai.skills.carpark_detection.detection_database" ) class RegistrationDB(Model): """Communicate between the database and the python objects.""" def __init__(self, **kwargs: Any) -> None: """Initialise the class.""" custom_path = kwargs.pop("custom_path", None) super().__init__(**kwargs) this_dir = os.getcwd() self.db_path = ( os.path.join(this_dir, "registration.db") if custom_path is None else custom_path ) if not os.path.exists(os.path.dirname(os.path.abspath(self.db_path))): raise ValueError(f"Path={self.db_path} not valid!") # pragma: nocover self._initialise_backend() def _initialise_backend(self) -> None: """Set up database and initialise the tables.""" if os.path.isfile(self.db_path): return self._execute_single_sql( "CREATE TABLE IF NOT EXISTS registered_table (address TEXT, ethereum_address TEXT, " "ethereum_signature TEXT, fetchai_signature TEXT, " "developer_handle TEXT, tweet TEXT)" ) def set_registered( self, address: str, ethereum_address: str, ethereum_signature: str, fetchai_signature: str, developer_handle: str, tweet: str, ) -> None: """Record a registration.""" command = "INSERT OR REPLACE INTO registered_table(address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet) values(?, ?, ?, ?, ?, ?)" variables = ( address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet, ) self._execute_single_sql(command, variables) def set_registered_developer_only( self, address: str, developer_handle: str, ) -> None: """Record a registration.""" command = "INSERT OR REPLACE INTO registered_table(address, developer_handle) values(?, ?)" variables = ( address, developer_handle, ) self._execute_single_sql(command, variables) def is_registered(self, address: str) -> bool: """Check if an address is registered.""" command = "SELECT * FROM registered_table WHERE address=?" variables = (address,) result = self._execute_single_sql(command, variables) return len(result) != 0 def get_developer_handle(self, address: str) -> str: """Get developer handle relating to an address.""" command = "SELECT developer_handle FROM registered_table WHERE address=?" variables = (address,) result = self._execute_single_sql(command, variables) if len(result[0]) != 1: raise ValueError( f"More than one developer_handle found for address={address}." ) return result[0][0] def get_ethereum_address( self, address: str, developer_handle: str ) -> str: # pragma: no cover """Get ethereum address relating to an address (hacky for backwards compatibility).""" command = "SELECT ethereum_address FROM registered_table WHERE address=?" variables = (address,) result = self._execute_single_sql(command, variables) if len(result) != 0 and len(result[0]) != 1: raise ValueError( f"More than one ethereum_address found for address={address}." ) if len(result) != 0 and (result[0][0] != "" or developer_handle == ""): return result[0][0] command = ( "SELECT ethereum_address FROM registered_table WHERE developer_handle=?" ) variables = (developer_handle,) result = self._execute_single_sql(command, variables) if len(result) == 0: raise ValueError( f"No ethereum_address found for address={address} and developer_handle={developer_handle}." ) if len(result[0]) != 1: raise ValueError( f"More than one ethereum_address found for developer_handle={developer_handle}." ) return result[0][0] def get_all_registered(self) -> List[str]: """Get all registered AW-1 AEAs.""" command = "SELECT address FROM registered_table" variables = () results = self._execute_single_sql(command, variables) registered = [result[0] for result in results] return registered def _execute_single_sql( self, command: str, variables: Tuple[str, ...] = (), print_exceptions: bool = True, ) -> List[Tuple[str, ...]]: """Query the database - all the other functions use this under the hood.""" conn = None ret = [] try: conn = sqlite3.connect(self.db_path, timeout=300) # 5 mins c = conn.cursor() c.execute(command, variables) ret = c.fetchall() conn.commit() except Exception as e: # pragma: nocover # pylint: disable=broad-except if print_exceptions: self.context.logger.warning(f"Exception in database: {e}") finally: if conn is not None: conn.close() return ret ================================================ FILE: packages/fetchai/skills/confirmation_aw1/skill.yaml ================================================ name: confirmation_aw1 author: fetchai version: 0.15.6 type: skill description: The confirmation_aw1 skill is a skill to confirm registration for Agent World 1. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmaGTwfKyYVAJEfqHjGVViTYer2f3JJj22ftQH19NuA3eQ __init__.py: QmcNQVFKDuc6tFhTJzWvQnZkLXiiDCy1eUoAH9FYoGnrNw behaviours.py: QmZVaGLyZZZJu5HBR1rNukpfnvpvEFxn8tA2JKL8JgFWUB dialogues.py: QmQnYQyRtmFYhHxCirxg5vchWSogwnKVEgozKBwL19svGe handlers.py: QmNsrvzaLfxHwd51ssWZjTGoVMvzemWamqLXN2oLW4gf95 registration_db.py: QmWWYUrickqQsp1CCxGwJdW67qBb9brbAEHAJGCFUWQ7Fc strategy.py: QmUfD9UH7UrPLG25RPB7qr1fS9wvCoc8eb3BALttY12REW fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: - fetchai/staking_erc20:0.10.3 protocols: - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/register:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: contract_api: args: {} class_name: ContractApiHandler ledger_api: args: {} class_name: LedgerApiHandler registration: args: {} class_name: AW1RegistrationHandler signing: args: {} class_name: SigningHandler models: contract_api_dialogues: args: {} class_name: ContractApiDialogues default_dialogues: args: {} class_name: DefaultDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues register_dialogues: args: {} class_name: RegisterDialogues registration_db: args: custom_path: path_to_db class_name: RegistrationDB signing_dialogues: args: {} class_name: SigningDialogues strategy: args: awx_aeas: [] developer_handle_only: false fetchai_staking_contract_address: '0x351bac612b50e87b46e4b10a282f632d41397de2' override_staking_check: false token_denomination: atestfet token_dispense_amount: 100000 class_name: Strategy dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/confirmation_aw1/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the strategy model.""" from typing import Any, Dict, List, Optional, Tuple, cast from aea.common import JSONLike from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import Terms from aea.skills.base import Model from packages.fetchai.contracts.staking_erc20.contract import PUBLIC_ID from packages.fetchai.skills.confirmation_aw1.registration_db import RegistrationDB REQUIRED_KEYS = [ "ethereum_address", "fetchai_address", "signature_of_ethereum_address", "signature_of_fetchai_address", "developer_handle", ] DEVELOPER_ONLY_REQUIRED_KEYS = [ "fetchai_address", "developer_handle", ] DEFAULT_TOKEN_DISPENSE_AMOUNT = 100000 DEFAULT_TOKEN_DENOMINATION = "atestfet" # nosec DEFAULT_CONTRACT_ADDRESS = "0x351bac612b50e87b46e4b10a282f632d41397de2" DEFAULT_OVERRIDE = False class Strategy(Model): """This class is the strategy model.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ self._token_denomination = kwargs.pop( "token_denomination", DEFAULT_TOKEN_DENOMINATION ) self._token_dispense_amount = kwargs.pop( "token_dispense_amount", DEFAULT_TOKEN_DISPENSE_AMOUNT ) self._fetchai_staking_contract_address = kwargs.pop( "fetchai_staking_contract_address", DEFAULT_CONTRACT_ADDRESS ) self._override_staking_check = kwargs.pop( "override_staking_check", DEFAULT_OVERRIDE ) self.developer_handle_only = kwargs.pop("developer_handle_only", False) self._awx_aeas: List[str] = kwargs.pop("awx_aeas", []) super().__init__(**kwargs) self._is_ready_to_register = False self._is_registered = False self.is_registration_pending = False self.signature_of_ethereum_address: Optional[str] = None self._ledger_id = self.context.default_ledger_id self._max_tx_fee = 100 self._contract_ledger_id = "ethereum" self._contract_callable = "get_stake" self._contract_id = str(PUBLIC_ID) self._in_process_registrations: Dict[str, Dict[str, str]] = {} @property def contract_id(self) -> str: """Get the ledger on which the contract is deployed.""" return self._contract_id @property def contract_address(self) -> str: """Get contract address.""" return self._fetchai_staking_contract_address @property def contract_ledger_id(self) -> str: """Get the ledger on which the contract is deployed.""" return self._contract_ledger_id @property def contract_callable(self) -> str: """Get the ledger on which the contract is deployed.""" return self._contract_callable @property def awx_aeas(self) -> List[str]: """Get list of AWx AEAs.""" return self._awx_aeas @property def all_registered_aeas(self) -> List[str]: """Get all the registered AEAs.""" registration_db = cast(RegistrationDB, self.context.registration_db) all_registered = registration_db.get_all_registered() return all_registered def lock_registration_temporarily(self, address: str, info: Dict[str, str]) -> None: """Lock this address for registration.""" self._in_process_registrations.update({address: info}) def finalize_registration(self, address: str) -> None: """Lock this address for registration.""" info = self._in_process_registrations.pop(address) self.context.logger.info( f"finalizing registration for address={address}, info={info}" ) registration_db = cast(RegistrationDB, self.context.registration_db) if self.developer_handle_only: registration_db.set_registered_developer_only( address=address, developer_handle=info["developer_handle"], ) else: registration_db.set_registered( address=address, ethereum_address=info["ethereum_address"], ethereum_signature=info["signature_of_ethereum_address"], fetchai_signature=info["signature_of_fetchai_address"], developer_handle=info["developer_handle"], tweet=info.get("tweet", ""), ) def unlock_registration(self, address: str) -> None: """Unlock this address for registration.""" info = self._in_process_registrations.pop(address, {}) self.context.logger.info( f"registration info did not pass staking checks = {info}" ) def get_developer_handle(self, address: str) -> str: """Get developer handle.""" registration_db = cast(RegistrationDB, self.context.registration_db) handle = registration_db.get_developer_handle(address) return handle def _valid_registration_developer_only( self, registration_info: Dict[str, str], sender: str ) -> Tuple[bool, int, str]: """ Check if the registration info is valid. :param registration_info: the registration info :param sender: the sender :return: tuple of success, error code and error message """ if not sender == registration_info["fetchai_address"]: return ( False, 1, "fetchai address of agent and registration info do not match!", ) if registration_info["developer_handle"] in ("", None): return (False, 1, "missing developer_handle!") if sender in self._in_process_registrations: return (False, 1, "registration in process for this address!") registration_db = cast(RegistrationDB, self.context.registration_db) if registration_db.is_registered(sender): return (False, 1, "already registered!") return (True, 0, "all good!") def valid_registration( self, registration_info: Dict[str, str], sender: str ) -> Tuple[bool, int, str]: """ Check if the registration info is valid. :param registration_info: the registration info :param sender: the sender :return: tuple of success, error code and error message """ if self.developer_handle_only: if not all( key in registration_info for key in DEVELOPER_ONLY_REQUIRED_KEYS ): return ( False, 1, f"missing keys in registration info, required: {DEVELOPER_ONLY_REQUIRED_KEYS}!", ) is_valid, error_code, error_msg = self._valid_registration_developer_only( registration_info, sender ) return (is_valid, error_code, error_msg) if not all(key in registration_info for key in REQUIRED_KEYS): return ( False, 1, f"missing keys in registration info, required: {REQUIRED_KEYS}!", ) is_valid, error_code, error_msg = self._valid_registration_developer_only( registration_info, sender ) if not is_valid: return (is_valid, error_code, error_msg) if not self._valid_signature( registration_info["ethereum_address"], registration_info["signature_of_fetchai_address"], sender, "ethereum", ): return (False, 1, "fetchai address and signature do not match!") if not self._valid_signature( sender, registration_info["signature_of_ethereum_address"], registration_info["ethereum_address"], "fetchai", ): return (False, 1, "ethereum address and signature do not match!") return (True, 0, "all good!") def _valid_signature( self, expected_signer: str, signature: str, message_str: str, ledger_id: str ) -> bool: """ Check if the signature and message match the expected signer. :param expected_signer: the signer :param signature: the signature :param message_str: the message :param ledger_id: the ledger id :return: bool indicating validity """ try: result = expected_signer in LedgerApis.recover_message( ledger_id, message_str.encode("utf-8"), signature ) except Exception as e: # pylint: disable=broad-except self.context.logger.warning(f"Signing exception: {e}") result = False return result def get_terms(self, counterparty: str) -> Terms: """ Get terms of transaction. :param counterparty: the counterparty to receive funds :return: the terms """ terms = Terms( ledger_id=self._ledger_id, sender_address=self.context.agent_address, counterparty_address=counterparty, amount_by_currency_id={ self._token_denomination: -self._token_dispense_amount }, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce="some", fee_by_currency_id={self._token_denomination: self._max_tx_fee}, ) return terms @staticmethod def get_kwargs(info: Dict[str, str]) -> JSONLike: """ Get the kwargs for the contract state call. :param info: info dict :return: kwargs json """ counterparty = info["ethereum_address"] return {"address": counterparty} def has_staked(self, state: JSONLike) -> bool: """ Check if the agent has staked. :param state: json state :return: bool, indicating outcome """ if self._override_staking_check: return True result = int(cast(str, state.get("stake", "0"))) > 0 return result ================================================ FILE: packages/fetchai/skills/confirmation_aw2/README.md ================================================ # Confirmation AW2 ## Description This skill purchases information from other agents as specified in its configuration. It acts as the confirmation AEA in Agent World 2. This skill searches for an agent in a vicinity on the sOEF that sells the data this skill is configured to buy. Once it found some agents which match Agent World 2 criteria, it requests this data, negotiates the price, pays the proposed amount if agreement is reached, and receives the data bought. ## Behaviours - `search`: searches for data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `default`: handles `default` messages for registration - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Models - `strategy`: allows the configuration of the purchasing. In particular, location and `search_radius` together determine the vicinity where the service is searched for, `search_query` specifies the query, and the remaining configuration specifies the terms of trade. Also, rules regarding Agent World 2 are enforced. ================================================ FILE: packages/fetchai/skills/confirmation_aw2/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the confirmation_aw2 skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/confirmation_aw2:0.13.6") ================================================ FILE: packages/fetchai/skills/confirmation_aw2/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours of the agent.""" from packages.fetchai.skills.generic_buyer.behaviours import ( GenericSearchBehaviour, GenericTransactionBehaviour, ) SearchBehaviour = GenericSearchBehaviour TransactionBehaviour = GenericTransactionBehaviour ================================================ FILE: packages/fetchai/skills/confirmation_aw2/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - SigningDialogues: The dialogues class keeps track of all dialogues of type signing. """ from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( SigningDialogues as GenericSigningDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues SigningDialogues = GenericSigningDialogues ================================================ FILE: packages/fetchai/skills/confirmation_aw2/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of the agent.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.skills.confirmation_aw2.strategy import Strategy from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogue, DefaultDialogues, ) from packages.fetchai.skills.generic_buyer.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, GenericSigningHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler SigningHandler = GenericSigningHandler class DefaultHandler(Handler): """This class implements the default handler.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ default_msg = cast(DefaultMessage, message) # recover dialogue default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_dialogue = cast(DefaultDialogue, default_dialogues.update(default_msg)) if default_dialogue is None: self._handle_unidentified_dialogue(default_msg) return # handle message if default_msg.performative == DefaultMessage.Performative.BYTES: self._handle_bytes(default_msg, default_dialogue) else: self._handle_invalid(default_msg, default_dialogue) def _handle_unidentified_dialogue(self, default_msg: DefaultMessage) -> None: """ Handle an unidentified dialogue. :param default_msg: the message """ self.context.logger.info( f"received invalid default message={default_msg}, unidentified dialogue." ) def _handle_bytes( self, default_msg: DefaultMessage, default_dialogue: DefaultDialogue ) -> None: """ Handle a default message of invalid performative. :param default_msg: the message :param default_dialogue: the default dialogue """ strategy = cast(Strategy, self.context.strategy) if default_msg.sender == strategy.aw1_aea: try: confirmed_aea, developer_handle = default_msg.content.decode( "utf-8" ).split("_") except Exception: # pylint: disable=broad-except confirmed_aea, developer_handle = "", "" if not LedgerApis.is_valid_address("fetchai", confirmed_aea): self.context.logger.warning( f"received invalid address={confirmed_aea} in dialogue={default_dialogue}." ) return if developer_handle == "": self.context.logger.warning( f"received invalid developer_handle={developer_handle}." ) return self.context.logger.info( f"adding confirmed_aea={confirmed_aea} with developer_handle={developer_handle} to db." ) strategy.register_counterparty(confirmed_aea, developer_handle) else: self.context.logger.warning( f"cannot handle default message of performative={default_msg.performative} in dialogue={default_dialogue}. Invalid sender={default_msg.sender}" ) def _handle_invalid( self, default_msg: DefaultMessage, default_dialogue: DefaultDialogue ) -> None: """ Handle a default message of invalid performative. :param default_msg: the message :param default_dialogue: the default dialogue """ self.context.logger.warning( f"cannot handle default message of performative={default_msg.performative} in dialogue={default_dialogue}." ) def teardown(self) -> None: """Implement the handler teardown.""" ================================================ FILE: packages/fetchai/skills/confirmation_aw2/registration_db.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a the registration db model.""" import datetime import json import logging import os import sqlite3 from typing import Any, Dict, List, Optional, Tuple from aea.skills.base import Model _default_logger = logging.getLogger( "aea.packages.fetchai.skills.confirmation_aw2.registration_db" ) class RegistrationDB(Model): """Communicate between the database and the python objects.""" def __init__(self, **kwargs: Any) -> None: """Initialise the class.""" custom_path = kwargs.pop("custom_path", None) super().__init__(**kwargs) this_dir = os.getcwd() self.db_path = ( os.path.join(this_dir, "registration.db") if custom_path is None else custom_path ) if not os.path.exists(os.path.dirname(os.path.abspath(self.db_path))): raise ValueError(f"Path={self.db_path} not valid!") # pragma: nocover self._initialise_backend() def _initialise_backend(self) -> None: """Set up database and initialise the tables.""" self._execute_single_sql( "CREATE TABLE IF NOT EXISTS registered_table (address TEXT, ethereum_address TEXT, " "ethereum_signature TEXT, fetchai_signature TEXT, " "developer_handle TEXT, tweet TEXT)" ) self._execute_single_sql( "CREATE TABLE IF NOT EXISTS trade_table (address TEXT PRIMARY KEY, first_trade timestamp, " "second_trade timestamp, first_info TEXT, second_info TEXT)" ) def set_trade( self, address: str, timestamp: datetime.datetime, data: Dict[str, str], ) -> None: """Record a registration.""" record = self.get_trade_table(address) if record is None: command = "INSERT INTO trade_table(address, first_trade, second_trade, first_info, second_info) values(?, ?, ?, ?, ?)" variables: Tuple[ str, datetime.datetime, Optional[datetime.datetime], str, Optional[str] ] = (address, timestamp, None, json.dumps(data), None) else: _, first_trade, second_trade, first_info, _ = record is_second = first_trade is not None and second_trade is None is_more_than_two = first_trade is not None and second_trade is not None if is_more_than_two or not is_second: return command = "INSERT or REPLACE into trade_table(address, first_trade, second_trade, first_info, second_info) values(?, ?, ?, ?, ?)" variables = ( address, first_trade, timestamp, first_info, json.dumps(data), ) self._execute_single_sql(command, variables) def get_trade_table(self, address: str) -> Optional[Tuple]: """Check whether a trade is second or not.""" command = "SELECT * FROM trade_table where address=?" ret = self._execute_single_sql(command, (address,)) return ret[0] if len(ret) > 0 else None def set_registered(self, address: str, developer_handle: str) -> None: """Record a registration.""" if self.is_registered(address): return command = "INSERT OR REPLACE INTO registered_table(address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet) values(?, ?, ?, ?, ?, ?)" variables = ( address, "", "", "", developer_handle, "", ) self._execute_single_sql(command, variables) def is_registered(self, address: str) -> bool: """Check if an address is registered.""" command = "SELECT * FROM registered_table WHERE address=?" variables = (address,) result = self._execute_single_sql(command, variables) return len(result) != 0 def is_allowed_to_trade(self, address: str, minimum_hours_between_txs: int) -> bool: """Check if an address is registered.""" record = self.get_trade_table(address) if record is None: # no record on trade: go ahead return True first_trade: Optional[str] = record[1] second_trade: Optional[str] = record[2] first_trade_present: bool = first_trade is not None second_trade_present: bool = second_trade is not None if not first_trade_present and not second_trade_present: # all trades empty: go ahead return True if first_trade is not None and not second_trade_present: now = datetime.datetime.now() first_trade_dt = datetime.datetime.strptime( first_trade, "%Y-%m-%d %H:%M:%S.%f" ) is_allowed_to_trade_ = now - first_trade_dt > datetime.timedelta( hours=minimum_hours_between_txs ) if not is_allowed_to_trade_: self.context.logger.info( f"Invalid attempt for counterparty={address}, not enough time since last trade!" ) return is_allowed_to_trade_ self.context.logger.info( f"Invalid attempt for counterparty={address}, already completed 2 trades!" ) return False def has_completed_two_trades(self, address: str) -> bool: """ Check if address has completed two trades. :param address: the address to check :return: bool """ record = self.get_trade_table(address) if record is None: return False first_trade: Optional[str] = record[1] second_trade: Optional[str] = record[2] first_trade_present: bool = first_trade is not None second_trade_present: bool = second_trade is not None return first_trade_present and second_trade_present def completed_two_trades(self) -> List[Tuple[str, str, str]]: """ Get the address, ethereum_address and developer handle combos which completed two trades. :return: (address, ethereum_address, developer_handle) """ command = "SELECT * FROM registered_table" variables = () result = self._execute_single_sql(command, variables) completed: List[Tuple[str, str, str]] = [] for row in result: address = row[0] ethereum_address = row[1] developer_handle = row[4] if self.has_completed_two_trades(address): completed.append((address, ethereum_address, developer_handle)) return completed def _execute_single_sql( self, command: str, variables: Tuple[Any, ...] = (), print_exceptions: bool = True, ) -> List[Tuple[str, ...]]: """Query the database - all the other functions use this under the hood.""" conn = None ret: List[Tuple[str, ...]] = [] try: conn = sqlite3.connect(self.db_path, timeout=300) # 5 mins c = conn.cursor() c.execute(command, variables) ret = c.fetchall() conn.commit() except Exception as e: # pragma: nocover # pylint: disable=broad-except if print_exceptions: self.context.logger.warning(f"Exception in database: {e}") finally: if conn is not None: conn.close() return ret ================================================ FILE: packages/fetchai/skills/confirmation_aw2/skill.yaml ================================================ name: confirmation_aw2 author: fetchai version: 0.13.6 type: skill description: This skill purchases information from other agents as specified in its configuration. It is the confirmation buyer for Agent World 2. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmSQgVJrmh5XBGLKVmzEzUVmeEr6BmZmeWebqixq6EBftd __init__.py: QmT9TnpHa8E3EGvPupDbjTJm3quhLTgUGAYjPXKGVnbsYs behaviours.py: QmSr6fB3N7dhVo1cLY1TGd2q8usjGwNmTCNjBBbtgtVf9j dialogues.py: QmXgXcs25v9ob9a9XwT49wvK788vbUdZyYxUgG3ndHjrix handlers.py: QmNvKz36cN7H53yUpdW7GDAFbk5YThSVnFd691GLi3N4uN registration_db.py: QmW4h9dsk7V5JmAwyJArYsAdUeKNKWL2q4cPK6FXpZV21C strategy.py: QmYAdTnztBNQXDp3CqMx1PEVhtz3zFdcLuQz8muZysDfyj fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/generic_buyer:0.27.6 behaviours: search: args: search_interval: 5 class_name: SearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: default_handler: args: {} class_name: DefaultHandler fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues registration_db: args: custom_path: path_to_db class_name: RegistrationDB signing_dialogues: args: {} class_name: SigningDialogues strategy: args: aw1_aea: null is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_quantity: 100 max_tx_fee: 3550000000000000 max_unit_price: 20 minimum_hours_between_txs: 4 minimum_minutes_since_last_attempt: 2 search_query: constraint_type: == search_key: seller_service search_value: weather_data search_radius: 50.0 service_id: weather_data stop_searching_on_result: false class_name: Strategy dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/confirmation_aw2/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import datetime from typing import Any, Dict, List, Optional, Tuple, cast from packages.fetchai.skills.confirmation_aw2.registration_db import RegistrationDB from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy class Strategy(GenericStrategy): """Strategy class extending Generic Strategy.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ aw1_aea: Optional[str] = kwargs.pop("aw1_aea", None) if aw1_aea is None: raise ValueError("aw1_aea must be provided!") self.aw1_aea = aw1_aea self.minimum_hours_between_txs = kwargs.pop("mininum_hours_between_txs", 4) self.minimum_minutes_since_last_attempt = kwargs.pop( "minimum_minutes_since_last_attempt", 2 ) super().__init__(**kwargs) self.last_attempt: Dict[str, datetime.datetime] = {} def get_acceptable_counterparties( self, counterparties: Tuple[str, ...] ) -> Tuple[str, ...]: """ Process counterparties and drop unacceptable ones. :param counterparties: a tuple of counterparties :return: list of counterparties """ valid_counterparties: List[str] = [] for counterparty in counterparties: if self.is_valid_counterparty(counterparty): valid_counterparties.append(counterparty) return tuple(valid_counterparties) def is_enough_time_since_last_attempt(self, counterparty: str) -> bool: """ Check if enough time has passed since last attempt for potential previous trade to complete. :param counterparty: the counterparty :return: bool indicating validity """ last_time = self.last_attempt.get(counterparty, None) if last_time is None: return True result = datetime.datetime.now() > last_time + datetime.timedelta( minutes=self.minimum_minutes_since_last_attempt ) return result def is_valid_counterparty(self, counterparty: str) -> bool: """ Check if the counterparty is valid. :param counterparty: the counterparty :return: bool indicating validity """ registration_db = cast(RegistrationDB, self.context.registration_db) if not registration_db.is_registered(counterparty): self.context.logger.info( f"Invalid counterparty={counterparty}, not registered!" ) return False if not self.is_enough_time_since_last_attempt(counterparty): self.context.logger.debug( f"Not enough time since last attempt for counterparty={counterparty}!" ) return False self.last_attempt[counterparty] = datetime.datetime.now() if not registration_db.is_allowed_to_trade( counterparty, self.minimum_hours_between_txs ): return False return True def successful_trade_with_counterparty( self, counterparty: str, data: Dict[str, str] ) -> None: """ Do something on successful trade. :param counterparty: the counterparty address :param data: the data """ registration_db = cast(RegistrationDB, self.context.registration_db) registration_db.set_trade(counterparty, datetime.datetime.now(), data) self.context.logger.info( f"Successful trade with={counterparty}. Data acquired={data}!" ) def register_counterparty(self, counterparty: str, developer_handle: str) -> None: """ Register a counterparty. :param counterparty: the counterparty address :param developer_handle: the developer handle """ registration_db = cast(RegistrationDB, self.context.registration_db) registration_db.set_registered(counterparty, developer_handle) ================================================ FILE: packages/fetchai/skills/confirmation_aw3/README.md ================================================ # Confirmation AW3 ## Description This skill purchases information from other agents as specified in its configuration. It acts as the confirmation AEA in Agent World 3. This skill searches for an agent in a vicinity on the sOEF that sells the data this skill is configured to buy. Once it found some agents which match Agent World 3 criteria, it requests this data, negotiates the price, pays the proposed amount if agreement is reached, and receives the data bought. The skill just randomly selects the location in which it searches and the query from a list. On each search it completes as many trade as possible. ## Behaviours - `search`: searches for data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `default`: handles `default` messages for registration - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Models - `strategy`: allows the configuration of the purchasing. In particular, location and `search_radius` together determine the vicinity where the service is searched for, `search_query` specifies the query, and the remaining configuration specifies the terms of trade. Also, rules regarding Agent World 3 are enforced. ================================================ FILE: packages/fetchai/skills/confirmation_aw3/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the confirmation_aw3 skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/confirmation_aw3:0.12.6") ================================================ FILE: packages/fetchai/skills/confirmation_aw3/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours of the agent.""" from packages.fetchai.skills.generic_buyer.behaviours import ( GenericSearchBehaviour, GenericTransactionBehaviour, ) TransactionBehaviour = GenericTransactionBehaviour SearchBehaviour = GenericSearchBehaviour ================================================ FILE: packages/fetchai/skills/confirmation_aw3/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - SigningDialogues: The dialogues class keeps track of all dialogues of type signing. """ from typing import Any from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogue as GenericDefaultDialogue, ) from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( SigningDialogues as GenericSigningDialogues, ) DefaultDialogue = GenericDefaultDialogue DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues SigningDialogues = GenericSigningDialogues HttpDialogue = BaseHttpDialogue class HttpDialogues(Model, BaseHttpDialogues): """This class keeps track of all http dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseHttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/confirmation_aw3/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of the agent.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.confirmation_aw3.dialogues import ( DefaultDialogue, DefaultDialogues, HttpDialogue, HttpDialogues, ) from packages.fetchai.skills.confirmation_aw3.strategy import Strategy from packages.fetchai.skills.generic_buyer.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, GenericSigningHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler SigningHandler = GenericSigningHandler class DefaultHandler(Handler): """This class implements the default handler.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ default_msg = cast(DefaultMessage, message) # recover dialogue default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_dialogue = cast(DefaultDialogue, default_dialogues.update(default_msg)) if default_dialogue is None: self._handle_unidentified_dialogue(default_msg) return # handle message if default_msg.performative == DefaultMessage.Performative.BYTES: self._handle_bytes(default_msg, default_dialogue) else: self._handle_invalid(default_msg, default_dialogue) def _handle_unidentified_dialogue(self, default_msg: DefaultMessage) -> None: """ Handle an unidentified dialogue. :param default_msg: the message """ self.context.logger.info( f"received invalid default message={default_msg}, unidentified dialogue." ) def _handle_bytes( self, default_msg: DefaultMessage, default_dialogue: DefaultDialogue ) -> None: """ Handle a default message of invalid performative. :param default_msg: the message :param default_dialogue: the default dialogue """ strategy = cast(Strategy, self.context.strategy) if default_msg.sender == strategy.aw1_aea: try: confirmed_aea, developer_handle = default_msg.content.decode( "utf-8" ).split("_") except Exception: # pylint: disable=broad-except confirmed_aea, developer_handle = "", "" if not LedgerApis.is_valid_address("fetchai", confirmed_aea): self.context.logger.warning( f"received invalid address={confirmed_aea} in dialogue={default_dialogue}." ) return if developer_handle == "": self.context.logger.warning( f"received invalid developer_handle={developer_handle}." ) return self.context.logger.info( f"adding confirmed_aea={confirmed_aea} with developer_handle={developer_handle} to db." ) strategy.register_counterparty(confirmed_aea, developer_handle) else: self.context.logger.warning( f"cannot handle default message of performative={default_msg.performative} in dialogue={default_dialogue}. Invalid sender={default_msg.sender}" ) def _handle_invalid( self, default_msg: DefaultMessage, default_dialogue: DefaultDialogue ) -> None: """ Handle a default message of invalid performative. :param default_msg: the message :param default_dialogue: the default dialogue """ self.context.logger.warning( f"cannot handle default message of performative={default_msg.performative} in dialogue={default_dialogue}." ) def teardown(self) -> None: """Implement the handler teardown.""" class HttpHandler(Handler): """This implements the http handler.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ http_msg = cast(HttpMessage, message) # recover dialogue http_dialogues = cast(HttpDialogues, self.context.http_dialogues) http_dialogue = cast(HttpDialogue, http_dialogues.update(http_msg)) if http_dialogue is None: self._handle_unidentified_dialogue(http_msg) return # handle message if http_msg.performative == HttpMessage.Performative.RESPONSE: self._handle_response(http_msg, http_dialogue) else: self._handle_invalid(http_msg, http_dialogue) def _handle_unidentified_dialogue(self, http_msg: HttpMessage) -> None: """ Handle an unidentified dialogue. :param http_msg: the message """ self.context.logger.info( "received invalid http message={}, unidentified dialogue.".format(http_msg) ) def _handle_response( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle a Http response. :param http_msg: the http message :param http_dialogue: the http dialogue """ self.context.logger.info( "received http response with status_code={}, status_text={} and body={!r} in dialogue={}".format( http_msg.status_code, http_msg.status_text, http_msg.body, http_dialogue ) ) def _handle_invalid( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle an invalid http message. :param http_msg: the http message :param http_dialogue: the http dialogue """ self.context.logger.warning( "cannot handle http message of performative={} in dialogue={}.".format( http_msg.performative, http_dialogue ) ) def teardown(self) -> None: """Implement the handler teardown.""" ================================================ FILE: packages/fetchai/skills/confirmation_aw3/registration_db.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a the registration db model.""" import datetime import json import logging import os import sqlite3 from collections import defaultdict from typing import Any, Dict, List, Tuple, cast from aea.skills.base import Model _default_logger = logging.getLogger( "aea.packages.fetchai.skills.confirmation_aw3.registration_db" ) class RegistrationDB(Model): """Communicate between the database and the python objects.""" def __init__(self, **kwargs: Any) -> None: """Initialise the class.""" custom_path = kwargs.pop("custom_path", None) super().__init__(**kwargs) this_dir = os.getcwd() self.db_path = ( os.path.join(this_dir, "registration.db") if custom_path is None else custom_path ) if not os.path.exists(os.path.dirname(os.path.abspath(self.db_path))): raise ValueError(f"Path={self.db_path} not valid!") # pragma: nocover self._initialise_backend() def _initialise_backend(self) -> None: """Set up database and initialise the tables.""" self._execute_single_sql( "CREATE TABLE IF NOT EXISTS registered_table (address TEXT NOT NULL, ethereum_address TEXT, " "ethereum_signature TEXT, fetchai_signature TEXT, " "developer_handle TEXT NOT NULL, tweet TEXT, PRIMARY KEY (address, developer_handle))" ) self._execute_single_sql( "CREATE TABLE IF NOT EXISTS trades_table (address TEXT, created_at timestamp, data TEXT)" ) def set_trade( self, address: str, timestamp: datetime.datetime, data: Dict[str, str], ) -> None: """Record a registration.""" command = "INSERT INTO trades_table(address, created_at, data) values(?, ?, ?)" variables: Tuple[str, datetime.datetime, str] = ( address, timestamp, json.dumps(data), ) self._execute_single_sql(command, variables) def get_trade_count(self, address: str) -> int: """Get trade count.""" command = "SELECT COUNT(*) FROM trades_table where address=?" ret = self._execute_single_sql(command, (address,)) return int(ret[0][0]) def get_developer_handle(self, address: str) -> str: """Get developer handle for address.""" command = "SELECT developer_handle FROM registered_table where address=?" ret = self._execute_single_sql(command, (address,)) if len(ret[0]) != 1: raise ValueError( f"More than one developer_handle found for address={address}." ) return ret[0][0] def get_addresses(self, developer_handle: str) -> List[str]: """Get addresses for developer handle.""" command = "SELECT address FROM registered_table where developer_handle=?" ret = self._execute_single_sql(command, (developer_handle,)) addresses = [address[0] for address in ret] if len(addresses) == 0: raise ValueError( f"Should find at least one address for developer_handle={developer_handle}." ) return addresses def get_handle_and_trades(self, address: str) -> Tuple[str, int]: """Get developer and number of trades for address.""" developer_handle = self.get_developer_handle(address) addresses = self.get_addresses(developer_handle) trades = 0 for address_ in addresses: trades += self.get_trade_count(address_) return (developer_handle, trades) def get_all_addresses_and_handles(self) -> List[Tuple[str, str]]: """Get all addresses.""" command = "SELECT address, developer_handle FROM registered_table" results = cast(List[Tuple[str, str]], self._execute_single_sql(command, ())) return results def get_leaderboard(self) -> List[Tuple[str, str, int]]: """Get the leader board.""" addresses_and_handles = self.get_all_addresses_and_handles() results_dir: Dict[Tuple[str, str], int] = defaultdict(int) for address, developer_handle in addresses_and_handles: trades = self.get_trade_count(address) if trades == 0: continue results_dir[(address, developer_handle)] += trades results = [(k[0], k[1], v) for k, v in results_dir.items()] results.sort(key=lambda x: x[2], reverse=True) return results def set_registered(self, address: str, developer_handle: str) -> None: """Record a registration.""" if self.is_registered(address): return command = "INSERT OR REPLACE INTO registered_table(address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet) values(?, ?, ?, ?, ?, ?)" variables = ( address, "", "", "", developer_handle, "", ) self._execute_single_sql(command, variables) def is_registered(self, address: str) -> bool: """Check if an address is registered.""" command = "SELECT * FROM registered_table WHERE address=?" variables = (address,) result = self._execute_single_sql(command, variables) return len(result) != 0 def _execute_single_sql( self, command: str, variables: Tuple[Any, ...] = (), print_exceptions: bool = True, ) -> List[Tuple[str, ...]]: """Query the database - all the other functions use this under the hood.""" conn = None ret: List[Tuple[str, ...]] = [] try: conn = sqlite3.connect(self.db_path, timeout=300) # 5 mins c = conn.cursor() c.execute(command, variables) ret = c.fetchall() conn.commit() except Exception as e: # pragma: nocover # pylint: disable=broad-except if print_exceptions: self.context.logger.warning(f"Exception in database: {e}") finally: if conn is not None: conn.close() return ret ================================================ FILE: packages/fetchai/skills/confirmation_aw3/skill.yaml ================================================ name: confirmation_aw3 author: fetchai version: 0.12.6 type: skill description: This skill purchases information from other agents as specified in its configuration. It is the confirmation buyer for Agent World 3. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qmc4XzqCbGB6mS17ZuvW2xuYnR796NX5KD3fyaNqchLirS __init__.py: QmSDPS2qm1UPFGfhMtKnSrAKsksRSuypqwF1rw1AP5PTEQ behaviours.py: QmagZNufvC8QgMRkPokWg71Gg2nbNSL5bBurxZtvgpGVhw dialogues.py: QmXzPttMCTFQxh7R1BXzqFcdezGp7yybstkZDPJFzjECMM handlers.py: QmSBUW5akPUegJqSAThyEtPtkhiFwTNQ7iqdadeztGAwhB registration_db.py: QmPXfcm3mmJyaTfyKvEdwSVhwtrA4aTiBQLENDNUHmzMvc strategy.py: QmaSrYsAtniAEpW5NrnqBqQiBfvhhSZ6aBWRFDtXowJjQJ fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/generic_buyer:0.27.6 behaviours: search: args: search_interval: 1800 class_name: SearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: default_handler: args: {} class_name: DefaultHandler fipa: args: {} class_name: FipaHandler http_handler: args: {} class_name: HttpHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues http_dialogues: args: {} class_name: HttpDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues registration_db: args: custom_path: path_to_db class_name: RegistrationDB signing_dialogues: args: {} class_name: SigningDialogues strategy: args: aw1_aea: null is_ledger_tx: true leaderboard_token: null leaderboard_url: null locations: berlin: latitude: 52.52 longitude: 13.405 london: latitude: 51.5074 longitude: -0.1278 san_francisco: latitude: 37.7749 longitude: -122.4194 shanghai: latitude: 31.2304 longitude: 121.4737 rome: latitude: 41.9028 longitude: 12.4964 rio_de_janeiro: latitude: -22.9068 longitude: -43.1729 sydney: latitude: -33.8688 longitude: 151.2093 delhi: latitude: 28.7041 longitude: 77.1025 tokyo: latitude: 35.6762 longitude: 139.6503 mexico_city: latitude: 19.4326 longitude: -99.1332 cairo: latitude: 30.0444 longitude: 31.2357 kinshasa: latitude: -4.4419 longitude: 15.2663 max_negotiations: 1 max_quantity: 100 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_queries: weather: constraint_type: == search_key: seller_service search_value: weather_data mobility: constraint_type: == search_key: seller_service search_value: mobility_data search_radius: 50.0 stop_searching_on_result: false class_name: Strategy dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/confirmation_aw3/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import datetime import json import random from typing import Any, Dict, List, Optional, Tuple, cast from aea.helpers.search.models import Location from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_PUBLIC_ID, ) from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.confirmation_aw3.dialogues import HttpDialogues from packages.fetchai.skills.confirmation_aw3.registration_db import RegistrationDB from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy class Strategy(GenericStrategy): """Strategy class extending Generic Strategy.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ aw1_aea: Optional[str] = kwargs.pop("aw1_aea", None) if aw1_aea is None: raise ValueError("aw1_aea must be provided!") self.aw1_aea = aw1_aea self._locations = kwargs.pop("locations", {}) if len(self._locations) == 0: raise ValueError("locations must have at least one entry") _, location = next(iter(self._locations.items())) kwargs["location"] = location self._search_queries = kwargs.pop("search_queries", {}) if len(self._search_queries) == 0: raise ValueError("search_queries must have at least one entry") _, search_query = next(iter(self._search_queries.items())) kwargs["search_query"] = search_query kwargs["service_id"] = search_query["search_value"] leaderboard_url = kwargs.pop("leaderboard_url", None) if leaderboard_url is None: raise ValueError("No leader board url provided!") self.leaderboard_url = f"{leaderboard_url}/insert" leaderboard_token = kwargs.pop("leaderboard_token") if leaderboard_token is None: raise ValueError("No leader board token provided!") self.leaderboard_token = leaderboard_token super().__init__(**kwargs) def get_acceptable_counterparties( self, counterparties: Tuple[str, ...] ) -> Tuple[str, ...]: """ Process counterparties and drop unacceptable ones. :param counterparties: tuple of counterparties :return: list of counterparties """ valid_counterparties: List[str] = [] for counterparty in counterparties: if self.is_valid_counterparty(counterparty): valid_counterparties.append(counterparty) return tuple(valid_counterparties) def is_valid_counterparty(self, counterparty: str) -> bool: """ Check if the counterparty is valid. :param counterparty: the counterparty :return: bool indicating validity """ registration_db = cast(RegistrationDB, self.context.registration_db) if not registration_db.is_registered(counterparty): self.context.logger.info( f"Invalid counterparty={counterparty}, not registered!" ) return False return True def successful_trade_with_counterparty( self, counterparty: str, data: Dict[str, str] ) -> None: """ Do something on successful trade. :param counterparty: the counterparty address :param data: the data """ registration_db = cast(RegistrationDB, self.context.registration_db) registration_db.set_trade(counterparty, datetime.datetime.now(), data) self.context.logger.info(f"Successful trade with={counterparty}.") developer_handle, nb_trades = registration_db.get_handle_and_trades( counterparty ) http_dialogues = cast(HttpDialogues, self.context.http_dialogues) request_http_message, _ = http_dialogues.create( counterparty=str(HTTP_CLIENT_PUBLIC_ID), performative=HttpMessage.Performative.REQUEST, method="POST", url=self.leaderboard_url, headers="Content-Type: application/json; charset=utf-8", version="", body=json.dumps( { "name": developer_handle, "points": nb_trades, "token": self.leaderboard_token, } ).encode("utf-8"), ) self.context.outbox.put_message(message=request_http_message) self.context.logger.info( f"Notifying leaderboard: developer_handle={developer_handle}, nb_trades={nb_trades}." ) def register_counterparty(self, counterparty: str, developer_handle: str) -> None: """ Register a counterparty. :param counterparty: the counterparty address :param developer_handle: the developer handle """ registration_db = cast(RegistrationDB, self.context.registration_db) registration_db.set_registered(counterparty, developer_handle) def update_search_query_params(self) -> None: """Update agent location and query for search.""" search_query_type, search_query = random.choice( # nosec list(self._search_queries.items()) ) self._search_query = search_query self._service_id = search_query["search_value"] location_name, location = random.choice(list(self._locations.items())) # nosec self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self.context.logger.info( f"New search_type={search_query_type} and location={location_name}." ) ================================================ FILE: packages/fetchai/skills/echo/README.md ================================================ # Echo ## Description This skill sends back the contents of any message it receives. ## Behaviours - `echo`: outputs messages ## Handlers - `echo`: handles `default` messages for echoing back the contents of any message received ## Links - Quick Start - Programmatically Build an AEA ================================================ FILE: packages/fetchai/skills/echo/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains an example of skill for an AEA.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/echo:0.20.6") ================================================ FILE: packages/fetchai/skills/echo/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours for the 'echo' skill.""" from aea.skills.behaviours import TickerBehaviour class EchoBehaviour(TickerBehaviour): """Echo behaviour.""" def setup(self) -> None: """Set up the behaviour.""" self.context.logger.info("Echo Behaviour: setup method called.") def act(self) -> None: """Act according to the behaviour.""" self.context.logger.info("Echo Behaviour: act method called.") def teardown(self) -> None: """Teardown the behaviour.""" self.context.logger.info("Echo Behaviour: teardown method called.") ================================================ FILE: packages/fetchai/skills/echo/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the classes required for dialogue management.""" from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_name, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/echo/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the handler for the 'echo' skill.""" from typing import cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.skills.echo.dialogues import DefaultDialogue, DefaultDialogues class EchoHandler(Handler): """Echo handler.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Set up the handler.""" self.context.logger.info("Echo Handler: setup method called.") def handle(self, message: Message) -> None: """ Handle the message. :param message: the message. """ default_message = cast(DefaultMessage, message) # recover dialogue default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_dialogue = cast(DefaultDialogue, default_dialogues.update(message)) if default_dialogue is None: self._handle_unidentified_dialogue(default_message) return # handle message if message.performative == DefaultMessage.Performative.BYTES: self._handle_bytes(default_message, default_dialogue) elif message.performative == DefaultMessage.Performative.ERROR: self._handle_error(default_message, default_dialogue) else: self._handle_invalid(default_message, default_dialogue) def teardown(self) -> None: """Teardown the handler.""" self.context.logger.info("Echo Handler: teardown method called.") def _handle_unidentified_dialogue(self, message: DefaultMessage) -> None: """ Handle unidentified dialogue. :param message: the message. """ self.context.logger.info( "received invalid default message={}, unidentified dialogue.".format( message ) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) reply, _ = default_dialogues.create( counterparty=message.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"default_message": message.encode()}, ) self.context.outbox.put_message(message=reply) def _handle_error(self, message: DefaultMessage, dialogue: DefaultDialogue) -> None: """ Handle a message of error performative. :param message: the default message. :param dialogue: the dialogue. """ self.context.logger.info( "received default error message={} in dialogue={}.".format( message, dialogue ) ) def _handle_bytes(self, message: DefaultMessage, dialogue: DefaultDialogue) -> None: """ Handle a message of bytes performative. :param message: the default message. :param dialogue: the default dialogue. """ self.context.logger.info( "Echo Handler: message={}, sender={}".format(message, message.sender) ) reply = dialogue.reply( performative=DefaultMessage.Performative.BYTES, target_message=message, content=message.content, ) self.context.outbox.put_message(message=reply) def _handle_invalid( self, message: DefaultMessage, dialogue: DefaultDialogue ) -> None: """ Handle an invalid message. :param message: the message. :param dialogue: the dialogue. """ self.context.logger.info( "received invalid message={} in dialogue={}.".format(message, dialogue) ) ================================================ FILE: packages/fetchai/skills/echo/skill.yaml ================================================ name: echo author: fetchai version: 0.20.6 type: skill description: The echo skill implements simple echo functionality. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmNijzN1uUKmcH64VUGCfZBDzAnJjkqjUrnFALVaAceU6i __init__.py: QmNPzngDudTFkW8jKh61U9fPKHKR1m5iPsTAqBbM8X1nqa behaviours.py: QmRAFuVFQr6LkXLU8jMkHksd2YUiTrx2ABSSTZgFrJpFxT dialogues.py: QmeV9E8Qf5cLCjWD3F5MLthpoP5oZwr52RJrAXc4p9ZntY handlers.py: QmPFAH8Ae3wKxZxu2xn2XAftgjg1cv9vaxdFqr3FJAfnTG fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/default:1.1.7 skills: [] behaviours: echo: args: tick_interval: 1.0 class_name: EchoBehaviour handlers: echo: args: {} class_name: EchoHandler models: default_dialogues: args: {} class_name: DefaultDialogues dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/erc1155_client/README.md ================================================ # ERC1155 Client ## Description This is a skill for demoing the purchase of data via a smart contract. This skill finds an `ERC1155 contract deployment AEA` on the sOEF, requests specific data, negotiates the price, pays the proposed amount via smart contract if agreement is reach, and receives the data bought. ## Behaviours - `search`: searches for the ERC1155 deployment agent on the sOEF ## Handlers - `contract_api`: handles `contract_api` messages for interactions with the smart contract - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - Contract Deployment Guide ================================================ FILE: packages/fetchai/skills/erc1155_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the erc-1155 client skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/erc1155_client:0.29.6") ================================================ FILE: packages/fetchai/skills/erc1155_client/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour for the erc-1155 client skill.""" from typing import Any, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.erc1155_client.dialogues import ( LedgerApiDialogues, OefSearchDialogues, ) from packages.fetchai.skills.erc1155_client.strategy import Strategy DEFAULT_SEARCH_INTERVAL = 5.0 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class SearchBehaviour(TickerBehaviour): """This class implements a search behaviour.""" def __init__(self, **kwargs: Any) -> None: """Initialize the search behaviour.""" search_interval = cast( float, kwargs.pop("search_interval", DEFAULT_SEARCH_INTERVAL) ) super().__init__(tick_interval=search_interval, **kwargs) def setup(self) -> None: """Implement the setup for the behaviour.""" strategy = cast(Strategy, self.context.strategy) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=strategy.ledger_id, address=cast(str, self.context.agent_addresses.get(strategy.ledger_id)), ) self.context.outbox.put_message(message=ledger_api_msg) def act(self) -> None: """Implement the act.""" strategy = cast(Strategy, self.context.strategy) if strategy.is_searching: query = strategy.get_location_and_service_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) self.context.outbox.put_message(message=oef_search_msg) def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/erc1155_client/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - Dialogue: The dialogue class maintains state of a dialogue and manages it. - Dialogues: The dialogues class keeps track of all dialogues. """ from typing import Any, Optional, Type from aea.common import Address from aea.exceptions import enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue as BaseContractApiDialogue, ) from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage class ContractApiDialogue(BaseContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_terms", "_associated_fipa_dialogue") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: message class """ BaseContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] self._associated_fipa_dialogue = None # type: Optional[BaseFipaDialogue] @property def terms(self) -> Terms: """Get the terms.""" if self._terms is None: raise ValueError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set the terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms @property def associated_fipa_dialogue(self) -> BaseFipaDialogue: """Get the associated fipa dialogue.""" if self._associated_fipa_dialogue is None: raise ValueError("Associated fipa dialogue not set!") return self._associated_fipa_dialogue @associated_fipa_dialogue.setter def associated_fipa_dialogue( self, associated_fipa_dialogue: BaseFipaDialogue ) -> None: """Set the associated fipa dialogue.""" enforce( self._associated_fipa_dialogue is None, "Associated fipa dialogue already set!", ) self._associated_fipa_dialogue = associated_fipa_dialogue class ContractApiDialogues(Model, BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=ContractApiDialogue, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) FipaDialogue = BaseFipaDialogue class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseFipaDialogue.Role.SELLER BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) LedgerApiDialogue = BaseLedgerApiDialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_contract_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_contract_api_dialogue = ( None ) # type: Optional[ContractApiDialogue] @property def associated_contract_api_dialogue(self) -> ContractApiDialogue: """Get the associated contract api dialogue.""" if self._associated_contract_api_dialogue is None: raise ValueError("Associated contract api dialogue not set!") return self._associated_contract_api_dialogue @associated_contract_api_dialogue.setter def associated_contract_api_dialogue( self, associated_contract_api_dialogue: ContractApiDialogue ) -> None: """Set the associated contract api dialogue.""" enforce( self._associated_contract_api_dialogue is None, "Associated contract api dialogue already set!", ) self._associated_contract_api_dialogue = associated_contract_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: packages/fetchai/skills/erc1155_client/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains handlers for the erc1155-client skill.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.helpers.transaction.base import RawMessage, Terms from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_client.dialogues import ( ContractApiDialogue, ContractApiDialogues, DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.erc1155_client.strategy import Strategy LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class FipaHandler(Handler): """This class implements a FIPA handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ fipa_msg = cast(FipaMessage, message) # recover dialogue fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return # handle message if fipa_msg.performative == FipaMessage.Performative.PROPOSE: self._handle_propose(fipa_msg, fipa_dialogue) else: self._handle_invalid(fipa_msg, fipa_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. Respond to the sender with a default message containing the appropriate error information. :param fipa_msg: the message """ self.context.logger.info( "unidentified dialogue for message={}.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=fipa_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": fipa_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_propose( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the CFP. If the CFP matches the supplied services then send a PROPOSE, otherwise send a DECLINE. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ if all( key in [ "contract_address", "from_supply", "to_supply", "value", "trade_nonce", "token_id", ] for key in fipa_msg.proposal.values.keys() ): # accept any proposal with the correct keys self.context.logger.info( "received valid PROPOSE from sender={}: proposal={}".format( fipa_msg.sender[-5:], fipa_msg.proposal.values, ) ) strategy = cast(Strategy, self.context.strategy) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, contract_address=fipa_msg.proposal.values["contract_address"], callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( { "from_address": fipa_msg.sender, "to_address": self.context.agent_address, "token_id": int(fipa_msg.proposal.values["token_id"]), "from_supply": int(fipa_msg.proposal.values["from_supply"]), "to_supply": int(fipa_msg.proposal.values["to_supply"]), "value": int(fipa_msg.proposal.values["value"]), "trade_nonce": int(fipa_msg.proposal.values["trade_nonce"]), } ), ) terms = Terms( ledger_id=strategy.ledger_id, sender_address=self.context.agent_address, counterparty_address=fipa_msg.sender, amount_by_currency_id={}, quantities_by_good_id={ str(fipa_msg.proposal.values["token_id"]): int( fipa_msg.proposal.values["from_supply"] ) - int(fipa_msg.proposal.values["to_supply"]) }, is_sender_payable_tx_fee=False, nonce=str(fipa_msg.proposal.values["trade_nonce"]), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = terms contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info( "requesting single hash message from contract api..." ) else: self.context.logger.info( "received invalid PROPOSE from sender={}: proposal={}".format( fipa_msg.sender[-5:], fipa_msg.proposal.values, ) ) def _handle_invalid( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle a fipa message of invalid performative. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle fipa message of performative={} in dialogue={}.".format( fipa_msg.performative, fipa_dialogue ) ) class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the oef search message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_msg, oef_search_dialogue ) ) def _handle_search( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle the search response. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ if len(oef_search_msg.agents) == 0: self.context.logger.info( "found no agents in dialogue={}, continue searching.".format( oef_search_dialogue ) ) return self.context.logger.info( "found agents={}, stopping search.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) strategy = cast(Strategy, self.context.strategy) strategy.is_searching = False query = strategy.get_service_query() fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) for opponent_address in oef_search_msg.agents: cfp_msg, _ = fipa_dialogues.create( counterparty=opponent_address, performative=FipaMessage.Performative.CFP, query=query, ) self.context.logger.info( "sending CFP to agent={}".format(opponent_address[-5:]) ) self.context.outbox.put_message(message=cfp_msg) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) class ContractApiHandler(Handler): """Implement the contract api handler.""" SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ contract_api_msg = cast(ContractApiMessage, message) # recover dialogue contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_dialogue = cast( Optional[ContractApiDialogue], contract_api_dialogues.update(contract_api_msg), ) if contract_api_dialogue is None: self._handle_unidentified_dialogue(contract_api_msg) return # handle message if contract_api_msg.performative is ContractApiMessage.Performative.RAW_MESSAGE: self._handle_raw_message(contract_api_msg, contract_api_dialogue) elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: self._handle_error(contract_api_msg, contract_api_dialogue) else: self._handle_invalid(contract_api_msg, contract_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue( self, contract_api_msg: ContractApiMessage ) -> None: """ Handle an unidentified dialogue. :param contract_api_msg: the contract api message """ self.context.logger.info( "received invalid contract_api message={}, unidentified dialogue.".format( contract_api_msg ) ) def _handle_raw_message( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_message performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw message={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_MESSAGE, raw_message=RawMessage( contract_api_msg.raw_message.ledger_id, contract_api_msg.raw_message.body, is_deprecated_mode=True, ), terms=contract_api_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of error performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received contract_api error message={} in dialogue={}.".format( contract_api_msg, contract_api_dialogue ) ) def _handle_invalid( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of invalid performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle contract_api message of performative={} in dialogue={}.".format( contract_api_msg.performative, contract_api_dialogue, ) ) class SigningHandler(Handler): """Implement the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_MESSAGE: self._handle_signed_message(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_message( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a signed message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ fipa_dialogue = ( signing_dialogue.associated_contract_api_dialogue.associated_fipa_dialogue ) last_fipa_msg = fipa_dialogue.last_incoming_message if last_fipa_msg is None: # pragma: nocover raise ValueError("Could not retrieve last fipa message.") inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.ACCEPT_W_INFORM, target_message=last_fipa_msg, info={"tx_signature": signing_msg.signed_message.body}, ) self.context.logger.info( "sending ACCEPT_W_INFORM to agent={}: tx_signature={}".format( last_fipa_msg.sender[-5:], signing_msg.signed_message, ) ) self.context.outbox.put_message(message=inform_msg) def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) class LedgerApiHandler(Handler): """Implement the ledger api handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/erc1155_client/skill.yaml ================================================ name: erc1155_client author: fetchai version: 0.29.6 type: skill description: The erc1155 client interacts with the erc1155 deployer to conduct an atomic swap. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmYpkQVUiWY7aWc8xN8FMzxKWSawHs2cyjWijzK6PDbsLX __init__.py: QmXJGNqfPNrhiwQoZZySRPGbP8dPgKnyQ3ZvRhrK11e3Ve behaviours.py: QmQohMmSi8PYAeyjGcusSaMbEAeSsVXGcVtS5XZjLgdPg1 dialogues.py: QmVFyavVzUv88AZGD8Wca4yyV4y9DU3Akw5M4RFxqnAnUm handlers.py: QmcxgzUjJyGUH5idHEfD8dn5mScTEzzMRoUhQ7ExwFQHud strategy.py: QmWtHkAyvkYZdHNZoc6r12cfJfruT1S9GXydeH9qUFDN7X fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: search: args: search_interval: 5 class_name: SearchBehaviour handlers: contract_api: args: {} class_name: ContractApiHandler fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: contract_api_dialogues: args: {} class_name: ContractApiDialogues default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: location: latitude: 51.5194 longitude: 0.127 search_query: constraint_type: == search_key: seller_service search_value: erc1155_contract search_radius: 5.0 class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/erc1155_client/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from typing import Any from aea.helpers.search.generic import SIMPLE_SERVICE_MODEL from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.skills.base import Model from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as CONTRACT_ID DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "seller_service", "search_value": "erc1155_contract", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) ledger_id = kwargs.pop("ledger_id", None) super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) self.is_searching = True self._contract_id = str(CONTRACT_ID) @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def contract_id(self) -> str: """Get the contract id.""" return self._contract_id def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType("distance", (self._agent_location, self._radius)) ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query def get_service_query(self) -> Query: """ Get the service query of the agent. :return: the query """ service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query([service_key_filter], model=SIMPLE_SERVICE_MODEL) return query ================================================ FILE: packages/fetchai/skills/erc1155_deploy/README.md ================================================ # ERC1155 Deploy ## Description This is a skill for selling data via a smart contract. This skill registers some data selling service on the sOEF. It can be requested (for example by an agent with the `generic_buyer` skill) to provide specific data. It then negotiates the price and delivers the data after it receives payment. ## Behaviours - `service_registration`: Deploys the smart contract, creates and mints tokens, registers `ERC1155 data selling service` on the sOEF ## Handlers - `contract_api`: handles `contract_api` messages for interactions with the smart contract - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger. - `oef_search`: handles `oef_search` messages if service registration on the sOEF is unsuccessful - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - Contract Deployment Guide ================================================ FILE: packages/fetchai/skills/erc1155_deploy/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the erc1155 deploy skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/erc1155_deploy:0.31.6") ================================================ FILE: packages/fetchai/skills/erc1155_deploy/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour of a erc1155 deploy skill AEA.""" from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.erc1155_deploy.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogues, OefSearchDialogues, ) from packages.fetchai.skills.erc1155_deploy.strategy import Strategy DEFAULT_SERVICES_INTERVAL = 30.0 DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class ServiceRegistrationBehaviour(TickerBehaviour): """This class implements a behaviour.""" def __init__(self, **kwargs: Any) -> None: """Initialise the behaviour.""" services_interval = kwargs.pop( "services_interval", DEFAULT_SERVICES_INTERVAL ) # type: int self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=services_interval, **kwargs) self.is_registered = False self.registration_in_progress = False self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """Implement the setup.""" self._request_balance() strategy = cast(Strategy, self.context.strategy) if not strategy.is_contract_deployed: self._request_contract_deploy_transaction() def act(self) -> None: """Implement the act.""" self._retry_failed_registration() strategy = cast(Strategy, self.context.strategy) if not strategy.is_behaviour_active: return if strategy.is_contract_deployed and not strategy.is_tokens_created: self._request_token_create_transaction() elif ( strategy.is_contract_deployed and strategy.is_tokens_created and not strategy.is_tokens_minted ): self._request_token_mint_transaction() elif ( strategy.is_contract_deployed and strategy.is_tokens_created and strategy.is_tokens_minted and not self.registration_in_progress and not self.is_registered ): self.registration_in_progress = True self._register_agent() def teardown(self) -> None: """Implement the task teardown.""" self._unregister_service() self._unregister_agent() def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _request_balance(self) -> None: """Request ledger balance.""" strategy = cast(Strategy, self.context.strategy) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=strategy.ledger_id, address=cast(str, self.context.agent_addresses.get(strategy.ledger_id)), ) self.context.outbox.put_message(message=ledger_api_msg) def _request_contract_deploy_transaction(self) -> None: """Request contract deploy transaction""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( {"deployer_address": self.context.agent_address, "gas": strategy.gas} ), ) contract_api_dialogue = cast( ContractApiDialogue, contract_api_dialogue, ) contract_api_dialogue.terms = strategy.get_deploy_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting contract deployment transaction...") def _request_token_create_transaction(self) -> None: """Request token create transaction.""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, contract_address=strategy.contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.context.agent_address, "token_ids": strategy.token_ids, "gas": strategy.gas, } ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_create_token_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting create batch transaction...") def _request_token_mint_transaction(self) -> None: """Request token mint transaction.""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, contract_address=strategy.contract_address, callable="get_mint_batch_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.context.agent_address, "recipient_address": self.context.agent_address, "token_ids": strategy.token_ids, "mint_quantities": strategy.mint_quantities, "gas": strategy.gas, } ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_mint_token_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting mint batch transaction...") def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """Register the agent's service.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_service_description() self._register(description, "registering agent's service on the SOEF.") def register_genus(self) -> None: """Register the agent's personality genus.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """Unregister service from the SOEF.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering service from SOEF.") def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") ================================================ FILE: packages/fetchai/skills/erc1155_deploy/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - Dialogue: The dialogue class maintains state of a dialogue and manages it. - Dialogues: The dialogues class keeps track of all dialogues. """ from typing import Any, Optional, Type from aea.common import Address from aea.exceptions import enforce from aea.helpers.search.models import Description from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue as BaseContractApiDialogue, ) from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage class ContractApiDialogue(BaseContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_terms",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get the terms.""" if self._terms is None: raise ValueError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set the terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class ContractApiDialogues(Model, BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=ContractApiDialogue, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class FipaDialogue(BaseFipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_proposal",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseFipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._proposal = None # type: Optional[Description] @property def proposal(self) -> Description: """Get the proposal.""" if self._proposal is None: raise ValueError("Proposal not set!") return self._proposal @proposal.setter def proposal(self, proposal: Description) -> None: """Set the proposal.""" enforce(self._proposal is None, "Proposal already set!") self._proposal = proposal class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseFipaDialogue.Role.SELLER BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_signing_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_signing_dialogue = None # type: Optional[SigningDialogue] @property def associated_signing_dialogue(self) -> "SigningDialogue": """Get the associated signing dialogue.""" if self._associated_signing_dialogue is None: raise ValueError("Associated signing dialogue not set!") return self._associated_signing_dialogue @associated_signing_dialogue.setter def associated_signing_dialogue( self, associated_signing_dialogue: "SigningDialogue" ) -> None: """Set the associated signing dialogue.""" enforce( self._associated_signing_dialogue is None, "Associated signing dialogue already set!", ) self._associated_signing_dialogue = associated_signing_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_contract_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_contract_api_dialogue = ( None ) # type: Optional[ContractApiDialogue] @property def associated_contract_api_dialogue(self) -> ContractApiDialogue: """Get the associated contract api dialogue.""" if self._associated_contract_api_dialogue is None: raise ValueError("Associated contract api dialogue not set!") return self._associated_contract_api_dialogue @associated_contract_api_dialogue.setter def associated_contract_api_dialogue( self, associated_contract_api_dialogue: ContractApiDialogue ) -> None: """Set the associated contract api dialogue.""" enforce( self._associated_contract_api_dialogue is None, "Associated contract api dialogue already set!", ) self._associated_contract_api_dialogue = associated_contract_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: packages/fetchai/skills/erc1155_deploy/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of the erc1155 deploy skill AEA.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_deploy.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.erc1155_deploy.dialogues import ( ContractApiDialogue, ContractApiDialogues, DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.erc1155_deploy.strategy import Strategy LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class FipaHandler(Handler): """This class implements a FIPA handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ fipa_msg = cast(FipaMessage, message) # recover dialogue fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return if fipa_msg.performative == FipaMessage.Performative.CFP: self._handle_cfp(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.ACCEPT_W_INFORM: self._handle_accept_w_inform(fipa_msg, fipa_dialogue) else: self._handle_invalid(fipa_msg, fipa_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. Respond to the sender with a default message containing the appropriate error information. :param fipa_msg: the message """ self.context.logger.info( "unidentified dialogue for message={}.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=fipa_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": fipa_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_cfp(self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle the CFP. If the CFP matches the supplied services then send a PROPOSE, otherwise send a DECLINE. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ strategy = cast(Strategy, self.context.strategy) self.context.logger.info( "received CFP from sender={}".format(fipa_msg.sender[-5:]) ) if not strategy.is_tokens_minted: self.context.logger.info("Contract items not minted yet. Try again later.") return # simply send the same proposal, independent of the query fipa_dialogue.proposal = strategy.get_proposal() proposal_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.PROPOSE, target_message=fipa_msg, proposal=fipa_dialogue.proposal, ) self.context.logger.info( "sending PROPOSE to agent={}: proposal={}".format( fipa_msg.sender[-5:], fipa_dialogue.proposal.values, ) ) self.context.outbox.put_message(message=proposal_msg) def _handle_accept_w_inform( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the ACCEPT_W_INFORM. If the ACCEPT_W_INFORM message contains the signed transaction, sign it too, otherwise do nothing. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ tx_signature = fipa_msg.info.get("tx_signature", None) if tx_signature is not None: self.context.logger.info( "received ACCEPT_W_INFORM from sender={}: tx_signature={}".format( fipa_msg.sender[-5:], tx_signature ) ) strategy = cast(Strategy, self.context.strategy) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, contract_address=strategy.contract_address, callable="get_atomic_swap_single_transaction", kwargs=ContractApiMessage.Kwargs( { "from_address": self.context.agent_address, "to_address": fipa_msg.sender, "token_id": int(fipa_dialogue.proposal.values["token_id"]), "from_supply": int( fipa_dialogue.proposal.values["from_supply"] ), "to_supply": int(fipa_dialogue.proposal.values["to_supply"]), "value": int(fipa_dialogue.proposal.values["value"]), "trade_nonce": int( fipa_dialogue.proposal.values["trade_nonce"] ), "signature": tx_signature, } ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_single_swap_terms( fipa_dialogue.proposal, fipa_msg.sender ) self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting single atomic swap transaction...") else: self.context.logger.info( "received ACCEPT_W_INFORM from sender={} with no signature.".format( fipa_msg.sender[-5:] ) ) def _handle_invalid( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle a fipa message of invalid performative. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle fipa message of performative={} in dialogue={}.".format( fipa_msg.performative, fipa_dialogue ) ) class LedgerApiHandler(Handler): """Implement the ledger api handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.outbox.put_message(message=msg) self.context.logger.info("requesting transaction receipt.") def _handle_transaction_receipt(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of transaction_receipt performative. :param ledger_api_msg: the ledger api message """ is_transaction_successful = LedgerApis.is_transaction_settled( ledger_api_msg.transaction_receipt.ledger_id, ledger_api_msg.transaction_receipt.receipt, ) if is_transaction_successful: self.context.logger.info( "transaction was successfully settled. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) strategy = cast(Strategy, self.context.strategy) if not strategy.is_contract_deployed: contract_address = cast( Optional[str], ledger_api_msg.transaction_receipt.receipt.get( "contractAddress", None ), ) if contract_address is None: raise ValueError("No contract address found.") # pragma: nocover strategy.contract_address = contract_address strategy.is_contract_deployed = is_transaction_successful strategy.is_behaviour_active = is_transaction_successful elif not strategy.is_tokens_created: strategy.is_tokens_created = is_transaction_successful strategy.is_behaviour_active = is_transaction_successful elif not strategy.is_tokens_minted: strategy.is_tokens_minted = is_transaction_successful strategy.is_behaviour_active = is_transaction_successful elif strategy.is_tokens_minted: self.context.is_active = False self.context.logger.info("demo finished!") else: # pragma: no cover self.context.logger.error("unexpected transaction receipt!") else: self.context.logger.error( "transaction failed. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class ContractApiHandler(Handler): """Implement the contract api handler.""" SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ contract_api_msg = cast(ContractApiMessage, message) # recover dialogue contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_dialogue = cast( Optional[ContractApiDialogue], contract_api_dialogues.update(contract_api_msg), ) if contract_api_dialogue is None: self._handle_unidentified_dialogue(contract_api_msg) return # handle message if ( contract_api_msg.performative is ContractApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(contract_api_msg, contract_api_dialogue) elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: self._handle_error(contract_api_msg, contract_api_dialogue) else: self._handle_invalid(contract_api_msg, contract_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue( self, contract_api_msg: ContractApiMessage ) -> None: """ Handle an unidentified dialogue. :param contract_api_msg: the message """ self.context.logger.info( "received invalid contract_api message={}, unidentified dialogue.".format( contract_api_msg ) ) def _handle_raw_transaction( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_transaction performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=contract_api_msg.raw_transaction, terms=contract_api_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of error performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received contract_api error message={} in dialogue={}.".format( contract_api_msg, contract_api_dialogue ) ) def _handle_invalid( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of invalid performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle contract_api message of performative={} in dialogue={}.".format( contract_api_msg.performative, contract_api_dialogue, ) ) class SigningHandler(Handler): """Implement the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, signed_transaction=signing_msg.signed_transaction, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): registration_behaviour.is_registered = True registration_behaviour.registration_in_progress = False self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) registration_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/erc1155_deploy/skill.yaml ================================================ name: erc1155_deploy author: fetchai version: 0.31.6 type: skill description: The ERC1155 deploy skill has the ability to deploy and interact with the smart contract. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmX98yCz4tVKF5dJUDUVh6T4vD3y9Gv42semKkpckqXjAA __init__.py: QmbtD65LEy39PpMtRMfXtR5D5JXALiuWNuTSoqoCpvDLsG behaviours.py: QmXxfA42Zi2eYgfRQEKDxcotw628KTTRPTCyeJv6aBJmuH dialogues.py: QmRsL56kWqrs6pf8MKBdU1bxrYpuwgt63yugtMYnjWmzvo handlers.py: QmeNBxsimbf9wjQ5Yar8NfUdzRKbpvVys3LA8Hdirk7QeZ strategy.py: QmR5ZEaGtBW57EJoGMVfKZGoPvFdzecwA4mmsWm1aomcB5 fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: service_registration: args: max_soef_registration_retries: 5 services_interval: 20 class_name: ServiceRegistrationBehaviour handlers: contract_api: args: {} class_name: ContractApiHandler fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: contract_api_dialogues: args: {} class_name: ContractApiDialogues default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: classification: piece: classification value: seller from_supply: 10 location: latitude: 51.5194 longitude: 0.127 mint_quantities: - 100 - 100 - 100 - 100 - 100 - 100 - 100 - 100 - 100 - 100 nb_tokens: 10 personality_data: piece: genus value: data service_data: key: seller_service value: erc1155_contract to_supply: 0 token_type: 2 value: 0 class_name: Strategy dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/erc1155_deploy/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import random # nosec from typing import Any, List from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, SIMPLE_SERVICE_MODEL, ) from aea.helpers.search.models import Description, Location from aea.helpers.transaction.base import Terms from aea.skills.base import Model from packages.fetchai.contracts.erc1155.contract import ERC1155Contract DEFAULT_IS_LEDGER_TX = True DEFAULT_NFT = 1 DEFAULT_FT = 2 DEFAULT_TOKEN_TYPE = DEFAULT_NFT DEFAULT_NB_TOKENS = 10 DEFAULT_MINT_QUANTITIES = [100, 100, 100, 100, 100, 100, 100, 100, 100, 100] DEFAULT_FROM_SUPPLY = 10 DEFAULT_TO_SUPPLY = 0 DEFAULT_VALUE = 0 DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_DATA = {"key": "seller_service", "value": "erc1155_contract"} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "seller"} DEFAULT_GAS = 5000000 class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """Initialize the strategy of the agent.""" ledger_id = kwargs.pop("ledger_id", None) self._token_type = kwargs.pop("token_type", DEFAULT_TOKEN_TYPE) enforce(self._token_type in [1, 2], "Token type must be 1 (NFT) or 2 (FT)") self._nb_tokens = kwargs.pop("nb_tokens", DEFAULT_NB_TOKENS) self._token_ids = kwargs.pop("token_ids", None) self._mint_quantities = kwargs.pop("mint_quantities", DEFAULT_MINT_QUANTITIES) enforce( len(self._mint_quantities) == self._nb_tokens, "Number of tokens must match mint quantities array size.", ) if self._token_type == 1: enforce( all(quantity == 1 for quantity in self._mint_quantities), "NFTs must have a quantity of 1", ) self._contract_address = kwargs.pop("contract_address", None) enforce( (self._token_ids is None and self._contract_address is None) or (self._token_ids is not None and self._contract_address is not None), "Either provide contract address and token ids or provide neither.", ) self.from_supply = kwargs.pop("from_supply", DEFAULT_FROM_SUPPLY) self.to_supply = kwargs.pop("to_supply", DEFAULT_TO_SUPPLY) self.value = kwargs.pop("value", DEFAULT_VALUE) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} self._simple_service_data = { self._set_service_data["key"]: self._set_service_data["value"] } self._gas = kwargs.pop("gas", DEFAULT_GAS) super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) self._contract_id = str(ERC1155Contract.contract_id) self.is_behaviour_active = True self._is_contract_deployed = self._contract_address is not None self._is_tokens_created = self._token_ids is not None self._is_tokens_minted = self._token_ids is not None if self._token_ids is None: self._token_ids = ERC1155Contract.generate_token_ids( token_type=self._token_type, nb_tokens=self._nb_tokens ) @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def contract_id(self) -> str: """Get the contract id.""" return self._contract_id @property def mint_quantities(self) -> List[int]: """Get the list of mint quantities.""" return self._mint_quantities @property def token_ids(self) -> List[int]: """Get the token ids.""" if self._token_ids is None: raise ValueError("Token ids not set.") return self._token_ids @property def contract_address(self) -> str: """Get the contract address.""" if self._contract_address is None: raise ValueError("Contract address not set!") return self._contract_address @contract_address.setter def contract_address(self, contract_address: str) -> None: """Set the contract address.""" enforce(self._contract_address is None, "Contract address already set!") self._contract_address = contract_address @property def is_contract_deployed(self) -> bool: """Get contract deploy status.""" return self._is_contract_deployed @is_contract_deployed.setter def is_contract_deployed(self, is_contract_deployed: bool) -> None: """Set contract deploy status.""" enforce( not self._is_contract_deployed and is_contract_deployed, "Only allowed to switch to true.", ) self._is_contract_deployed = is_contract_deployed @property def is_tokens_created(self) -> bool: """Get token created status.""" return self._is_tokens_created @is_tokens_created.setter def is_tokens_created(self, is_tokens_created: bool) -> None: """Set token created status.""" enforce( not self._is_tokens_created and is_tokens_created, "Only allowed to switch to true.", ) self._is_tokens_created = is_tokens_created @property def is_tokens_minted(self) -> bool: """Get token minted status.""" return self._is_tokens_minted @is_tokens_minted.setter def is_tokens_minted(self, is_tokens_minted: bool) -> None: """Set token minted status.""" enforce( not self._is_tokens_minted and is_tokens_minted, "Only allowed to switch to true.", ) self._is_tokens_minted = is_tokens_minted @property def gas(self) -> int: """Get gas.""" return self._gas def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_service_description(self) -> Description: """ Get the simple service description. :return: a description of the offered services """ description = Description( self._simple_service_data, data_model=SIMPLE_SERVICE_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description def get_deploy_terms(self) -> Terms: """ Get deploy terms of deployment. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) return terms def get_create_token_terms(self) -> Terms: """ Get create token terms of deployment. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) return terms def get_mint_token_terms(self) -> Terms: """ Get mint token terms of deployment. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) return terms def get_proposal(self) -> Description: """Get the proposal.""" trade_nonce = random.randrange( # nosec 0, 10000000 ) # quickfix, to avoid contract call token_id = self.token_ids[0] proposal = Description( { "contract_address": self.contract_address, "token_id": str(token_id), "trade_nonce": str(trade_nonce), "from_supply": str(self.from_supply), "to_supply": str(self.to_supply), "value": str(self.value), } ) return proposal def get_single_swap_terms( self, proposal: Description, counterparty_address: str ) -> Terms: """Get the proposal.""" terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=counterparty_address, amount_by_currency_id={ str(proposal.values["token_id"]): int(proposal.values["from_supply"]) - int(proposal.values["to_supply"]) }, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce=str(proposal.values["trade_nonce"]), fee_by_currency_id={}, ) return terms ================================================ FILE: packages/fetchai/skills/error/README.md ================================================ # Error ## Description The error skill is used to handle incoming envelops that cannot be properly handled by the framework. It handles the following cases: - AEA receives an envelope referencing an unsupported protocol, - AEA experiences a decoding error when reading an envelope, - AEA receives an envelope referencing a protocol for which no skill is active. ## Handlers - `error_handler`: handles `default` messages for problematic envelopes/messages. ================================================ FILE: packages/fetchai/skills/error/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the error skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/error:0.18.6") ================================================ FILE: packages/fetchai/skills/error/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the implementation of the handler for the 'default' protocol.""" import base64 from typing import Optional from aea.configurations.base import PublicId from aea.mail.base import Envelope from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage class ErrorHandler(Handler): """This class implements the error handler.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ def teardown(self) -> None: """Implement the handler teardown.""" def send_unsupported_protocol(self, envelope: Envelope) -> None: """ Handle the received envelope in case the protocol is not supported. :param envelope: the envelope """ self.context.logger.warning( "Unsupported protocol: {}. You might want to add a handler for this protocol.".format( envelope.protocol_specification_id ) ) encoded_protocol_specification_id = base64.b85encode( str.encode(str(envelope.protocol_specification_id)) ) encoded_envelope = base64.b85encode(envelope.encode()) reply = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="Unsupported protocol.", error_data={ "protocol_id": encoded_protocol_specification_id, "envelope": encoded_envelope, }, ) reply.sender = self.context.agent_address reply.to = envelope.sender self.context.outbox.put_message(message=reply) def send_decoding_error(self, envelope: Envelope) -> None: """ Handle a decoding error. :param envelope: the envelope """ self.context.logger.warning( "Decoding error for envelope: {}. protocol_specification_id='{}' and message='{!r}' are inconsistent.".format( envelope, envelope.protocol_specification_id, envelope.message ) ) encoded_envelope = base64.b85encode(envelope.encode()) reply = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.DECODING_ERROR, error_msg="Decoding error.", error_data={"envelope": encoded_envelope}, ) reply.sender = self.context.agent_address reply.to = envelope.sender self.context.outbox.put_message(message=reply) def send_unsupported_skill(self, envelope: Envelope) -> None: """ Handle the received envelope in case the skill is not supported. :param envelope: the envelope """ self.context.logger.warning( "Cannot handle envelope: no active handler registered for the protocol_specification_id='{}'.".format( envelope.protocol_specification_id ) ) encoded_envelope = base64.b85encode(envelope.encode()) reply = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.UNSUPPORTED_SKILL, error_msg="Unsupported skill.", error_data={"envelope": encoded_envelope}, ) reply.sender = self.context.agent_address reply.to = envelope.sender self.context.outbox.put_message(message=reply) ================================================ FILE: packages/fetchai/skills/error/skill.yaml ================================================ name: error author: fetchai version: 0.18.6 type: skill description: The error skill implements basic error handling required by all AEAs. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmNw5XkR9DfMycuD3j7dpGdvnyQEZTBRoYdraFiTXYTLkv __init__.py: QmPNutzpA7C3YzSNpnKYY6fRkXiyx8HwZW1CEjiPJ56CJv handlers.py: QmfEqAzS2PTcxrgh7crVrvuDVkjF3Fa2GndRyUu8UPUSq4 fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/default:1.1.7 skills: [] behaviours: {} handlers: error_handler: args: {} class_name: ErrorHandler models: {} dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/error_test_skill/README.md ================================================ # Error Test Skill ## Description Skill raises an exception on behaviour's act ## Behaviours - RaiseError - just raises an error on act ## Handlers ## Links ================================================ FILE: packages/fetchai/skills/error_test_skill/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains an example of skill raises exception for an AEA.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/error_test_skill:0.1.2") ================================================ FILE: packages/fetchai/skills/error_test_skill/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours for the 'error_test' skill.""" from aea.skills.behaviours import TickerBehaviour class RaiseErrorBehaviour(TickerBehaviour): """Echo behaviour.""" def setup(self) -> None: """Set up the behaviour.""" self.context.logger.info("Raise Error Behaviour: setup method called.") def act(self) -> None: """Act according to the behaviour.""" self.context.logger.info("Raise Error Behaviour: act method called.") raise Exception("Expected exception!") def teardown(self) -> None: """Teardown the behaviour.""" self.context.logger.info("Raise Error Behaviour: teardown method called.") ================================================ FILE: packages/fetchai/skills/error_test_skill/skill.yaml ================================================ name: error_test_skill author: fetchai version: 0.1.2 type: skill description: The error test skil for testing. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmWt4ZwTxErR6E8jhJGNoL7aJ2Re1W9WjZ9J13gh2xzKeX __init__.py: QmeCKAnqeT1GdRGtj8sJBvUw5BnP6aVDzpDdsCSGj3fZCP behaviours.py: Qmcfd3fEUiKgSPcXpeTMtCojfjpGBc9LjRwx3X22TsMxKW fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: [] skills: [] behaviours: raiseerror: args: tick_interval: 1.0 class_name: RaiseErrorBehaviour handlers: {} models: {} dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/fetch_block/README.md ================================================ # Fetch Block ## Description This skill is used to get the latest block data from the Fetch ledger. ## Behaviours - `fetch_block_behaviour`: requests latest block data every `tick_interval` seconds from the REST endpoint for the FetchAI ledger. ## Handlers - `http`: handles incoming `http` messages, retrieves the block data from the appropriate response, and stores it in shared state under the key: `block_data`. ================================================ FILE: packages/fetchai/skills/fetch_block/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the FetchBlock skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/fetch_block:0.12.6") ================================================ FILE: packages/fetchai/skills/fetch_block/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour to get the latest block from the Fetch ledger.""" from typing import Any, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import CONNECTION_ID as LEDGER_API_ADDRESS from packages.fetchai.protocols.ledger_api.custom_types import Kwargs from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.skills.fetch_block.dialogues import LedgerApiDialogues class FetchBlockBehaviour(TickerBehaviour): """This class provides a behaviour to get the latest block from the Fetch ledger.""" def __init__(self, **kwargs: Any) -> None: """Initialize the fetch block behaviour.""" super().__init__(**kwargs) def _get_block(self) -> None: """Request the latest block by sending a message to the ledger API.""" ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=LedgerApiMessage.Performative.GET_STATE, ledger_id=self.context.default_ledger_id, callable="blocks", args=("latest",), kwargs=Kwargs({}), ) self.context.outbox.put_message(message=ledger_api_msg) def setup(self) -> None: """Implement the setup.""" self.context.logger.info("setting up FetchBlockBehaviour") def act(self) -> None: """Implement the act.""" self.context.logger.info("Fetching latest block...") self._get_block() def teardown(self) -> None: """Implement the task teardown.""" self.context.logger.info("tearing down FetchBlockBehaviour") ================================================ FILE: packages/fetchai/skills/fetch_block/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains dialogues used by the fetch_block skill.""" from typing import Any, Type from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) ================================================ FILE: packages/fetchai/skills/fetch_block/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains handlers for the fetch_block skill.""" from typing import Any, Dict, Optional, cast from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.skills.fetch_block.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, ) class LedgerApiHandler(Handler): """Implement the ledger api handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ self.context.logger.info("Handling ledger api msg") ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.STATE: self._handle_state(ledger_api_msg) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) # pragma: nocover else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_state(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of state performative. :param ledger_api_msg: the ledger api message """ self.context.logger.debug(f"Handling ledger API message: {ledger_api_msg}") block_info = ledger_api_msg.state.body # type: Dict[str, Any] block_height_str = ( block_info.get("block", {}).get("header", {}).get("height", None) ) if block_height_str: block_height = int(block_height_str) # type: Optional[int] else: block_height = None # pragma: nocover if block_height is None: # pragma: nocover self.context.logger.info("block height not present") else: self.context.logger.info( "Retrieved latest block: " + str({"block_height": block_height}) ) self.context.shared_state["observation"] = {"block": block_info} def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( # pragma: nocover "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/fetch_block/skill.yaml ================================================ name: fetch_block author: fetchai version: 0.12.6 type: skill description: Retrieve the latest block from the Fetch ledger license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmNi2rZ6b8pgx5Uop4FMvNagGLbzHEJUruwFzbqHMHpYtd __init__.py: QmbjWVWAaMcaAiv9ynLGttHRbC5t3RGVEMUkctR2XXTkqS behaviours.py: Qmdg334UUoAyvcuqv2eVKAG42fMYzB4kqTjDcyBZsWtoYJ dialogues.py: Qma1KWoLRxPJMaxacGLbVEdkuvERG7UbmA4hT385KYww3A handlers.py: QmYhe8XfYmbrWxqZqFiCuiSnSFpiBkKRQKo62brchcAG1s fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/ledger_api:1.1.7 skills: [] behaviours: fetch_block_behaviour: args: tick_interval: 5 class_name: FetchBlockBehaviour handlers: ledger_api: args: {} class_name: LedgerApiHandler models: ledger_api_dialogues: args: {} class_name: LedgerApiDialogues dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/fipa_dummy_buyer/README.md ================================================ # Fipa Dummy Buyer ## Description A sample skill to interact with a simple seller using the `fetchai/fipa` protocol. ================================================ FILE: packages/fetchai/skills/fipa_dummy_buyer/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the fipa dummy buyer skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/fipa_dummy_buyer:0.3.6") ================================================ FILE: packages/fetchai/skills/fipa_dummy_buyer/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour for the fipa dummy buyer skill.""" from typing import Any, cast from aea.helpers.search.models import Constraint, ConstraintType, Query from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.skills.fipa_dummy_buyer.dialogues import FipaDialogues class FIPAInitializerBehaviour(TickerBehaviour): """Fipa buyer cfp initializer.""" def __init__(self, **kwargs: Any) -> None: """Init fipa behaviour.""" if "opponent_address" not in kwargs: raise ValueError("Opponent address has to be specified for behaviour!") self.opponent_address: str = cast(str, kwargs.pop("opponent_address", None)) self.is_enabled: bool = True super().__init__(**kwargs) def setup(self) -> None: """Implement the setup for the behaviour.""" def act(self) -> None: """ Implement the act. :return: None """ if not self.is_enabled: return dialogues = cast(FipaDialogues, self.context.fipa_dialogues) cfp_msg, _ = dialogues.create( counterparty=self.opponent_address, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) self.context.outbox.put_message(cfp_msg) self.context.logger.info("CFP message sent.") def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/fipa_dummy_buyer/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - FipaDialogue: The dialogue class maintains state of a dialogue of type fipa and manages it. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. """ from typing import Any from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues FipaDialogue = BaseFipaDialogue class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseFipaDialogue.Role.BUYER BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) ================================================ FILE: packages/fetchai/skills/fipa_dummy_buyer/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains handlers for the fipa dummy buyer skill.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.skills.fipa_dummy_buyer.dialogues import FipaDialogues class FipaBuyerHandler(Handler): """This class implements a FIPA handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """Handle an evelope.""" dialogues = cast(FipaDialogues, self.context.fipa_dialogues) if message.performative == FipaMessage.Performative.PROPOSE: buyer_dialogue = dialogues.update(message) if not buyer_dialogue: self.context.logger.error("error on propose message dialogue update") return # got a message, switch off initializer self.context.behaviours.initializer.is_enabled = False accept_msg = buyer_dialogue.reply( performative=FipaMessage.Performative.ACCEPT, target_message=message ) self.context.outbox.put_message(message=accept_msg) elif message.performative == FipaMessage.Performative.MATCH_ACCEPT: buyer_dialogue = dialogues.update(message) if not buyer_dialogue: self.context.logger.error( "error on MATCH_ACCEPT message dialogue update" ) return end_msg = buyer_dialogue.reply( performative=FipaMessage.Performative.END, target_message=message ) self.context.outbox.put_message(message=end_msg) self.context.logger.info("FIPA INTERACTION COMPLETE") else: self.context.logger.error( f"unsupported performative: {message.performative}" ) def teardown(self) -> None: """Implement the handler teardown.""" ================================================ FILE: packages/fetchai/skills/fipa_dummy_buyer/skill.yaml ================================================ name: fipa_dummy_buyer author: fetchai version: 0.3.6 type: skill description: Sample skill for FIPA interaction as a buyer. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmRvuk8JPFrir9kwiT1zRBWYZvYUBrjyqMurk2UMaDBABW __init__.py: QmU7MygHSwN2hqvoVvJmJpFBwLYRW3ouqLkbCA5ogtXMZ6 behaviours.py: QmZXfki7foJ6sjuqvbRUxAN5oiQR3LNnwx7zpMGPqtvRp9 dialogues.py: QmSEr25YWaUsk6pM2prJtJHQmjdM5ESwXc5JhHrcu8dVJc handlers.py: QmVv174YGzezt8twwkGuPz1dFAY9TNnXVvcJSdDvZVqVAL fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/fipa:1.1.7 skills: [] behaviours: initializer: args: opponent_address: opponent_address tick_interval: 5.0 class_name: FIPAInitializerBehaviour handlers: fipa: args: {} class_name: FipaBuyerHandler models: fipa_dialogues: args: {} class_name: FipaDialogues dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/generic_buyer/README.md ================================================ # Generic Buyer ## Description This is a generic skill for buying data. This skill finds an agent on the sOEF which sells data, requests specific data, negotiates the price, pays the proposed amount if agreement is reach, and receives the data bought. ## Behaviours - `search`: searches for data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - Generic Skills - Generic Skill Step by Step Guide ================================================ FILE: packages/fetchai/skills/generic_buyer/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the generic buyer skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/generic_buyer:0.27.6") ================================================ FILE: packages/fetchai/skills/generic_buyer/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour for the generic buyer skill.""" from typing import Any, List, Optional, Set, cast from aea.protocols.dialogue.base import DialogueLabel from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogue, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy DEFAULT_MAX_PROCESSING = 120 DEFAULT_TX_INTERVAL = 2.0 DEFAULT_SEARCH_INTERVAL = 5.0 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericSearchBehaviour(TickerBehaviour): """This class implements a search behaviour.""" def __init__(self, **kwargs: Any): """Initialize the search behaviour.""" search_interval = cast( float, kwargs.pop("search_interval", DEFAULT_SEARCH_INTERVAL) ) super().__init__(tick_interval=search_interval, **kwargs) def setup(self) -> None: """Implement the setup for the behaviour.""" strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx: ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=strategy.ledger_id, address=cast(str, self.context.agent_addresses.get(strategy.ledger_id)), ) self.context.outbox.put_message(message=ledger_api_msg) else: strategy.is_searching = True def act(self) -> None: """Implement the act.""" strategy = cast(GenericStrategy, self.context.strategy) if not strategy.is_searching: return transaction_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) remaining_transactions_count = len(transaction_behaviour.waiting) if remaining_transactions_count > 0: self.context.logger.info( f"Transaction behaviour has {remaining_transactions_count} transactions remaining. Skipping search!" ) return strategy.update_search_query_params() query = strategy.get_location_and_service_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) self.context.outbox.put_message(message=oef_search_msg) def teardown(self) -> None: """Implement the task teardown.""" class GenericTransactionBehaviour(TickerBehaviour): """A behaviour to sequentially submit transactions to the blockchain.""" def __init__(self, **kwargs: Any): """Initialize the transaction behaviour.""" tx_interval = cast( float, kwargs.pop("transaction_interval", DEFAULT_TX_INTERVAL) ) self.max_processing = cast( float, kwargs.pop("max_processing", DEFAULT_MAX_PROCESSING) ) self.processing_time = 0.0 self.waiting: List[FipaDialogue] = [] self.processing: Optional[LedgerApiDialogue] = None self.timedout: Set[DialogueLabel] = set() super().__init__(tick_interval=tx_interval, **kwargs) def setup(self) -> None: """Setup behaviour.""" def act(self) -> None: """Implement the act.""" if self.processing is not None: if self.processing_time <= self.max_processing: # already processing self.processing_time += self.tick_interval return self._timeout_processing() if len(self.waiting) == 0: # nothing to process return self._start_processing() def _start_processing(self) -> None: """Process the next transaction.""" fipa_dialogue = self.waiting.pop(0) self.context.logger.info( f"Processing transaction, {len(self.waiting)} transactions remaining" ) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, terms=fipa_dialogue.terms, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.processing_time = 0.0 self.processing = ledger_api_dialogue self.context.logger.info( f"requesting transfer transaction from ledger api for message={ledger_api_msg}..." ) self.context.outbox.put_message(message=ledger_api_msg) def teardown(self) -> None: """Teardown behaviour.""" def _timeout_processing(self) -> None: """Timeout processing.""" if self.processing is None: return self.timedout.add(self.processing.dialogue_label) self.waiting.append(self.processing.associated_fipa_dialogue) self.processing_time = 0.0 self.processing = None def finish_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Finish processing. :param ledger_api_dialogue: the ledger api dialogue """ if self.processing == ledger_api_dialogue: self.processing_time = 0.0 self.processing = None return if ledger_api_dialogue.dialogue_label not in self.timedout: raise ValueError( f"Non-matching dialogues in transaction behaviour: {self.processing} and {ledger_api_dialogue}" ) self.timedout.remove(ledger_api_dialogue.dialogue_label) self.context.logger.debug( f"Timeout dialogue in transaction processing: {ledger_api_dialogue}" ) # don't reset, as another might be processing def failed_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Failed processing. Currently, we retry processing indefinitely. :param ledger_api_dialogue: the ledger api dialogue """ self.finish_processing(ledger_api_dialogue) self.waiting.append(ledger_api_dialogue.associated_fipa_dialogue) ================================================ FILE: packages/fetchai/skills/generic_buyer/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - FipaDialogue: The dialogue class maintains state of a dialogue of type fipa and manages it. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogue: The dialogue class maintains state of a dialogue of type ledger_api and manages it. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. """ from typing import Any, Optional, Type from aea.common import Address from aea.exceptions import AEAEnforceError, enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class FipaDialogue(BaseFipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ( "_terms", "_associated_ledger_api_dialogue", ) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseFipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get terms.""" if self._terms is None: raise AEAEnforceError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseFipaDialogue.Role.BUYER BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_fipa_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_fipa_dialogue = None # type: Optional[FipaDialogue] @property def associated_fipa_dialogue(self) -> FipaDialogue: """Get associated_fipa_dialogue.""" if self._associated_fipa_dialogue is None: raise AEAEnforceError("FipaDialogue not set!") return self._associated_fipa_dialogue @associated_fipa_dialogue.setter def associated_fipa_dialogue(self, fipa_dialogue: FipaDialogue) -> None: """Set associated_fipa_dialogue""" enforce(self._associated_fipa_dialogue is None, "FipaDialogue already set!") self._associated_fipa_dialogue = fipa_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_ledger_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_ledger_api_dialogue = None # type: Optional[LedgerApiDialogue] @property def associated_ledger_api_dialogue(self) -> LedgerApiDialogue: """Get associated_ledger_api_dialogue.""" if self._associated_ledger_api_dialogue is None: raise AEAEnforceError("LedgerApiDialogue not set!") return self._associated_ledger_api_dialogue @associated_ledger_api_dialogue.setter def associated_ledger_api_dialogue( self, ledger_api_dialogue: LedgerApiDialogue ) -> None: """Set associated_ledger_api_dialogue""" enforce( self._associated_ledger_api_dialogue is None, "LedgerApiDialogue already set!", ) self._associated_ledger_api_dialogue = ledger_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: packages/fetchai/skills/generic_buyer/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains handlers for the generic buyer skill.""" import pprint from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.generic_buyer.behaviours import GenericTransactionBehaviour from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericFipaHandler(Handler): """This class implements a FIPA handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ fipa_msg = cast(FipaMessage, message) # recover dialogue fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return # handle message if fipa_msg.performative == FipaMessage.Performative.PROPOSE: self._handle_propose(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.DECLINE: self._handle_decline(fipa_msg, fipa_dialogue, fipa_dialogues) elif fipa_msg.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM: self._handle_match_accept(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.INFORM: self._handle_inform(fipa_msg, fipa_dialogue, fipa_dialogues) else: self._handle_invalid(fipa_msg, fipa_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. :param fipa_msg: the message """ self.context.logger.info( "received invalid fipa message={}, unidentified dialogue.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=fipa_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": fipa_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_propose( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the propose. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received proposal={} from sender={}".format( fipa_msg.proposal.values, fipa_msg.sender[-5:], ) ) strategy = cast(GenericStrategy, self.context.strategy) acceptable = strategy.is_acceptable_proposal(fipa_msg.proposal) affordable = strategy.is_affordable_proposal(fipa_msg.proposal) if acceptable and affordable: self.context.logger.info( "accepting the proposal from sender={}".format(fipa_msg.sender[-5:]) ) terms = strategy.terms_from_proposal(fipa_msg.proposal, fipa_msg.sender) fipa_dialogue.terms = terms accept_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.ACCEPT, target_message=fipa_msg, ) self.context.outbox.put_message(message=accept_msg) else: self.context.logger.info( "declining the proposal from sender={}".format(fipa_msg.sender[-5:]) ) decline_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=fipa_msg, ) self.context.outbox.put_message(message=decline_msg) def _handle_decline( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue, fipa_dialogues: FipaDialogues, ) -> None: """ Handle the decline. :param fipa_msg: the message :param fipa_dialogue: the fipa dialogue :param fipa_dialogues: the fipa dialogues """ self.context.logger.info( "received DECLINE from sender={}".format(fipa_msg.sender[-5:]) ) target_message = fipa_dialogue.get_message_by_id(fipa_msg.target) if not target_message: raise ValueError("Can not find target message!") # pragma: nocover declined_performative = target_message.performative if declined_performative == FipaMessage.Performative.CFP: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_CFP, fipa_dialogue.is_self_initiated ) if declined_performative == FipaMessage.Performative.ACCEPT: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) def _handle_match_accept( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the match accept. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received MATCH_ACCEPT_W_INFORM from sender={} with info={}".format( fipa_msg.sender[-5:], fipa_msg.info ) ) strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx: transfer_address = fipa_msg.info.get("address", None) if transfer_address is not None and isinstance(transfer_address, str): fipa_dialogue.terms.counterparty_address = ( # pragma: nocover transfer_address ) tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.waiting.append(fipa_dialogue) else: inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=fipa_msg, info={"Done": "Sending payment via bank transfer"}, ) self.context.outbox.put_message(message=inform_msg) self.context.logger.info( "informing counterparty={} of payment.".format(fipa_msg.sender[-5:]) ) def _handle_inform( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue, fipa_dialogues: FipaDialogues, ) -> None: """ Handle the match inform. :param fipa_msg: the message :param fipa_dialogue: the fipa dialogue :param fipa_dialogues: the fipa dialogues """ self.context.logger.info( "received INFORM from sender={}".format(fipa_msg.sender[-5:]) ) if len(fipa_msg.info.keys()) >= 1: data = fipa_msg.info data_string = pprint.pformat(data)[:1000] self.context.logger.info(f"received the following data={data_string}") fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.SUCCESSFUL, fipa_dialogue.is_self_initiated ) strategy = cast(GenericStrategy, self.context.strategy) strategy.successful_trade_with_counterparty(fipa_msg.sender, data) else: self.context.logger.info( "received no data from sender={}".format(fipa_msg.sender[-5:]) ) def _handle_invalid( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle a fipa message of invalid performative. :param fipa_msg: the message :param fipa_dialogue: the fipa dialogue """ self.context.logger.warning( "cannot handle fipa message of performative={} in dialogue={}.".format( fipa_msg.performative, fipa_dialogue ) ) class GenericOefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_msg, oef_search_dialogue ) ) def _handle_search( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle the search response. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ if len(oef_search_msg.agents) == 0: self.context.logger.info( f"found no agents in dialogue={oef_search_dialogue}, continue searching." ) return strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_stop_searching_on_result: self.context.logger.info( "found agents={}, stopping search.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) strategy.is_searching = False # stopping search else: self.context.logger.info( "found agents={}.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) query = strategy.get_service_query() fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) counterparties = strategy.get_acceptable_counterparties(oef_search_msg.agents) for counterparty in counterparties: cfp_msg, _ = fipa_dialogues.create( counterparty=counterparty, performative=FipaMessage.Performative.CFP, query=query, ) self.context.outbox.put_message(message=cfp_msg) self.context.logger.info( "sending CFP to agent={}".format(counterparty[-5:]) ) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) class GenericSigningHandler(Handler): """Implement the signing handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue last_ledger_api_msg = ledger_api_dialogue.last_incoming_message if last_ledger_api_msg is None: raise ValueError("Could not retrieve last message in ledger api dialogue") ledger_api_msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, target_message=last_ledger_api_msg, signed_transaction=signing_msg.signed_transaction, ) self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) signing_msg_ = cast( Optional[SigningMessage], signing_dialogue.last_outgoing_message ) if ( signing_msg_ is not None and signing_msg_.performative == SigningMessage.Performative.SIGN_TRANSACTION ): tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) class GenericLedgerApiHandler(Handler): """Implement the ledger handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ strategy = cast(GenericStrategy, self.context.strategy) if ledger_api_msg.balance > 0: self.context.logger.info( "starting balance on {} ledger={}.".format( strategy.ledger_id, ledger_api_msg.balance, ) ) strategy.balance = ledger_api_msg.balance strategy.is_searching = True else: self.context.logger.warning( f"you have no starting balance on {strategy.ledger_id} ledger! Stopping skill {self.skill_id}." ) self.context.is_active = False def _handle_raw_transaction( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of raw_transaction performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(ledger_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=ledger_api_msg.raw_transaction, terms=ledger_api_dialogue.associated_fipa_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) ledger_api_msg_ = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.logger.info("checking transaction is settled.") self.context.outbox.put_message(message=ledger_api_msg_) def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ fipa_dialogue = ledger_api_dialogue.associated_fipa_dialogue is_settled = LedgerApis.is_transaction_settled( fipa_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.receipt ) tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) if is_settled: tx_behaviour.finish_processing(ledger_api_dialogue) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ledger_api_msg_ is None: raise ValueError( # pragma: nocover "Could not retrieve last ledger_api message" ) fipa_msg = cast(Optional[FipaMessage], fipa_dialogue.last_incoming_message) if fipa_msg is None: raise ValueError("Could not retrieve last fipa message") inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=fipa_msg, info={"transaction_digest": ledger_api_msg_.transaction_digest.body}, ) self.context.outbox.put_message(message=inform_msg) self.context.logger.info( "transaction confirmed, informing counterparty={} of transaction digest.".format( fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], ) ) else: tx_behaviour.failed_processing(ledger_api_dialogue) self.context.logger.info( "transaction_receipt={} not settled or not valid, aborting".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ( ledger_api_msg_ is not None and ledger_api_msg_.performative != LedgerApiMessage.Performative.GET_BALANCE ): tx_behaviour = cast( GenericTransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/generic_buyer/skill.yaml ================================================ name: generic_buyer author: fetchai version: 0.27.6 type: skill description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmRUURLxvnfRR8Qq1UBeecFRoKiJLxQJj5GtUyKP7SyhTu __init__.py: QmYCvgy81AT3SjWrUKCnjAnmdenDCrdFRE91BhW8tBuLDL behaviours.py: QmVydJUVMEG4o2WNFdPN1bo8Rv46c7Mpt8K5jQkF2fpPLz dialogues.py: QmZ8yqZRJ8KhFXcfA5H7XWBTyqZf9tCyCR22HVUpfb6aJs handlers.py: QmQfGNxqDncaP4ekRKZvjE4SE8P5KSLGFDSLSQbWbM3xaA strategy.py: QmWZfWVGpbxuZTWPigAzK6mrrHfgwEbsvf41SNq9FggpKr fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: search: args: search_interval: 5 class_name: GenericSearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: GenericTransactionBehaviour handlers: fipa: args: {} class_name: GenericFipaHandler ledger_api: args: {} class_name: GenericLedgerApiHandler oef_search: args: {} class_name: GenericOefSearchHandler signing: args: {} class_name: GenericSigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_quantity: 100 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 service_id: generic_service stop_searching_on_result: true class_name: GenericStrategy dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: true ================================================ FILE: packages/fetchai/skills/generic_buyer/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from typing import Any, Dict, List, Tuple from aea.common import Address from aea.exceptions import enforce from aea.helpers.search.generic import SIMPLE_SERVICE_MODEL from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.helpers.transaction.base import Terms from aea.skills.base import Model DEFAULT_IS_LEDGER_TX = True DEFAULT_MAX_UNIT_PRICE = 5 DEFAULT_MAX_TX_FEE = 2 DEFAULT_SERVICE_ID = "generic_service" DEFAULT_MIN_QUANTITY = 1 DEFAULT_MAX_QUANTITY = 100 DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "seller_service", "search_value": "generic_service", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 DEFAULT_MAX_NEGOTIATIONS = 2 class GenericStrategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ ledger_id = kwargs.pop("ledger_id", None) currency_id = kwargs.pop("currency_id", None) self._is_ledger_tx = kwargs.pop("is_ledger_tx", DEFAULT_IS_LEDGER_TX) self._max_unit_price = kwargs.pop("max_unit_price", DEFAULT_MAX_UNIT_PRICE) self._min_quantity = kwargs.pop("min_quantity", DEFAULT_MIN_QUANTITY) self._max_quantity = kwargs.pop("max_quantity", DEFAULT_MAX_QUANTITY) self._max_tx_fee = kwargs.pop("max_tx_fee", DEFAULT_MAX_TX_FEE) self._service_id = kwargs.pop("service_id", DEFAULT_SERVICE_ID) self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self._max_negotiations = kwargs.pop( "max_negotiations", DEFAULT_MAX_NEGOTIATIONS ) self._is_stop_searching_on_result = kwargs.pop("stop_searching_on_result", True) super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) if currency_id is None: currency_id = self.context.currency_denominations.get(self._ledger_id, None) enforce( currency_id is not None, f"Currency denomination for ledger_id={self._ledger_id} not specified.", ) self._currency_id = currency_id self._is_searching = False self._balance = 0 @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def is_ledger_tx(self) -> bool: """Check whether or not tx are settled on a ledger.""" return self._is_ledger_tx @property def is_stop_searching_on_result(self) -> bool: """Check if search is stopped on result.""" return self._is_stop_searching_on_result @property def is_searching(self) -> bool: """Check if the agent is searching.""" return self._is_searching @is_searching.setter def is_searching(self, is_searching: bool) -> None: """Check if the agent is searching.""" enforce(isinstance(is_searching, bool), "Can only set bool on is_searching!") self._is_searching = is_searching @property def balance(self) -> int: """Get the balance.""" return self._balance @balance.setter def balance(self, balance: int) -> None: """Set the balance.""" self._balance = balance @property def max_negotiations(self) -> int: """Get the maximum number of negotiations the agent can start.""" return self._max_negotiations def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType("distance", (self._agent_location, self._radius)) ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query def get_service_query(self) -> Query: """ Get the service query of the agent. :return: the query """ service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query([service_key_filter], model=SIMPLE_SERVICE_MODEL) return query def is_acceptable_proposal(self, proposal: Description) -> bool: """ Check whether it is an acceptable proposal. :param proposal: a description :return: whether it is acceptable """ result = ( all( key in proposal.values for key in [ "ledger_id", "currency_id", "price", "service_id", "quantity", "tx_nonce", ] ) and proposal.values["ledger_id"] == self.ledger_id and proposal.values["price"] > 0 and proposal.values["quantity"] >= self._min_quantity and proposal.values["quantity"] <= self._max_quantity and proposal.values["price"] <= proposal.values["quantity"] * self._max_unit_price and proposal.values["currency_id"] == self._currency_id and proposal.values["service_id"] == self._service_id and isinstance(proposal.values["tx_nonce"], str) and proposal.values["tx_nonce"] != "" ) return result def is_affordable_proposal(self, proposal: Description) -> bool: """ Check whether it is an affordable proposal. :param proposal: a description :return: whether it is affordable """ if self.is_ledger_tx: payable = proposal.values.get("price", 0) + self._max_tx_fee result = self.balance >= payable else: result = True return result def get_acceptable_counterparties( self, counterparties: Tuple[str, ...] ) -> Tuple[str, ...]: """ Process counterparties and drop unacceptable ones. :param counterparties: a tuple of counterparties :return: list of counterparties """ valid_counterparties: List[str] = [] for idx, counterparty in enumerate(counterparties): if idx < self.max_negotiations: valid_counterparties.append(counterparty) return tuple(valid_counterparties) def terms_from_proposal( self, proposal: Description, counterparty_address: Address ) -> Terms: """ Get the terms from a proposal. :param proposal: the proposal :param counterparty_address: the counterparty :return: terms """ buyer_address = self.context.agent_addresses[proposal.values["ledger_id"]] terms = Terms( ledger_id=proposal.values["ledger_id"], sender_address=buyer_address, counterparty_address=counterparty_address, amount_by_currency_id={ proposal.values["currency_id"]: -proposal.values["price"] }, quantities_by_good_id={ proposal.values["service_id"]: proposal.values["quantity"] }, is_sender_payable_tx_fee=True, nonce=proposal.values["tx_nonce"], fee_by_currency_id={proposal.values["currency_id"]: self._max_tx_fee}, ) return terms def successful_trade_with_counterparty( self, counterparty: str, data: Dict[str, str] ) -> None: """ Do something on successful trade. :param counterparty: the counterparty address :param data: the data """ def update_search_query_params(self) -> None: """Update agent location and query for search.""" ================================================ FILE: packages/fetchai/skills/generic_seller/README.md ================================================ # Generic Seller ## Description This is a generic skill for selling data. This skill registers some data selling service on the sOEF. It can be requested (for example by an agent with the `generic_buyer` skill) to provide specific data. It then negotiates the price and delivers the data after it receives payment. ## Behaviours - `service_registration`: registers data selling service on the sOEF ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages if service registration on the sOEF is unsuccessful ## Links - Generic Skills - Generic Skill Step by Step Guide ================================================ FILE: packages/fetchai/skills/generic_seller/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the generic seller skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/generic_seller:0.28.6") ================================================ FILE: packages/fetchai/skills/generic_seller/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviour of a generic seller AEA.""" from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_seller.dialogues import ( LedgerApiDialogues, OefSearchDialogues, ) from packages.fetchai.skills.generic_seller.strategy import GenericStrategy DEFAULT_SERVICES_INTERVAL = 60.0 DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericServiceRegistrationBehaviour(TickerBehaviour): """This class implements a behaviour.""" def __init__(self, **kwargs: Any): """Initialise the behaviour.""" services_interval = kwargs.pop( "services_interval", DEFAULT_SERVICES_INTERVAL ) # type: int self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=services_interval, **kwargs) self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """Implement the setup.""" strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx: ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=strategy.ledger_id, address=cast(str, self.context.agent_addresses.get(strategy.ledger_id)), ) self.context.outbox.put_message(message=ledger_api_msg) self._register_agent() def act(self) -> None: """Implement the act.""" self._retry_failed_registration() def teardown(self) -> None: """Implement the task teardown.""" self._unregister_service() self._unregister_agent() def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """Register the agent's service.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_register_service_description() self._register(description, "registering agent's service on the SOEF.") def register_genus(self) -> None: """Register the agent's personality genus.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """Unregister service from the SOEF.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering service from SOEF.") def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" strategy = cast(GenericStrategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") ================================================ FILE: packages/fetchai/skills/generic_seller/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - Dialogue: The dialogue class maintains state of a dialogue and manages it. - Dialogues: The dialogues class keeps track of all dialogues. """ from typing import Any, Dict, Optional, Type from aea.common import Address from aea.exceptions import AEAEnforceError, enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class FipaDialogue(BaseFipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("data_for_sale", "_terms") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseFipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self.data_for_sale = None # type: Optional[Dict[str, str]] self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get terms.""" if self._terms is None: raise AEAEnforceError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.SELLER BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_fipa_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_fipa_dialogue = None # type: Optional[FipaDialogue] @property def associated_fipa_dialogue(self) -> FipaDialogue: """Get associated_fipa_dialogue.""" if self._associated_fipa_dialogue is None: raise AEAEnforceError("FipaDialogue not set!") return self._associated_fipa_dialogue @associated_fipa_dialogue.setter def associated_fipa_dialogue(self, fipa_dialogue: FipaDialogue) -> None: """Set associated_fipa_dialogue""" enforce(self._associated_fipa_dialogue is None, "FipaDialogue already set!") self._associated_fipa_dialogue = fipa_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/generic_seller/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of a generic seller AEA.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import TransactionDigest from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) from packages.fetchai.skills.generic_seller.dialogues import ( DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.generic_seller.strategy import GenericStrategy LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class GenericFipaHandler(Handler): """This class implements a FIPA handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ fipa_msg = cast(FipaMessage, message) # recover dialogue fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return # handle message if fipa_msg.performative == FipaMessage.Performative.CFP: self._handle_cfp(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.DECLINE: self._handle_decline(fipa_msg, fipa_dialogue, fipa_dialogues) elif fipa_msg.performative == FipaMessage.Performative.ACCEPT: self._handle_accept(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.INFORM: self._handle_inform(fipa_msg, fipa_dialogue) else: self._handle_invalid(fipa_msg, fipa_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. :param fipa_msg: the message """ self.context.logger.info( "received invalid fipa message={}, unidentified dialogue.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=fipa_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": fipa_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_cfp(self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle the CFP. If the CFP matches the supplied services then send a PROPOSE, otherwise send a DECLINE. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received CFP from sender={}".format(fipa_msg.sender[-5:]) ) strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_matching_supply(fipa_msg.query): proposal, terms, data_for_sale = strategy.generate_proposal_terms_and_data( fipa_msg.query, fipa_msg.sender ) fipa_dialogue.data_for_sale = data_for_sale fipa_dialogue.terms = terms self.context.logger.info( "sending a PROPOSE with proposal={} to sender={}".format( proposal.values, fipa_msg.sender[-5:] ) ) proposal_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.PROPOSE, target_message=fipa_msg, proposal=proposal, ) self.context.outbox.put_message(message=proposal_msg) else: self.context.logger.info( "declined the CFP from sender={}".format(fipa_msg.sender[-5:]) ) decline_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=fipa_msg, ) self.context.outbox.put_message(message=decline_msg) def _handle_decline( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue, fipa_dialogues: FipaDialogues, ) -> None: """ Handle the DECLINE. Close the dialogue. :param fipa_msg: the message :param fipa_dialogue: the dialogue object :param fipa_dialogues: the dialogues object """ self.context.logger.info( "received DECLINE from sender={}".format(fipa_msg.sender[-5:]) ) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) def _handle_accept( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the ACCEPT. Respond with a MATCH_ACCEPT_W_INFORM which contains the address to send the funds to. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received ACCEPT from sender={}".format(fipa_msg.sender[-5:]) ) info = {"address": fipa_dialogue.terms.sender_address} match_accept_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, target_message=fipa_msg, info=info, ) self.context.logger.info( "sending MATCH_ACCEPT_W_INFORM to sender={} with info={}".format( fipa_msg.sender[-5:], info, ) ) self.context.outbox.put_message(message=match_accept_msg) def _handle_inform( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle the INFORM. If the INFORM message contains the transaction_digest then verify that it is settled, otherwise do nothing. If the transaction is settled, send the data, otherwise do nothing. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.info( "received INFORM from sender={}".format(fipa_msg.sender[-5:]) ) strategy = cast(GenericStrategy, self.context.strategy) if strategy.is_ledger_tx and "transaction_digest" in fipa_msg.info.keys(): self.context.logger.info( "checking whether transaction={} has been received ...".format( fipa_msg.info["transaction_digest"] ) ) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, transaction_digest=TransactionDigest( fipa_dialogue.terms.ledger_id, fipa_msg.info["transaction_digest"] ), ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.outbox.put_message(message=ledger_api_msg) elif strategy.is_ledger_tx: self.context.logger.warning( "did not receive transaction digest from sender={}.".format( fipa_msg.sender[-5:] ) ) elif not strategy.is_ledger_tx and "Done" in fipa_msg.info.keys(): inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=fipa_msg, info=fipa_dialogue.data_for_sale, ) self.context.outbox.put_message(message=inform_msg) fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.SUCCESSFUL, fipa_dialogue.is_self_initiated ) self.context.logger.info( "transaction confirmed, sending data={} to buyer={}.".format( fipa_dialogue.data_for_sale, fipa_msg.sender[-5:], ) ) else: self.context.logger.warning( "did not receive transaction confirmation from sender={}.".format( fipa_msg.sender[-5:] ) ) def _handle_invalid( self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle a fipa message of invalid performative. :param fipa_msg: the message :param fipa_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle fipa message of performative={} in dialogue={}.".format( fipa_msg.performative, fipa_dialogue ) ) class GenericLedgerApiHandler(Handler): """Implement the ledger handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ fipa_dialogue = ledger_api_dialogue.associated_fipa_dialogue is_settled = LedgerApis.is_transaction_settled( fipa_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.receipt ) is_valid = LedgerApis.is_transaction_valid( fipa_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.transaction, fipa_dialogue.terms.sender_address, fipa_dialogue.terms.counterparty_address, fipa_dialogue.terms.nonce, fipa_dialogue.terms.counterparty_payable_amount, ) if is_settled and is_valid: last_message = cast( Optional[FipaMessage], fipa_dialogue.last_incoming_message ) if last_message is None: raise ValueError("Cannot retrieve last fipa message.") inform_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.INFORM, target_message=last_message, info=fipa_dialogue.data_for_sale, ) self.context.outbox.put_message(message=inform_msg) fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.SUCCESSFUL, fipa_dialogue.is_self_initiated ) self.context.logger.info( "transaction confirmed, sending data={} to buyer={}.".format( fipa_dialogue.data_for_sale, last_message.sender[-5:], ) ) else: self.context.logger.info( "transaction_receipt={} not settled or not valid, aborting".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class GenericOefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( GenericServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( GenericServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) registration_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/generic_seller/skill.yaml ================================================ name: generic_seller author: fetchai version: 0.28.6 type: skill description: The weather station skill implements the functionality to sell weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmfQM11g1zjQYDUmia3eFmeckab2FDBCGwG8QDRZfEG4RY __init__.py: QmSSfdZmyPsMVbLsS3bjTFL3CCGousHYM3QkJHw6AY1efL behaviours.py: QmNmZHrFPVninUyyNDWng6o9fBLZ2vJmVLRxP1fzMapLai dialogues.py: QmXcoYGuAdAD5HerA2khymJicPqf9VgtZ9YS6zx4mx8MWy handlers.py: QmaXoWxvFBMdtqhChLc3eaWPEsDpur3jwmfJ8Z8DkNrMaD strategy.py: QmZU5jR78iZ515rMdxUE5KiPRwo9UhDw4yXkZguzN8hnBd fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 skills: [] behaviours: service_registration: args: max_soef_registration_retries: 5 services_interval: 20 class_name: GenericServiceRegistrationBehaviour handlers: fipa: args: {} class_name: GenericFipaHandler ledger_api: args: {} class_name: GenericLedgerApiHandler oef_search: args: {} class_name: GenericOefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller data_for_sale: generic: data has_data_source: false is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: generic_service service_id: generic_service unit_price: 10 class_name: GenericStrategy dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: true ================================================ FILE: packages/fetchai/skills/generic_seller/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import uuid from typing import Any, Dict, Optional, Tuple from aea.common import Address from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, SIMPLE_SERVICE_MODEL, ) from aea.helpers.search.models import Description, Location, Query from aea.helpers.transaction.base import Terms from aea.skills.base import Model DEFAULT_IS_LEDGER_TX = True DEFAULT_UNIT_PRICE = 4 DEFAULT_SERVICE_ID = "generic_service" DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_DATA = {"key": "seller_service", "value": "generic_service"} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "seller"} DEFAULT_HAS_DATA_SOURCE = False DEFAULT_DATA_FOR_SALE = { "some_generic_data_key": "some_generic_data_value" } # type: Optional[Dict[str, Any]] class GenericStrategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ ledger_id = kwargs.pop("ledger_id", None) currency_id = kwargs.pop("currency_id", None) self._is_ledger_tx = kwargs.pop("is_ledger_tx", DEFAULT_IS_LEDGER_TX) self._unit_price = kwargs.pop("unit_price", DEFAULT_UNIT_PRICE) self._service_id = kwargs.pop("service_id", DEFAULT_SERVICE_ID) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} self._simple_service_data = { self._set_service_data["key"]: self._set_service_data["value"] } self._has_data_source = kwargs.pop("has_data_source", DEFAULT_HAS_DATA_SOURCE) data_for_sale_ordered = kwargs.pop("data_for_sale", DEFAULT_DATA_FOR_SALE) data_for_sale = { str(key): str(value) for key, value in data_for_sale_ordered.items() } super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) if currency_id is None: currency_id = self.context.currency_denominations.get(self._ledger_id, None) enforce( currency_id is not None, f"Currency denomination for ledger_id={self._ledger_id} not specified.", ) self._currency_id = currency_id enforce( self.context.agent_addresses.get(self._ledger_id, None) is not None, "Wallet does not contain cryptos for provided ledger id.", ) self._data_for_sale = data_for_sale @property def data_for_sale(self) -> Dict[str, str]: """Get the data for sale.""" if self._has_data_source: return self.collect_from_data_source() # pragma: nocover return self._data_for_sale @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def is_ledger_tx(self) -> bool: """Check whether or not tx are settled on a ledger.""" return self._is_ledger_tx def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_service_description(self) -> Description: """ Get the simple service description. :return: a description of the offered services """ description = Description( self._simple_service_data, data_model=SIMPLE_SERVICE_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description def is_matching_supply(self, query: Query) -> bool: """ Check if the query matches the supply. :param query: the query :return: bool indicating whether matches or not """ return query.check(self.get_service_description()) def generate_proposal_terms_and_data( # pylint: disable=unused-argument self, query: Query, counterparty_address: Address ) -> Tuple[Description, Terms, Dict[str, str]]: """ Generate a proposal matching the query. :param query: the query :param counterparty_address: the counterparty of the proposal. :return: a tuple of proposal, terms and the weather data """ data_for_sale = self.data_for_sale sale_quantity = len(data_for_sale) seller_address = self.context.agent_addresses[self.ledger_id] total_price = sale_quantity * self._unit_price if self.is_ledger_tx: tx_nonce = LedgerApis.generate_tx_nonce( identifier=self.ledger_id, seller=seller_address, client=counterparty_address, ) else: tx_nonce = uuid.uuid4().hex # pragma: nocover proposal = Description( { "ledger_id": self.ledger_id, "price": total_price, "currency_id": self._currency_id, "service_id": self._service_id, "quantity": sale_quantity, "tx_nonce": tx_nonce, } ) terms = Terms( ledger_id=self.ledger_id, sender_address=seller_address, counterparty_address=counterparty_address, amount_by_currency_id={self._currency_id: total_price}, quantities_by_good_id={self._service_id: -sale_quantity}, is_sender_payable_tx_fee=False, nonce=tx_nonce, fee_by_currency_id={self._currency_id: 0}, ) return proposal, terms, data_for_sale def collect_from_data_source(self) -> Dict[str, str]: """Implement the logic to communicate with the sensor.""" raise NotImplementedError ================================================ FILE: packages/fetchai/skills/gym/README.md ================================================ # Gym ## Description This skill trains an RL algorithm using OpenAI Gym. This skill is part of the Fetch.ai Gym demo. It trains a reinforcement learning (RL) algorithm using OpenAI Gym. It demonstrates how to wrap an RL agent in a skill by decoupling the RL agent from `gym.Env`, allowing them to run in separate execution environments. ## Handlers - `gym`: handles `gym` messages for interactions with a gym environment. ## Links - Gym Demo - Gym Example - OpenAI Gym ================================================ FILE: packages/fetchai/skills/gym/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains an example of skill for an AEA.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/gym:0.21.6") ================================================ FILE: packages/fetchai/skills/gym/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - GymDialogue: The dialogue class maintains state of a dialogue of type gym and manages it. - GymDialogues: The dialogues class keeps track of all dialogues of type gym. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.gym.dialogues import GymDialogue as BaseGymDialogue from packages.fetchai.protocols.gym.dialogues import GymDialogues as BaseGymDialogues DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=str(self.context.skill_id), role_from_first_message=role_from_first_message, ) GymDialogue = BaseGymDialogue class GymDialogues(Model, BaseGymDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseGymDialogue.Role.AGENT BaseGymDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/gym/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the handler for the 'gym' skill.""" from typing import Any, Optional, cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.skills.gym.dialogues import ( DefaultDialogues, GymDialogue, GymDialogues, ) from packages.fetchai.skills.gym.rl_agent import DEFAULT_NB_STEPS from packages.fetchai.skills.gym.tasks import GymTask class GymHandler(Handler): """Gym handler.""" SUPPORTED_PROTOCOL = GymMessage.protocol_id def __init__(self, **kwargs: Any): """Initialize the handler.""" nb_steps = kwargs.pop("nb_steps", DEFAULT_NB_STEPS) super().__init__(**kwargs) self.task = GymTask(self.context, nb_steps) self._task_id: Optional[int] = None def setup(self) -> None: """Set up the handler.""" self.context.logger.info("Gym handler: setup method called.") # launch the task self._task_id = self.context.task_manager.enqueue_task(self.task) def handle(self, message: Message) -> None: """ Implement messages. :param message: the message :return: None """ gym_msg = cast(GymMessage, message) # recover dialogue gym_dialogues = cast(GymDialogues, self.context.gym_dialogues) gym_dialogue = cast(GymDialogue, gym_dialogues.update(gym_msg)) if gym_dialogue is None: self._handle_unidentified_dialogue(gym_msg) return # handle message if gym_msg.performative == GymMessage.Performative.PERCEPT: self._handle_percept(gym_msg, gym_dialogue) elif gym_msg.performative == GymMessage.Performative.STATUS: self._handle_status(gym_msg, gym_dialogue) else: self._handle_invalid(gym_msg, gym_dialogue) def _handle_unidentified_dialogue(self, gym_msg: GymMessage) -> None: """ Handle an unidentified dialogue. :param gym_msg: the message """ self.context.logger.info( "received invalid gym message={}, unidentified dialogue.".format(gym_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=gym_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"gym_message": gym_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_percept(self, gym_msg: GymMessage, gym_dialogue: GymDialogue) -> None: """ Handle messages. :param gym_msg: the gym message :param gym_dialogue: the gym dialogue """ if self.task.proxy_env.active_gym_dialogue == gym_dialogue: self.task.proxy_env_queue.put(gym_msg) else: self.context.logger.warning("gym dialogue not active dialogue.") def _handle_status(self, gym_msg: GymMessage, gym_dialogue: GymDialogue) -> None: """ Handle messages. :param gym_msg: the gym message :param gym_dialogue: the gym dialogue """ if ( self.task.proxy_env.active_gym_dialogue == gym_dialogue and gym_msg.content.get("reset", "failure") == "success" ): self.task.proxy_env_queue.put(gym_msg) else: self.context.logger.warning("gym dialogue not active dialogue.") def _handle_invalid(self, gym_msg: GymMessage, gym_dialogue: GymDialogue) -> None: """ Handle an invalid http message. :param gym_msg: the gym message :param gym_dialogue: the gym dialogue """ self.context.logger.warning( "cannot handle gym message of performative={} in dialogue={}.".format( gym_msg.performative, gym_dialogue ) ) def teardown(self) -> None: """Teardown the handler.""" self.context.logger.info("Gym handler: teardown method called.") if self._task_id is None: return # pragma: nocover self.task.teardown() result = self.context.task_manager.get_task_result(self._task_id) if not result.successful(): self.context.logger.warning("Task not successful!") ================================================ FILE: packages/fetchai/skills/gym/helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the helpers for the 'gym' skill.""" from abc import ABC, abstractmethod from queue import Queue from typing import Any, Optional, Tuple, cast import gym from aea.protocols.base import Message from aea.skills.base import SkillContext from packages.fetchai.connections.gym.connection import ( PUBLIC_ID as GYM_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.skills.gym.dialogues import GymDialogue, GymDialogues Action = Any Observation = Any Reward = float Done = bool Info = dict Feedback = Tuple[Observation, Reward, Done, Info] NB_STEPS = 500 class ProxyEnv(gym.Env): """This class is an implementation of the ProxyEnv, using bandit RL solution.""" def __init__(self, skill_context: SkillContext) -> None: """ Instantiate the proxy environment. :param skill_context: the skill context """ super().__init__() self._skill_context = skill_context self._queue = Queue() # type: Queue self._is_rl_agent_trained = False self._step_count = 0 self._active_dialogue = None # type: Optional[GymDialogue] self.gym_address = str(GYM_CONNECTION_PUBLIC_ID) @property def gym_dialogues(self) -> GymDialogues: """Get the gym dialogues.""" return cast(GymDialogues, self._skill_context.gym_dialogues) @property def active_gym_dialogue(self) -> GymDialogue: """Get the active gym dialogue.""" if self._active_dialogue is None: raise ValueError("GymDialogue not set yet.") return self._active_dialogue @property def queue(self) -> Queue: """Get queue.""" return self._queue @property def is_rl_agent_trained(self) -> bool: """Get training status.""" return self._is_rl_agent_trained def step(self, action: Action) -> Feedback: # type: ignore """ Run one time-step of the environment's dynamics. Mirrors the standard 'step' method of a gym environment. - The action is given to _encode_action, which does the necessary conversion to an envelope. - The envelope is given to the outbox of the proxy agent. - The method blocks until the _queue returns an envelope. - The envelope is decoded with _decode_percept to a message. - The message is converted into the standard observation, reward, done and info via _message_to_percept :param action: the action sent to the step method (e.g. the output of an RL algorithm) :return: a Tuple containing the Feedback of Observation, Reward, Done and Info """ self._step_count += 1 step_id = self._step_count self._encode_and_send_action(action, step_id) # Wait (blocking!) for the response envelope from the environment gym_msg = self._queue.get(block=True, timeout=None) # type: GymMessage if gym_msg.performative != GymMessage.Performative.PERCEPT: raise ValueError( "Unexpected performative. Expected={} got={}".format( GymMessage.Performative.PERCEPT, gym_msg.performative ) ) if gym_msg.step_id == step_id: observation, reward, done, info = self._message_to_percept(gym_msg) else: raise ValueError( "Unexpected step id! expected={}, actual={}".format( step_id, gym_msg.step_id ) ) return observation, reward, done, info def render(self, mode: str = "human") -> None: """ Render the environment. :param mode: the mode """ def reset(self) -> None: # type: ignore # pylint: disable=arguments-differ """Reset the environment.""" self._step_count = 0 self._is_rl_agent_trained = False gym_msg, gym_dialogue = self.gym_dialogues.create( counterparty=self.gym_address, performative=GymMessage.Performative.RESET, ) gym_dialogue = cast(GymDialogue, gym_dialogue) self._active_dialogue = gym_dialogue self._skill_context.outbox.put_message(message=gym_msg) # Wait (blocking!) for the response envelope from the environment response_msg = self._queue.get(block=True, timeout=None) # type: GymMessage if response_msg.performative != GymMessage.Performative.STATUS: raise ValueError( "Unexpected performative. Expected={} got={}".format( GymMessage.Performative.STATUS, response_msg.performative ) ) def close(self) -> None: """Close the environment.""" self._is_rl_agent_trained = True last_msg = self.active_gym_dialogue.last_message if last_msg is None: # pragma: nocover raise ValueError("Cannot retrieve last message.") gym_msg = self.active_gym_dialogue.reply( performative=GymMessage.Performative.CLOSE, target_message=last_msg, ) self._skill_context.outbox.put_message(message=gym_msg) def _encode_and_send_action(self, action: Action, step_id: int) -> None: """ Encode the 'action' sent to the step function and send it. :param action: the action that is the output of an RL algorithm. :param step_id: the step id """ last_msg = self.active_gym_dialogue.last_message if last_msg is None: # pragma: nocover raise ValueError("Cannot retrieve last message.") gym_msg = self.active_gym_dialogue.reply( performative=GymMessage.Performative.ACT, target_message=last_msg, action=GymMessage.AnyObject(action), step_id=step_id, ) # Send the message via the proxy agent and to the environment self._skill_context.outbox.put_message(message=gym_msg) @staticmethod def _message_to_percept(message: Message) -> Feedback: """ Transform the message received from the gym environment into observation, reward, done, info. :param message: the message received as a response to the action performed in apply_action. :return: the standard feedback (observation, reward, done, info) of a gym environment. """ msg = cast(GymMessage, message) observation = msg.observation.any reward = msg.reward done = msg.done info = msg.info.any return observation, reward, done, info class RLAgent(ABC): """Abstract RL Agent.""" @abstractmethod def fit(self, proxy_env: ProxyEnv, nb_steps: int) -> None: """ Train the agent on the given proxy environment. :param proxy_env: the proxy gym environment :param nb_steps: number of training steps to be performed. """ ================================================ FILE: packages/fetchai/skills/gym/rl_agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This contains the rl agent class.""" import logging import random from typing import Any, Dict, Tuple import numpy as np from packages.fetchai.skills.gym.helpers import ProxyEnv, RLAgent DEFAULT_NB_STEPS = 4000 NB_GOODS = 10 _default_logger = logging.getLogger("aea.packages.fetchai.skills.gym.rl_agent") class PriceBandit: """A class for a multi-armed bandit model of price.""" def __init__(self, price: float, beta_a: float = 1.0, beta_b: float = 1.0): """ Instantiate a price bandit object. :param price: the price this bandit is modelling :param beta_a: the a parameter of the beta distribution :param beta_b: the b parameter of the beta distribution """ self.price = price # default params imply a uniform random prior self.beta_a = beta_a self.beta_b = beta_b def sample(self) -> int: """ Sample from the bandit. :return: the sampled value """ return round(np.random.beta(self.beta_a, self.beta_b)) def update(self, outcome: bool) -> None: """ Update the bandit. :param outcome: the outcome used for updating """ outcome_int = 1 if outcome else 0 # explicit type conversion self.beta_a += outcome_int self.beta_b += 1 - outcome_int class GoodPriceModel: """A class for a price model of a good.""" def __init__(self, bound: int = 100): """Instantiate a good price model.""" self.price_bandits = dict( (price, PriceBandit(price)) for price in range(bound + 1) ) def update(self, outcome: bool, price: int) -> None: """ Update the respective bandit. :param price: the price to be updated :param outcome: the negotiation outcome """ bandit = self.price_bandits[price] bandit.update(outcome) def get_price_expectation(self) -> int: """ Get best price. :return: the winning price """ maxsample = -1 winning_price = 0 for price, bandit in self.price_bandits.items(): sample = bandit.sample() if sample > maxsample: maxsample = sample winning_price = price return winning_price class MyRLAgent(RLAgent): """This class is a reinforcement learning agent that interacts with the agent framework.""" def __init__(self, nb_goods: int, logger: logging.Logger = _default_logger) -> None: """ Instantiate the RL agent. :param nb_goods: number of goods :param logger: the logger. """ self.good_price_models = dict( (good_id, GoodPriceModel()) for good_id in range(nb_goods) ) # type: Dict[int, GoodPriceModel] self.logger = logger def _pick_an_action(self) -> Any: """ Pick an action. :return: any """ # Get the good good_id = self._get_random_next_good() # Pick the best price based on own model. good_price_model = self.good_price_models[good_id] price = good_price_model.get_price_expectation() action = [good_id, price] return action def _update_model( # pylint: disable=unused-argument self, observation: Any, reward: float, done: bool, info: Dict, action: Tuple[int, int], ) -> None: """ Update the model. :param: observation: agent's observation of the current environment :param: reward: amount of reward returned after previous action :param: done: whether the episode has ended :param: info: auxiliary diagnostic information :param: action: action the agent performed on the environment to which the response was the above 4 """ good_id, price = action # Update the price model: good_price_model = self.good_price_models[good_id] outcome = reward == 1.0 good_price_model.update(outcome, price) def _get_random_next_good(self) -> int: """ Get the next good for trading (randomly). :return: a random good """ return random.choice(list(self.good_price_models.keys())) # nosec def fit(self, proxy_env: ProxyEnv, nb_steps: int) -> None: """ Train the agent on the given proxy environment. :param proxy_env: the proxy gym environment :param nb_steps: number of training steps to be performed. """ action_counter = 0 proxy_env.reset() while action_counter < nb_steps: action = self._pick_an_action() obs, reward, done, info = proxy_env.step(action) self._update_model(obs, reward, done, info, action) action_counter += 1 if action_counter % 10 == 0: self.logger.info( "Action: step_id='{}' action='{}' reward='{}'".format( action_counter, action, reward ) ) proxy_env.close() ================================================ FILE: packages/fetchai/skills/gym/skill.yaml ================================================ name: gym author: fetchai version: 0.21.6 type: skill description: The gym skill wraps an RL agent. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmeYFajc1RJmSTi37WtrT6bY6cBNs5jua7oVkxbUegK3tW __init__.py: QmV7evSScN3xK5CzvebBb7bMrkgtinK44yZ1yirgNu7aX5 dialogues.py: QmVePSYdPfXczXsriLxWguySkzHrRqkvs17HYhCu3W1poH handlers.py: QmQB9c4Pf4Unr4qDEbsFinKeLJGZVukq5V4k8U3jcfqcAo helpers.py: QmQ5esD6NubPdadz2zLmXjPHk6RcECyrHYfXhxdLzdKGBA rl_agent.py: QmPZc3gtESxa3VjPZ2TciB8cPsGRA14f5udyCZKiFyhJhp tasks.py: QmUx77jqj4BtWUL5pSxmiAnhhpTVww1g8JZmNXFyGHECFE fingerprint_ignore_patterns: [] connections: - fetchai/gym:0.20.6 contracts: [] protocols: - fetchai/gym:1.1.7 skills: [] behaviours: {} handlers: gym: args: nb_steps: 4000 class_name: GymHandler models: default_dialogues: args: {} class_name: DefaultDialogues gym_dialogues: args: {} class_name: GymDialogues dependencies: gym: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/gym/tasks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tasks for the 'gym' skill.""" from queue import Queue from typing import Any from aea.skills.base import SkillContext from aea.skills.tasks import Task from packages.fetchai.skills.gym.helpers import ProxyEnv from packages.fetchai.skills.gym.rl_agent import DEFAULT_NB_STEPS, MyRLAgent, NB_GOODS class GymTask(Task): """Gym task.""" def __init__(self, skill_context: SkillContext, nb_steps: int = DEFAULT_NB_STEPS): """Initialize the task.""" super().__init__(logger=skill_context.logger) self.logger.debug("GymTask.__init__: arguments: nb_steps={}".format(nb_steps)) self._rl_agent = MyRLAgent(NB_GOODS, self.logger) self._proxy_env = ProxyEnv(skill_context) self.nb_steps = nb_steps self.is_rl_agent_training = False def _fit(self, proxy_env: ProxyEnv, nb_steps: int) -> None: """Fit the RL agent.""" self._rl_agent.fit(proxy_env, nb_steps) self.logger.info("Training finished. You can exit now via CTRL+C.") @property def proxy_env(self) -> ProxyEnv: """Get the queue.""" return self._proxy_env @property def proxy_env_queue(self) -> Queue: """Get the queue.""" return self._proxy_env.queue def setup(self) -> None: """Set up the task.""" self.logger.info("Gym task: setup method called.") def execute(self, *args: Any, **kwargs: Any) -> None: """Execute the task.""" if not self._proxy_env.is_rl_agent_trained and not self.is_rl_agent_training: self._start_training() if self._proxy_env.is_rl_agent_trained and self.is_rl_agent_training: self._stop_training() def teardown(self) -> None: """Teardown the task.""" self.logger.info("Gym Task: teardown method called.") if self.is_rl_agent_training: self._stop_training() def _start_training(self) -> None: """Start training the RL agent.""" self.logger.info("Training starting ...") self.is_rl_agent_training = True self._fit(self._proxy_env, self.nb_steps) def _stop_training(self) -> None: """Stop training the RL agent.""" if self.is_rl_agent_training: self.is_rl_agent_training = False self._proxy_env.close() ================================================ FILE: packages/fetchai/skills/hello_world/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the 'Hello World!' skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/hello_world:0.1.5") ================================================ FILE: packages/fetchai/skills/hello_world/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviour of the 'Hello World!' skill.""" from typing import Any from aea.skills.behaviours import OneShotBehaviour DEFAULT_MESSAGE = "Hello World!" class HelloWorld(OneShotBehaviour): """This skill prints 'Hello World!' on the screen.""" def __init__(self, **kwargs: Any) -> None: """Initialise the behaviour.""" self.message = kwargs.pop("message", DEFAULT_MESSAGE) # type: str super().__init__(**kwargs) def setup(self) -> None: """The setup.""" def act(self) -> None: """The act.""" self.context.logger.info(self.message) def teardown(self) -> None: """The teardown.""" ================================================ FILE: packages/fetchai/skills/hello_world/skill.yaml ================================================ name: hello_world author: fetchai version: 0.1.5 type: skill description: This skill prints `Hello World!` on the screen. The message can be configured to be any different string. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmbChC9LnP2VDCELBpdvjQVWQeGxNGMBSxUCJkYvQAfQyC behaviours.py: QmageynDzUCdo4SUpQ7S3MxhW2Lfe6HVbWGdkX86rE82hm fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: [] skills: [] behaviours: hello_world: args: message: Hello World! class_name: HelloWorld handlers: {} models: {} dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/http_echo/README.md ================================================ # HTTP Echo ## Description This skill echoes the HTTP requests it receives as HTTP response. This skill is part of the Fetch.ai "HTTP Connection and Skill" guide. Upon receiving an HTTP request, it sends back the same information in the request as an HTTP response. ## Handlers - `http_handler`: handles `http` messages for echoing back an HTTP request as an HTTP response. ## Links - HTTP Connection and Skill ================================================ FILE: packages/fetchai/skills/http_echo/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the http echo skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/http_echo:0.21.6") ================================================ FILE: packages/fetchai/skills/http_echo/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - HttpDialogue: The dialogue class maintains state of a dialogue of type http and manages it. - HttpDialogues: The dialogues class keeps track of all dialogues of type http. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) HttpDialogue = BaseHttpDialogue class HttpDialogues(Model, BaseHttpDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseHttpDialogue.Role.SERVER BaseHttpDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/http_echo/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the handler for the 'http_echo' skill.""" import json from typing import cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.http_echo.dialogues import ( DefaultDialogues, HttpDialogue, HttpDialogues, ) class HttpHandler(Handler): """This implements the echo handler.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ http_msg = cast(HttpMessage, message) # recover dialogue http_dialogues = cast(HttpDialogues, self.context.http_dialogues) http_dialogue = cast(HttpDialogue, http_dialogues.update(http_msg)) if http_dialogue is None: self._handle_unidentified_dialogue(http_msg) return # handle message if http_msg.performative == HttpMessage.Performative.REQUEST: self._handle_request(http_msg, http_dialogue) else: self._handle_invalid(http_msg, http_dialogue) def _handle_unidentified_dialogue(self, http_msg: HttpMessage) -> None: """ Handle an unidentified dialogue. :param http_msg: the message """ self.context.logger.info( "received invalid http message={}, unidentified dialogue.".format(http_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=http_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"http_message": http_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_request( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle a Http request. :param http_msg: the http message :param http_dialogue: the http dialogue """ self.context.logger.info( "received http request with method={}, url={} and body={!r}".format( http_msg.method, http_msg.url, http_msg.body, ) ) if http_msg.method == "get": self._handle_get(http_msg, http_dialogue) elif http_msg.method == "post": self._handle_post(http_msg, http_dialogue) def _handle_get(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb GET. :param http_msg: the http message :param http_dialogue: the http dialogue """ http_response = http_dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_msg, version=http_msg.version, status_code=200, status_text="Success", headers=http_msg.headers, body=json.dumps({"tom": {"type": "cat", "age": 10}}).encode("utf-8"), ) self.context.logger.info("responding with: {}".format(http_response)) self.context.outbox.put_message(message=http_response) def _handle_post(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb POST. :param http_msg: the http message :param http_dialogue: the http dialogue """ http_response = http_dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_msg, version=http_msg.version, status_code=200, status_text="Success", headers=http_msg.headers, body=http_msg.body, ) self.context.logger.info("responding with: {}".format(http_response)) self.context.outbox.put_message(message=http_response) def _handle_invalid( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle an invalid http message. :param http_msg: the http message :param http_dialogue: the http dialogue """ self.context.logger.warning( "cannot handle http message of performative={} in dialogue={}.".format( http_msg.performative, http_dialogue ) ) def teardown(self) -> None: """Implement the handler teardown.""" ================================================ FILE: packages/fetchai/skills/http_echo/skill.yaml ================================================ name: http_echo author: fetchai version: 0.21.6 type: skill description: The http echo skill prints out the content of received http messages and responds with success. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmPCkgR8tH6rUeBhiykisZsQ3qi4eoSRVSx2xhyjCULnu1 __init__.py: QmNiooQ4Y6DYyysoPNNhhQsbAksh58y6ec5KxVVLptP4Hv dialogues.py: QmXft7PHDgb8MgEicPukB768qKLxNgYorX6VpNR4vDSLU6 handlers.py: QmQrwrsyp5qpFeerfSM8iBVdsowTfZa9Fwso5juN1Q8Vw8 fingerprint_ignore_patterns: [] connections: - fetchai/http_server:0.23.6 contracts: [] protocols: - fetchai/http:1.1.7 skills: [] behaviours: {} handlers: http_handler: args: {} class_name: HttpHandler models: default_dialogues: args: {} class_name: DefaultDialogues http_dialogues: args: {} class_name: HttpDialogues dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/ml_data_provider/README.md ================================================ # ML Train Provider ## Description This skill sells ML data for training. This skill is part of the Fetch.ai ML skill demo. It registers its "ML data selling service" on the sOEF. It can be requested (for example by an agent with the `ml_train` skill) to provide specific data samples, which it delivers after it receives payment. ## Behaviours - `service_registration`: registers service on the sOEF search service ## Handlers - `ml_trade`: handles `ml_trade` messages for negotiating the terms of trade - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages if service registration on the sOEF is unsuccessful ## Links - ML Demo ================================================ FILE: packages/fetchai/skills/ml_data_provider/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the ml train and predict skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/ml_data_provider:0.27.6") ================================================ FILE: packages/fetchai/skills/ml_data_provider/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours of the agent.""" from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) ServiceRegistrationBehaviour = GenericServiceRegistrationBehaviour ================================================ FILE: packages/fetchai/skills/ml_data_provider/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - MlTradeDialogue: The dialogue class maintains state of a dialogue of type ml_trade and manages it. - MlTradeDialogues: The dialogues class keeps track of all dialogues of type ml_trade. - OefSearchDialogue: The dialogue class maintains state of a dialogue of type oef_search and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ml_trade.dialogues import ( MlTradeDialogue as BaseMlTradeDialogue, ) from packages.fetchai.protocols.ml_trade.dialogues import ( MlTradeDialogues as BaseMlTradeDialogues, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) MlTradeDialogue = BaseMlTradeDialogue class MlTradeDialogues(Model, BaseMlTradeDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return MlTradeDialogue.Role.SELLER BaseMlTradeDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) LedgerApiDialogue = BaseLedgerApiDialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/ml_data_provider/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the handler for the 'ml_data_provider' skill.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.ml_data_provider.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.ml_data_provider.dialogues import ( DefaultDialogues, LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, MlTradeDialogues, OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.ml_data_provider.strategy import Strategy class MlTradeHandler(Handler): """ML trade handler.""" SUPPORTED_PROTOCOL = MlTradeMessage.protocol_id def setup(self) -> None: """Set up the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ml_trade_msg = cast(MlTradeMessage, message) # recover dialogue ml_trade_dialogues = cast(MlTradeDialogues, self.context.ml_trade_dialogues) ml_trade_dialogue = cast( MlTradeDialogue, ml_trade_dialogues.update(ml_trade_msg) ) if ml_trade_dialogue is None: self._handle_unidentified_dialogue(ml_trade_msg) return # handle message if ml_trade_msg.performative == MlTradeMessage.Performative.CFP: self._handle_cft(ml_trade_msg, ml_trade_dialogue) elif ml_trade_msg.performative == MlTradeMessage.Performative.ACCEPT: self._handle_accept(ml_trade_msg, ml_trade_dialogue) else: self._handle_invalid(ml_trade_msg, ml_trade_dialogue) def teardown(self) -> None: """Teardown the handler.""" def _handle_unidentified_dialogue(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle an unidentified dialogue. :param ml_trade_msg: the message """ self.context.logger.info( "received invalid ml_trade message={}, unidentified dialogue.".format( ml_trade_msg ) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=ml_trade_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"ml_trade_message": ml_trade_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_cft( self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue ) -> None: """ Handle call for terms. :param ml_trade_msg: the ml trade message :param ml_trade_dialogue: the dialogue object """ query = ml_trade_msg.query self.context.logger.info( "got a Call for Terms from {}.".format(ml_trade_msg.sender[-5:]) ) strategy = cast(Strategy, self.context.strategy) if not strategy.is_matching_supply(query): self.context.logger.info("query does not match supply.") return terms = strategy.generate_terms() self.context.logger.info( "sending to the address={} a Terms message: {}".format( ml_trade_msg.sender[-5:], terms.values ) ) terms_msg = ml_trade_dialogue.reply( performative=MlTradeMessage.Performative.TERMS, target_message=ml_trade_msg, terms=terms, ) self.context.outbox.put_message(message=terms_msg) def _handle_accept( self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue ) -> None: """ Handle accept. :param ml_trade_msg: the ml trade message :param ml_trade_dialogue: the dialogue object """ terms = ml_trade_msg.terms self.context.logger.info( "got an Accept from {}: {}".format(ml_trade_msg.sender[-5:], terms.values) ) strategy = cast(Strategy, self.context.strategy) if not strategy.is_valid_terms(terms): self.context.logger.info("terms are not valid.") return data = strategy.sample_data(terms.values["batch_size"]) self.context.logger.info( "sending to address={} a Data message: shape={}".format( ml_trade_msg.sender[-5:], data[0].shape ) ) payload = strategy.encode_sample_data(data) data_msg = ml_trade_dialogue.reply( performative=MlTradeMessage.Performative.DATA, target_message=ml_trade_msg, terms=terms, payload=payload, ) self.context.outbox.put_message(message=data_msg) def _handle_invalid( self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue ) -> None: """ Handle a fipa message of invalid performative. :param ml_trade_msg: the message :param ml_trade_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle ml_trade message of performative={} in dialogue={}.".format( ml_trade_msg.performative, ml_trade_dialogue ) ) class LedgerApiHandler(Handler): """Implement the ledger handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service_registration, ) registration_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/ml_data_provider/skill.yaml ================================================ name: ml_data_provider author: fetchai version: 0.27.6 type: skill description: The ml data provider skill implements a provider for Machine Learning datasets in order to monetize data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmcPsTwjWbUTMamK8aH94yRgYrKrmCwzffmMUueLya5wAg __init__.py: Qma4KbWbeEcrX2gSghAwGU2fjpvS1xyEWZPTaiifxYgSEM behaviours.py: QmZvLViapWxG1H41wn4e6Mzt8nmV6yyuk2sSsLJKZTQy2c dialogues.py: QmaVve3Ldt3TN1QSA1x58tBruPDRZ5bnVYHencsVHvfsoh handlers.py: QmYkHwQSjJh7CsMDRscidGzn4DMcWTJrFYMhV9yVaGNFZF strategy.py: QmW8SBVyZyfpQG4b25xvjtEQVuKNPbqryYLdMVzkjtYFxc fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/ml_trade:1.1.7 - fetchai/oef_search:1.1.7 skills: - fetchai/generic_seller:0.28.6 behaviours: service_registration: args: services_interval: 20 class_name: ServiceRegistrationBehaviour handlers: ledger_api: args: {} class_name: LedgerApiHandler ml_trade: args: {} class_name: MlTradeHandler oef_search: args: {} class_name: OefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues ml_trade_dialogues: args: {} class_name: MlTradeDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: batch_size: 2 buyer_tx_fee: 1550000000000000 classification: piece: classification value: seller is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data price_per_data_batch: 100 seller_tx_fee: 0 service_data: key: dataset_id value: fmnist service_id: data_service class_name: Strategy dependencies: numpy: {} tensorflow: version: <3.0.0,>=2.4.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/ml_data_provider/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import json import uuid from typing import Any, Tuple import numpy as np from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, SIMPLE_DATA_MODEL, ) from aea.helpers.search.models import Description, Location, Query from aea.skills.base import Model DEFAULT_PRICE_PER_DATA_BATCH = 10 DEFAULT_BATCH_SIZE = 32 DEFAULT_SELLER_TX_FEE = 0 DEFAULT_BUYER_TX_FEE = 0 DEFAULT_SERVICE_ID = "data_service" DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_SERVICE_DATA = {"key": "dataset_id", "value": "fmnist"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "seller"} class NumpyArrayEncoder(json.JSONEncoder): """This class defines a custom JSON encoder for numpy ndarray objects.""" def default( # pylint: disable=arguments-differ,arguments-renamed self, obj: Any ) -> Any: """Encode an object (including a numpy ndarray) into its JSON representation.""" if isinstance(obj, np.ndarray): return obj.tolist() return json.JSONEncoder.default(self, obj) # pragma: nocover class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """Initialize the strategy of the agent.""" self.price_per_data_batch = kwargs.pop( "price_per_data_batch", DEFAULT_PRICE_PER_DATA_BATCH ) self.batch_size = kwargs.pop("batch_size", DEFAULT_BATCH_SIZE) self.seller_tx_fee = kwargs.pop("seller_tx_fee", DEFAULT_SELLER_TX_FEE) self.buyer_tx_fee = kwargs.pop("buyer_tx_fee", DEFAULT_BUYER_TX_FEE) currency_id = kwargs.pop("currency_id", None) ledger_id = kwargs.pop("ledger_id", None) self._is_ledger_tx = kwargs.pop("is_ledger_tx", False) self._service_id = kwargs.pop("service_id", DEFAULT_SERVICE_ID) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} self._simple_service_data = { self._set_service_data["key"]: self._set_service_data["value"] } super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) if currency_id is None: currency_id = self.context.currency_denominations.get(self._ledger_id, None) enforce( currency_id is not None, f"Currency denomination for ledger_id={self._ledger_id} not specified.", ) self._currency_id = currency_id # loading ML dataset # (this could be parametrized) from tensorflow import keras # pylint: disable=import-outside-toplevel ( (self.train_x, self.train_y), (self.test_x, self.test_y), ) = keras.datasets.fashion_mnist.load_data() @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def is_ledger_tx(self) -> str: """Get the is_ledger_tx.""" return self._is_ledger_tx def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_service_description(self) -> Description: """ Get the simple service description. :return: a description of the offered services """ description = Description( self._simple_service_data, data_model=SIMPLE_DATA_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description def sample_data(self, n: int) -> Tuple: """Sample N rows from data.""" idx = np.arange(self.train_x.shape[0]) mask = np.zeros_like(idx, dtype=bool) selected = np.random.choice(idx, n, replace=False) mask[selected] = True x_sample = self.train_x[mask] y_sample = self.train_y[mask] return x_sample, y_sample @staticmethod def encode_sample_data(data: Tuple) -> bytes: """Serialize data (a tuple of two numpy ndarrays) into bytes.""" data_dict = { "data_0": data[0], "data_1": data[1], } return json.dumps(data_dict, cls=NumpyArrayEncoder).encode("utf-8") def is_matching_supply(self, query: Query) -> bool: """ Check if the query matches the supply. :param query: the query :return: bool indicating whether matches or not """ service_desc = self.get_service_description() return query.check(service_desc) def generate_terms(self) -> Description: """ Generate a proposal. :return: a tuple of proposal and the weather data """ address = self.context.agent_addresses[self.ledger_id] proposal = Description( { "batch_size": self.batch_size, "price": self.price_per_data_batch, "seller_tx_fee": self.seller_tx_fee, "buyer_tx_fee": self.buyer_tx_fee, "currency_id": self._currency_id, "ledger_id": self.ledger_id, "address": address, "service_id": self._service_id, "nonce": uuid.uuid4().hex, } ) return proposal def is_valid_terms(self, terms: Description) -> bool: """ Check the terms are valid. :param terms: the terms :return: boolean """ generated_terms = self.generate_terms() return all( terms.values[key] == generated_terms.values[key] for key in [ "batch_size", "price", "seller_tx_fee", "buyer_tx_fee", "currency_id", "ledger_id", "address", "service_id", ] ) ================================================ FILE: packages/fetchai/skills/ml_train/README.md ================================================ # ML Train ## Description This skill buys ML data for training. This skill is part of the Fetch.ai ML skill demo. It finds an agent which sells ML data, requests data and pays the proposed amount. Then trains an ML model using the bought data. ## Behaviours - `search`: searches for ML data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `ml_trade`: handles `ml_trade` messages for negotiating the terms of trade - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - ML Demo ================================================ FILE: packages/fetchai/skills/ml_train/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the ml train and predict skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/ml_train:0.29.6") ================================================ FILE: packages/fetchai/skills/ml_train/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours.""" from typing import Any, List, Optional, Set, cast from aea.protocols.dialogue.base import DialogueLabel from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.ledger_api import LedgerApiMessage from packages.fetchai.skills.generic_buyer.behaviours import GenericSearchBehaviour from packages.fetchai.skills.ml_train.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, ) from packages.fetchai.skills.ml_train.strategy import Strategy from packages.fetchai.skills.ml_train.tasks import MLTrainTask DEFAULT_MAX_PROCESSING = 120 DEFAULT_TX_INTERVAL = 2.0 DEFAULT_SEARCH_INTERVAL = 5.0 LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class SearchBehaviour(GenericSearchBehaviour): """This class implements a search behaviour for the ML skill.""" def act(self) -> None: """Implement the act.""" strategy = cast(Strategy, self.context.strategy) if strategy.current_task_id is not None: result = self.context.task_manager.get_task_result(strategy.current_task_id) if not result.ready(): return if not result.successful(): return strategy.weights = result.get() strategy.current_task_id = None if len(strategy.data) > 0: data = strategy.data.pop(0) ml_task_id = self.context.task_manager.enqueue_task( MLTrainTask( train_data=data[:2], epochs_per_batch=5, weights=strategy.weights ) ) strategy.current_task_id = ml_task_id return super().act() class TransactionBehaviour(TickerBehaviour): """A behaviour to sequentially submit transactions to the blockchain.""" def __init__(self, **kwargs: Any): """Initialize the transaction behaviour.""" tx_interval = cast( float, kwargs.pop("transaction_interval", DEFAULT_TX_INTERVAL) ) self.max_processing = cast( float, kwargs.pop("max_processing", DEFAULT_MAX_PROCESSING) ) self.processing_time = 0.0 self.waiting: List[MlTradeDialogue] = [] self.processing: Optional[LedgerApiDialogue] = None self.timedout: Set[DialogueLabel] = set() super().__init__(tick_interval=tx_interval, **kwargs) def setup(self) -> None: """Setup behaviour.""" def act(self) -> None: """Implement the act.""" if self.processing is not None: if self.processing_time <= self.max_processing: # already processing self.processing_time += self.tick_interval return self._timeout_processing() if len(self.waiting) == 0: # nothing to process return self._start_processing() def _start_processing(self) -> None: """Process the next transaction.""" ml_trade_dialogue = self.waiting.pop(0) self.context.logger.info( f"Processing transaction, {len(self.waiting)} transactions remaining" ) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, terms=ml_trade_dialogue.terms, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_ml_trade_dialogue = ml_trade_dialogue self.processing_time = 0.0 self.processing = ledger_api_dialogue self.context.logger.info( f"requesting transfer transaction from ledger api for message={ledger_api_msg}..." ) self.context.outbox.put_message(message=ledger_api_msg) def teardown(self) -> None: """Teardown behaviour.""" def _timeout_processing(self) -> None: """Timeout processing.""" if self.processing is None: return self.timedout.add(self.processing.dialogue_label) self.waiting.append(self.processing.associated_ml_trade_dialogue) self.processing_time = 0.0 self.processing = None def finish_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Finish processing. :param ledger_api_dialogue: the ledger api dialogue """ if self.processing == ledger_api_dialogue: self.processing_time = 0.0 self.processing = None return if ledger_api_dialogue.dialogue_label not in self.timedout: raise ValueError( f"Non-matching dialogues in transaction behaviour: {self.processing} and {ledger_api_dialogue}" ) self.timedout.remove(ledger_api_dialogue.dialogue_label) self.context.logger.debug( f"Timeout dialogue in transaction processing: {ledger_api_dialogue}" ) # don't reset, as another might be processing def failed_processing(self, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Failed processing. Currently, we retry processing indefinitely. :param ledger_api_dialogue: the ledger api dialogue """ self.finish_processing(ledger_api_dialogue) self.waiting.append(ledger_api_dialogue.associated_ml_trade_dialogue) ================================================ FILE: packages/fetchai/skills/ml_train/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - LedgerApiDialogue: The dialogue class maintains state of a dialogue of type ledger_api and manages it. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - MlTradeDialogue: The dialogue class maintains state of a dialogue of type ml_trade and manages it. - MlTradeDialogues: The dialogues class keeps track of all dialogues of type ml_trade. """ from typing import Any, Dict, Optional, Type from aea.common import Address from aea.exceptions import AEAEnforceError, enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.dialogues import LedgerApiMessage from packages.fetchai.protocols.ml_trade.dialogues import ( MlTradeDialogue as BaseMlTradeDialogue, ) from packages.fetchai.protocols.ml_trade.dialogues import ( MlTradeDialogues as BaseMlTradeDialogues, ) from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class MlTradeDialogue(BaseMlTradeDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("data_for_sale", "_terms") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[MlTradeMessage] = MlTradeMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseMlTradeDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self.data_for_sale = None # type: Optional[Dict[str, str]] self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get terms.""" if self._terms is None: raise AEAEnforceError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class MlTradeDialogues(Model, BaseMlTradeDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseMlTradeDialogue.Role.BUYER BaseMlTradeDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_ml_trade_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_ml_trade_dialogue = None # type: Optional[MlTradeDialogue] @property def associated_ml_trade_dialogue(self) -> MlTradeDialogue: """Get associated_ml_trade_dialogue.""" if self._associated_ml_trade_dialogue is None: raise AEAEnforceError("MlTradeDialogue not set!") return self._associated_ml_trade_dialogue @associated_ml_trade_dialogue.setter def associated_ml_trade_dialogue(self, ml_trade_dialogue: MlTradeDialogue) -> None: """Set associated_ml_trade_dialogue""" enforce( self._associated_ml_trade_dialogue is None, "MlTradeDialogue already set!" ) self._associated_ml_trade_dialogue = ml_trade_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_ledger_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_ledger_api_dialogue = None # type: Optional[LedgerApiDialogue] @property def associated_ledger_api_dialogue(self) -> LedgerApiDialogue: """Get associated_ledger_api_dialogue.""" if self._associated_ledger_api_dialogue is None: raise AEAEnforceError("LedgerApiDialogue not set!") return self._associated_ledger_api_dialogue @associated_ledger_api_dialogue.setter def associated_ledger_api_dialogue( self, ledger_api_dialogue: LedgerApiDialogue ) -> None: """Set associated_ledger_api_dialogue""" enforce( self._associated_ledger_api_dialogue is None, "LedgerApiDialogue already set!", ) self._associated_ledger_api_dialogue = ledger_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: packages/fetchai/skills/ml_train/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the handler for the 'ml_train' skill.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.ml_train.behaviours import TransactionBehaviour from packages.fetchai.skills.ml_train.dialogues import ( DefaultDialogues, LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, MlTradeDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.ml_train.strategy import Strategy DUMMY_DIGEST = "dummy_digest" LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class MlTradeHandler(Handler): """ML trade handler.""" SUPPORTED_PROTOCOL = MlTradeMessage.protocol_id def setup(self) -> None: """Set up the handler.""" def handle(self, message: Message) -> None: """ Handle messages. :param message: the message """ ml_trade_msg = cast(MlTradeMessage, message) # recover dialogue ml_trade_dialogues = cast(MlTradeDialogues, self.context.ml_trade_dialogues) ml_trade_dialogue = cast( MlTradeDialogue, ml_trade_dialogues.update(ml_trade_msg) ) if ml_trade_dialogue is None: self._handle_unidentified_dialogue(ml_trade_msg) return # handle message if ml_trade_msg.performative == MlTradeMessage.Performative.TERMS: self._handle_terms(ml_trade_msg, ml_trade_dialogue) elif ml_trade_msg.performative == MlTradeMessage.Performative.DATA: self._handle_data(ml_trade_msg) else: self._handle_invalid(ml_trade_msg, ml_trade_dialogue) def teardown(self) -> None: """Teardown the handler.""" def _handle_unidentified_dialogue(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle an unidentified dialogue. :param ml_trade_msg: the message """ self.context.logger.info( "received invalid ml_trade message={}, unidentified dialogue.".format( ml_trade_msg ) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=ml_trade_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"ml_trade_message": ml_trade_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_terms( self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue ) -> None: """ Handle the terms of the request. :param ml_trade_msg: the ml trade message :param ml_trade_dialogue: the dialogue object """ proposal_terms = ml_trade_msg.terms self.context.logger.info( "received terms message from {}: terms={}".format( ml_trade_msg.sender[-5:], proposal_terms.values ) ) strategy = cast(Strategy, self.context.strategy) acceptable = strategy.is_acceptable_terms(proposal_terms) affordable = strategy.is_affordable_terms(proposal_terms) if not (acceptable and affordable): self.context.logger.info( "rejecting, terms are not acceptable and/or affordable" ) return if strategy.is_ledger_tx: terms = strategy.terms_from_proposal(proposal_terms) ml_trade_dialogue.terms = terms tx_behaviour = cast( TransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.waiting.append(ml_trade_dialogue) else: # accept directly with a dummy transaction digest, no settlement ml_accept = ml_trade_dialogue.reply( performative=MlTradeMessage.Performative.ACCEPT, target_message=ml_trade_msg, tx_digest=DUMMY_DIGEST, terms=proposal_terms, ) self.context.outbox.put_message(message=ml_accept) self.context.logger.info("sending dummy transaction digest ...") def _handle_data(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle the data. :param ml_trade_msg: the ml trade message """ strategy = cast(Strategy, self.context.strategy) terms = ml_trade_msg.terms payload = ml_trade_msg.payload data = strategy.decode_sample_data(payload) if data is None: self.context.logger.info( "received data message with no data from {}".format( ml_trade_msg.sender[-5:] ) ) else: self.context.logger.info( "received data message from {}: data shape={}, terms={}".format( ml_trade_msg.sender[-5:], data[0].shape, terms.values ) ) strategy.data.append(data) strategy.is_searching = True def _handle_invalid( self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue ) -> None: """ Handle a fipa message of invalid performative. :param ml_trade_msg: the message :param ml_trade_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle ml_trade message of performative={} in dialogue={}.".format( ml_trade_msg.performative, ml_trade_dialogue ) ) class OEFSearchHandler(Handler): """The OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_msg, oef_search_dialogue ) ) def _handle_search( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle the search response. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ if len(oef_search_msg.agents) == 0: self.context.logger.info( "found no agents in dialogue={}, continue searching.".format( oef_search_dialogue ) ) return self.context.logger.info( "found agents={}, stopping search.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) strategy = cast(Strategy, self.context.strategy) strategy.is_searching = False query = strategy.get_service_query() ml_trade_dialogues = cast(MlTradeDialogues, self.context.ml_trade_dialogues) for idx, opponent_address in enumerate(oef_search_msg.agents): if idx >= strategy.max_negotiations: continue self.context.logger.info( "sending CFT to agent={}".format(opponent_address[-5:]) ) cft_msg, _ = ml_trade_dialogues.create( counterparty=opponent_address, performative=MlTradeMessage.Performative.CFP, query=query, ) self.context.outbox.put_message(message=cft_msg) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) class LedgerApiHandler(Handler): """Implement the ledger handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative == LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ strategy = cast(Strategy, self.context.strategy) if ledger_api_msg.balance > 0: self.context.logger.info( "starting balance on {} ledger={}.".format( strategy.ledger_id, ledger_api_msg.balance, ) ) strategy.is_searching = True strategy.balance = ledger_api_msg.balance else: self.context.logger.warning( "you have no starting balance on {} ledger!".format(strategy.ledger_id) ) self.context.is_active = False def _handle_raw_transaction( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of raw_transaction performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(ledger_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) last_msg = cast(LedgerApiMessage, ledger_api_dialogue.last_outgoing_message) if last_msg is None: raise ValueError( # pragma: nocover "Could not retrive last outgoing ledger_api_msg." ) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=ledger_api_msg.raw_transaction, terms=last_msg.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) ledger_api_msg_ = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.logger.info("checking transaction is settled.") self.context.outbox.put_message(message=ledger_api_msg_) def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ ml_trade_dialogue = ledger_api_dialogue.associated_ml_trade_dialogue is_settled = LedgerApis.is_transaction_settled( ml_trade_dialogue.terms.ledger_id, ledger_api_msg.transaction_receipt.receipt, ) tx_behaviour = cast(TransactionBehaviour, self.context.behaviours.transaction) if is_settled: tx_behaviour.finish_processing(ledger_api_dialogue) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ledger_api_msg_ is None: raise ValueError( # pragma: nocover "Could not retrieve last ledger_api message" ) ml_trade_msg = cast( Optional[MlTradeMessage], ml_trade_dialogue.last_incoming_message ) if ml_trade_msg is None: raise ValueError("Could not retrieve last ml_trade message") ml_accept = ml_trade_dialogue.reply( performative=MlTradeMessage.Performative.ACCEPT, target_message=ml_trade_msg, tx_digest=ledger_api_msg_.transaction_digest.body, terms=ml_trade_msg.terms, ) self.context.outbox.put_message(message=ml_accept) self.context.logger.info( "informing counterparty={} of transaction digest={}.".format( ml_trade_msg.sender[-5:], ledger_api_msg_.transaction_digest, ) ) else: tx_behaviour.failed_processing(ledger_api_dialogue) self.context.logger.info( "transaction_receipt={} not settled or not valid, aborting".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) ledger_api_msg_ = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_outgoing_message ) if ( ledger_api_msg_ is not None and ledger_api_msg_.performative != LedgerApiMessage.Performative.GET_BALANCE ): tx_behaviour = cast( TransactionBehaviour, self.context.behaviours.transaction ) tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class SigningHandler(Handler): """Implement the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue last_ledger_api_msg = cast( Optional[LedgerApiMessage], ledger_api_dialogue.last_incoming_message ) if last_ledger_api_msg is None: raise ValueError("Could not retrieve last message in ledger api dialogue") ledger_api_msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, target_message=last_ledger_api_msg, signed_transaction=signing_msg.signed_transaction, ) self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) signing_msg_ = cast( Optional[SigningMessage], signing_dialogue.last_outgoing_message ) if ( signing_msg_ is not None and signing_msg_.performative == SigningMessage.Performative.SIGN_TRANSACTION ): tx_behaviour = cast( TransactionBehaviour, self.context.behaviours.transaction ) ledger_api_dialogue = signing_dialogue.associated_ledger_api_dialogue tx_behaviour.failed_processing(ledger_api_dialogue) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) ================================================ FILE: packages/fetchai/skills/ml_train/skill.yaml ================================================ name: ml_train author: fetchai version: 0.29.6 type: skill description: The ml train and predict skill implements a simple skill which buys training data, trains a model and sells predictions. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmPzgPycfq5bRWQhFqy1ARH1vMyXzDZucn9zXuf4V3hGnS __init__.py: QmXZYbUeGUQrEZszz8eJGCqXqxMx4VPf4xvy8jZYUSmxip behaviours.py: QmWeexaVncgAR3vZa3CBDfvzTfZDTcG6GXHwFjbM6wzJed dialogues.py: QmdVwUF6wjX8bkfpsnog7KGF269HK5395DUjmqD1eT3qUR handlers.py: QmezY9DZ6Sgi6SqZnyc3Gcte4NUmKSsQUSbZDmr6kWsbAt strategy.py: QmYmzBsWDe4pxGYpYt1iNHQQz2MFRgMAULygW9Zdu4UkQR tasks.py: QmPLZyT4pX2Kq2HGkBdS5Jwyz6CFgsk4B4SSBUxJu1kP3C fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/ml_trade:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/generic_buyer:0.27.6 behaviours: search: args: search_interval: 10 class_name: SearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: ledger_api: args: {} class_name: LedgerApiHandler ml_trade: args: {} class_name: MlTradeHandler oef_search: args: {} class_name: OEFSearchHandler signing: args: {} class_name: SigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues ml_trade_dialogues: args: {} class_name: MlTradeDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_buyer_tx_fee: 1550000000000000 max_negotiations: 2 max_unit_price: 70 search_query: constraint_type: == search_key: dataset_id search_value: fmnist search_radius: 5.0 service_id: data_service class_name: Strategy dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 numpy: {} tensorflow: version: <3.0.0,>=2.4.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/ml_train/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import json from typing import Any, List, Optional, Tuple import numpy as np from aea.exceptions import enforce from aea.helpers.search.generic import SIMPLE_DATA_MODEL from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.helpers.transaction.base import Terms from aea.skills.base import Model DEFAULT_MAX_ROW_PRICE = 5 DEFAULT_MAX_TX_FEE = 2 DEFAULT_MAX_NEGOTIATIONS = 1 DEFAULT_SERVICE_ID = "service_data" DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "dataset_id", "search_value": "fmnist", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """Initialize the strategy of the agent.""" self._max_unit_price = kwargs.pop("max_unit_price", DEFAULT_MAX_ROW_PRICE) self._max_buyer_tx_fee = kwargs.pop("max_buyer_tx_fee", DEFAULT_MAX_TX_FEE) currency_id = kwargs.pop("currency_id", None) ledger_id = kwargs.pop("ledger_id", None) self._is_ledger_tx = kwargs.pop("is_ledger_tx", False) self._max_negotiations = kwargs.pop( "max_negotiations", DEFAULT_MAX_NEGOTIATIONS ) self._service_id = kwargs.pop("service_id", DEFAULT_SERVICE_ID) self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) if currency_id is None: currency_id = self.context.currency_denominations.get(self._ledger_id, None) enforce( currency_id is not None, f"Currency denomination for ledger_id={self._ledger_id} not specified.", ) self._currency_id = currency_id self._is_searching = False self._tx_id = 0 self._balance = 0 self._current_task_id = None # type: Optional[int] self._weights = None # type: Optional[Any] self.data = [] # type: List[Any] @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def is_ledger_tx(self) -> str: """Get the is_ledger_tx.""" return self._is_ledger_tx @property def max_negotiations(self) -> int: """Get the max negotiations.""" return self._max_negotiations @property def is_searching(self) -> bool: """Check if the agent is searching.""" return self._is_searching @is_searching.setter def is_searching(self, is_searching: bool) -> None: """Check if the agent is searching.""" enforce(isinstance(is_searching, bool), "Can only set bool on is_searching!") self._is_searching = is_searching @property def balance(self) -> int: """Get the balance.""" return self._balance @balance.setter def balance(self, balance: int) -> None: """Set the balance.""" self._balance = balance @property def current_task_id(self) -> Optional[int]: """Get the current_task_id.""" return self._current_task_id @current_task_id.setter def current_task_id(self, task_id: int) -> None: """Set the current_task_id.""" self._current_task_id = task_id @property def weights(self) -> Optional[List[np.ndarray]]: """Get the weights.""" return self._weights @weights.setter def weights(self, weights: List[np.ndarray]) -> None: """Set the weights.""" self._weights = weights def get_next_transaction_id(self) -> str: """ Get the next transaction id. :return: The next transaction id """ self._tx_id += 1 return "transaction_{}".format(self._tx_id) def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType("distance", (self._agent_location, self._radius)) ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query def get_service_query(self) -> Query: """ Get the service query of the agent. :return: the query """ service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query([service_key_filter], model=SIMPLE_DATA_MODEL) return query def is_acceptable_terms(self, terms: Description) -> bool: """ Check whether the terms are acceptable. :param terms: the terms :return: boolean """ result = ( (terms.values["price"] - terms.values["seller_tx_fee"] > 0) and ( terms.values["price"] <= self._max_unit_price * terms.values["batch_size"] ) and (terms.values["buyer_tx_fee"] <= self._max_buyer_tx_fee) and (terms.values["currency_id"] == self._currency_id) and (terms.values["ledger_id"] == self._ledger_id) and (terms.values["service_id"] == self._service_id) ) return result def is_affordable_terms(self, terms: Description) -> bool: """ Check whether the terms are affordable. :param terms: the terms :return: whether it is affordable """ if self.is_ledger_tx: payable = ( terms.values["price"] - terms.values["seller_tx_fee"] + terms.values["buyer_tx_fee"] ) result = self.balance >= payable else: result = True return result def terms_from_proposal(self, proposal: Description) -> Terms: """ Get the terms from a proposal. :param proposal: the proposal :return: terms """ buyer_address = self.context.agent_addresses[proposal.values["ledger_id"]] terms = Terms( ledger_id=proposal.values["ledger_id"], sender_address=buyer_address, counterparty_address=proposal.values["address"], amount_by_currency_id={ proposal.values["currency_id"]: -proposal.values["price"] }, quantities_by_good_id={ proposal.values["service_id"]: proposal.values["batch_size"] }, is_sender_payable_tx_fee=True, nonce=proposal.values["nonce"], fee_by_currency_id={proposal.values["currency_id"]: self._max_buyer_tx_fee}, ) return terms def update_search_query_params(self) -> None: """Update search query params.""" @staticmethod def decode_sample_data(payload: bytes) -> Optional[Tuple[np.ndarray, np.ndarray]]: """Deserialize a bytes payload into data (a tuple of two numpy ndarrays or None).""" decoded_payload = json.loads(payload) if decoded_payload is None: return None numpy_data_0 = np.asarray(decoded_payload["data_0"]) numpy_data_1 = np.asarray(decoded_payload["data_1"]) decoded_data = (numpy_data_0, numpy_data_1) return decoded_data ================================================ FILE: packages/fetchai/skills/ml_train/tasks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tasks for the 'ml_train' skill.""" from typing import Any, List, Optional, Tuple import numpy as np from aea.skills.tasks import Task class MLTrainTask(Task): """ML train task.""" def __init__( self, train_data: Tuple[np.ndarray, np.ndarray], epochs_per_batch: int = 10, weights: Optional[List[np.ndarray]] = None, ): """Initialize the task.""" super().__init__() self.train_x, self.train_y = train_data self.epochs_per_batch = epochs_per_batch self.weights = weights def setup(self) -> None: """Set up the task.""" self.logger.info("ML Train task: setup method called.") def make_model(self) -> Any: """Make model.""" import tensorflow as tf # pylint: disable=import-outside-toplevel model = tf.keras.Sequential( [ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation="relu"), tf.keras.layers.Dense(10, activation="softmax"), ] ) model.compile( optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"], ) if self.weights is not None: model.set_weights(self.weights) return model def execute(self, *args: Any, **kwargs: Any) -> Any: """Execute the task.""" self.logger.info(f"Start training with {self.train_x.shape[0]} rows") model = self.make_model() model.fit(self.train_x, self.train_y, epochs=self.epochs_per_batch) new_weights = model.get_weights() loss, acc = model.evaluate(self.train_x, self.train_y, verbose=2) self.logger.info("Loss: {}, Acc: {}".format(loss, acc)) return new_weights def teardown(self) -> None: """Teardown the task.""" self.logger.info("ML Train task: teardown method called.") ================================================ FILE: packages/fetchai/skills/registration_aw1/README.md ================================================ # Registration AW1 ## Description The `registration_aw1` skill is for registration in Agent World 1. ## Behaviours - `registration`: manages the registration flow. ## Handlers - `registration`: handles `register` messages. - `signing`: handles `signing` messages for transaction signing by the decision maker ## Models - `strategy`: contains the registration configurations ================================================ FILE: packages/fetchai/skills/registration_aw1/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/registration_aw1:0.13.6") ================================================ FILE: packages/fetchai/skills/registration_aw1/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a behaviour.""" from typing import Dict, List, Optional, Set, cast from aea.helpers.transaction.base import RawMessage, Terms from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.registration_aw1.dialogues import ( RegisterDialogues, SigningDialogues, ) from packages.fetchai.skills.registration_aw1.strategy import Strategy class AW1RegistrationBehaviour(TickerBehaviour): """This class scaffolds a behaviour.""" def setup(self) -> None: """Implement the setup.""" strategy = cast(Strategy, self.context.strategy) if strategy.announce_termination_key is not None: self.context.shared_state[strategy.announce_termination_key] = False if not strategy.developer_handle_only: signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) msg, _ = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_MESSAGE, raw_message=RawMessage( strategy.ledger_id, strategy.ethereum_address.encode("utf-8") ), terms=Terms( ledger_id=strategy.ledger_id, sender_address="", counterparty_address="", amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ), ) self.context.logger.info("sending signing_msg to decision maker...") self.context.decision_maker_message_queue.put_nowait(msg) def act(self) -> None: """Implement the act.""" strategy = cast(Strategy, self.context.strategy) if not strategy.is_ready_to_register: return aw1_registration_aeas: Optional[Set[str]] = self.context.shared_state.get( strategy.shared_storage_key, None ) if aw1_registration_aeas is None: return if strategy.is_registered or strategy.is_registration_pending: return strategy.is_registration_pending = True self._register_for_aw1( aw1_registration_aeas, strategy.registration_info, strategy.whitelist ) def teardown(self) -> None: """Implement the task teardown.""" def _register_for_aw1( self, aw1_registration_aeas: Set[str], registration_info: Dict[str, str], whitelist: List[str], ) -> None: """ Register for Agent World 1. :param aw1_registration_aeas: the AEAs to register with :param registration_info: the info to send :param whitelist: the allowed agents """ register_dialogues = cast(RegisterDialogues, self.context.register_dialogues) for agent in aw1_registration_aeas: if agent not in whitelist: self.context.logger.info(f"agent={agent} not in whitelist={whitelist}") return msg, _ = register_dialogues.create( counterparty=agent, performative=RegisterMessage.Performative.REGISTER, info=registration_info, ) self.context.logger.info(f"sending registration info: {registration_info}") self.context.outbox.put_message(msg) ================================================ FILE: packages/fetchai/skills/registration_aw1/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - OefSearchDialogue: The dialogue class maintains state of a dialogue and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues. """ from typing import Any from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Address, Model from packages.fetchai.protocols.register.dialogues import ( RegisterDialogue as BaseRegisterDialogue, ) from packages.fetchai.protocols.register.dialogues import ( RegisterDialogues as BaseRegisterDialogues, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) RegisterDialogue = BaseRegisterDialogue class RegisterDialogues(Model, BaseRegisterDialogues): """This class keeps track of all register dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseRegisterDialogue.Role.AGENT BaseRegisterDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) SigningDialogue = BaseSigningDialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all signing dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/registration_aw1/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a handler.""" from typing import Optional, cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.registration_aw1.dialogues import ( RegisterDialogue, RegisterDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.registration_aw1.strategy import Strategy class AW1RegistrationHandler(Handler): """This class handles register messages.""" SUPPORTED_PROTOCOL = RegisterMessage.protocol_id def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ register_msg = cast(RegisterMessage, message) # recover dialogue register_dialogues = cast(RegisterDialogues, self.context.register_dialogues) register_dialogue = cast( Optional[RegisterDialogue], register_dialogues.update(register_msg) ) if register_dialogue is None: self._handle_unidentified_dialogue(register_msg) return # handle message if register_msg.performative is RegisterMessage.Performative.SUCCESS: self._handle_success(register_msg, register_dialogue) elif register_msg.performative is RegisterMessage.Performative.ERROR: self._handle_error(register_msg, register_dialogue) else: self._handle_invalid(register_msg, register_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, register_msg: RegisterMessage) -> None: """ Handle an unidentified dialogue. :param register_msg: the message """ self.context.logger.info( f"received invalid register_msg message={register_msg}, unidentified dialogue." ) def _handle_success( self, register_msg: RegisterMessage, register_dialogue: RegisterDialogue ) -> None: """ Handle an register message. :param register_msg: the register message :param register_dialogue: the dialogue """ self.context.logger.debug( f"received register_msg success message={register_msg} in dialogue={register_dialogue}." ) self.context.logger.info( f"received register message success, info={register_msg.info}. Stop me now!" ) strategy = cast(Strategy, self.context.strategy) strategy.is_registered = True strategy.is_registration_pending = False strategy.is_ready_to_register = False if strategy.announce_termination_key is not None: self.context.shared_state[strategy.announce_termination_key] = True def _handle_error( self, register_msg: RegisterMessage, register_dialogue: RegisterDialogue ) -> None: """ Handle an register message. :param register_msg: the register message :param register_dialogue: the dialogue """ self.context.logger.debug( f"received register_msg error message={register_msg} in dialogue={register_dialogue}." ) self.context.logger.info( f"received register message error, error_msg={register_msg.error_msg}. Stop me now!" ) strategy = cast(Strategy, self.context.strategy) strategy.is_registration_pending = False strategy.is_ready_to_register = False def _handle_invalid( self, register_msg: RegisterMessage, register_dialogue: RegisterDialogue ) -> None: """ Handle an register message. :param register_msg: the register message :param register_dialogue: the dialogue """ self.context.logger.warning( f"cannot handle register_msg message of performative={register_msg.performative} in dialogue={register_dialogue}." ) class SigningHandler(Handler): """Implement the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_MESSAGE: self._handle_signed_message(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( f"received invalid signing message={signing_msg}, unidentified dialogue." ) def _handle_signed_message( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a signed message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.debug( f"received signing message from decision maker, message={signing_msg} in dialogue={signing_dialogue}" ) self.context.logger.info( f"received signing message from decision maker, signature={signing_msg.signed_message.body} stored!" ) strategy = cast(Strategy, self.context.strategy) strategy.signature_of_ethereum_address = signing_msg.signed_message.body strategy.is_ready_to_register = True def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( f"transaction signing was not successful. Error_code={signing_msg.error_code} in dialogue={signing_dialogue}" ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( f"cannot handle signing message of performative={signing_msg.performative} in dialogue={signing_dialogue}." ) ================================================ FILE: packages/fetchai/skills/registration_aw1/skill.yaml ================================================ name: registration_aw1 author: fetchai version: 0.13.6 type: skill description: The registration_aw1 skill is a skill for registration in Agent World 1. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmUMdQxB7uC3SuP92vfiCRr4fHUeWMcfJNmJ74BoCk8eHT __init__.py: QmYQgGit93v872iD5etZ5k5F2tNo1b5W6Sjtv7nyNwjDh3 behaviours.py: QmS5mMwaq2pQSaphNPA4QRxXRBBUpVerLvpYfg532Hb9wk dialogues.py: QmbdecnniJWxH7dnNUB14JKU16BwSKx37USyqcJahEQpVw handlers.py: QmUi9Ry9xd7R8RPFbeW3wePBT26s5q4Mvojs2zuR4xFXfW strategy.py: QmZp5WvwW1qKvgRfWemUDtFiYEQExUqkLuwe89RdBZmtdt fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/register:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/simple_service_search:0.11.6 behaviours: registration: args: tick_interval: 10 class_name: AW1RegistrationBehaviour handlers: registration: args: {} class_name: AW1RegistrationHandler signing: args: {} class_name: SigningHandler models: register_dialogues: args: {} class_name: RegisterDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: announce_termination_key: null developer_handle: PUT_YOUR_DEVELOPER_HANDLE_HERE developer_handle_only: false ethereum_address: PUT_YOUR_ETHEREUM_ADDRESS_HERE shared_storage_key: agents_found signature_of_fetchai_address: PUT_YOUR_SIGNATURE_HERE tweet: PUT_THE_LINK_TO_YOUR_TWEET_HERE whitelist: [] class_name: Strategy dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/registration_aw1/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a model.""" from typing import Any, Dict, List, Optional from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import enforce from aea.skills.base import Model DEFAULT_SHARED_STORAGE_KEY = "agents_found" DEFAULT_ETHEREUM_ADDRESS = "PUT_YOUR_ETHEREUM_ADDRESS_HERE" DEFAULT_SIGNATURE_OF_FETCHAI_ADDRESS = "PUT_YOUR_SIGNATURE_HERE" DEFAULT_DEVELOPER_HANDLE = "PUT_YOUR_DEVELOPER_HANDLE_HERE" DEFAULT_TWEET = "PUT_THE_LINK_TO_YOUR_TWEET_HERE" DEFAULT_WHITELIST: List[str] = [] class Strategy(Model): """This class is the strategy model.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ developer_handle = kwargs.pop("developer_handle", DEFAULT_DEVELOPER_HANDLE) enforce( developer_handle != DEFAULT_DEVELOPER_HANDLE and isinstance(developer_handle, str), f"Not a valid developer_handle: {developer_handle}", ) self._developer_handle = developer_handle self._whitelist = kwargs.pop("whitelist", DEFAULT_WHITELIST) self._shared_storage_key = kwargs.pop( "shared_storage_key", DEFAULT_SHARED_STORAGE_KEY ) self.announce_termination_key = kwargs.pop("announce_termination_key", None) self.developer_handle_only = kwargs.pop("developer_handle_only", False) if not self.developer_handle_only: ethereum_address = kwargs.pop("ethereum_address", DEFAULT_ETHEREUM_ADDRESS) enforce( ethereum_address != DEFAULT_ETHEREUM_ADDRESS and LedgerApis.is_valid_address("ethereum", ethereum_address), f"Not a valid ethereum_address: {ethereum_address}", ) self._ethereum_address = ethereum_address signature_of_fetchai_address = kwargs.pop( "signature_of_fetchai_address", DEFAULT_SIGNATURE_OF_FETCHAI_ADDRESS ) enforce( signature_of_fetchai_address != DEFAULT_SIGNATURE_OF_FETCHAI_ADDRESS and isinstance(signature_of_fetchai_address, str), f"Not a valid signature_of_fetchai_address: {signature_of_fetchai_address}", ) self._signature_of_fetchai_address = signature_of_fetchai_address tweet = kwargs.pop("tweet", DEFAULT_TWEET) enforce(isinstance(tweet, str), "Not a valid tweet link") self._tweet = tweet self._is_ready_to_register = False else: self._is_ready_to_register = True self._ethereum_address = "some_dummy_address" self._signature_of_fetchai_address = None self._tweet = None super().__init__(**kwargs) self._is_registered = False self.is_registration_pending = False self.signature_of_ethereum_address: Optional[str] = None self._ledger_id = self.context.default_ledger_id @property def shared_storage_key(self) -> str: """Get shared storage key.""" return self._shared_storage_key @property def whitelist(self) -> List[str]: """Get the whitelist.""" return self._whitelist @property def ethereum_address(self) -> str: """Get the ethereum address.""" return self._ethereum_address @property def ledger_id(self) -> str: """Get ledger id.""" return self._ledger_id @property def is_ready_to_register(self) -> bool: """Get readiness for registration.""" return self._is_ready_to_register @is_ready_to_register.setter def is_ready_to_register(self, is_ready_to_register: bool) -> None: """Set readiness for registration.""" self._is_ready_to_register = is_ready_to_register @property def is_registered(self) -> bool: """Get registration status.""" return self._is_registered @is_registered.setter def is_registered(self, is_registered: bool) -> None: """Set registration status.""" enforce(not self._is_registered, "Can only switch to true.") self._is_registered = is_registered @property def registration_info(self) -> Dict[str, str]: """ Get the location description. :return: a description of the agent's location """ if self.developer_handle_only: info = { "fetchai_address": self.context.agent_address, "developer_handle": self._developer_handle, } else: info = { "ethereum_address": self._ethereum_address, "fetchai_address": self.context.agent_address, "signature_of_ethereum_address": self.signature_of_ethereum_address, "signature_of_fetchai_address": self._signature_of_fetchai_address, "developer_handle": self._developer_handle, } if self._tweet != DEFAULT_TWEET: info.update({"tweet": self._tweet}) return info ================================================ FILE: packages/fetchai/skills/simple_aggregation/README.md ================================================ # Simple Aggregation ## Description This skill is used to find peers on the agent communication network, exchange observations of some real-world quantity, and periodically aggregate the collected observations. ## Behaviours - `search_behaviour`: searches for other aggregating peers every `search_interval` seconds, as specified in the skill configuration. - `aggregation_behaviour`: makes observations and aggregates those received from peers every `aggregation_interval` seconds, as specified in the skill configuration. ## Handlers - `aggregation`: handles `aggregation` messages to and from peers - `oef_search`: handles `oef_search` messages for interacting with the SOEF ================================================ FILE: packages/fetchai/skills/simple_aggregation/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the oracle aggregation skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_aggregation:0.3.6") ================================================ FILE: packages/fetchai/skills/simple_aggregation/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours for the oracle aggregation skill.""" from time import time from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.aggregation.message import AggregationMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_aggregation.dialogues import ( AggregationDialogues, OefSearchDialogues, ) from packages.fetchai.skills.simple_aggregation.strategy import AggregationStrategy DEFAULT_SEARCH_INTERVAL = 30.0 DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 DEFAULT_AGGREGATION_INTERVAL = 5.0 DEFAULT_SOURCE = "" DEFAULT_SIGNATURE = "" class SearchBehaviour(TickerBehaviour): """This class implements the service registration behaviour for the simple aggregation skill""" def __init__(self, **kwargs: Any) -> None: """Initialize the search behaviour.""" search_interval = cast( float, kwargs.pop("search_interval", DEFAULT_SEARCH_INTERVAL) ) self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=search_interval, **kwargs) self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """Implement the setup for the behaviour.""" self._register_agent() def act(self) -> None: """Implement the act.""" self._retry_failed_registration() strategy = cast(AggregationStrategy, self.context.strategy) query = strategy.get_location_and_service_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) self.context.outbox.put_message(message=oef_search_msg) def teardown(self) -> None: """Implement the task teardown.""" self._unregister_service() self._unregister_agent() def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" strategy = cast(AggregationStrategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """Register the agent's service.""" strategy = cast(AggregationStrategy, self.context.strategy) description = strategy.get_register_service_description() self._register(description, "registering agent's service on the SOEF.") def register_genus(self) -> None: """Register the agent's personality genus.""" strategy = cast(AggregationStrategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" strategy = cast(AggregationStrategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """Unregister service from the SOEF.""" strategy = cast(AggregationStrategy, self.context.strategy) description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering service from SOEF.") def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" strategy = cast(AggregationStrategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") class AggregationBehaviour(TickerBehaviour): """This class implements an aggregation behaviour.""" def __init__(self, **kwargs: Any) -> None: """Initialize the aggregation behaviour.""" aggregation_interval = cast( float, kwargs.pop("aggregation_interval", DEFAULT_AGGREGATION_INTERVAL) ) super().__init__(tick_interval=aggregation_interval, **kwargs) def act(self) -> None: """Implement the act.""" strategy = cast(AggregationStrategy, self.context.strategy) quantity = self.context.shared_state.get(strategy.quantity_name, {}) value = quantity.get("value", None) if value: strategy.make_observation( value, str(time()), source=DEFAULT_SOURCE, signature=DEFAULT_SIGNATURE, ) self.broadcast_observation() def broadcast_observation(self) -> None: """Send latest observation to current list of peers.""" strategy = cast(AggregationStrategy, self.context.strategy) obs = strategy.observation if obs is None: self.context.logger.info("No observation to send") return aggregation_dialogues = cast( AggregationDialogues, self.context.aggregation_dialogues ) for counterparty in strategy.peers: obs_msg, _ = aggregation_dialogues.create( counterparty=counterparty, performative=AggregationMessage.Performative.OBSERVATION, **obs, ) self.context.outbox.put_message(message=obs_msg) self.context.logger.info( "sending observation to peer={}".format(counterparty[-5:]) ) ================================================ FILE: packages/fetchai/skills/simple_aggregation/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the dialogues for the oracle aggregation skill.""" from typing import Any from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.aggregation.dialogues import ( AggregationDialogue as BaseAggregationDialogue, ) from packages.fetchai.protocols.aggregation.dialogues import ( AggregationDialogues as BaseAggregationDialogues, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) AggregationDialogue = BaseAggregationDialogue class AggregationDialogues(Model, BaseAggregationDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseAggregationDialogue.Role.AGENT BaseAggregationDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=AggregationDialogue, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/simple_aggregation/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers for the oracle aggregation skill.""" from typing import Any, Dict, Optional, cast from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.aggregation.message import AggregationMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_aggregation.behaviours import SearchBehaviour from packages.fetchai.skills.simple_aggregation.dialogues import ( AggregationDialogue, AggregationDialogues, DefaultDialogues, OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.simple_aggregation.strategy import AggregationStrategy def get_observation_from_message(obs_msg: AggregationMessage) -> Dict[str, Any]: """ Extract the observation from an observation message :param obs_msg: the message :return: the observation dict """ obs = { "value": obs_msg.value, "time": obs_msg.time, "source": obs_msg.source, "signature": obs_msg.signature, } return obs class AggregationHandler(Handler): """This class implements a simple aggregation handler.""" SUPPORTED_PROTOCOL = AggregationMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ aggregation_msg = cast(AggregationMessage, message) # recover dialogue aggregation_dialogues = cast( AggregationDialogues, self.context.aggregation_dialogues ) aggregation_dialogue = cast( AggregationDialogue, aggregation_dialogues.update(aggregation_msg) ) if aggregation_dialogue is None: self._handle_unidentified_dialogue(aggregation_msg) return # handle message if aggregation_msg.performative == AggregationMessage.Performative.OBSERVATION: self._handle_observation(aggregation_msg) elif ( aggregation_msg.performative == AggregationMessage.Performative.AGGREGATION ): self._handle_aggregation(aggregation_msg) else: self._handle_invalid( aggregation_msg, aggregation_dialogue ) # pragma: nocover def _handle_unidentified_dialogue( self, aggregation_msg: AggregationMessage ) -> None: """ Handle an unidentified dialogue. :param aggregation_msg: the message """ self.context.logger.info( "received invalid aggregation message={}, unidentified dialogue.".format( aggregation_msg ) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=aggregation_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"aggregation_message": aggregation_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _handle_observation(self, obs_msg: AggregationMessage) -> None: """ Handle the observation. :param obs_msg: the message """ self.context.logger.info( "received observation from sender={}".format(obs_msg.sender[-5:]) ) strategy = cast(AggregationStrategy, self.context.strategy) obs = get_observation_from_message(obs_msg) strategy.add_observation(obs_msg.sender, obs) strategy.aggregate_observations() self.context.logger.info(f"observation: {obs}") def _handle_aggregation(self, aggregation_msg: AggregationMessage) -> None: """ Handle the aggregation. :param aggregation_msg: the message """ self.context.logger.info( "received aggregation from sender={}".format(aggregation_msg.sender[-5:]) ) def _handle_invalid( self, aggregation_msg: AggregationMessage, aggregation_dialogue: AggregationDialogue, ) -> None: """ Handle a aggregation message of invalid performative. :param aggregation_msg: the message :param aggregation_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle aggregation message of performative={} in dialogue={}.".format( aggregation_msg.performative, aggregation_dialogue ) ) # pragma: nocover def teardown(self) -> None: """Implement the handler teardown.""" class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( SearchBehaviour, self.context.behaviours.search, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( SearchBehaviour, self.context.behaviours.search, ) registration_behaviour.failed_registration_msg = target_message def _handle_search( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle the search response. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ if len(oef_search_msg.agents) == 0: self.context.logger.info( f"found no agents in dialogue={oef_search_dialogue}, continue searching." ) return strategy = cast(AggregationStrategy, self.context.strategy) self.context.logger.info( "found agents={}.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) strategy.add_peers(oef_search_msg.agents) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/simple_aggregation/skill.yaml ================================================ name: simple_aggregation author: fetchai version: 0.3.6 type: skill description: The skill for aggregating observations between AEAs license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmcHh82NQkgDx4w2cNN6sGzWquSXEYp4BgPrbSBo5tTf2w __init__.py: QmacrtqzSrgzHTp2bqiVLPjDMydiVKNN6AtJG8sEzRVu1o behaviours.py: QmXsdJUEC9MPAGQvUgUYuZV8du9Dtejexbe941sqgYaSzh dialogues.py: QmeGB6miynaHtBG9LU96tiwCp8AKZwJ8y9oGqVeRNkWpvW handlers.py: QmNYga7nHVfyrqsnHCPYXKSVkY9gvsxHCQufHR4Ls2AVyD strategy.py: QmTepCGXskTbVh9AqigU5JN4my9Qbj4prvtsPVrreYnt6p fingerprint_ignore_patterns: [] contracts: [] protocols: - fetchai/aggregation:0.2.7 - fetchai/default:1.1.7 - fetchai/oef_search:1.1.7 skills: [] behaviours: aggregation: args: aggregation_interval: 10 class_name: AggregationBehaviour search: args: max_soef_registration_retries: 5 search_interval: 10 class_name: SearchBehaviour handlers: aggregation: args: {} class_name: AggregationHandler oef_search: args: {} class_name: OefSearchHandler models: aggregation_dialogues: args: {} class_name: AggregationDialogues default_dialogues: args: {} class_name: DefaultDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: aggregation_function: mean classification: piece: classification value: agent decimals: 0 location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data quantity_name: null search_query: constraint_type: == search_key: service search_value: generic_aggregation_service search_radius: 5.0 service_id: generic_aggregation_service class_name: AggregationStrategy dependencies: {} is_abstract: false connections: [] ================================================ FILE: packages/fetchai/skills/simple_aggregation/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the strategy for the oracle aggregation skill.""" import statistics from typing import Any, Dict, Optional, Set, Tuple from aea.common import Address from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.skills.base import Model DEFAULT_SEARCH_RADIUS = 5.0 DEFAULT_SERVICE_ID = "aggregation" DEFAULT_AGGREGATION_FUNCTION = "mean" DEFAULT_SERVICE_DATA = {"key": "service", "value": "generic_aggregation_service"} DEFAULT_QUANTITY_NAME = "quantity" DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "agent"} DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "service", "search_value": "generic_aggregation_service", "constraint_type": "==", } IMPLEMENTED_AGGREGATION_FUNCTIONS = {"mean", "median", "mode"} DEFAULT_DECIMALS = 0 class AggregationStrategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ self._round = 0 self._peers = set() # type: Set[Address] self._observations = {} # type: Dict[Address, Dict[str, Any]] self._aggregation = None # type: Optional[Any] self._quantity_name = kwargs.pop("quantity_name", DEFAULT_QUANTITY_NAME) self._service_id = kwargs.pop("service_id", DEFAULT_SERVICE_ID) self._aggregation_function = kwargs.pop( "aggregation_function", DEFAULT_AGGREGATION_FUNCTION ) enforce( self._aggregation_function in IMPLEMENTED_AGGREGATION_FUNCTIONS, f"aggregation_function must be one of {IMPLEMENTED_AGGREGATION_FUNCTIONS}", ) self._aggregate = getattr(statistics, self._aggregation_function) self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self._decimals = kwargs.pop("decimals", DEFAULT_DECIMALS) self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} self._simple_service_data = { self._set_service_data["key"]: self._set_service_data["value"] } super().__init__(**kwargs) ledger_id = kwargs.pop("ledger_id", None) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id # pragma: nocover @property def observation(self) -> Optional[Dict[str, Any]]: """Get latest observation""" my_address = self.context.agent_addresses[self._ledger_id] return self._observations.get(my_address, None) @property def peers(self) -> Set[str]: """Get registered peers.""" return self._peers @property def quantity_name(self) -> str: """Get the name of the quantity to aggregate.""" return self._quantity_name def add_peers(self, found_peers: Tuple[str, ...]) -> None: """ Update registered peers with list found in search :param found_peers: the peers found """ for _, peer in enumerate(found_peers): self._peers.add(peer) def add_observation(self, peer: Address, obs: Dict[str, Any]) -> None: """Add new observation to list of observations""" if peer in self._peers: self._observations[peer] = obs def make_observation( self, value: int, obs_time: str, source: str = "", signature: str = "" ) -> None: """Make observation of oracle value""" my_address = self.context.agent_addresses[self._ledger_id] observation = dict( value=value, time=obs_time, source=source, signature=signature ) self.context.logger.info(f"Observation: {observation}") self._observations[my_address] = observation def aggregate_observations(self) -> None: """Aggregate values from all observations from myself and peers""" values = [float(obs["value"]) for obs in self._observations.values()] if len(values) == 0: # pragma: nocover self.context.logger.info("No observations to aggregate") return self._aggregation = self._aggregate(values) aggregated_key = self.quantity_name + "_" + self._aggregation_function self.context.shared_state[aggregated_key] = { "value": int(self._aggregation), "decimals": self._decimals, } self.context.logger.info(f"Observations: {values}") self.context.logger.info( f"Aggregation ({self._aggregation_function}): {self._aggregation}" ) def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType( "distance", (self._agent_location["location"], self._radius) ), ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query ================================================ FILE: packages/fetchai/skills/simple_buyer/README.md ================================================ # Simple Buyer ## Description This skill purchases information from other agents as specified in its configuration. This skill searches for an agent in a vicinity on the sOEF that sells the data this skill is configured to buy. Once found, it requests this data, negotiates the price, pays the proposed amount if agreement is reached, and receives the data bought. ## Behaviours - `search`: searches for data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Models - `strategy`: allows the configuration of the purchasing. In particular, `location` and `search_radius` together determine the vicinity where the service is searched for, `search_query` specifies the query, and the remaining configuration specifies the terms of trade. ================================================ FILE: packages/fetchai/skills/simple_buyer/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the buyer_aw2 skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_buyer:0.13.6") ================================================ FILE: packages/fetchai/skills/simple_buyer/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours of the agent.""" from packages.fetchai.skills.generic_buyer.behaviours import ( GenericSearchBehaviour, GenericTransactionBehaviour, ) SearchBehaviour = GenericSearchBehaviour TransactionBehaviour = GenericTransactionBehaviour ================================================ FILE: packages/fetchai/skills/simple_buyer/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - SigningDialogues: The dialogues class keeps track of all dialogues of type signing. """ from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( SigningDialogues as GenericSigningDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues SigningDialogues = GenericSigningDialogues ================================================ FILE: packages/fetchai/skills/simple_buyer/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of the agent.""" from packages.fetchai.skills.generic_buyer.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, GenericSigningHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler SigningHandler = GenericSigningHandler ================================================ FILE: packages/fetchai/skills/simple_buyer/skill.yaml ================================================ name: simple_buyer author: fetchai version: 0.13.6 type: skill description: This skill purchases information from other agents as specified in its configuration. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmSYbruWV7Cte46FvKgPK8LcJXsShGMTmW6Q2oMxekz327 __init__.py: QmRTEKXBc4r4iTUSUTR49ypjPLCZtMGPs13Fuu1c7XNQtM behaviours.py: QmSr6fB3N7dhVo1cLY1TGd2q8usjGwNmTCNjBBbtgtVf9j dialogues.py: QmXgXcs25v9ob9a9XwT49wvK788vbUdZyYxUgG3ndHjrix handlers.py: QmP3Q6x3NMcWgRi6H5GtDtvnLWSoB1HeG8vTd4zcRZUgNj strategy.py: QmdHPLehqRr1dxuCbp4ENYmVMb8Ykvzg2Uzfos5kFJSr3D fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/generic_buyer:0.27.6 behaviours: search: args: search_interval: 5 class_name: SearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_quantity: 100 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_query: constraint_type: == search_key: seller_service search_value: weather_data search_radius: 50.0 service_id: weather_data class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/simple_buyer/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy Strategy = GenericStrategy ================================================ FILE: packages/fetchai/skills/simple_data_request/README.md ================================================ # Simple Data Request ## Description This skill is used to request data from a HTTP endpoint and then save it in the AEA's shared state for use by other skills. ## Behaviours - `http_request`: requests data every `request_interval` seconds from a HTTP endpoint using the `url`, `method` and `body` specified in the skill configuration. ## Handlers - `http`: handles incoming `http` messages. Data received in responses is saved in the shared state using the key specified in the skill configuration: `shared_state_key`. ================================================ FILE: packages/fetchai/skills/simple_data_request/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the simple_data_request skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_data_request:0.14.6") ================================================ FILE: packages/fetchai/skills/simple_data_request/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a a behaviour to request data from a HTTP endpoint.""" import json from typing import Any, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_PUBLIC_ID, ) from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.simple_data_request.dialogues import HttpDialogues DEFAULT_REQUEST_INTERVAL = 20.0 class HttpRequestBehaviour(TickerBehaviour): """This class defines an http request behaviour.""" def __init__(self, **kwargs: Any): """Initialise the behaviour.""" request_interval = kwargs.pop( "request_interval", DEFAULT_REQUEST_INTERVAL ) # type: int self.url = kwargs.pop("url", None) self.method = kwargs.pop("method", None) self.body = kwargs.pop("body", None) self.lookup_termination_key = kwargs.pop("lookup_termination_key", None) if self.url is None or self.method is None or self.body is None: raise ValueError("Url, method and body must be provided.") super().__init__(tick_interval=request_interval, **kwargs) def setup(self) -> None: """Implement the setup.""" def act(self) -> None: """Implement the act.""" if self.lookup_termination_key is not None: prerequisite_satisfied = self.context.shared_state.get( self.lookup_termination_key, False ) if not prerequisite_satisfied: return self._generate_http_request() def _generate_http_request(self) -> None: """Generate http request to provided url with provided body and method.""" http_dialogues = cast(HttpDialogues, self.context.http_dialogues) request_http_message, _ = http_dialogues.create( counterparty=str(HTTP_CLIENT_PUBLIC_ID), performative=HttpMessage.Performative.REQUEST, method=self.method, url=self.url, headers="", version="", body=json.dumps(self.body).encode("utf-8"), ) self.context.outbox.put_message(message=request_http_message) def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/simple_data_request/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - HttpDialogue: The dialogue class maintains state of a http dialogue and manages it. - HttpDialogues: The dialogues class keeps track of all http dialogues. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues HttpDialogue = BaseHttpDialogue class HttpDialogues(Model, BaseHttpDialogues): """This class keeps track of all http dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseHttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/simple_data_request/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a handler to process HTTP responses.""" from typing import Any, cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.simple_data_request.dialogues import ( HttpDialogue, HttpDialogues, ) class HttpHandler(Handler): """This class represents the handler for HTTP messages.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id def __init__(self, **kwargs: Any): """ Initialize the handler. :param kwargs: keyword arguments """ self.shared_state_key = kwargs.pop("shared_state_key", None) if self.shared_state_key is None: raise ValueError("No shared_state_key provided!") super().__init__(**kwargs) def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to an envelope. :param message: the message """ http_msg = cast(HttpMessage, message) # recover dialogue http_dialogues = cast(HttpDialogues, self.context.http_dialogues) http_dialogue = cast(HttpDialogue, http_dialogues.update(http_msg)) if http_dialogue is None: self._handle_unidentified_dialogue(http_msg) return # handle message (only process responses, we do not expect requests) if http_msg.performative == HttpMessage.Performative.RESPONSE: self._handle_response(http_msg, http_dialogue) else: self._handle_invalid(http_msg, http_dialogue) def _handle_unidentified_dialogue(self, http_msg: HttpMessage) -> None: """ Handle an unidentified dialogue. :param http_msg: the message """ self.context.logger.info( "received invalid http message={}, unidentified dialogue.".format(http_msg) ) def _handle_response( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle a http response. :param http_msg: the message :param http_dialogue: the dialogue object """ self.context.logger.debug( "received http response={} in dialogue={}.".format(http_msg, http_dialogue) ) data_received = http_msg.body # save the data in the shared state to make it accessible to other skills self.context.logger.info( "updating shared_state with received data={!r}!".format(data_received) ) self.context.shared_state[self.shared_state_key] = data_received def _handle_invalid( self, http_msg: HttpMessage, http_dialogue: HttpDialogue ) -> None: """ Handle a http message of invalid performative. :param http_msg: the message :param http_dialogue: the dialogue object """ self.context.logger.warning( "cannot handle http message of performative={} in dialogue={}.".format( http_msg.performative, http_dialogue ) ) def teardown(self) -> None: """Implement the handler teardown.""" ================================================ FILE: packages/fetchai/skills/simple_data_request/skill.yaml ================================================ name: simple_data_request author: fetchai version: 0.14.6 type: skill description: This skill is used to request data from a HTTP endpoint and then saving it in the AEA's shared state for use by other skills. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmemL4YrZjQSMjhF2uymRmntNHSoyvAQ66R9TtnEwmDxiv __init__.py: QmW6iSrqrczrFvKxaTgkyQXneR4ckMimYZheFu8EHsB66N behaviours.py: QmcAsxDGEjf2zofag5X3PxmaPBwrjqf9zoSZVdLwCUFRaC dialogues.py: QmVYsxmS1okvCqTQFs6PwNRWvzP1YeguD1FxLopQWe1a9h handlers.py: QmZ52t9KWAzLLrUv5ZZvfFT4CgB4YvN9HceDcWuQBvRqxT fingerprint_ignore_patterns: [] connections: - fetchai/http_client:0.24.6 contracts: [] protocols: - fetchai/http:1.1.7 skills: [] behaviours: http_request: args: body: '' lookup_termination_key: null method: null request_interval: 20 url: null class_name: HttpRequestBehaviour handlers: http: args: shared_state_key: null class_name: HttpHandler models: http_dialogues: args: {} class_name: HttpDialogues dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/simple_oracle/README.md ================================================ # Simple Oracle ## Description This skill is used to deploy an oracle smart contract to a ledger, grant oracle permissions, and periodically update the oracle value in the contract. ## Behaviours - `simple_oracle_behaviour`: deploys oracle contract, grants the oracle role to the agent address, and updates the oracle value every `tick_interval` seconds, as specified in the skill configuration. ## Handlers - `contract_api`: handles `contract_api` messages for interactions with a smart contract - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `prometheus`: handles `prometheus` messages for interactions with a prometheus server - `signing`: handles `signing` messages for transaction signing by the decision maker ================================================ FILE: packages/fetchai/skills/simple_oracle/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the simple oracle skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_oracle:0.16.5") ================================================ FILE: packages/fetchai/skills/simple_oracle/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a simple Fetch oracle contract deployment behaviour.""" from typing import Any, Dict, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import CONNECTION_ID as LEDGER_API_ADDRESS from packages.fetchai.connections.prometheus.connection import ( PUBLIC_ID as PROM_CONNECTION_ID, ) from packages.fetchai.contracts.oracle.contract import PUBLIC_ID as CONTRACT_PUBLIC_ID from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.skills.simple_oracle.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogues, PrometheusDialogues, ) from packages.fetchai.skills.simple_oracle.strategy import Strategy DEFAULT_UPDATE_INTERVAL = 5 DEFAULT_DECIMALS = 5 EXPIRATION_BLOCK = 1000000000000000 class SimpleOracleBehaviour(TickerBehaviour): """This class implements a behaviour that deploys a Fetch oracle contract.""" def __init__(self, **kwargs: Any): """Initialise the behaviour.""" update_interval = kwargs.pop( "update_interval", DEFAULT_UPDATE_INTERVAL ) # type: int super().__init__(tick_interval=update_interval, **kwargs) def setup(self) -> None: """Implement the setup.""" self.context.logger.info("Setting up Fetch oracle contract...") strategy = cast(Strategy, self.context.strategy) if not strategy.is_contract_deployed: self._request_contract_deploy_transaction() else: self.context.logger.info("Fetch oracle contract address already added") if strategy.is_oracle_role_granted: self.context.logger.info("Oracle role already granted") prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) if prom_dialogues.enabled: for metric in prom_dialogues.metrics: metric_name = metric["name"] self.context.logger.info("Adding Prometheus metric: " + metric_name) self.add_prometheus_metric( metric_name, metric["type"], metric["description"], dict(metric["labels"]), ) def act(self) -> None: """Implement the act.""" strategy = cast(Strategy, self.context.strategy) # Request account balance self._get_balance() if not strategy.is_contract_deployed: self.context.logger.info("Oracle contract not yet deployed") return if not strategy.is_oracle_role_granted: self.context.logger.info("Oracle not yet created") self._request_grant_role_transaction() return # Check for oracle from data collecting skill oracle_data = self.context.shared_state.get(strategy.oracle_value_name, None) if oracle_data is None: self.context.logger.info("No oracle value to publish") else: self.context.logger.info("Publishing oracle value") self.context.logger.info(f"Update kwargs: {oracle_data}") self._request_update_transaction(oracle_data) def _request_contract_deploy_transaction(self) -> None: """Request contract deployment transaction.""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=strategy.get_deploy_kwargs(), ) contract_api_dialogue = cast( ContractApiDialogue, contract_api_dialogue, ) contract_api_dialogue.terms = strategy.get_deploy_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting contract deployment transaction...") def _request_grant_role_transaction(self) -> None: """Request transaction that grants oracle role in a Fetch oracle contract.""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=str(CONTRACT_PUBLIC_ID), contract_address=strategy.contract_address, callable="get_grant_role_transaction", kwargs=ContractApiMessage.Kwargs( { "oracle_address": self.context.agent_address, "gas": strategy.gas_limit_grant_role, "tx_fee": strategy.gas_price * strategy.gas_limit_grant_role, } ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_grant_role_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting grant role transaction...") @staticmethod def _get_tx_expriration_block() -> int: """Return max block number for no expiration.""" return 2**256 - 1 def _request_update_transaction(self, update_kwargs: Dict[str, Any]) -> None: """Request transaction that updates value in Fetch oracle contract.""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) update_kwargs = { **update_kwargs, "txExpirationBlock": self._get_tx_expriration_block(), } contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=str(CONTRACT_PUBLIC_ID), contract_address=strategy.contract_address, callable="get_update_transaction", kwargs=ContractApiMessage.Kwargs( { "oracle_address": self.context.agent_address, "update_function": strategy.update_function, "update_kwargs": update_kwargs, "gas": strategy.gas_limit_update, "tx_fee": strategy.gas_price * strategy.gas_limit_update, } ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_update_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting update transaction...") def _get_balance(self) -> None: """Request balance of agent account by sending a message to the ledger API.""" strategy = cast(Strategy, self.context.strategy) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, _ = ledger_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=strategy.ledger_id, address=cast(str, self.context.agent_addresses.get(strategy.ledger_id)), ) self.context.outbox.put_message(message=ledger_api_msg) def add_prometheus_metric( self, metric_name: str, metric_type: str, description: str, labels: Dict[str, str], ) -> None: """ Add a prometheus metric. :param metric_name: the name of the metric to add. :param metric_type: the type of the metric. :param description: a description of the metric. :param labels: the metric labels. """ # context prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) # prometheus update message message, _ = prom_dialogues.create( counterparty=str(PROM_CONNECTION_ID), performative=PrometheusMessage.Performative.ADD_METRIC, type=metric_type, title=metric_name, description=description, labels=labels, ) # send message self.context.outbox.put_message(message=message) def update_prometheus_metric( self, metric_name: str, update_func: str, value: float, labels: Dict[str, str], ) -> None: """ Update a prometheus metric. :param metric_name: the name of the metric. :param update_func: the name of the update function (e.g. inc, dec, set, ...). :param value: the value to provide to the update function. :param labels: the metric labels. """ # context prom_dialogues = cast(PrometheusDialogues, self.context.prometheus_dialogues) # prometheus update message message, _ = prom_dialogues.create( counterparty=str(PROM_CONNECTION_ID), performative=PrometheusMessage.Performative.UPDATE_METRIC, title=metric_name, callable=update_func, value=value, labels=labels, ) # send message self.context.outbox.put_message(message=message) def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/simple_oracle/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the ContractAPI and LedgerAPI dialogues.""" from typing import Any, Optional, Type from aea.common import Address from aea.exceptions import enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue as BaseContractApiDialogue, ) from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.prometheus.dialogues import ( PrometheusDialogue as BasePrometheusDialogue, ) from packages.fetchai.protocols.prometheus.dialogues import ( PrometheusDialogues as BasePrometheusDialogues, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage PrometheusDialogue = BasePrometheusDialogue class ContractApiDialogue(BaseContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_terms",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get the terms.""" if self._terms is None: # pragma: nocover raise ValueError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set the terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class ContractApiDialogues(Model, BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=ContractApiDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_signing_dialogue", "_associated_transaction_type") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_signing_dialogue = None # type: Optional[SigningDialogue] self._associated_transaction_type = None # type: Optional[str] @property def associated_signing_dialogue(self) -> "SigningDialogue": """Get the associated signing dialogue.""" if self._associated_signing_dialogue is None: # pragma: nocover raise ValueError("Associated signing dialogue not set!") return self._associated_signing_dialogue @associated_signing_dialogue.setter def associated_signing_dialogue( self, associated_signing_dialogue: "SigningDialogue" ) -> None: """Set the associated signing dialogue.""" enforce( self._associated_signing_dialogue is None, "Associated signing dialogue already set!", ) self._associated_signing_dialogue = associated_signing_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_contract_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_contract_api_dialogue = ( None ) # type: Optional[ContractApiDialogue] @property def associated_contract_api_dialogue(self) -> ContractApiDialogue: """Get the associated contract api dialogue.""" if self._associated_contract_api_dialogue is None: # pragma: nocover raise ValueError("Associated contract api dialogue not set!") return self._associated_contract_api_dialogue @associated_contract_api_dialogue.setter def associated_contract_api_dialogue( self, associated_contract_api_dialogue: ContractApiDialogue ) -> None: """Set the associated contract api dialogue.""" enforce( self._associated_contract_api_dialogue is None, "Associated contract api dialogue already set!", ) self._associated_contract_api_dialogue = associated_contract_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) class PrometheusDialogues(Model, BasePrometheusDialogues): """The dialogues class keeps track of all prometheus dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ self.enabled = kwargs.pop("enabled", False) self.metrics = kwargs.pop("metrics", []) Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return PrometheusDialogue.Role.AGENT BasePrometheusDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/simple_oracle/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers for the Fetch oracle contract deployer.""" from typing import Optional, cast from aea_ledger_fetchai import FetchAIApi from aea.common import JSONLike from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import CONNECTION_ID as LEDGER_API_ADDRESS from packages.fetchai.contracts.oracle.contract import PUBLIC_ID as CONTRACT_PUBLIC_ID from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.simple_oracle.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogue, LedgerApiDialogues, PrometheusDialogue, PrometheusDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.simple_oracle.strategy import Strategy class LedgerApiHandler(Handler): """Implement the ledger api handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ self.context.logger.info("Handling ledger api msg") ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "Balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) if self.context.prometheus_dialogues.enabled: metric_name = "oracle_account_balance_ETH" self.context.behaviours.simple_oracle_behaviour.update_prometheus_metric( metric_name, "set", float(ledger_api_msg.balance), {} ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.outbox.put_message(message=msg) self.context.logger.info("requesting transaction receipt.") def _request_init_transaction(self, ledger_id: str, tx_receipt: JSONLike) -> None: """ Send a message to request the initialisation transaction to finalise contract deployment :param ledger_id: the ledger id :param tx_receipt: the transaction receipt """ strategy = cast(Strategy, self.context.strategy) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) fetchai_api = cast(FetchAIApi, LedgerApis.get_api(ledger_id)) code_id = fetchai_api.get_code_id(tx_receipt) # type: Optional[int] if code_id: contract_api_msg, dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=ledger_id, contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( { "label": "OracleContract", "init_msg": {"fee": str(strategy.initial_fee_deploy)}, "gas": strategy.gas_limit_instantiate, "tx_fee": strategy.gas_price * strategy.gas_limit_instantiate, "amount": 1, "code_id": code_id, "deployer_address": self.context.agent_address, } ), ) contract_api_dialogue = cast(ContractApiDialogue, dialogue) contract_api_dialogue.terms = strategy.get_deploy_terms( is_init_transaction=True ) self.context.outbox.put_message(message=contract_api_msg) else: # pragma: nocover self.context.logger.info("Failed to initialize contract: code_id not found") def _save_contract_address_to_file(self) -> None: """Save the oracle contract address to a text file if specified in config.""" strategy = cast(Strategy, self.context.strategy) if strategy.contract_address_file: # pragma: nocover self.context.logger.info( f"Saving contract address to file: {strategy.contract_address_file}" ) with open(strategy.contract_address_file, "w", encoding="utf-8") as file: file.write(strategy.contract_address) def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_receipt performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ ledger_id = cast(str, ledger_api_msg.transaction_receipt.ledger_id) tx_receipt = cast(JSONLike, ledger_api_msg.transaction_receipt.receipt) is_transaction_successful = LedgerApis.is_transaction_settled( ledger_id, tx_receipt, ) if is_transaction_successful: self.context.logger.info( "transaction was successfully settled. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) strategy = cast(Strategy, self.context.strategy) contract_api_dialogue = ( ledger_api_dialogue.associated_signing_dialogue.associated_contract_api_dialogue ) transaction_label = contract_api_dialogue.terms.kwargs.get("label", "None") if not strategy.is_contract_deployed: if transaction_label == "store": self._request_init_transaction(ledger_id, tx_receipt) elif transaction_label in {"deploy", "init"}: contract_address = LedgerApis.get_contract_address( ledger_id, tx_receipt ) if contract_address is None: raise ValueError( "No contract address found." ) # pragma: nocover strategy.contract_address = contract_address strategy.is_contract_deployed = True self._save_contract_address_to_file() self.context.logger.info( f"Oracle contract successfully deployed at address: {contract_address}" ) else: self.context.logger.error( f"Invalid transaction label: {transaction_label}" ) # pragma: nocover elif ( not strategy.is_oracle_role_granted and transaction_label == "grant_role" ): strategy.is_oracle_role_granted = is_transaction_successful if is_transaction_successful: self.context.logger.info("Oracle role successfully granted!") else: # pragma: nocover self.context.logger.info("Failed to grant oracle role") elif transaction_label == "update": self.context.logger.info("Oracle value successfully updated!") if self.context.prometheus_dialogues.enabled: metric_name = "num_oracle_updates" self.context.behaviours.simple_oracle_behaviour.update_prometheus_metric( metric_name, "inc", 1.0, {} ) else: # pragma: nocover self.context.logger.error("unexpected transaction receipt!") else: self.context.logger.error( "transaction failed. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class ContractApiHandler(Handler): """Implement the contract api handler.""" SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ self.context.logger.info("Handling contract api msg") contract_api_msg = cast(ContractApiMessage, message) # recover dialogue contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_dialogue = cast( Optional[ContractApiDialogue], contract_api_dialogues.update(contract_api_msg), ) if contract_api_dialogue is None: self._handle_unidentified_dialogue(contract_api_msg) return # handle message if ( contract_api_msg.performative is ContractApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(contract_api_msg, contract_api_dialogue) elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: self._handle_error(contract_api_msg, contract_api_dialogue) else: self._handle_invalid(contract_api_msg, contract_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue( self, contract_api_msg: ContractApiMessage ) -> None: """ Handle an unidentified dialogue. :param contract_api_msg: the message """ self.context.logger.info( "received invalid contract_api message={}, unidentified dialogue.".format( contract_api_msg ) ) def _handle_raw_transaction( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_transaction performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=contract_api_msg.raw_transaction, terms=contract_api_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of error performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( contract_api_msg, contract_api_dialogue ) ) def _handle_invalid( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of invalid performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle contract_api message of performative={} in dialogue={}.".format( contract_api_msg.performative, contract_api_dialogue, ) ) class SigningHandler(Handler): """Implement the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, signed_transaction=signing_msg.signed_transaction, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) class PrometheusHandler(Handler): """This class handles responses from the prometheus server.""" SUPPORTED_PROTOCOL = PrometheusMessage.protocol_id def setup(self) -> None: """Set up the handler.""" if self.context.prometheus_dialogues.enabled: self.context.logger.info("setting up PrometheusHandler") def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ message = cast(PrometheusMessage, message) # recover dialogue prometheus_dialogues = cast( PrometheusDialogues, self.context.prometheus_dialogues ) prometheus_dialogue = cast( PrometheusDialogue, prometheus_dialogues.update(message) ) if prometheus_dialogue is None: self._handle_unidentified_dialogue(message) return if message.performative == PrometheusMessage.Performative.RESPONSE: self.context.logger.debug( f"Prometheus response ({message.code}): {message.message}" ) else: # pragma: nocover self.context.logger.debug( f"got unexpected prometheus message: Performative = {PrometheusMessage.Performative}" ) def _handle_unidentified_dialogue(self, msg: Message) -> None: """ Handle an unidentified dialogue. :param msg: the unidentified message to be handled """ self.context.logger.info( "received invalid message={}, unidentified dialogue.".format(msg) ) def teardown(self) -> None: """Teardown the handler.""" ================================================ FILE: packages/fetchai/skills/simple_oracle/skill.yaml ================================================ name: simple_oracle author: fetchai version: 0.16.5 type: skill description: This skill deploys a Fetch oracle contract license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qme3QavjkmFm4HWMLEAJpAHCYd4miKq2j2LXayRDSRSGiw __init__.py: QmYuLwWrL7SLS8H2W3R5SkVtpp5ufJSmjuNkzko6AbN6zS behaviours.py: QmXyscjzvxLeJcssKgpuxqXwDyX1QGcwPPvEcMa5ymyTbH dialogues.py: QmS4XJDpNXxjKewDUk6HREXrm8pa3KndTR7vzppVcAURCt handlers.py: QmQm7ofZBu3HHB1ETwBzmnQiTFHNeouVFssNrfQeJ9Rpfd strategy.py: QmWw9SzUtphNC1mjST5KNnT5zkjAP7EPu9n4qkeDtJXL2B fingerprint_ignore_patterns: [] contracts: - fetchai/oracle:0.12.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/prometheus:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: simple_oracle_behaviour: args: update_interval: 15 class_name: SimpleOracleBehaviour handlers: contract_api: args: {} class_name: ContractApiHandler ledger_api: args: {} class_name: LedgerApiHandler prometheus: args: {} class_name: PrometheusHandler signing: args: {} class_name: SigningHandler models: contract_api_dialogues: args: {} class_name: ContractApiDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues prometheus_dialogues: args: enabled: true metrics: - name: oracle_account_balance_ETH type: Gauge description: Balance of oracle contract (ETH) labels: {} - name: num_oracle_updates type: Gauge description: Number of updates published to oracle contract labels: {} class_name: PrometheusDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: contract_address: null contract_address_file: null erc20_address: '0x0000000000000000000000000000000000000000' gas_limit_deploy: 1500000 gas_limit_grant_role: 1500000 gas_limit_instantiate: 500000 gas_limit_update: 1500000 gas_price: 1000000000 initial_fee_deploy: 1000000000000 is_oracle_role_granted: false ledger_id: null oracle_value_name: null update_function: null class_name: Strategy dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false connections: [] ================================================ FILE: packages/fetchai/skills/simple_oracle/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from typing import Any from aea_ledger_ethereum import EthereumApi from aea.configurations.constants import DEFAULT_LEDGER from aea.exceptions import enforce from aea.helpers.transaction.base import Terms from aea.skills.base import Model from packages.fetchai.protocols.contract_api.custom_types import Kwargs DEFAULT_LEDGER_ID = DEFAULT_LEDGER MAX_BLOCK_HEIGHT = 1000000000000000000 DEFAULT_ORACLE_VALUE_NAME = "oracle_value" class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """Initialize the strategy of the agent.""" self._ledger_id = kwargs.pop("ledger_id", DEFAULT_LEDGER_ID) self._contract_address = kwargs.pop("contract_address", None) self._erc20_address = kwargs.pop("erc20_address", None) self._update_function = kwargs.pop("update_function", None) self._is_oracle_role_granted = kwargs.pop("is_oracle_role_granted", False) self._initial_fee_deploy = kwargs.pop("initial_fee_deploy", 0) self._gas_limit_deploy = kwargs.pop("gas_limit_deploy", 0) self._gas_limit_instantiate = kwargs.pop("gas_limit_instantiate", 0) self._gas_limit_grant_role = kwargs.pop("gas_limit_grant_role", 0) self._gas_limit_update = kwargs.pop("gas_limit_update", 0) self._gas_price = kwargs.pop("gas_price", 0) self._oracle_value_name = kwargs.pop( "oracle_value_name", DEFAULT_ORACLE_VALUE_NAME ) self._contract_address_file = kwargs.pop("contract_address_file", None) super().__init__(**kwargs) self.is_behaviour_active = True self._is_contract_deployed = self._contract_address is not None @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def update_function(self) -> str: """Get the name of the oracle value update function.""" return self._update_function @property def initial_fee_deploy(self) -> int: """Get the initial for contract deployment.""" return self._initial_fee_deploy @property def gas_limit_deploy(self) -> int: """Get the gas limit for deploying a contract.""" return self._gas_limit_deploy @property def gas_limit_instantiate(self) -> int: """Get the default gas for instantiating a contract.""" return self._gas_limit_instantiate @property def gas_limit_grant_role(self) -> int: """Get the gas limit for role granting.""" return self._gas_limit_grant_role @property def gas_limit_update(self) -> int: """Get the gas limit for updating a value.""" return self._gas_limit_update @property def gas_price(self) -> int: """Get the gas price.""" return self._gas_price @property def oracle_value_name(self) -> str: """Get the name of the oracle value.""" return self._oracle_value_name @property def contract_address_file(self) -> str: """Get the filename where the oracle contract is to be stored.""" return self._contract_address_file @property def contract_address(self) -> str: """Get the contract address.""" if self._contract_address is None: # pragma: nocover raise ValueError("Contract address not set!") return self._contract_address @contract_address.setter def contract_address(self, contract_address: str) -> None: """Set the contract address.""" enforce(self._contract_address is None, "Contract address already set!") self._contract_address = contract_address @property def erc20_address(self) -> str: """Get the erc20 address for token payment.""" if self._erc20_address is None: # pragma: nocover raise ValueError("ERC20 address not set!") return self._erc20_address @erc20_address.setter def erc20_address(self, erc20_address: str) -> None: # pragma: nocover """Set the erc20 address for token payment.""" enforce(self._erc20_address is None, "ERC20 address already set!") self._erc20_address = erc20_address @property def is_contract_deployed(self) -> bool: """Get contract deploy status.""" return self._is_contract_deployed @is_contract_deployed.setter def is_contract_deployed(self, is_contract_deployed: bool) -> None: """Set contract deploy status.""" enforce( not self._is_contract_deployed and is_contract_deployed, "Only allowed to switch to true.", ) self._is_contract_deployed = is_contract_deployed @property def is_oracle_role_granted(self) -> bool: """Get oracle role status.""" return self._is_oracle_role_granted @is_oracle_role_granted.setter def is_oracle_role_granted(self, is_oracle_role_granted: bool) -> None: """Set oracle role status.""" enforce( not self._is_oracle_role_granted and is_oracle_role_granted, "Only allowed to switch to true.", ) self._is_oracle_role_granted = is_oracle_role_granted def get_deploy_terms(self, is_init_transaction: bool = False) -> Terms: """ Get terms of deployment. :param is_init_transaction: whether the transaction is init or store. :return: terms """ if self.ledger_id == EthereumApi.identifier: label = "deploy" else: if is_init_transaction: label = "init" else: label = "store" terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", label=label, ) return terms def get_grant_role_terms(self) -> Terms: """ Get terms of oracle role granting. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", label="grant_role", ) return terms def get_update_terms(self) -> Terms: """ Get terms of update transaction. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", label="update", ) return terms def get_deploy_kwargs(self) -> Kwargs: """ Get kwargs for the contract deployment :return: kwargs """ if self.ledger_id == EthereumApi.identifier: kwargs = Kwargs( { "deployer_address": self.context.agent_address, "gas": self.gas_limit_deploy, "ERC20Address": self.erc20_address, "initialFee": self.initial_fee_deploy, } ) else: kwargs = Kwargs( { "deployer_address": self.context.agent_address, "gas": self.gas_limit_deploy, "tx_fee": self.gas_price * self.gas_limit_deploy, } ) return kwargs ================================================ FILE: packages/fetchai/skills/simple_oracle_client/README.md ================================================ # Simple Oracle Client ## Description This skill is used to deploy an oracle client smart contract to a ledger, approve the contract to make FET payments on behalf of the agent, and periodically call the contract function that requests the oracle value. ## Behaviours - `simple_oracle_client_behaviour`: deploys oracle client contract, approves contract transactions, and calls the contract to request the oracle value every `tick_interval` seconds, as specified in the skill configuration. ## Handlers - `contract_api`: handles `contract_api` messages for interactions with the smart contract - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `signing`: handles `signing` messages for transaction signing by the decision maker ================================================ FILE: packages/fetchai/skills/simple_oracle_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the simple oracle client skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_oracle_client:0.13.5") ================================================ FILE: packages/fetchai/skills/simple_oracle_client/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a simple Fetch oracle client behaviour.""" from typing import Any, cast from aea_ledger_ethereum import EthereumApi from aea.skills.behaviours import TickerBehaviour from packages.fetchai.connections.ledger.base import CONNECTION_ID as LEDGER_API_ADDRESS from packages.fetchai.contracts.fet_erc20.contract import ( PUBLIC_ID as FET_ERC20_PUBLIC_ID, ) from packages.fetchai.contracts.oracle_client.contract import ( PUBLIC_ID as CLIENT_CONTRACT_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.skills.simple_oracle_client.dialogues import ( ContractApiDialogue, ContractApiDialogues, ) from packages.fetchai.skills.simple_oracle_client.strategy import Strategy DEFAULT_QUERY_INTERVAL = 5 class SimpleOracleClientBehaviour(TickerBehaviour): """This class implements a behaviour that deploys a Fetch oracle client contract.""" def __init__(self, **kwargs: Any): """Initialise the behaviour.""" query_interval = kwargs.pop( "query_interval", DEFAULT_QUERY_INTERVAL ) # type: int super().__init__(tick_interval=query_interval, **kwargs) def setup(self) -> None: """Implement the setup.""" self.context.logger.info("Setting up Fetch oracle client contract...") strategy = cast(Strategy, self.context.strategy) if not strategy.is_client_contract_deployed: self._request_contract_deploy_transaction() else: self.context.logger.info( "Fetch oracle client contract address already added" ) def act(self) -> None: """Implement the act.""" strategy = cast(Strategy, self.context.strategy) if not strategy.is_client_contract_deployed: self.context.logger.info("Oracle client contract not yet deployed") return if ( strategy.ledger_id == EthereumApi.identifier and not strategy.is_oracle_transaction_approved ): self.context.logger.info( "Oracle client contract not yet approved to spend tokens" ) self._request_approve_transaction() return # Call contract function that queries oracle value self.context.logger.info("Calling contract to request oracle value...") self._request_query_transaction() def _request_contract_deploy_transaction(self) -> None: """Request contract deployment transaction""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=str(CLIENT_CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=strategy.get_deploy_kwargs(), ) contract_api_dialogue = cast( ContractApiDialogue, contract_api_dialogue, ) contract_api_dialogue.terms = strategy.get_deploy_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting contract deployment transaction...") def _request_approve_transaction(self) -> None: """Request transaction that approves client contract to spend tokens on behalf of sender.""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=str(FET_ERC20_PUBLIC_ID), contract_address=strategy.erc20_address, callable="get_approve_transaction", kwargs=ContractApiMessage.Kwargs( { "from_address": self.context.agent_address, "spender": strategy.client_contract_address, "amount": strategy.approve_amount, "gas": strategy.gas_limit_approve, } ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_approve_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting query transaction...") def _request_query_transaction(self) -> None: """Request transaction that requests value from Fetch oracle contract.""" strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=str(CLIENT_CONTRACT_PUBLIC_ID), contract_address=strategy.client_contract_address, callable="get_query_transaction", kwargs=ContractApiMessage.Kwargs( { "from_address": self.context.agent_address, "query_function": strategy.query_function, "amount": strategy.query_oracle_fee, "gas": strategy.gas_limit_query, "tx_fee": strategy.gas_price * strategy.gas_limit_query, } ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_query_terms() self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting query transaction...") def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/simple_oracle_client/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the ContractAPI and LedgerAPI dialogues for the simple oracle client skill.""" from typing import Any, Optional, Type from aea.common import Address from aea.exceptions import enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue as BaseContractApiDialogue, ) from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage class ContractApiDialogue(BaseContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_terms",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] @property def terms(self) -> Terms: """Get the terms.""" if self._terms is None: # pragma: nocover raise ValueError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set the terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class ContractApiDialogues(Model, BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=ContractApiDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_signing_dialogue", "_associated_transaction_type") def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_signing_dialogue = None # type: Optional[SigningDialogue] self._associated_transaction_type = None # type: Optional[str] @property def associated_signing_dialogue(self) -> "SigningDialogue": """Get the associated signing dialogue.""" if self._associated_signing_dialogue is None: # pragma: nocover raise ValueError("Associated signing dialogue not set!") return self._associated_signing_dialogue @associated_signing_dialogue.setter def associated_signing_dialogue( self, associated_signing_dialogue: "SigningDialogue" ) -> None: """Set the associated signing dialogue.""" enforce( self._associated_signing_dialogue is None, "Associated signing dialogue already set!", ) self._associated_signing_dialogue = associated_signing_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_contract_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_contract_api_dialogue = ( None ) # type: Optional[ContractApiDialogue] @property def associated_contract_api_dialogue(self) -> ContractApiDialogue: """Get the associated contract api dialogue.""" if self._associated_contract_api_dialogue is None: # pragma: nocover raise ValueError("Associated contract api dialogue not set!") return self._associated_contract_api_dialogue @associated_contract_api_dialogue.setter def associated_contract_api_dialogue( self, associated_contract_api_dialogue: ContractApiDialogue ) -> None: """Set the associated contract api dialogue.""" enforce( self._associated_contract_api_dialogue is None, "Associated contract api dialogue already set!", ) self._associated_contract_api_dialogue = associated_contract_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: packages/fetchai/skills/simple_oracle_client/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers for the Fetch oracle client skill.""" from typing import Optional, cast from aea_ledger_fetchai import FetchAIApi from aea.common import JSONLike from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import CONNECTION_ID as LEDGER_API_ADDRESS from packages.fetchai.contracts.oracle_client.contract import ( PUBLIC_ID as CONTRACT_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.simple_oracle_client.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogue, LedgerApiDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.simple_oracle_client.strategy import Strategy class LedgerApiHandler(Handler): """Implement the ledger api handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ self.context.logger.info("Handling ledger api msg") ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.outbox.put_message(message=msg) self.context.logger.info("requesting transaction receipt.") def _request_init_transaction(self, ledger_id: str, tx_receipt: JSONLike) -> None: """ Send a message to request the initialisation transaction to finalise contract deployment :param ledger_id: the ledger id :param tx_receipt: the transaction receipt """ strategy = cast(Strategy, self.context.strategy) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) fetchai_api = cast(FetchAIApi, LedgerApis.get_api(ledger_id)) code_id = fetchai_api.get_code_id(tx_receipt) # type: Optional[int] if code_id: contract_api_msg, dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=ledger_id, contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( { "label": "OracleContract", "init_msg": { "oracle_contract_address": strategy.oracle_contract_address }, "gas": strategy.gas_limit_instantiate, "tx_fee": strategy.gas_price * strategy.gas_limit_instantiate, "amount": 0, "code_id": code_id, "deployer_address": self.context.agent_address, } ), ) contract_api_dialogue = cast(ContractApiDialogue, dialogue) contract_api_dialogue.terms = strategy.get_deploy_terms( is_init_transaction=True ) self.context.outbox.put_message(message=contract_api_msg) else: # pragma: nocover self.context.logger.info("Failed to initialize contract: code_id not found") def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_receipt performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ ledger_id = cast(str, ledger_api_msg.transaction_receipt.ledger_id) tx_receipt = cast(JSONLike, ledger_api_msg.transaction_receipt.receipt) is_transaction_successful = LedgerApis.is_transaction_settled( ledger_api_msg.transaction_receipt.ledger_id, ledger_api_msg.transaction_receipt.receipt, ) if is_transaction_successful: self.context.logger.info( "transaction was successfully settled. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) strategy = cast(Strategy, self.context.strategy) contract_api_dialogue = ( ledger_api_dialogue.associated_signing_dialogue.associated_contract_api_dialogue ) transaction_label = contract_api_dialogue.terms.kwargs.get("label", "None") if not strategy.is_client_contract_deployed: if transaction_label == "store": self._request_init_transaction(ledger_id, tx_receipt) elif transaction_label in {"deploy", "init"}: client_contract_address = LedgerApis.get_contract_address( ledger_id, tx_receipt ) if client_contract_address is None: raise ValueError( "No contract address found." ) # pragma: nocover strategy.client_contract_address = client_contract_address strategy.is_client_contract_deployed = True self.context.logger.info( f"Oracle client contract successfully deployed at address: {client_contract_address}" ) else: self.context.logger.error( f"Invalid transaction label: {transaction_label}" ) # pragma: nocover elif ( not strategy.is_oracle_transaction_approved and transaction_label == "approve" ): strategy.is_oracle_transaction_approved = is_transaction_successful if is_transaction_successful: self.context.logger.info("Oracle client transactions approved!") else: # pragma: nocover self.context.logger.info( "Failed to approve oracle client transactions" ) elif transaction_label == "query": self.context.logger.info("Oracle value successfully requested!") else: # pragma nocover self.context.logger.error("unexpected transaction receipt!") else: self.context.logger.error( "transaction failed. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class ContractApiHandler(Handler): """Implement the contract api handler.""" SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ self.context.logger.info("Handling contract api msg") contract_api_msg = cast(ContractApiMessage, message) # recover dialogue contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_dialogue = cast( Optional[ContractApiDialogue], contract_api_dialogues.update(contract_api_msg), ) if contract_api_dialogue is None: self._handle_unidentified_dialogue(contract_api_msg) return # handle message if ( contract_api_msg.performative is ContractApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(contract_api_msg, contract_api_dialogue) elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: self._handle_error(contract_api_msg, contract_api_dialogue) else: self._handle_invalid(contract_api_msg, contract_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue( self, contract_api_msg: ContractApiMessage ) -> None: """ Handle an unidentified dialogue. :param contract_api_msg: the message """ self.context.logger.info( "received invalid contract_api message={}, unidentified dialogue.".format( contract_api_msg ) ) def _handle_raw_transaction( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_transaction performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=contract_api_msg.raw_transaction, terms=contract_api_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of error performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( contract_api_msg, contract_api_dialogue ) ) def _handle_invalid( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of invalid performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle contract_api message of performative={} in dialogue={}.".format( contract_api_msg.performative, contract_api_dialogue, ) ) class SigningHandler(Handler): """Implement the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, signed_transaction=signing_msg.signed_transaction, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) ================================================ FILE: packages/fetchai/skills/simple_oracle_client/skill.yaml ================================================ name: simple_oracle_client author: fetchai version: 0.13.5 type: skill description: This skill deploys a Fetch oracle client contract and calls this contract to request an oracle value license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmeP17RSTejkr3mmh4UsMFN6H6L8XL2a6EsdEaKQshgXJW __init__.py: QmYnEMkY229Cf6phe8QpcpaNGRMX4HJcLxGTQykHjQp4Mz behaviours.py: QmafcEseTDXqwTHoPF41PSaFT7reEuV2ZUsrXH26L1dNey dialogues.py: QmZJwtbs31WczMtXCK9jfaxh29mohBEY7YdRQ4zaReD2mF handlers.py: QmdHoVdCbSEzDx6ASD4hjdRm742cQPn6d1YCJbe3T3cX3W strategy.py: QmYmPZwE1zDLvbnv8K18b9GbW9jBW7YS9gZB2tEifmzWnT fingerprint_ignore_patterns: [] contracts: - fetchai/fet_erc20:0.9.2 - fetchai/oracle_client:0.11.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: simple_oracle_client_behaviour: args: query_interval: 15 class_name: SimpleOracleClientBehaviour handlers: contract_api: args: {} class_name: ContractApiHandler ledger_api: args: {} class_name: LedgerApiHandler signing: args: {} class_name: SigningHandler models: contract_api_dialogues: args: {} class_name: ContractApiDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: approve_amount: 1000000000000000000 client_contract_address: null erc20_address: null gas_limit_approve: 300000 gas_limit_deploy: 1500000 gas_limit_instantiate: 500000 gas_limit_query: 500000 gas_price: 1000000000 ledger_id: null oracle_contract_address: null query_function: null query_oracle_fee: 1000000000000 class_name: Strategy dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false connections: [] ================================================ FILE: packages/fetchai/skills/simple_oracle_client/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from typing import Any from aea_ledger_ethereum import EthereumApi from aea.configurations.constants import DEFAULT_LEDGER from aea.exceptions import enforce from aea.helpers.transaction.base import Terms from aea.skills.base import Model from packages.fetchai.protocols.contract_api.custom_types import Kwargs DEFAULT_LEDGER_ID = DEFAULT_LEDGER MAX_BLOCK_HEIGHT = 1000000000000000000 class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """Initialize the strategy of the agent.""" self._ledger_id = kwargs.pop("ledger_id", DEFAULT_LEDGER_ID) self._oracle_contract_address = kwargs.pop("oracle_contract_address", None) self._client_contract_address = kwargs.pop("client_contract_address", None) self._erc20_address = kwargs.pop("erc20_address", None) self._query_function = kwargs.pop("query_function", None) self._query_oracle_fee = kwargs.pop("query_oracle_fee", 0) self._gas_limit_deploy = kwargs.pop("gas_limit_deploy", 0) self._gas_limit_instantiate = kwargs.pop("gas_limit_instantiate", 0) self._gas_limit_query = kwargs.pop("gas_limit_query", 0) self._gas_limit_approve = kwargs.pop("gas_limit_approve", 0) self._gas_price = kwargs.pop("gas_price", 0) self._approve_amount = kwargs.pop("approve_amount", 0) super().__init__(**kwargs) self.is_behaviour_active = True self._is_oracle_contract_set = self._oracle_contract_address is not None self._is_client_contract_deployed = self._client_contract_address is not None self._is_oracle_transaction_approved = False @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def query_function(self) -> str: """Get the name of the oracle value query function.""" return self._query_function @property def query_oracle_fee(self) -> str: """Get the fee amount for querying the oracle contract.""" return self._query_oracle_fee @property def gas_limit_instantiate(self) -> int: """Get the default gas for instantiating a contract.""" return self._gas_limit_instantiate @property def gas_limit_deploy(self) -> int: """Get the default gas for deploying a contract.""" return self._gas_limit_deploy @property def gas_limit_query(self) -> int: """Get the default gas for querying oracle value.""" return self._gas_limit_query @property def gas_limit_approve(self) -> int: """Get the default gas for querying oracle value.""" return self._gas_limit_query @property def gas_price(self) -> int: """Get the gas price.""" return self._gas_price @property def approve_amount(self) -> str: """Get the amount of tokens to approve for spending by the client contract.""" return self._approve_amount @property def oracle_contract_address(self) -> str: """Get the oracle contract address.""" if self._oracle_contract_address is None: # pragma: nocover raise ValueError("Oracle contract address not set!") return self._oracle_contract_address @oracle_contract_address.setter def oracle_contract_address(self, oracle_contract_address: str) -> None: """Set the oracle contract address.""" enforce( self._oracle_contract_address is None, "Oracle contract address already set!", ) self._oracle_contract_address = oracle_contract_address @property def client_contract_address(self) -> str: """Get the client contract address.""" if self._client_contract_address is None: # pragma: nocover raise ValueError("Oracle client contract address not set!") return self._client_contract_address @client_contract_address.setter def client_contract_address(self, client_contract_address: str) -> None: """Set the oracle client contract address.""" enforce( self._client_contract_address is None, "Oracle client contract address already set!", ) self._client_contract_address = client_contract_address @property def erc20_address(self) -> str: """Get the erc20 address for token payment.""" if self._erc20_address is None: # pragma: nocover raise ValueError("ERC20 address not set!") return self._erc20_address @erc20_address.setter def erc20_address(self, erc20_address: str) -> None: """Set the erc20 address for token payment.""" enforce(self._erc20_address is None, "ERC20 address already set!") self._erc20_address = erc20_address @property def is_oracle_contract_set(self) -> bool: """Get oracle contract status.""" return self._is_oracle_contract_set @is_oracle_contract_set.setter def is_oracle_contract_set(self, is_oracle_contract_set: bool) -> None: """Set oracle contract status.""" enforce( not self._is_oracle_contract_set and is_oracle_contract_set, "Only allowed to switch to true.", ) self._is_oracle_contract_set = is_oracle_contract_set @property def is_oracle_transaction_approved(self) -> bool: """Get oracle transaction approval status.""" return self._is_oracle_transaction_approved @is_oracle_transaction_approved.setter def is_oracle_transaction_approved( self, is_oracle_transaction_approved: bool ) -> None: """Set oracle transaction approval status.""" enforce( not self._is_oracle_transaction_approved and is_oracle_transaction_approved, "Only allowed to switch to true.", ) self._is_oracle_transaction_approved = is_oracle_transaction_approved @property def is_client_contract_deployed(self) -> bool: """Get oracle contract status.""" return self._is_client_contract_deployed @is_client_contract_deployed.setter def is_client_contract_deployed(self, is_client_contract_deployed: bool) -> None: """Set client contract deploy status.""" enforce( not self._is_client_contract_deployed and is_client_contract_deployed, "Only allowed to switch to true.", ) self._is_client_contract_deployed = is_client_contract_deployed def get_deploy_terms(self, is_init_transaction: bool = False) -> Terms: """ Get terms of deployment. :param is_init_transaction: whether the transaction is init or store. :return: terms """ if self.ledger_id == EthereumApi.identifier: label = "deploy" else: if is_init_transaction: label = "init" else: label = "store" terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", label=label, ) return terms def get_query_terms(self) -> Terms: """ Get terms of query transaction. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", label="query", ) return terms def get_approve_terms(self) -> Terms: """ Get terms of approve transaction. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", label="approve", ) return terms def get_deploy_kwargs(self) -> Kwargs: """ Get kwargs for the contract deployment :return: kwargs """ if self.ledger_id == EthereumApi.identifier: kwargs = Kwargs( { "deployer_address": self.context.agent_address, "fetchOracleContractAddress": self.oracle_contract_address, "gas": self.gas_limit_deploy, } ) else: kwargs = Kwargs( { "deployer_address": self.context.agent_address, "gas": self.gas_limit_deploy, "tx_fee": self.gas_price * self.gas_limit_deploy, } ) return kwargs ================================================ FILE: packages/fetchai/skills/simple_seller/README.md ================================================ # Simple Seller ## Description This skill is used to sell data present in the shared state. ## Behaviours - `service_registration`: registers data selling service on the sOEF ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages if service registration on the sOEF is unsuccessful ## Models - the `strategy` model is extended from the `fetchai/generic_seller` skill and loads data from the shared state using the key `shared_state_key` specified in the skill configuration. ================================================ FILE: packages/fetchai/skills/simple_seller/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the simple_seller skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_seller:0.14.6") ================================================ FILE: packages/fetchai/skills/simple_seller/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a service registration behaviour (reused from generic_seller skill).""" from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) ServiceRegistrationBehaviour = GenericServiceRegistrationBehaviour ================================================ FILE: packages/fetchai/skills/simple_seller/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management (reused from generic_seller skill). - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. """ from packages.fetchai.skills.generic_seller.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues ================================================ FILE: packages/fetchai/skills/simple_seller/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains handlers (reused from the generic_seller skill).""" from packages.fetchai.skills.generic_seller.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler ================================================ FILE: packages/fetchai/skills/simple_seller/skill.yaml ================================================ name: simple_seller author: fetchai version: 0.14.6 type: skill description: The simple_seller skill extends the generic_seller skill and sells data from the shared state of the AEA. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmcBRmTneLHvBfHcw4ZfVKkMw8cZFbvHdmAactNhNrd4HU __init__.py: QmQztRLPBQZyWCZmW1ZBpgSTz3JFSnBt6dCYTpTBYsrUhr behaviours.py: QmPjoQS4RdWpsmhh2xabtW3MDRAdRch4J2MMe8MdpSGFYp dialogues.py: QmSY1VNCShSjiVuwToNvu7k5pfYEXiHjRQP5fHRkrx8UHz handlers.py: QmZhWji4oE5odDoxJbWhrMS9yzj42nKhBfwM3KgG5GnrdA strategy.py: QmUdEa63iofjreKba2oQc3CkACV6PYyevmTTyiit6pWnij fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 skills: - fetchai/generic_seller:0.28.6 behaviours: service_registration: args: services_interval: 20 class_name: ServiceRegistrationBehaviour handlers: fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller has_data_source: true is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: weather_data service_id: weather_data shared_state_key: null unit_price: 10 class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/simple_seller/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class (extended from the generic_seller skill).""" import json from typing import Any, Dict from packages.fetchai.skills.generic_seller.strategy import GenericStrategy class Strategy(GenericStrategy): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ self.shared_state_key = kwargs.pop("shared_state_key", None) if self.shared_state_key is None: raise ValueError("No shared_state_key provided!") super().__init__(**kwargs) def collect_from_data_source(self) -> Dict[str, str]: """ Build the data payload. :return: a dict of the data found in the shared state. """ data = self.context.shared_state.get(self.shared_state_key, b"{}") formatted_data = self._format_data(data) return formatted_data def _format_data(self, data: bytes) -> Dict[str, str]: """ Convert to dict. :param data: the bytes data to format :return: a dict with key and values as strings """ result: Dict[str, str] = {} try: loaded = json.loads(data) if isinstance(loaded, dict) and all( isinstance(key, str) and isinstance(value, str) for key, value in loaded.items() ): result = loaded else: result = {"data": json.dumps(loaded)} except json.decoder.JSONDecodeError as e: self.context.logger.warning(f"error when loading json: {e}") return result ================================================ FILE: packages/fetchai/skills/simple_service_registration/README.md ================================================ # Simple Service Registration ## Description This skill registers and unregisters an agent and service on the sOEF. This skill is used in the "Guide on Writing a Skill" section in the documentation. On start, it registers an agent and its service on the sOEF, and on termination it unregisters the agent and its service from sOEF. ## Behaviours - `service`: registers and unregisters a service on the sOEF ## Handlers - `oef_search`: handles `oef_search` messages if interactions with the sOEF is erratic ## Links - Guide on Building a Skill ================================================ FILE: packages/fetchai/skills/simple_service_registration/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_service_registration:0.23.6") ================================================ FILE: packages/fetchai/skills/simple_service_registration/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a simple behaviour to register a service.""" from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogues, ) from packages.fetchai.skills.simple_service_registration.strategy import Strategy DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 DEFAULT_SERVICES_INTERVAL = 30.0 class ServiceRegistrationBehaviour(TickerBehaviour): """This class implements a behaviour.""" def __init__(self, **kwargs: Any) -> None: """Initialise the behaviour.""" services_interval = kwargs.pop( "services_interval", DEFAULT_SERVICES_INTERVAL ) # type: int self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=services_interval, **kwargs) self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """Implement the setup.""" self._register_agent() def act(self) -> None: """Implement the act.""" self._retry_failed_registration() def teardown(self) -> None: """Implement the task teardown.""" self._unregister_service() self._unregister_agent() def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """Register the agent's service.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_service_description() self._register(description, "registering agent's service on the SOEF.") def register_genus(self) -> None: """Register the agent's personality genus.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """Unregister service from the SOEF.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering service from SOEF.") def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") ================================================ FILE: packages/fetchai/skills/simple_service_registration/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - OefSearchDialogue: The dialogue class maintains state of a dialogue and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/simple_service_registration/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of a generic seller AEA.""" from typing import Optional, cast from aea.configurations.base import PublicId from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_registration.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) class OefSearchHandler(Handler): """This class implements an OEF search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Call to setup the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( ServiceRegistrationBehaviour, self.context.behaviours.service, ) registration_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/simple_service_registration/skill.yaml ================================================ name: simple_service_registration author: fetchai version: 0.23.6 type: skill description: The simple service registration skills is a skill to register a service. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmcAU3oi7tWS86UmAePbubQLinLutQsSQ1eGce5LperZty __init__.py: QmTBYeERGsytUjBYUFCBCA55yuXaQSetGT235Zz8fbV16L behaviours.py: QmWuomUzWYZXeshgbnnzzeC9xE3tNwJReGwCBtbqhMXcaY dialogues.py: QmbgKy2b6WiWSFXxdwDaawAsoBY2VsBAdhRgUL6WTaGhEt handlers.py: QmehzwbrYqX2VAoAzVC7754rdJFjYoNCs58R5oBu5ChnDq strategy.py: QmcwQhz4RsqN35BiApeTDoyuggqXXooezW6HvsTx9ugXGj fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/oef_search:1.1.7 skills: [] behaviours: service: args: max_soef_registration_retries: 5 services_interval: 30 class_name: ServiceRegistrationBehaviour handlers: oef_search: args: {} class_name: OefSearchHandler models: oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: generic_service class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/simple_service_registration/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a strategy model.""" from typing import Any from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import Description, Location from aea.skills.base import Model DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_DATA = {"key": "seller_service", "value": "generic_service"} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "seller"} class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._set_service_data) == 2 and "key" in self._set_service_data and "value" in self._set_service_data, "service_data must contain keys `key` and `value`", ) self._remove_service_data = {"key": self._set_service_data["key"]} super().__init__(**kwargs) def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ description = Description( self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description ================================================ FILE: packages/fetchai/skills/simple_service_search/README.md ================================================ # Simple Service Search ## Description This skill searches for services on the sOEF. ## Behaviours - `service_search`: sends search queries to the sOEF using the `oef_search` protocol. ## Handlers - `oef_search`: handles `oef_search` messages, in particular search responses. Search results are stored in the shared state using the key `shared_storage_key`. ## Models - `strategy`: builds the search query from the data provided in the skill configuration: `search_location`, `search_query` and `search_radius`. ================================================ FILE: packages/fetchai/skills/simple_service_search/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the simple service search skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/simple_service_search:0.11.6") ================================================ FILE: packages/fetchai/skills/simple_service_search/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a simple behaviour to search a service.""" from typing import cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_search.dialogues import OefSearchDialogues from packages.fetchai.skills.simple_service_search.strategy import Strategy class ServiceSearchBehaviour(TickerBehaviour): """This class provides a simple search behaviour.""" def setup(self) -> None: """Implement the setup.""" def act(self) -> None: """Implement the act.""" strategy = cast(Strategy, self.context.strategy) oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) search_request, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=strategy.get_query(), ) self.context.logger.info("sending search request to OEF search node") self.context.outbox.put_message(message=search_request) def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/simple_service_search/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - OefSearchDialogue: The dialogue class maintains state of a dialogue and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues. """ from typing import Any from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Address, Model from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), # problem role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/simple_service_search/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of a simple service searching AEA.""" from typing import Optional, cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_search.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.simple_service_search.strategy import Strategy class OefSearchHandler(Handler): """This class provides a simple search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id def setup(self) -> None: """Set up the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: self._handle_search(oef_search_msg) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_msg, oef_search_dialogue ) ) def _handle_search(self, oef_search_msg: OefSearchMessage) -> None: """ Handle the search response. :param oef_search_msg: the oef search message """ agents = list(oef_search_msg.agents) nb_agents = len(agents) if nb_agents == 0: self.context.logger.info( f"no agents found, search_response={oef_search_msg}" ) return strategy = cast(Strategy, self.context.strategy) self.context.logger.info( f"found number of agents={nb_agents}, search_response={oef_search_msg}" ) if self.context.shared_state.get(strategy.shared_storage_key, None) is None: self.context.shared_state[strategy.shared_storage_key] = set() for agent in agents: self.context.shared_state[strategy.shared_storage_key].add(agent) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) def teardown(self) -> None: """Teardown the handler.""" ================================================ FILE: packages/fetchai/skills/simple_service_search/skill.yaml ================================================ name: simple_service_search author: fetchai version: 0.11.6 type: skill description: A simple search skill utilising the SOEF search node. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qme6VKFeMeJe4BjUEeqL1tTVisW4QUEFDu3PCgcHyUJWG8 __init__.py: QmcbnC34XeYwZkpeUXdBEoaD8NQ35xpzQ2VT2Y5tpXAxu6 behaviours.py: QmQVn5ASsL6fPgyvLJbKt9DHKaPRtQbtvNbj4L9Wk1zKr8 dialogues.py: QmazwDnVLDM3UsfDHPcamVWaipNVnNDAsRqTZe8vB6cXTn handlers.py: Qmd8rkgUezaM9LbHmZnAp7Mn9uCGJ14WaCPgeMQF6QPWy7 strategy.py: QmZ6vRXXSMJgzFxgWT8rZcdxZ4oBuXbuWooGnfo8neWqr9 fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/oef_search:1.1.7 skills: [] behaviours: service_search: args: tick_interval: 30 class_name: ServiceSearchBehaviour handlers: oef_search: args: {} class_name: OefSearchHandler models: oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: search_location: latitude: 51.5194 longitude: 0.127 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 shared_storage_key: agents_found class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/simple_service_search/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a strategy model.""" from typing import Any from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.skills.base import Model DEFAULT_SEARCH_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "seller_service", "search_value": "generic_service", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 DEFAULT_SHARED_STORAGE_KEY = "agents_found" class Strategy(Model): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("search_location", DEFAULT_SEARCH_LOCATION) self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self._shared_storage_key = kwargs.pop( "shared_storage_key", DEFAULT_SHARED_STORAGE_KEY ) super().__init__(**kwargs) @property def shared_storage_key(self) -> str: """Get shared storage key.""" return self._shared_storage_key def get_query(self) -> Query: """ Get the location description. :return: a description of the agent's location """ close_to_my_service = Constraint( "location", ConstraintType("distance", (self._agent_location, self._radius)) ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query([close_to_my_service, service_key_filter]) return query ================================================ FILE: packages/fetchai/skills/tac_control/README.md ================================================ # TAC Control ## Description This is the skill for managing a TAC. This skill is part of the Fetch.ai TAC demo. It manages the progression of the competition along its various stages. ## Behaviours - `tac`: manages progression of the competition ## Handlers - `tac`: handles `tac` messages for managing the competition - `oef`: handles `oef_search` messages if registration or unregistration on the sOEF is unsuccessful ## Links - TAC Demo ================================================ FILE: packages/fetchai/skills/tac_control/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the tac control skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/tac_control:0.25.6") ================================================ FILE: packages/fetchai/skills/tac_control/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a the behaviours.""" import datetime from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.base import Behaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_control.dialogues import ( OefSearchDialogues, TacDialogues, ) from packages.fetchai.skills.tac_control.game import Game, Phase from packages.fetchai.skills.tac_control.parameters import Parameters DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 class TacBehaviour(Behaviour): """This class implements the TAC control behaviour.""" def __init__(self, **kwargs: Any): """Instantiate the behaviour.""" self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(**kwargs) self._registered_description = None # type: Optional[Description] self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """Implement the setup.""" self._register_agent() def act(self) -> None: """Implement the act.""" self._retry_failed_registration() game = cast(Game, self.context.game) parameters = cast(Parameters, self.context.parameters) now = datetime.datetime.now() if ( game.phase.value == Phase.PRE_GAME.value and parameters.registration_start_time < now < parameters.start_time and game.is_registered_agent ): game.phase = Phase.GAME_REGISTRATION self._register_tac() self.context.logger.info( "TAC open for registration until: {}".format(parameters.start_time) ) elif ( game.phase.value == Phase.GAME_REGISTRATION.value and parameters.start_time < now < parameters.end_time ): if game.registration.nb_agents < parameters.min_nb_agents: self._cancel_tac(game) game.phase = Phase.POST_GAME self._unregister_tac() else: game.phase = Phase.GAME_SETUP game.create() self._start_tac(game) self._unregister_tac() game.phase = Phase.GAME elif game.phase.value == Phase.GAME.value and now > parameters.end_time: self._cancel_tac(game) game.phase = Phase.POST_GAME def teardown(self) -> None: """Implement the task teardown.""" self._unregister_tac() self._unregister_agent() def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" game = cast(Game, self.context.game) description = game.get_location_description() self._register(description, "registering agent on SOEF.") def register_genus(self) -> None: """Register the agent's personality genus.""" game = cast(Game, self.context.game) description = game.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" game = cast(Game, self.context.game) description = game.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _register_tac(self) -> None: """Register the agent's TAC controller service on the SOEF.""" game = cast(Game, self.context.game) description = game.get_register_tac_description() self._register(description, "registering TAC data model on SOEF.") def _unregister_tac(self) -> None: """Unregister from the OEF as a TAC controller agent.""" game = cast(Game, self.context.game) description = game.get_unregister_tac_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self._registered_description = None self.context.logger.info("unregistering TAC data model from SOEF.") def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" game = cast(Game, self.context.game) description = game.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") def _start_tac(self, game: Game) -> None: """ Create a game and send the game configuration to every registered agent. :param game: the game """ count = len(game.conf.agent_addr_to_name) participant_names = sorted(list(game.conf.agent_addr_to_name.values())) self.context.logger.info( f"starting competition with {count} participants and list of participants: {participant_names}" ) self.context.logger.info( "started competition:\n{}".format(game.holdings_summary) ) self.context.logger.info( "computed equilibrium:\n{}".format(game.equilibrium_summary) ) tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) for agent_address in game.conf.agent_addr_to_name.keys(): _tac_dialogues = tac_dialogues.get_dialogues_with_counterparty( agent_address ) if len(_tac_dialogues) != 1: raise ValueError("Error when retrieving dialogue.") tac_dialogue = _tac_dialogues[0] last_msg = tac_dialogue.last_message if last_msg is None: raise ValueError("Error when retrieving last message.") agent_state = game.current_agent_states[agent_address] info = ( {"contract_address": game.conf.contract_address} if game.conf.has_contract_address else {} ) tac_msg = tac_dialogue.reply( performative=TacMessage.Performative.GAME_DATA, target_message=last_msg, amount_by_currency_id=agent_state.amount_by_currency_id, exchange_params_by_currency_id=agent_state.exchange_params_by_currency_id, quantities_by_good_id=agent_state.quantities_by_good_id, utility_params_by_good_id=agent_state.utility_params_by_good_id, fee_by_currency_id=game.conf.fee_by_currency_id, currency_id_to_name=game.conf.currency_id_to_name, agent_addr_to_name=game.conf.agent_addr_to_name, good_id_to_name=game.conf.good_id_to_name, version_id=game.conf.version_id, info=info, ) self.context.outbox.put_message(message=tac_msg) self.context.logger.debug( "sending game data to '{}': {}".format(agent_address, str(tac_msg)) ) def _cancel_tac(self, game: Game) -> None: """ Notify agents that the TAC is cancelled. :param game: the game """ self.context.logger.info("notifying agents that TAC is cancelled.") tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) for agent_address in game.registration.agent_addr_to_name.keys(): _tac_dialogues = tac_dialogues.get_dialogues_with_counterparty( agent_address ) if len(_tac_dialogues) != 1: raise ValueError("Error when retrieving dialogue.") tac_dialogue = _tac_dialogues[0] last_msg = tac_dialogue.last_message if last_msg is None: # pragma: nocover raise ValueError("Error when retrieving last message.") tac_msg = tac_dialogue.reply( performative=TacMessage.Performative.CANCELLED, ) self.context.outbox.put_message(message=tac_msg) if game.phase == Phase.GAME: self.context.logger.info( "finished competition:\n{}".format(game.holdings_summary) ) self.context.logger.info( "computed equilibrium:\n{}".format(game.equilibrium_summary) ) self.context.is_active = False ================================================ FILE: packages/fetchai/skills/tac_control/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - OefSearchDialogue: The dialogue class maintains state of a dialogue of type oef_search and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - TacDialogue: The dialogue class maintains state of a dialogue of type tac and manages it. - TacDialogues: The dialogues class keeps track of all dialogues of type tac. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue from aea.skills.base import Model from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.tac.dialogues import TacDialogue as BaseTacDialogue from packages.fetchai.protocols.tac.dialogues import TacDialogues as BaseTacDialogues DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) TacDialogue = BaseTacDialogue class TacDialogues(Model, BaseTacDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return TacDialogue.Role.CONTROLLER BaseTacDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/tac_control/game.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a class representing the game.""" import copy import datetime import pprint from enum import Enum from typing import Any, Dict, List, Optional, cast from aea.common import Address from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import AEAEnforceError, enforce from aea.helpers.preference_representations.base import ( linear_utility, logarithmic_utility, ) from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import Description from aea.helpers.transaction.base import Terms from aea.skills.base import Model from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_control.helpers import ( determine_scaling_factor, generate_currency_endowments, generate_equilibrium_prices_and_holdings, generate_exchange_params, generate_good_endowments, generate_utility_params, ) from packages.fetchai.skills.tac_control.parameters import Parameters GoodId = str CurrencyId = str Quantity = int EquilibriumQuantity = float Parameter = float TransactionId = str CurrencyEndowment = Dict[CurrencyId, Quantity] ExchangeParams = Dict[CurrencyId, Parameter] GoodEndowment = Dict[GoodId, Quantity] UtilityParams = Dict[GoodId, Parameter] EquilibriumCurrencyHoldings = Dict[CurrencyId, EquilibriumQuantity] EquilibriumGoodHoldings = Dict[GoodId, EquilibriumQuantity] class Phase(Enum): """This class defines the phases of the game.""" PRE_GAME = "pre_game" CONTRACT_DEPLOYMENT_PROPOSAL = "contract_deployment_proposal" CONTRACT_DEPLOYED = "contract_deployed" GAME_REGISTRATION = "game_registration" GAME_SETUP = "game_setup" TOKENS_CREATION_PROPOSAL = "token_creation_proposal" # nosec TOKENS_CREATED = "tokens_created" # nosec TOKENS_MINTING_PROPOSAL = "token_minting_proposal" TOKENS_MINTED = "tokens_minted" # nosec GAME = "game" POST_GAME = "post_game" CANCELLED_GAME = "cancelled_game" class Configuration: """Class containing the configuration of the game.""" def __init__( self, version_id: str, tx_fee: int, agent_addr_to_name: Dict[Address, str], currency_id_to_name: Dict[str, str], good_id_to_name: Dict[str, str], ): """ Instantiate a game configuration. :param version_id: the version of the game. :param tx_fee: the fee for a transaction. :param agent_addr_to_name: a dictionary mapping agent addresses to agent names (as strings). :param currency_id_to_name: the mapping of currency id to name. :param good_id_to_name: the mapping of good id to name. """ self._version_id = version_id self._tx_fee = tx_fee self._agent_addr_to_name = agent_addr_to_name self._currency_id_to_name = currency_id_to_name self._good_id_to_name = good_id_to_name self._contract_address = None # type: Optional[str] self._check_consistency() @property def version_id(self) -> str: """Agent number of a TAC instance.""" return self._version_id @property def fee_by_currency_id(self) -> Dict[str, int]: """Transaction fee for the TAC instance.""" return {next(iter(self.currency_id_to_name.keys())): self._tx_fee} @property def agent_addr_to_name(self) -> Dict[Address, str]: """Map agent addresses to names.""" return self._agent_addr_to_name @property def currency_id_to_name(self) -> Dict[str, str]: """Map currency ids to names.""" return self._currency_id_to_name @property def good_id_to_name(self) -> Dict[str, str]: """Map good ids to names.""" return self._good_id_to_name @property def has_contract_address(self) -> bool: """Check if contract address is present.""" return self._contract_address is not None @property def contract_address(self) -> str: """Get the contract address for the game.""" if self._contract_address is None: raise AEAEnforceError("Contract_address not set yet!") return self._contract_address @contract_address.setter def contract_address(self, contract_address: str) -> None: """Set the contract address for the game.""" enforce(self._contract_address is None, "Contract_address already set!") self._contract_address = contract_address def _check_consistency(self) -> None: """ Check the consistency of the game configuration. :raises: AEAEnforceError: if some constraint is not satisfied. """ if self.version_id is None: raise AEAEnforceError("A version id must be set.") enforce(self._tx_fee >= 0, "Tx fee must be non-negative.") enforce(len(self.agent_addr_to_name) >= 2, "Must have at least two agents.") enforce(len(self.good_id_to_name) >= 2, "Must have at least two goods.") enforce(len(self.currency_id_to_name) == 1, "Must have exactly one currency.") enforce( next(iter(self.currency_id_to_name)) not in self.good_id_to_name, "Currency id and good ids cannot overlap.", ) class Initialization: """Class containing the initialization of the game.""" def __init__( self, agent_addr_to_currency_endowments: Dict[Address, CurrencyEndowment], agent_addr_to_exchange_params: Dict[Address, ExchangeParams], agent_addr_to_good_endowments: Dict[Address, GoodEndowment], agent_addr_to_utility_params: Dict[Address, UtilityParams], good_id_to_eq_prices: Dict[GoodId, float], agent_addr_to_eq_good_holdings: Dict[Address, EquilibriumGoodHoldings], agent_addr_to_eq_currency_holdings: Dict[Address, EquilibriumCurrencyHoldings], ): """ Instantiate a game initialization. :param agent_addr_to_currency_endowments: the currency endowments of the agents. A nested dict where the outer key is the agent id and the inner key is the currency id. :param agent_addr_to_exchange_params: the exchange params representing the exchange rate the agents use between currencies. :param agent_addr_to_good_endowments: the good endowments of the agents. A nested dict where the outer key is the agent id and the inner key is the good id. :param agent_addr_to_utility_params: the utility params representing the preferences of the agents. :param good_id_to_eq_prices: the competitive equilibrium prices of the goods. A list. :param agent_addr_to_eq_good_holdings: the competitive equilibrium good holdings of the agents. :param agent_addr_to_eq_currency_holdings: the competitive equilibrium money holdings of the agents. """ self._agent_addr_to_currency_endowments = agent_addr_to_currency_endowments self._agent_addr_to_exchange_params = agent_addr_to_exchange_params self._agent_addr_to_good_endowments = agent_addr_to_good_endowments self._agent_addr_to_utility_params = agent_addr_to_utility_params self._good_id_to_eq_prices = good_id_to_eq_prices self._agent_addr_to_eq_good_holdings = agent_addr_to_eq_good_holdings self._agent_addr_to_eq_currency_holdings = agent_addr_to_eq_currency_holdings self._check_consistency() @property def agent_addr_to_currency_endowments(self) -> Dict[Address, CurrencyEndowment]: """Get currency endowments of agents.""" return self._agent_addr_to_currency_endowments @property def agent_addr_to_exchange_params(self) -> Dict[Address, ExchangeParams]: """Get exchange params of agents.""" return self._agent_addr_to_exchange_params @property def agent_addr_to_good_endowments(self) -> Dict[Address, GoodEndowment]: """Get good endowments of the agents.""" return self._agent_addr_to_good_endowments @property def agent_addr_to_utility_params(self) -> Dict[Address, UtilityParams]: """Get utility parameters of agents.""" return self._agent_addr_to_utility_params @property def good_id_to_eq_prices(self) -> Dict[GoodId, float]: """Get theoretical equilibrium prices (a benchmark).""" return self._good_id_to_eq_prices @property def agent_addr_to_eq_good_holdings(self) -> Dict[Address, EquilibriumGoodHoldings]: """Get theoretical equilibrium good holdings (a benchmark).""" return self._agent_addr_to_eq_good_holdings @property def agent_addr_to_eq_currency_holdings( self, ) -> Dict[Address, EquilibriumCurrencyHoldings]: """Get theoretical equilibrium currency holdings (a benchmark).""" return self._agent_addr_to_eq_currency_holdings def _check_consistency(self) -> None: """Check the consistency of the game configuration.""" enforce( all( c_e >= 0 for currency_endowments in self.agent_addr_to_currency_endowments.values() for c_e in currency_endowments.values() ), "Currency endowments must be non-negative.", ) enforce( all( p > 0 for params in self.agent_addr_to_exchange_params.values() for p in params.values() ), "ExchangeParams must be strictly positive.", ) enforce( all( g_e > 0 for good_endowments in self.agent_addr_to_good_endowments.values() for g_e in good_endowments.values() ), "Good endowments must be strictly positive.", ) enforce( all( p > 0 for params in self.agent_addr_to_utility_params.values() for p in params.values() ), "UtilityParams must be strictly positive.", ) enforce( len(self.agent_addr_to_good_endowments.keys()) == len(self.agent_addr_to_currency_endowments.keys()), "Length of endowments must be the same.", ) enforce( len(self.agent_addr_to_exchange_params.keys()) == len(self.agent_addr_to_utility_params.keys()), "Length of params must be the same.", ) enforce( all( len(self.good_id_to_eq_prices.values()) == len(eq_good_holdings) for eq_good_holdings in self.agent_addr_to_eq_good_holdings.values() ), "Length of eq_prices and an element of eq_good_holdings must be the same.", ) enforce( len(self.agent_addr_to_eq_good_holdings.values()) == len(self.agent_addr_to_eq_currency_holdings.values()), "Length of eq_good_holdings and eq_currency_holdings must be the same.", ) enforce( all( len(self.agent_addr_to_exchange_params[agent_addr]) == len(endowments) for agent_addr, endowments in self.agent_addr_to_currency_endowments.items() ), "Dimensions for exchange_params and currency_endowments rows must be the same.", ) enforce( all( len(self.agent_addr_to_utility_params[agent_addr]) == len(endowments) for agent_addr, endowments in self.agent_addr_to_good_endowments.items() ), "Dimensions for utility_params and good_endowments rows must be the same.", ) class Transaction(Terms): """Convenience representation of a transaction.""" def __init__( self, ledger_id: str, sender_address: Address, counterparty_address: Address, amount_by_currency_id: Dict[str, int], quantities_by_good_id: Dict[str, int], is_sender_payable_tx_fee: bool, nonce: str, fee_by_currency_id: Optional[Dict[str, int]], sender_signature: str, counterparty_signature: str, ) -> None: """ Instantiate transaction. This extends a terms object to be used as a transaction. :param ledger_id: the ledger on which the terms are to be settled. :param sender_address: the sender address of the transaction. :param counterparty_address: the counterparty address of the transaction. :param amount_by_currency_id: the amount by the currency of the transaction. :param quantities_by_good_id: a map from good id to the quantity of that good involved in the transaction. :param is_sender_payable_tx_fee: whether the sender or counterparty pays the tx fee. :param nonce: nonce to be included in transaction to discriminate otherwise identical transactions. :param fee_by_currency_id: the fee associated with the transaction. :param sender_signature: the signature of the terms by the sender. :param counterparty_signature: the signature of the terms by the counterparty. """ super().__init__( ledger_id=ledger_id, sender_address=sender_address, counterparty_address=counterparty_address, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, fee_by_currency_id=fee_by_currency_id, ) self._sender_signature = sender_signature self._counterparty_signature = counterparty_signature @property def sender_signature(self) -> str: """Get the sender signature.""" return self._sender_signature @property def counterparty_signature(self) -> str: """Get the counterparty signature.""" return self._counterparty_signature def has_matching_signatures(self) -> bool: """ Check that the signatures match the terms of trade. :return: True if the transaction has been signed by both parties """ result = ( self.sender_address in LedgerApis.recover_message( # pylint: disable=no-member identifier=self.ledger_id, message=self.sender_hash.encode("utf-8"), signature=self.sender_signature, ) ) result = ( result and self.counterparty_address in LedgerApis.recover_message( # pylint: disable=no-member identifier=self.ledger_id, message=self.counterparty_hash.encode("utf-8"), signature=self.counterparty_signature, ) ) return result @classmethod def from_message(cls, message: TacMessage) -> "Transaction": """ Create a transaction from a proposal. :param message: the message :return: Transaction """ enforce( message.performative == TacMessage.Performative.TRANSACTION, "Wrong performative", ) sender_is_seller = all( value >= 0 for value in message.amount_by_currency_id.values() ) transaction = Transaction( ledger_id=message.ledger_id, sender_address=message.sender_address, counterparty_address=message.counterparty_address, amount_by_currency_id=message.amount_by_currency_id, fee_by_currency_id=message.fee_by_currency_id, quantities_by_good_id=message.quantities_by_good_id, is_sender_payable_tx_fee=not sender_is_seller, nonce=message.nonce, sender_signature=message.sender_signature, counterparty_signature=message.counterparty_signature, ) enforce( transaction.id == message.transaction_id, "Transaction content does not match hash.", ) return transaction def __eq__(self, other: Any) -> bool: """Compare to another object.""" return ( isinstance(other, Transaction) and super().__eq__(other) and self.sender_signature == other.sender_signature and self.counterparty_signature == other.counterparty_signature ) class AgentState: """Represent the state of an agent during the game.""" def __init__( self, agent_address: Address, amount_by_currency_id: Dict[CurrencyId, Quantity], exchange_params_by_currency_id: Dict[CurrencyId, Parameter], quantities_by_good_id: Dict[GoodId, Quantity], utility_params_by_good_id: Dict[GoodId, Parameter], ): """ Instantiate an agent state object. :param agent_address: the agent address :param amount_by_currency_id: the amount for each currency :param exchange_params_by_currency_id: the exchange parameters of the different currencies :param quantities_by_good_id: the quantities for each good. :param utility_params_by_good_id: the utility params for every good. """ enforce( len(amount_by_currency_id.keys()) == len(exchange_params_by_currency_id.keys()), "Different number of elements in amount_by_currency_id and exchange_params_by_currency_id", ) enforce( len(quantities_by_good_id.keys()) == len(utility_params_by_good_id.keys()), "Different number of elements in quantities_by_good_id and utility_params_by_good_id", ) self._agent_address = agent_address self._amount_by_currency_id = copy.copy(amount_by_currency_id) self._exchange_params_by_currency_id = copy.copy(exchange_params_by_currency_id) self._quantities_by_good_id = quantities_by_good_id self._utility_params_by_good_id = copy.copy(utility_params_by_good_id) @property def agent_address(self) -> str: """Get address of the agent which state that is.""" return self._agent_address @property def amount_by_currency_id(self) -> Dict[CurrencyId, Quantity]: """Get the amount for each currency.""" return copy.copy(self._amount_by_currency_id) @property def exchange_params_by_currency_id(self) -> Dict[CurrencyId, Parameter]: """Get the exchange parameters for each currency.""" return copy.copy(self._exchange_params_by_currency_id) @property def quantities_by_good_id(self) -> Dict[GoodId, Quantity]: """Get holding of each good.""" return copy.copy(self._quantities_by_good_id) @property def utility_params_by_good_id(self) -> Dict[GoodId, Parameter]: """Get utility parameter for each good.""" return copy.copy(self._utility_params_by_good_id) def get_score(self) -> float: """ Compute the score of the current state. The score is computed as the sum of all the utilities for the good holdings with positive quantity plus the money left. :return: the score. """ goods_score = logarithmic_utility( self.utility_params_by_good_id, self.quantities_by_good_id ) money_score = linear_utility( self.exchange_params_by_currency_id, self.amount_by_currency_id ) score = goods_score + money_score return score def is_consistent_transaction(self, tx: Transaction) -> bool: """ Check if the transaction is consistent. E.g. check that the agent state has enough money if it is a buyer or enough holdings if it is a seller. :param tx: the transaction :return: True if the transaction is legal wrt the current state, False otherwise. """ result = self.agent_address in [tx.sender_address, tx.counterparty_address] result = result and tx.is_single_currency if not result: return result if all(amount == 0 for amount in tx.amount_by_currency_id.values()) and all( quantity == 0 for quantity in tx.quantities_by_good_id.values() ): # reject the transaction when there is no wealth exchange result = False elif all(amount <= 0 for amount in tx.amount_by_currency_id.values()) and all( quantity >= 0 for quantity in tx.quantities_by_good_id.values() ): # sender is buyer, counterparty is seller if self.agent_address == tx.sender_address: # check this sender state has enough money result = result and ( self.amount_by_currency_id[tx.currency_id] >= tx.sender_payable_amount ) elif self.agent_address == tx.counterparty_address: # check this counterparty state has enough goods result = result and all( self.quantities_by_good_id[good_id] >= quantity for good_id, quantity in tx.quantities_by_good_id.items() ) elif all(amount >= 0 for amount in tx.amount_by_currency_id.values()) and all( quantity <= 0 for quantity in tx.quantities_by_good_id.values() ): # sender is seller, counterparty is buyer # Note, on a ledger, this atomic swap would only be possible for amount == 0! if self.agent_address == tx.sender_address: # check this sender state has enough goods result = result and all( self.quantities_by_good_id[good_id] >= -quantity for good_id, quantity in tx.quantities_by_good_id.items() ) elif self.agent_address == tx.counterparty_address: # check this counterparty state has enough money result = result and ( self.amount_by_currency_id[tx.currency_id] >= tx.counterparty_payable_amount ) else: result = False return result def apply(self, transactions: List[Transaction]) -> "AgentState": """ Apply a list of transactions to the current state. :param transactions: the sequence of transaction. :return: the final state. """ new_state = copy.copy(self) for tx in transactions: new_state.update(tx) return new_state def update(self, tx: Transaction) -> None: """ Update the agent state from a transaction. :param tx: the transaction. """ enforce(self.is_consistent_transaction(tx), "Inconsistent transaction.") new_amount_by_currency_id = self.amount_by_currency_id if self.agent_address == tx.sender_address: # settling the transaction for the sender for currency_id, amount in tx.amount_by_currency_id.items(): new_amount_by_currency_id[currency_id] += amount elif self.agent_address == tx.counterparty_address: # settling the transaction for the counterparty for currency_id, amount in tx.amount_by_currency_id.items(): new_amount_by_currency_id[currency_id] -= amount self._amount_by_currency_id = new_amount_by_currency_id new_quantities_by_good_id = self.quantities_by_good_id for good_id, quantity in tx.quantities_by_good_id.items(): if self.agent_address == tx.sender_address: new_quantities_by_good_id[good_id] += quantity elif self.agent_address == tx.counterparty_address: new_quantities_by_good_id[good_id] -= quantity self._quantities_by_good_id = new_quantities_by_good_id def __copy__(self) -> "AgentState": """Copy the object.""" return AgentState( self.agent_address, self.amount_by_currency_id, self.exchange_params_by_currency_id, self.quantities_by_good_id, self.utility_params_by_good_id, ) def __str__(self) -> str: """From object to string.""" return "AgentState{}".format( pprint.pformat( { "agent_address": self.agent_address, "amount_by_currency_id": self.amount_by_currency_id, "exchange_params_by_currency_id": self.exchange_params_by_currency_id, "quantities_by_good_id": self.quantities_by_good_id, "utility_params_by_good_id": self.utility_params_by_good_id, } ) ) def __eq__(self, other: Any) -> bool: """Compare equality of two instances of the class.""" return ( isinstance(other, AgentState) and self.agent_address == other.agent_address and self.amount_by_currency_id == other.amount_by_currency_id and self.exchange_params_by_currency_id == other.exchange_params_by_currency_id and self.quantities_by_good_id == other.quantities_by_good_id and self.utility_params_by_good_id == other.utility_params_by_good_id ) class Transactions: """Class managing the transactions.""" def __init__(self) -> None: """Instantiate the transaction class.""" self._confirmed = {} # type: Dict[datetime.datetime, Transaction] self._confirmed_per_agent = ( {} ) # type: Dict[Address, Dict[datetime.datetime, Transaction]] @property def confirmed(self) -> Dict[datetime.datetime, Transaction]: """Get the confirmed transactions.""" return self._confirmed @property def confirmed_per_agent( self, ) -> Dict[Address, Dict[datetime.datetime, Transaction]]: """Get the confirmed transactions by agent.""" return self._confirmed_per_agent def add(self, transaction: Transaction) -> None: """ Add a confirmed transaction. :param transaction: the transaction """ now = datetime.datetime.now() self._confirmed[now] = transaction if self._confirmed_per_agent.get(transaction.sender_address) is None: self._confirmed_per_agent[transaction.sender_address] = {} self._confirmed_per_agent[transaction.sender_address][now] = transaction if self._confirmed_per_agent.get(transaction.counterparty_address) is None: self._confirmed_per_agent[transaction.counterparty_address] = {} self._confirmed_per_agent[transaction.counterparty_address][now] = transaction class Registration: """Class managing the registration of the game.""" def __init__(self) -> None: """Instantiate the registration class.""" self._agent_addr_to_name = {} # type: Dict[str, str] @property def agent_addr_to_name(self) -> Dict[str, str]: """Get the registered agent addresses and their names.""" return self._agent_addr_to_name @property def nb_agents(self) -> int: """Get the number of registered agents.""" return len(self._agent_addr_to_name) def register_agent(self, agent_addr: Address, agent_name: str) -> None: """ Register an agent. :param agent_addr: the Address of the agent :param agent_name: the name of the agent """ self._agent_addr_to_name[agent_addr] = agent_name def unregister_agent(self, agent_addr: Address) -> None: """ Register an agent. :param agent_addr: the Address of the agent """ self._agent_addr_to_name.pop(agent_addr) class Game(Model): """A class to manage a TAC instance.""" def __init__(self, **kwargs: Any) -> None: """Instantiate the search class.""" super().__init__(**kwargs) self._phase = Phase.PRE_GAME self._registration = Registration() self._conf = None # type: Optional[Configuration] self._initialization = None # type: Optional[Initialization] self._initial_agent_states = None # type: Optional[Dict[str, AgentState]] self._current_agent_states = None # type: Optional[Dict[str, AgentState]] self._transactions = Transactions() self._already_minted_agents = [] # type: List[str] self._is_allowed_to_mint = True self.is_registered_agent = False @property def phase(self) -> Phase: """Get the game phase.""" return self._phase @phase.setter def phase(self, phase: Phase) -> None: """Set the game phase.""" self.context.logger.debug("Game phase set to: {}".format(phase)) self._phase = phase @property def registration(self) -> Registration: """Get the registration.""" return self._registration @property def conf(self) -> Configuration: """Get game configuration.""" if self._conf is None: raise AEAEnforceError("Call create before calling configuration.") return self._conf @property def initialization(self) -> Initialization: """Get game initialization.""" if self._initialization is None: raise AEAEnforceError("Call create before calling initialization.") return self._initialization @property def initial_agent_states(self) -> Dict[str, AgentState]: """Get initial state of each agent.""" if self._initial_agent_states is None: raise AEAEnforceError("Call create before calling initial_agent_states.") return self._initial_agent_states @property def current_agent_states(self) -> Dict[str, AgentState]: """Get current state of each agent.""" if self._current_agent_states is None: raise AEAEnforceError("Call create before calling current_agent_states.") return self._current_agent_states @property def transactions(self) -> Transactions: """Get the transactions.""" return self._transactions def create(self) -> None: """Create a game.""" enforce(self.phase != Phase.GAME, "A game phase is already active.") self._phase = Phase.GAME_SETUP self._generate() @property def is_allowed_to_mint(self) -> bool: """Get is allowed to mint.""" return self._is_allowed_to_mint @is_allowed_to_mint.setter def is_allowed_to_mint(self, is_allowed_to_mint: bool) -> None: """Get is allowed to mint.""" self._is_allowed_to_mint = is_allowed_to_mint def get_next_agent_state_for_minting(self) -> Optional[AgentState]: """Get next agent state for token minting.""" result = None for agent_addr, agent_state in self.initial_agent_states.items(): if agent_addr in self._already_minted_agents: continue self._already_minted_agents.append(agent_addr) result = agent_state break return result def _generate(self) -> None: """Generate a TAC game.""" parameters = cast(Parameters, self.context.parameters) self._conf = Configuration( parameters.version_id, parameters.tx_fee, self.registration.agent_addr_to_name, parameters.currency_id_to_name, parameters.good_id_to_name, ) scaling_factor = determine_scaling_factor(parameters.money_endowment) agent_addr_to_currency_endowments = generate_currency_endowments( list(self.conf.agent_addr_to_name.keys()), list(self.conf.currency_id_to_name.keys()), parameters.money_endowment, ) agent_addr_to_exchange_params = generate_exchange_params( list(self.conf.agent_addr_to_name.keys()), list(self.conf.currency_id_to_name.keys()), ) agent_addr_to_good_endowments = generate_good_endowments( list(self.conf.agent_addr_to_name.keys()), list(self.conf.good_id_to_name.keys()), parameters.base_good_endowment, parameters.lower_bound_factor, parameters.upper_bound_factor, ) agent_addr_to_utility_params = generate_utility_params( list(self.conf.agent_addr_to_name.keys()), list(self.conf.good_id_to_name.keys()), scaling_factor, ) ( good_id_to_eq_prices, agent_addr_to_eq_good_holdings, agent_addr_to_eq_currency_holdings, ) = generate_equilibrium_prices_and_holdings( agent_addr_to_good_endowments, agent_addr_to_utility_params, agent_addr_to_currency_endowments, agent_addr_to_exchange_params, scaling_factor, ) self._initialization = Initialization( agent_addr_to_currency_endowments, agent_addr_to_exchange_params, agent_addr_to_good_endowments, agent_addr_to_utility_params, good_id_to_eq_prices, agent_addr_to_eq_good_holdings, agent_addr_to_eq_currency_holdings, ) self._initial_agent_states = dict( ( agent_addr, AgentState( agent_addr, self.initialization.agent_addr_to_currency_endowments[agent_addr], self.initialization.agent_addr_to_exchange_params[agent_addr], self.initialization.agent_addr_to_good_endowments[agent_addr], self.initialization.agent_addr_to_utility_params[agent_addr], ), ) for agent_addr in self.conf.agent_addr_to_name.keys() ) self._current_agent_states = dict( ( agent_addr, AgentState( agent_addr, self.initialization.agent_addr_to_currency_endowments[agent_addr], self.initialization.agent_addr_to_exchange_params[agent_addr], self.initialization.agent_addr_to_good_endowments[agent_addr], self.initialization.agent_addr_to_utility_params[agent_addr], ), ) for agent_addr in self.conf.agent_addr_to_name.keys() ) @property def holdings_summary(self) -> str: """Get holdings summary (a string representing the holdings for every agent).""" result = "\n" + "Current good & money allocation & score: \n" for agent_addr, agent_state in self.current_agent_states.items(): result = ( result + "- " + self.conf.agent_addr_to_name[agent_addr] + ":" + "\n" ) for good_id, quantity in agent_state.quantities_by_good_id.items(): result += ( " " + self.conf.good_id_to_name[good_id] + ": " + str(quantity) + "\n" ) for currency_id, amount in agent_state.amount_by_currency_id.items(): result += ( " " + self.conf.currency_id_to_name[currency_id] + ": " + str(amount) + "\n" ) result += " score: " + str(round(agent_state.get_score(), 2)) + "\n" result = result + "\n" return result @property def equilibrium_summary(self) -> str: """Get equilibrium summary.""" result = "\n" + "Equilibrium prices: \n" for good_id, eq_price in self.initialization.good_id_to_eq_prices.items(): result = ( result + self.conf.good_id_to_name[good_id] + " " + str(eq_price) + "\n" ) result = result + "\n" result = result + "Equilibrium good allocation: \n" for ( agent_addr, eq_allocations, ) in self.initialization.agent_addr_to_eq_good_holdings.items(): result = result + "- " + self.conf.agent_addr_to_name[agent_addr] + ":\n" for good_id, quantity in eq_allocations.items(): result = ( result + " " + self.conf.good_id_to_name[good_id] + ": " + str(quantity) + "\n" ) result = result + "\n" result = result + "Equilibrium money allocation: \n" for ( agent_addr, eq_allocations, ) in self.initialization.agent_addr_to_eq_currency_holdings.items(): result = result + "- " + self.conf.agent_addr_to_name[agent_addr] + ":\n" for currency_id, quantity in eq_allocations.items(): result = ( result + " " + self.conf.currency_id_to_name[currency_id] + ": " + str(quantity) + "\n" ) result = result + "\n" return result def is_transaction_valid(self, tx: Transaction) -> bool: """ Check whether the transaction is signed correctly and valid given the state of the game. :param tx: the transaction. :return: True if the transaction is valid, False otherwise. :raises: AEAEnforceError: if the data in the transaction are not allowed (e.g. negative amount). """ sender_state = self.current_agent_states[tx.sender_address] counterparty_state = self.current_agent_states[tx.counterparty_address] result = tx.has_matching_signatures() result = result and sender_state.is_consistent_transaction(tx) result = result and counterparty_state.is_consistent_transaction(tx) return result def settle_transaction(self, tx: Transaction) -> None: """ Settle a valid transaction. :param tx: the game transaction. :raises: AEAEnforceError if the transaction is not valid. """ if self._current_agent_states is None: raise AEAEnforceError("Call create before calling current_agent_states.") enforce(self.is_transaction_valid(tx), "Transaction is not valid.") sender_state = self.current_agent_states[tx.sender_address] counterparty_state = self.current_agent_states[tx.counterparty_address] new_sender_state = sender_state.apply([tx]) new_counterparty_state = counterparty_state.apply([tx]) self.transactions.add(tx) self._current_agent_states.update({tx.sender_address: new_sender_state}) self._current_agent_states.update( {tx.counterparty_address: new_counterparty_state} ) def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self.context.parameters.agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_tac_description(self) -> Description: """Get the tac description for registering.""" description = Description( self.context.parameters.set_service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self.context.parameters.set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self.context.parameters.set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_unregister_tac_description(self) -> Description: """Get the tac description for unregistering.""" description = Description( self.context.parameters.remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description ================================================ FILE: packages/fetchai/skills/tac_control/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers.""" from typing import Optional, cast from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_control.behaviours import TacBehaviour from packages.fetchai.skills.tac_control.dialogues import ( DefaultDialogues, OefSearchDialogue, OefSearchDialogues, TacDialogue, TacDialogues, ) from packages.fetchai.skills.tac_control.game import Game, Phase, Transaction from packages.fetchai.skills.tac_control.parameters import Parameters class TacHandler(Handler): """This class handles oef messages.""" SUPPORTED_PROTOCOL = TacMessage.protocol_id def setup(self) -> None: """Implement the handler setup.""" def handle(self, message: Message) -> None: """ Handle a register message. If the address is already registered, answer with an error message. :param message: the 'get agent state' TacMessage. """ tac_msg = cast(TacMessage, message) # recover dialogue tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) tac_dialogue = cast(TacDialogue, tac_dialogues.update(tac_msg)) if tac_dialogue is None: self._handle_unidentified_dialogue(tac_msg) return self.context.logger.debug( "handling TAC message. performative={}".format(tac_msg.performative) ) if tac_msg.performative == TacMessage.Performative.REGISTER: self._on_register(tac_msg, tac_dialogue) elif tac_msg.performative == TacMessage.Performative.UNREGISTER: self._on_unregister(tac_msg, tac_dialogue) elif tac_msg.performative == TacMessage.Performative.TRANSACTION: self._on_transaction(tac_msg, tac_dialogue) else: self._handle_invalid(tac_msg, tac_dialogue) self.context.logger.warning( "TAC Message performative not recognized or not permitted." ) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, tac_msg: TacMessage) -> None: """ Handle an unidentified dialogue. :param tac_msg: the message """ self.context.logger.info( "received invalid tac message={}, unidentified dialogue (reference={}).".format( tac_msg, tac_msg.dialogue_reference ) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=tac_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"tac_message": tac_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _on_register(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle a register message. If the address is not registered, answer with an error message. :param tac_msg: the tac message :param tac_dialogue: the tac dialogue """ game = cast(Game, self.context.game) if not game.phase == Phase.GAME_REGISTRATION: self.context.logger.warning( "received registration outside of game registration phase: '{}'".format( tac_msg ) ) return parameters = cast(Parameters, self.context.parameters) agent_name = tac_msg.agent_name if len(parameters.whitelist) != 0 and agent_name not in parameters.whitelist: self.context.logger.warning( "agent name not in whitelist: '{}'".format(agent_name) ) error_msg = tac_dialogue.reply( performative=TacMessage.Performative.TAC_ERROR, target_message=tac_msg, error_code=TacMessage.ErrorCode.AGENT_NAME_NOT_IN_WHITELIST, ) self.context.outbox.put_message(message=error_msg) return game = cast(Game, self.context.game) if tac_msg.sender in game.registration.agent_addr_to_name: self.context.logger.warning( "agent already registered: '{}'".format( game.registration.agent_addr_to_name[tac_msg.sender], ) ) error_msg = tac_dialogue.reply( performative=TacMessage.Performative.TAC_ERROR, target_message=tac_msg, error_code=TacMessage.ErrorCode.AGENT_ADDR_ALREADY_REGISTERED, ) self.context.outbox.put_message(message=error_msg) return if agent_name in game.registration.agent_addr_to_name.values(): self.context.logger.warning( "agent with this name already registered: '{}'".format(agent_name) ) error_msg = tac_dialogue.reply( performative=TacMessage.Performative.TAC_ERROR, target_message=tac_msg, error_code=TacMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED, ) self.context.outbox.put_message(message=error_msg) return game.registration.register_agent(tac_msg.sender, agent_name) self.context.logger.info( "agent '{}' registered as '{}'".format(tac_msg.sender, agent_name) ) def _on_unregister(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle a unregister message. If the address is not registered, answer with an error message. :param tac_msg: the tac message :param tac_dialogue: the tac dialogue """ game = cast(Game, self.context.game) if not game.phase == Phase.GAME_REGISTRATION: self.context.logger.warning( "received unregister outside of game registration phase: '{}'".format( tac_msg ) ) return if tac_msg.sender not in game.registration.agent_addr_to_name: self.context.logger.warning( "agent not registered: '{}'".format(tac_msg.sender) ) error_msg = tac_dialogue.reply( performative=TacMessage.Performative.TAC_ERROR, target_message=tac_msg, error_code=TacMessage.ErrorCode.AGENT_NOT_REGISTERED, ) self.context.outbox.put_message(message=error_msg) else: self.context.logger.debug( "agent unregistered: '{}'".format( game.conf.agent_addr_to_name[tac_msg.sender], ) ) game.registration.unregister_agent(tac_msg.sender) def _on_transaction(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle a transaction TacMessage message. If the transaction is invalid (e.g. because the state of the game are not consistent), reply with an error. :param tac_msg: the tac message :param tac_dialogue: the tac dialogue """ game = cast(Game, self.context.game) if not game.phase == Phase.GAME: self.context.logger.warning( "received transaction outside of game phase: '{}'".format(tac_msg) ) return transaction = Transaction.from_message(tac_msg) self.context.logger.debug("handling transaction: {}".format(transaction)) game = cast(Game, self.context.game) if game.is_transaction_valid(transaction): self._handle_valid_transaction(tac_msg, tac_dialogue, transaction) else: self._handle_invalid_transaction(tac_msg, tac_dialogue) def _handle_valid_transaction( self, tac_msg: TacMessage, tac_dialogue: TacDialogue, transaction: Transaction ) -> None: """ Handle a valid transaction. That is: - update the game state - send a transaction confirmation both to the buyer and the seller. :param tac_msg: the message :param tac_dialogue: the fipa dialogue :param transaction: the transaction. """ game = cast(Game, self.context.game) self.context.logger.info( "handling valid transaction: {}".format(transaction.id[-10:]) ) game.settle_transaction(transaction) # send the transaction confirmation. sender_tac_msg = tac_dialogue.reply( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, target_message=tac_msg, transaction_id=transaction.sender_hash, amount_by_currency_id=transaction.amount_by_currency_id, quantities_by_good_id=transaction.quantities_by_good_id, ) self.context.outbox.put_message(message=sender_tac_msg) tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) recovered_tac_dialogues = tac_dialogues.get_dialogues_with_counterparty( transaction.counterparty_address ) if len(recovered_tac_dialogues) != 1: raise ValueError("Error when retrieving dialogue.") recovered_tac_dialogue = recovered_tac_dialogues[0] last_msg = recovered_tac_dialogue.last_message if last_msg is None: raise ValueError("Error when retrieving last message.") counterparty_tac_msg = recovered_tac_dialogue.reply( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, target_message=last_msg, transaction_id=transaction.counterparty_hash, amount_by_currency_id=transaction.amount_by_currency_id, quantities_by_good_id=transaction.quantities_by_good_id, ) self.context.outbox.put_message(message=counterparty_tac_msg) # log messages self.context.logger.info( "transaction '{}' between '{}' and '{}' settled successfully.".format( transaction.id[-10:], sender_tac_msg.sender, counterparty_tac_msg.sender ) ) self.context.logger.info( "total number of transactions settled: {}".format( len(game.transactions.confirmed) ) ) self.context.logger.info("current state:\n{}".format(game.holdings_summary)) def _handle_invalid_transaction( self, tac_msg: TacMessage, tac_dialogue: TacDialogue ) -> None: """Handle an invalid transaction.""" self.context.logger.info( "handling invalid transaction: {}, tac_msg={}".format( tac_msg.transaction_id, tac_msg ) ) error_msg = tac_dialogue.reply( performative=TacMessage.Performative.TAC_ERROR, target_message=tac_msg, error_code=TacMessage.ErrorCode.TRANSACTION_NOT_VALID, info={"transaction_id": tac_msg.transaction_id}, ) self.context.outbox.put_message(message=error_msg) def _handle_invalid(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle a tac message of invalid performative. :param tac_msg: the message :param tac_dialogue: the fipa dialogue """ self.context.logger.warning( "cannot handle tac message of performative={} in dialogue={}.".format( tac_msg.performative, tac_dialogue ) ) class OefSearchHandler(Handler): """Handle the message exchange with the OEF search node.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id def setup(self) -> None: """Implement the handler setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._handle_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.info( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( TacBehaviour, self.context.behaviours.tac, ) if "location_agent" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): game = cast(Game, self.context.game) game.is_registered_agent = True self.context.logger.info( "the agent, with its genus and classification, is successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _handle_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_error_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search error message={} in dialogue={}.".format( oef_search_error_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( TacBehaviour, self.context.behaviours.tac, ) registration_behaviour.failed_registration_msg = target_message def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/tac_control/helpers.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the helpers methods for the controller agent.""" import random from typing import Dict, List, Tuple, cast import numpy as np from aea.exceptions import enforce from packages.fetchai.contracts.erc1155.contract import ERC1155Contract QUANTITY_SHIFT = 1 # Any non-negative integer is fine. FT_NAME = "FT" FT_ID = 2 def generate_good_ids(nb_goods: int, starting_index: int) -> List[int]: """ Generate ids for things. :param nb_goods: the number of things. :param starting_index: the index to start creating from. :return: list of good ids """ good_ids = ERC1155Contract.generate_token_ids(FT_ID, nb_goods, starting_index) enforce( len(good_ids) == nb_goods, "Length of good ids and number of goods must match." ) return good_ids def generate_currency_ids(nb_currencies: int, starting_index: int) -> List[int]: """ Generate currency ids. :param nb_currencies: the number of currencies. :param starting_index: the index to start creating from. :return: list of currency ids """ currency_ids = ERC1155Contract.generate_token_ids( FT_ID, nb_currencies, starting_index ) enforce( len(currency_ids) == nb_currencies, "Length of currency ids and number of currencies must match.", ) return currency_ids def generate_currency_id_to_name( nb_currencies: int, currency_ids: List[int], starting_index: int = 0 ) -> Dict[str, str]: """ Generate a dictionary mapping good ids to names. :param nb_currencies: the number of currencies. :param currency_ids: the currency ids :param starting_index: the starting index :return: a dictionary mapping currency's ids to names. """ if currency_ids != []: enforce( len(currency_ids) == nb_currencies, "Length of currency_ids does not match nb_currencies.", ) else: currency_ids = generate_currency_ids(nb_currencies, starting_index) currency_id_to_name = { str(currency_id): "{}_{}".format(FT_NAME, currency_id) for currency_id in currency_ids } return currency_id_to_name def generate_good_id_to_name( nb_goods: int, good_ids: List[int], starting_index: int = 0 ) -> Dict[str, str]: """ Generate a dictionary mapping good ids to names. :param nb_goods: the number of things. :param good_ids: a list of good ids :param starting_index: the starting index :return: a dictionary mapping goods' ids to names. """ if good_ids != []: enforce( len(good_ids) == nb_goods, "Length of good_ids does not match nb_goods." ) else: good_ids = generate_good_ids(nb_goods, starting_index) good_id_to_name = { str(good_id): "{}_{}".format(FT_NAME, good_id) for good_id in good_ids } return good_id_to_name def determine_scaling_factor(money_endowment: int) -> float: """ Compute the scaling factor based on the money amount. :param money_endowment: the endowment of money for the agent :return: the scaling factor """ scaling_factor = 10.0 ** (len(str(money_endowment)) - 1) return scaling_factor def generate_good_endowments( agent_addresses: List[str], good_ids: List[str], base_amount: int, uniform_lower_bound_factor: int, uniform_upper_bound_factor: int, ) -> Dict[str, Dict[str, int]]: """ Compute good endowments per agent. That is, a matrix of shape (nb_agents, nb_goods). :param agent_addresses: the addresses of the agents :param good_ids: the list of good ids :param base_amount: the base amount of instances per good :param uniform_lower_bound_factor: the lower bound of the uniform distribution for the sampling of the good instance number. :param uniform_upper_bound_factor: the upper bound of the uniform distribution for the sampling of the good instance number. :return: the endowments matrix. """ # sample good instances nb_agents = len(agent_addresses) instances_per_good = _sample_good_instances( nb_agents, good_ids, base_amount, uniform_lower_bound_factor, uniform_upper_bound_factor, ) # each agent receives at least base amount of each good base_assignment = {good_id: base_amount for good_id in good_ids} endowments = {agent_addr: base_assignment for agent_addr in agent_addresses} # randomly assign additional goods to create differences for good_id in good_ids: for _ in range(instances_per_good[good_id] - (base_amount * nb_agents)): idx = random.randint(0, nb_agents - 1) # nosec agent_addr = agent_addresses[idx] endowments[agent_addr][good_id] += 1 return endowments def generate_utility_params( agent_addresses: List[str], good_ids: List[str], scaling_factor: float ) -> Dict[str, Dict[str, float]]: """ Compute the preference matrix. That is, a generic element e_ij is the utility of good j for agent i. :param agent_addresses: the agent addresses :param good_ids: the list of good ids :param scaling_factor: a scaling factor for all the utility params generated. :return: the preference matrix. """ decimals = 4 if len(good_ids) < 100 else 8 utility_function_params = {} # type: Dict[str, Dict[str, float]] for agent_addr in agent_addresses: random_integers = [ random.randint(1, 101) for _ in range(len(good_ids)) # nosec ] total = sum(random_integers) normalized_fractions = [ round(i / float(total), decimals) for i in random_integers ] if not sum(normalized_fractions) == 1.0: # pragma: no cover normalized_fractions[-1] = round( 1.0 - sum(normalized_fractions[0:-1]), decimals ) # scale the utility params params = { good_id: param * scaling_factor for good_id, param in zip(good_ids, normalized_fractions) } utility_function_params[agent_addr] = params return utility_function_params def _sample_good_instances( nb_agents: int, good_ids: List[str], base_amount: int, uniform_lower_bound_factor: int, uniform_upper_bound_factor: int, ) -> Dict[str, int]: """ Sample the number of instances for a good. :param nb_agents: the number of agents :param good_ids: the good ids :param base_amount: the base amount of instances per good :param uniform_lower_bound_factor: the lower bound factor of a uniform distribution :param uniform_upper_bound_factor: the upper bound factor of a uniform distribution :return: the number of instances I sampled. """ a = base_amount * nb_agents + nb_agents * uniform_lower_bound_factor b = base_amount * nb_agents + nb_agents * uniform_upper_bound_factor # Return random integer in range [a, b] nb_instances = {good_id: round(np.random.uniform(a, b)) for good_id in good_ids} return nb_instances def generate_currency_endowments( agent_addresses: List[str], currency_ids: List[str], money_endowment: int ) -> Dict[str, Dict[str, int]]: """ Compute the initial money amounts for each agent. :param agent_addresses: addresses of the agents. :param currency_ids: the currency ids. :param money_endowment: money endowment per agent. :return: the nested dict of currency endowments """ currency_endowment = {currency_id: money_endowment for currency_id in currency_ids} return {agent_addr: currency_endowment for agent_addr in agent_addresses} def generate_exchange_params( agent_addresses: List[str], currency_ids: List[str], ) -> Dict[str, Dict[str, float]]: """ Compute the exchange parameters for each agent. :param agent_addresses: addresses of the agents. :param currency_ids: the currency ids. :return: the nested dict of currency endowments """ exchange_params = {currency_id: 1.0 for currency_id in currency_ids} return {agent_addr: exchange_params for agent_addr in agent_addresses} def generate_equilibrium_prices_and_holdings( # pylint: disable=unused-argument agent_addr_to_good_endowments: Dict[str, Dict[str, int]], agent_addr_to_utility_params: Dict[str, Dict[str, float]], agent_addr_to_currency_endowments: Dict[str, Dict[str, int]], agent_addr_to_exchange_params: Dict[str, Dict[str, float]], scaling_factor: float, quantity_shift: int = QUANTITY_SHIFT, ) -> Tuple[Dict[str, float], Dict[str, Dict[str, float]], Dict[str, Dict[str, float]]]: """ Compute the competitive equilibrium prices and allocation. :param agent_addr_to_good_endowments: endowments of the agents :param agent_addr_to_utility_params: utility function params of the agents (already scaled) :param agent_addr_to_currency_endowments: money endowment per agent. :param agent_addr_to_exchange_params: exchange params per agent. :param scaling_factor: a scaling factor for all the utility params generated. :param quantity_shift: a factor to shift the quantities in the utility function (to ensure the natural logarithm can be used on the entire range of quantities) :return: the lists of equilibrium prices, equilibrium good holdings and equilibrium money holdings """ # create ordered lists agent_addresses = [] # type: List[str] good_ids = [] # type: List[str] good_ids_to_idx = {} # type: Dict[str, int] good_endowments_l = [] # type: List[List[int]] utility_params_l = [] # type: List[List[float]] currency_endowment_l = [] # type: List[int] count = 0 currency_id = "" for agent_addr, good_endowment in agent_addr_to_good_endowments.items(): agent_addresses.append(agent_addr) enforce( len(agent_addr_to_currency_endowments[agent_addr].values()) == 1, "Cannot have more than one currency.", ) currency_endowment_l.append( list(agent_addr_to_currency_endowments[agent_addr].values())[0] ) currency_id = list(agent_addr_to_currency_endowments[agent_addr].keys())[0] enforce( len(good_endowment.keys()) == len(agent_addr_to_utility_params[agent_addr].keys()), "Good endowments and utility params inconsistent.", ) temp_g_e = [0] * len(good_endowment.keys()) temp_u_p = [0.0] * len(agent_addr_to_utility_params[agent_addr].keys()) idx = 0 for good_id, quantity in good_endowment.items(): if count == 0: good_ids.append(good_id) good_ids_to_idx[good_id] = idx idx += 1 temp_g_e[good_ids_to_idx[good_id]] = quantity temp_u_p[good_ids_to_idx[good_id]] = agent_addr_to_utility_params[ agent_addr ][good_id] count += 1 good_endowments_l.append(temp_g_e) utility_params_l.append(temp_u_p) # maths endowments_a = np.array(good_endowments_l, dtype=int) # type: ignore scaled_utility_params_a = np.array( # type: ignore utility_params_l, dtype=float # type: ignore ) # note, they are already scaled endowments_by_good = np.sum(endowments_a, axis=0) scaled_params_by_good = np.sum(scaled_utility_params_a, axis=0) eq_prices = np.divide( scaled_params_by_good, quantity_shift * len(agent_addresses) + endowments_by_good, ) eq_good_holdings = np.divide(scaled_utility_params_a, eq_prices) - quantity_shift eq_currency_holdings = ( np.transpose(np.dot(eq_prices, np.transpose(endowments_a + quantity_shift))) + currency_endowment_l - scaling_factor ) # back to dicts eq_prices_dict = { good_id: cast(float, eq_price) for good_id, eq_price in zip(good_ids, eq_prices.tolist()) } eq_good_holdings_dict = { agent_addr: {good_id: cast(float, v) for good_id, v in zip(good_ids, egh)} for agent_addr, egh in zip(agent_addresses, eq_good_holdings.tolist()) } eq_currency_holdings_dict = { agent_addr: {currency_id: cast(float, eq_currency_holding)} for agent_addr, eq_currency_holding in zip( agent_addresses, eq_currency_holdings.tolist() ) } return eq_prices_dict, eq_good_holdings_dict, eq_currency_holdings_dict ================================================ FILE: packages/fetchai/skills/tac_control/parameters.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a class representing the game parameters.""" import datetime from typing import Any, Dict, List, Optional, Set from aea.exceptions import AEAEnforceError, enforce from aea.helpers.search.models import Location from aea.skills.base import Model from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as CONTRACT_ID from packages.fetchai.skills.tac_control.helpers import ( generate_currency_id_to_name, generate_good_id_to_name, ) DEFAULT_MIN_NB_AGENTS = 2 DEFAULT_MONEY_ENDOWMENT = 200 DEFAULT_NB_GOODS = 9 # ERC1155 vyper contract only accepts 10 tokens per mint/create DEFAULT_NB_CURRENCIES = 1 DEFAULT_TX_FEE = 1 DEFAULT_GAS = 5000000 DEFAULT_BASE_GOOD_ENDOWMENT = 2 DEFAULT_LOWER_BOUND_FACTOR = 1 DEFAULT_UPPER_BOUND_FACTOR = 1 DEFAULT_REGISTRATION_START_TIME = "01 01 2020 00:01" DEFAULT_REGISTRATION_TIMEOUT = 60 DEFAULT_ITEM_SETUP_TIMEOUT = 60 DEFAULT_COMPETITION_TIMEOUT = 300 DEFAULT_INACTIVITY_TIMEOUT = 30 DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_DATA = {"key": "tac", "value": "v1"} DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "service"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "tac.controller"} class Parameters(Model): """This class contains the parameters of the game.""" def __init__(self, **kwargs: Any) -> None: """Instantiate the parameter class.""" ledger_id = kwargs.pop("ledger_id", None) self._contract_address = kwargs.pop( "contract_address", None ) # type: Optional[str] self._good_ids = kwargs.pop("good_ids", []) # type: List[int] self._currency_ids = kwargs.pop("currency_ids", []) # type: List[int] self._min_nb_agents = kwargs.pop( "min_nb_agents", DEFAULT_MIN_NB_AGENTS ) # type: int self._money_endowment = kwargs.pop( "money_endowment", DEFAULT_MONEY_ENDOWMENT ) # type: int self._nb_goods = kwargs.pop("nb_goods", DEFAULT_NB_GOODS) # type: int self._nb_currencies = kwargs.pop( "nb_currencies", DEFAULT_NB_CURRENCIES ) # type: int self._tx_fee = kwargs.pop("tx_fee", DEFAULT_TX_FEE) # type: int self._gas = kwargs.pop("gas", DEFAULT_GAS) # type: int self._base_good_endowment = kwargs.pop( "base_good_endowment", DEFAULT_BASE_GOOD_ENDOWMENT ) # type: int self._lower_bound_factor = kwargs.pop( "lower_bound_factor", DEFAULT_LOWER_BOUND_FACTOR ) # type: int self._upper_bound_factor = kwargs.pop( "upper_bound_factor", DEFAULT_UPPER_BOUND_FACTOR ) # type: int registration_start_time = kwargs.pop( "registration_start_time", DEFAULT_REGISTRATION_START_TIME ) # type: str self._registration_start_time = datetime.datetime.strptime( registration_start_time, "%d %m %Y %H:%M" ) # type: datetime.datetime self._registration_timeout = kwargs.pop( "registration_timeout", DEFAULT_REGISTRATION_TIMEOUT ) # type: int self._item_setup_timeout = kwargs.pop( "item_setup_timeout", DEFAULT_ITEM_SETUP_TIMEOUT ) # type: int self._competition_timeout = kwargs.pop( "competition_timeout", DEFAULT_COMPETITION_TIMEOUT ) # type: int self._inactivity_timeout = kwargs.pop( "inactivity_timeout", DEFAULT_INACTIVITY_TIMEOUT ) # type: int self._whitelist = set(kwargs.pop("whitelist", [])) # type: Set[str] self._location = kwargs.pop("location", DEFAULT_LOCATION) self._service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( len(self._service_data) == 2 and "key" in self._service_data and "value" in self._service_data, "service_data must contain keys `key` and `value`", ) self._version_id = self._service_data["value"] # type: str self._agent_location = { "location": Location( latitude=self._location["latitude"], longitude=self._location["longitude"], ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self._set_service_data = self._service_data self._remove_service_data = {"key": self._service_data["key"]} self._simple_service_data = { self._service_data["key"]: self._service_data["value"] } super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) self._contract_id = str(CONTRACT_ID) self._currency_id_to_name = generate_currency_id_to_name( self.nb_currencies, self.currency_ids ) self._good_id_to_name = generate_good_id_to_name( self.nb_goods, self.good_ids, starting_index=1 ) self._registration_end_time = ( self._registration_start_time + datetime.timedelta(seconds=self._registration_timeout) ) self._start_time = self._registration_end_time + datetime.timedelta( seconds=self._item_setup_timeout ) self._end_time = self._start_time + datetime.timedelta( seconds=self._competition_timeout ) now = datetime.datetime.now() if now > self.registration_start_time: self.context.logger.warning( "TAC registration start time {} is in the past! Deregistering skill.".format( self.registration_start_time ) ) self.context.is_active = False else: self.context.logger.info( "TAC registation start time: {}, and registration end time: {}, and start time: {}, and end time: {}".format( self.registration_start_time, self.registration_end_time, self.start_time, self.end_time, ) ) self._check_consistency() @property def ledger_id(self) -> str: """Get the ledger identifier.""" return self._ledger_id @property def contract_address(self) -> str: """The contract address of an already deployed smart-contract.""" if self._contract_address is None: raise AEAEnforceError("No contract address provided.") return self._contract_address @contract_address.setter def contract_address(self, contract_address: str) -> None: """Set contract address of an already deployed smart-contract.""" if self._contract_address is not None: raise AEAEnforceError("Contract address already provided.") self._contract_address = contract_address @property def contract_id(self) -> str: """Get the contract id.""" return self._contract_id @property def is_contract_deployed(self) -> bool: """Check if there is a deployed instance of the contract.""" return self._contract_address is not None @property def good_ids(self) -> List[int]: """The item ids of an already deployed smart-contract.""" return self._good_ids @property def currency_ids(self) -> List[int]: """The currency ids of an already deployed smart-contract.""" return self._currency_ids @property def min_nb_agents(self) -> int: """Minimum number of agents required for a TAC instance.""" return self._min_nb_agents @property def money_endowment(self) -> int: """Money endowment per agent for a TAC instance.""" return self._money_endowment @property def nb_goods(self) -> int: """Good number for a TAC instance.""" return self._nb_goods @property def nb_currencies(self) -> int: """Currency number for a TAC instance.""" return self._nb_currencies @property def currency_id_to_name(self) -> Dict[str, str]: """Mapping of currency ids to names""" return self._currency_id_to_name @property def good_id_to_name(self) -> Dict[str, str]: """Mapping of good ids to names.""" return self._good_id_to_name @property def tx_fee(self) -> int: """Transaction fee for a TAC instance.""" return self._tx_fee @property def gas(self) -> int: """Gas for TAC contract operations.""" return self._gas @property def base_good_endowment(self) -> int: """Minimum endowment of each agent for each good.""" return self._base_good_endowment @property def lower_bound_factor(self) -> int: """Lower bound of a uniform distribution.""" return self._lower_bound_factor @property def upper_bound_factor(self) -> int: """Upper bound of a uniform distribution.""" return self._upper_bound_factor @property def registration_start_time(self) -> datetime.datetime: """TAC registration start time.""" return self._registration_start_time @property def registration_end_time(self) -> datetime.datetime: """TAC registration end time.""" return self._registration_end_time @property def start_time(self) -> datetime.datetime: """TAC start time.""" return self._start_time @property def end_time(self) -> datetime.datetime: """TAC end time.""" return self._end_time @property def inactivity_timeout(self) -> int: """Timeout of agent inactivity from controller perspective (no received transactions).""" return self._inactivity_timeout @property def whitelist(self) -> Set[str]: """Whitelist of agent addresses allowed into the TAC instance.""" return self._whitelist @property def version_id(self) -> str: """Version id.""" return self._version_id @property def agent_location(self) -> Dict[str, Location]: """Get the agent location.""" return self._agent_location @property def set_service_data(self) -> Dict[str, str]: """Get the set service data.""" return self._set_service_data @property def set_personality_data(self) -> Dict[str, str]: """Get the set service data.""" return self._set_personality_data @property def set_classification(self) -> Dict[str, str]: """Get the set service data.""" return self._set_classification @property def remove_service_data(self) -> Dict[str, str]: """Get the remove service data.""" return self._remove_service_data @property def simple_service_data(self) -> Dict[str, str]: """Get the simple service data.""" return self._simple_service_data def _check_consistency(self) -> None: """Check the parameters are consistent.""" if self._contract_address is not None and ( (self._good_ids is not None and len(self._good_ids) != self._nb_goods) or ( self._currency_ids is not None and len(self._currency_ids) != self._nb_currencies ) ): raise ValueError( "If the contract address is set, then good ids and currency id must be provided and consistent." ) ================================================ FILE: packages/fetchai/skills/tac_control/skill.yaml ================================================ name: tac_control author: fetchai version: 0.25.6 type: skill description: The tac control skill implements the logic for an AEA to control an instance of the TAC. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmdQcAgQ21CSovduTzvhf1nsxwQZWTYkDAc9Tfsz6gXWMh __init__.py: QmQLj6yg3zW6EBiRVLbZVqQojUSEJPcxwqMjFmUeyxkiAn behaviours.py: QmNmcawRFT6NEFCWpwECwE7bbX7ZnVVZXDLDeP5pKiQSZr dialogues.py: QmQWmTQKxhNV9A8B4h2dfmy917LCut3PY7Ny61nJvdxJPH game.py: QmVsUWKiNaQL3Fv58Huqf2BCTePApxkXiedqGMYgJvXyXE handlers.py: QmYdCncGTRQgdi5XB6VdjKZ7oBtphXKt3atrdYM2VtHU6N helpers.py: QmZByC5bR7Fz7K6eDS9Ccu7jMajUPcnQT6KSQouMmHLR13 parameters.py: QmVuYzxPYenwTGUcswCuXC912AeZLaSYb9Sr2Tu3R4chvK fingerprint_ignore_patterns: [] connections: [] contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/default:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/tac:1.1.7 skills: [] behaviours: tac: args: max_soef_registration_retries: 5 class_name: TacBehaviour handlers: oef: args: {} class_name: OefSearchHandler tac: args: {} class_name: TacHandler models: default_dialogues: args: {} class_name: DefaultDialogues game: args: {} class_name: Game oef_search_dialogues: args: {} class_name: OefSearchDialogues parameters: args: base_good_endowment: 2 classification: piece: classification value: tac.controller competition_timeout: 180 currency_ids: [] good_ids: [] inactivity_timeout: 60 item_setup_timeout: 0 location: latitude: 51.5194 longitude: 0.127 lower_bound_factor: 1 min_nb_agents: 2 money_endowment: 2000000 nb_currencies: 1 nb_goods: 9 personality_data: piece: genus value: service registration_start_time: 01 01 2020 00:01 registration_timeout: 60 service_data: key: tac value: v1 tx_fee: 1 upper_bound_factor: 1 whitelist: [] class_name: Parameters tac_dialogues: args: {} class_name: TacDialogues dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 numpy: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/tac_control_contract/README.md ================================================ # TAC Control Contract ## Description This is the skill for managing a smart contract-based TAC. This skill is part of the Fetch.ai TAC demo. It manages the smart contract (contract deployment, creating tokens, minting tokens) and manages the progression of the competition along its various stages. ## Behaviours - `tac`: deploys smart contract, manages progression of the competition ## Handlers - `contract_api`: handles `contract_api` messages for interaction with a smart contract - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef`: handles `oef_search` messages if registration or unregistration on the sOEF is unsuccessful - `signing`: handles `signing` messages for interaction with the decision maker - `tac`: handles `tac` messages for registering/unregistering agents in the TAC ## Links - TAC Demo ================================================ FILE: packages/fetchai/skills/tac_control_contract/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the tac control skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/tac_control_contract:0.27.6") ================================================ FILE: packages/fetchai/skills/tac_control_contract/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours.""" import datetime from typing import List, cast from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.skills.tac_control.behaviours import ( TacBehaviour as BaseTacBehaviour, ) from packages.fetchai.skills.tac_control_contract.dialogues import ( ContractApiDialogue, ContractApiDialogues, ) from packages.fetchai.skills.tac_control_contract.game import Game, Phase from packages.fetchai.skills.tac_control_contract.parameters import Parameters LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) FETCHAI_LEDGER_ID = "fetchai" class TacBehaviour(BaseTacBehaviour): """This class implements the TAC control behaviour.""" def setup(self) -> None: """Implement the setup.""" super().setup() parameters = cast(Parameters, self.context.parameters) if not parameters.is_contract_deployed: game = cast(Game, self.context.game) game.phase = Phase.CONTRACT_DEPLOYMENT_PROPOSAL self._request_contract_deploy_transaction() def _request_contract_deploy_transaction(self) -> None: """Request contract deploy transaction""" parameters = cast(Parameters, self.context.parameters) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) kwargs = { "deployer_address": self.context.agent_address, "gas": parameters.gas, } if parameters.ledger_id == FETCHAI_LEDGER_ID: kwargs["tx_fee"] = parameters.contract_deploy_tx_fee contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=parameters.ledger_id, contract_id=parameters.contract_id, callable=ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs(kwargs), # type: ignore ) contract_api_dialogue = cast( ContractApiDialogue, contract_api_dialogue, ) contract_api_dialogue.terms = parameters.get_deploy_terms() contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting contract deployment transaction...") def act(self) -> None: """Implement the act.""" game = cast(Game, self.context.game) parameters = cast(Parameters, self.context.parameters) now = datetime.datetime.now() if ( game.phase.value == Phase.CONTRACT_DEPLOYED.value and parameters.registration_start_time < now < parameters.registration_end_time ): game.phase = Phase.GAME_REGISTRATION self._register_tac() self.context.logger.info( "TAC open for registration until: {}".format( parameters.registration_end_time ) ) elif ( game.phase.value == Phase.GAME_REGISTRATION.value and parameters.registration_end_time < now < parameters.start_time ): self.context.logger.info("closing registration!") if game.registration.nb_agents < parameters.min_nb_agents: self.context.logger.info( "registered agents={}, minimum agents required={}".format( game.registration.nb_agents, parameters.min_nb_agents, ) ) self._cancel_tac(game) game.phase = Phase.POST_GAME self._unregister_tac() self.context.is_active = False else: game.phase = Phase.GAME_SETUP game.create() game.conf.contract_address = parameters.contract_address self._unregister_tac() elif ( game.phase.value == Phase.GAME_SETUP.value and parameters.registration_end_time < now < parameters.start_time ): game.phase = Phase.TOKENS_CREATION_PROPOSAL self._request_create_items_transaction(game) elif game.phase.value == Phase.TOKENS_CREATED.value: game.phase = Phase.TOKENS_MINTING_PROPOSAL self._request_mint_items_transaction(game) elif game.phase.value == Phase.TOKENS_MINTING_PROPOSAL.value: self._request_mint_items_transaction(game) elif ( game.phase.value == Phase.TOKENS_MINTED.value and parameters.start_time < now < parameters.end_time ): game.phase = Phase.GAME self._start_tac(game) elif game.phase.value == Phase.GAME.value and now > parameters.end_time: game.phase = Phase.POST_GAME self._cancel_tac(game) self.context.is_active = False def _request_create_items_transaction(self, game: Game) -> None: """ Request token create transaction :param game: the game """ parameters = cast(Parameters, self.context.parameters) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) token_ids = [int(good_id) for good_id in game.conf.good_id_to_name.keys()] + [ int(currency_id) for currency_id in game.conf.currency_id_to_name.keys() ] kwargs = { "deployer_address": self.context.agent_address, "token_ids": token_ids, "gas": parameters.gas, } if parameters.ledger_id == FETCHAI_LEDGER_ID: kwargs["tx_fee"] = parameters.contract_execute_tx_fee contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=parameters.ledger_id, contract_id=parameters.contract_id, contract_address=parameters.contract_address, callable=ContractApiDialogue.Callable.GET_CREATE_BATCH_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs(kwargs), # type: ignore ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = parameters.get_create_token_terms() contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_CREATE_BATCH_TRANSACTION ) self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info("requesting create items transaction...") def _request_mint_items_transaction(self, game: Game) -> None: """ Request token mint transaction :param game: the game """ if not game.is_allowed_to_mint: return game.is_allowed_to_mint = False agent_state = game.get_next_agent_state_for_minting() if agent_state is None: return name = game.registration.agent_addr_to_name[agent_state.agent_address] self.context.logger.info( f"requesting mint_items transactions for agent={name}." ) parameters = cast(Parameters, self.context.parameters) token_ids = [] # type: List[int] mint_quantities = [] # type: List[int] for good_id, quantity in agent_state.quantities_by_good_id.items(): token_ids.append(int(good_id)) mint_quantities.append(quantity) for currency_id, amount in agent_state.amount_by_currency_id.items(): token_ids.append(int(currency_id)) mint_quantities.append(amount) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) kwargs = { "deployer_address": self.context.agent_address, "recipient_address": agent_state.agent_address, "token_ids": token_ids, "mint_quantities": mint_quantities, "gas": parameters.gas, } if parameters.ledger_id == FETCHAI_LEDGER_ID: kwargs["tx_fee"] = parameters.contract_execute_tx_fee # type: ignore contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=parameters.ledger_id, contract_id=parameters.contract_id, contract_address=parameters.contract_address, callable=ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs(kwargs), # type: ignore ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = parameters.get_mint_token_terms() contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION ) self.context.outbox.put_message(message=contract_api_msg) ================================================ FILE: packages/fetchai/skills/tac_control_contract/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - OefSearchDialogue: The dialogue class maintains state of a dialogue of type oef_search and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - TacDialogue: The dialogue class maintains state of a dialogue of type tac and manages it. - TacDialogues: The dialogues class keeps track of all dialogues of type tac. """ from enum import Enum from typing import Any, Optional, Type from aea.common import Address from aea.exceptions import enforce from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue as BaseContractApiDialogue, ) from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_control.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.skills.tac_control.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.skills.tac_control.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.skills.tac_control.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.skills.tac_control.dialogues import TacDialogue as BaseTacDialogue from packages.fetchai.skills.tac_control.dialogues import ( TacDialogues as BaseTacDialogues, ) DefaultDialogue = BaseDefaultDialogue DefaultDialogues = BaseDefaultDialogues OefSearchDialogue = BaseOefSearchDialogue OefSearchDialogues = BaseOefSearchDialogues TacDialogue = BaseTacDialogue TacDialogues = BaseTacDialogues class ContractApiDialogue(BaseContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_terms", "_callable") class Callable(Enum): """Contract callable.""" GET_DEPLOY_TRANSACTION = "get_deploy_transaction" GET_CREATE_BATCH_TRANSACTION = "get_create_batch_transaction" GET_MINT_BATCH_TRANSACTION = "get_mint_batch_transaction" def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._terms = None # type: Optional[Terms] self._callable = None # type: Optional[ContractApiDialogue.Callable] @property def callable(self) -> "Callable": """Get the callable.""" if self._callable is None: raise ValueError("Callable not set!") return self._callable @callable.setter def callable( # pylint: disable=redefined-builtin self, callable: "Callable" ) -> None: """Set the callable.""" enforce(self._callable is None, "Callable already set!") self._callable = callable @property def terms(self) -> Terms: """Get the terms.""" if self._terms is None: raise ValueError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set the terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class ContractApiDialogues(Model, BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=ContractApiDialogue, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_contract_api_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_contract_api_dialogue = ( None ) # type: Optional[ContractApiDialogue] @property def associated_contract_api_dialogue(self) -> ContractApiDialogue: """Get the associated contract api dialogue.""" if self._associated_contract_api_dialogue is None: raise ValueError("Associated contract api dialogue not set!") return self._associated_contract_api_dialogue @associated_contract_api_dialogue.setter def associated_contract_api_dialogue( self, associated_contract_api_dialogue: ContractApiDialogue ) -> None: """Set the associated contract api dialogue.""" enforce( self._associated_contract_api_dialogue is None, "Associated contract api dialogue already set!", ) self._associated_contract_api_dialogue = associated_contract_api_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_signing_dialogue",) def __init__( self, dialogue_label: BaseDialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_signing_dialogue = None # type: Optional[SigningDialogue] @property def associated_signing_dialogue(self) -> "SigningDialogue": """Get the associated signing dialogue.""" if self._associated_signing_dialogue is None: raise ValueError("Associated signing dialogue not set!") return self._associated_signing_dialogue @associated_signing_dialogue.setter def associated_signing_dialogue( self, associated_signing_dialogue: "SigningDialogue" ) -> None: """Set the associated signing dialogue.""" enforce( self._associated_signing_dialogue is None, "Associated signing dialogue already set!", ) self._associated_signing_dialogue = associated_signing_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) ================================================ FILE: packages/fetchai/skills/tac_control_contract/game.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a class representing the game.""" from packages.fetchai.skills.tac_control.game import AgentState as BaseAgentState from packages.fetchai.skills.tac_control.game import Configuration as BaseConfiguration from packages.fetchai.skills.tac_control.game import Game as BaseGame from packages.fetchai.skills.tac_control.game import ( Initialization as BaseInitialization, ) from packages.fetchai.skills.tac_control.game import Phase as BasePhase from packages.fetchai.skills.tac_control.game import Registration as BaseRegistration from packages.fetchai.skills.tac_control.game import Transaction as BaseTransaction from packages.fetchai.skills.tac_control.game import Transactions as BaseTransactions AgentState = BaseAgentState Configuration = BaseConfiguration Game = BaseGame Initialization = BaseInitialization Phase = BasePhase Registration = BaseRegistration Transaction = BaseTransaction Transactions = BaseTransactions ================================================ FILE: packages/fetchai/skills/tac_control_contract/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers.""" from typing import Optional, cast from aea_ledger_fetchai import FetchAIApi from aea.common import JSONLike from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_control.handlers import ( OefSearchHandler as BaseOefSearchHandler, ) from packages.fetchai.skills.tac_control.handlers import TacHandler as BaseTacHandler from packages.fetchai.skills.tac_control_contract.dialogues import ( ContractApiDialogue, ContractApiDialogues, ContractApiMessage, LedgerApiDialogue, LedgerApiDialogues, LedgerApiMessage, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.tac_control_contract.game import Game, Phase from packages.fetchai.skills.tac_control_contract.parameters import Parameters LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) TacHandler = BaseTacHandler OefSearchHandler = BaseOefSearchHandler class ContractApiHandler(Handler): """Implement the contract api handler.""" SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ contract_api_msg = cast(ContractApiMessage, message) # recover dialogue contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_dialogue = cast( Optional[ContractApiDialogue], contract_api_dialogues.update(contract_api_msg), ) if contract_api_dialogue is None: self._handle_unidentified_dialogue(contract_api_msg) return # handle message if ( contract_api_msg.performative is ContractApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(contract_api_msg, contract_api_dialogue) elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: self._handle_error(contract_api_msg, contract_api_dialogue) else: self._handle_invalid(contract_api_msg, contract_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue( self, contract_api_msg: ContractApiMessage ) -> None: """ Handle an unidentified dialogue. :param contract_api_msg: the message """ self.context.logger.info( "received invalid contract_api message={}, unidentified dialogue.".format( contract_api_msg ) ) def _handle_raw_transaction( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_transaction performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=contract_api_msg.raw_transaction, terms=contract_api_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of error performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received contract_api error message={} in dialogue={}.".format( contract_api_msg, contract_api_dialogue ) ) def _handle_invalid( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of invalid performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle contract_api message of performative={} in dialogue={}.".format( contract_api_msg.performative, contract_api_dialogue, ) ) class SigningHandler(Handler): """Implement the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info("transaction signing was successful.") ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, signed_transaction=signing_msg.signed_transaction, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) class LedgerApiHandler(Handler): """Implement the ledger api handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg, ledger_api_dialogue) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.outbox.put_message(message=msg) self.context.logger.info("requesting transaction receipt.") def _request_init_transaction( self, ledger_id: str, deployment_tx_receipt: JSONLike ) -> None: """ Send a message to request the initialisation transaction to finalise contract deployment on fetch ledger :param ledger_id: the ledger id :param deployment_tx_receipt: the transaction receipt """ parameters = cast(Parameters, self.context.parameters) contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) fetchai_api = cast(FetchAIApi, LedgerApis.get_api(ledger_id)) code_id = fetchai_api.get_code_id(deployment_tx_receipt) # type: Optional[int] if code_id: contract_api_msg, dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=parameters.ledger_id, contract_id=parameters.contract_id, callable=ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( { "label": "TACERC1155", "init_msg": {}, "gas": parameters.gas, "amount": 0, "code_id": code_id, "deployer_address": self.context.agent_address, "tx_fee": parameters.contract_init_tx_fee, } ), ) contract_api_dialogue = cast(ContractApiDialogue, dialogue) contract_api_dialogue.terms = parameters.get_deploy_terms( is_init_transaction=True ) contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info( "requesting contract initialisation transaction..." ) else: # pragma: nocover self.context.logger.info("Failed to initialise contract: code_id not found") def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_receipt performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ ledger_id = ledger_api_msg.transaction_receipt.ledger_id tx_receipt = ledger_api_msg.transaction_receipt.receipt is_transaction_successful = LedgerApis.is_transaction_settled( ledger_id, tx_receipt ) signing_dialogue = ledger_api_dialogue.associated_signing_dialogue contract_api_dialogue = signing_dialogue.associated_contract_api_dialogue if is_transaction_successful: self.context.logger.info( "transaction was successfully settled. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) parameters = cast(Parameters, self.context.parameters) game = cast(Game, self.context.game) if ( contract_api_dialogue.callable == ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ): transaction_label = contract_api_dialogue.terms.kwargs.get( "label", None ) if game.phase != Phase.CONTRACT_DEPLOYED: if ( transaction_label == "store" ): # deploying contract on fetch ledger self._request_init_transaction(ledger_id, tx_receipt) elif transaction_label in {"deploy", "init"}: contract_address = LedgerApis.get_contract_address( ledger_id, tx_receipt ) if contract_address is None: raise ValueError( "No contract address found." ) # pragma: nocover parameters.contract_address = contract_address game.phase = Phase.CONTRACT_DEPLOYED self.context.logger.info("contract deployed.") else: self.context.logger.error( f"Invalid transaction label: {transaction_label}" ) # pragma: nocover elif ( contract_api_dialogue.callable == ContractApiDialogue.Callable.GET_CREATE_BATCH_TRANSACTION ): game.phase = Phase.TOKENS_CREATED self.context.logger.info("tokens created.") elif ( contract_api_dialogue.callable == ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION ): self.context.logger.info("tokens minted.") parameters.nb_completed_minting += 1 game.is_allowed_to_mint = True if game.registration.nb_agents == parameters.nb_completed_minting: game.phase = Phase.TOKENS_MINTED self.context.logger.info("all tokens minted.") else: self.context.logger.error("unexpected transaction receipt!") else: self.context.logger.error( "transaction failed. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/tac_control_contract/helpers.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the helpers methods for the controller agent.""" import random from typing import Dict, List, Tuple, cast import numpy as np from aea.exceptions import enforce from packages.fetchai.contracts.erc1155.contract import ERC1155Contract QUANTITY_SHIFT = 1 # Any non-negative integer is fine. FT_NAME = "FT" FT_ID = 2 NB_CURRENCIES = 1 def generate_good_id_to_name(good_ids: List[int]) -> Dict[str, str]: """ Generate a dictionary mapping good ids to names. :param good_ids: a list of good ids :return: a dictionary mapping goods' ids to names. """ good_id_to_name = { str(good_id): "{}_{}".format(FT_NAME, good_id) for good_id in good_ids } return good_id_to_name def generate_good_ids(nb_goods: int) -> List[int]: """ Generate ids for things. :param nb_goods: the number of things. :return: a list of good ids """ good_ids = ERC1155Contract.generate_token_ids(FT_ID, nb_goods) enforce( len(good_ids) == nb_goods, "Length of good ids and number of goods must match." ) return good_ids def generate_currency_id_to_name(currency_ids: List[int]) -> Dict[str, str]: """ Generate a dictionary mapping good ids to names. :param currency_ids: the currency ids :return: a dictionary mapping currency's ids to names. """ currency_id_to_name = { str(currency_id): "{}_{}".format(FT_NAME, currency_id) for currency_id in currency_ids } return currency_id_to_name def generate_currency_ids(nb_currencies: int) -> List[int]: """ Generate currency ids. :param nb_currencies: the number of currencies. :return: a list of currency ids """ currency_ids = ERC1155Contract.generate_token_ids(FT_ID, nb_currencies) enforce( len(currency_ids) == nb_currencies, "Length of currency ids and number of currencies must match.", ) return currency_ids def determine_scaling_factor(money_endowment: int) -> float: """ Compute the scaling factor based on the money amount. :param money_endowment: the endowment of money for the agent :return: the scaling factor """ scaling_factor = 10.0 ** (len(str(money_endowment)) - 1) return scaling_factor def generate_good_endowments( agent_addresses: List[str], good_ids: List[str], base_amount: int, uniform_lower_bound_factor: int, uniform_upper_bound_factor: int, ) -> Dict[str, Dict[str, int]]: """ Compute good endowments per agent. That is, a matrix of shape (nb_agents, nb_goods). :param agent_addresses: the addresses of the agents :param good_ids: the list of good ids :param base_amount: the base amount of instances per good :param uniform_lower_bound_factor: the lower bound of the uniform distribution for the sampling of the good instance number. :param uniform_upper_bound_factor: the upper bound of the uniform distribution for the sampling of the good instance number. :return: the endowments matrix. """ # sample good instances nb_agents = len(agent_addresses) instances_per_good = _sample_good_instances( nb_agents, good_ids, base_amount, uniform_lower_bound_factor, uniform_upper_bound_factor, ) # each agent receives at least base amount of each good base_assignment = {good_id: base_amount for good_id in good_ids} endowments = {agent_addr: base_assignment for agent_addr in agent_addresses} # randomly assign additional goods to create differences for good_id in good_ids: for _ in range(instances_per_good[good_id] - (base_amount * nb_agents)): idx = random.randint(0, nb_agents - 1) # nosec agent_addr = agent_addresses[idx] endowments[agent_addr][good_id] += 1 return endowments def generate_utility_params( agent_addresses: List[str], good_ids: List[str], scaling_factor: float ) -> Dict[str, Dict[str, float]]: """ Compute the preference matrix. That is, a generic element e_ij is the utility of good j for agent i. :param agent_addresses: the agent addresses :param good_ids: the list of good ids :param scaling_factor: a scaling factor for all the utility params generated. :return: the preference matrix. """ decimals = 4 if len(good_ids) < 100 else 8 utility_function_params = {} # type: Dict[str, Dict[str, float]] for agent_addr in agent_addresses: random_integers = [ random.randint(1, 101) for _ in range(len(good_ids)) # nosec ] total = sum(random_integers) normalized_fractions = [ round(i / float(total), decimals) for i in random_integers ] if not sum(normalized_fractions) == 1.0: normalized_fractions[-1] = round( # pragma: no cover 1.0 - sum(normalized_fractions[0:-1]), decimals ) # scale the utility params params = { good_id: param * scaling_factor for good_id, param in zip(good_ids, normalized_fractions) } utility_function_params[agent_addr] = params return utility_function_params def _sample_good_instances( nb_agents: int, good_ids: List[str], base_amount: int, uniform_lower_bound_factor: int, uniform_upper_bound_factor: int, ) -> Dict[str, int]: """ Sample the number of instances for a good. :param nb_agents: the number of agents :param good_ids: the good ids :param base_amount: the base amount of instances per good :param uniform_lower_bound_factor: the lower bound factor of a uniform distribution :param uniform_upper_bound_factor: the upper bound factor of a uniform distribution :return: the number of instances I sampled. """ a = base_amount * nb_agents + nb_agents * uniform_lower_bound_factor b = base_amount * nb_agents + nb_agents * uniform_upper_bound_factor # Return random integer in range [a, b] nb_instances = {good_id: round(np.random.uniform(a, b)) for good_id in good_ids} return nb_instances def generate_currency_endowments( agent_addresses: List[str], currency_ids: List[str], money_endowment: int ) -> Dict[str, Dict[str, int]]: """ Compute the initial money amounts for each agent. :param agent_addresses: addresses of the agents. :param currency_ids: the currency ids. :param money_endowment: money endowment per agent. :return: the nested dict of currency endowments """ currency_endowment = {currency_id: money_endowment for currency_id in currency_ids} return {agent_addr: currency_endowment for agent_addr in agent_addresses} def generate_exchange_params( agent_addresses: List[str], currency_ids: List[str], ) -> Dict[str, Dict[str, float]]: """ Compute the exchange parameters for each agent. :param agent_addresses: addresses of the agents. :param currency_ids: the currency ids. :return: the nested dict of currency endowments """ exchange_params = {currency_id: 1.0 for currency_id in currency_ids} return {agent_addr: exchange_params for agent_addr in agent_addresses} def generate_equilibrium_prices_and_holdings( # pylint: disable=unused-argument agent_addr_to_good_endowments: Dict[str, Dict[str, int]], agent_addr_to_utility_params: Dict[str, Dict[str, float]], agent_addr_to_currency_endowments: Dict[str, Dict[str, int]], agent_addr_to_exchange_params: Dict[str, Dict[str, float]], scaling_factor: float, quantity_shift: int = QUANTITY_SHIFT, ) -> Tuple[Dict[str, float], Dict[str, Dict[str, float]], Dict[str, float]]: """ Compute the competitive equilibrium prices and allocation. :param agent_addr_to_good_endowments: endowments of the agents :param agent_addr_to_utility_params: utility function params of the agents (already scaled) :param agent_addr_to_currency_endowments: money endowment per agent. :param agent_addr_to_exchange_params: exchange params per agent. :param scaling_factor: a scaling factor for all the utility params generated. :param quantity_shift: a factor to shift the quantities in the utility function (to ensure the natural logarithm can be used on the entire range of quantities) :return: the lists of equilibrium prices, equilibrium good holdings and equilibrium money holdings """ # create ordered lists agent_addresses = [] # type: List[str] good_ids = [] # type: List[str] good_ids_to_idx = {} # type: Dict[str, int] good_endowments_l = [] # type: List[List[int]] utility_params_l = [] # type: List[List[float]] currency_endowment_l = [] # type: List[int] count = 0 for agent_addr, good_endowment in agent_addr_to_good_endowments.items(): agent_addresses.append(agent_addr) enforce( len(agent_addr_to_currency_endowments[agent_addr].values()) == 1, "Cannot have more than one currency.", ) currency_endowment_l.append( list(agent_addr_to_currency_endowments[agent_addr].values())[0] ) enforce( len(good_endowment.keys()) == len(agent_addr_to_utility_params[agent_addr].keys()), "Good endowments and utility params inconsistent.", ) temp_g_e = [0] * len(good_endowment.keys()) temp_u_p = [0.0] * len(agent_addr_to_utility_params[agent_addr].keys()) idx = 0 for good_id, quantity in good_endowment.items(): if count == 0: good_ids.append(good_id) good_ids_to_idx[good_id] = idx idx += 1 temp_g_e[good_ids_to_idx[good_id]] = quantity temp_u_p[good_ids_to_idx[good_id]] = agent_addr_to_utility_params[ agent_addr ][good_id] count += 1 good_endowments_l.append(temp_g_e) utility_params_l.append(temp_u_p) # maths endowments_a = np.array(good_endowments_l, dtype=int) # type: ignore scaled_utility_params_a = np.array( # type: ignore utility_params_l, dtype=float # type: ignore ) # note, they are already scaled endowments_by_good = np.sum(endowments_a, axis=0) scaled_params_by_good = np.sum(scaled_utility_params_a, axis=0) eq_prices = np.divide( scaled_params_by_good, quantity_shift * len(agent_addresses) + endowments_by_good, ) eq_good_holdings = np.divide(scaled_utility_params_a, eq_prices) - quantity_shift eq_currency_holdings = ( np.transpose(np.dot(eq_prices, np.transpose(endowments_a + quantity_shift))) + currency_endowment_l - scaling_factor ) # back to dicts eq_prices_dict = { good_id: cast(float, eq_price) for good_id, eq_price in zip(good_ids, eq_prices.tolist()) } eq_good_holdings_dict = { agent_addr: {good_id: cast(float, v) for good_id, v in zip(good_ids, egh)} for agent_addr, egh in zip(agent_addresses, eq_good_holdings.tolist()) } eq_currency_holdings_dict = { agent_addr: cast(float, eq_currency_holding) for agent_addr, eq_currency_holding in zip( agent_addresses, eq_currency_holdings.tolist() ) } return eq_prices_dict, eq_good_holdings_dict, eq_currency_holdings_dict ================================================ FILE: packages/fetchai/skills/tac_control_contract/parameters.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a class representing the game parameters.""" from typing import Any from aea_ledger_ethereum import EthereumApi from aea_ledger_fetchai import FetchAIApi from aea.helpers.transaction.base import Terms from packages.fetchai.skills.tac_control.parameters import Parameters as BaseParameters DEFAULT_CONTRACT_DEPLOY_FEE = 2000000000000000 DEFAULT_CONTRACT_INIT_FEE = 2000000000000000 DEFAULT_CONTRACT_EXECUTE_FEE = 2000000000000000 class Parameters(BaseParameters): """This class contains the parameters of the game.""" def __init__(self, **kwargs: Any) -> None: """Instantiate the parameter class.""" super().__init__(**kwargs) self.nb_completed_minting = 0 self._contract_init_tx_fee = kwargs.pop( "contract_init_tx_fee", DEFAULT_CONTRACT_INIT_FEE ) # type: int self._contract_deploy_tx_fee = kwargs.pop( "contract_deploy_tx_fee", DEFAULT_CONTRACT_DEPLOY_FEE ) # type: int self._contract_execute_tx_fee = kwargs.pop( "contract_execute_tx_fee", DEFAULT_CONTRACT_EXECUTE_FEE ) # type: int def get_deploy_terms(self, is_init_transaction: bool = False) -> Terms: """ Get deploy terms of deployment. :param is_init_transaction: whether this is for contract initialisation stage (for fetch ledger) or not. :return: terms """ if self.ledger_id == EthereumApi.identifier: label = "deploy" elif self.ledger_id == FetchAIApi.identifier: label = "store" if is_init_transaction: label = "init" else: raise ValueError( f"Unidentified ledger id: {self.ledger_id}" ) # pragma: nocover terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", label=label, ) return terms def get_create_token_terms(self) -> Terms: """ Get create token terms of deployment. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) return terms def get_mint_token_terms(self) -> Terms: """ Get mint token terms of deployment. :return: terms """ terms = Terms( ledger_id=self.ledger_id, sender_address=self.context.agent_address, counterparty_address=self.context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) return terms @property def contract_deploy_tx_fee(self) -> int: """Transaction fee for a TAC instance.""" return self._contract_deploy_tx_fee @property def contract_init_tx_fee(self) -> int: """Transaction fee for a TAC instance.""" return self._contract_init_tx_fee @property def contract_execute_tx_fee(self) -> int: """Transaction fee for a TAC instance.""" return self._contract_execute_tx_fee ================================================ FILE: packages/fetchai/skills/tac_control_contract/skill.yaml ================================================ name: tac_control_contract author: fetchai version: 0.27.6 type: skill description: The tac control skill implements the logic for an AEA to control an instance of the TAC. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmYoLcfTSQNX3FPna6g2ttKTQRjF8DpxpzLxEQLdg4whe1 __init__.py: QmVpmZmA7JNrBUTRdeThgw9GTnPMDEg4JRNLu8WUh8ZqWa behaviours.py: QmV6UDZrmUXVfoqU8J5GgQmtanDKNbUd14t6UEJGSaynx8 dialogues.py: QmbxjXD42RbKtYWUwnQzscAiKJuijfNgUK3G1WJfU9Lkxf game.py: QmYuh179BNFk4vt2df8WYDWT6C4J7ogJEgm4d1vUUNCEa7 handlers.py: QmUF3inkBDQ7ufvYyw238BM5LFeTq2T19FBT2MTBKHK7uo helpers.py: QmdkhuZ1tAb5fn16dSiwLDHzhpF16FKK7DCHp5cJnGobHQ parameters.py: QmcgNoSMmvEj4kwLJSdfZYi6hSG6os6WuwgYuZoj4DbWQc fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/default:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/tac:1.1.7 skills: - fetchai/tac_control:0.25.6 behaviours: tac: args: {} class_name: TacBehaviour handlers: contract_api: args: {} class_name: ContractApiHandler ledger_api: args: {} class_name: LedgerApiHandler oef: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler tac: args: {} class_name: TacHandler models: contract_api_dialogues: args: {} class_name: ContractApiDialogues default_dialogues: args: {} class_name: DefaultDialogues game: args: {} class_name: Game ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues parameters: args: base_good_endowment: 4 competition_timeout: 360 contract_deploy_tx_fee: 2000000000000000 contract_execute_tx_fee: 2000000000000000 contract_init_tx_fee: 2000000000000000 currency_ids: [] good_ids: [] inactivity_timeout: 60 item_setup_timeout: 120 location: latitude: 51.5194 longitude: 0.127 lower_bound_factor: 1 min_nb_agents: 2 money_endowment: 2000000 nb_currencies: 1 nb_goods: 9 registration_start_time: 01 01 2020 00:01 registration_timeout: 60 service_data: key: tac value: v1 tx_fee: 1 upper_bound_factor: 1 whitelist: [] class_name: Parameters signing_dialogues: args: {} class_name: SigningDialogues tac_dialogues: args: {} class_name: TacDialogues dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 numpy: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/tac_negotiation/README.md ================================================ # TAC Negotiation ## Description This is the skill for negotiation in a TAC. This skill is part of the Fetch.ai TAC demo. It manages registration and searching of agents and services on the sOEF and negotiations of goods with other agents. ## Behaviours - `clean_up`: updates and cleans up confirmed and pending transactions - `tac_negotiation`: registers/unregisters the agent and its buying/selling services on the sOEF ## Handlers - `contract_api`: handles `contract_api` messages for interaction with a smart contract - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef`: handles `oef_search` messages to manage the buyers/sellers it finds - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - TAC Demo ================================================ FILE: packages/fetchai/skills/tac_negotiation/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the tac negotiation skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/tac_negotiation:0.29.6") ================================================ FILE: packages/fetchai/skills/tac_negotiation/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a behaviour.""" from typing import Any, Optional, cast from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.tac_negotiation.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.tac_negotiation.strategy import Strategy from packages.fetchai.skills.tac_negotiation.transactions import Transactions DEFAULT_MAX_SOEF_REGISTRATION_RETRIES = 5 DEFAULT_REGISTER_AND_SEARCH_INTERVAL = 5.0 class GoodsRegisterAndSearchBehaviour(TickerBehaviour): """This class implements the goods register and search behaviour.""" def __init__(self, **kwargs: Any): """Initialize the search behaviour.""" search_interval = cast( float, kwargs.pop("search_interval", DEFAULT_REGISTER_AND_SEARCH_INTERVAL) ) self._max_soef_registration_retries = kwargs.pop( "max_soef_registration_retries", DEFAULT_MAX_SOEF_REGISTRATION_RETRIES ) # type: int super().__init__(tick_interval=search_interval, **kwargs) self.is_registered = False self.failed_registration_msg = None # type: Optional[OefSearchMessage] self._nb_retries = 0 def setup(self) -> None: """Implement the setup.""" def act(self) -> None: """Implement the act.""" # the flag "is_game_finished" is set by the 'tac_participation' # skill to notify the other skill that the TAC game is finished. if self.context.shared_state.get("is_game_finished", False): self.context.is_active = False return if ( not self.context.decision_maker_handler_context.goal_pursuit_readiness.is_ready ): return strategy = cast(Strategy, self.context.strategy) strategy.tac_version_id = self.context.shared_state.get("tac_version_id", None) if strategy.tac_version_id is None: self.context.logger.error("Cannot get the tac_version_id. Stopping!") self.context.is_active = False return if not self.is_registered: self._retry_failed_registration() self._register_agent() self._search_services() def teardown(self) -> None: """Implement the task teardown.""" if self.is_registered: self._unregister_service() self._unregister_agent() self.is_registered = False def _retry_failed_registration(self) -> None: """Retry a failed registration.""" if self.failed_registration_msg is not None: self._nb_retries += 1 if self._nb_retries > self._max_soef_registration_retries: self.context.is_active = False return oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.failed_registration_msg.to, performative=self.failed_registration_msg.performative, service_description=self.failed_registration_msg.service_description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( f"Retrying registration on SOEF. Retry {self._nb_retries} out of {self._max_soef_registration_retries}." ) self.failed_registration_msg = None def _register(self, description: Description, logger_msg: str) -> None: """ Register something on the SOEF. :param description: the description of what is being registered :param logger_msg: the logger message to print after the registration """ oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info(logger_msg) def _register_agent(self) -> None: """Register the agent's location.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() self._register(description, "registering agent on SOEF.") def register_service(self) -> None: """Register the agent's service.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_service_description() self._register( description, "updating service directory as {}.".format(strategy.registering_as), ) def register_genus(self) -> None: """Register the agent's personality genus.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_personality_description() self._register( description, "registering agent's personality genus on the SOEF." ) def register_classification(self) -> None: """Register the agent's personality classification.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_register_classification_description() self._register( description, "registering agent's personality classification on the SOEF." ) def _unregister_service(self) -> None: """Unregister service from OEF Service Directory.""" strategy = cast(Strategy, self.context.strategy) oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) self.context.logger.debug( "unregistering from service directory as {}.".format( strategy.registering_as ) ) description = strategy.get_unregister_service_description() oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) def _unregister_agent(self) -> None: """Unregister agent from the SOEF.""" strategy = cast(Strategy, self.context.strategy) description = strategy.get_location_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info("unregistering agent from SOEF.") def _search_services(self) -> None: """ Search on OEF Service Directory. In particular, search - for sellers and their supply, or - for buyers and their demand, or - for both. """ strategy = cast(Strategy, self.context.strategy) oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) query = strategy.get_location_and_service_query() for (is_seller_search, searching_for) in strategy.searching_for_types: oef_search_msg, oef_search_dialogue = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) oef_search_dialogue = cast(OefSearchDialogue, oef_search_dialogue) oef_search_dialogue.is_seller_search = is_seller_search self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( "searching for {}, search_id={}.".format( searching_for, oef_search_msg.dialogue_reference ) ) class TransactionCleanUpBehaviour(TickerBehaviour): """This class implements the cleanup of the transactions class.""" def setup(self) -> None: """Implement the setup.""" def act(self) -> None: """Implement the task execution.""" transactions = cast(Transactions, self.context.transactions) transactions.update_confirmed_transactions() transactions.cleanup_pending_transactions() def teardown(self) -> None: """Implement the task teardown.""" ================================================ FILE: packages/fetchai/skills/tac_negotiation/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - Dialogues: The dialogues class keeps track of all dialogues. """ from typing import Any, Optional, Type, cast from aea.common import Address from aea.exceptions import enforce from aea.helpers.search.models import Description from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel from aea.skills.base import Model from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue as BaseContractApiDialogue, ) from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.cosm_trade.dialogues import ( CosmTradeDialogue as BaseCosmTradeDialogue, ) from packages.fetchai.protocols.cosm_trade.dialogues import ( CosmTradeDialogues as BaseCosmTradeDialogues, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue as BaseLedgerApiDialogue, ) from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_negotiation.helpers import ( DEMAND_DATAMODEL_NAME, SUPPLY_DATAMODEL_NAME, ) class FipaDialogue(BaseFipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_proposal", "_terms", "_counterparty_signature") def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[FipaMessage] = FipaMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseFipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._proposal = None # type: Optional[Description] self._terms = None # type: Optional[Terms] self._counterparty_signature = None # type: Optional[str] @property def counterparty_signature(self) -> str: """Get counterparty signature.""" if self._counterparty_signature is None: raise ValueError("counterparty_signature not set!") return self._counterparty_signature @counterparty_signature.setter def counterparty_signature(self, counterparty_signature: str) -> None: """Set is_seller_search.""" enforce( self._counterparty_signature is None, "counterparty_signature already set!" ) self._counterparty_signature = counterparty_signature @property def proposal(self) -> Description: """Get the proposal.""" if self._proposal is None: raise ValueError("Proposal not set!") return self._proposal @proposal.setter def proposal(self, proposal: Description) -> None: """Set the proposal.""" enforce(self._proposal is None, "Proposal already set!") self._proposal = proposal @property def terms(self) -> Terms: """Get the terms.""" if self._terms is None: raise ValueError("Terms not set!") return self._terms @terms.setter def terms(self, terms: Terms) -> None: """Set the terms.""" enforce(self._terms is None, "Terms already set!") self._terms = terms class FipaDialogues(Model, BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ fipa_message = cast(FipaMessage, message) if fipa_message.performative != FipaMessage.Performative.CFP: raise ValueError("First message must be a CFP!") # pragma: nocover query = fipa_message.query if query.model is None: raise ValueError("Query must have a data model!") # pragma: nocover if query.model.name not in [ SUPPLY_DATAMODEL_NAME, DEMAND_DATAMODEL_NAME, ]: raise ValueError( # pragma: nocover "Query data model name must be in [{},{}]".format( SUPPLY_DATAMODEL_NAME, DEMAND_DATAMODEL_NAME ) ) if message.sender != receiver_address: # message is by other is_seller = ( query.model.name == SUPPLY_DATAMODEL_NAME ) # the counterparty is querying for supply/sellers (this agent is receiving their CFP so is the seller) else: # message is by self is_seller = ( query.model.name == DEMAND_DATAMODEL_NAME ) # the agent is querying for demand/buyers (this agent is sending the CFP so it is the seller) role = FipaDialogue.Role.SELLER if is_seller else FipaDialogue.Role.BUYER return role BaseFipaDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) class ContractApiDialogue(BaseContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_fipa_dialogue",) def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[ContractApiMessage] = ContractApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_fipa_dialogue: Optional[FipaDialogue] = None @property def associated_fipa_dialogue(self) -> FipaDialogue: """Get associated_fipa_dialogue.""" if self._associated_fipa_dialogue is None: raise ValueError("associated_fipa_dialogue not set!") return self._associated_fipa_dialogue @associated_fipa_dialogue.setter def associated_fipa_dialogue(self, associated_fipa_dialogue: FipaDialogue) -> None: """Set associated_fipa_dialogue.""" enforce( self._associated_fipa_dialogue is None, "associated_fipa_dialogue already set!", ) self._associated_fipa_dialogue = associated_fipa_dialogue class ContractApiDialogues(Model, BaseContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=ContractApiDialogue, ) CosmTradeDialogue = BaseCosmTradeDialogue class CosmTradeDialogues(Model, BaseCosmTradeDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return CosmTradeDialogue.Role.AGENT BaseCosmTradeDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, dialogue_class=CosmTradeDialogue, ) DefaultDialogue = BaseDefaultDialogue class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) class LedgerApiDialogue(BaseLedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_signing_dialogue",) def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[LedgerApiMessage] = LedgerApiMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseLedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_signing_dialogue = None # type: Optional[SigningDialogue] @property def associated_signing_dialogue(self) -> "SigningDialogue": """Get the associated signing dialogue.""" if self._associated_signing_dialogue is None: raise ValueError("Associated signing dialogue not set!") return self._associated_signing_dialogue @associated_signing_dialogue.setter def associated_signing_dialogue( self, associated_signing_dialogue: "SigningDialogue" ) -> None: """Set the associated signing dialogue.""" enforce( self._associated_signing_dialogue is None, "Associated signing dialogue already set!", ) self._associated_signing_dialogue = associated_signing_dialogue class LedgerApiDialogues(Model, BaseLedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseLedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=LedgerApiDialogue, ) class OefSearchDialogue(BaseOefSearchDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_is_seller_search",) def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[OefSearchMessage] = OefSearchMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseOefSearchDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._is_seller_search = None # type: Optional[bool] @property def is_seller_search(self) -> bool: """Get if it is a seller search.""" if self._is_seller_search is None: raise ValueError("is_seller_search not set!") return self._is_seller_search @is_seller_search.setter def is_seller_search(self, is_seller_search: bool) -> None: """Set is_seller_search.""" enforce(self._is_seller_search is None, "is_seller_search already set!") self._is_seller_search = is_seller_search class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=OefSearchDialogue, ) class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("_associated_fipa_dialogue",) def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[SigningMessage] = SigningMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self._associated_fipa_dialogue: Optional[FipaDialogue] = None self._associated_cosm_trade_dialogue: Optional[CosmTradeDialogue] = None @property def associated_fipa_dialogue(self) -> FipaDialogue: """Get associated_fipa_dialogue.""" if self._associated_fipa_dialogue is None: raise ValueError("associated_fipa_dialogue not set!") return self._associated_fipa_dialogue @associated_fipa_dialogue.setter def associated_fipa_dialogue(self, associated_fipa_dialogue: FipaDialogue) -> None: """Set associated_fipa_dialogue.""" enforce( self._associated_fipa_dialogue is None, "associated_fipa_dialogue already set!", ) self._associated_fipa_dialogue = associated_fipa_dialogue @property def associated_cosm_trade_dialogue(self) -> Optional[CosmTradeDialogue]: """Get associated_cosm_trade_dialogue.""" return self._associated_cosm_trade_dialogue @associated_cosm_trade_dialogue.setter def associated_cosm_trade_dialogue( self, associated_cosm_trade_dialogue: CosmTradeDialogue ) -> None: """Set associated_cosm_trade_dialogue.""" enforce( self._associated_cosm_trade_dialogue is None, "associated_cosm_trade_dialogue already set!", ) self._associated_cosm_trade_dialogue = associated_cosm_trade_dialogue class SigningDialogues(Model, BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseSigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: packages/fetchai/skills/tac_negotiation/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a scaffold of a handler.""" from collections import OrderedDict from typing import Optional, Tuple, cast from aea_ledger_ethereum import EthereumApi from aea_ledger_fetchai import FetchAIApi from aea.configurations.base import PublicId from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import enforce from aea.helpers.transaction.base import RawMessage, RawTransaction from aea.protocols.base import Message from aea.protocols.dialogue.base import DialogueLabel from aea.skills.base import Handler from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.cosm_trade.message import CosmTradeMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_negotiation.behaviours import ( GoodsRegisterAndSearchBehaviour, ) from packages.fetchai.skills.tac_negotiation.dialogues import ( ContractApiDialogue, ContractApiDialogues, CosmTradeDialogue, CosmTradeDialogues, DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.tac_negotiation.strategy import Strategy from packages.fetchai.skills.tac_negotiation.transactions import Transactions LEDGER_API_ADDRESS = str(LEDGER_CONNECTION_PUBLIC_ID) class FipaNegotiationHandler(Handler): """This class implements the fipa negotiation handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Dispatch message to relevant handler and respond. :param message: the message """ fipa_msg = cast(FipaMessage, message) # recover dialogue fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return self.context.logger.info( "received {} from {} (as {}), message={}".format( fipa_msg.performative.value, fipa_msg.sender[-5:], fipa_dialogue.role, fipa_msg, ) ) if fipa_msg.performative == FipaMessage.Performative.CFP: self._on_cfp(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.PROPOSE: self._on_propose(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.DECLINE: self._on_decline(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.ACCEPT: self._on_accept(fipa_msg, fipa_dialogue) elif fipa_msg.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM: self._on_match_accept(fipa_msg, fipa_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. Respond to the sender with a default message containing the appropriate error information. :param fipa_msg: the message """ self.context.logger.info( "received invalid fipa message={}, unidentified dialogue.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=fipa_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": fipa_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _on_cfp(self, cfp: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle a CFP. :param cfp: the fipa message containing the CFP :param fipa_dialogue: the fipa_dialogue """ strategy = cast(Strategy, self.context.strategy) proposal = strategy.get_proposal_for_query( cfp.query, cast(FipaDialogue.Role, fipa_dialogue.role) ) if proposal is None: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=cfp, ) fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_CFP, fipa_dialogue.is_self_initiated ) else: transactions = cast(Transactions, self.context.transactions) fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.PROPOSE, target_message=cfp, proposal=proposal, ) fipa_dialogue.terms = strategy.terms_from_proposal( proposal, self.context.agent_address, cfp.sender, cast(FipaDialogue.Role, fipa_dialogue.role), ) transactions.add_pending_proposal( fipa_dialogue.dialogue_label, fipa_msg.message_id, fipa_dialogue.terms ) self.context.logger.info( "sending {} to {} (as {}), message={}".format( fipa_msg.performative, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg ) ) self.context.outbox.put_message(message=fipa_msg) def _on_propose(self, propose: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle a Propose. :param propose: the message containing the Propose :param fipa_dialogue: the fipa_dialogue """ strategy = cast(Strategy, self.context.strategy) fipa_dialogue.terms = strategy.terms_from_proposal( propose.proposal, self.context.agent_address, propose.sender, cast(FipaDialogue.Role, fipa_dialogue.role), ) transactions = cast(Transactions, self.context.transactions) if strategy.is_profitable_transaction( fipa_dialogue.terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ): fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.ACCEPT, target_message=propose, ) transactions.add_locked_tx( fipa_dialogue.terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ) transactions.add_pending_initial_acceptance( fipa_dialogue.dialogue_label, fipa_msg.message_id, fipa_dialogue.terms ) else: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=propose, ) fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) self.context.logger.info( "sending {} to {} (as {}), message={}".format( fipa_msg.performative, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg ) ) self.context.outbox.put_message(message=fipa_msg) def _on_decline(self, decline: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle a Decline. :param decline: the Decline message :param fipa_dialogue: the fipa_dialogue """ fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) target_message = fipa_dialogue.get_message_by_id(decline.target) if not target_message: raise ValueError("Can not find target message!") # pragma: nocover declined_performative = target_message.performative if declined_performative == FipaMessage.Performative.CFP: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_CFP, fipa_dialogue.is_self_initiated ) if declined_performative == FipaMessage.Performative.PROPOSE: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) transactions = cast(Transactions, self.context.transactions) terms = transactions.pop_pending_proposal( fipa_dialogue.dialogue_label, decline.target ) if declined_performative == FipaMessage.Performative.ACCEPT: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) transactions = cast(Transactions, self.context.transactions) terms = transactions.pop_pending_initial_acceptance( fipa_dialogue.dialogue_label, decline.target ) transactions.pop_locked_tx(terms) def _on_accept(self, accept: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle an Accept. :param accept: the Accept message :param fipa_dialogue: the fipa_dialogue """ transactions = cast(Transactions, self.context.transactions) terms = transactions.pop_pending_proposal( fipa_dialogue.dialogue_label, accept.target ) strategy = cast(Strategy, self.context.strategy) if strategy.is_profitable_transaction( terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ): transactions.add_locked_tx( terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ) if strategy.is_contract_tx: if strategy.ledger_id == EthereumApi.identifier: contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) kwargs = strategy.kwargs_from_terms( fipa_dialogue.terms, is_from_terms_sender=False ) kwargs.pop("tx_fee", None) ( contract_api_msg, contract_api_dialogue, ) = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, contract_address=strategy.contract_address, callable="get_hash_batch", kwargs=ContractApiMessage.Kwargs(kwargs), ) contract_api_dialogue = cast( ContractApiDialogue, contract_api_dialogue ) contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.logger.info( "requesting batch transaction hash, sending {} to {}, message={}".format( contract_api_msg.performative, strategy.contract_id, contract_api_msg, ) ) self.context.outbox.put_message(message=contract_api_msg) elif strategy.ledger_id == FetchAIApi.identifier: public_key = self.context.public_keys.get(strategy.ledger_id, None) if public_key is None: self.context.logger.info( f"Agent has no public key for {strategy.ledger_id}." ) return fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"public_key": public_key}, ) self.context.outbox.put_message(message=fipa_msg) self.context.logger.info( "sending {} to {} (as {}), message={}.".format( fipa_msg.performative.value, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg, ) ) else: enforce(False, f"Unidentified ledger id: {strategy.ledger_id}") else: signing_dialogues = cast( SigningDialogues, self.context.signing_dialogues ) raw_message = RawMessage( ledger_id=terms.ledger_id, body=terms.sender_hash.encode("utf-8") ) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_MESSAGE, terms=terms, raw_message=raw_message, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.logger.info( "requesting signature, sending {} to decision_maker, message={}".format( signing_msg.performative, signing_msg, ) ) self.context.decision_maker_message_queue.put(signing_msg) else: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=accept, ) dialogues = cast(FipaDialogues, self.context.fipa_dialogues) dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) self.context.logger.info( "sending {} to {} (as {}), message={}".format( fipa_msg.performative, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg, ) ) self.context.outbox.put_message(message=fipa_msg) def _on_match_accept( self, match_accept: FipaMessage, fipa_dialogue: FipaDialogue ) -> None: """ Handle a matching Accept. :param match_accept: the MatchAccept message :param fipa_dialogue: the fipa_dialogue """ strategy = cast(Strategy, self.context.strategy) if strategy.is_contract_tx: if strategy.ledger_id == FetchAIApi.identifier: counterparty_public_key = match_accept.info.get("public_key", None) if counterparty_public_key is None: self.context.logger.info( f"{match_accept.performative} did not contain counterparty public_key!" ) return sender_public_key = self.context.public_keys.get( strategy.ledger_id, None ) if sender_public_key is None: self.context.logger.info( f"Agent has no public key for {strategy.ledger_id}." ) return contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, contract_address=strategy.contract_address, callable="get_atomic_swap_batch_transaction", kwargs=ContractApiMessage.Kwargs( strategy.kwargs_from_terms( fipa_dialogue.terms, sender_public_key=sender_public_key, counterparty_public_key=counterparty_public_key, ) ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.logger.info( "requesting batch atomic swap transaction, sending {} to {}, message={}".format( contract_api_msg.performative, strategy.contract_id, contract_api_msg, ) ) self.context.outbox.put_message(message=contract_api_msg) elif strategy.ledger_id == EthereumApi.identifier: counterparty_signature = match_accept.info.get("signature") if counterparty_signature is None: self.context.logger.info( f"{match_accept.performative} did not contain counterparty signature!" ) return fipa_dialogue.counterparty_signature = counterparty_signature contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=strategy.contract_id, contract_address=strategy.contract_address, callable="get_atomic_swap_batch_transaction", kwargs=ContractApiMessage.Kwargs( strategy.kwargs_from_terms( fipa_dialogue.terms, signature=counterparty_signature, ) ), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.logger.info( "requesting batch atomic swap transaction, sending {} to {}, message={}".format( contract_api_msg.performative, strategy.contract_id, contract_api_msg, ) ) self.context.outbox.put_message(message=contract_api_msg) else: enforce(False, f"Unidentified ledger id: {strategy.ledger_id}") else: counterparty_signature = match_accept.info.get("signature") if counterparty_signature is None: self.context.logger.info( f"{match_accept.performative} did not contain counterparty signature!" ) return fipa_dialogue.counterparty_signature = counterparty_signature transactions = cast(Transactions, self.context.transactions) terms = transactions.pop_pending_initial_acceptance( fipa_dialogue.dialogue_label, match_accept.target ) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) raw_message = RawMessage( ledger_id=terms.ledger_id, body=terms.sender_hash.encode("utf-8") ) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_MESSAGE, terms=terms, raw_message=raw_message, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.logger.info( "requesting signature, sending {} to decision_maker, message={}".format( signing_msg.performative, signing_msg, ) ) self.context.decision_maker_message_queue.put(signing_msg) class CosmTradeHandler(Handler): """This class implements the cosm_trade negotiation handler.""" SUPPORTED_PROTOCOL = CosmTradeMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Dispatch message to relevant handler and respond. :param message: the message """ strategy = cast(Strategy, self.context.strategy) enforce( strategy.is_contract_tx, "A cosm_trade dialogue is only used in the contract-based TAC", ) enforce( strategy.ledger_id == FetchAIApi.identifier, "A cosm_trade dialogue is only used in the fetchai-based TAC", ) cosm_trade_msg = cast(CosmTradeMessage, message) # recover dialogue cosm_trade_dialogues = cast( CosmTradeDialogues, self.context.cosm_trade_dialogues ) cosm_trade_dialogue = cast( CosmTradeDialogue, cosm_trade_dialogues.update(cosm_trade_msg) ) if cosm_trade_dialogue is None: self._handle_unidentified_dialogue(cosm_trade_msg) return self.context.logger.info( "received {} from {} (as {}), message={}".format( cosm_trade_msg.performative.value, cosm_trade_msg.sender[-5:], cosm_trade_dialogue.role, cosm_trade_msg, ) ) if ( cosm_trade_msg.performative == CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION ): self._on_signed_tx(cosm_trade_msg, cosm_trade_dialogue) elif cosm_trade_msg.performative == CosmTradeMessage.Performative.ERROR: self._on_error(cosm_trade_msg, cosm_trade_dialogue) elif cosm_trade_msg.performative == CosmTradeMessage.Performative.END: self._on_end(cosm_trade_msg, cosm_trade_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, cosm_trade_msg: CosmTradeMessage) -> None: """ Handle an unidentified dialogue. Respond to the sender with a default message containing the appropriate error information. :param cosm_trade_msg: the message """ self.context.logger.info( "received invalid cosm_trade message={}, unidentified dialogue.".format( cosm_trade_msg ) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=cosm_trade_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"cosm_trade_message": cosm_trade_msg.encode()}, ) self.context.outbox.put_message(message=default_msg) def _on_signed_tx( self, inform_signed_tx: CosmTradeMessage, cosm_trade_dialogue: CosmTradeDialogue ) -> None: """ Handle a Propose. :param inform_signed_tx: the message containing the signed transaction :param cosm_trade_dialogue: the cosm_trade_dialogue """ tx_signed_by_the_other_party = inform_signed_tx.signed_transaction self.context.logger.info( "received inform_signed_tx with signed_tx={}".format( tx_signed_by_the_other_party ) ) fipa_dialogue_ref = inform_signed_tx.fipa_dialogue_id if fipa_dialogue_ref is None: self.context.logger.info( "inform_signed_tx must contain fipa dialogue reference." ) return fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) dialogue_label = DialogueLabel( cast(Tuple[str, str], fipa_dialogue_ref), inform_signed_tx.sender, inform_signed_tx.sender, ) fipa_dialogue = cast( FipaDialogue, fipa_dialogues.get_dialogue_from_label(dialogue_label) ) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=RawTransaction( ledger_id=tx_signed_by_the_other_party.ledger_id, body=tx_signed_by_the_other_party.body, ), terms=fipa_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_fipa_dialogue = fipa_dialogue signing_dialogue.associated_cosm_trade_dialogue = cosm_trade_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _on_error( self, error_msg: CosmTradeMessage, cosm_trade_dialogue: CosmTradeDialogue ) -> None: """ Handle an Error. :param error_msg: the Error message :param cosm_trade_dialogue: the cosm_trade_dialogue """ self.context.logger.info( "received cosm_trade_api error message={} in dialogue={}.".format( error_msg, cosm_trade_dialogue ) ) cosm_trade_dialogues = cast( CosmTradeDialogues, self.context.cosm_trade_dialogues ) cosm_trade_dialogues.dialogue_stats.add_dialogue_endstate( CosmTradeDialogue.EndState.FAILED, cosm_trade_dialogue.is_self_initiated ) def _on_end( self, end_msg: CosmTradeMessage, cosm_trade_dialogue: CosmTradeDialogue ) -> None: """ Handle an End. :param end_msg: the Accept message :param cosm_trade_dialogue: the fipa_dialogue """ self.context.logger.info( "received cosm_trade_api end message={} in dialogue={}.".format( end_msg, cosm_trade_dialogue ) ) cosm_trade_dialogues = cast( CosmTradeDialogues, self.context.cosm_trade_dialogues ) cosm_trade_dialogues.dialogue_stats.add_dialogue_endstate( CosmTradeDialogue.EndState.SUCCESSFUL, cosm_trade_dialogue.is_self_initiated ) class SigningHandler(Handler): """This class implements the transaction handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Dispatch message to relevant handler and respond. :param message: the message """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return self.context.logger.info( "received {} from decision_maker, message={}".format( signing_msg.performative.value, signing_msg, ) ) # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_MESSAGE: self._handle_signed_message(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param signing_msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_message( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ fipa_dialogue = signing_dialogue.associated_fipa_dialogue last_fipa_message = cast(FipaMessage, fipa_dialogue.last_incoming_message) enforce(last_fipa_message is not None, "last message not recovered.") if last_fipa_message.performative == FipaMessage.Performative.ACCEPT: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, target_message=last_fipa_message, info={"signature": signing_msg.signed_message.body}, ) self.context.outbox.put_message(message=fipa_msg) self.context.logger.info( "sending {} to {} (as {}), message={}.".format( fipa_msg.performative.value, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg, ) ) elif ( last_fipa_message.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM ): counterparty_signature = fipa_dialogue.counterparty_signature tx_id = fipa_dialogue.terms.sender_hash if "transactions" not in self.context.shared_state.keys(): self.context.shared_state["transactions"] = OrderedDict() tx = { "terms": fipa_dialogue.terms, "sender_signature": signing_msg.signed_message.body, "counterparty_signature": counterparty_signature, } self.context.shared_state["transactions"][tx_id] = tx self.context.logger.info(f"sending transaction to controller, tx={tx}.") else: enforce( False, "last message should be of performative accept or match accept." ) def _handle_signed_transaction( # pylint: disable=unused-argument self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ strategy = cast(Strategy, self.context.strategy) if not strategy.is_contract_tx: self.context.logger.warning( "signed transaction handler only for contract case." ) return cosm_trade_dialogue = signing_dialogue.associated_cosm_trade_dialogue if cosm_trade_dialogue is not None: # tx is now signed by both parties, submit to ledger ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, signed_transaction=signing_msg.signed_transaction, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue self.context.logger.info( "sending {} to ledger {}, message={}".format( ledger_api_msg.performative, strategy.ledger_id, ledger_api_msg, ) ) self.context.outbox.put_message(message=ledger_api_msg) return fipa_dialogue = signing_dialogue.associated_fipa_dialogue last_fipa_message = cast(FipaMessage, fipa_dialogue.last_incoming_message) enforce(last_fipa_message is not None, "last message not recovered.") if last_fipa_message.performative == FipaMessage.Performative.ACCEPT: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, target_message=last_fipa_message, info={"tx_signature": signing_msg.signed_transaction}, ) self.context.logger.info( "sending {} to {} (as {}), message={}.".format( fipa_msg.performative.value, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg, ) ) self.context.outbox.put_message(message=fipa_msg) elif ( last_fipa_message.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM ): if strategy.ledger_id == EthereumApi.identifier: ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=LEDGER_API_ADDRESS, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, signed_transaction=signing_msg.signed_transaction, ) ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue self.context.logger.info( "sending {} to ledger {}, message={}".format( ledger_api_msg.performative, strategy.ledger_id, ledger_api_msg, ) ) self.context.outbox.put_message(message=ledger_api_msg) elif strategy.ledger_id == FetchAIApi.identifier: # send the constructed and signed tx to the other party to sign cosm_trade_dialogues = cast( CosmTradeDialogues, self.context.cosm_trade_dialogues ) cosm_trade_msg, _ = cosm_trade_dialogues.create( counterparty=signing_dialogue.associated_fipa_dialogue.dialogue_label.dialogue_opponent_addr, performative=CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, signed_transaction=signing_msg.signed_transaction, fipa_dialogue_id=signing_dialogue.associated_fipa_dialogue.dialogue_label.dialogue_reference, ) self.context.logger.info( "sending {} to {}, message={}.".format( cosm_trade_msg.performative.value, cosm_trade_msg.to[-5:], cosm_trade_msg, ) ) self.context.outbox.put_message(message=cosm_trade_msg) else: enforce(False, f"Unidentified ledger id: {strategy.ledger_id}") else: enforce( False, "last message should be of performative accept or match accept." ) def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) class LedgerApiHandler(Handler): """Implement the ledger api handler.""" SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ ledger_api_msg = cast(LedgerApiMessage, message) # recover dialogue ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) ) if ledger_api_dialogue is None: self._handle_unidentified_dialogue(ledger_api_msg) return # handle message if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: self._handle_balance(ledger_api_msg) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_DIGEST ): self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) elif ( ledger_api_msg.performative is LedgerApiMessage.Performative.TRANSACTION_RECEIPT ): self._handle_transaction_receipt(ledger_api_msg) elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: self._handle_error(ledger_api_msg, ledger_api_dialogue) else: self._handle_invalid(ledger_api_msg, ledger_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle an unidentified dialogue. :param ledger_api_msg: the message """ self.context.logger.info( "received invalid ledger_api message={}, unidentified dialogue.".format( ledger_api_msg ) ) def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of balance performative. :param ledger_api_msg: the ledger api message """ self.context.logger.info( "starting balance on {} ledger={}.".format( ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "transaction was successfully submitted. Transaction digest={}".format( ledger_api_msg.transaction_digest ) ) msg = ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=ledger_api_msg, transaction_digest=ledger_api_msg.transaction_digest, ) self.context.outbox.put_message(message=msg) self.context.logger.info("requesting transaction receipt.") def _handle_transaction_receipt(self, ledger_api_msg: LedgerApiMessage) -> None: """ Handle a message of transaction_receipt performative. :param ledger_api_msg: the ledger api message """ is_transaction_successful = LedgerApis.is_transaction_settled( ledger_api_msg.transaction_receipt.ledger_id, ledger_api_msg.transaction_receipt.receipt, ) if is_transaction_successful: self.context.logger.info( "transaction was successfully settled. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) else: self.context.logger.error( "transaction failed. Transaction receipt={}".format( ledger_api_msg.transaction_receipt ) ) def _handle_error( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of error performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received ledger_api error message={} in dialogue={}.".format( ledger_api_msg, ledger_api_dialogue ) ) def _handle_invalid( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue ) -> None: """ Handle a message of invalid performative. :param ledger_api_msg: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle ledger_api message of performative={} in dialogue={}.".format( ledger_api_msg.performative, ledger_api_dialogue, ) ) class OefSearchHandler(Handler): """This class implements the oef search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SEARCH_RESULT: self._on_search_result(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.SUCCESS: self._handle_success(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._on_oef_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.warning( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _handle_success( self, oef_search_success_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an oef search message. :param oef_search_success_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.info( "received oef_search success message={} in dialogue={}.".format( oef_search_success_msg, oef_search_dialogue ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_success_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): description = target_message.service_description data_model_name = description.data_model.name registration_behaviour = cast( GoodsRegisterAndSearchBehaviour, self.context.behaviours.tac_negotiation, ) if "location_agent" in data_model_name: registration_behaviour.register_service() elif "set_service_key" in data_model_name: registration_behaviour.register_genus() elif ( "personality_agent" in data_model_name and description.values["piece"] == "genus" ): registration_behaviour.register_classification() elif ( "personality_agent" in data_model_name and description.values["piece"] == "classification" ): registration_behaviour.is_registered = True self.context.logger.info( "the agent, with its genus and classification, and its service are successfully registered on the SOEF." ) else: self.context.logger.warning( f"received soef SUCCESS message as a reply to the following unexpected message: {target_message}" ) def _on_oef_error( self, oef_search_error_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue, ) -> None: """ Handle an OEF error message. :param oef_search_error_msg: the oef search msg :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "received OEF Search error: dialogue_reference={}, oef_error_operation={}".format( oef_search_dialogue.dialogue_label.dialogue_reference, oef_search_error_msg.oef_error_operation, ) ) target_message = cast( OefSearchMessage, oef_search_dialogue.get_message_by_id(oef_search_error_msg.target), ) if ( target_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE ): registration_behaviour = cast( GoodsRegisterAndSearchBehaviour, self.context.behaviours.tac_negotiation, ) registration_behaviour.failed_registration_msg = target_message def _on_search_result( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Split the search results from the OEF search node. :param oef_search_msg: the search result :param oef_search_dialogue: the dialogue """ agents = list(oef_search_msg.agents) search_id = oef_search_msg.dialogue_reference[0] if self.context.agent_address in agents: agents.remove(self.context.agent_address) agents_less_self = tuple(agents) self._handle_search( agents_less_self, search_id, is_searching_for_sellers=oef_search_dialogue.is_seller_search, ) def _handle_search( self, agents: Tuple[str, ...], search_id: str, is_searching_for_sellers: bool ) -> None: """ Handle the search response. :param agents: the agents returned by the search :param search_id: the search id :param is_searching_for_sellers: whether the agent is searching for sellers """ searched_for = "sellers" if is_searching_for_sellers else "buyers" if len(agents) > 0: self.context.logger.info( "found potential {} agents={} on search_id={}.".format( searched_for, list(map(lambda x: x[-5:], agents)), search_id, ) ) strategy = cast(Strategy, self.context.strategy) fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) query = strategy.get_own_services_query(is_searching_for_sellers) for opponent_addr in agents: self.context.logger.info( "sending CFP to agent={}".format(opponent_addr[-5:]) ) fipa_msg, _ = fipa_dialogues.create( counterparty=opponent_addr, performative=FipaMessage.Performative.CFP, query=query, ) self.context.outbox.put_message(message=fipa_msg) else: self.context.logger.info( "found no {} agents on search_id={}, continue searching.".format( searched_for, search_id ) ) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) class ContractApiHandler(Handler): """Implement the contract api handler.""" SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ contract_api_msg = cast(ContractApiMessage, message) # recover dialogue contract_api_dialogues = cast( ContractApiDialogues, self.context.contract_api_dialogues ) contract_api_dialogue = cast( Optional[ContractApiDialogue], contract_api_dialogues.update(contract_api_msg), ) if contract_api_dialogue is None: self._handle_unidentified_dialogue(contract_api_msg) return # handle message if contract_api_msg.performative is ContractApiMessage.Performative.RAW_MESSAGE: self._handle_raw_message(contract_api_msg, contract_api_dialogue) elif ( contract_api_msg.performative is ContractApiMessage.Performative.RAW_TRANSACTION ): self._handle_raw_transaction(contract_api_msg, contract_api_dialogue) elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: self._handle_error(contract_api_msg, contract_api_dialogue) else: self._handle_invalid(contract_api_msg, contract_api_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue( self, contract_api_msg: ContractApiMessage ) -> None: """ Handle an unidentified dialogue. :param contract_api_msg: the message """ self.context.logger.info( "received invalid contract_api message={}, unidentified dialogue.".format( contract_api_msg ) ) def _handle_raw_message( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_message performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw message={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_MESSAGE, raw_message=RawMessage( contract_api_msg.raw_message.ledger_id, contract_api_msg.raw_message.body, is_deprecated_mode=True, ), terms=contract_api_dialogue.associated_fipa_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_fipa_dialogue = ( contract_api_dialogue.associated_fipa_dialogue ) self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the message to the decision maker. Waiting for confirmation ..." ) def _handle_raw_transaction( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of raw_transaction performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info("received raw transaction={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg, signing_dialogue = signing_dialogues.create( counterparty=self.context.decision_maker_address, performative=SigningMessage.Performative.SIGN_TRANSACTION, raw_transaction=contract_api_msg.raw_transaction, terms=contract_api_dialogue.associated_fipa_dialogue.terms, ) signing_dialogue = cast(SigningDialogue, signing_dialogue) signing_dialogue.associated_fipa_dialogue = ( contract_api_dialogue.associated_fipa_dialogue ) self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of error performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( "received contract_api error message={} in dialogue={}.".format( contract_api_msg, contract_api_dialogue ) ) def _handle_invalid( self, contract_api_msg: ContractApiMessage, contract_api_dialogue: ContractApiDialogue, ) -> None: """ Handle a message of invalid performative. :param contract_api_msg: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( "cannot handle contract_api message of performative={} in dialogue={}.".format( contract_api_msg.performative, contract_api_dialogue, ) ) ================================================ FILE: packages/fetchai/skills/tac_negotiation/helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This class contains the helpers for FIPA negotiation.""" import copy from typing import Dict, List, Union, cast from aea.helpers.search.models import ( Attribute, Constraint, ConstraintExpr, ConstraintType, DataModel, Description, Or, Query, ) SUPPLY_DATAMODEL_NAME = "supply" DEMAND_DATAMODEL_NAME = "demand" def _build_goods_datamodel(good_ids: List[str], is_supply: bool) -> DataModel: """ Build a data model for supply and demand of goods (i.e. for offered or requested goods). :param good_ids: a list of ids (i.e. identifiers) of the relevant goods. :param is_supply: Boolean indicating whether it is a supply or demand data model :return: the data model. """ good_quantities_attributes = [ Attribute(good_id, int, True, "A good on offer.") for good_id in good_ids ] ledger_id_attribute = Attribute( "ledger_id", str, True, "The ledger for transacting." ) currency_attribute = Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods." ) price_attribute = Attribute( "price", int, False, "The price of the goods in the currency." ) fee_attribute = Attribute( "fee", int, False, "The transaction fee payable by the buyer in the currency.", ) nonce_attribute = Attribute( "nonce", str, False, "The nonce to distinguish identical descriptions." ) description = SUPPLY_DATAMODEL_NAME if is_supply else DEMAND_DATAMODEL_NAME attributes = good_quantities_attributes + [ ledger_id_attribute, currency_attribute, price_attribute, fee_attribute, nonce_attribute, ] data_model = DataModel(description, attributes) return data_model def build_goods_description( quantities_by_good_id: Dict[str, int], currency_id: str, ledger_id: str, is_supply: bool, ) -> Description: """ Get the service description (good quantities supplied or demanded and their price). :param quantities_by_good_id: a dictionary mapping the ids of the goods to the quantities. :param currency_id: the currency used for pricing and transacting. :param ledger_id: the ledger used for transacting. :param is_supply: True if the description is indicating supply, False if it's indicating demand. :return: the description to advertise on the Service Directory. """ data_model = _build_goods_datamodel( good_ids=list(quantities_by_good_id.keys()), is_supply=is_supply ) values = cast(Dict[str, Union[int, str]], copy.copy(quantities_by_good_id)) values.update({"currency_id": currency_id}) values.update({"ledger_id": ledger_id}) desc = Description(values, data_model=data_model) return desc def build_goods_query( good_ids: List[str], currency_id: str, ledger_id: str, is_searching_for_sellers: bool, ) -> Query: """ Build buyer or seller search query. Specifically, build the search query - to look for sellers if the agent is a buyer, or - to look for buyers if the agent is a seller. In particular, if the agent is a buyer and the demanded good ids are {'tac_good_0', 'tac_good_2', 'tac_good_3'}, the resulting constraint expression is: tac_good_0 >= 1 OR tac_good_2 >= 1 OR tac_good_3 >= 1 That is, the OEF will return all the sellers that have at least one of the good in the query (assuming that the sellers are registered with the data model specified). :param good_ids: the list of good ids to put in the query :param currency_id: the currency used for pricing and transacting. :param ledger_id: the ledger used for transacting. :param is_searching_for_sellers: Boolean indicating whether the query is for sellers (supply) or buyers (demand). :return: the query """ data_model = _build_goods_datamodel( good_ids=good_ids, is_supply=is_searching_for_sellers ) constraints = [Constraint(good_id, ConstraintType(">=", 1)) for good_id in good_ids] constraints.append(Constraint("currency_id", ConstraintType("==", currency_id))) constraints.append(Constraint("ledger_id", ConstraintType("==", ledger_id))) constraint_expr = cast(List[ConstraintExpr], constraints) if len(good_ids) > 1: constraint_expr = [Or(constraint_expr)] query = Query(constraint_expr, model=data_model) return query ================================================ FILE: packages/fetchai/skills/tac_negotiation/skill.yaml ================================================ name: tac_negotiation author: fetchai version: 0.29.6 type: skill description: The tac negotiation skill implements the logic for an AEA to do fipa negotiation in the TAC. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmNWeRkM9RzqkyPu5vQpdgXw5Qw8DpkX2ghNaAD6cNQnUo __init__.py: Qmecsd4pHWrSSo4vdCLjzyersvLPDGcvQtudp2MdnbydA1 behaviours.py: QmP4S2fTjtYXYc4YRAgwafE3uXAAERbPienc4yBuAJojDA dialogues.py: QmT3koAkBQ8ZBRMDrkKQV9K7s71mYxPTVDCDL7b9UcmsRe handlers.py: QmTgPAgrMByEgbQLdR9a3DHfPGvf4LobtnppxVv3HPoTGA helpers.py: QmUdAigxsjxG7qH34AYGTGySj7UXMm6AbruFGibhQXk9U7 strategy.py: QmchRFawnZrWso9gqiefSfJ1Z3wz7Z6dWeQLgNxnZ7h8Tx transactions.py: QmbxD3g2Bc52GDhcy6njkbkA77C5Y1RxusNnRZ5Laounov fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/contract_api:1.1.7 - fetchai/cosm_trade:0.2.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/tac_participation:0.25.6 behaviours: clean_up: args: tick_interval: 5.0 class_name: TransactionCleanUpBehaviour tac_negotiation: args: max_soef_registration_retries: 5 search_interval: 5.0 class_name: GoodsRegisterAndSearchBehaviour handlers: contract_api: args: {} class_name: ContractApiHandler cosm_trade: args: {} class_name: CosmTradeHandler fipa: args: {} class_name: FipaNegotiationHandler ledger_api: args: {} class_name: LedgerApiHandler oef: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: contract_api_dialogues: args: {} class_name: ContractApiDialogues cosm_trade_dialogues: args: {} class_name: CosmTradeDialogues default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: classification: piece: classification value: tac.participant is_contract_tx: false location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data register_as: both search_for: both search_radius: 5.0 service_key: tac_service tx_fee_proposal: 1500000000000000 class_name: Strategy transactions: args: pending_transaction_timeout: 30 class_name: Transactions dependencies: aea-ledger-ethereum: version: <2.0.0,>=1.0.0 aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false ================================================ FILE: packages/fetchai/skills/tac_negotiation/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the abstract class defining an agent's strategy for the TAC.""" import copy import random from enum import Enum from typing import Any, Dict, List, Optional, Tuple, cast from aea.common import Address from aea.decision_maker.gop import OwnershipState, Preferences from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.helpers.transaction.base import Terms from aea.skills.base import Model from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as CONTRACT_ID from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue from packages.fetchai.skills.tac_negotiation.helpers import ( build_goods_description, build_goods_query, ) from packages.fetchai.skills.tac_negotiation.transactions import Transactions ROUNDING_ADJUSTMENT = 1 DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SERVICE_KEY = "tac_service" DEFAULT_SEARCH_QUERY = { "search_key": "tac_service", "search_value": "generic_service", "constraint_type": "==", } DEFAULT_PERSONALITY_DATA = {"piece": "genus", "value": "data"} DEFAULT_CLASSIFICATION = {"piece": "classification", "value": "tac.participant"} DEFAULT_SEARCH_RADIUS = 5.0 DEFAULT_TX_FEE_PROPOSAL = 1500000000000000 class Strategy(Model): """This class defines an abstract strategy for the agent.""" class RegisterAs(Enum): """This class defines the service registration options.""" SELLER = "seller" BUYER = "buyer" BOTH = "both" class SearchFor(Enum): """This class defines the service search options.""" SELLERS = "sellers" BUYERS = "buyers" BOTH = "both" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ self._register_as = Strategy.RegisterAs(kwargs.pop("register_as", "both")) self._search_for = Strategy.SearchFor(kwargs.pop("search_for", "both")) self._is_contract_tx = kwargs.pop("is_contract_tx", False) ledger_id = kwargs.pop("ledger_id", None) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( latitude=location["latitude"], longitude=location["longitude"] ) } self._set_personality_data = kwargs.pop( "personality_data", DEFAULT_PERSONALITY_DATA ) enforce( len(self._set_personality_data) == 2 and "piece" in self._set_personality_data and "value" in self._set_personality_data, "personality_data must contain keys `key` and `value`", ) self._set_classification = kwargs.pop("classification", DEFAULT_CLASSIFICATION) enforce( len(self._set_classification) == 2 and "piece" in self._set_classification and "value" in self._set_classification, "classification must contain keys `key` and `value`", ) self.service_key = kwargs.pop("service_key", DEFAULT_SERVICE_KEY) self.tac_version_id: Optional[str] = None self._remove_service_data = {"key": self.service_key} self._simple_service_data = {self.service_key: self._register_as.value} self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self._tx_fee_proposal = kwargs.pop("tx_fee_proposal", DEFAULT_TX_FEE_PROPOSAL) self._contract_id = str(CONTRACT_ID) super().__init__(**kwargs) self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) @property def registering_as(self) -> str: """Get what the agent is registering as.""" return ( self._register_as.value if self._register_as != self.RegisterAs.BOTH else "buyer and seller" ) @property def searching_for(self) -> str: """Get what the agent is searching for.""" return ( self._search_for.value if self._search_for != self.SearchFor.BOTH else "buyer and seller" ) @property def searching_for_types(self) -> List[Tuple[bool, str]]: """Get the types the agent is searching for.""" result = [] # type: List[Tuple[bool, str]] if self._search_for in [self.SearchFor.SELLERS, self.SearchFor.BOTH]: result.append((True, "sellers")) if self._search_for in [self.SearchFor.BUYERS, self.SearchFor.BOTH]: result.append((False, "buyers")) return result @property def is_contract_tx(self) -> bool: """Check if tx are made against the ERC1155 or not.""" return self._is_contract_tx @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def contract_id(self) -> str: """Get the contract id.""" return self._contract_id @property def contract_address(self) -> str: """Get the contract address.""" contract_address = self.context.shared_state.get( "erc1155_contract_address", None ) enforce(contract_address is not None, "ERC1155Contract address not set!") return contract_address def get_location_description(self) -> Description: """ Get the location description. :return: a description of the agent's location """ description = Description( self._agent_location, data_model=AGENT_LOCATION_MODEL, ) return description def get_register_service_description(self) -> Description: """ Get the register service description. :return: a description of the offered services """ service_data = { "key": f"{self.service_key}_{self.tac_version_id}", "value": self._register_as.value, } description = Description( service_data, data_model=AGENT_SET_SERVICE_MODEL, ) return description def get_register_personality_description(self) -> Description: """ Get the register personality description. :return: a description of the personality """ description = Description( self._set_personality_data, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_register_classification_description(self) -> Description: """ Get the register classification description. :return: a description of the classification """ description = Description( self._set_classification, data_model=AGENT_PERSONALITY_MODEL, ) return description def get_unregister_service_description(self) -> Description: """ Get the unregister service description. :return: a description of the to be removed service """ description = Description( self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description def get_location_and_service_query(self) -> Query: """ Get the location and service query of the agent. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType( "distance", (self._agent_location["location"], self._radius) ), ) search_query = { "search_key": f"{self.service_key}_{self.tac_version_id}", "search_value": self._search_for.value, "constraint_type": "==", } service_key_filter = Constraint( search_query["search_key"], ConstraintType( search_query["constraint_type"], search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query def get_own_service_description(self, is_supply: bool) -> Description: """ Get the description of the supplied goods (as a seller), or the demanded goods (as a buyer). :param is_supply: Boolean indicating whether it is supply or demand. :return: the description (to advertise on the Service Directory). """ transactions = cast(Transactions, self.context.transactions) ownership_state_after_locks = transactions.ownership_state_after_locks( is_seller=is_supply ) quantities_by_good_id = ( self._supplied_goods(ownership_state_after_locks.quantities_by_good_id) if is_supply else self._demanded_goods(ownership_state_after_locks.quantities_by_good_id) ) currency_id = next( iter(ownership_state_after_locks.amount_by_currency_id.keys()) ) desc = build_goods_description( quantities_by_good_id=quantities_by_good_id, currency_id=currency_id, ledger_id=self.ledger_id, is_supply=is_supply, ) return desc @staticmethod def _supplied_goods(good_holdings: Dict[str, int]) -> Dict[str, int]: """ Generate a dictionary of quantities which are supplied. :param good_holdings: a dictionary of current good holdings :return: a dictionary of quantities supplied """ supply = {} # type: Dict[str, int] for good_id, quantity in good_holdings.items(): supply[good_id] = quantity - 1 if quantity > 1 else 0 return supply @staticmethod def _demanded_goods(good_holdings: Dict[str, int]) -> Dict[str, int]: """ Generate a dictionary of quantities which are demanded. :param good_holdings: a dictionary of current good holdings :return: a dictionary of quantities supplied """ demand = {} # type: Dict[str, int] for good_id in good_holdings.keys(): demand[good_id] = 1 return demand def get_own_services_query( self, is_searching_for_sellers: bool, ) -> Query: """ Build a query. In particular, build the query to look for agents - which supply the agent's demanded goods (i.e. sellers), or - which demand the agent's supplied goods (i.e. buyers). :param is_searching_for_sellers: Boolean indicating whether the search is for sellers or buyers. :return: the Query, or None. """ transactions = cast(Transactions, self.context.transactions) ownership_state_after_locks = transactions.ownership_state_after_locks( is_seller=not is_searching_for_sellers ) good_id_to_quantities = ( self._demanded_goods(ownership_state_after_locks.quantities_by_good_id) if is_searching_for_sellers else self._supplied_goods(ownership_state_after_locks.quantities_by_good_id) ) currency_id = next( iter(ownership_state_after_locks.amount_by_currency_id.keys()) ) query = build_goods_query( good_ids=list(good_id_to_quantities.keys()), currency_id=currency_id, ledger_id=self.ledger_id, is_searching_for_sellers=is_searching_for_sellers, ) return query def _get_proposal_for_query( self, query: Query, is_seller: bool ) -> Optional[Description]: """ Generate proposal (in the form of a description) which matches the query. :param query: the query for which to build the proposal :param is_seller: whether the agent making the proposal is a seller or not :return: a description """ candidate_proposals = self._generate_candidate_proposals(is_seller) proposals = [] for proposal in candidate_proposals: if not query.check(proposal): continue # pragma: nocover proposals.append(proposal) if not proposals: return None # pragma: nocover return random.choice(proposals) # nosec def get_proposal_for_query( self, query: Query, role: FipaDialogue.Role ) -> Optional[Description]: """ Generate proposal (in the form of a description) which matches the query. :param query: the query for which to build the proposal :param role: the role of the agent making the proposal (seller or buyer) :return: a description """ is_seller = role == FipaDialogue.Role.SELLER own_service_description = self.get_own_service_description( is_supply=is_seller, ) if not query.check(own_service_description): # pragma: nocover self.context.logger.debug("current holdings do not satisfy CFP query.") return None proposal_description = self._get_proposal_for_query(query, is_seller=is_seller) if proposal_description is None: self.context.logger.debug( # pragma: nocover "current strategy does not generate proposal that satisfies CFP query." ) return proposal_description def _generate_candidate_proposals(self, is_seller: bool) -> List[Description]: """ Generate proposals from the agent in the role of seller/buyer. :param is_seller: the bool indicating whether the agent is a seller. :return: a list of proposals in Description form """ transactions = cast(Transactions, self.context.transactions) ownership_state_after_locks = transactions.ownership_state_after_locks( is_seller=is_seller ) good_id_to_quantities = ( self._supplied_goods(ownership_state_after_locks.quantities_by_good_id) if is_seller else self._demanded_goods(ownership_state_after_locks.quantities_by_good_id) ) nil_proposal_dict = { good_id: 0 for good_id in good_id_to_quantities.keys() } # type: Dict[str, int] proposals = [] fee_by_currency_id = self.context.shared_state.get( "tx_fee", {"FET": self._tx_fee_proposal} ) buyer_tx_fee = next(iter(fee_by_currency_id.values())) ownership_state = cast( OwnershipState, self.context.decision_maker_handler_context.ownership_state ) currency_id = list(ownership_state.amount_by_currency_id.keys())[0] preferences = cast( Preferences, self.context.decision_maker_handler_context.preferences ) for good_id, quantity in good_id_to_quantities.items(): if is_seller and quantity == 0: continue proposal_dict = copy.copy(nil_proposal_dict) proposal_dict[good_id] = 1 proposal = build_goods_description( quantities_by_good_id=proposal_dict, currency_id=currency_id, ledger_id=self.ledger_id, is_supply=is_seller, ) if is_seller: delta_quantities_by_good_id = { good_id: quantity * -1 for good_id, quantity in proposal_dict.items() } # type: Dict[str, int] else: delta_quantities_by_good_id = proposal_dict marginal_utility_from_delta_good_holdings = preferences.marginal_utility( ownership_state=ownership_state_after_locks, delta_quantities_by_good_id=delta_quantities_by_good_id, ) switch = -1 if is_seller else 1 breakeven_price_rounded = ( round(marginal_utility_from_delta_good_holdings) * switch ) if is_seller: proposal.values["price"] = breakeven_price_rounded + ROUNDING_ADJUSTMENT else: proposal.values["price"] = ( breakeven_price_rounded - buyer_tx_fee - ROUNDING_ADJUSTMENT ) proposal.values["fee"] = buyer_tx_fee if not proposal.values["price"] > 0: continue nonce = transactions.get_next_nonce() proposal.values["nonce"] = nonce proposals.append(proposal) return proposals def is_profitable_transaction(self, terms: Terms, role: FipaDialogue.Role) -> bool: """ Check if a transaction is profitable. Is it a profitable transaction? - apply all the locks for role. - check if the transaction is consistent with the locks (enough money/holdings) - check that we gain score. :param terms: the terms :param role: the role of the agent (seller or buyer) :return: True if the transaction is good (as stated above), False otherwise. """ is_seller = role == FipaDialogue.Role.SELLER transactions = cast(Transactions, self.context.transactions) ownership_state_after_locks = transactions.ownership_state_after_locks( is_seller ) if not ownership_state_after_locks.is_affordable_transaction(terms): return False preferences = cast( Preferences, self.context.decision_maker_handler_context.preferences ) proposal_delta_score = preferences.utility_diff_from_transaction( ownership_state_after_locks, terms ) return proposal_delta_score >= 0 @staticmethod def terms_from_proposal( proposal: Description, sender: Address, counterparty: Address, role: FipaDialogue.Role, ) -> Terms: """ Get the terms from a proposal. :param proposal: the proposal :param sender: the sender of the proposal :param counterparty: the receiver of the proposal :param role: the role :return: the terms """ is_seller = role == FipaDialogue.Role.SELLER goods_component = copy.copy(proposal.values) [ # pylint: disable=expression-not-assigned goods_component.pop(key) for key in ["fee", "price", "currency_id", "nonce", "ledger_id"] ] # switch signs based on whether seller or buyer role amount = proposal.values["price"] if is_seller else -proposal.values["price"] fee = proposal.values["fee"] if is_seller: for good_id in goods_component.keys(): goods_component[good_id] = goods_component[good_id] * (-1) amount_by_currency_id = {proposal.values["currency_id"]: amount} fee_by_currency_id = {proposal.values["currency_id"]: fee} nonce = proposal.values["nonce"] ledger_id = proposal.values["ledger_id"] terms = Terms( ledger_id=ledger_id, sender_address=sender, counterparty_address=counterparty, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=goods_component, is_sender_payable_tx_fee=not is_seller, nonce=nonce, fee_by_currency_id=fee_by_currency_id, ) return terms @staticmethod def kwargs_from_terms( terms: Terms, signature: Optional[str] = None, sender_public_key: Optional[str] = None, counterparty_public_key: Optional[str] = None, is_from_terms_sender: bool = True, ) -> Dict[str, Any]: """ Get the contract api message kwargs from the terms. :param terms: the terms :param signature: the signature (for ethereum or non-contract-based case) :param sender_public_key: the sender's public key (for fetchai ledger case) :param counterparty_public_key: the counterparty's public key (for fetchai ledger case) :param is_from_terms_sender: whether from == terms.sender_address (i.e. agent submitting tx is the one which terms are considered) :return: the kwargs """ all_tokens = {**terms.amount_by_currency_id, **terms.quantities_by_good_id} token_ids = sorted([int(key) for key in all_tokens.keys()]) if is_from_terms_sender: from_supplies = [ 0 if int(all_tokens[str(token_id)]) >= 0 else -int(all_tokens[str(token_id)]) for token_id in token_ids ] to_supplies = [ 0 if int(all_tokens[str(token_id)]) <= 0 else int(all_tokens[str(token_id)]) for token_id in token_ids ] else: from_supplies = [ 0 if int(all_tokens[str(token_id)]) <= 0 else int(all_tokens[str(token_id)]) for token_id in token_ids ] to_supplies = [ 0 if int(all_tokens[str(token_id)]) >= 0 else -int(all_tokens[str(token_id)]) for token_id in token_ids ] kwargs = { "from_address": terms.sender_address if is_from_terms_sender else terms.counterparty_address, "to_address": terms.counterparty_address if is_from_terms_sender else terms.sender_address, "token_ids": token_ids, "from_supplies": from_supplies, "to_supplies": to_supplies, "value": 0, "trade_nonce": int(terms.nonce), "tx_fee": list(terms.fee_by_currency_id.values())[0], } enforce( sender_public_key is not None and counterparty_public_key is not None or sender_public_key is None and counterparty_public_key is None, "Either provide both sender's and counterparty's public-keys or neither's.", ) enforce( not ( signature is not None and sender_public_key is not None and counterparty_public_key is not None ), "Either provide signature (for Ethereum-based TAC) or sender and counterparty's public keys (for Fetchai-based TAC), or neither (for and non-contract-based Tac)", ) if signature is not None: kwargs["signature"] = signature elif sender_public_key is not None: kwargs["value"] = 1 kwargs["from_pubkey"] = sender_public_key kwargs["to_pubkey"] = counterparty_public_key return kwargs ================================================ FILE: packages/fetchai/skills/tac_negotiation/transactions.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a class to manage transactions.""" import datetime from collections import defaultdict, deque from typing import Any, Deque, Dict, List, Tuple, cast from aea.decision_maker.gop import OwnershipState from aea.exceptions import enforce from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueLabel from aea.skills.base import Model from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue MessageId = int class Transactions(Model): """Class to handle pending transaction proposals/acceptances and locked transactions.""" def __init__(self, **kwargs: Any) -> None: """Initialize the transactions.""" self._pending_transaction_timeout = kwargs.pop( "pending_transaction_timeout", 30 ) super().__init__(**kwargs) self._pending_proposals = defaultdict( lambda: {} ) # type: Dict[DialogueLabel, Dict[MessageId, Terms]] self._pending_initial_acceptances = defaultdict( lambda: {} ) # type: Dict[DialogueLabel, Dict[MessageId, Terms]] self._locked_txs = {} # type: Dict[str, Terms] self._locked_txs_as_buyer = {} # type: Dict[str, Terms] self._locked_txs_as_seller = {} # type: Dict[str, Terms] self._last_update_for_transactions = ( deque() ) # type: Deque[Tuple[datetime.datetime, str]] self._nonce = 0 @property def pending_proposals( self, ) -> Dict[DialogueLabel, Dict[MessageId, Terms]]: """Get the pending proposals.""" return self._pending_proposals @property def pending_initial_acceptances( self, ) -> Dict[DialogueLabel, Dict[MessageId, Terms]]: """Get the pending initial acceptances.""" return self._pending_initial_acceptances def get_next_nonce(self) -> str: """Get the next nonce.""" self._nonce += 1 return str(self._nonce) def update_confirmed_transactions(self) -> None: """Update model wrt to confirmed transactions.""" confirmed_tx_ids = self.context.shared_state.pop( "confirmed_tx_ids", [] ) # type: List[str] for transaction_id in confirmed_tx_ids: # remove (safely) the associated pending proposal (if present) self._locked_txs.pop(transaction_id, None) self._locked_txs_as_buyer.pop(transaction_id, None) self._locked_txs_as_seller.pop(transaction_id, None) def cleanup_pending_transactions(self) -> None: """Remove all the pending messages (i.e. either proposals or acceptances) that have been stored for an amount of time longer than the timeout.""" queue = self._last_update_for_transactions timeout = datetime.timedelta(0, self._pending_transaction_timeout) if len(queue) == 0: return next_date, next_item = queue[0] while datetime.datetime.now() - next_date > timeout: # remove the element from the queue queue.popleft() # extract dialogue label and message id transaction_id = next_item self.context.logger.debug( "removing transaction from pending list: {}".format(transaction_id) ) # remove (safely) the associated pending proposal (if present) self._locked_txs.pop(transaction_id, None) self._locked_txs_as_buyer.pop(transaction_id, None) self._locked_txs_as_seller.pop(transaction_id, None) # check the next transaction, if present if len(queue) == 0: break # pragma: no cover next_date, next_item = queue[0] # pragma: no cover def add_pending_proposal( self, dialogue_label: DialogueLabel, proposal_id: int, terms: Terms, ) -> None: """ Add a proposal (in the form of a transaction) to the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal :param terms: the terms """ enforce( dialogue_label not in self._pending_proposals and proposal_id not in self._pending_proposals[dialogue_label], "Proposal is already in the list of pending proposals.", ) self._pending_proposals[dialogue_label][proposal_id] = terms def pop_pending_proposal( self, dialogue_label: DialogueLabel, proposal_id: int ) -> Terms: """ Remove a proposal (in the form of a transaction) from the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal :return: terms """ enforce( dialogue_label in self._pending_proposals and proposal_id in self._pending_proposals[dialogue_label], "Cannot find the proposal in the list of pending proposals.", ) terms = self._pending_proposals[dialogue_label].pop(proposal_id) return terms def add_pending_initial_acceptance( self, dialogue_label: DialogueLabel, proposal_id: int, terms: Terms, ) -> None: """ Add an acceptance (in the form of a transaction) to the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal :param terms: the terms """ enforce( dialogue_label not in self._pending_initial_acceptances and proposal_id not in self._pending_initial_acceptances[dialogue_label], "Initial acceptance is already in the list of pending initial acceptances.", ) self._pending_initial_acceptances[dialogue_label][proposal_id] = terms def pop_pending_initial_acceptance( self, dialogue_label: DialogueLabel, proposal_id: int ) -> Terms: """ Remove an acceptance (in the form of a transaction) from the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal :return: the transaction message """ enforce( dialogue_label in self._pending_initial_acceptances and proposal_id in self._pending_initial_acceptances[dialogue_label], "Cannot find the initial acceptance in the list of pending initial acceptances.", ) terms = self._pending_initial_acceptances[dialogue_label].pop(proposal_id) return terms def _register_transaction_with_time(self, transaction_id: str) -> None: """ Register a transaction with a creation datetime. :param transaction_id: the transaction id """ now = datetime.datetime.now() self._last_update_for_transactions.append((now, transaction_id)) def add_locked_tx(self, terms: Terms, role: FipaDialogue.Role) -> None: """ Add a lock (in the form of a transaction). :param terms: the terms :param role: the role of the agent (seller or buyer) """ as_seller = role == FipaDialogue.Role.SELLER transaction_id = terms.id enforce( transaction_id not in self._locked_txs, "This transaction is already a locked transaction.", ) self._register_transaction_with_time(transaction_id) self._locked_txs[transaction_id] = terms if as_seller: self._locked_txs_as_seller[transaction_id] = terms else: self._locked_txs_as_buyer[transaction_id] = terms def pop_locked_tx(self, terms: Terms) -> Terms: """ Remove a lock (in the form of a transaction). :param terms: the terms :return: the transaction """ transaction_id = terms.id enforce( transaction_id in self._locked_txs, "Cannot find this transaction in the list of locked transactions.", ) terms = self._locked_txs.pop(transaction_id) self._locked_txs_as_buyer.pop(transaction_id, None) self._locked_txs_as_seller.pop(transaction_id, None) return terms def ownership_state_after_locks(self, is_seller: bool) -> OwnershipState: """ Apply all the locks to the current ownership state of the agent. This assumes, that all the locked transactions will be successful. :param is_seller: Boolean indicating the role of the agent. :return: the agent state with the locks applied to current state """ all_terms = ( list(self._locked_txs_as_seller.values()) if is_seller else list(self._locked_txs_as_buyer.values()) ) ownership_state = cast( OwnershipState, self.context.decision_maker_handler_context.ownership_state ) ownership_state_after_locks = ownership_state.apply_transactions(all_terms) return ownership_state_after_locks ================================================ FILE: packages/fetchai/skills/tac_participation/README.md ================================================ # TAC Participation ## Description This is the skill for participating in a TAC. This skill is part of the Fetch.ai TAC demo. It searches for a TAC on the sOEF, and if found, participates in the TAC by communicating with the controller agent. ## Behaviours - `tac_search`: searches for a TAC - `transaction_processing`: processes transactions during the competition ## Handlers - `tac`: handles `tac` messages by the controller for participating in the competition - `oef`: handles `oef_search` messages to find and connect with a controller ## Links - TAC Demo ================================================ FILE: packages/fetchai/skills/tac_participation/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the tac participation skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/tac_participation:0.25.6") ================================================ FILE: packages/fetchai/skills/tac_participation/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a tac search behaviour.""" from collections import OrderedDict from typing import Any, Dict, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.dialogues import OefSearchDialogues from packages.fetchai.skills.tac_participation.game import Game, Phase class TacSearchBehaviour(TickerBehaviour): """This class scaffolds a behaviour.""" def setup(self) -> None: """Implement the setup.""" def act(self) -> None: """Implement the act.""" game = cast(Game, self.context.game) if game.phase.value == Phase.PRE_GAME.value: self._search_for_tac() def teardown(self) -> None: """Implement the task teardown.""" def _search_for_tac(self) -> None: """ Search for active TAC Controller. We assume that the controller is registered as a service with the 'tac' data model and with an attribute version = expected_version_id. """ game = cast(Game, self.context.game) query = game.get_game_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( "searching for TAC, search_id={}".format(oef_search_msg.dialogue_reference) ) class TransactionProcessBehaviour(TickerBehaviour): """This class implements the processing of the transactions class.""" def setup(self) -> None: """Implement the setup.""" def act(self) -> None: """Implement the task execution.""" game = cast(Game, self.context.game) if game.phase.value == Phase.GAME.value: self._process_transactions() def teardown(self) -> None: """Implement the task teardown.""" def _process_transactions(self) -> None: """Process transactions.""" game = cast(Game, self.context.game) tac_dialogue = game.tac_dialogue transactions = cast( Dict[str, Dict[str, Any]], self.context.shared_state.get("transactions", OrderedDict()), ) tx_ids = list(transactions.keys()) for tx_id in tx_ids: last_msg = ( tac_dialogue.last_message ) # could be a problem if messages are delivered out of order if last_msg is None: raise ValueError("No last message available.") tx_content = transactions.pop(tx_id, None) if tx_content is None: raise ValueError("Tx for id={} not found.".format(tx_id)) terms = tx_content["terms"] sender_signature = tx_content["sender_signature"] counterparty_signature = tx_content["counterparty_signature"] msg = tac_dialogue.reply( performative=TacMessage.Performative.TRANSACTION, target_message=last_msg, transaction_id=tx_id, ledger_id=terms.ledger_id, sender_address=terms.sender_address, counterparty_address=terms.counterparty_address, amount_by_currency_id=terms.amount_by_currency_id, fee_by_currency_id=terms.fee_by_currency_id, quantities_by_good_id=terms.quantities_by_good_id, sender_signature=sender_signature, counterparty_signature=counterparty_signature, nonce=terms.nonce, ) self.context.logger.info( "sending transaction {} to controller, message={}.".format(tx_id, msg) ) self.context.outbox.put_message(message=msg) ================================================ FILE: packages/fetchai/skills/tac_participation/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - OefSearchDialogue: The dialogue class maintains state of a dialogue of type oef_search and manages it. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - SigningDialogue: The dialogue class maintains state of a dialogue of type signing and manages it. - SigningDialogues: The dialogues class keeps track of all dialogues of type signing. - TacDialogue: The dialogue class maintains state of a dialogue of type tac and manages it. - TacDialogues: The dialogues class keeps track of all dialogues of type tac. """ from typing import Any from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Model from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.state_update.dialogues import ( StateUpdateDialogue as BaseStateUpdateDialogue, ) from packages.fetchai.protocols.state_update.dialogues import ( StateUpdateDialogues as BaseStateUpdateDialogues, ) from packages.fetchai.protocols.tac.dialogues import TacDialogue as BaseTacDialogue from packages.fetchai.protocols.tac.dialogues import TacDialogues as BaseTacDialogues OefSearchDialogue = BaseOefSearchDialogue class OefSearchDialogues(Model, BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseOefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) StateUpdateDialogue = BaseStateUpdateDialogue class StateUpdateDialogues(Model, BaseStateUpdateDialogues): """This class keeps track of all state_update dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseStateUpdateDialogue.Role.SKILL BaseStateUpdateDialogues.__init__( self, self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) TacDialogue = BaseTacDialogue class TacDialogues(Model, BaseTacDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs: Any) -> None: """ Initialize dialogues. :param kwargs: keyword arguments """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return BaseTacDialogue.Role.PARTICIPANT BaseTacDialogues.__init__( self, self_address=self.context.agent_address, role_from_first_message=role_from_first_message, ) ================================================ FILE: packages/fetchai/skills/tac_participation/game.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains a class representing the game.""" from enum import Enum from typing import Any, Dict, List, Optional from aea.common import Address from aea.exceptions import AEAEnforceError, enforce from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.skills.base import Model from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.dialogues import ( StateUpdateDialogue, TacDialogue, ) DEFAULT_LOCATION = {"longitude": 0.1270, "latitude": 51.5194} DEFAULT_SEARCH_QUERY = { "search_key": "tac", "search_value": "v1", "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 class Phase(Enum): """This class defines the phases of the game.""" PRE_GAME = "pre_game" GAME_REGISTRATION = "game_registration" GAME_SETUP = "game_setup" GAME = "game" POST_GAME = "post_game" class Configuration: """Class containing the game configuration of a TAC instance.""" def __init__( self, version_id: str, fee_by_currency_id: Dict[str, int], agent_addr_to_name: Dict[Address, str], good_id_to_name: Dict[str, str], controller_addr: Address, ): """ Instantiate a game configuration. :param version_id: the version of the game. :param fee_by_currency_id: the fee for a transaction by currency id. :param agent_addr_to_name: a dictionary mapping agent addresses to agent names (as strings). :param good_id_to_name: a dictionary mapping good ids to good names (as strings). :param controller_addr: the address of the controller """ self._version_id = version_id self._nb_agents = len(agent_addr_to_name) self._nb_goods = len(good_id_to_name) self._fee_by_currency_id = fee_by_currency_id self._agent_addr_to_name = agent_addr_to_name self._good_id_to_name = good_id_to_name self._controller_addr = controller_addr self._check_consistency() @property def version_id(self) -> str: """Agent number of a TAC instance.""" return self._version_id @property def nb_agents(self) -> int: """Agent number of a TAC instance.""" return self._nb_agents @property def nb_goods(self) -> int: """Good number of a TAC instance.""" return self._nb_goods @property def tx_fee(self) -> int: """Transaction fee for the TAC instance.""" enforce( len(self._fee_by_currency_id) == 1, "More than one currency id present!" ) value = next(iter(self._fee_by_currency_id.values())) return value @property def fee_by_currency_id(self) -> Dict[str, int]: """Transaction fee for the TAC instance.""" return self._fee_by_currency_id @property def agent_addr_to_name(self) -> Dict[Address, str]: """Map agent addresses to names.""" return self._agent_addr_to_name @property def good_id_to_name(self) -> Dict[Address, str]: """Map good ids to names.""" return self._good_id_to_name @property def agent_addresses(self) -> List[Address]: """List of agent addresses.""" return list(self._agent_addr_to_name.keys()) @property def agent_names(self) -> List[str]: """List of agent names.""" return list(self._agent_addr_to_name.values()) @property def good_ids(self) -> List[Address]: """List of good ids.""" return list(self._good_id_to_name.keys()) @property def good_names(self) -> List[str]: """List of good names.""" return list(self._good_id_to_name.values()) @property def controller_addr(self) -> str: """Get the controller address.""" return self._controller_addr def _check_consistency(self) -> None: """Check the consistency of the game configuration.""" enforce(self.version_id is not None, "A version id must be set.") enforce( len(self.fee_by_currency_id) == 1 and self.tx_fee >= 0, "Tx fee must be non-negative.", ) enforce(self.nb_agents > 1, "Must have at least two agents.") enforce(self.nb_goods > 1, "Must have at least two goods.") enforce( len(self.agent_addresses) == self.nb_agents, "There must be one address for each agent.", ) enforce( len(set(self.agent_names)) == self.nb_agents, "Agents' names must be unique.", ) enforce( len(self.good_ids) == self.nb_goods, "There must be one id for each good." ) enforce( len(set(self.good_names)) == self.nb_goods, "Goods' names must be unique." ) class Game(Model): """This class deals with the game.""" def __init__(self, **kwargs: Any): """Instantiate the game class.""" self._expected_controller_addr = kwargs.pop( "expected_controller_addr", None ) # type: Optional[str] self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) if "search_value" not in self._search_query: # pragma: nocover raise ValueError("search_value not found in search_query") self._expected_version_id = self._search_query["search_value"] location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = Location( latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) ledger_id = kwargs.pop("ledger_id", None) self._is_using_contract = kwargs.pop("is_using_contract", False) # type: bool super().__init__(**kwargs) self._phase = Phase.PRE_GAME self._conf = None # type: Optional[Configuration] self._contract_address = None # type: Optional[str] self._tac_dialogue = None # type: Optional[TacDialogue] self._state_update_dialogue = None # type: Optional[StateUpdateDialogue] self._ledger_id = ( ledger_id if ledger_id is not None else self.context.default_ledger_id ) @property def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id @property def is_using_contract(self) -> bool: """Returns the is_using_contract.""" return self._is_using_contract @property def expected_version_id(self) -> str: """Get the expected version id of the TAC.""" return self._expected_version_id @property def phase(self) -> Phase: """Get the game phase.""" return self._phase @property def contract_address(self) -> str: """Get the contract address.""" if self._contract_address is None: raise AEAEnforceError("Contract address not set!") return self._contract_address @contract_address.setter def contract_address(self, contract_address: str) -> None: """Set the contract address.""" enforce(self._contract_address is None, "Contract address already set!") self._contract_address = contract_address @property def tac_dialogue(self) -> TacDialogue: """Retrieve the tac dialogue.""" if self._tac_dialogue is None: raise AEAEnforceError("TacDialogue not set!") return self._tac_dialogue @tac_dialogue.setter def tac_dialogue(self, tac_dialogue: TacDialogue) -> None: """Set the tac dialogue.""" enforce(self._tac_dialogue is None, "TacDialogue already set!") self._tac_dialogue = tac_dialogue @property def state_update_dialogue(self) -> StateUpdateDialogue: """Retrieve the state_update dialogue.""" if self._state_update_dialogue is None: raise AEAEnforceError("StateUpdateDialogue not set!") return self._state_update_dialogue @state_update_dialogue.setter def state_update_dialogue(self, state_update_dialogue: StateUpdateDialogue) -> None: """Set the state_update dialogue.""" enforce(self._state_update_dialogue is None, "StateUpdateDialogue already set!") self._state_update_dialogue = state_update_dialogue @property def expected_controller_addr(self) -> Address: """Get the expected controller address.""" if self._expected_controller_addr is None: raise AEAEnforceError("Expected controller address not assigned!") return self._expected_controller_addr @property def conf(self) -> Configuration: """Get the game configuration.""" if self._conf is None: raise AEAEnforceError("Game configuration not assigned!") return self._conf def init(self, tac_message: TacMessage, controller_addr: Address) -> None: """ Populate data structures with the game data. :param tac_message: the tac message with the game instance data :param controller_addr: the address of the controller """ enforce( tac_message.performative == TacMessage.Performative.GAME_DATA, "Wrong TacMessage for initialization of TAC game.", ) enforce( controller_addr == self.expected_controller_addr, "TacMessage from unexpected controller.", ) enforce( tac_message.version_id == self.expected_version_id, f"TacMessage for unexpected game, expected={self.expected_version_id}, found={tac_message.version_id}", ) self._conf = Configuration( tac_message.version_id, tac_message.fee_by_currency_id, tac_message.agent_addr_to_name, tac_message.good_id_to_name, controller_addr, ) def update_expected_controller_addr(self, controller_addr: Address) -> None: """ Overwrite the expected controller address. :param controller_addr: the address of the controller """ self.context.logger.warning( "TAKE CARE! Circumventing controller identity check! For added security provide the expected controller key as an argument to the Game instance and check against it." ) self._expected_controller_addr = controller_addr def update_game_phase(self, phase: Phase) -> None: """ Update the game phase. :param phase: the game phase """ self._phase = phase def get_game_query(self) -> Query: """ Get the query for the TAC game. :return: the query """ close_to_my_service = Constraint( "location", ConstraintType("distance", (self._agent_location, self._radius)) ) service_key_filter = Constraint( self._search_query["search_key"], ConstraintType( self._search_query["constraint_type"], self._search_query["search_value"], ), ) query = Query( [close_to_my_service, service_key_filter], ) return query ================================================ FILE: packages/fetchai/skills/tac_participation/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers.""" from typing import Dict, Optional, Tuple, cast from aea.common import Address from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.state_update.message import StateUpdateMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.dialogues import ( OefSearchDialogue, OefSearchDialogues, StateUpdateDialogue, StateUpdateDialogues, TacDialogue, TacDialogues, ) from packages.fetchai.skills.tac_participation.game import Game, Phase class OefSearchHandler(Handler): """This class handles oef messages.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id def setup(self) -> None: """Implement the handler setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ oef_search_msg = cast(OefSearchMessage, message) # recover dialogue oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_dialogue = cast( Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) if oef_search_dialogue is None: self._handle_unidentified_dialogue(oef_search_msg) return # handle message if oef_search_msg.performative == OefSearchMessage.Performative.SEARCH_RESULT: self._on_search_result(oef_search_msg, oef_search_dialogue) elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: self._on_oef_error(oef_search_msg, oef_search_dialogue) else: self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ Handle an unidentified dialogue. :param oef_search_msg: the message """ self.context.logger.warning( "received invalid oef_search message={}, unidentified dialogue.".format( oef_search_msg ) ) def _on_oef_error( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an OEF error message. :param oef_search_msg: the oef search msg :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "received OEF Search error: dialogue_reference={}, oef_error_operation={}".format( oef_search_dialogue.dialogue_label.dialogue_reference, oef_search_msg.oef_error_operation, ) ) def _on_search_result( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Split the search results from the OEF search node. :param oef_search_msg: the search result :param oef_search_dialogue: the dialogue """ self.context.logger.debug( "on search result: dialogue_reference={} agents={}".format( oef_search_dialogue.dialogue_label.dialogue_reference, oef_search_msg.agents, ) ) self._on_controller_search_result(oef_search_msg.agents) def _handle_invalid( self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue ) -> None: """ Handle an oef search message. :param oef_search_msg: the oef search message :param oef_search_dialogue: the dialogue """ self.context.logger.warning( "cannot handle oef_search message of performative={} in dialogue={}.".format( oef_search_msg.performative, oef_search_dialogue, ) ) def _on_controller_search_result( self, agent_addresses: Tuple[Address, ...] ) -> None: """ Process the search result for a controller. :param agent_addresses: list of agent addresses """ game = cast(Game, self.context.game) if game.phase.value != Phase.PRE_GAME.value: self.context.logger.debug( "ignoring controller search result, the agent is already competing." ) return if len(agent_addresses) == 0: self.context.logger.info("couldn't find the TAC controller. Retrying...") elif len(agent_addresses) > 1: self.context.logger.warning( "found more than one TAC controller. Retrying..." ) else: self.context.logger.info("found the TAC controller. Registering...") controller_addr = agent_addresses[0] self._register_to_tac(controller_addr) def _register_to_tac(self, controller_addr: Address) -> None: """ Register to active TAC Controller. :param controller_addr: the address of the controller. """ game = cast(Game, self.context.game) game.update_expected_controller_addr(controller_addr) game.update_game_phase(Phase.GAME_REGISTRATION) tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) tac_msg, tac_dialogue = tac_dialogues.create( counterparty=controller_addr, performative=TacMessage.Performative.REGISTER, agent_name=self.context.agent_name, ) tac_dialogue = cast(TacDialogue, tac_dialogue) game.tac_dialogue = tac_dialogue self.context.outbox.put_message(message=tac_msg) self.context.behaviours.tac_search.is_active = False self.context.shared_state["tac_version_id"] = game.expected_version_id class TacHandler(Handler): """This class handles oef messages.""" SUPPORTED_PROTOCOL = TacMessage.protocol_id def setup(self) -> None: """Implement the handler setup.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message """ tac_msg = cast(TacMessage, message) # recover dialogue tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) tac_dialogue = cast(Optional[TacDialogue], tac_dialogues.update(tac_msg)) if tac_dialogue is None: self._handle_unidentified_dialogue(tac_msg) return # handle message game = cast(Game, self.context.game) self.context.logger.debug( "handling controller response. performative={}".format(tac_msg.performative) ) if tac_msg.sender != game.expected_controller_addr: raise ValueError( "The sender of the message is not the controller agent we registered with." ) if tac_msg.performative == TacMessage.Performative.TAC_ERROR: self._on_tac_error(tac_msg, tac_dialogue) elif tac_msg.performative == TacMessage.Performative.GAME_DATA: self._on_start(tac_msg) elif tac_msg.performative == TacMessage.Performative.CANCELLED: self._on_cancelled(tac_msg) elif tac_msg.performative == TacMessage.Performative.TRANSACTION_CONFIRMATION: self._on_transaction_confirmed(tac_msg) else: self._handle_invalid(tac_msg, tac_dialogue) def teardown(self) -> None: """Implement the handler teardown.""" def _handle_unidentified_dialogue(self, tac_msg: TacMessage) -> None: """ Handle an unidentified dialogue. :param tac_msg: the message """ self.context.logger.warning( "received invalid tac message={}, unidentified dialogue.".format(tac_msg) ) def _on_tac_error(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle 'on tac error' event emitted by the controller. :param tac_msg: The tac message. :param tac_dialogue: the tac dialogue """ error_code = tac_msg.error_code self.context.logger.debug( "received error from the controller in dialogue={}. error_msg={}".format( tac_dialogue, TacMessage.ErrorCode.to_msg(error_code.value) ) ) if error_code == TacMessage.ErrorCode.TRANSACTION_NOT_VALID: info = cast(Dict[str, str], tac_msg.info) transaction_id = ( cast(str, info.get("transaction_id")) if (info is not None and info.get("transaction_id") is not None) else "NO_TX_ID" ) self.context.logger.warning( "received error on transaction id: {}".format(transaction_id[-10:]) ) def _on_start(self, tac_msg: TacMessage) -> None: """ Handle the 'start' event emitted by the controller. :param tac_msg: the game data """ game = cast(Game, self.context.game) if game.phase.value != Phase.GAME_REGISTRATION.value: self.context.logger.warning( "we do not expect a start message in game phase={}".format( game.phase.value ) ) return self.context.logger.info( "received start event from the controller. Starting to compete..." ) game = cast(Game, self.context.game) game.init(tac_msg, tac_msg.sender) game.update_game_phase(Phase.GAME) if game.is_using_contract: contract_address = ( None if tac_msg.info is None else tac_msg.info.get("contract_address") ) if contract_address is not None: game.contract_address = contract_address self.context.shared_state["erc1155_contract_address"] = contract_address self.context.logger.info( "received a contract address: {}".format(contract_address) ) self._update_ownership_and_preferences(tac_msg) else: self.context.logger.warning("did not receive a contract address!") else: self._update_ownership_and_preferences(tac_msg) def _update_ownership_and_preferences(self, tac_msg: TacMessage) -> None: """ Update ownership and preferences. :param tac_msg: the game data """ self.context.logger.info("processing game data, message={}".format(tac_msg)) state_update_dialogues = cast( StateUpdateDialogues, self.context.state_update_dialogues ) state_update_msg, state_update_dialogue = state_update_dialogues.create( counterparty=self.context.decision_maker_address, performative=StateUpdateMessage.Performative.INITIALIZE, amount_by_currency_id=tac_msg.amount_by_currency_id, quantities_by_good_id=tac_msg.quantities_by_good_id, exchange_params_by_currency_id=tac_msg.exchange_params_by_currency_id, utility_params_by_good_id=tac_msg.utility_params_by_good_id, ) self.context.shared_state["fee_by_currency_id"] = tac_msg.fee_by_currency_id state_update_dialogue = cast(StateUpdateDialogue, state_update_dialogue) game = cast(Game, self.context.game) game.state_update_dialogue = state_update_dialogue self.context.decision_maker_message_queue.put_nowait(state_update_msg) def _on_cancelled(self, tac_msg: TacMessage) -> None: """ Handle the cancellation of the competition from the TAC controller. :param tac_msg: the TacMessage. """ game = cast(Game, self.context.game) if game.phase.value not in [Phase.GAME_REGISTRATION.value, Phase.GAME.value]: self.context.logger.warning( "we do not expect a message in game phase={}, received msg={}".format( game.phase.value, tac_msg ) ) return self.context.logger.info("received cancellation from the controller.") game = cast(Game, self.context.game) game.update_game_phase(Phase.POST_GAME) self.context.is_active = False self.context.shared_state["is_game_finished"] = True def _on_transaction_confirmed(self, tac_msg: TacMessage) -> None: """ Handle 'on transaction confirmed' event emitted by the controller. :param tac_msg: the TacMessage. """ game = cast(Game, self.context.game) if game.phase.value != Phase.GAME.value: self.context.logger.warning( "we do not expect a transaction in game phase={}, received msg={}".format( game.phase.value, tac_msg ) ) return self.context.logger.info( "received transaction confirmation from the controller: transaction_id={}".format( tac_msg.transaction_id ) ) state_update_dialogue = game.state_update_dialogue last_msg = state_update_dialogue.last_message if last_msg is None: raise ValueError("Could not retrieve last message.") state_update_msg = state_update_dialogue.reply( performative=StateUpdateMessage.Performative.APPLY, target_message=last_msg, amount_by_currency_id=tac_msg.amount_by_currency_id, quantities_by_good_id=tac_msg.quantities_by_good_id, ) self.context.decision_maker_message_queue.put_nowait(state_update_msg) if "confirmed_tx_ids" not in self.context.shared_state.keys(): self.context.shared_state["confirmed_tx_ids"] = [] self.context.shared_state["confirmed_tx_ids"].append(tac_msg.transaction_id) def _handle_invalid(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle an oef search message. :param tac_msg: the tac message :param tac_dialogue: the tac dialogue """ self.context.logger.warning( "cannot handle tac message of performative={} in dialogue={}.".format( tac_msg.performative, tac_dialogue ) ) ================================================ FILE: packages/fetchai/skills/tac_participation/skill.yaml ================================================ name: tac_participation author: fetchai version: 0.25.6 type: skill description: The tac participation skill implements the logic for an AEA to participate in the TAC. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmW3M9mYP2NdtzpBytNaLCUrkno3xZoNieBZ58cdcku5nm __init__.py: QmNS35yXppvMYqXC9E9P7hw8LYUcuWXdZKP5avkjWNWgT5 behaviours.py: QmVVeGX9SfhS59k1FDxdUNTexybigvjLRPZiVkrf2yG5kd dialogues.py: QmWrqCc6RpPQW8Dw4sEocGHSRHPkrnAHfPqLhZsT1H5FJB game.py: QmP5j5YdYyr5oKN2zGnurzCQSm25qGHh4RG9zVqQLkmdix handlers.py: QmRDG5WLmebXtahUMG4DBNVgqKZUBmPv6Whvs5ogS58YuP fingerprint_ignore_patterns: [] connections: [] contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/oef_search:1.1.7 - fetchai/state_update:1.1.7 - fetchai/tac:1.1.7 skills: [] behaviours: tac_search: args: tick_interval: 5 class_name: TacSearchBehaviour transaction_processing: args: tick_interval: 2 class_name: TransactionProcessBehaviour handlers: oef: args: {} class_name: OefSearchHandler tac: args: {} class_name: TacHandler models: game: args: is_using_contract: false location: latitude: 51.5194 longitude: 0.127 search_query: constraint_type: == search_key: tac search_value: v1 search_radius: 5.0 class_name: Game oef_search_dialogues: args: {} class_name: OefSearchDialogues state_update_dialogues: args: {} class_name: StateUpdateDialogues tac_dialogues: args: {} class_name: TacDialogues dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/task_test_skill/README.md ================================================ # Test task skill ## Description Simple task skill to test ## Handlers ## Links ================================================ FILE: packages/fetchai/skills/task_test_skill/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains an example of skill for an AEA.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/task_test_skill:0.1.2") ================================================ FILE: packages/fetchai/skills/task_test_skill/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours for the 'test task skill' skill.""" from typing import Optional from aea.skills.behaviours import TickerBehaviour from aea.skills.tasks import Task from packages.fetchai.skills.task_test_skill.tasks import SimpleTask class TaskBehaviour(TickerBehaviour): """Echo behaviour.""" task_id: Optional[int] task: Task def setup(self) -> None: """Set up the behaviour.""" self.context.logger.info("Task Behaviour: setup method called.") self.task = SimpleTask("some data") self.task_id = None def act(self) -> None: """Act according to the behaviour.""" self.context.logger.info("Task Behaviour: act method called.") if self.task_id is None: self.set_task() else: self.get_task_result() def set_task(self) -> None: """Set background task to run.""" if self.task_id: return self.task_id = self.context.task_manager.enqueue_task(self.task) self.context.logger.info("Task set.") def get_task_result(self) -> None: """Get result of the task.""" if self.task_id is None: return async_result = self.context.task_manager.get_task_result(self.task_id) if not async_result.ready(): return self.context.logger.info(f"Task result is ready: {async_result.get()}") self.task_id = None def teardown(self) -> None: """Teardown the behaviour.""" self.context.logger.info("Task Behaviour: teardown method called.") ================================================ FILE: packages/fetchai/skills/task_test_skill/skill.yaml ================================================ name: task_test_skill author: fetchai version: 0.1.2 type: skill description: Skill with simple task to run. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmcXGDniXKQgNuoKwYor6mb5qXMWRHSvFT8cppxTj66Ngz __init__.py: QmVg95RRZXNFRM17PCy8iZSXBzsFo4Xm1PVP9k1nC7epiT behaviours.py: QmSV3NmWaVLDF5GvCLXMjYSxUqZ5dAT5vXtazNZUtiquV8 tasks.py: QmUA5k1vGCXVfBA5RTcJZXnbQgy5YL7ZWtVViiJiBVmLiF fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: [] skills: [] behaviours: task: args: tick_interval: 3.0 class_name: TaskBehaviour handlers: {} models: {} dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/task_test_skill/tasks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tasks for the 'task test skill' skill.""" import time from typing import Any from aea.skills.tasks import Task class SimpleTask(Task): """Simple task.""" def __init__(self, data: Any): """Initialize the task.""" super().__init__() self.data = data def execute(self, *args: Any, **kwargs: Any) -> Any: """Execute task.""" time.sleep(3) return self.data ================================================ FILE: packages/fetchai/skills/thermometer/README.md ================================================ # Thermometer ## Description This skill sells thermometer data. This skill is part of the Fetch.ai thermometer demo. It can be requested (for example by an agent with the `thermometer_client` skill) to provide thermometer data. If agreement is reached on the price via negotiation, it reads data from a (real or fake) thermometer, then delivers it after receiving payment. ## Behaviours - `service_registration`: registers thermometer data selling service on the sOEF ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages if service registration on the sOEF is unsuccessful ## Links - Thermometer Demo ================================================ FILE: packages/fetchai/skills/thermometer/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/thermometer:0.27.6") ================================================ FILE: packages/fetchai/skills/thermometer/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours of the agent.""" from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) ServiceRegistrationBehaviour = GenericServiceRegistrationBehaviour ================================================ FILE: packages/fetchai/skills/thermometer/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. """ from packages.fetchai.skills.generic_seller.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues ================================================ FILE: packages/fetchai/skills/thermometer/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of a thermometer AEA.""" from packages.fetchai.skills.generic_seller.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler ================================================ FILE: packages/fetchai/skills/thermometer/skill.yaml ================================================ name: thermometer author: fetchai version: 0.27.6 type: skill description: The thermometer skill implements the functionality to sell data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmUTeGxHXAZcM9hLcWTYywxMTdKjw7gQrXs2EAwgqejce9 __init__.py: QmNmBXfP81RZWToRq1JAoccVj6fgXg1VRFM9wpCrDjgKHz behaviours.py: QmZvLViapWxG1H41wn4e6Mzt8nmV6yyuk2sSsLJKZTQy2c dialogues.py: QmZckK3x2oPgXmnP4XaEBJQoaPp8Gh4ojDHnxzeNsTf4tC handlers.py: QmZ2gCqdxWntR6nB7CZS5UBtKjFC4Y8g5Ex8afJq2B1b65 strategy.py: QmZGPb3pSyKYcz76yg9XiJYN1PZDjTL8VM7Y4tF19NtE9B fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 skills: - fetchai/generic_seller:0.28.6 behaviours: service_registration: args: services_interval: 20 class_name: ServiceRegistrationBehaviour handlers: fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller data_for_sale: temperature: 26 has_data_source: false is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: thermometer_data service_id: thermometer_data unit_price: 10 class_name: Strategy dependencies: pyserial: {} temper-py: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/thermometer/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import time from typing import Dict from temper import Temper from packages.fetchai.skills.generic_seller.strategy import GenericStrategy MAX_RETRIES = 10 class Strategy(GenericStrategy): """This class defines a strategy for the agent.""" def collect_from_data_source(self) -> Dict[str, str]: """ Build the data payload. :return: the data """ temper = Temper() retries = 0 degrees = {} while retries < MAX_RETRIES: results = temper.read() if "internal temperature" in results[0].keys(): degrees = {"thermometer_data": str(results[0]["internal temperature"])} break self.context.logger.debug("Couldn't read the sensor I am re-trying.") time.sleep(0.5) retries += 1 return degrees ================================================ FILE: packages/fetchai/skills/thermometer_client/README.md ================================================ # Thermometer Client ## Description This skill buys thermometer data. This skill is part of the Fetch.ai thermometer demo. It finds an agent which sells thermometer data, requests data from a reading and pays the proposed amount. ## Behaviours - `search`: searches for thermometer data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - Thermometer Demo ================================================ FILE: packages/fetchai/skills/thermometer_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/thermometer_client:0.26.6") ================================================ FILE: packages/fetchai/skills/thermometer_client/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours of the agent.""" from packages.fetchai.skills.generic_buyer.behaviours import ( GenericSearchBehaviour, GenericTransactionBehaviour, ) SearchBehaviour = GenericSearchBehaviour TransactionBehaviour = GenericTransactionBehaviour ================================================ FILE: packages/fetchai/skills/thermometer_client/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - SigningDialogues: The dialogues class keeps track of all dialogues of type signing. """ from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( SigningDialogues as GenericSigningDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues SigningDialogues = GenericSigningDialogues ================================================ FILE: packages/fetchai/skills/thermometer_client/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of the agent.""" from packages.fetchai.skills.generic_buyer.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, GenericSigningHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler SigningHandler = GenericSigningHandler ================================================ FILE: packages/fetchai/skills/thermometer_client/skill.yaml ================================================ name: thermometer_client author: fetchai version: 0.26.6 type: skill description: The thermometer client skill implements the skill to purchase temperature data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmdjHRzrhq636HNZYKaR8YCFKromFJqkKL3vMKHFpnmwSq __init__.py: QmcrMiyeunFmRmJM8DsLg8FsHbFfqQaKChn3bcKp1sTumt behaviours.py: QmSr6fB3N7dhVo1cLY1TGd2q8usjGwNmTCNjBBbtgtVf9j dialogues.py: QmXgXcs25v9ob9a9XwT49wvK788vbUdZyYxUgG3ndHjrix handlers.py: QmP3Q6x3NMcWgRi6H5GtDtvnLWSoB1HeG8vTd4zcRZUgNj strategy.py: QmdHPLehqRr1dxuCbp4ENYmVMb8Ykvzg2Uzfos5kFJSr3D fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/generic_buyer:0.27.6 behaviours: search: args: search_interval: 5 class_name: SearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_quantity: 100 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_query: constraint_type: == search_key: seller_service search_value: thermometer_data search_radius: 5.0 service_id: thermometer_data class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/thermometer_client/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy Strategy = GenericStrategy ================================================ FILE: packages/fetchai/skills/weather_client/README.md ================================================ # Weather Client ## Description This skill buys dummy weather data. This skill is part of the Fetch.ai weather demo. It finds an agent which sells weather data, requests data for specific dates and pays the proposed amount. ## Behaviours - `search`: searches for weather data selling service on the sOEF - `transaction`: sequentially processes transactions' settlements on a blockchain ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages to manage the sellers found - `signing`: handles `signing` messages for transaction signing by the decision maker ## Links - Weather Demo ================================================ FILE: packages/fetchai/skills/weather_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/weather_client:0.26.6") ================================================ FILE: packages/fetchai/skills/weather_client/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the behaviours of the agent.""" from packages.fetchai.skills.generic_buyer.behaviours import ( GenericSearchBehaviour, GenericTransactionBehaviour, ) SearchBehaviour = GenericSearchBehaviour TransactionBehaviour = GenericTransactionBehaviour ================================================ FILE: packages/fetchai/skills/weather_client/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. - SigningDialogues: The dialogues class keeps track of all dialogues of type signing. """ from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) from packages.fetchai.skills.generic_buyer.dialogues import ( SigningDialogues as GenericSigningDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues SigningDialogues = GenericSigningDialogues ================================================ FILE: packages/fetchai/skills/weather_client/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of the agent.""" from packages.fetchai.skills.generic_buyer.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, GenericSigningHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler SigningHandler = GenericSigningHandler ================================================ FILE: packages/fetchai/skills/weather_client/skill.yaml ================================================ name: weather_client author: fetchai version: 0.26.6 type: skill description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: Qma4TrocZKtFcLeL1xKZxwz5no5qVHp5HjLrynYsKpw15Z __init__.py: QmTEC9T2zb2a9pYoVeAJigGhAjKWN48MUnTRuyqm761UdM behaviours.py: QmSr6fB3N7dhVo1cLY1TGd2q8usjGwNmTCNjBBbtgtVf9j dialogues.py: QmXgXcs25v9ob9a9XwT49wvK788vbUdZyYxUgG3ndHjrix handlers.py: QmP3Q6x3NMcWgRi6H5GtDtvnLWSoB1HeG8vTd4zcRZUgNj strategy.py: QmdHPLehqRr1dxuCbp4ENYmVMb8Ykvzg2Uzfos5kFJSr3D fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: - fetchai/generic_buyer:0.27.6 behaviours: search: args: search_interval: 5 class_name: SearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: TransactionBehaviour handlers: fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler signing: args: {} class_name: SigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_quantity: 100 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_query: constraint_type: == search_key: seller_service search_value: weather_data search_radius: 5.0 service_id: weather_data class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/weather_client/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy Strategy = GenericStrategy ================================================ FILE: packages/fetchai/skills/weather_station/README.md ================================================ # Weather Station ## Description This skill sells dummy weather data. This skill is part of the Fetch.ai weather demo. It reads data from a database, that is populated with dummy data from a weather station. It can be requested (for example by an agent with the `weather_client` skill) to provide weather data for specific dates, which it delivers after it receives payment. ## Behaviours - `service_registration`: registers weather selling service on the sOEF ## Handlers - `fipa`: handles `fipa` messages for negotiation - `ledger_api`: handles `ledger_api` messages for interacting with a ledger - `oef_search`: handles `oef_search` messages if service registration on the sOEF is unsuccessful ## Links - Weather Demo ================================================ FILE: packages/fetchai/skills/weather_station/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the default skill.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("fetchai/weather_station:0.27.6") ================================================ FILE: packages/fetchai/skills/weather_station/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours of the agent.""" from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) ServiceRegistrationBehaviour = GenericServiceRegistrationBehaviour ================================================ FILE: packages/fetchai/skills/weather_station/db_communication.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the Database Communication for the weather agent.""" import datetime import os.path import sqlite3 from typing import Dict, cast my_path = os.path.dirname(__file__) DB_SOURCE = os.path.join(my_path, "dummy_weather_station_data.db") class DBCommunication: """A class to communicate with a database.""" def __init__(self) -> None: """Initialize the database communication.""" self.source = DB_SOURCE def db_connection(self) -> sqlite3.Connection: """ Get db connection. :return: the db connection """ con = sqlite3.connect(self.source) return con def get_data_for_specific_dates( self, start_date: str, end_date: str ) -> Dict[str, int]: """ Get data for specific dates. :param start_date: the start date :param end_date: the end date :return: the data """ con = self.db_connection() cur = con.cursor() start_dt = datetime.datetime.strptime(start_date, "%d/%m/%Y") start = int((start_dt - datetime.datetime.fromtimestamp(0)).total_seconds()) end_dt = datetime.datetime.strptime(end_date, "%d/%m/%Y") end = int((end_dt - datetime.datetime.fromtimestamp(0)).total_seconds()) cur.execute( "SELECT * FROM data WHERE idx BETWEEN ? AND ?", (str(start), str(end)) ) data = cast(Dict[str, int], cur.fetchall()) cur.close() con.close() return data ================================================ FILE: packages/fetchai/skills/weather_station/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for dialogue management. - DefaultDialogues: The dialogues class keeps track of all dialogues of type default. - FipaDialogues: The dialogues class keeps track of all dialogues of type fipa. - LedgerApiDialogues: The dialogues class keeps track of all dialogues of type ledger_api. - OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. """ from packages.fetchai.skills.generic_seller.dialogues import ( DefaultDialogues as GenericDefaultDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( FipaDialogues as GenericFipaDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( LedgerApiDialogues as GenericLedgerApiDialogues, ) from packages.fetchai.skills.generic_seller.dialogues import ( OefSearchDialogues as GenericOefSearchDialogues, ) DefaultDialogues = GenericDefaultDialogues FipaDialogues = GenericFipaDialogues LedgerApiDialogues = GenericLedgerApiDialogues OefSearchDialogues = GenericOefSearchDialogues ================================================ FILE: packages/fetchai/skills/weather_station/dummy_weather_station_data.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains dummy weather station data.""" import datetime import logging import os.path import random import sqlite3 import time from typing import Dict, Union from aea.exceptions import enforce _default_logger = logging.getLogger( "aea.packages.fetchai.skills.weather_station.dummy_weather_station_data" ) my_path = os.path.dirname(__file__) DB_SOURCE = os.path.join(my_path, "dummy_weather_station_data.db") # Checking if the database exists con = sqlite3.connect(DB_SOURCE) cur = con.cursor() cur.close() con.commit() con.close() # Create a table if it doesn't exist' command = """ CREATE TABLE IF NOT EXISTS data ( abs_pressure REAL, delay REAL, hum_in REAL, hum_out REAL, idx TEXT, rain REAL, temp_in REAL, temp_out REAL, wind_ave REAL, wind_dir REAL, wind_gust REAL)""" con = sqlite3.connect(DB_SOURCE) cur = con.cursor() cur.execute(command) cur.close() con.commit() if con is not None: _default_logger.debug( "Weather station: I closed the db after checking it is populated!" ) con.close() class Forecast: """Represents a whether forecast.""" @staticmethod def add_data(tagged_data: Dict[str, Union[int, datetime.datetime]]) -> None: """ Add data to the forecast. :param tagged_data: the data dictionary """ con_ = sqlite3.connect(DB_SOURCE) cur_ = con_.cursor() cur_.execute( """INSERT INTO data(abs_pressure, delay, hum_in, hum_out, idx, rain, temp_in, temp_out, wind_ave, wind_dir, wind_gust) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( tagged_data["abs_pressure"], tagged_data["delay"], tagged_data["hum_in"], tagged_data["hum_out"], int( ( datetime.datetime.now() - datetime.datetime.fromtimestamp(0) ).total_seconds() ), tagged_data["rain"], tagged_data["temp_in"], tagged_data["temp_out"], tagged_data["wind_ave"], tagged_data["wind_dir"], tagged_data["wind_gust"], ), ) _default_logger.info("Wheather station: I added data in the db!") cur_.close() con_.commit() con_.close() def generate(self, number_of_entries: int) -> None: """Generate weather data.""" # some arbitrary max number to prevent arbitrarily large entries enforce(number_of_entries <= 1000000, "number_of_entries is too high!") for _ in range(number_of_entries): # nosec dict_of_data = { "abs_pressure": random.randrange(1022, 1025, 1), "delay": random.randint(2, 7), "hum_in": random.randrange(33, 40, 1), "hum_out": random.randrange(33, 80, 1), "idx": datetime.datetime.now(), "rain": random.randrange(70, 74, 1), "temp_in": random.randrange(18, 28, 1), "temp_out": random.randrange(2, 20, 1), "wind_ave": random.randrange(0, 10, 1), "wind_dir": random.randrange(0, 14, 1), "wind_gust": random.randrange(1, 7, 1), } # type: Dict[str, Union[int, datetime.datetime]] self.add_data(dict_of_data) time.sleep(5) if __name__ == "__main__": # pragma: nocover a = Forecast() a.generate(59) ================================================ FILE: packages/fetchai/skills/weather_station/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the handlers of a thermometer AEA.""" from packages.fetchai.skills.generic_seller.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, ) FipaHandler = GenericFipaHandler LedgerApiHandler = GenericLedgerApiHandler OefSearchHandler = GenericOefSearchHandler ================================================ FILE: packages/fetchai/skills/weather_station/skill.yaml ================================================ name: weather_station author: fetchai version: 0.27.6 type: skill description: The weather station skill implements the functionality to sell weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmVbpEJquwxzBZ2feJuRYBWrWjxQ9jhE1Er4LvmPU24ki9 __init__.py: QmPR7BN5S1moGnV6Wf9SjNE1Sm8tGd4aXLEp2G48Ys1LWM behaviours.py: QmaRDMaDVsjVfkAtAHkWLU9Fa88UxyLTczdkm97E9c5TFT db_communication.py: QmYY2eMJ8YHSnkKzvrQYe46rgwZJCjwDayCGKv8C2HroRQ dialogues.py: QmZckK3x2oPgXmnP4XaEBJQoaPp8Gh4ojDHnxzeNsTf4tC dummy_weather_station_data.py: QmTXFBa29Zi62jPJksjfUAvNCpRJVBkTGK33bbekaP4Loh handlers.py: QmZ2gCqdxWntR6nB7CZS5UBtKjFC4Y8g5Ex8afJq2B1b65 strategy.py: QmTdN7JpBdbfVJ2sSW5VQuWprfB4kUawF5PqQMC3AVcxds fingerprint_ignore_patterns: - '*.db' connections: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 skills: - fetchai/generic_seller:0.28.6 behaviours: service_registration: args: services_interval: 20 class_name: ServiceRegistrationBehaviour handlers: fipa: args: {} class_name: FipaHandler ledger_api: args: {} class_name: LedgerApiHandler oef_search: args: {} class_name: OefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller data_for_sale: pressure: 20 temperature: 26 wind: 10 has_data_source: false is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: weather_data service_id: weather_data unit_price: 10 class_name: Strategy dependencies: {} is_abstract: false ================================================ FILE: packages/fetchai/skills/weather_station/strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import json import time from typing import Any, Dict from packages.fetchai.skills.generic_seller.strategy import GenericStrategy from packages.fetchai.skills.weather_station.db_communication import DBCommunication DEFAULT_DATE_ONE = "3/10/2019" DEFAULT_DATE_TWO = "15/10/2019" class Strategy(GenericStrategy): """This class defines a strategy for the agent.""" def __init__(self, **kwargs: Any) -> None: """ Initialize the strategy of the agent. :param kwargs: keyword arguments """ self._date_one = kwargs.pop("date_one", DEFAULT_DATE_ONE) self._date_two = kwargs.pop("date_two", DEFAULT_DATE_TWO) self.db = DBCommunication() super().__init__(**kwargs) def collect_from_data_source(self) -> Dict[str, str]: """ Build the data payload. :return: a tuple of the data and the rows """ fetched_data = self.db.get_data_for_specific_dates( self._date_one, self._date_two ) weather_data = {} # type: Dict[str, str] row_data = {} # type: Dict[int, Dict[str, Any]] counter = 0 for items in fetched_data: if counter > 10: # so not too much data is sent break # pragma: nocover counter += 1 dict_of_data = { "abs_pressure": items[0], "delay": items[1], "hum_in": items[2], "hum_out": items[3], "idx": time.ctime(int(items[4])), "rain": items[5], "temp_in": items[6], "temp_out": items[7], "wind_ave": items[8], "wind_dir": items[9], "wind_gust": items[10], } row_data[counter] = dict_of_data weather_data["weather_data"] = json.dumps(row_data) return weather_data ================================================ FILE: packages/hashes.csv ================================================ fetchai/agents/aries_alice,QmSVGggvwbxziatmYxgzhZBuqNp5GSjmZuuvPYshNstCyK fetchai/agents/aries_faber,QmUAAu88wkoa3EM7RZrNsJ9h7Pj6p5w1d1nNHzsSVGyWoh fetchai/agents/car_data_buyer,QmTqTbUpRv7ktaXPe1yicvvkfzBvd48uyoEodanMJREQW2 fetchai/agents/car_detector,QmdQEkPuy7KfpFXttSzoRjw9GoX6KJbroopUjQcYAxHevM fetchai/agents/coin_price_feed,QmXiv1BkaBaQcvm6JfKYrUpycY8TQZEf14rzMQnVWtjBuq fetchai/agents/coin_price_oracle,QmaT13ZATFEDPuw4BPECZ4hrgpBj9L15XcRx2SxUH5wFZd fetchai/agents/coin_price_oracle_client,Qmb9WaU3ZGZBHQjHiSf6e6CShSz8JVpaxm42T6EHuzoGRG fetchai/agents/confirmation_aea_aw1,QmRbg76Kh7Ew9VD47hb9L8kC217oUJWn68dtTfeeBj2AZv fetchai/agents/confirmation_aea_aw2,QmRUvLAKLB1eppazDByupnH8eErLRoFaiBWQQv5KoHBubu fetchai/agents/confirmation_aea_aw3,QmaFMsn4fVCpnnHbMcGZk2GtdVugjbnsEwTyTNwQMznJxo fetchai/agents/confirmation_aea_aw5,QmfQbjLsvXm4eK8HtfMJBGaAr2dmLN2WfH59H1fBL6B4Yp fetchai/agents/erc1155_client,QmX43bRz1n6ZiFTtwGFMvip4GrKYNEikywbdaYKSXqxEnX fetchai/agents/erc1155_deployer,QmTnz2dQ8HxsfM9zYTaZzFgJrouCz5hXstZz7GBanNosCC fetchai/agents/error_test,QmQH8h8mzs87wdC4pYoeKEiJAyQmifhVBvaPs643qXk9A2 fetchai/agents/fipa_dummy_buyer,QmTGcmMQA1aSFF1LvmNrVk72qJ8YQu3k7NcE6FCNAvEggd fetchai/agents/generic_buyer,QmPVHoQAcqetX3B2UVsqH2K9LWVSarWxgBgfApePDqbTca fetchai/agents/generic_seller,QmdB7xfGviAypy5Xbh3vtattyrYGKXSGsrQqywfo9XspXk fetchai/agents/gym_aea,QmfZsRWfudLF4JcG75wMvKHX7ncJa6vSmJKwGcoKfSiEos fetchai/agents/hello_world,QmRDz4xeFfW4zMCmndaEkWkg8yz5K6tXgnUrfq6mzmnjot fetchai/agents/latest_block_feed,QmQ6VPSxHoC7rnhy3b6K3ksTonJ82TC4MoJRn8qi9VGWP8 fetchai/agents/ml_data_provider,QmdzzZugBvfTfzZ6TNJoQCfL1mLLg9R7KNSTmBTmU1fbiu fetchai/agents/ml_model_trainer,QmNqhDoZpwf8oAxbdcRrHXW9JyjAoMBmjjuGT6EuwCVXua fetchai/agents/my_first_aea,QmcCvC8HagrMcpKVDzjADy1SsiqYPGGRjafhBHJa1Xr1qR fetchai/agents/registration_aea_aw1,QmU3sSdJ9jpcdiU9Davcz6QtoLShPqEcSgk6CXdRXwMuL4 fetchai/agents/simple_aggregator,QmWh4zCi4Pbuy8w4Li7FYLzHrr7h7tAxrS5NgY3bSwwmPb fetchai/agents/simple_buyer_aw2,QmSxMusFh7FAWQ68kEVCqt4KNHT9r27fmhtgvULqVVZ7Cj fetchai/agents/simple_buyer_aw5,QmXyEPAk3CBWFMhko48JUFAJJtKTYfErWj2cU6caRD3bEk fetchai/agents/simple_seller_aw2,QmNnStVWhTGVhinc5DkT7qXqhLQa48TafmHf5KqNM3mnLv fetchai/agents/simple_seller_aw5,QmcQekaHYcXeCJ31cdiv89Pz3KSe5dPsY9QWso6xntDHms fetchai/agents/simple_service_registration,QmejN5MTSFDYqgk55w6mibYaBsUEAQGg9Pt3uaMjkT69vR fetchai/agents/simple_service_search,QmToaJcurxQHQG1pX9nfR4UgWGxAfu9vC5uruCu4s4nhRV fetchai/agents/tac_controller,QmW1SUYBsGAWfgetVChEUweXYenZv6gbPfJcrXsQW36T7C fetchai/agents/tac_controller_contract,QmPWkjkgKNx59jtCgqezKQHnRjoBfzRooShkvE6sT1ti8T fetchai/agents/tac_participant,QmbCnA38beKrg86kxjaKa5A87ZfQE1rYgbubHQGhWwdQgp fetchai/agents/tac_participant_contract,QmT4PkNF2cpJgpR4oUubwvwxPqdF5qJeTQFhjjLePunCa5 fetchai/agents/thermometer_aea,QmZ48DA8dpc6bt5qygU9K5RVdrNdSy3yji7z5nM3f38EF1 fetchai/agents/thermometer_client,QmS5drSLH3kNLhFvDfmdEh5MvtwXnfFdNNEvwFArfyq5Q1 fetchai/agents/weather_client,QmV3jNVcYG8vrrbRK4ZQ7NSr949iJVKh8awPSxXV6Arsn1 fetchai/agents/weather_station,QmdHEjfCrn6EmkVC2xuLsq4J8xae2ZPtdga3NmWGfiBrqV fetchai/connections/gym,QmYoYrLTgA4HBcprxVmWJwGzrmKcyjsBYVKamcStYVGzJr fetchai/connections/http_client,QmPXUdzkaZt2CSUXeyrc1gTj7eHfuNKa9XL3gvoohvoGgt fetchai/connections/http_server,QmSA3qQVrztMucpZevvvAe1mLFPknNBKEXZSq9kAQJP1he fetchai/connections/ledger,QmcEbe77YiRRwCbAh1JduyPsABSUYDnsJSNZ4BicEoiDDN fetchai/connections/local,QmQogxCUruQTzCKQxnrquEnmUNsoV9NjdDYqwng37uhgf7 fetchai/connections/oef,QmfUr3wQyHMnQ5C57NeD3ypL2JPe2BVMM8w1DZ79e63ycK fetchai/connections/p2p_libp2p,QmWpakcMeK6DGa7BSUtnXK7cAW1shLpK2UjDsKwjFu9jcS fetchai/connections/p2p_libp2p_client,QmU4NArBA92C5GfGUNuXB1zo8kz5DoNuYjM2Zwmu1g6YHp fetchai/connections/p2p_libp2p_mailbox,QmTNxWRQSU6KtuYqBog7WKvYwuTswHJ7XunxtAtbiNTpCv fetchai/connections/p2p_stub,QmQjwk8myY3JgVuwKLnoMb4e6DGeomaBY5ETFxgn45cZZ4 fetchai/connections/prometheus,Qmdb1fEagWSxbwPZsVytdzrQ1xFbKXvo5ZVWUZTxfhtBze fetchai/connections/scaffold,QmYRgd4gLA3CtevU3Rj72Vafu9V6sjk4xRrHu5JosvB7gP fetchai/connections/soef,QmYU9X28XttovDc27mLWZznG1KHNJjgcUxE8rhNfVLkvah fetchai/connections/stub,QmYKqNesPLTz1RiTGTXS5qoZTDnWxsMqP12Dfj93TrT1Yp fetchai/connections/tcp,QmdaPEEGf2oL6uV1CGqPR1Dm2t4mh9nadqg3tU2waZPr97 fetchai/connections/webhook,QmfXrJrSjbX6xw2QpkvZPibdGXmtRAY7mcScTYvUJ9ztvP fetchai/contracts/erc1155,QmYd8y8nccJwdsbrh3Muq3xJZgjpEEWXATWeydoPhvuQ78 fetchai/contracts/fet_erc20,QmPddVorxNKahXJJPAaRFo39AsDkE3bJWerQSDY8iY4zy1 fetchai/contracts/oracle,QmPdNhJTFquUbFigNQFtM3ne2QH24NMVsz1bvuCezg3T1M fetchai/contracts/oracle_client,QmdrkLzEdUFThsLwnhQLmH3gyWG1keB6Fawo2VKLdaYiQQ fetchai/contracts/scaffold,QmVgRzr6yJ3AV2Y6DFxz5EMtTgDmSkeg2b6Cx3H2WkFBHR fetchai/contracts/staking_erc20,Qmf7hfDNUWCBzZij7bWVtNgKGVVodp41oWTDymzpS5oEKj fetchai/protocols/acn,QmNQpXzAGSzu3nBW4sNvnUVrjbxn7x6ZWGoP4ttWyXdrW1 fetchai/protocols/aggregation,QmX3Mrkk2SagPESD6z8ejtUiVP2ZC8j7w4QRfYTGNj8USe fetchai/protocols/contract_api,QmUuKXkDbArLq7URiEjgJy2eN23KciXUsR5f6tV1mdRaYZ fetchai/protocols/cosm_trade,QmTpGR4Svn5d9UBix3RpQfRLgWXXHBmpdw9zmbWK7pvH3i fetchai/protocols/default,QmVTKic2PP7MbaE5Dnfgy3CLZn6WTwcJ3r6sq5dKBkooLL fetchai/protocols/fipa,QmZ4deeTdrcp1tP3UbBDxgSR5jEjsyvRqdN7aiNaNjnD3n fetchai/protocols/gym,QmWdcJWAFNb4eCCHS2T8bzzpzDFgpMMkamM8JY3bvgTUuz fetchai/protocols/http,Qmb5yKV5p7TiNa1X96VMpdA2MZU6exHAEfrJenhUsjUBWX fetchai/protocols/ledger_api,QmVc7nC5T9swaBskuAKQkzzixQVQ6g1gnPpopGYa9f79P1 fetchai/protocols/ml_trade,QmWyjEdkx6Y2H3kctMP7A6oDZsdDZJYFua61K3c4bFZB8K fetchai/protocols/oef_search,QmPZj5Wt7dgL8N2rZT9ThiVe4pehR5sDS6q3j5v9gkSpEQ fetchai/protocols/prometheus,QmTiCY15XgeurxmPrmv6euXfrc3GdeM1JmbFy9Tz698xvt fetchai/protocols/register,QmQN8wjYTeZ6tGW2vM9cCn47QFwDCawcNBcoPR8AYf4Xae fetchai/protocols/scaffold,QmakHDyafqNtxVyV47M17zHyhzf7osfcnzsbfmvsorhydL fetchai/protocols/signing,QmZzbiFHPjhmj4FATsAPTrrnnaCMQQco7XR8NYCwrGi3yo fetchai/protocols/state_update,QmbyYfchCK6QydpsGdxecShLBZfuKNMoPhamW1qXC68PXy fetchai/protocols/tac,QmT2iCyx55dpSFPVoVEarkoosn1o2FcRzfiMPFMs3nybhC fetchai/skills/advanced_data_request,QmQs2Mj7WTmvxCajV8TQSdA2RPMPKLSCvYgybNzBQve1Si fetchai/skills/aries_alice,QmWy7T7twbXd3fsg4rmmxvmaB21pTDdAGp8Eycpbj6JWfj fetchai/skills/aries_faber,QmVZo6pMmLc73W4SGBYrRfwpaqBMHjyG9UGLHh3G2r1QBU fetchai/skills/carpark_client,Qmc9qTrfUkXTrc1RcP9rhZ2tackvHBHm5bR6c6q3D8A1kb fetchai/skills/carpark_detection,QmURfz1pazMoaCjuEtWqaPce5hU2521PwCjyuYwvbdBqUB fetchai/skills/confirmation_aw1,QmeYFKJmTfkzVyjb5VamqNjAVMawuqSSx3mqBT1GZVqU8b fetchai/skills/confirmation_aw2,QmPvHhtunr3UZibEabpRx2XMNUQaWSxtFSmJ99SgbdRj8q fetchai/skills/confirmation_aw3,QmXzUFTYPgJcH2XoFobWxsw3icf7N86gHNSrwcHg3vychV fetchai/skills/echo,QmQQBsarJtA7Eo9dr6hVwz7DziYzxv51zdJeUHjquUGgec fetchai/skills/erc1155_client,QmVKTQc1TDVHJ5ZRvAWhCZFBx3rteUHLir3yNJJ8EsPCmE fetchai/skills/erc1155_deploy,QmV2v5RU6jf55f3zh2bhxWV439fJMjWqWz9zYTXZheSXAY fetchai/skills/error,QmZGZZAuwSCJkZ4atmcUNE3d3q5iErKQ5AZNsjWJnBT1N8 fetchai/skills/error_test_skill,QmV1AH2aEEzKec9mG76wGm1q1BsrZLxwYH7SFtw4ACfeAd fetchai/skills/fetch_block,QmNP6YvsPuNTPB7E2N3dbZ65WDdbCkXrG7WpAJJ9qkbTWG fetchai/skills/fipa_dummy_buyer,QmRnMgmXLJ7ZHRX5jFvUVyXVrQMoZy1f7pAi6wY8NfBttD fetchai/skills/generic_buyer,QmTCCZsrRuS7R1GY6EdJnKvXAfhsxBV6YJT2pyLVsokkyx fetchai/skills/generic_seller,QmZ4HkEwPdad5UXEJ7rcgAvVBUZZ3xSdSqnNGmT6FWtemh fetchai/skills/gym,QmPK9MD8xDYDRZM9mvXKycxoyoUvFUvWPkp1aBjR7T8tM1 fetchai/skills/hello_world,QmUxmB8E9HhQy5At1FGjD4RCELbZFxhxaN97NAgDi7Dx7U fetchai/skills/http_echo,QmfAXHmFQ6CPTZxnt82LWVEyDKC6PdrYGraogcHyL8X9Uf fetchai/skills/ml_data_provider,QmWgTAUEK9nnSDXE49Gunb2QhrbfBnSkaVhXwGjvTxYfCP fetchai/skills/ml_train,QmcWjoaTKSKALwye6guQCFYLw2PNrqmaxMzaUMod9SJSJt fetchai/skills/registration_aw1,QmP4JAhVEnTaQLFUSEE1DCBPpDHt2Mb4KYNmAeBixMqCGv fetchai/skills/scaffold,QmfMLDBLPiBjQmDJHthYm59565NGNKEp9nCNgU4YaMrgSf fetchai/skills/simple_aggregation,QmPQQxmV469eJTuqa7ajPG4LGUiNqQERmHLtpn5NcHd1az fetchai/skills/simple_buyer,QmUGHbgtw1TceTzYmEh4AMT73Fv7xsEuuE4xykpXdkZCcc fetchai/skills/simple_data_request,QmPxN2aDVEUzyjpBCYy4FB8bzDRHV86f4egMgsngPzvHXa fetchai/skills/simple_oracle,QmcckbJGCNffeoQuon7GhSjNkJxaSbmAkRCW6yZk7LQUnf fetchai/skills/simple_oracle_client,Qmb13j9KcdgRTuvEHyQSvRRs5Z2gMtHSxf8wmUCfLK5AKt fetchai/skills/simple_seller,Qmbt3cy9ZZEWZU2m3QwtsutDVsT1wCkXB32hQLQevbTnXo fetchai/skills/simple_service_registration,QmaLvqZDZyz5XaRKjkw4PLTkHpuchUwdiRTne5oajSJc9x fetchai/skills/simple_service_search,QmbhL9rGxpdzk2Va4PeNYjNvMSheN9iEHkqPPJWYpwcp2n fetchai/skills/tac_control,QmcAH9LkQHULdihxe2b3uPXm3AEYUbY3pio6rr22oB4Mb9 fetchai/skills/tac_control_contract,QmWVkwj4gZgmKHEg6iH3cqTNNNUpxWJJ5jkfoBfkE7gUK7 fetchai/skills/tac_negotiation,Qmb4GLTCEU4hfkSdmdJ3NusLMHFv1o5J5P19r3Fwv5xxRB fetchai/skills/tac_participation,QmaWj9n5cpp1nWo3HCwVtJbU6M9zB4Qut4prBxRhNha6cC fetchai/skills/task_test_skill,QmeSJeSZ8d8Do1jL8AbgiLWtnchNShWaJs9ChL9heFT4Po fetchai/skills/thermometer,QmaAyLDL9MaiuFZHnmjoVB42zgVsGxJ8G6eHL8VzwjbJfj fetchai/skills/thermometer_client,QmScE56BE6eMDELi1ntNBbjSkhvvfPC3YGEsyGoCN23sq8 fetchai/skills/weather_client,QmcUvp36Berop9VzuC2T8AD5q2ELx27q5Zvy4jPsPn4Hnk fetchai/skills/weather_station,QmXYnZ4Dahc9xoEJ6yAWyw8Ba5Zu7fsrhR8iDEJ6pJNStz ================================================ FILE: plugins/aea-cli-ipfs/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2019 Fetch.AI Limited Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: plugins/aea-cli-ipfs/MANIFEST.in ================================================ include README.md LICENSE HISTORY.md recursive-include aea_cli_ipfs recursive-include tests * ================================================ FILE: plugins/aea-cli-ipfs/README.md ================================================ # AEA CLI IPFS Plug-in IPFS command to publish and download directories. ## Installation and usage Make sure you have `aea` installed. Then, install the plug-in: ``` bash pip install aea-cli-ipfs ``` Now you should be able to run `aea ipfs`. ``` bash Usage: aea ipfs [OPTIONS] COMMAND [ARGS]... IPFS Commands Options: --help Show this message and exit. Commands: add Add directory to ipfs, if not directory specified the current... download Download directory by it's hash, if not target directory... remove Remove a directory from ipfs by it's hash. Usage: aea ipfs add [OPTIONS] [DIR_PATH] Add directory to ipfs, if not directory specified the current one will be added. Options: -p, --publish --help Show this message and exit. Usage: aea ipfs remove [OPTIONS] hash_id Remove a directory from ipfs by it's hash. Options: --help Show this message and exit. Usage: aea ipfs download [OPTIONS] hash_id [TARGET_DIR] Download directory by it's hash, if not target directory specified will use current one. Options: --help Show this message and exit. ``` ================================================ FILE: plugins/aea-cli-ipfs/aea_cli_ipfs/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """IPFS CLI plug-in for the AEA CLI tool.""" ================================================ FILE: plugins/aea-cli-ipfs/aea_cli_ipfs/core.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Core components for `ipfs cli command`.""" import os import time from contextlib import suppress from typing import Any, Optional import click from aea_cli_ipfs.ipfs_utils import ( DownloadError, IPFSTool, NodeError, PublishError, RemoveError, ) @click.group() @click.pass_context def ipfs(click_context: click.Context) -> None: """IPFS Commands""" ipfs_tool = IPFSTool() click_context.obj = ipfs_tool try: ipfs_tool.chec_ipfs_node_running() except NodeError as e: click.echo("Can not connect to the local ipfs node. Starting own one.") ipfs_tool.daemon.start() for _ in range(10): with suppress(NodeError): # pragma: nocover ipfs_tool.chec_ipfs_node_running() click.echo("ipfs node started.") break time.sleep(1) else: raise click.ClickException( "Failed to connect or start ipfs node! Please check ipfs is installed or launched!" ) from e @ipfs.result_callback() @click.pass_context def process_result(click_context: click.Context, *_: Any) -> None: """Tear down command group.""" ipfs_tool = click_context.obj if ipfs_tool.daemon.is_started(): # pragma: nocover click.echo("Stopping ipfs node launched to execute the command.") ipfs_tool.daemon.stop() click.echo("Daemon stopped.") @ipfs.command() @click.argument( "dir_path", type=click.Path( exists=True, dir_okay=True, file_okay=False, resolve_path=True, readable=True ), required=False, ) @click.option("-p", "--publish", is_flag=True) @click.option("--no-pin", is_flag=True) @click.pass_context def add( click_context: click.Context, dir_path: Optional[str], publish: bool = False, no_pin: bool = False, ) -> None: """Add directory to ipfs, if not directory specified the current one will be added.""" dir_path = dir_path or os.getcwd() ipfs_tool = click_context.obj click.echo(f"Starting processing: {dir_path}") name, hash_, _ = ipfs_tool.add(dir_path, pin=(not no_pin)) click.echo(f"Added: `{name}`, hash is {hash_}") if publish: click.echo("Publishing...") try: response = ipfs_tool.publish(hash_) click.echo(f"Published to {response['Name']}") except PublishError as e: raise click.ClickException(f"Publish failed: {str(e)}") from e @ipfs.command() @click.argument( "hash_", metavar="hash", type=str, required=True, ) @click.pass_context def remove(click_context: click.Context, hash_: str) -> None: """Remove a directory from ipfs by it's hash.""" ipfs_tool = click_context.obj try: ipfs_tool.remove(hash_) click.echo(f"{hash_} was removed successfully") except RemoveError as e: raise click.ClickException(f"Remove error: {str(e)}") from e @ipfs.command() @click.argument( "hash_", metavar="hash", type=str, required=True, ) @click.argument( "target_dir", type=click.Path(dir_okay=True, file_okay=False, resolve_path=True), required=False, ) @click.pass_context def download( click_context: click.Context, hash_: str, target_dir: Optional[str] ) -> None: """Download directory by it's hash, if not target directory specified will use current one.""" target_dir = target_dir or os.getcwd() ipfs_tool = click_context.obj click.echo(f"Download {hash_} to {target_dir}") try: ipfs_tool.download(hash_, target_dir) click.echo("Download complete!") except DownloadError as e: # pragma: nocover raise click.ClickException(str(e)) from e ================================================ FILE: plugins/aea-cli-ipfs/aea_cli_ipfs/ipfs_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Ipfs utils for `ipfs cli command`.""" import os import shutil import signal import subprocess # nosec from pathlib import Path from typing import Dict, List, Optional, Tuple import ipfshttpclient # type: ignore class IPFSDaemon: """ Set up the IPFS daemon. :raises Exception: if IPFS is not installed. """ def __init__(self) -> None: """Initialise IPFS daemon.""" # check we have ipfs self.process = None # type: Optional[subprocess.Popen] def is_started(self) -> bool: """Check daemon was started.""" return bool(self.process) def start(self) -> None: """Run the ipfs daemon.""" self.process = subprocess.Popen( # nosec # pylint: disable=consider-using-with ["ipfs", "daemon"], stdout=subprocess.PIPE, env=os.environ.copy(), ) def stop(self) -> None: # pragma: nocover """Terminate the ipfs daemon.""" if self.process is None: return self.process.send_signal(signal.SIGTERM) self.process.wait(timeout=30) poll = self.process.poll() if poll is None: self.process.terminate() self.process.wait(2) class BaseIPFSToolException(Exception): """Base ipfs tool exception.""" class RemoveError(BaseIPFSToolException): """Exception on remove.""" class PublishError(BaseIPFSToolException): """Exception on publish.""" class NodeError(BaseIPFSToolException): """Exception for node connection check.""" class DownloadError(BaseIPFSToolException): """Exception on download failed.""" class IPFSTool: """IPFS tool to add, publish, remove, download directories.""" def __init__(self, client_options: Optional[Dict] = None): """ Init tool. :param client_options: dict, options for ipfshttpclient instance. """ self.client = ipfshttpclient.Client(**(client_options or {})) self.daemon = IPFSDaemon() def add(self, dir_path: str, pin: bool = True) -> Tuple[str, str, List]: """ Add directory to ipfs. It wraps into directory. :param dir_path: str, path to dir to publish :param pin: bool, pin object or not :return: dir name published, hash, list of items processed """ response = self.client.add( dir_path, pin=pin, recursive=True, wrap_with_directory=True ) return response[-2]["Name"], response[-1]["Hash"], response[:-1] def remove(self, hash_id: str) -> Dict: """ Remove dir added by it's hash. :param hash_id: str. hash of dir to remove :return: dict with unlinked items. """ try: return self.client.pin.rm(hash_id, recursive=True) except ipfshttpclient.exceptions.ErrorResponse as e: raise RemoveError(f"Error on {hash_id} remove: {str(e)}") from e def download(self, hash_id: str, target_dir: str, fix_path: bool = True) -> None: """ Download dir by it's hash. :param hash_id: str. hash of file to download :param target_dir: str. directory to place downloaded :param fix_path: bool. default True. on download don't wrap result in to hash_id directory. """ if not os.path.exists(target_dir): # pragma: nocover os.makedirs(target_dir, exist_ok=True) if os.path.exists(os.path.join(target_dir, hash_id)): # pragma: nocover raise DownloadError(f"{hash_id} was already downloaded to {target_dir}") self.client.get(hash_id, target_dir) downloaded_path = str(Path(target_dir) / hash_id) if fix_path: # self.client.get creates result with hash name # and content, but we want content in the target dir try: for each_file in Path(downloaded_path).iterdir(): # grabs all files shutil.move(str(each_file), target_dir) except shutil.Error as e: # pragma: nocover raise DownloadError(f"error on move files {str(e)}") from e os.rmdir(downloaded_path) def publish(self, hash_id: str) -> Dict: """ Publish directory by it's hash id. :param hash_id: hash of the directory to publish. :return: dict of names it was publish for. """ try: return self.client.name.publish(hash_id) except ipfshttpclient.exceptions.TimeoutError as e: # pragma: nocover raise PublishError( "can not publish within timeout, check internet connection!" ) from e def chec_ipfs_node_running(self) -> None: """Check ipfs node running.""" try: self.client.id() except ipfshttpclient.exceptions.CommunicationError as e: raise NodeError(f"Can not connect to node. Is node running?:\n{e}") from e ================================================ FILE: plugins/aea-cli-ipfs/pyproject.toml ================================================ [build-system] requires = ["setuptools", "wheel"] build_backend = "setuptools.build_meta" ================================================ FILE: plugins/aea-cli-ipfs/setup.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Setup script for the plug-in.""" from setuptools import setup # type: ignore setup( name="aea-cli-ipfs", version="1.0.4", author="Fetch.AI Limited", license="Apache-2.0", description="CLI extension for AEA framework wrapping IPFS functionality.", packages=["aea_cli_ipfs"], entry_points={"aea.cli": ["ipfs_cli_command = aea_cli_ipfs.core:ipfs"]}, install_requires=["aea>=1.0.0, <2.0.0", "ipfshttpclient==0.8.0a2"], classifiers=[ "Environment :: Console", "Environment :: Web Environment", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft", "Operating System :: Unix", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Communications", "Topic :: Internet", "Topic :: Software Development", ], ) ================================================ FILE: plugins/aea-cli-ipfs/tests/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for aea client ipfs plugin.""" ================================================ FILE: plugins/aea-cli-ipfs/tests/test_aea_cli_ipfs.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for aea cli ipfs plugin.""" import os import sys from unittest.mock import patch import click import ipfshttpclient # type: ignore import pytest from click.testing import CliRunner from aea.cli.core import cli sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from aea_cli_ipfs.core import ( # noqa # type: ignore # pylint: disable=wrong-import-position PublishError, ipfs, ) cli.add_command(ipfs) def test_ipfs(): """Test aea ipfs command itself.""" runner = CliRunner() with patch("ipfshttpclient.Client.id"): r = runner.invoke(cli, ["ipfs"], catch_exceptions=False, standalone_mode=False) assert r.exit_code == 0 def test_ipfs_add(): """Test aea ipfs add.""" runner = CliRunner() with patch("ipfshttpclient.Client.name.publish") as ipfs_publish, patch( "ipfshttpclient.Client.id" ) as ipfs_id, patch( "ipfshttpclient.Client.add", return_value=[{"Name": "name", "Hash": "hash"}] * 2 ) as ipfs_add: r = runner.invoke(cli, ["ipfs", "add", "-p"], catch_exceptions=False) assert r.exit_code == 0 ipfs_id.assert_called() ipfs_add.assert_called() ipfs_publish.assert_called() with patch( "ipfshttpclient.Client.name.publish", side_effect=PublishError("oops") ) as ipfs_publish, patch("ipfshttpclient.Client.id") as ipfs_id, patch( "ipfshttpclient.Client.add", return_value=[{"Name": "name", "Hash": "hash"}] * 2 ) as ipfs_add: with pytest.raises(click.ClickException, match="Publish failed.*oops"): runner.invoke( cli, ["ipfs", "add", "-p"], catch_exceptions=False, standalone_mode=False, ) def test_node_not_alive_can_not_be_started(): """Test error on node connection failed""" runner = CliRunner() with patch( "ipfshttpclient.Client.id", side_effect=ipfshttpclient.exceptions.CommunicationError( original=Exception("oops") ), ), patch("time.sleep"), patch("subprocess.Popen"): with pytest.raises( click.ClickException, match="Failed to connect or start ipfs node! Please check ipfs is installed or launched!", ): runner.invoke( cli, ["ipfs", "add", "-p"], catch_exceptions=False, standalone_mode=False, ) @patch("ipfshttpclient.Client.id") def test_ipfs_download(*_): """Test aea ipfs download.""" runner = CliRunner() with patch("ipfshttpclient.Client.get") as ipfs_get, patch("os.rmdir"), patch( "pathlib.Path.iterdir", return_value=[1] ), patch("shutil.move"): r = runner.invoke( cli, ["ipfs", "download", "some_hash"], catch_exceptions=False ) assert r.exit_code == 0 ipfs_get.assert_called() @patch("ipfshttpclient.Client.id") def test_ipfs_remove(*_): """Test aea ipfs remove.""" runner = CliRunner() with patch("ipfshttpclient.Client.pin.rm") as ipfs_rm: r = runner.invoke(cli, ["ipfs", "remove", "some_hash"], catch_exceptions=False) assert r.exit_code == 0 ipfs_rm.assert_called() with patch( "ipfshttpclient.Client.pin.rm", side_effect=ipfshttpclient.exceptions.ErrorResponse( "oops", original=Exception() ), ) as ipfs_rm: with pytest.raises(click.ClickException, match="Remove error:.*oops"): runner.invoke( cli, ["ipfs", "remove", "some_hash"], catch_exceptions=False, standalone_mode=False, ) if __name__ == "__main__": pytest.main([__file__]) ================================================ FILE: plugins/aea-ledger-cosmos/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2019 Fetch.AI Limited Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: plugins/aea-ledger-cosmos/MANIFEST.in ================================================ include README.md LICENSE HISTORY.md recursive-include aea_ledger_cosmos recursive-include tests * ================================================ FILE: plugins/aea-ledger-cosmos/README.md ================================================ # Cosmos crypto plug-in Cosmos crypto plug-in for the AEA framework. ## Install ``` bash python setup.py install ``` ## Run tests ``` bash python setup.py test ``` ================================================ FILE: plugins/aea-ledger-cosmos/aea_ledger_cosmos/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Python package wrapping the public and private key cryptography and ledger api.""" from .cosmos import * # noqa isort:skip from .cosmos import _default_logger, _BYTECODE, _COSMOS, _CosmosApi # noqa isort:skip ================================================ FILE: plugins/aea-ledger-cosmos/aea_ledger_cosmos/cosmos.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Cosmos module wrapping the public and private key cryptography and ledger api.""" import base64 import gzip import hashlib import json import logging import time from collections import namedtuple from itertools import chain from json.decoder import JSONDecodeError from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, cast from Crypto.Cipher import AES # nosec from Crypto.Protocol.KDF import scrypt # nosec from Crypto.Random import get_random_bytes # nosec from bech32 import ( # pylint: disable=wrong-import-order bech32_decode, bech32_encode, convertbits, ) from cosmpy.auth.rest_client import AuthRestClient from cosmpy.bank.rest_client import BankRestClient, QueryBalanceRequest from cosmpy.common.rest_client import RestClient from cosmpy.cosmwasm.rest_client import CosmWasmRestClient from cosmpy.crypto.hashfuncs import ripemd160 from cosmpy.protos.cosmos.auth.v1beta1.auth_pb2 import BaseAccount from cosmpy.protos.cosmos.auth.v1beta1.query_pb2 import QueryAccountRequest from cosmpy.protos.cosmos.bank.v1beta1.tx_pb2 import MsgSend from cosmpy.protos.cosmos.base.v1beta1.coin_pb2 import Coin from cosmpy.protos.cosmos.crypto.secp256k1.keys_pb2 import PubKey as ProtoPubKey from cosmpy.protos.cosmos.tx.signing.v1beta1.signing_pb2 import SignMode from cosmpy.protos.cosmos.tx.v1beta1.service_pb2 import ( BroadcastMode, BroadcastTxRequest, GetTxRequest, ) from cosmpy.protos.cosmos.tx.v1beta1.tx_pb2 import ( AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, Tx, TxBody, ) from cosmpy.protos.cosmwasm.wasm.v1.query_pb2 import QuerySmartContractStateRequest from cosmpy.protos.cosmwasm.wasm.v1.tx_pb2 import ( MsgExecuteContract, MsgInstantiateContract, MsgStoreCode, ) from cosmpy.tx.rest_client import TxRestClient from ecdsa import ( # type: ignore # pylint: disable=wrong-import-order SECP256k1, SigningKey, VerifyingKey, ) from ecdsa.util import ( # type: ignore # pylint: disable=wrong-import-order sigencode_string_canonize, ) from google.protobuf.any_pb2 import Any as ProtoAny from google.protobuf.json_format import MessageToDict, ParseDict from aea.common import Address, JSONLike from aea.crypto.base import Crypto, FaucetApi, Helper, LedgerApi from aea.crypto.helpers import KeyIsIncorrect, hex_to_bytes_for_key from aea.exceptions import AEAEnforceError from aea.helpers import http_requests as requests from aea.helpers.base import try_decorator _default_logger = logging.getLogger(__name__) _COSMOS = "cosmos" TESTNET_NAME = "testnet" DEFAULT_FAUCET_URL = "INVALID_URL" DEFAULT_ADDRESS = "https://cosmos.bigdipper.live" DEFAULT_CURRENCY_DENOM = "uatom" DEFAULT_CHAIN_ID = "cosmoshub-3" DEFAULT_GAS_AMOUNT = 1550000 # Txs will fail if gas_limit is higher than MAXIMUM_GAS_AMOUNT MAXIMUM_GAS_AMOUNT = 2000000 _BYTECODE = "wasm_byte_code" class DataEncrypt: """Class to encrypt/decrypt data strings with password provided.""" @classmethod def _aes_encrypt( cls, password: str, data: bytes ) -> Tuple[bytes, bytes, bytes, bytes]: """ Encryption schema for private keys :param password: plaintext password to use for encryption :param data: plaintext data to encrypt :return: encrypted data, nonce, tag, salt """ key, salt = cls._password_to_key_and_salt(password) cipher = AES.new(key, AES.MODE_EAX) ciphertext, tag = cipher.encrypt_and_digest(data) # type:ignore return ciphertext, cipher.nonce, tag, salt # type:ignore @staticmethod def _password_to_key_and_salt( password: str, salt: Optional[bytes] = None ) -> Tuple[bytes, bytes]: salt = salt or get_random_bytes(16) key = scrypt(password, salt, 16, N=2**14, r=8, p=1) # type: ignore return key, salt # type: ignore @classmethod def _aes_decrypt( cls, password: str, encrypted_data: bytes, nonce: bytes, tag: bytes, salt: bytes ) -> bytes: """ Decryption schema for private keys. :param password: plaintext password used for encryption :param encrypted_data: data to decrypt :param nonce: bytes :param tag: bytes :param salt: bytes :return: decrypted data as plaintext """ # Hash password key, _ = cls._password_to_key_and_salt(password, salt) cipher = AES.new(key, AES.MODE_EAX, nonce) try: decrypted_data = cipher.decrypt_and_verify( # type:ignore encrypted_data, tag ) except ValueError as e: if e.args[0] == "MAC check failed": raise ValueError("Decrypt error! Bad password?") from e raise # pragma: nocover return decrypted_data @classmethod def encrypt(cls, data: bytes, password: str) -> bytes: """Encrypt data with password.""" if not isinstance(data, bytes): # pragma: nocover raise ValueError(f"data has to be bytes! not {type(data)}") encrypted_data, nonce, tag, salt = cls._aes_encrypt(password, data) json_data = { "encrypted_data": cls.bytes_encode(encrypted_data), "nonce": cls.bytes_encode(nonce), "tag": cls.bytes_encode(tag), "salt": cls.bytes_encode(salt), } return json.dumps(json_data).encode() @staticmethod def bytes_encode(data: bytes) -> str: """Encode bytes to ascii friendly string.""" return base64.b64encode(data).decode() @staticmethod def bytes_decode(data: str) -> bytes: """Decode ascii friendly string to bytes.""" return base64.b64decode(data) @classmethod def decrypt(cls, encrypted_data: bytes, password: str) -> bytes: """Decrypt data with password provided.""" if not isinstance(encrypted_data, bytes): # pragma: nocover raise ValueError( f"encrypted_data has to be str! not {type(encrypted_data)}" ) try: json_data = json.loads(encrypted_data) decrypted_data = cls._aes_decrypt( password, encrypted_data=cls.bytes_decode(json_data["encrypted_data"]), nonce=cls.bytes_decode(json_data["nonce"]), tag=cls.bytes_decode(json_data["tag"]), salt=cls.bytes_decode(json_data["salt"]), ) return decrypted_data except (KeyError, JSONDecodeError) as e: raise ValueError(f"Bad encrypted key format!: {str(e)}") from e class CosmosHelper(Helper): """Helper class usable as Mixin for CosmosApi or as standalone class.""" address_prefix = _COSMOS @staticmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool: """ Check whether a transaction is settled or not. :param tx_receipt: the receipt of the transaction. :return: True if the transaction has been settled, False o/w. """ is_successful = False if tx_receipt is not None: code = tx_receipt.get("code", None) is_successful = code is None if not is_successful: _default_logger.warning( # pragma: nocover f"Transaction {tx_receipt.get('txhash')} not settled. Raw log: {tx_receipt.get('rawLog')}" ) return is_successful @classmethod def get_code_id(cls, tx_receipt: JSONLike) -> Optional[int]: """ Retrieve the `code_id` from a transaction receipt. :param tx_receipt: the receipt of the transaction. :return: the code id, if present """ code_id: Optional[int] = None try: attributes = cls.get_event_attributes(tx_receipt) code_id = int(attributes["code_id"]) except (KeyError, IndexError): # pragma: nocover code_id = None return code_id @staticmethod def get_event_attributes(tx_receipt: JSONLike) -> Dict: """ Retrieve events attributes from tx receipt. :param tx_receipt: the receipt of the transaction. :return: dict """ return { i["key"]: i["value"] for i in chain(*[i["attributes"] for i in tx_receipt["logs"][0]["events"]]) # type: ignore } @classmethod def get_contract_address(cls, tx_receipt: JSONLike) -> Optional[str]: """ Retrieve the `contract_address` from a transaction receipt. :param tx_receipt: the receipt of the transaction. :return: the contract address, if present """ contract_address: Optional[str] = None try: attributes = cls.get_event_attributes(tx_receipt) contract_address = attributes["_contract_address"] except (KeyError, IndexError): # pragma: nocover contract_address = None return contract_address @staticmethod def is_transaction_valid( tx: JSONLike, seller: Address, client: Address, tx_nonce: str, amount: int, ) -> bool: """ Check whether a transaction is valid or not. :param tx: the transaction. :param seller: the address of the seller. :param client: the address of the client. :param tx_nonce: the transaction nonce. :param amount: the amount we expect to get from the transaction. :return: True if the random_message is equals to tx['input'] """ if tx is None: return False # pragma: no cover try: _tx = cast(dict, tx.get("tx", {})).get("body", {}).get("messages", [])[0] recovered_amount = int(_tx.get("amount")[0].get("amount")) sender = _tx.get("fromAddress") recipient = _tx.get("toAddress") is_valid = ( recovered_amount == amount and sender == client and recipient == seller ) except (KeyError, IndexError): # pragma: no cover is_valid = False return is_valid @staticmethod def generate_tx_nonce(seller: Address, client: Address) -> str: """ Generate a unique hash to distinguish transactions with the same terms. :param seller: the address of the seller. :param client: the address of the client. :return: return the hash in hex. """ time_stamp = int(time.time()) aggregate_hash = hashlib.sha256( b"".join([seller.encode(), client.encode(), time_stamp.to_bytes(32, "big")]) ) return aggregate_hash.hexdigest() @classmethod def get_address_from_public_key(cls, public_key: str) -> str: """ Get the address from the public key. :param public_key: the public key :return: str """ public_key_bytes = bytes.fromhex(public_key) s = hashlib.new("sha256", public_key_bytes).digest() r = ripemd160(s) five_bit_r = convertbits(r, 8, 5) if five_bit_r is None: # pragma: nocover raise AEAEnforceError("Unsuccessful bech32.convertbits call") address = bech32_encode(cls.address_prefix, five_bit_r) return address @classmethod def recover_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[Address, ...]: """ Recover the addresses from the hash. :param message: the message we expect :param signature: the transaction signature :param is_deprecated_mode: if the deprecated signing was used :return: the recovered addresses """ public_keys = cls.recover_public_keys_from_message(message, signature) addresses = [ cls.get_address_from_public_key(public_key) for public_key in public_keys ] return tuple(addresses) @classmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[str, ...]: """ Get the public key used to produce the `signature` of the `message` :param message: raw bytes used to produce signature :param signature: signature of the message :param is_deprecated_mode: if the deprecated signing was used :return: the recovered public keys """ signature_b64 = base64.b64decode(signature) verifying_keys = VerifyingKey.from_public_key_recovery( signature_b64, message, SECP256k1, hashfunc=hashlib.sha256, ) public_keys = [ verifying_key.to_string("compressed").hex() for verifying_key in verifying_keys ] return tuple(public_keys) @staticmethod def get_hash(message: bytes) -> str: """ Get the hash of a message. :param message: the message to be hashed. :return: the hash of the message. """ digest = hashlib.sha256(message).hexdigest() return digest @classmethod def is_valid_address(cls, address: Address) -> bool: """ Check if the address is valid. :param address: the address to validate :return: whether address is valid or not """ result = bech32_decode(address) return result != (None, None) and result[0] == cls.address_prefix @classmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str]: """ Load contract interface. :param file_path: the file path to the interface :return: the interface """ with open(file_path, "rb") as interface_file_cosmos: contract_interface = { _BYTECODE: str( base64.b64encode( gzip.compress(interface_file_cosmos.read(), 6) ).decode() ) } return contract_interface class CosmosCrypto(Crypto[SigningKey]): """Class wrapping the Account Generation from Ethereum ledger.""" identifier = _COSMOS helper = CosmosHelper def __init__( self, private_key_path: Optional[str] = None, password: Optional[str] = None ) -> None: """ Instantiate an ethereum crypto object. :param private_key_path: the private key path of the agent :param password: the password to encrypt/decrypt the private key. """ super().__init__(private_key_path=private_key_path, password=password) self._public_key = self.entity.get_verifying_key().to_string("compressed").hex() self._address = self.helper.get_address_from_public_key(self.public_key) @property def private_key(self) -> str: """ Return a private key. :return: a private key string """ return self.entity.to_string().hex() @property def public_key(self) -> str: """ Return a public key in hex format. :return: a public key string in hex format """ return self._public_key @property def address(self) -> str: """ Return the address for the key pair. :return: a display_address str """ return self._address @classmethod def load_private_key_from_path( cls, file_name: str, password: Optional[str] = None ) -> SigningKey: """ Load a private key in hex format from a file. :param file_name: the path to the hex file. :param password: the password to encrypt/decrypt the private key. :return: the Entity. """ private_key = cls.load(file_name, password) try: signing_key = SigningKey.from_string( hex_to_bytes_for_key(private_key), curve=SECP256k1 ) except KeyIsIncorrect as e: if not password: raise KeyIsIncorrect( f"Error on key `{file_name}` load! Try to specify `password`: Error: {repr(e)} " ) from e raise KeyIsIncorrect( f"Error on key `{file_name}` load! Wrong password?: Error: {repr(e)} " ) from e return signing_key def sign_message( self, message: bytes, is_deprecated_mode: bool = False, # pylint: disable=unused-argument ) -> str: """ Sign a message in bytes string form. :param message: the message to be signed :param is_deprecated_mode: if the deprecated signing is used :return: signature of the message in string form """ signature_compact = self.entity.sign_deterministic( message, hashfunc=hashlib.sha256, sigencode=sigencode_string_canonize, ) signature_base64_str = base64.b64encode(signature_compact).decode("utf-8") return signature_base64_str def sign_transaction(self, transaction: JSONLike) -> JSONLike: """ Sign a transaction in bytes string form. :param transaction: the transaction to be signed :return: signed transaction """ tx = ParseDict(transaction["tx"], Tx()) # If public key is not already part of transaction if tx.auth_info.signer_infos[0].public_key.value == b"": if len(transaction["sign_data"]) == 1: # type: ignore # Insert public key to auth info from_pub_key_packed = ProtoAny() from_pub_key_pb = ProtoPubKey(key=bytes.fromhex(self.public_key)) from_pub_key_packed.Pack(from_pub_key_pb, type_url_prefix="/") # type: ignore tx.auth_info.signer_infos[ 0 ].public_key.value = from_pub_key_packed.value else: # Fails if public key is not present in transaction with multiple signers raise RuntimeError( "Public key can be added during singing only for single message transactions." ) current_sign_data = transaction["sign_data"][self.address] # type: ignore sd = SignDoc() sd.body_bytes = tx.body.SerializeToString() sd.auth_info_bytes = tx.auth_info.SerializeToString() sd.chain_id = current_sign_data["chain_id"] # type: ignore sd.account_number = current_sign_data["account_number"] # type: ignore data_for_signing = sd.SerializeToString() # Generating signature: signature = base64.b64decode(self.sign_message(data_for_signing)) tx.signatures.extend([signature]) return {"tx": MessageToDict(tx), "sign_data": transaction["sign_data"]} @classmethod def generate_private_key(cls) -> SigningKey: """Generate a key pair for cosmos network.""" signing_key = SigningKey.generate(curve=SECP256k1) return signing_key def encrypt(self, password: str) -> str: """ Encrypt the private key and return in json. :param password: the password to decrypt. :return: json string containing encrypted private key. """ return DataEncrypt.encrypt(self.private_key.encode(), password).decode() @classmethod def decrypt(cls, keyfile_json: str, password: str) -> str: """ Decrypt the private key and return in raw form. :param keyfile_json: json string containing encrypted private key. :param password: the password to decrypt. :return: the raw private key. """ try: return DataEncrypt.decrypt(keyfile_json.encode(), password).decode() except UnicodeDecodeError as e: raise ValueError( "key file data can not be translated to string! bad password?" ) from e class _CosmosApi(LedgerApi): """Class to interact with the Cosmos SDK via a HTTP APIs.""" identifier = _COSMOS def __init__(self, **kwargs: Any) -> None: """Initialize the Cosmos ledger APIs.""" self._api = None self.network_address = kwargs.pop("address", DEFAULT_ADDRESS) self.denom = kwargs.pop("denom", DEFAULT_CURRENCY_DENOM) self.chain_id = kwargs.pop("chain_id", DEFAULT_CHAIN_ID) self.rest_client = RestClient(self.network_address) self.tx_client = TxRestClient(self.rest_client) self.auth_client = AuthRestClient(self.rest_client) self.wasm_client = CosmWasmRestClient(self.rest_client) self.bank_client = BankRestClient(self.rest_client) @property def api(self) -> Any: """Get the underlying API object.""" return self._api def get_balance(self, address: Address) -> Optional[int]: """Get the balance of a given account.""" balance = self._try_get_balance(address) return balance @try_decorator( "Encountered exception when trying get balance: {}", logger_method=_default_logger.warning, ) def _try_get_balance(self, address: Address) -> Optional[int]: res = self.bank_client.Balance( QueryBalanceRequest(address=address, denom=self.denom) ) return int(res.balance.amount) def get_state( self, callable_name: str, *args: Any, **kwargs: Any ) -> Optional[JSONLike]: """ Call a specified function on the ledger API. Based on the cosmos REST API specification, which takes a path (strings separated by '/'). The convention here is to define the root of the path (txs, blocks, etc.) as the callable_name and the rest of the path as args. :param callable_name: name of the callable :param args: positional arguments :param kwargs: keyword arguments :return: the transaction dictionary """ response = self._try_get_state(callable_name, *args, **kwargs) return response @try_decorator( "Encountered exception when trying get state: {}", logger_method=_default_logger.warning, ) def _try_get_state( # pylint: disable=unused-argument self, callable_name: str, *args: Any, **kwargs: Any ) -> Optional[JSONLike]: """Try to call a function on the ledger API.""" result: Optional[JSONLike] = None query = "/".join(args) url = self.network_address + f"/{callable_name}/{query}" response = requests.get(url=url) if response.status_code == 200: result = response.json() else: # pragma: nocover raise ValueError("Cannot get state: {}".format(response.json())) return result def get_deploy_transaction( self, contract_interface: Dict[str, str], deployer_address: Address, **kwargs: Any, ) -> Optional[JSONLike]: """ Get the transaction to deploy the smart contract. Dispatches to _get_storage_transaction and _get_init_transaction based on kwargs. :param contract_interface: the contract interface. :param deployer_address: The address that will deploy the contract. :param kwargs: keyword arguments. :return: the transaction dictionary. """ denom = ( kwargs.pop("denom") if kwargs.get("denom", None) is not None else self.denom ) tx_fee_denom = ( kwargs.pop("tx_fee_denom") if kwargs.get("tx_fee_denom", None) is not None else denom ) chain_id = ( kwargs.pop("chain_id") if kwargs.get("chain_id", None) is not None else self.chain_id ) account_number = kwargs.pop("account_number", None) sequence = kwargs.pop("sequence", None) if account_number is None or sequence is None: account_number, sequence = self._try_get_account_number_and_sequence( deployer_address ) if account_number is None or sequence is None: return None # pragma: nocover label = kwargs.pop("label", None) code_id = kwargs.pop("code_id", None) amount = kwargs.pop("amount", None) init_msg = kwargs.pop("init_msg", None) unexpected_keys = [ key for key in kwargs.keys() if key not in ["tx_fee", "gas", "memo"] ] if len(unexpected_keys) != 0: # pragma: nocover raise ValueError(f"Unexpected keyword arguments: {unexpected_keys}") if label is None and code_id is None and amount is None and init_msg is None: return self._get_storage_transaction( contract_interface, deployer_address, tx_fee_denom, chain_id, account_number, sequence, **kwargs, ) if label is None: raise ValueError( # pragma: nocover "Missing required keyword argument `label` of type `str` for `_get_init_transaction`." ) if code_id is None: raise ValueError( # pragma: nocover "Missing required keyword argument `code_id` of type `int` for `_get_init_transaction`." ) if amount is None: raise ValueError( # pragma: nocover "Missing required keyword argument `amount` of type `int` for `_get_init_transaction`." ) if init_msg is None: raise ValueError( # pragma: nocover "Missing required keyword argument `init_msg` of type `JSONLike` `for `_get_init_transaction`." ) return self._get_init_transaction( deployer_address, denom, chain_id, account_number, sequence, amount, code_id, init_msg, label, tx_fee_denom, **kwargs, ) def _get_storage_transaction( self, contract_interface: Dict[str, str], deployer_address: Address, tx_fee_denom: str, chain_id: str, account_number: int, sequence: int, tx_fee: int = 0, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", ) -> Optional[JSONLike]: """ Create a CosmWasm bytecode deployment transaction. :param contract_interface: the contract interface. :param deployer_address: the deployer address. :param tx_fee_denom: the denomination of tx_fee. :param chain_id: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet). :param account_number: the account number. :param sequence: the sequence number. :param tx_fee: the transaction fee. :param gas: Maximum amount of gas to be used on executing command. :param memo: any string comment. :return: the unsigned CosmWasm contract deploy message """ store_msg = MsgStoreCode( sender=str(deployer_address), wasm_byte_code=base64.b64decode(contract_interface[_BYTECODE]), ) store_msg_packed = ProtoAny() store_msg_packed.Pack(store_msg, type_url_prefix="/") # type: ignore tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(deployer_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[store_msg_packed], ) return tx def _get_init_transaction( self, deployer_address: Address, denom: str, chain_id: str, account_number: int, sequence: int, amount: int, code_id: int, init_msg: JSONLike, label: str, tx_fee_denom: str, tx_fee: int = 0, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", ) -> Optional[JSONLike]: """ Create a CosmWasm InitMsg transaction. :param deployer_address: the deployer address of the message initiator. :param denom: the name of the denomination of the contract funds :param chain_id: the Chain ID of the CosmWasm transaction. :param account_number: the account number of the deployer. :param sequence: the sequence of the deployer. :param amount: Contract's initial funds amount :param code_id: the ID of contract bytecode. :param init_msg: the InitMsg containing parameters for contract constructor. :param label: the label name of the contract. :param tx_fee_denom: Denomination of tx_fee :param tx_fee: the tx fee accepted. :param gas: Maximum amount of gas to be used on executing command. :param memo: any string comment. :return: the unsigned CosmWasm InitMsg """ if amount == 0: init_funds = [] else: init_funds = [Coin(denom=denom, amount=str(amount))] tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] init_msg = MsgInstantiateContract( sender=str(deployer_address), code_id=code_id, msg=json.dumps(init_msg).encode("UTF8"), label=label, funds=init_funds, ) init_msg_packed = ProtoAny() init_msg_packed.Pack(init_msg, type_url_prefix="/") # type: ignore tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(deployer_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[init_msg_packed], ) return tx def get_handle_transaction( self, sender_address: Address, contract_address: Address, handle_msg: Any, amount: int, tx_fee: int, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None, ) -> Optional[JSONLike]: """ Create a CosmWasm HandleMsg transaction. :param sender_address: the sender address of the message initiator. :param contract_address: the address of the smart contract. :param handle_msg: HandleMsg in JSON format. :param amount: Funds amount sent with transaction. :param tx_fee: the tx fee accepted. :param denom: the name of the denomination of the contract funds :param gas: Maximum amount of gas to be used on executing command. :param memo: any string comment. :param chain_id: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet). :param account_number: Account number :param sequence: Sequence :param tx_fee_denom: Denomination of tx_fee, identical with denom param when None :return: the unsigned CosmWasm HandleMsg """ denom = denom if denom is not None else self.denom chain_id = chain_id if chain_id is not None else self.chain_id tx_fee_denom = tx_fee_denom if tx_fee_denom is not None else denom if account_number is None or sequence is None: account_number, sequence = self._try_get_account_number_and_sequence( sender_address ) if account_number is None or sequence is None: return None # pragma: nocover if amount == 0: funds = [] else: funds = [Coin(denom=denom, amount=str(amount))] tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] execute_msg = MsgExecuteContract( sender=str(sender_address), contract=contract_address, msg=json.dumps(handle_msg).encode("UTF8"), funds=funds, ) execute_msg_packed = ProtoAny() execute_msg_packed.Pack(execute_msg, type_url_prefix="/") # type: ignore tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(sender_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[execute_msg_packed], ) return tx def execute_contract_query( self, contract_address: Address, query_msg: JSONLike ) -> Optional[JSONLike]: """ Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing. :param contract_address: the address of the smart contract. :param query_msg: QueryMsg in JSON format. :return: the message receipt """ result = self._try_execute_wasm_query(contract_address, query_msg) return result @try_decorator( "Encountered exception when trying to execute wasm query: {}", logger_method=_default_logger.warning, ) def _try_execute_wasm_query( self, contract_address: Address, query_msg: JSONLike ) -> Optional[JSONLike]: """ Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing. :param contract_address: the address of the smart contract. :param query_msg: QueryMsg in JSON format. :return: the message receipt """ request = QuerySmartContractStateRequest( address=contract_address, query_data=json.dumps(query_msg).encode("UTF8") ) res = self.wasm_client.SmartContractState(request) return json.loads(res.data) def get_transfer_transaction( # pylint: disable=arguments-differ self, sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None, **kwargs: Any, ) -> Optional[JSONLike]: """ Submit a transfer transaction to the ledger. :param sender_address: the sender address of the payer. :param destination_address: the destination address of the payee. :param amount: the amount of wealth to be transferred. :param tx_fee: the transaction fee. :param tx_nonce: verifies the authenticity of the tx :param denom: the denomination of tx fee and amount :param gas: the gas used. :param memo: memo to include in tx. :param chain_id: the chain ID of the transaction. :param account_number: Account number :param sequence: Sequence :param tx_fee_denom: Denomination of tx_fee, identical with denom param when None :param kwargs: keyword arguments. :return: the transfer transaction """ denom = denom if denom is not None else self.denom chain_id = chain_id if chain_id is not None else self.chain_id tx_fee_denom = tx_fee_denom if tx_fee_denom is not None else denom if account_number is None or sequence is None: account_number, sequence = self._try_get_account_number_and_sequence( sender_address ) if account_number is None or sequence is None: return None # pragma: nocover tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] amount_coins = [Coin(denom=denom, amount=str(amount))] msg_send = MsgSend( from_address=str(sender_address), to_address=str(destination_address), amount=amount_coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") # type: ignore tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(sender_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[send_msg_packed], ) return tx def get_packed_exec_msg( self, sender_address: Address, contract_address: str, msg: JSONLike, funds: int = 0, denom: Optional[str] = None, ) -> ProtoAny: """ Create and pack MsgExecuteContract :param sender_address: Address of sender :param contract_address: Address of contract :param msg: Paramaters to be passed to smart contract :param funds: Funds to be sent to smart contract :param denom: the denomination of funds :return: Packed MsgExecuteContract """ denom = denom if denom is not None else self.denom if funds == 0: funds_coins = [] else: funds_coins = [Coin(denom=denom, amount=str(funds))] msg_send = MsgExecuteContract( sender=str(sender_address), contract=contract_address, msg=json.dumps(msg).encode("UTF8"), funds=funds_coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") return send_msg_packed def get_packed_send_msg( self, from_address: Address, to_address: Address, amount: int, denom: Optional[str] = None, ) -> ProtoAny: """ Generate and pack MsgSend :param from_address: Address of sender :param to_address: Address of recipient :param amount: amount of coins to be sent :param denom: the denomination of and amount :return: packer ProtoAny type message """ denom = denom if denom is not None else self.denom amount_coins = [Coin(denom=denom, amount=str(amount))] msg_send = MsgSend( from_address=str(from_address), to_address=str(to_address), amount=amount_coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") return send_msg_packed def get_multi_transaction( self, from_addresses: List[str], pub_keys: Optional[List[bytes]], msgs: List[ProtoAny], gas: int, tx_fee: int = 0, memo: str = "", chain_id: Optional[str] = None, denom: Optional[str] = None, tx_fee_denom: Optional[str] = None, ) -> JSONLike: """ Generate transaction with multiple messages :param from_addresses: Addresses of signers :param pub_keys: Public keys of signers :param msgs: Messages to be included in transaction :param gas: the gas used. :param tx_fee: the transaction fee. :param memo: memo to include in tx. :param chain_id: the chain ID of the transaction. :param denom: the denomination of tx fee :param tx_fee_denom: Denomination of tx_fee, identical with denom param when None :raises: RuntimeError if number of pubkeys is not equal to number of from_addresses :return: the transaction """ if pub_keys is not None and len(pub_keys) != len(from_addresses): raise RuntimeError( "Number of pubkeys is not equal to number of addresses" ) # pragma: nocover denom = denom if denom is not None else self.denom chain_id = chain_id if chain_id is not None else self.chain_id tx_fee_denom = tx_fee_denom if tx_fee_denom is not None else denom tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] account_numbers: List[int] = [] sequences: List[int] = [] for address in from_addresses: account_number, sequence = self._try_get_account_number_and_sequence( address ) account_numbers.append(account_number) sequences.append(sequence) # Prevent requests overflow time.sleep(1) return self._get_transaction( account_numbers=account_numbers, from_addresses=from_addresses, chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=sequences, msgs=msgs, pub_keys=pub_keys, ) @staticmethod def _get_transaction( account_numbers: List[int], from_addresses: List[str], chain_id: str, tx_fee: List[Coin], gas: int, memo: str, sequences: List[int], msgs: List[ProtoAny], pub_keys: Optional[List[bytes]] = None, ) -> JSONLike: """ Get a transaction. :param account_numbers: Account numbers for each signer. :param from_addresses: Addresses of each sender :param chain_id: the chain ID of the transaction. :param tx_fee: the transaction fee. :param gas: the gas used. :param memo: memo to include in tx. :param sequences: Sequence for each sender. :param msgs: Messages to be part of transaction. :param pub_keys: Public keys of each sender :raises: RuntimeError :return: the transaction """ # Txs will fail if gas is higher than MAXIMUM_GAS_AMOUNT if gas > MAXIMUM_GAS_AMOUNT: _default_logger.warning( f"Gas limit {gas} is above maximum gas limit {MAXIMUM_GAS_AMOUNT}. Gas limit was truncated to maximum." ) gas = MAXIMUM_GAS_AMOUNT # Checks if pub_keys is None: if len(from_addresses) == 1: pub_keys = [b""] else: # In case when pubkey is inserted during signing would make second signer to change tx and make the first signature invalid raise RuntimeError( "Only transaction with one signer can be generated without pubkeys" ) if len(account_numbers) != len(from_addresses) or len(from_addresses) != len( sequences ): raise RuntimeError( "Amount of provided from_addresses, sequences and account_numbers is not equal" ) # Get account and signer info for each sender signer_infos: List[SignerInfo] = [] sign_data: JSONLike = {} for from_address, pub_key, sequence, account_number in zip( from_addresses, pub_keys, sequences, account_numbers ): from_pub_key_packed = ProtoAny() from_pub_key_pb = ProtoPubKey(key=pub_key) from_pub_key_packed.Pack(from_pub_key_pb, type_url_prefix="/") # type: ignore # Prepare auth info single = ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT) mode_info = ModeInfo(single=single) signer_info = SignerInfo( public_key=from_pub_key_packed, mode_info=mode_info, sequence=sequence, ) signer_infos.append(signer_info) sign_data[from_address] = { "account_number": account_number, "chain_id": chain_id, } # Prepare auth info auth_info = AuthInfo( signer_infos=signer_infos, fee=Fee(amount=tx_fee, gas_limit=gas), ) # Prepare Tx body tx_body = TxBody() tx_body.memo = memo tx_body.messages.extend(msgs) # Prepare Tx tx = Tx(body=tx_body, auth_info=auth_info) return {"tx": MessageToDict(tx), "sign_data": sign_data} @try_decorator( "Encountered exception when trying to get account number and sequence: {}", logger_method=_default_logger.warning, ) def _try_get_account_number_and_sequence( self, address: Address ) -> Tuple[Optional[int], Optional[int]]: """ Try get account number and sequence for an address. :param address: the address :return: a tuple of account number and sequence """ account_response = self.auth_client.Account( QueryAccountRequest(address=address) ) account = BaseAccount() if account_response.account.Is(BaseAccount.DESCRIPTOR): account_response.account.Unpack(account) else: raise TypeError("Unexpected account type") # pragma: nocover return account.account_number, account.sequence def send_signed_transaction(self, tx_signed: JSONLike) -> Optional[str]: """ Send a signed transaction and wait for confirmation. :param tx_signed: the signed transaction :return: tx_digest, if present """ tx = ParseDict(tx_signed["tx"], Tx()) tx_data = tx.SerializeToString() broad_tx_req = BroadcastTxRequest( tx_bytes=tx_data, mode=BroadcastMode.BROADCAST_MODE_SYNC ) broad_tx_resp = self.tx_client.BroadcastTx(broad_tx_req) if broad_tx_resp.tx_response.code != 0: raw_log = broad_tx_resp.tx_response.raw_log _default_logger.warning( f"Sending transaction failed: {raw_log} {broad_tx_resp}" ) tx_digest = None else: tx_digest = broad_tx_resp.tx_response.txhash return tx_digest def get_transaction_receipt(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction receipt for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ tx_with_receipt = self._try_get_transaction_with_receipt(tx_digest) if tx_with_receipt is None: return None return tx_with_receipt.get("txResponse") @try_decorator( "Encountered exception when trying to get transaction receipt: {}", logger_method=_default_logger.warning, ) def _try_get_transaction_with_receipt(self, tx_digest: str) -> Optional[JSONLike]: """ Try get the transaction receipt for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ tx_request = GetTxRequest(hash=tx_digest) tx_response = self.tx_client.GetTx(tx_request) return MessageToDict(tx_response) def get_transaction(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx, if present """ # Cosmos does not distinguish between transaction receipt and transaction tx_with_receipt = self._try_get_transaction_with_receipt(tx_digest) if tx_with_receipt is None: return None # pragma: nocover return {"tx": tx_with_receipt.get("tx")} def get_contract_instance( self, contract_interface: Dict[str, str], contract_address: Optional[str] = None ) -> Any: """ Get the instance of a contract. :param contract_interface: the contract interface. :param contract_address: the contract address. :return: the contract instance """ # Instance object not available for cosmwasm return None def update_with_gas_estimate(self, transaction: JSONLike) -> JSONLike: """ Attempts to update the transaction with a gas estimate :param transaction: the transaction :raises: NotImplementedError """ raise NotImplementedError( # pragma: nocover "No gas estimation has been implemented." ) class CosmosApi(_CosmosApi, CosmosHelper): """Class to interact with the Cosmos SDK via a HTTP APIs.""" """ Equivalent to: @dataclass class CosmosFaucetStatus: tx_digest: Optional[str] status: str """ CosmosFaucetStatus = namedtuple("CosmosFaucetStatus", ["tx_digest", "status"]) class CosmosFaucetApi(FaucetApi): """Cosmos testnet faucet API.""" FAUCET_STATUS_PENDING = "pending" # noqa: F841 FAUCET_STATUS_PROCESSING = "processing" # noqa: F841 FAUCET_STATUS_COMPLETED = "complete" # noqa: F841 FAUCET_STATUS_FAILED = "failed" # noqa: F841 identifier = _COSMOS testnet_faucet_url = DEFAULT_FAUCET_URL testnet_name = TESTNET_NAME max_retry_attempts = 15 def __init__( self, poll_interval: Optional[float] = None, final_wait_interval: Optional[float] = None, ): """Initialize CosmosFaucetApi.""" self._poll_interval = float(poll_interval or 2) self._final_wait_interval = float(final_wait_interval or 5) def get_wealth(self, address: Address, url: Optional[str] = None) -> None: """ Get wealth from the faucet for the provided address. :param address: the address. :param url: the url :raises: RuntimeError of explicit faucet failures """ uid = self._try_create_faucet_claim(address, url) if uid is None: # pragma: nocover raise RuntimeError("Unable to create faucet claim") retry_attempts = self.max_retry_attempts while retry_attempts > 0: retry_attempts -= 1 # lookup status form the claim uid status = self._try_check_faucet_claim(uid, url) if status is None: # pragma: nocover raise RuntimeError("Failed to check faucet claim status") # if the status is complete if status.status == self.FAUCET_STATUS_COMPLETED: break # if the status is failure if status.status not in ( self.FAUCET_STATUS_PENDING, self.FAUCET_STATUS_PROCESSING, ): # pragma: nocover raise RuntimeError(f"Failed to get wealth for {address}") # if the status is incomplete time.sleep(self._poll_interval) if retry_attempts == 0: raise ValueError("Faucet claim check timed out!") # pragma: nocover # Wait to ensure that balance is increased on chain time.sleep(self._final_wait_interval) @classmethod @try_decorator( "An error occured while attempting to request a faucet request:\n{}", logger_method=_default_logger.error, ) def _try_create_faucet_claim( cls, address: Address, url: Optional[str] = None ) -> Optional[str]: """ Create a token faucet claim request :param address: the address to request funds :param url: the url :return: None on failure, otherwise the request uid """ uri = cls._faucet_request_uri(url) response = requests.post(url=uri, json={"address": address}) uid = None if response.status_code == 200: try: uid = response.json()["uuid"] except KeyError: # pragma: nocover ValueError(f"key `uid` not found in response_json={response.json()}") _default_logger.info("Wealth claim generated, uid: {}".format(uid)) else: # pragma: no cover _default_logger.warning( "Response: {}, Text: {}".format(response.status_code, response.text) ) return uid @classmethod @try_decorator( "An error occured while attempting to request a faucet request:\n{}", logger_method=_default_logger.error, ) def _try_check_faucet_claim( cls, uid: str, url: Optional[str] = None ) -> Optional[CosmosFaucetStatus]: """ Check the status of a faucet request :param uid: The request uid to be checked :param url: the url :return: None on failure otherwise a CosmosFaucetStatus for the specified uid """ response = requests.get(cls._faucet_status_uri(uid, url)) if response.status_code != 200: # pragma: nocover _default_logger.warning( "Response: {}, Text: {}".format(response.status_code, response.text) ) return None # parse the response data = response.json() tx_digest = None if "txStatus" in data["claim"]: tx_digest = data["claim"]["txStatus"]["hash"] return CosmosFaucetStatus( tx_digest=tx_digest, status=data["claim"]["status"], ) @classmethod def _faucet_request_uri(cls, url: Optional[str] = None) -> str: """ Generates the request URI derived from `cls.faucet_base_url` or provided url. :param url: the url :return: the faucet request uri """ if cls.testnet_faucet_url is None: # pragma: nocover raise ValueError("Testnet faucet url not set.") url = cls.testnet_faucet_url if url is None else url return f"{url}/api/v3/claims" @classmethod def _faucet_status_uri(cls, uid: str, url: Optional[str] = None) -> str: """Generates the status URI derived from `cls.faucet_base_url`.""" return f"{cls._faucet_request_uri(url)}/{uid}" ================================================ FILE: plugins/aea-ledger-cosmos/pyproject.toml ================================================ [build-system] requires = ["setuptools", "wheel"] build_backend = "setuptools.build_meta" ================================================ FILE: plugins/aea-ledger-cosmos/setup.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Setup script for "aea_ledger_cosmos" package.""" from setuptools import find_packages, setup setup( name="aea-ledger-cosmos", version="1.2.5", author="Fetch.AI Limited", license="Apache-2.0", description="Python package wrapping the public and private key cryptography and ledger api of Cosmos.", packages=find_packages(include=["aea_ledger_cosmos*"]), install_requires=[ "aea>=1.0.0, <2.0.0", "ecdsa>=0.15,<0.17.0", "bech32==1.2.0", "pycryptodome>=3.10.1,<4.0.0", "cosmpy>=0.6.2,<0.7.0", ], tests_require=["pytest"], entry_points={ "aea.cryptos": ["cosmos = aea_ledger_cosmos:CosmosCrypto"], "aea.ledger_apis": ["cosmos = aea_ledger_cosmos:CosmosApi"], "aea.faucet_apis": ["cosmos = aea_ledger_cosmos:CosmosFaucetApi"], }, classifiers=[ "Environment :: Console", "Environment :: Web Environment", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft", "Operating System :: Unix", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Communications", "Topic :: Internet", "Topic :: Software Development", ], ) ================================================ FILE: plugins/aea-ledger-cosmos/tests/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for the aea_ledger_cosmos package.""" ================================================ FILE: plugins/aea-ledger-cosmos/tests/conftest.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Conftest module for Pytest.""" import inspect import os from aea_ledger_cosmos import CosmosCrypto CUR_PATH = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore ROOT_DIR = os.path.join(CUR_PATH, "..") MAX_FLAKY_RERUNS = 3 COSMOS = CosmosCrypto.identifier COSMOS_DEFAULT_ADDRESS = "INVALID_URL" COSMOS_DEFAULT_CURRENCY_DENOM = "INVALID_CURRENCY_DENOM" COSMOS_DEFAULT_CHAIN_ID = "INVALID_CHAIN_ID" COSMOS_TESTNET_CONFIG = {"address": COSMOS_DEFAULT_ADDRESS} ================================================ FILE: plugins/aea-ledger-cosmos/tests/data/cosmos_private_key.txt ================================================ 81b0352f99a08a754b56e529dda965c4ce974edb6db7e90035e01ed193e1b7bc ================================================ FILE: plugins/aea-ledger-cosmos/tests/data/dummy_contract/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" ================================================ FILE: plugins/aea-ledger-cosmos/tests/data/dummy_contract/build/some.json ================================================ { "contractName": "erc1155", "abi": [ { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event" }, { "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor" }, { "name": "getAddress", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_addr" } ], "constant": true, "payable": false, "type": "function", "gas": 370 }, { "name": "getHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 11446 }, { "name": "getSingleHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 2041 }, { "name": "supportsInterface", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "bytes32", "name": "_interfaceID" } ], "constant": true, "payable": false, "type": "function", "gas": 868 }, { "name": "is_nonce_used", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "addr" }, { "type": "uint256", "name": "nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 1052 }, { "name": "is_token_id_exists", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "uint256", "name": "token_id" } ], "constant": true, "payable": false, "type": "function", "gas": 928 }, { "name": "safeTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_value" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 80803 }, { "name": "safeBatchTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_values" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 748184 }, { "name": "balanceOf", "outputs": [ { "type": "uint256", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "uint256", "name": "_id" } ], "constant": true, "payable": false, "type": "function", "gas": 1172 }, { "name": "balanceOfBatch", "outputs": [ { "type": "uint256[10]", "name": "out" } ], "inputs": [ { "type": "address[10]", "name": "_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": true, "payable": false, "type": "function", "gas": 7524 }, { "name": "setApprovalForAll", "outputs": [], "inputs": [ { "type": "address", "name": "_operator" }, { "type": "bool", "name": "_approved" } ], "constant": false, "payable": false, "type": "function", "gas": 38136 }, { "name": "isApprovedForAll", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "address", "name": "_operator" } ], "constant": true, "payable": false, "type": "function", "gas": 1301 }, { "name": "createSingle", "outputs": [], "inputs": [ { "type": "address", "name": "_item_owner" }, { "type": "uint256", "name": "_id" }, { "type": "string", "name": "_path" } ], "constant": false, "payable": false, "type": "function", "gas": 597461 }, { "name": "createBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_items_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": false, "payable": false, "type": "function", "gas": 927926 }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "burn", "outputs": [], "inputs": [ { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function", "gas": 40353 }, { "name": "burnBatch", "outputs": [], "inputs": [ { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function", "gas": 382149 }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "owner", "outputs": [ { "type": "address", "name": "out" } ], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1263 } ], "bytecode": "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6000600155600160006301ffc9a760e05260c052604060c020556001600063d9b67a2660e05260c052604060c020553360025561405a56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd5b6100d061405a036100d06000396100d061405a036000f3", "deployedBytecode": "0x600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd", "source": "# Author: Sören Steiger, github.com/ssteiger\n# Author: Fetch.ai, github.com/fetchai\n# License: MIT\n\n# ERC1155 Token Standard\n# https://eips.ethereum.org/EIPS/eip-1155\n\n########################EXTERNAL-CONTRACTS####################################\n\ncontract ERC1155TokenReceiver:\n # Note: The ERC-165 identifier for this interface is 0x4e2312e0.\n\n def onERC1155Received(_operator: address, _from: address, _id: uint256, _value: uint256,\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of a single ERC1155 token type.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))` (i.e. 0xf23a6e61) if it accepts the transfer.\n # This function MUST revert if it rejects the transfer.\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _id The ID of the token being transferred\n # @param _value The amount of tokens being transferred\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))`\n # \"\"\"\n\n def onERC1155BatchReceived(_operator: address, _from: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE],\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of multiple ERC1155 token types.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))` (i.e. 0xbc197c81) if it accepts the transfer(s).\n # This function MUST revert if it rejects the transfer(s).\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the batch transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _ids An array containing ids of each token being transferred (order and length must match _values array)\n # @param _values An array containing amounts of each token being transferred (order and length must match _ids array)\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))`\n # \"\"\"\n\n########################END-EXTERNAL-CONTRACTS####################################\n########################EVENTS####################################\n\nMAX_URI_SIZE: constant(uint256) = 1024\n\nTransferSingle: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address), _id: uint256,\n _value: uint256})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_id` argument MUST be the token type being transferred.\n# The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nTransferBatch: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address),\n _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE]})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_ids` argument MUST be the list of tokens being transferred.\n# The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nApprovalForAll: event({_owner: indexed(address), _operator: indexed(address), _approved: bool})\n# @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled).\n\n\nURI: event({_value: string[MAX_URI_SIZE], _id: indexed(uint256)})\n# @dev MUST emit when the URI is updated for a token ID.\n# URIs are defined in RFC 3986.\n# The URI MUST point to a JSON file that conforms to the \"ERC-1155 Metadata URI JSON Schema\".\n\n########################END-EVENTS####################################\n########################INITIALIZATION####################################\n\nsupportedInterfaces: map(bytes32, bool)\n# https://eips.ethereum.org/EIPS/eip-165\nERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7\nERC1155_INTERFACE_ID: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26\ntokensIdCount: uint256\nowner: public(address)\n\nbalancesOf: map(address, map(uint256, uint256))\nnoncesOf: map(address, map(uint256, bool))\nuri: map(uint256, string[256])\noperators: map(address, map(address, bool))\ntoken_ids: map(uint256, bool)\n\n# This is to be set before contract migration!\nBATCH_SIZE: constant(uint256) = 10\n\n\n@public\ndef __init__():\n \"\"\"\n @notice Called once and only upon contract deployment.\n \"\"\"\n self.tokensIdCount = convert(0, uint256)\n self.supportedInterfaces[ERC165_INTERFACE_ID] = True\n self.supportedInterfaces[ERC1155_INTERFACE_ID] = True\n self.owner = msg.sender\n\n########################END-INITIALIZATION####################################\n########################PRIVATE-FUNCTIONS####################################\n\n\n######### THIS IS A TEMPORARY SOLUTION #################\n@public\n@constant\ndef getAddress(_addr: address) -> bytes32:\n hash: bytes32 = convert(_addr, bytes32)\n return hash\n##################### END ##############################\n\n@private\n@constant\ndef _getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n aggregate_hash: bytes32 = keccak256(concat(convert(_ids[0], bytes32), convert(_from_supplies[0], bytes32), convert(_to_supplies[0], bytes32)))\n for i in range(BATCH_SIZE):\n if not i == 0:\n aggregate_hash = keccak256(concat(aggregate_hash, convert(_ids[i], bytes32), convert(_from_supplies[i], bytes32), convert(_to_supplies[i], bytes32)))\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n aggregate_hash,\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n\n@private\n@constant\ndef getHashOld(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_ids[0], bytes32),\n convert(_ids[1], bytes32),\n convert(_ids[2], bytes32),\n convert(_ids[3], bytes32),\n convert(_ids[4], bytes32),\n convert(_ids[5], bytes32),\n convert(_ids[6], bytes32),\n convert(_ids[7], bytes32),\n convert(_ids[8], bytes32),\n convert(_ids[9], bytes32),\n convert(_from_supplies[0], bytes32),\n convert(_from_supplies[1], bytes32),\n convert(_from_supplies[2], bytes32),\n convert(_from_supplies[3], bytes32),\n convert(_from_supplies[4], bytes32),\n convert(_from_supplies[5], bytes32),\n convert(_from_supplies[6], bytes32),\n convert(_from_supplies[7], bytes32),\n convert(_from_supplies[8], bytes32),\n convert(_from_supplies[9], bytes32),\n convert(_to_supplies[0], bytes32),\n convert(_to_supplies[1], bytes32),\n convert(_to_supplies[2], bytes32),\n convert(_to_supplies[3], bytes32),\n convert(_to_supplies[4], bytes32),\n convert(_to_supplies[5], bytes32),\n convert(_to_supplies[6], bytes32),\n convert(_to_supplies[7], bytes32),\n convert(_to_supplies[8], bytes32),\n convert(_to_supplies[9], bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@private\n@constant\ndef _getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_id, bytes32),\n convert(_from_supply, bytes32),\n convert(_to_supply, bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n\n@private\n@constant\ndef ecrecoverSig(_hash: bytes32, _sig: bytes[65]) -> address:\n \"\"\"\n @notice Check whether the the signature matches the hash.\n @param _hash The hash to be checked.\n @param _sig The signature which is meant to match the hash.\n @return the address which signed the signature or the zero address\n \"\"\"\n if len(_sig) != 65:\n return ZERO_ADDRESS\n # ref. https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d\n # The signature format is a compact form of:\n # {bytes32 r}{bytes32 s}{uint8 v}\n r: bytes32 = extract32(_sig, 0, type=bytes32)\n s: bytes32 = extract32(_sig, 32, type=bytes32)\n v: int128 = convert(slice(_sig, start=64, len=1), int128)\n # Version of signature should be 27 or 28, but 0 and 1 are also possible versions.\n # geth uses [0, 1] and some clients have followed. This might change, see:\n # https://github.com/ethereum/go-ethereum/issues/2053\n if v < 27:\n v += 27\n if v in [27, 28]:\n return ecrecover(_hash, convert(v, uint256), convert(r, uint256), convert(s, uint256))\n return ZERO_ADDRESS\n\n\n@private\n@constant\ndef decode_id(id: uint256) -> int128:\n \"\"\"\n @notice Decodes the id of the token inorder to find out if it NFT or FT.\n @param id: uint256\n @return token_id : int128 (Specified id for FT and NFT.)\n @dev shift(x, -y): returns x with the bits shifted to the right by y places, which is equivalent to dividing x by 2**y.\n \"\"\"\n decoded_token_id: int128 = convert(shift(id, -128), int128)\n decoded_index: int128 = convert(id % 2 ** 128, int128)\n return decoded_token_id\n\n########################END-PRIVATE-FUNCTIONS################################\n########################PUBLIC-FUNCTIONS#####################################\n\n@public\n@constant\ndef supportsInterface(_interfaceID: bytes32) -> bool:\n \"\"\"\n @notice Check whether the interface id is supported.\n @param _interfaceID The interface id\n @return True if the interface id is supported.\n \"\"\"\n return self.supportedInterfaces[_interfaceID]\n\n@public\n@constant\ndef is_nonce_used(addr: address, nonce: uint256) -> bool:\n \"\"\"\n @notice Checks if the given nonce for the give address is unused.\n @param nonce: uint256 the counter of the transaction\n @param address: the address that want to transact.\n \"\"\"\n return self.noncesOf[addr][nonce]\n\n@public\n@constant\ndef is_token_id_exists(token_id: uint256) -> bool:\n \"\"\"\n @notice Checks if the given token_id is already created.\n @param token_id: uint256 the id of the token.\n \"\"\"\n return self.token_ids[token_id]\n\n@public\ndef safeTransferFrom(_from: address, _to: address, _id: uint256, _value: uint256, _data: bytes[256]):\n \"\"\"\n @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if balance of holder for token `_id` is lower than the `_value` sent.\n MUST revert on any other error.\n MUST emit the `TransferSingle` event to reflect the balance change (see \"Safe Transfer Rules\" section of the standard).\n After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _id ID of the token type\n @param _value Transfer amount\n @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n assert self.balancesOf[_from][_id] >= _value, \"Not enough tokens.\"\n\n self.balancesOf[_from][_id] -= _value\n self.balancesOf[_to][_id] += _value\n\n log.TransferSingle(msg.sender, _from, _to, _id, _value)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef safeBatchTransferFrom(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE], _data: bytes[256]):\n \"\"\"\n @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if length of `_ids` is not the same as length of `_values`.\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _ids IDs of each token type (order and length must match _values array)\n @param _values Transfer amounts per token type (order and length must match _ids array)\n @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[_from][id] >= _values[i]\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _values)\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_from][id] -= _values[i]\n self.balancesOf[_to][id] += _values[i]\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _values, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256[BATCH_SIZE],uint256[BATCH_SIZE],bytes)\", bytes32)\n\n\n@public\n@constant\ndef balanceOf(_owner: address, _id: uint256) -> uint256:\n \"\"\"\n @notice Get the balance of an account's tokens.\n @param _owner The address of the token holder\n @param _id ID of the token\n @return The _owner's balance of the token type requested\n \"\"\"\n return self.balancesOf[_owner][_id]\n\n\n@public\n@constant\ndef balanceOfBatch( _owner: address[BATCH_SIZE], _ids: uint256[BATCH_SIZE]) -> uint256[BATCH_SIZE]:\n \"\"\"\n @notice Get the balance of multiple account/token pairs\n @param _owners The addresses of the token holders\n @param _ids ID of the tokens\n @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair)\n \"\"\"\n returnBalances: uint256[BATCH_SIZE]\n for i in range(BATCH_SIZE):\n returnBalances[i] = self.balancesOf[_owner[i]][_ids[i]]\n return returnBalances\n\n\n@public\ndef setApprovalForAll(_operator: address, _approved: bool):\n \"\"\"\n @notice Enable or disable approval for a third party (\"operator\") to manage all of the caller's tokens.\n @dev MUST emit the ApprovalForAll event on success.\n @param _operator Address to add to the set of authorized operators\n @param _approved True if the operator is approved, false to revoke approval\n @return None\n \"\"\"\n (self.operators[msg.sender])[_operator] = _approved\n log.ApprovalForAll(msg.sender, _operator, _approved)\n\n\n@public\n@constant\ndef isApprovedForAll(_owner: address, _operator: address) -> bool:\n \"\"\"\n @notice Queries the approval status of an operator for a given owner.\n @param _owner The owner of the tokens.\n @param _operator Address of authorized operator.\n @return True if the operator is approved, false if not\n \"\"\"\n return (self.operators[_owner])[_operator]\n\n\n@public\ndef createSingle(_item_owner: address, _id: uint256, _path: string[256]):\n \"\"\"\n @notice Create a new token type that we can mint later.\n @param _item_owner The owner of the item.\n @param _id The id of the token.\n @param _path The path to the token data.\n @return None\n \"\"\"\n assert _item_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create item.\"\n self.balancesOf[_item_owner][_id] = 0\n self.tokensIdCount += 1\n self.token_ids[_id] = True\n self.uri[_id] = _path\n log.URI(_path, _id)\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _item_owner, _id, 0)\n\n\n@public\ndef createBatch(_items_owner: address, _ids: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Create new token types that we can mint later.\n @param _items_owner The owner of the items.\n @param _ids The ids of the tokens.\n @return None\n \"\"\"\n assert _items_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create items.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_items_owner][id] = 0\n self.tokensIdCount += 1\n self.token_ids[id] = True\n zero_supply: uint256[BATCH_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _items_owner, _ids, zero_supply)\n\n\n@public\ndef mint(_to: address, _id: uint256, _supply: uint256, _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a token.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _id The id of the token.\n @param _supply The supply to be minted for the token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n decoded_id: int128 = self.decode_id(_id)\n assert decoded_id == 1 or decoded_id == 2\n if decoded_id == 1 :\n assert _supply == 1, \"Cannot mint NFT with _supply more than 1\"\n self.balancesOf[_to][_id] = _supply\n\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, _id, _supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _id, _supply, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef mintBatch(_to: address, _ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a batch of tokens.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _supplies The supply to be minted for each token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n decoded_id: int128 = self.decode_id(id)\n assert decoded_id == 1 or decoded_id == 2\n\n if decoded_id == 1 :\n assert _supplies[i] == 1\n\n self.balancesOf[_to][id] = _supplies[i]\n\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, _ids, _supplies)\n\n for i in range(BATCH_SIZE):\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _ids[i], _supplies[i], _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef burn(_id: uint256, _supply: uint256):\n \"\"\"\n @notice Burns the supply of the specified token.\n @param _id The id of the token\n @param _supply Supply to be burned\n @return None\n \"\"\"\n assert self.balancesOf[msg.sender][_id] >= _supply, \"Not enough tokens to burn.\"\n self.balancesOf[msg.sender][_id] -= _supply\n log.TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, _id, _supply)\n\n\n@public\ndef burnBatch(_ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Burns the supply of the specified tokens.\n @dev At this point anyone can burn items if they own it.\n @param _ids The ids of the token\n @param _supplies Supplies to be burned\n @return None\n \"\"\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[msg.sender][id] >= _supplies[i]\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[msg.sender][id] -= _supplies[i]\n log.TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, _ids, _supplies)\n\n\n@public\n@payable\ndef tradeBatch(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supplies[i] > 0 and _to_supplies[i] > 0\n MUST revert if len(_ids) != len(_from_supplies) != len(_to_supplies)\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `positive and negative values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n\n assert _from == msg.sender or (self.operators[_from])[msg.sender], \"_from must be the sender or approved address\"\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n assert _to_supplies[i] == 0\n assert self.balancesOf[_from][id] >= _from_supplies[i]\n else:\n assert _from_supplies[i] == 0\n assert self.balancesOf[_to][id] >= _to_supplies[i]\n\n # Create hash from variables.\n hash: bytes32 = self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n self.balancesOf[_from][id] -= _from_supplies[i]\n self.balancesOf[_to][id] += _from_supplies[i]\n else:\n self.balancesOf[_from][id] += _to_supplies[i]\n self.balancesOf[_to][id] -= _to_supplies[i]\n\n send(_to, msg.value)\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _from_supplies)\n log.TransferBatch(msg.sender, _to, _from, _ids, _to_supplies)\n\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _to, _ids, _from_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155BatchReceived(msg.sender, _from, _ids, _to_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n\n@public\n@payable\ndef trade(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supply > 0 and _to_supply > 0\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_id` is lower than the respective amount in `positive or negative value` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The from address (seller of eth, potential receiver of tokens).\n @param _to The receiver address (receiver of tokens).\n @param _id The id of the token\n @param _from_supply The change in value of token (for _from)\n @param _to_supply The change in value of token (for _to)\n @param _value_eth The value of the ETH sent to the _from address.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n if _from_supply > 0:\n assert _to_supply == 0\n assert self.balancesOf[_from][_id] >= _from_supply\n else:\n assert _from_supply == 0\n assert self.balancesOf[_to][_id] >= _to_supply\n\n # Create hash from variables.\n hash: bytes32 = self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n if _from_supply > 0:\n self.balancesOf[_from][_id] -= _from_supply\n self.balancesOf[_to][_id] += _from_supply\n else:\n self.balancesOf[_from][_id] += _to_supply\n self.balancesOf[_to][_id] -= _to_supply\n\n send(_to, msg.value)\n\n log.TransferSingle(msg.sender, _from, _to, _id, _from_supply)\n log.TransferSingle(msg.sender, _to, _from, _id, _to_supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _to, _id, _from_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155Received(msg.sender, _from, _id, _to_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n", "sourcePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/erc1155.vy", "compiler": { "name": "vyper", "version": "0.1.0b12+commit.a01cdc8" }, "networks": { "1583918911727": { "events": { "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" }, "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8": { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8" }, "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31" }, "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event", "signature": "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b" } }, "links": {}, "address": "0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7", "transactionHash": "0x816b272ebd644b189a3addfbd429b0ea0fa7b2403cca3aa038bcd59f09d9c116" } }, "schemaVersion": "3.0.19", "updatedAt": "2020-03-11T13:47:51.885Z", "networkType": "ethereum" } ================================================ FILE: plugins/aea-ledger-cosmos/tests/data/dummy_contract/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" from aea.contracts.base import Contract from aea.crypto.base import LedgerApi class DummyContract(Contract): """The some contract class.""" @classmethod def some_method(cls, ledger_api: LedgerApi, contract_address: str) -> None: """Some method.""" pass ================================================ FILE: plugins/aea-ledger-cosmos/tests/data/dummy_contract/contract.yaml ================================================ name: dummy author: fetchai version: 0.1.0 type: contract description: A test contract license: Apache-2.0 aea_version: '>=0.9.0, <0.10.0' fingerprint: __init__.py: QmWbNjFh6E5V4n2qBwyZyXZdmmHvcZSVnwKrcM34MAE56S build/some.json: Qma5n7au2NDCg1nLwYfYnmFNwWChFuXtu65w5DV7wAZRvw build/some.wasm: Qmc9gthbdwRSywinTHKjRVQdFzrKTxUuLDx2ryNfQp1xqf contract.py: QmYfn2V3tXv7MCyhT5Kz5ArtX2FZWHC1jfeRmqCNsRxDL5 fingerprint_ignore_patterns: [] build_entrypoint: path/to/script.py class_name: DummyContract contract_interface_paths: cosmos: build/some.wasm ethereum: build/some.json fetchai: build/some.wasm dependencies: {} ================================================ FILE: plugins/aea-ledger-cosmos/tests/test_cosmos.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the ethereum module.""" import os import shutil import tempfile from pathlib import Path from unittest.mock import MagicMock, Mock, patch from uuid import uuid4 import pytest # type:ignore from aea_ledger_cosmos import CosmosApi, CosmosCrypto, CosmosHelper from aea_ledger_cosmos.cosmos import _default_logger as cosmos_logger from tests.conftest import COSMOS_TESTNET_CONFIG, ROOT_DIR @pytest.fixture(scope="session") def cosmos_private_key_file(): """Pytest fixture to create a temporary Cosmos private key file.""" crypto = CosmosCrypto() temp_dir = Path(tempfile.mkdtemp()) try: temp_file = temp_dir / "private.key" temp_file.write_text(crypto.private_key) yield str(temp_file) finally: shutil.rmtree(temp_dir) def test_creation(cosmos_private_key_file): """Test the creation of the crypto_objects.""" assert CosmosCrypto(), "Did not manage to initialise the crypto module" assert CosmosCrypto( cosmos_private_key_file ), "Did not manage to load the cosmos private key" def test_key_file_encryption_decryption(cosmos_private_key_file): """Test cosmos private key encrypted and decrypted correctly.""" cosmos = CosmosCrypto(cosmos_private_key_file) pk_data = Path(cosmos_private_key_file).read_text() password = uuid4().hex encrypted_data = cosmos.encrypt(password) decrypted_data = cosmos.decrypt(encrypted_data, password) assert encrypted_data != pk_data assert pk_data == decrypted_data with pytest.raises(ValueError, match="Decrypt error! Bad password?"): cosmos.decrypt(encrypted_data, "BaD_PassWord") with pytest.raises(ValueError, match="Bad encrypted key format!"): cosmos.decrypt("some_data" * 16, "BaD_PassWord") def test_initialization(): """Test the initialisation of the variables.""" account = CosmosCrypto() assert account.entity is not None, "The property must return the account." assert ( account.address is not None ), "After creation the display address must not be None" assert account.address.startswith("cosmos") assert ( account.public_key is not None ), "After creation the public key must no be None" def test_sign_and_recover_message(cosmos_private_key_file): """Test the signing and the recovery of a message.""" account = CosmosCrypto(cosmos_private_key_file) sign_bytes = account.sign_message(message=b"hello") assert len(sign_bytes) > 0, "The len(signature) must not be 0" recovered_addresses = CosmosApi.recover_message( message=b"hello", signature=sign_bytes ) assert ( account.address in recovered_addresses ), "Failed to recover the correct address." def test_sign_and_recover_message_public_key(cosmos_private_key_file): """Test the signing and the recovery function for the eth_crypto.""" COSMOS_PRIVATE_KEY_PATH = os.path.join( ROOT_DIR, "tests", "data", "cosmos_private_key.txt" ) account = CosmosCrypto(COSMOS_PRIVATE_KEY_PATH) sign_bytes = account.sign_message(message=b"hello") assert len(sign_bytes) > 0, "The len(signature) must not be 0" recovered_public_keys = CosmosApi.recover_public_keys_from_message( message=b"hello", signature=sign_bytes ) assert len(recovered_public_keys) == 2, "Wrong number of public keys recovered." assert ( CosmosApi.get_address_from_public_key(recovered_public_keys[0]) == account.address ), "Failed to recover the correct address." def test_get_hash(): """Test the get hash functionality.""" expected_hash = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" hash_ = CosmosApi.get_hash(message=b"hello") assert expected_hash == hash_ def test_dump_positive(cosmos_private_key_file): """Test dump.""" account = CosmosCrypto(cosmos_private_key_file) account.dump(MagicMock()) def test_api_creation(): """Test api instantiation.""" assert CosmosApi(**COSMOS_TESTNET_CONFIG), "Failed to initialise the api" def test_api_none(): """Test the "api" of the cryptoApi is none.""" cosmos_api = CosmosApi(**COSMOS_TESTNET_CONFIG) assert cosmos_api.api is None, "The api property is not None." def test_generate_nonce(): """Test generate nonce.""" nonce = CosmosApi.generate_tx_nonce( seller="some_seller_addr", client="some_buyer_addr" ) assert len(nonce) > 0 and int( nonce, 16 ), "The len(nonce) must not be 0 and must be hex" def test_validate_address(): """Test the is_valid_address functionality.""" account = CosmosCrypto() assert CosmosApi.is_valid_address(account.address) assert not CosmosApi.is_valid_address(account.address + "wrong") def test_load_contract_interface(): """Test the load_contract_interface method.""" path = Path(ROOT_DIR, "tests", "data", "dummy_contract", "build", "some.wasm") result = CosmosApi.load_contract_interface(path) assert "wasm_byte_code" in result def test_helper_is_settled(): """Test CosmosHelper.is_transaction_settled.""" assert CosmosHelper.is_transaction_settled({"code": None}) is True with patch.object(cosmos_logger, "warning") as warning_mock: assert CosmosHelper.is_transaction_settled({"code": "some value"}) is False warning_mock.assert_called_once() def test_helper_get_code_id(): """Test CosmosHelper.is_transaction_settled.""" assert ( CosmosHelper.get_code_id( { "logs": [ { "msg_index": 0, "log": "", "events": [ { "type": "message", "attributes": [ {"key": "action", "value": "store-code"}, {"key": "module", "value": "wasm"}, { "key": "signer", "value": "fetch1pa7q6urt98dfe2rsvfaefj8zhh792sdfuzym2t", }, {"key": "code_id", "value": "631"}, ], } ], } ] } ) == 631 ) def test_helper_get_contract_address(): """Test CosmosHelper.is_transaction_settled.""" assert ( CosmosHelper.get_contract_address( { "logs": [ { "msg_index": 0, "log": "", "events": [ { "type": "message", "attributes": [ {"key": "action", "value": "instantiate"}, {"key": "module", "value": "wasm"}, { "key": "signer", "value": "fetch1pa7q6urt98dfe2rsvfaefj8zhh792sdfuzym2t", }, {"key": "code_id", "value": "631"}, { "key": "_contract_address", "value": "fetch1lhd5t8jdjn0n4q27hsah6c0907nxrswcp5l4nw", }, ], } ], } ] } ) == "fetch1lhd5t8jdjn0n4q27hsah6c0907nxrswcp5l4nw" ) @patch.object( CosmosApi, "_try_get_account_number_and_sequence", return_value=(None, None) ) def test_cosmos_api_get_deploy_transaction(*args): """Test CosmosApi._get_deploy_transaction.""" cosmos_api = CosmosApi() assert cosmos_api.get_deploy_transaction(*[Mock()] * 2) is None @patch.object( CosmosApi, "_try_get_account_number_and_sequence", return_value=(None, None) ) def test_cosmos_api_get_handle_transaction(*args): """Test CosmosApi.get_handle_transaction.""" cosmos_api = CosmosApi() assert cosmos_api.get_handle_transaction(*[Mock()] * 7) is None @patch.object( CosmosApi, "_try_get_account_number_and_sequence", return_value=(None, None) ) def test_cosmos_api_get_transfer_transaction(*args): """Test CosmosApi.get_transfer_transaction.""" cosmos_api = CosmosApi() assert cosmos_api.get_transfer_transaction(*[Mock()] * 7) is None ================================================ FILE: plugins/aea-ledger-ethereum/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2019 Fetch.AI Limited Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: plugins/aea-ledger-ethereum/MANIFEST.in ================================================ include README.md LICENSE HISTORY.md recursive-include aea_ledger_ethereum recursive-include tests * ================================================ FILE: plugins/aea-ledger-ethereum/README.md ================================================ # Ethereum crypto plug-in Ethereum crypto plug-in for the AEA framework. ## Install ``` bash python setup.py install ``` ## Run tests ``` bash python setup.py test ``` ================================================ FILE: plugins/aea-ledger-ethereum/aea_ledger_ethereum/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Python package wrapping the public and private key cryptography and ledger api of Ethereum.""" from .ethereum import * # noqa isort:skip from .ethereum import _ABI, _BYTECODE, _ETHEREUM # noqa isort:skip ================================================ FILE: plugins/aea-ledger-ethereum/aea_ledger_ethereum/ethereum.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Ethereum module wrapping the public and private key cryptography and ledger api.""" import json import logging import threading import time import warnings from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast import ipfshttpclient # noqa: F401 # pylint: disable=unused-import import web3._utils.request from eth_account import Account from eth_account._utils.signing import to_standard_signature_bytes from eth_account.datastructures import HexBytes, SignedTransaction from eth_account.messages import _hash_eip191_message, encode_defunct from eth_keys import keys from eth_typing import HexStr from lru import LRU # type: ignore # pylint: disable=no-name-in-module from web3 import HTTPProvider, Web3 from web3.datastructures import AttributeDict from web3.gas_strategies.rpc import rpc_gas_price_strategy from web3.types import TxData, TxParams, TxReceipt, Wei from aea.common import Address, JSONLike from aea.crypto.base import Crypto, FaucetApi, Helper, LedgerApi from aea.crypto.helpers import DecryptError, KeyIsIncorrect, hex_to_bytes_for_key from aea.exceptions import enforce from aea.helpers import http_requests as requests from aea.helpers.base import try_decorator from aea.helpers.io import open_file _default_logger = logging.getLogger(__name__) _ETHEREUM = "ethereum" TESTNET_NAME = "ganache" DEFAULT_ADDRESS = "http://127.0.0.1:8545" DEFAULT_CHAIN_ID = 1337 DEFAULT_CURRENCY_DENOM = "wei" ETH_GASSTATION_URL = "https://ethgasstation.info/api/ethgasAPI.json" _ABI = "abi" _BYTECODE = "bytecode" def get_gas_price_strategy( gas_price_strategy: Optional[str] = None, api_key: Optional[str] = None ) -> Callable[[Web3, TxParams], Wei]: """Get the gas price strategy.""" supported_gas_price_modes = ["safeLow", "average", "fast", "fastest"] if gas_price_strategy is None: _default_logger.debug( "Gas price strategy not provided. Falling back to `rpc_gas_price_strategy`." ) return rpc_gas_price_strategy if gas_price_strategy not in supported_gas_price_modes: _default_logger.debug( f"Gas price strategy `{gas_price_strategy}` not in list of supported modes: {supported_gas_price_modes}. Falling back to `rpc_gas_price_strategy`." ) return rpc_gas_price_strategy if api_key is None: _default_logger.debug( "No ethgasstation api key provided. Falling back to `rpc_gas_price_strategy`." ) return rpc_gas_price_strategy def gas_station_gas_price_strategy( # pylint: disable=redefined-outer-name,unused-argument web3: Web3, transaction_params: TxParams ) -> Wei: """ Get gas price from Eth Gas Station api. Visit `https://docs.ethgasstation.info/gas-price` for documentation. :param web3: web3 instance :param transaction_params: transaction parameters :return: wei """ response = requests.get(f"{ETH_GASSTATION_URL}?api-key={api_key}") if response.status_code != 200: raise ValueError( # pragma: nocover f"Gas station API response: {response.status_code}, {response.text}" ) response_dict = response.json() _default_logger.debug("Gas station API response: {}".format(response_dict)) result = response_dict.get(gas_price_strategy, None) if type(result) not in [int, float]: # pragma: nocover raise ValueError(f"Invalid return value for `{gas_price_strategy}`!") gwei_result = result / 10 # adjustment (see api documentation) wei_result = web3.toWei(gwei_result, "gwei") return Wei(wei_result) return gas_station_gas_price_strategy class SignedTransactionTranslator: """Translator for SignedTransaction.""" @staticmethod def to_dict(signed_transaction: SignedTransaction) -> Dict[str, Union[str, int]]: """Write SignedTransaction to dict.""" signed_transaction_dict = { "raw_transaction": signed_transaction.rawTransaction.hex(), "hash": signed_transaction.hash.hex(), "r": signed_transaction.r, "s": signed_transaction.s, "v": signed_transaction.v, } return signed_transaction_dict @staticmethod def from_dict(signed_transaction_dict: JSONLike) -> SignedTransaction: """Get SignedTransaction from dict.""" if ( not isinstance(signed_transaction_dict, dict) and len(signed_transaction_dict) == 5 ): raise ValueError( # pragma: nocover f"Invalid for conversion. Found object: {signed_transaction_dict}." ) signed_transaction = SignedTransaction( rawTransaction=HexBytes(signed_transaction_dict["raw_transaction"]), hash=HexBytes(signed_transaction_dict["hash"]), r=signed_transaction_dict["r"], s=signed_transaction_dict["s"], v=signed_transaction_dict["v"], ) return signed_transaction class AttributeDictTranslator: """Translator for AttributeDict.""" @classmethod def _remove_hexbytes(cls, value: Any) -> Any: """Process value to remove hexbytes.""" if value is None: return value if isinstance(value, HexBytes): return value.hex() if isinstance(value, list): return cls._process_list(value, cls._remove_hexbytes) if type(value) in (bool, int, float, str, bytes): return value if isinstance(value, AttributeDict): return cls.to_dict(value) raise NotImplementedError( # pragma: nocover f"Unknown type conversion. Found type: {type(value)}" ) @classmethod def _add_hexbytes(cls, value: Any) -> Any: """Process value to add hexbytes.""" if value is None: return value if isinstance(value, str): try: int(value, 16) return HexBytes(value) except Exception: # pylint: disable=broad-except return value if isinstance(value, list): return cls._process_list(value, cls._add_hexbytes) if isinstance(value, dict): return cls.from_dict(value) if type(value) in (bool, int, float, bytes): return value raise NotImplementedError( # pragma: nocover f"Unknown type conversion. Found type: {type(value)}" ) @classmethod def _process_list(cls, li: list, callable_name: Callable) -> List: """Simplify a list with process value.""" return [callable_name(el) for el in li] @classmethod def _valid_key(cls, key: Any) -> str: """Check validity of key.""" if isinstance(key, str): return key raise ValueError("Key must be string.") # pragma: nocover @classmethod def to_dict(cls, attr_dict: Union[AttributeDict, TxReceipt, TxData]) -> JSONLike: """Simplify to dict.""" if not isinstance(attr_dict, AttributeDict): raise ValueError("No AttributeDict provided.") # pragma: nocover result = { cls._valid_key(key): cls._remove_hexbytes(value) for key, value in attr_dict.items() } return result @classmethod def from_dict(cls, di: JSONLike) -> AttributeDict: """Get back attribute dict.""" if not isinstance(di, dict): raise ValueError("No dict provided.") # pragma: nocover processed_dict = { cls._valid_key(key): cls._add_hexbytes(value) for key, value in di.items() } return AttributeDict(processed_dict) class EthereumCrypto(Crypto[Account]): """Class wrapping the Account Generation from Ethereum ledger.""" identifier = _ETHEREUM def __init__( self, private_key_path: Optional[str] = None, password: Optional[str] = None ) -> None: """ Instantiate an ethereum crypto object. :param private_key_path: the private key path of the agent :param password: the password to encrypt/decrypt the private key. """ super().__init__(private_key_path=private_key_path, password=password) bytes_representation = Web3.toBytes(hexstr=self.entity.key.hex()) self._public_key = str(keys.PrivateKey(bytes_representation).public_key) self._address = str(self.entity.address) @property def private_key(self) -> str: """ Return a private key. :return: a private key string """ return self.entity.key.hex() @property def public_key(self) -> str: """ Return a public key in hex format. :return: a public key string in hex format """ return self._public_key @property def address(self) -> str: """ Return the address for the key pair. :return: a display_address str """ return self._address @classmethod def load_private_key_from_path( cls, file_name: str, password: Optional[str] = None ) -> Account: """ Load a private key in hex format from a file. :param file_name: the path to the hex file. :param password: the password to encrypt/decrypt the private key. :return: the Entity. """ private_key = cls.load(file_name, password) try: if not private_key.startswith("0x"): hex_to_bytes_for_key(private_key) except KeyIsIncorrect as e: if not password: raise KeyIsIncorrect( f"Error on key `{file_name}` load! Try to specify `password`: Error: {repr(e)} " ) from e raise KeyIsIncorrect( f"Error on key `{file_name}` load! Wrong password?: Error: {repr(e)} " ) from e account = Account.from_key( # pylint: disable=no-value-for-parameter private_key=private_key ) return account def sign_message(self, message: bytes, is_deprecated_mode: bool = False) -> str: """ Sign a message in bytes string form. :param message: the message to be signed :param is_deprecated_mode: if the deprecated signing is used :return: signature of the message in string form """ if is_deprecated_mode and len(message) == 32: with warnings.catch_warnings(): warnings.simplefilter("ignore") signature_dict = self.entity.signHash(message) signed_msg = signature_dict["signature"].hex() else: signable_message = encode_defunct(primitive=message) signature = self.entity.sign_message(signable_message=signable_message) signed_msg = signature["signature"].hex() return signed_msg def sign_transaction(self, transaction: JSONLike) -> JSONLike: """ Sign a transaction in bytes string form. :param transaction: the transaction to be signed :return: signed transaction """ signed_transaction = self.entity.sign_transaction(transaction_dict=transaction) # Note: self.entity.signTransaction(transaction_dict=transaction) == signed_transaction # noqa: E800 signed_transaction_dict = SignedTransactionTranslator.to_dict( signed_transaction ) return cast(JSONLike, signed_transaction_dict) @classmethod def generate_private_key(cls) -> Account: """Generate a key pair for ethereum network.""" account = Account.create() # pylint: disable=no-value-for-parameter return account def encrypt(self, password: str) -> str: """ Encrypt the private key and return in json. :param password: the password to decrypt. :return: json string containing encrypted private key. """ encrypted = Account.encrypt(self.private_key, password) return json.dumps(encrypted) @classmethod def decrypt(cls, keyfile_json: str, password: str) -> str: """ Decrypt the private key and return in raw form. :param keyfile_json: json str containing encrypted private key. :param password: the password to decrypt. :return: the raw private key. """ try: private_key = Account.decrypt(keyfile_json, password) except ValueError as e: if e.args[0] == "MAC mismatch": raise DecryptError() from e raise return private_key.hex()[2:] class EthereumHelper(Helper): """Helper class usable as Mixin for EthereumApi or as standalone class.""" @staticmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool: """ Check whether a transaction is settled or not. :param tx_receipt: the receipt associated to the transaction. :return: True if the transaction has been settled, False o/w. """ is_successful = False if tx_receipt is not None: is_successful = tx_receipt.get("status", 0) == 1 return is_successful @staticmethod def get_contract_address(tx_receipt: JSONLike) -> Optional[str]: """ Retrieve the `contract_address` from a transaction receipt. :param tx_receipt: the receipt of the transaction. :return: the contract address, if present """ contract_address = cast(Optional[str], tx_receipt.get("contractAddress", None)) return contract_address @staticmethod def is_transaction_valid( tx: dict, seller: Address, client: Address, tx_nonce: str, amount: int, ) -> bool: """ Check whether a transaction is valid or not. :param tx: the transaction. :param seller: the address of the seller. :param client: the address of the client. :param tx_nonce: the transaction nonce. :param amount: the amount we expect to get from the transaction. :return: True if the random_message is equals to tx['input'] """ is_valid = False if tx is not None: is_valid = ( tx.get("input") == tx_nonce and tx.get("value") == amount and tx.get("from") == client and tx.get("to") == seller ) return is_valid @staticmethod def generate_tx_nonce(seller: Address, client: Address) -> str: """ Generate a unique hash to distinguish transactions with the same terms. :param seller: the address of the seller. :param client: the address of the client. :return: return the hash in hex. """ time_stamp = int(time.time()) aggregate_hash = Web3.keccak( b"".join([seller.encode(), client.encode(), time_stamp.to_bytes(32, "big")]) ) return aggregate_hash.hex() @classmethod def get_address_from_public_key(cls, public_key: str) -> str: """ Get the address from the public key. :param public_key: the public key :return: str """ keccak_hash = Web3.keccak(hexstr=public_key) raw_address = keccak_hash[-20:].hex().upper() address = Web3.toChecksumAddress(raw_address) return address @classmethod def recover_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[Address, ...]: """ Recover the addresses from the hash. :param message: the message we expect :param signature: the transaction signature :param is_deprecated_mode: if the deprecated signing was used :return: the recovered addresses """ if is_deprecated_mode: enforce(len(message) == 32, "Message must be hashed to exactly 32 bytes.") with warnings.catch_warnings(): warnings.simplefilter("ignore") address = Account.recoverHash( # pylint: disable=no-value-for-parameter message_hash=message, signature=signature ) else: signable_message = encode_defunct(primitive=message) address = Account.recover_message( # pylint: disable=no-value-for-parameter signable_message=signable_message, signature=signature ) return (address,) @classmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[str, ...]: """ Get the public key used to produce the `signature` of the `message` :param message: raw bytes used to produce signature :param signature: signature of the message :param is_deprecated_mode: if the deprecated signing was used :return: the recovered public keys """ if not is_deprecated_mode: signable_message = encode_defunct(primitive=message) message = _hash_eip191_message(signable_message) hash_bytes = HexBytes(message) # code taken from https://github.com/ethereum/eth-account/blob/master/eth_account/account.py#L428 if len(hash_bytes) != 32: # pragma: nocover raise ValueError("The message hash must be exactly 32-bytes") signature_bytes = HexBytes(signature) signature_bytes_standard = to_standard_signature_bytes(signature_bytes) signature_obj = keys.Signature(signature_bytes=signature_bytes_standard) pubkey = signature_obj.recover_public_key_from_msg_hash(hash_bytes) return (str(pubkey),) @staticmethod def get_hash(message: bytes) -> str: """ Get the hash of a message. :param message: the message to be hashed. :return: the hash of the message. """ digest = Web3.keccak(message).hex() return digest @classmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str]: """ Load contract interface. :param file_path: the file path to the interface :return: the interface """ with open_file(file_path, "r") as interface_file_ethereum: contract_interface = json.load(interface_file_ethereum) for key in [_ABI, _BYTECODE]: if key not in contract_interface: # pragma: nocover raise ValueError(f"Contract {file_path} missing key {key}.") return contract_interface class EthereumApi(LedgerApi, EthereumHelper): """Class to interact with the Ethereum Web3 APIs.""" identifier = _ETHEREUM def __init__(self, **kwargs: Any): """ Initialize the Ethereum ledger APIs. :param kwargs: keyword arguments """ self._api = Web3( HTTPProvider(endpoint_uri=kwargs.pop("address", DEFAULT_ADDRESS)) ) self._chain_id = kwargs.pop("chain_id", DEFAULT_CHAIN_ID) self._gas_price_api_key = kwargs.pop("gas_price_api_key", None) @property def api(self) -> Web3: """Get the underlying API object.""" return self._api def get_balance(self, address: Address) -> Optional[int]: """Get the balance of a given account.""" return self._try_get_balance(address) @try_decorator("Unable to retrieve balance: {}", logger_method="warning") def _try_get_balance(self, address: Address) -> Optional[int]: """Get the balance of a given account.""" check_address = self._api.toChecksumAddress(address) return self._api.eth.getBalance(check_address) # pylint: disable=no-member def get_state( self, callable_name: str, *args: Any, **kwargs: Any ) -> Optional[JSONLike]: """Call a specified function on the ledger API.""" response = self._try_get_state(callable_name, *args, **kwargs) return response @try_decorator("Unable to get state: {}", logger_method="warning") def _try_get_state( # pylint: disable=unused-argument self, callable_name: str, *args: Any, **kwargs: Any ) -> Optional[JSONLike]: """Try to call a function on the ledger API.""" function = getattr(self._api.eth, callable_name) response = function(*args, **kwargs) if isinstance(response, AttributeDict): result = AttributeDictTranslator.to_dict(response) return result if type(response) in (int, float, bytes, str, list, dict): # pragma: nocover # missing full checks for nested objects return {f"{callable_name}_result": response} raise NotImplementedError( # pragma: nocover f"Response must be of types=int, float, bytes, str, list, dict. Found={type(response)}." ) def get_transfer_transaction( # pylint: disable=arguments-differ self, sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, chain_id: Optional[int] = None, gas_price: Optional[str] = None, gas_price_strategy: Optional[str] = None, **kwargs: Any, ) -> Optional[JSONLike]: """ Submit a transfer transaction to the ledger. :param sender_address: the sender address of the payer. :param destination_address: the destination address of the payee. :param amount: the amount of wealth to be transferred (in Wei). :param tx_fee: the transaction fee (gas) to be used (in Wei). :param tx_nonce: verifies the authenticity of the tx. :param chain_id: the Chain ID of the Ethereum transaction. :param gas_price: the gas price (in Wei) :param gas_price_strategy: the gas price strategy to be used. :param kwargs: keyword arguments :return: the transfer transaction """ transaction: Optional[JSONLike] = None chain_id = chain_id if chain_id is not None else self._chain_id gas_price = ( self._try_get_gas_price(gas_price_strategy) if gas_price is None else gas_price ) if gas_price is None: return transaction # pragma: nocover nonce = self._try_get_transaction_count(sender_address) if nonce is None: return transaction transaction = { "nonce": nonce, "chainId": chain_id, "to": destination_address, "value": amount, "gas": tx_fee, "gasPrice": gas_price, "data": tx_nonce, } transaction = self.update_with_gas_estimate(transaction) return transaction @try_decorator("Unable to retrieve gas price: {}", logger_method="warning") def _try_get_gas_price( self, gas_price_strategy: Optional[str] = None ) -> Optional[int]: """Try get the gas price based on the provided strategy.""" gas_price_strategy_callable = get_gas_price_strategy( gas_price_strategy, self._gas_price_api_key ) prior_strategy = self._api.eth.gasPriceStrategy try: self._api.eth.setGasPriceStrategy(gas_price_strategy_callable) gas_price = self._api.eth.generateGasPrice() finally: if prior_strategy is not None: self._api.eth.setGasPriceStrategy(prior_strategy) # pragma: nocover return gas_price @try_decorator("Unable to retrieve transaction count: {}", logger_method="warning") def _try_get_transaction_count(self, address: Address) -> Optional[int]: """Try get the transaction count.""" nonce = self._api.eth.getTransactionCount( # pylint: disable=no-member self._api.toChecksumAddress(address) ) return nonce def update_with_gas_estimate(self, transaction: JSONLike) -> JSONLike: """ Attempts to update the transaction with a gas estimate :param transaction: the transaction :return: the updated transaction """ gas_estimate = self._try_get_gas_estimate(transaction) if gas_estimate is not None: specified_gas = transaction["gas"] if specified_gas < gas_estimate: # eventually; there should be some specifiable strategy _default_logger.warning( # pragma: nocover f"Needed to increase gas to cover the gas consumption of the transaction. Estimated gas consumption is: {gas_estimate}. Specified gas was: {specified_gas}." ) transaction["gas"] = gas_estimate return transaction @try_decorator("Unable to retrieve gas estimate: {}", logger_method="warning") def _try_get_gas_estimate(self, transaction: JSONLike) -> Optional[int]: """Try get the gas estimate.""" gas_estimate = self._api.eth.estimateGas( # pylint: disable=no-member transaction=cast(TxParams, AttributeDictTranslator.from_dict(transaction)) ) return gas_estimate def send_signed_transaction(self, tx_signed: JSONLike) -> Optional[str]: """ Send a signed transaction and wait for confirmation. :param tx_signed: the signed transaction :return: tx_digest, if present """ tx_digest = self._try_send_signed_transaction(tx_signed) return tx_digest @try_decorator("Unable to send transaction: {}", logger_method="warning") def _try_send_signed_transaction(self, tx_signed: JSONLike) -> Optional[str]: """ Try send a signed transaction. :param tx_signed: the signed transaction :return: tx_digest, if present """ signed_transaction = SignedTransactionTranslator.from_dict(tx_signed) hex_value = self._api.eth.sendRawTransaction( # pylint: disable=no-member signed_transaction.rawTransaction ) tx_digest = hex_value.hex() _default_logger.debug( "Successfully sent transaction with digest: {}".format(tx_digest) ) return tx_digest def get_transaction_receipt(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction receipt for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ tx_receipt = self._try_get_transaction_receipt(tx_digest) return tx_receipt @try_decorator( "Error when attempting getting tx receipt: {}", logger_method="debug" ) def _try_get_transaction_receipt(self, tx_digest: str) -> Optional[JSONLike]: """ Try get the transaction receipt. :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ tx_receipt = self._api.eth.getTransactionReceipt( # pylint: disable=no-member cast(HexStr, tx_digest) ) return AttributeDictTranslator.to_dict(tx_receipt) def get_transaction(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx, if present """ tx = self._try_get_transaction(tx_digest) return tx @try_decorator("Error when attempting getting tx: {}", logger_method="debug") def _try_get_transaction(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction. :param tx_digest: the transaction digest. :return: the tx, if found """ tx = self._api.eth.getTransaction( cast(HexStr, tx_digest) ) # pylint: disable=no-member return AttributeDictTranslator.to_dict(tx) def get_contract_instance( self, contract_interface: Dict[str, str], contract_address: Optional[str] = None ) -> Any: """ Get the instance of a contract. :param contract_interface: the contract interface. :param contract_address: the contract address. :return: the contract instance """ if contract_address is None: instance = self.api.eth.contract( abi=contract_interface[_ABI], bytecode=contract_interface[_BYTECODE], ) else: _contract_address = self.api.toChecksumAddress(contract_address) instance = self.api.eth.contract( # type: ignore address=_contract_address, abi=contract_interface[_ABI], bytecode=contract_interface[_BYTECODE], ) return instance def get_deploy_transaction( # pylint: disable=arguments-differ self, contract_interface: Dict[str, str], deployer_address: Address, value: int = 0, gas: int = 0, gas_price: Optional[str] = None, gas_price_strategy: Optional[str] = None, **kwargs: Any, ) -> Optional[JSONLike]: """ Get the transaction to deploy the smart contract. :param contract_interface: the contract interface. :param deployer_address: The address that will deploy the contract. :param value: value to send to contract (in Wei) :param gas: the gas to be used (in Wei) :param gas_price: the gas price (in Wei) :param gas_price_strategy: the gas price strategy to be used. :param kwargs: keyword arguments :return: the transaction dictionary. """ transaction: Optional[JSONLike] = None _deployer_address = self.api.toChecksumAddress(deployer_address) nonce = self.api.eth.getTransactionCount(_deployer_address) if nonce is None: return transaction gas_price = ( self._try_get_gas_price(gas_price_strategy) if gas_price is None else gas_price ) if gas_price is None: return transaction # pragma: nocover instance = self.get_contract_instance(contract_interface) data = instance.constructor(**kwargs).buildTransaction().get("data", "0x") transaction = { "from": _deployer_address, # only 'from' address, don't insert 'to' address! "value": value, "gas": gas, "gasPrice": gas_price, "nonce": nonce, "data": data, } transaction = self.update_with_gas_estimate(transaction) return transaction @classmethod def is_valid_address(cls, address: Address) -> bool: """ Check if the address is valid. :param address: the address to validate :return: whether the address is valid """ return Web3.isAddress(address) class EthereumFaucetApi(FaucetApi): """Ethereum testnet faucet API.""" identifier = _ETHEREUM testnet_name = TESTNET_NAME def get_wealth(self, address: Address, url: Optional[str] = None) -> None: """ Get wealth from the faucet for the provided address. :param address: the address. :param url: the url """ self._try_get_wealth(address, url) @staticmethod @try_decorator( "An error occured while attempting to generate wealth:\n{}", logger_method="error", ) def _try_get_wealth(address: Address, url: Optional[str] = None) -> None: """ Get wealth from the faucet for the provided address. :param address: the address. :param url: the url """ if url is None: raise ValueError( # pragma: nocover "Url is none, no default url provided. Please provide a faucet url." ) response = requests.get(url + address) if response.status_code // 100 == 5: # pragma: no cover _default_logger.error("Response: {}".format(response.status_code)) elif response.status_code // 100 in [3, 4]: # pragma: nocover response_dict = json.loads(response.text) _default_logger.warning( "Response: {}\nMessage: {}".format( response.status_code, response_dict.get("message") ) ) elif response.status_code // 100 == 2: # pragma: no cover response_dict = json.loads(response.text) _default_logger.info( "Response: {}\nMessage: {}".format( response.status_code, response_dict.get("message") ) ) class LruLockWrapper: """Wrapper for LRU with threading.Lock.""" def __init__(self, lru: LRU) -> None: """Init wrapper.""" self.lru = lru self.lock = threading.Lock() def __getitem__(self, *args: Any, **kwargs: Any) -> Any: """Get item""" with self.lock: return self.lru.__getitem__(*args, **kwargs) def __setitem__(self, *args: Any, **kwargs: Any) -> Any: """Set item.""" with self.lock: return self.lru.__setitem__(*args, **kwargs) def __contains__(self, *args: Any, **kwargs: Any) -> Any: """Contain item.""" with self.lock: return self.lru.__contains__(*args, **kwargs) def __delitem__(self, *args: Any, **kwargs: Any) -> Any: """Del item.""" with self.lock: return self.lru.__delitem__(*args, **kwargs) def set_wrapper_for_web3py_session_cache() -> None: """Wrap web3py session cache with threading.Lock.""" # pylint: disable=protected-access web3._utils.request._session_cache = LruLockWrapper( web3._utils.request._session_cache ) set_wrapper_for_web3py_session_cache() ================================================ FILE: plugins/aea-ledger-ethereum/pyproject.toml ================================================ [build-system] requires = ["setuptools", "wheel"] build_backend = "setuptools.build_meta" ================================================ FILE: plugins/aea-ledger-ethereum/setup.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Setup script for "aea_ledger_ethereum" package.""" from setuptools import find_packages, setup setup( name="aea-ledger-ethereum", version="1.1.2", author="Fetch.AI Limited", license="Apache-2.0", description="Python package wrapping the public and private key cryptography and ledger api of Ethereum.", packages=find_packages(include=["aea_ledger_ethereum*"]), install_requires=[ "aea>=1.0.0, <2.0.0", "web3==5.31.1", "ipfshttpclient==0.8.0a2", ], tests_require=["pytest"], entry_points={ "aea.cryptos": ["ethereum = aea_ledger_ethereum:EthereumCrypto"], "aea.ledger_apis": ["ethereum = aea_ledger_ethereum:EthereumApi"], "aea.faucet_apis": ["ethereum = aea_ledger_ethereum:EthereumFaucetApi"], }, classifiers=[ "Environment :: Console", "Environment :: Web Environment", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft", "Operating System :: Unix", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Communications", "Topic :: Internet", "Topic :: Software Development", ], ) ================================================ FILE: plugins/aea-ledger-ethereum/tests/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for the aea_ledger_ethereum package.""" ================================================ FILE: plugins/aea-ledger-ethereum/tests/conftest.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Conftest module for Pytest.""" import inspect import logging import os import platform import shutil import tempfile import time from functools import wraps from pathlib import Path from typing import Callable, Generator import docker import pytest from aea_ledger_ethereum import EthereumCrypto from aea.configurations.constants import PRIVATE_KEY_PATH_SCHEMA from tests.docker_image import DockerImage, GanacheDockerImage CUR_PATH = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore ROOT_DIR = os.path.join(CUR_PATH, "..") MAX_FLAKY_RERUNS = 3 ETHEREUM = EthereumCrypto.identifier ETHEREUM_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(ETHEREUM) ETHEREUM_PRIVATE_KEY_PATH = os.path.join( ROOT_DIR, "tests", "data", ETHEREUM_PRIVATE_KEY_FILE ) ETHEREUM_DEFAULT_ADDRESS = "http://127.0.0.1:8545" ETHEREUM_DEFAULT_CHAIN_ID = 1337 ETHEREUM_DEFAULT_CURRENCY_DENOM = "wei" ETHEREUM_TESTNET_CONFIG = {"address": ETHEREUM_DEFAULT_ADDRESS} # URL to local Ganache instance DEFAULT_GANACHE_ADDR = "http://127.0.0.1" DEFAULT_GANACHE_PORT = 8545 DEFAULT_GANACHE_CHAIN_ID = 1337 GAS_PRICE_API_KEY = "" DEFAULT_AMOUNT = 1000000000000000000000 FUNDED_ETH_PRIVATE_KEY_1 = ( "0xa337a9149b4e1eafd6c21c421254cf7f98130233595db25f0f6f0a545fb08883" ) FUNDED_ETH_PRIVATE_KEY_2 = ( "0x04b4cecf78288f2ab09d1b4c60219556928f86220f0fb2dcfc05e6a1c1149dbf" ) FUNDED_ETH_PRIVATE_KEY_3 = ( "0x6F611408F7EF304947621C51A4B7D84A13A2B9786E9F984DA790A096E8260C64" ) logger = logging.getLogger(__name__) def action_for_platform(platform_name: str, skip: bool = True) -> Callable: """ Decorate a pytest class or method to skip on certain platform. :param platform_name: check `platform.system()` for available platforms. :param skip: if True, the test will be skipped; if False, the test will be run ONLY on the chosen platform. :return: decorated object """ # for docstyle. def decorator(pytest_func): """ For the sake of clarity, assume the chosen platform for the action is "Windows". If the following condition is true: - the current system is not Windows (is_different) AND we want to skip it (skip) OR - the current system is Windows (not is_different) AND we want to run only on it (not skip) we run the test, else we skip the test. logically, the condition is a boolean equivalence between the variables "is_different" and "skip" Hence, the condition becomes: :param pytest_func: the pytest function to wrap :return: the wrapped function """ is_different = platform.system() != platform_name if is_different is skip: return pytest_func def action(*args, **kwargs): if skip: pytest.skip( f"Skipping the test since it doesn't work on {platform_name}." ) else: pytest.skip( f"Skipping the test since it works only on {platform_name}." ) if isinstance(pytest_func, type): return type( pytest_func.__name__, (pytest_func,), { "setup_class": action, "setup": action, "setUp": action, "_skipped": True, }, ) @wraps(pytest_func) def wrapper(*args, **kwargs): # type: ignore action(*args, **kwargs) return wrapper return decorator @pytest.fixture(scope="session") def ethereum_private_key_file(): """Pytest fixture to create a temporary Ethereum private key file.""" crypto = EthereumCrypto() temp_dir = Path(tempfile.mkdtemp()) try: temp_file = temp_dir / "private.key" temp_file.write_text(crypto.private_key) yield str(temp_file) finally: shutil.rmtree(temp_dir) @pytest.fixture(scope="session") def ethereum_testnet_config(ganache_addr, ganache_port): """Get Ethereum ledger api configurations using Ganache.""" new_uri = f"{ganache_addr}:{ganache_port}" new_config = { "address": new_uri, "chain_id": DEFAULT_GANACHE_CHAIN_ID, "denom": ETHEREUM_DEFAULT_CURRENCY_DENOM, "gas_price_api_key": GAS_PRICE_API_KEY, } return new_config @pytest.fixture(scope="session") def ganache_addr() -> str: """HTTP address to the Ganache node.""" return DEFAULT_GANACHE_ADDR @pytest.fixture(scope="session") def ganache_port() -> int: """Port of the connection to the OEF Node to use during the tests.""" return DEFAULT_GANACHE_PORT @pytest.fixture(scope="session") def ganache_configuration(ethereum_private_key_file): """Get the Ganache configuration for testing purposes.""" return dict( accounts_balances=[ (FUNDED_ETH_PRIVATE_KEY_1, DEFAULT_AMOUNT), (FUNDED_ETH_PRIVATE_KEY_2, DEFAULT_AMOUNT), (FUNDED_ETH_PRIVATE_KEY_3, DEFAULT_AMOUNT), (Path(ethereum_private_key_file).read_text().strip(), DEFAULT_AMOUNT), ], ) @pytest.mark.integration @pytest.mark.ledger @pytest.fixture(scope="session") @action_for_platform("Linux", skip=False) def ganache( ganache_configuration, ganache_addr, ganache_port, timeout: float = 2.0, max_attempts: int = 10, ): """Launch the Ganache image.""" client = docker.from_env() image = GanacheDockerImage( client, "http://127.0.0.1", 8545, config=ganache_configuration ) yield from _launch_image(image, timeout=timeout, max_attempts=max_attempts) def _launch_image( image: DockerImage, timeout: float = 2.0, max_attempts: int = 10 ) -> Generator: """ Launch image. :param image: an instance of Docker image. :param timeout: timeout to launch :param max_attempts: max launch attempts :yield: image """ image.check_skip() image.stop_if_already_running() container = image.create() container.start() logger.info(f"Setting up image {image.tag}...") success = image.wait(max_attempts, timeout) if not success: container.stop() container.remove() pytest.fail(f"{image.tag} doesn't work. Exiting...") else: logger.info("Done!") time.sleep(timeout) yield logger.info(f"Stopping the image {image.tag}...") container.stop() container.remove() ================================================ FILE: plugins/aea-ledger-ethereum/tests/data/dummy_contract/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" ================================================ FILE: plugins/aea-ledger-ethereum/tests/data/dummy_contract/build/some.json ================================================ { "contractName": "erc1155", "abi": [ { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event" }, { "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor" }, { "name": "getAddress", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_addr" } ], "constant": true, "payable": false, "type": "function", "gas": 370 }, { "name": "getHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 11446 }, { "name": "getSingleHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 2041 }, { "name": "supportsInterface", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "bytes32", "name": "_interfaceID" } ], "constant": true, "payable": false, "type": "function", "gas": 868 }, { "name": "is_nonce_used", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "addr" }, { "type": "uint256", "name": "nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 1052 }, { "name": "is_token_id_exists", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "uint256", "name": "token_id" } ], "constant": true, "payable": false, "type": "function", "gas": 928 }, { "name": "safeTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_value" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 80803 }, { "name": "safeBatchTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_values" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 748184 }, { "name": "balanceOf", "outputs": [ { "type": "uint256", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "uint256", "name": "_id" } ], "constant": true, "payable": false, "type": "function", "gas": 1172 }, { "name": "balanceOfBatch", "outputs": [ { "type": "uint256[10]", "name": "out" } ], "inputs": [ { "type": "address[10]", "name": "_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": true, "payable": false, "type": "function", "gas": 7524 }, { "name": "setApprovalForAll", "outputs": [], "inputs": [ { "type": "address", "name": "_operator" }, { "type": "bool", "name": "_approved" } ], "constant": false, "payable": false, "type": "function", "gas": 38136 }, { "name": "isApprovedForAll", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "address", "name": "_operator" } ], "constant": true, "payable": false, "type": "function", "gas": 1301 }, { "name": "createSingle", "outputs": [], "inputs": [ { "type": "address", "name": "_item_owner" }, { "type": "uint256", "name": "_id" }, { "type": "string", "name": "_path" } ], "constant": false, "payable": false, "type": "function", "gas": 597461 }, { "name": "createBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_items_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": false, "payable": false, "type": "function", "gas": 927926 }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "burn", "outputs": [], "inputs": [ { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function", "gas": 40353 }, { "name": "burnBatch", "outputs": [], "inputs": [ { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function", "gas": 382149 }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "owner", "outputs": [ { "type": "address", "name": "out" } ], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1263 } ], "bytecode": "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6000600155600160006301ffc9a760e05260c052604060c020556001600063d9b67a2660e05260c052604060c020553360025561405a56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd5b6100d061405a036100d06000396100d061405a036000f3", "deployedBytecode": "0x600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd", "source": "# Author: Sören Steiger, github.com/ssteiger\n# Author: Fetch.ai, github.com/fetchai\n# License: MIT\n\n# ERC1155 Token Standard\n# https://eips.ethereum.org/EIPS/eip-1155\n\n########################EXTERNAL-CONTRACTS####################################\n\ncontract ERC1155TokenReceiver:\n # Note: The ERC-165 identifier for this interface is 0x4e2312e0.\n\n def onERC1155Received(_operator: address, _from: address, _id: uint256, _value: uint256,\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of a single ERC1155 token type.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))` (i.e. 0xf23a6e61) if it accepts the transfer.\n # This function MUST revert if it rejects the transfer.\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _id The ID of the token being transferred\n # @param _value The amount of tokens being transferred\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))`\n # \"\"\"\n\n def onERC1155BatchReceived(_operator: address, _from: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE],\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of multiple ERC1155 token types.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))` (i.e. 0xbc197c81) if it accepts the transfer(s).\n # This function MUST revert if it rejects the transfer(s).\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the batch transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _ids An array containing ids of each token being transferred (order and length must match _values array)\n # @param _values An array containing amounts of each token being transferred (order and length must match _ids array)\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))`\n # \"\"\"\n\n########################END-EXTERNAL-CONTRACTS####################################\n########################EVENTS####################################\n\nMAX_URI_SIZE: constant(uint256) = 1024\n\nTransferSingle: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address), _id: uint256,\n _value: uint256})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_id` argument MUST be the token type being transferred.\n# The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nTransferBatch: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address),\n _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE]})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_ids` argument MUST be the list of tokens being transferred.\n# The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nApprovalForAll: event({_owner: indexed(address), _operator: indexed(address), _approved: bool})\n# @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled).\n\n\nURI: event({_value: string[MAX_URI_SIZE], _id: indexed(uint256)})\n# @dev MUST emit when the URI is updated for a token ID.\n# URIs are defined in RFC 3986.\n# The URI MUST point to a JSON file that conforms to the \"ERC-1155 Metadata URI JSON Schema\".\n\n########################END-EVENTS####################################\n########################INITIALIZATION####################################\n\nsupportedInterfaces: map(bytes32, bool)\n# https://eips.ethereum.org/EIPS/eip-165\nERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7\nERC1155_INTERFACE_ID: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26\ntokensIdCount: uint256\nowner: public(address)\n\nbalancesOf: map(address, map(uint256, uint256))\nnoncesOf: map(address, map(uint256, bool))\nuri: map(uint256, string[256])\noperators: map(address, map(address, bool))\ntoken_ids: map(uint256, bool)\n\n# This is to be set before contract migration!\nBATCH_SIZE: constant(uint256) = 10\n\n\n@public\ndef __init__():\n \"\"\"\n @notice Called once and only upon contract deployment.\n \"\"\"\n self.tokensIdCount = convert(0, uint256)\n self.supportedInterfaces[ERC165_INTERFACE_ID] = True\n self.supportedInterfaces[ERC1155_INTERFACE_ID] = True\n self.owner = msg.sender\n\n########################END-INITIALIZATION####################################\n########################PRIVATE-FUNCTIONS####################################\n\n\n######### THIS IS A TEMPORARY SOLUTION #################\n@public\n@constant\ndef getAddress(_addr: address) -> bytes32:\n hash: bytes32 = convert(_addr, bytes32)\n return hash\n##################### END ##############################\n\n@private\n@constant\ndef _getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n aggregate_hash: bytes32 = keccak256(concat(convert(_ids[0], bytes32), convert(_from_supplies[0], bytes32), convert(_to_supplies[0], bytes32)))\n for i in range(BATCH_SIZE):\n if not i == 0:\n aggregate_hash = keccak256(concat(aggregate_hash, convert(_ids[i], bytes32), convert(_from_supplies[i], bytes32), convert(_to_supplies[i], bytes32)))\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n aggregate_hash,\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n\n@private\n@constant\ndef getHashOld(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_ids[0], bytes32),\n convert(_ids[1], bytes32),\n convert(_ids[2], bytes32),\n convert(_ids[3], bytes32),\n convert(_ids[4], bytes32),\n convert(_ids[5], bytes32),\n convert(_ids[6], bytes32),\n convert(_ids[7], bytes32),\n convert(_ids[8], bytes32),\n convert(_ids[9], bytes32),\n convert(_from_supplies[0], bytes32),\n convert(_from_supplies[1], bytes32),\n convert(_from_supplies[2], bytes32),\n convert(_from_supplies[3], bytes32),\n convert(_from_supplies[4], bytes32),\n convert(_from_supplies[5], bytes32),\n convert(_from_supplies[6], bytes32),\n convert(_from_supplies[7], bytes32),\n convert(_from_supplies[8], bytes32),\n convert(_from_supplies[9], bytes32),\n convert(_to_supplies[0], bytes32),\n convert(_to_supplies[1], bytes32),\n convert(_to_supplies[2], bytes32),\n convert(_to_supplies[3], bytes32),\n convert(_to_supplies[4], bytes32),\n convert(_to_supplies[5], bytes32),\n convert(_to_supplies[6], bytes32),\n convert(_to_supplies[7], bytes32),\n convert(_to_supplies[8], bytes32),\n convert(_to_supplies[9], bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@private\n@constant\ndef _getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_id, bytes32),\n convert(_from_supply, bytes32),\n convert(_to_supply, bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n\n@private\n@constant\ndef ecrecoverSig(_hash: bytes32, _sig: bytes[65]) -> address:\n \"\"\"\n @notice Check whether the the signature matches the hash.\n @param _hash The hash to be checked.\n @param _sig The signature which is meant to match the hash.\n @return the address which signed the signature or the zero address\n \"\"\"\n if len(_sig) != 65:\n return ZERO_ADDRESS\n # ref. https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d\n # The signature format is a compact form of:\n # {bytes32 r}{bytes32 s}{uint8 v}\n r: bytes32 = extract32(_sig, 0, type=bytes32)\n s: bytes32 = extract32(_sig, 32, type=bytes32)\n v: int128 = convert(slice(_sig, start=64, len=1), int128)\n # Version of signature should be 27 or 28, but 0 and 1 are also possible versions.\n # geth uses [0, 1] and some clients have followed. This might change, see:\n # https://github.com/ethereum/go-ethereum/issues/2053\n if v < 27:\n v += 27\n if v in [27, 28]:\n return ecrecover(_hash, convert(v, uint256), convert(r, uint256), convert(s, uint256))\n return ZERO_ADDRESS\n\n\n@private\n@constant\ndef decode_id(id: uint256) -> int128:\n \"\"\"\n @notice Decodes the id of the token inorder to find out if it NFT or FT.\n @param id: uint256\n @return token_id : int128 (Specified id for FT and NFT.)\n @dev shift(x, -y): returns x with the bits shifted to the right by y places, which is equivalent to dividing x by 2**y.\n \"\"\"\n decoded_token_id: int128 = convert(shift(id, -128), int128)\n decoded_index: int128 = convert(id % 2 ** 128, int128)\n return decoded_token_id\n\n########################END-PRIVATE-FUNCTIONS################################\n########################PUBLIC-FUNCTIONS#####################################\n\n@public\n@constant\ndef supportsInterface(_interfaceID: bytes32) -> bool:\n \"\"\"\n @notice Check whether the interface id is supported.\n @param _interfaceID The interface id\n @return True if the interface id is supported.\n \"\"\"\n return self.supportedInterfaces[_interfaceID]\n\n@public\n@constant\ndef is_nonce_used(addr: address, nonce: uint256) -> bool:\n \"\"\"\n @notice Checks if the given nonce for the give address is unused.\n @param nonce: uint256 the counter of the transaction\n @param address: the address that want to transact.\n \"\"\"\n return self.noncesOf[addr][nonce]\n\n@public\n@constant\ndef is_token_id_exists(token_id: uint256) -> bool:\n \"\"\"\n @notice Checks if the given token_id is already created.\n @param token_id: uint256 the id of the token.\n \"\"\"\n return self.token_ids[token_id]\n\n@public\ndef safeTransferFrom(_from: address, _to: address, _id: uint256, _value: uint256, _data: bytes[256]):\n \"\"\"\n @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if balance of holder for token `_id` is lower than the `_value` sent.\n MUST revert on any other error.\n MUST emit the `TransferSingle` event to reflect the balance change (see \"Safe Transfer Rules\" section of the standard).\n After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _id ID of the token type\n @param _value Transfer amount\n @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n assert self.balancesOf[_from][_id] >= _value, \"Not enough tokens.\"\n\n self.balancesOf[_from][_id] -= _value\n self.balancesOf[_to][_id] += _value\n\n log.TransferSingle(msg.sender, _from, _to, _id, _value)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef safeBatchTransferFrom(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE], _data: bytes[256]):\n \"\"\"\n @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if length of `_ids` is not the same as length of `_values`.\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _ids IDs of each token type (order and length must match _values array)\n @param _values Transfer amounts per token type (order and length must match _ids array)\n @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[_from][id] >= _values[i]\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _values)\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_from][id] -= _values[i]\n self.balancesOf[_to][id] += _values[i]\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _values, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256[BATCH_SIZE],uint256[BATCH_SIZE],bytes)\", bytes32)\n\n\n@public\n@constant\ndef balanceOf(_owner: address, _id: uint256) -> uint256:\n \"\"\"\n @notice Get the balance of an account's tokens.\n @param _owner The address of the token holder\n @param _id ID of the token\n @return The _owner's balance of the token type requested\n \"\"\"\n return self.balancesOf[_owner][_id]\n\n\n@public\n@constant\ndef balanceOfBatch( _owner: address[BATCH_SIZE], _ids: uint256[BATCH_SIZE]) -> uint256[BATCH_SIZE]:\n \"\"\"\n @notice Get the balance of multiple account/token pairs\n @param _owners The addresses of the token holders\n @param _ids ID of the tokens\n @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair)\n \"\"\"\n returnBalances: uint256[BATCH_SIZE]\n for i in range(BATCH_SIZE):\n returnBalances[i] = self.balancesOf[_owner[i]][_ids[i]]\n return returnBalances\n\n\n@public\ndef setApprovalForAll(_operator: address, _approved: bool):\n \"\"\"\n @notice Enable or disable approval for a third party (\"operator\") to manage all of the caller's tokens.\n @dev MUST emit the ApprovalForAll event on success.\n @param _operator Address to add to the set of authorized operators\n @param _approved True if the operator is approved, false to revoke approval\n @return None\n \"\"\"\n (self.operators[msg.sender])[_operator] = _approved\n log.ApprovalForAll(msg.sender, _operator, _approved)\n\n\n@public\n@constant\ndef isApprovedForAll(_owner: address, _operator: address) -> bool:\n \"\"\"\n @notice Queries the approval status of an operator for a given owner.\n @param _owner The owner of the tokens.\n @param _operator Address of authorized operator.\n @return True if the operator is approved, false if not\n \"\"\"\n return (self.operators[_owner])[_operator]\n\n\n@public\ndef createSingle(_item_owner: address, _id: uint256, _path: string[256]):\n \"\"\"\n @notice Create a new token type that we can mint later.\n @param _item_owner The owner of the item.\n @param _id The id of the token.\n @param _path The path to the token data.\n @return None\n \"\"\"\n assert _item_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create item.\"\n self.balancesOf[_item_owner][_id] = 0\n self.tokensIdCount += 1\n self.token_ids[_id] = True\n self.uri[_id] = _path\n log.URI(_path, _id)\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _item_owner, _id, 0)\n\n\n@public\ndef createBatch(_items_owner: address, _ids: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Create new token types that we can mint later.\n @param _items_owner The owner of the items.\n @param _ids The ids of the tokens.\n @return None\n \"\"\"\n assert _items_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create items.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_items_owner][id] = 0\n self.tokensIdCount += 1\n self.token_ids[id] = True\n zero_supply: uint256[BATCH_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _items_owner, _ids, zero_supply)\n\n\n@public\ndef mint(_to: address, _id: uint256, _supply: uint256, _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a token.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _id The id of the token.\n @param _supply The supply to be minted for the token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n decoded_id: int128 = self.decode_id(_id)\n assert decoded_id == 1 or decoded_id == 2\n if decoded_id == 1 :\n assert _supply == 1, \"Cannot mint NFT with _supply more than 1\"\n self.balancesOf[_to][_id] = _supply\n\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, _id, _supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _id, _supply, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef mintBatch(_to: address, _ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a batch of tokens.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _supplies The supply to be minted for each token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n decoded_id: int128 = self.decode_id(id)\n assert decoded_id == 1 or decoded_id == 2\n\n if decoded_id == 1 :\n assert _supplies[i] == 1\n\n self.balancesOf[_to][id] = _supplies[i]\n\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, _ids, _supplies)\n\n for i in range(BATCH_SIZE):\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _ids[i], _supplies[i], _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef burn(_id: uint256, _supply: uint256):\n \"\"\"\n @notice Burns the supply of the specified token.\n @param _id The id of the token\n @param _supply Supply to be burned\n @return None\n \"\"\"\n assert self.balancesOf[msg.sender][_id] >= _supply, \"Not enough tokens to burn.\"\n self.balancesOf[msg.sender][_id] -= _supply\n log.TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, _id, _supply)\n\n\n@public\ndef burnBatch(_ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Burns the supply of the specified tokens.\n @dev At this point anyone can burn items if they own it.\n @param _ids The ids of the token\n @param _supplies Supplies to be burned\n @return None\n \"\"\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[msg.sender][id] >= _supplies[i]\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[msg.sender][id] -= _supplies[i]\n log.TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, _ids, _supplies)\n\n\n@public\n@payable\ndef tradeBatch(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supplies[i] > 0 and _to_supplies[i] > 0\n MUST revert if len(_ids) != len(_from_supplies) != len(_to_supplies)\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `positive and negative values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n\n assert _from == msg.sender or (self.operators[_from])[msg.sender], \"_from must be the sender or approved address\"\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n assert _to_supplies[i] == 0\n assert self.balancesOf[_from][id] >= _from_supplies[i]\n else:\n assert _from_supplies[i] == 0\n assert self.balancesOf[_to][id] >= _to_supplies[i]\n\n # Create hash from variables.\n hash: bytes32 = self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n self.balancesOf[_from][id] -= _from_supplies[i]\n self.balancesOf[_to][id] += _from_supplies[i]\n else:\n self.balancesOf[_from][id] += _to_supplies[i]\n self.balancesOf[_to][id] -= _to_supplies[i]\n\n send(_to, msg.value)\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _from_supplies)\n log.TransferBatch(msg.sender, _to, _from, _ids, _to_supplies)\n\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _to, _ids, _from_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155BatchReceived(msg.sender, _from, _ids, _to_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n\n@public\n@payable\ndef trade(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supply > 0 and _to_supply > 0\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_id` is lower than the respective amount in `positive or negative value` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The from address (seller of eth, potential receiver of tokens).\n @param _to The receiver address (receiver of tokens).\n @param _id The id of the token\n @param _from_supply The change in value of token (for _from)\n @param _to_supply The change in value of token (for _to)\n @param _value_eth The value of the ETH sent to the _from address.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n if _from_supply > 0:\n assert _to_supply == 0\n assert self.balancesOf[_from][_id] >= _from_supply\n else:\n assert _from_supply == 0\n assert self.balancesOf[_to][_id] >= _to_supply\n\n # Create hash from variables.\n hash: bytes32 = self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n if _from_supply > 0:\n self.balancesOf[_from][_id] -= _from_supply\n self.balancesOf[_to][_id] += _from_supply\n else:\n self.balancesOf[_from][_id] += _to_supply\n self.balancesOf[_to][_id] -= _to_supply\n\n send(_to, msg.value)\n\n log.TransferSingle(msg.sender, _from, _to, _id, _from_supply)\n log.TransferSingle(msg.sender, _to, _from, _id, _to_supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _to, _id, _from_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155Received(msg.sender, _from, _id, _to_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n", "sourcePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/erc1155.vy", "compiler": { "name": "vyper", "version": "0.1.0b12+commit.a01cdc8" }, "networks": { "1583918911727": { "events": { "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" }, "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8": { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8" }, "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31" }, "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event", "signature": "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b" } }, "links": {}, "address": "0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7", "transactionHash": "0x816b272ebd644b189a3addfbd429b0ea0fa7b2403cca3aa038bcd59f09d9c116" } }, "schemaVersion": "3.0.19", "updatedAt": "2020-03-11T13:47:51.885Z", "networkType": "ethereum" } ================================================ FILE: plugins/aea-ledger-ethereum/tests/data/dummy_contract/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" from aea.contracts.base import Contract from aea.crypto.base import LedgerApi class DummyContract(Contract): """The some contract class.""" @classmethod def some_method(cls, ledger_api: LedgerApi, contract_address: str) -> None: """Some method.""" pass ================================================ FILE: plugins/aea-ledger-ethereum/tests/data/dummy_contract/contract.yaml ================================================ name: dummy author: fetchai version: 0.1.0 type: contract description: A test contract license: Apache-2.0 aea_version: '>=0.9.0, <0.10.0' fingerprint: __init__.py: QmWbNjFh6E5V4n2qBwyZyXZdmmHvcZSVnwKrcM34MAE56S build/some.json: Qma5n7au2NDCg1nLwYfYnmFNwWChFuXtu65w5DV7wAZRvw build/some.wasm: Qmc9gthbdwRSywinTHKjRVQdFzrKTxUuLDx2ryNfQp1xqf contract.py: QmYfn2V3tXv7MCyhT5Kz5ArtX2FZWHC1jfeRmqCNsRxDL5 fingerprint_ignore_patterns: [] build_entrypoint: path/to/script.py class_name: DummyContract contract_interface_paths: cosmos: build/some.wasm ethereum: build/some.json fetchai: build/some.wasm dependencies: {} ================================================ FILE: plugins/aea-ledger-ethereum/tests/data/ethereum_private_key.txt ================================================ 0x6F611408F7EF304947621C51A4B7D84A13A2B9786E9F984DA790A096E8260C64 ================================================ FILE: plugins/aea-ledger-ethereum/tests/docker_image.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains testing utilities.""" import logging import re import shutil import subprocess import time from abc import ABC, abstractmethod from typing import Dict, List, Optional import docker import pytest import requests from docker import DockerClient from docker.models.containers import Container from aea.exceptions import enforce logger = logging.getLogger(__name__) class DockerImage(ABC): """A class to wrap interatction with a Docker image.""" MINIMUM_DOCKER_VERSION = (19, 0, 0) def __init__(self, client: docker.DockerClient): """Initialize.""" self._client = client def check_skip(self): """ Check whether the test should be skipped. By default, nothing happens. """ self._check_docker_binary_available() def _check_docker_binary_available(self): """Check the 'Docker' CLI tool is in the OS PATH.""" result = shutil.which("docker") if result is None: pytest.skip("Docker not in the OS Path; skipping the test") result = subprocess.run( # nosec ["docker", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if result.returncode != 0: pytest.skip("'docker --version' failed with exit code {result.returncode}") match = re.search( r"Docker version ([0-9]+)\.([0-9]+)\.([0-9]+)", result.stdout.decode("utf-8"), ) if match is None: pytest.skip("cannot read version from the output of 'docker --version'") version = (int(match.group(1)), int(match.group(2)), int(match.group(3))) if version < self.MINIMUM_DOCKER_VERSION: pytest.skip( f"expected Docker version to be at least {'.'.join(self.MINIMUM_DOCKER_VERSION)}, found {'.'.join(version)}" ) @property @abstractmethod def tag(self) -> str: """Return the tag of the image.""" def stop_if_already_running(self): """Stop the running images with the same tag, if any.""" client = docker.from_env() for container in client.containers.list(): if self.tag in container.image.tags: logger.info(f"Stopping image {self.tag}...") container.stop() @abstractmethod def create(self) -> Container: """Instantiate the image in a container.""" @abstractmethod def wait(self, max_attempts: int = 15, sleep_rate: float = 1.0) -> bool: """ Wait until the image is running. :param max_attempts: max number of attempts. :param sleep_rate: the amount of time to sleep between different requests. :return: True if the wait was successful, False otherwise. """ return True class GanacheDockerImage(DockerImage): """Wrapper to Ganache Docker image.""" def __init__( self, client: DockerClient, addr: str, port: int, config: Optional[Dict] = None, gas_limit: int = 10000000000000, ): """ Initialize the Ganache Docker image. :param client: the Docker client. :param addr: the address. :param port: the port. :param config: optional configuration to command line. :param gas_limit: the gas limit for blocks. """ super().__init__(client) self._addr = addr self._port = port self._config = config or {} self._gas_limit = gas_limit @property def tag(self) -> str: """Get the image tag.""" return "trufflesuite/ganache-cli:latest" def _make_ports(self) -> Dict: """Make ports dictionary for Docker.""" return {f"{self._port}/tcp": ("0.0.0.0", self._port)} # nosec def _build_command(self) -> List[str]: """Build command.""" cmd = ["ganache-cli"] cmd += ["--gasLimit=" + str(self._gas_limit)] accounts_balances = self._config.get("accounts_balances", []) for account, balance in accounts_balances: cmd += [f"--account='{account},{balance}'"] return cmd def create(self) -> Container: """Create the container.""" cmd = self._build_command() container = self._client.containers.run( self.tag, command=cmd, detach=True, ports=self._make_ports() ) return container def wait(self, max_attempts: int = 15, sleep_rate: float = 1.0) -> bool: """Wait until the image is up.""" request = dict(jsonrpc=2.0, method="web3_clientVersion", params=[], id=1) for i in range(max_attempts): try: response = requests.post(f"{self._addr}:{self._port}", json=request) enforce(response.status_code == 200, "") return True except Exception: logger.info( "Attempt %s failed. Retrying in %s seconds...", i, sleep_rate ) time.sleep(sleep_rate) return False ================================================ FILE: plugins/aea-ledger-ethereum/tests/test_ethereum.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the ethereum module.""" import hashlib import logging import tempfile import time from pathlib import Path from unittest.mock import MagicMock, patch import pytest from aea_ledger_ethereum import ( AttributeDictTranslator, EthereumApi, EthereumCrypto, EthereumFaucetApi, EthereumHelper, LruLockWrapper, get_gas_price_strategy, requests, ) from web3 import Web3 from web3._utils.request import _session_cache as session_cache from web3.gas_strategies.rpc import rpc_gas_price_strategy from aea.crypto.helpers import DecryptError, KeyIsIncorrect from tests.conftest import DEFAULT_GANACHE_CHAIN_ID, MAX_FLAKY_RERUNS, ROOT_DIR def test_attribute_dict_translator(): """Test the AttributeDictTranslator.""" di = { "1": None, "2": True, "3": b"some", "4": 0.1, "5": [1, None, True, {}], "6": {"hex": "0x01"}, } res = AttributeDictTranslator.from_dict(di) assert AttributeDictTranslator.to_dict(res) == di def test_creation(ethereum_private_key_file): """Test the creation of the crypto_objects.""" assert EthereumCrypto(), "Managed to initialise the eth_account" assert EthereumCrypto( ethereum_private_key_file ), "Managed to load the eth private key" def test_initialization(): """Test the initialisation of the variables.""" account = EthereumCrypto() assert account.entity is not None, "The property must return the account." assert ( account.address is not None and type(account.address) == str ), "After creation the display address must not be None" assert ( account.public_key is not None and type(account.public_key) == str ), "After creation the public key must no be None" assert account.entity is not None, "After creation the entity must no be None" def test_derive_address(): """Test the get_address_from_public_key method""" account = EthereumCrypto() address = EthereumApi.get_address_from_public_key(account.public_key) assert account.address == address, "Address derivation incorrect" def test_sign_and_recover_message(ethereum_private_key_file): """Test the signing and the recovery function for the eth_crypto.""" account = EthereumCrypto(ethereum_private_key_file) sign_bytes = account.sign_message(message=b"hello") assert len(sign_bytes) > 0, "The len(signature) must not be 0" recovered_addresses = EthereumApi.recover_message( message=b"hello", signature=sign_bytes ) assert len(recovered_addresses) == 1, "Wrong number of addresses recovered." assert ( recovered_addresses[0] == account.address ), "Failed to recover the correct address." def test_sign_and_recover_message_deprecated(ethereum_private_key_file): """Test the signing and the recovery function for the eth_crypto.""" account = EthereumCrypto(ethereum_private_key_file) message = b"hello" message_hash = hashlib.sha256(message).digest() sign_bytes = account.sign_message(message=message_hash, is_deprecated_mode=True) assert len(sign_bytes) > 0, "The len(signature) must not be 0" recovered_addresses = EthereumApi.recover_message( message=message_hash, signature=sign_bytes, is_deprecated_mode=True ) assert len(recovered_addresses) == 1, "Wrong number of addresses recovered." assert ( recovered_addresses[0] == account.address ), "Failed to recover the correct address." def test_sign_and_recover_message_public_key(ethereum_private_key_file): """Test the signing and the recovery function for the eth_crypto.""" account = EthereumCrypto(ethereum_private_key_file) sign_bytes = account.sign_message(message=b"hello") assert len(sign_bytes) > 0, "The len(signature) must not be 0" recovered_public_keys = EthereumApi.recover_public_keys_from_message( message=b"hello", signature=sign_bytes ) assert len(recovered_public_keys) == 1, "Wrong number of public keys recovered." assert ( EthereumApi.get_address_from_public_key(recovered_public_keys[0]) == account.address ), "Failed to recover the correct address." def test_get_hash(): """Test the get hash functionality.""" expected_hash = "0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8" hash_ = EthereumApi.get_hash(message=b"hello") assert expected_hash == hash_ def test_dump_positive(ethereum_private_key_file): """Test dump.""" account = EthereumCrypto(ethereum_private_key_file) account.dump(MagicMock()) def test_api_creation(ethereum_testnet_config): """Test api instantiation.""" assert EthereumApi(**ethereum_testnet_config), "Failed to initialise the api" def test_api_none(ethereum_testnet_config): """Test the "api" of the cryptoApi is none.""" eth_api = EthereumApi(**ethereum_testnet_config) assert eth_api.api is not None, "The api property is None." def test_validate_address(): """Test the is_valid_address functionality.""" account = EthereumCrypto() assert EthereumApi.is_valid_address(account.address) assert not EthereumApi.is_valid_address(account.address + "wrong") @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_balance(ethereum_testnet_config, ganache, ethereum_private_key_file): """Test the balance is zero for a new account.""" ethereum_api = EthereumApi(**ethereum_testnet_config) ec = EthereumCrypto() balance = ethereum_api.get_balance(ec.address) assert balance == 0, "New account has a positive balance." ec = EthereumCrypto(private_key_path=ethereum_private_key_file) balance = ethereum_api.get_balance(ec.address) assert balance > 0, "Existing account has no balance." @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_state(ethereum_testnet_config, ganache): """Test that get_state() with 'getBlock' function returns something containing the block number.""" ethereum_api = EthereumApi(**ethereum_testnet_config) callable_name = "getBlock" args = ("latest",) block = ethereum_api.get_state(callable_name, *args) assert block is not None, "response to getBlock is empty." assert "number" in block, "response to getBlock() does not contain 'number'" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_construct_sign_and_submit_transfer_transaction( ethereum_testnet_config, ganache, ethereum_private_key_file ): """Test the construction, signing and submitting of a transfer transaction.""" account = EthereumCrypto(private_key_path=ethereum_private_key_file) ec2 = EthereumCrypto() ethereum_api = EthereumApi(**ethereum_testnet_config) amount = 40000 tx_nonce = ethereum_api.generate_tx_nonce(ec2.address, account.address) transfer_transaction = ethereum_api.get_transfer_transaction( sender_address=account.address, destination_address=ec2.address, amount=amount, tx_fee=30000, tx_nonce=tx_nonce, chain_id=DEFAULT_GANACHE_CHAIN_ID, ) assert ( isinstance(transfer_transaction, dict) and len(transfer_transaction) == 7 ), "Incorrect transfer_transaction constructed." signed_transaction = account.sign_transaction(transfer_transaction) assert ( isinstance(signed_transaction, dict) and len(signed_transaction) == 5 ), "Incorrect signed_transaction constructed." transaction_digest = ethereum_api.send_signed_transaction(signed_transaction) assert transaction_digest is not None, "Failed to submit transfer transaction!" not_settled = True elapsed_time = 0 while not_settled and elapsed_time < 20: elapsed_time += 1 time.sleep(2) transaction_receipt = ethereum_api.get_transaction_receipt(transaction_digest) if transaction_receipt is None: continue is_settled = ethereum_api.is_transaction_settled(transaction_receipt) not_settled = not is_settled assert transaction_receipt is not None, "Failed to retrieve transaction receipt." assert is_settled, "Failed to verify tx!" tx = ethereum_api.get_transaction(transaction_digest) is_valid = ethereum_api.is_transaction_valid( tx, ec2.address, account.address, tx_nonce, amount ) assert is_valid, "Failed to settle tx correctly!" assert tx != transaction_receipt, "Should not be same!" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_wealth_positive(caplog): """Test the balance is zero for a new account.""" with caplog.at_level(logging.DEBUG, logger="aea.crypto.ethereum._default_logger"): ethereum_faucet_api = EthereumFaucetApi() ec = EthereumCrypto() ethereum_faucet_api.get_wealth(ec.address, "some_url") assert ( "Invalid URL" in caplog.text ), f"Cannot find message in output: {caplog.text}" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_deploy_transaction(ethereum_testnet_config, ganache): """Test the get deploy transaction method.""" ethereum_api = EthereumApi(**ethereum_testnet_config) ec2 = EthereumCrypto() interface = {"abi": [], "bytecode": b""} deploy_tx = ethereum_api.get_deploy_transaction( contract_interface=interface, deployer_address=ec2.address, ) assert type(deploy_tx) == dict and len(deploy_tx) == 6 assert all( key in ["from", "value", "gas", "gasPrice", "nonce", "data"] for key in deploy_tx.keys() ) def test_load_contract_interface(): """Test the load_contract_interface method.""" path = Path(ROOT_DIR, "tests", "data", "dummy_contract", "build", "some.json") result = EthereumApi.load_contract_interface(path) assert "abi" in result assert "bytecode" in result @patch.object(EthereumApi, "_try_get_transaction_count", return_value=None) def test_ethereum_api_get_transfer_transaction(*args): """Test EthereumApi.get_transfer_transaction.""" ethereum_api = EthereumApi() assert ethereum_api.get_transfer_transaction(*[MagicMock()] * 7) is None def test_ethereum_api_get_deploy_transaction(*args): """Test EthereumApi.get_deploy_transaction.""" ethereum_api = EthereumApi() with patch.object(ethereum_api.api.eth, "getTransactionCount", return_value=None): assert ( ethereum_api.get_deploy_transaction( {"acc": "acc"}, "0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7" ) is None ) def test_session_cache(): """Test session cache.""" assert isinstance(session_cache, LruLockWrapper) session_cache[1] = 1 assert session_cache[1] == 1 del session_cache[1] assert 1 not in session_cache def test_gas_price_strategy_eth_gasstation(): """Test the gas price strategy when using eth gasstation.""" gas_price_strategy = "fast" excepted_result = 10 callable_ = get_gas_price_strategy(gas_price_strategy, "api_key") with patch.object( requests, "get", return_value=MagicMock( status_code=200, json=MagicMock(return_value={gas_price_strategy: excepted_result}), ), ): result = callable_(Web3, "tx_params") assert result == excepted_result / 10 * 1000000000 def test_gas_price_strategy_not_supported(caplog): """Test the gas price strategy when not supported.""" gas_price_strategy = "superfast" with caplog.at_level(logging.DEBUG, logger="aea.crypto.ethereum._default_logger"): callable_ = get_gas_price_strategy(gas_price_strategy, "api_key") assert callable_ == rpc_gas_price_strategy assert ( f"Gas price strategy `{gas_price_strategy}` not in list of supported modes:" in caplog.text ) def test_gas_price_strategy_no_api_key(caplog): """Test the gas price strategy when no api key is provided.""" gas_price_strategy = "fast" with caplog.at_level(logging.DEBUG, logger="aea.crypto.ethereum._default_logger"): callable_ = get_gas_price_strategy(gas_price_strategy, None) assert callable_ == rpc_gas_price_strategy assert ( "No ethgasstation api key provided. Falling back to `rpc_gas_price_strategy`." in caplog.text ) def test_dump_load_with_password(): """Test dumping and loading a key with password.""" with tempfile.TemporaryDirectory() as dirname: encrypted_file_name = Path(dirname, "eth_key_encrypted") password = "somePwd" # nosec ec = EthereumCrypto() ec.dump(encrypted_file_name, password) assert encrypted_file_name.exists() with pytest.raises(DecryptError, match="Decrypt error! Bad password?"): ec2 = EthereumCrypto.load_private_key_from_path( encrypted_file_name, "wrongPassw" ) ec2 = EthereumCrypto(encrypted_file_name, password) assert ec2.private_key == ec.private_key def test_load_errors(): """Test load errors: bad password, no password specified.""" ec = EthereumCrypto() with patch.object(EthereumCrypto, "load", return_value="bad sTring"): with pytest.raises(KeyIsIncorrect, match="Try to specify `password`"): ec.load_private_key_from_path("any path") with pytest.raises(KeyIsIncorrect, match="Wrong password?"): ec.load_private_key_from_path("any path", password="some") def test_decrypt_error(): """Test bad password error on decrypt.""" ec = EthereumCrypto() ec._pritvate_key = EthereumCrypto.generate_private_key() password = "test" encrypted_data = ec.encrypt(password=password) with pytest.raises(DecryptError, match="Bad password"): ec.decrypt(encrypted_data, password + "some") with patch( "aea_ledger_ethereum.ethereum.Account.decrypt", side_effect=ValueError("expected"), ): with pytest.raises(ValueError, match="expected"): ec.decrypt(encrypted_data, password + "some") def test_helper_get_contract_address(): """Test EthereumHelper.get_contract_address.""" assert EthereumHelper.get_contract_address({"contractAddress": "123"}) == "123" ================================================ FILE: plugins/aea-ledger-ethereum/tests/test_ethereum_contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the ethereum module.""" from pathlib import Path from typing import Dict, cast import pytest from aea_ledger_ethereum import EthereumApi, EthereumCrypto from tests.conftest import ETHEREUM_PRIVATE_KEY_PATH, MAX_FLAKY_RERUNS, ROOT_DIR @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_contract_instance(ethereum_testnet_config, ganache): """Test the get contract instance method.""" ec = EthereumCrypto(private_key_path=ETHEREUM_PRIVATE_KEY_PATH) ethereum_api = EthereumApi(**ethereum_testnet_config) full_path = Path(ROOT_DIR, "tests", "data", "dummy_contract", "build", "some.json") contract_interface = ethereum_api.load_contract_interface(full_path) tx = ethereum_api.get_deploy_transaction( contract_interface, ec.address, gas=5000000 ) gas = ethereum_api.api.eth.estimateGas(transaction=tx) tx["gas"] = gas tx_signed = ec.sign_transaction(tx) tx_receipt = ethereum_api.send_signed_transaction(tx_signed) receipt = ethereum_api.get_transaction_receipt(tx_receipt) erc1155_contract_address = cast(Dict, receipt)["contractAddress"] interface = {"abi": [], "bytecode": b""} instance = ethereum_api.get_contract_instance( contract_interface=interface, contract_address=erc1155_contract_address, ) assert str(type(instance)) == "" instance = ethereum_api.get_contract_instance( contract_interface=interface, ) assert ( str(type(instance)) == "" ) ================================================ FILE: plugins/aea-ledger-fetchai/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2019 Fetch.AI Limited Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: plugins/aea-ledger-fetchai/MANIFEST.in ================================================ include README.md LICENSE HISTORY.md recursive-include aea_ledger_fetchai recursive-include tests * ================================================ FILE: plugins/aea-ledger-fetchai/README.md ================================================ # Fetch.AI crypto plug-in Fetch.AI crypto plug-in for the AEA framework. ## Install ``` bash python setup.py install ``` ## Run tests ``` bash python setup.py test ``` ================================================ FILE: plugins/aea-ledger-fetchai/aea_ledger_fetchai/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Python package wrapping the public and private key cryptography and ledger api. The module '_cosmos.py' must be the same of "aea_ledger_cosmos.cosmos". """ from .fetchai import * # noqa isort:skip from .fetchai import _FETCH, _FETCHAI # noqa isort:skip ================================================ FILE: plugins/aea-ledger-fetchai/aea_ledger_fetchai/_cosmos.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Cosmos module wrapping the public and private key cryptography and ledger api.""" import base64 import gzip import hashlib import json import logging import time from collections import namedtuple from itertools import chain from json.decoder import JSONDecodeError from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, cast from Crypto.Cipher import AES # nosec from Crypto.Protocol.KDF import scrypt # nosec from Crypto.Random import get_random_bytes # nosec from bech32 import ( # pylint: disable=wrong-import-order bech32_decode, bech32_encode, convertbits, ) from cosmpy.auth.rest_client import AuthRestClient from cosmpy.bank.rest_client import BankRestClient, QueryBalanceRequest from cosmpy.common.rest_client import RestClient from cosmpy.cosmwasm.rest_client import CosmWasmRestClient from cosmpy.crypto.hashfuncs import ripemd160 from cosmpy.protos.cosmos.auth.v1beta1.auth_pb2 import BaseAccount from cosmpy.protos.cosmos.auth.v1beta1.query_pb2 import QueryAccountRequest from cosmpy.protos.cosmos.bank.v1beta1.tx_pb2 import MsgSend from cosmpy.protos.cosmos.base.v1beta1.coin_pb2 import Coin from cosmpy.protos.cosmos.crypto.secp256k1.keys_pb2 import PubKey as ProtoPubKey from cosmpy.protos.cosmos.tx.signing.v1beta1.signing_pb2 import SignMode from cosmpy.protos.cosmos.tx.v1beta1.service_pb2 import ( BroadcastMode, BroadcastTxRequest, GetTxRequest, ) from cosmpy.protos.cosmos.tx.v1beta1.tx_pb2 import ( AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, Tx, TxBody, ) from cosmpy.protos.cosmwasm.wasm.v1.query_pb2 import QuerySmartContractStateRequest from cosmpy.protos.cosmwasm.wasm.v1.tx_pb2 import ( MsgExecuteContract, MsgInstantiateContract, MsgStoreCode, ) from cosmpy.tx.rest_client import TxRestClient from ecdsa import ( # type: ignore # pylint: disable=wrong-import-order SECP256k1, SigningKey, VerifyingKey, ) from ecdsa.util import ( # type: ignore # pylint: disable=wrong-import-order sigencode_string_canonize, ) from google.protobuf.any_pb2 import Any as ProtoAny from google.protobuf.json_format import MessageToDict, ParseDict from aea.common import Address, JSONLike from aea.crypto.base import Crypto, FaucetApi, Helper, LedgerApi from aea.crypto.helpers import KeyIsIncorrect, hex_to_bytes_for_key from aea.exceptions import AEAEnforceError from aea.helpers import http_requests as requests from aea.helpers.base import try_decorator _default_logger = logging.getLogger(__name__) _COSMOS = "cosmos" TESTNET_NAME = "testnet" DEFAULT_FAUCET_URL = "INVALID_URL" DEFAULT_ADDRESS = "https://cosmos.bigdipper.live" DEFAULT_CURRENCY_DENOM = "uatom" DEFAULT_CHAIN_ID = "cosmoshub-3" DEFAULT_GAS_AMOUNT = 1550000 # Txs will fail if gas_limit is higher than MAXIMUM_GAS_AMOUNT MAXIMUM_GAS_AMOUNT = 2000000 _BYTECODE = "wasm_byte_code" class DataEncrypt: """Class to encrypt/decrypt data strings with password provided.""" @classmethod def _aes_encrypt( cls, password: str, data: bytes ) -> Tuple[bytes, bytes, bytes, bytes]: """ Encryption schema for private keys :param password: plaintext password to use for encryption :param data: plaintext data to encrypt :return: encrypted data, nonce, tag, salt """ key, salt = cls._password_to_key_and_salt(password) cipher = AES.new(key, AES.MODE_EAX) ciphertext, tag = cipher.encrypt_and_digest(data) # type:ignore return ciphertext, cipher.nonce, tag, salt # type:ignore @staticmethod def _password_to_key_and_salt( password: str, salt: Optional[bytes] = None ) -> Tuple[bytes, bytes]: salt = salt or get_random_bytes(16) key = scrypt(password, salt, 16, N=2**14, r=8, p=1) # type: ignore return key, salt # type: ignore @classmethod def _aes_decrypt( cls, password: str, encrypted_data: bytes, nonce: bytes, tag: bytes, salt: bytes ) -> bytes: """ Decryption schema for private keys. :param password: plaintext password used for encryption :param encrypted_data: data to decrypt :param nonce: bytes :param tag: bytes :param salt: bytes :return: decrypted data as plaintext """ # Hash password key, _ = cls._password_to_key_and_salt(password, salt) cipher = AES.new(key, AES.MODE_EAX, nonce) try: decrypted_data = cipher.decrypt_and_verify( # type:ignore encrypted_data, tag ) except ValueError as e: if e.args[0] == "MAC check failed": raise ValueError("Decrypt error! Bad password?") from e raise # pragma: nocover return decrypted_data @classmethod def encrypt(cls, data: bytes, password: str) -> bytes: """Encrypt data with password.""" if not isinstance(data, bytes): # pragma: nocover raise ValueError(f"data has to be bytes! not {type(data)}") encrypted_data, nonce, tag, salt = cls._aes_encrypt(password, data) json_data = { "encrypted_data": cls.bytes_encode(encrypted_data), "nonce": cls.bytes_encode(nonce), "tag": cls.bytes_encode(tag), "salt": cls.bytes_encode(salt), } return json.dumps(json_data).encode() @staticmethod def bytes_encode(data: bytes) -> str: """Encode bytes to ascii friendly string.""" return base64.b64encode(data).decode() @staticmethod def bytes_decode(data: str) -> bytes: """Decode ascii friendly string to bytes.""" return base64.b64decode(data) @classmethod def decrypt(cls, encrypted_data: bytes, password: str) -> bytes: """Decrypt data with password provided.""" if not isinstance(encrypted_data, bytes): # pragma: nocover raise ValueError( f"encrypted_data has to be str! not {type(encrypted_data)}" ) try: json_data = json.loads(encrypted_data) decrypted_data = cls._aes_decrypt( password, encrypted_data=cls.bytes_decode(json_data["encrypted_data"]), nonce=cls.bytes_decode(json_data["nonce"]), tag=cls.bytes_decode(json_data["tag"]), salt=cls.bytes_decode(json_data["salt"]), ) return decrypted_data except (KeyError, JSONDecodeError) as e: raise ValueError(f"Bad encrypted key format!: {str(e)}") from e class CosmosHelper(Helper): """Helper class usable as Mixin for CosmosApi or as standalone class.""" address_prefix = _COSMOS @staticmethod def is_transaction_settled(tx_receipt: JSONLike) -> bool: """ Check whether a transaction is settled or not. :param tx_receipt: the receipt of the transaction. :return: True if the transaction has been settled, False o/w. """ is_successful = False if tx_receipt is not None: code = tx_receipt.get("code", None) is_successful = code is None if not is_successful: _default_logger.warning( # pragma: nocover f"Transaction {tx_receipt.get('txhash')} not settled. Raw log: {tx_receipt.get('rawLog')}" ) return is_successful @classmethod def get_code_id(cls, tx_receipt: JSONLike) -> Optional[int]: """ Retrieve the `code_id` from a transaction receipt. :param tx_receipt: the receipt of the transaction. :return: the code id, if present """ code_id: Optional[int] = None try: attributes = cls.get_event_attributes(tx_receipt) code_id = int(attributes["code_id"]) except (KeyError, IndexError): # pragma: nocover code_id = None return code_id @staticmethod def get_event_attributes(tx_receipt: JSONLike) -> Dict: """ Retrieve events attributes from tx receipt. :param tx_receipt: the receipt of the transaction. :return: dict """ return { i["key"]: i["value"] for i in chain(*[i["attributes"] for i in tx_receipt["logs"][0]["events"]]) # type: ignore } @classmethod def get_contract_address(cls, tx_receipt: JSONLike) -> Optional[str]: """ Retrieve the `contract_address` from a transaction receipt. :param tx_receipt: the receipt of the transaction. :return: the contract address, if present """ contract_address: Optional[str] = None try: attributes = cls.get_event_attributes(tx_receipt) contract_address = attributes["_contract_address"] except (KeyError, IndexError): # pragma: nocover contract_address = None return contract_address @staticmethod def is_transaction_valid( tx: JSONLike, seller: Address, client: Address, tx_nonce: str, amount: int, ) -> bool: """ Check whether a transaction is valid or not. :param tx: the transaction. :param seller: the address of the seller. :param client: the address of the client. :param tx_nonce: the transaction nonce. :param amount: the amount we expect to get from the transaction. :return: True if the random_message is equals to tx['input'] """ if tx is None: return False # pragma: no cover try: _tx = cast(dict, tx.get("tx", {})).get("body", {}).get("messages", [])[0] recovered_amount = int(_tx.get("amount")[0].get("amount")) sender = _tx.get("fromAddress") recipient = _tx.get("toAddress") is_valid = ( recovered_amount == amount and sender == client and recipient == seller ) except (KeyError, IndexError): # pragma: no cover is_valid = False return is_valid @staticmethod def generate_tx_nonce(seller: Address, client: Address) -> str: """ Generate a unique hash to distinguish transactions with the same terms. :param seller: the address of the seller. :param client: the address of the client. :return: return the hash in hex. """ time_stamp = int(time.time()) aggregate_hash = hashlib.sha256( b"".join([seller.encode(), client.encode(), time_stamp.to_bytes(32, "big")]) ) return aggregate_hash.hexdigest() @classmethod def get_address_from_public_key(cls, public_key: str) -> str: """ Get the address from the public key. :param public_key: the public key :return: str """ public_key_bytes = bytes.fromhex(public_key) s = hashlib.new("sha256", public_key_bytes).digest() r = ripemd160(s) five_bit_r = convertbits(r, 8, 5) if five_bit_r is None: # pragma: nocover raise AEAEnforceError("Unsuccessful bech32.convertbits call") address = bech32_encode(cls.address_prefix, five_bit_r) return address @classmethod def recover_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[Address, ...]: """ Recover the addresses from the hash. :param message: the message we expect :param signature: the transaction signature :param is_deprecated_mode: if the deprecated signing was used :return: the recovered addresses """ public_keys = cls.recover_public_keys_from_message(message, signature) addresses = [ cls.get_address_from_public_key(public_key) for public_key in public_keys ] return tuple(addresses) @classmethod def recover_public_keys_from_message( cls, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[str, ...]: """ Get the public key used to produce the `signature` of the `message` :param message: raw bytes used to produce signature :param signature: signature of the message :param is_deprecated_mode: if the deprecated signing was used :return: the recovered public keys """ signature_b64 = base64.b64decode(signature) verifying_keys = VerifyingKey.from_public_key_recovery( signature_b64, message, SECP256k1, hashfunc=hashlib.sha256, ) public_keys = [ verifying_key.to_string("compressed").hex() for verifying_key in verifying_keys ] return tuple(public_keys) @staticmethod def get_hash(message: bytes) -> str: """ Get the hash of a message. :param message: the message to be hashed. :return: the hash of the message. """ digest = hashlib.sha256(message).hexdigest() return digest @classmethod def is_valid_address(cls, address: Address) -> bool: """ Check if the address is valid. :param address: the address to validate :return: whether address is valid or not """ result = bech32_decode(address) return result != (None, None) and result[0] == cls.address_prefix @classmethod def load_contract_interface(cls, file_path: Path) -> Dict[str, str]: """ Load contract interface. :param file_path: the file path to the interface :return: the interface """ with open(file_path, "rb") as interface_file_cosmos: contract_interface = { _BYTECODE: str( base64.b64encode( gzip.compress(interface_file_cosmos.read(), 6) ).decode() ) } return contract_interface class CosmosCrypto(Crypto[SigningKey]): """Class wrapping the Account Generation from Ethereum ledger.""" identifier = _COSMOS helper = CosmosHelper def __init__( self, private_key_path: Optional[str] = None, password: Optional[str] = None ) -> None: """ Instantiate an ethereum crypto object. :param private_key_path: the private key path of the agent :param password: the password to encrypt/decrypt the private key. """ super().__init__(private_key_path=private_key_path, password=password) self._public_key = self.entity.get_verifying_key().to_string("compressed").hex() self._address = self.helper.get_address_from_public_key(self.public_key) @property def private_key(self) -> str: """ Return a private key. :return: a private key string """ return self.entity.to_string().hex() @property def public_key(self) -> str: """ Return a public key in hex format. :return: a public key string in hex format """ return self._public_key @property def address(self) -> str: """ Return the address for the key pair. :return: a display_address str """ return self._address @classmethod def load_private_key_from_path( cls, file_name: str, password: Optional[str] = None ) -> SigningKey: """ Load a private key in hex format from a file. :param file_name: the path to the hex file. :param password: the password to encrypt/decrypt the private key. :return: the Entity. """ private_key = cls.load(file_name, password) try: signing_key = SigningKey.from_string( hex_to_bytes_for_key(private_key), curve=SECP256k1 ) except KeyIsIncorrect as e: if not password: raise KeyIsIncorrect( f"Error on key `{file_name}` load! Try to specify `password`: Error: {repr(e)} " ) from e raise KeyIsIncorrect( f"Error on key `{file_name}` load! Wrong password?: Error: {repr(e)} " ) from e return signing_key def sign_message( self, message: bytes, is_deprecated_mode: bool = False, # pylint: disable=unused-argument ) -> str: """ Sign a message in bytes string form. :param message: the message to be signed :param is_deprecated_mode: if the deprecated signing is used :return: signature of the message in string form """ signature_compact = self.entity.sign_deterministic( message, hashfunc=hashlib.sha256, sigencode=sigencode_string_canonize, ) signature_base64_str = base64.b64encode(signature_compact).decode("utf-8") return signature_base64_str def sign_transaction(self, transaction: JSONLike) -> JSONLike: """ Sign a transaction in bytes string form. :param transaction: the transaction to be signed :return: signed transaction """ tx = ParseDict(transaction["tx"], Tx()) # If public key is not already part of transaction if tx.auth_info.signer_infos[0].public_key.value == b"": if len(transaction["sign_data"]) == 1: # type: ignore # Insert public key to auth info from_pub_key_packed = ProtoAny() from_pub_key_pb = ProtoPubKey(key=bytes.fromhex(self.public_key)) from_pub_key_packed.Pack(from_pub_key_pb, type_url_prefix="/") # type: ignore tx.auth_info.signer_infos[ 0 ].public_key.value = from_pub_key_packed.value else: # Fails if public key is not present in transaction with multiple signers raise RuntimeError( "Public key can be added during singing only for single message transactions." ) current_sign_data = transaction["sign_data"][self.address] # type: ignore sd = SignDoc() sd.body_bytes = tx.body.SerializeToString() sd.auth_info_bytes = tx.auth_info.SerializeToString() sd.chain_id = current_sign_data["chain_id"] # type: ignore sd.account_number = current_sign_data["account_number"] # type: ignore data_for_signing = sd.SerializeToString() # Generating signature: signature = base64.b64decode(self.sign_message(data_for_signing)) tx.signatures.extend([signature]) return {"tx": MessageToDict(tx), "sign_data": transaction["sign_data"]} @classmethod def generate_private_key(cls) -> SigningKey: """Generate a key pair for cosmos network.""" signing_key = SigningKey.generate(curve=SECP256k1) return signing_key def encrypt(self, password: str) -> str: """ Encrypt the private key and return in json. :param password: the password to decrypt. :return: json string containing encrypted private key. """ return DataEncrypt.encrypt(self.private_key.encode(), password).decode() @classmethod def decrypt(cls, keyfile_json: str, password: str) -> str: """ Decrypt the private key and return in raw form. :param keyfile_json: json string containing encrypted private key. :param password: the password to decrypt. :return: the raw private key. """ try: return DataEncrypt.decrypt(keyfile_json.encode(), password).decode() except UnicodeDecodeError as e: raise ValueError( "key file data can not be translated to string! bad password?" ) from e class _CosmosApi(LedgerApi): """Class to interact with the Cosmos SDK via a HTTP APIs.""" identifier = _COSMOS def __init__(self, **kwargs: Any) -> None: """Initialize the Cosmos ledger APIs.""" self._api = None self.network_address = kwargs.pop("address", DEFAULT_ADDRESS) self.denom = kwargs.pop("denom", DEFAULT_CURRENCY_DENOM) self.chain_id = kwargs.pop("chain_id", DEFAULT_CHAIN_ID) self.rest_client = RestClient(self.network_address) self.tx_client = TxRestClient(self.rest_client) self.auth_client = AuthRestClient(self.rest_client) self.wasm_client = CosmWasmRestClient(self.rest_client) self.bank_client = BankRestClient(self.rest_client) @property def api(self) -> Any: """Get the underlying API object.""" return self._api def get_balance(self, address: Address) -> Optional[int]: """Get the balance of a given account.""" balance = self._try_get_balance(address) return balance @try_decorator( "Encountered exception when trying get balance: {}", logger_method=_default_logger.warning, ) def _try_get_balance(self, address: Address) -> Optional[int]: res = self.bank_client.Balance( QueryBalanceRequest(address=address, denom=self.denom) ) return int(res.balance.amount) def get_state( self, callable_name: str, *args: Any, **kwargs: Any ) -> Optional[JSONLike]: """ Call a specified function on the ledger API. Based on the cosmos REST API specification, which takes a path (strings separated by '/'). The convention here is to define the root of the path (txs, blocks, etc.) as the callable_name and the rest of the path as args. :param callable_name: name of the callable :param args: positional arguments :param kwargs: keyword arguments :return: the transaction dictionary """ response = self._try_get_state(callable_name, *args, **kwargs) return response @try_decorator( "Encountered exception when trying get state: {}", logger_method=_default_logger.warning, ) def _try_get_state( # pylint: disable=unused-argument self, callable_name: str, *args: Any, **kwargs: Any ) -> Optional[JSONLike]: """Try to call a function on the ledger API.""" result: Optional[JSONLike] = None query = "/".join(args) url = self.network_address + f"/{callable_name}/{query}" response = requests.get(url=url) if response.status_code == 200: result = response.json() else: # pragma: nocover raise ValueError("Cannot get state: {}".format(response.json())) return result def get_deploy_transaction( self, contract_interface: Dict[str, str], deployer_address: Address, **kwargs: Any, ) -> Optional[JSONLike]: """ Get the transaction to deploy the smart contract. Dispatches to _get_storage_transaction and _get_init_transaction based on kwargs. :param contract_interface: the contract interface. :param deployer_address: The address that will deploy the contract. :param kwargs: keyword arguments. :return: the transaction dictionary. """ denom = ( kwargs.pop("denom") if kwargs.get("denom", None) is not None else self.denom ) tx_fee_denom = ( kwargs.pop("tx_fee_denom") if kwargs.get("tx_fee_denom", None) is not None else denom ) chain_id = ( kwargs.pop("chain_id") if kwargs.get("chain_id", None) is not None else self.chain_id ) account_number = kwargs.pop("account_number", None) sequence = kwargs.pop("sequence", None) if account_number is None or sequence is None: account_number, sequence = self._try_get_account_number_and_sequence( deployer_address ) if account_number is None or sequence is None: return None # pragma: nocover label = kwargs.pop("label", None) code_id = kwargs.pop("code_id", None) amount = kwargs.pop("amount", None) init_msg = kwargs.pop("init_msg", None) unexpected_keys = [ key for key in kwargs.keys() if key not in ["tx_fee", "gas", "memo"] ] if len(unexpected_keys) != 0: # pragma: nocover raise ValueError(f"Unexpected keyword arguments: {unexpected_keys}") if label is None and code_id is None and amount is None and init_msg is None: return self._get_storage_transaction( contract_interface, deployer_address, tx_fee_denom, chain_id, account_number, sequence, **kwargs, ) if label is None: raise ValueError( # pragma: nocover "Missing required keyword argument `label` of type `str` for `_get_init_transaction`." ) if code_id is None: raise ValueError( # pragma: nocover "Missing required keyword argument `code_id` of type `int` for `_get_init_transaction`." ) if amount is None: raise ValueError( # pragma: nocover "Missing required keyword argument `amount` of type `int` for `_get_init_transaction`." ) if init_msg is None: raise ValueError( # pragma: nocover "Missing required keyword argument `init_msg` of type `JSONLike` `for `_get_init_transaction`." ) return self._get_init_transaction( deployer_address, denom, chain_id, account_number, sequence, amount, code_id, init_msg, label, tx_fee_denom, **kwargs, ) def _get_storage_transaction( self, contract_interface: Dict[str, str], deployer_address: Address, tx_fee_denom: str, chain_id: str, account_number: int, sequence: int, tx_fee: int = 0, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", ) -> Optional[JSONLike]: """ Create a CosmWasm bytecode deployment transaction. :param contract_interface: the contract interface. :param deployer_address: the deployer address. :param tx_fee_denom: the denomination of tx_fee. :param chain_id: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet). :param account_number: the account number. :param sequence: the sequence number. :param tx_fee: the transaction fee. :param gas: Maximum amount of gas to be used on executing command. :param memo: any string comment. :return: the unsigned CosmWasm contract deploy message """ store_msg = MsgStoreCode( sender=str(deployer_address), wasm_byte_code=base64.b64decode(contract_interface[_BYTECODE]), ) store_msg_packed = ProtoAny() store_msg_packed.Pack(store_msg, type_url_prefix="/") # type: ignore tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(deployer_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[store_msg_packed], ) return tx def _get_init_transaction( self, deployer_address: Address, denom: str, chain_id: str, account_number: int, sequence: int, amount: int, code_id: int, init_msg: JSONLike, label: str, tx_fee_denom: str, tx_fee: int = 0, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", ) -> Optional[JSONLike]: """ Create a CosmWasm InitMsg transaction. :param deployer_address: the deployer address of the message initiator. :param denom: the name of the denomination of the contract funds :param chain_id: the Chain ID of the CosmWasm transaction. :param account_number: the account number of the deployer. :param sequence: the sequence of the deployer. :param amount: Contract's initial funds amount :param code_id: the ID of contract bytecode. :param init_msg: the InitMsg containing parameters for contract constructor. :param label: the label name of the contract. :param tx_fee_denom: Denomination of tx_fee :param tx_fee: the tx fee accepted. :param gas: Maximum amount of gas to be used on executing command. :param memo: any string comment. :return: the unsigned CosmWasm InitMsg """ if amount == 0: init_funds = [] else: init_funds = [Coin(denom=denom, amount=str(amount))] tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] init_msg = MsgInstantiateContract( sender=str(deployer_address), code_id=code_id, msg=json.dumps(init_msg).encode("UTF8"), label=label, funds=init_funds, ) init_msg_packed = ProtoAny() init_msg_packed.Pack(init_msg, type_url_prefix="/") # type: ignore tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(deployer_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[init_msg_packed], ) return tx def get_handle_transaction( self, sender_address: Address, contract_address: Address, handle_msg: Any, amount: int, tx_fee: int, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None, ) -> Optional[JSONLike]: """ Create a CosmWasm HandleMsg transaction. :param sender_address: the sender address of the message initiator. :param contract_address: the address of the smart contract. :param handle_msg: HandleMsg in JSON format. :param amount: Funds amount sent with transaction. :param tx_fee: the tx fee accepted. :param denom: the name of the denomination of the contract funds :param gas: Maximum amount of gas to be used on executing command. :param memo: any string comment. :param chain_id: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet). :param account_number: Account number :param sequence: Sequence :param tx_fee_denom: Denomination of tx_fee, identical with denom param when None :return: the unsigned CosmWasm HandleMsg """ denom = denom if denom is not None else self.denom chain_id = chain_id if chain_id is not None else self.chain_id tx_fee_denom = tx_fee_denom if tx_fee_denom is not None else denom if account_number is None or sequence is None: account_number, sequence = self._try_get_account_number_and_sequence( sender_address ) if account_number is None or sequence is None: return None # pragma: nocover if amount == 0: funds = [] else: funds = [Coin(denom=denom, amount=str(amount))] tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] execute_msg = MsgExecuteContract( sender=str(sender_address), contract=contract_address, msg=json.dumps(handle_msg).encode("UTF8"), funds=funds, ) execute_msg_packed = ProtoAny() execute_msg_packed.Pack(execute_msg, type_url_prefix="/") # type: ignore tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(sender_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[execute_msg_packed], ) return tx def execute_contract_query( self, contract_address: Address, query_msg: JSONLike ) -> Optional[JSONLike]: """ Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing. :param contract_address: the address of the smart contract. :param query_msg: QueryMsg in JSON format. :return: the message receipt """ result = self._try_execute_wasm_query(contract_address, query_msg) return result @try_decorator( "Encountered exception when trying to execute wasm query: {}", logger_method=_default_logger.warning, ) def _try_execute_wasm_query( self, contract_address: Address, query_msg: JSONLike ) -> Optional[JSONLike]: """ Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing. :param contract_address: the address of the smart contract. :param query_msg: QueryMsg in JSON format. :return: the message receipt """ request = QuerySmartContractStateRequest( address=contract_address, query_data=json.dumps(query_msg).encode("UTF8") ) res = self.wasm_client.SmartContractState(request) return json.loads(res.data) def get_transfer_transaction( # pylint: disable=arguments-differ self, sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, denom: Optional[str] = None, gas: int = DEFAULT_GAS_AMOUNT, memo: str = "", chain_id: Optional[str] = None, account_number: Optional[int] = None, sequence: Optional[int] = None, tx_fee_denom: Optional[str] = None, **kwargs: Any, ) -> Optional[JSONLike]: """ Submit a transfer transaction to the ledger. :param sender_address: the sender address of the payer. :param destination_address: the destination address of the payee. :param amount: the amount of wealth to be transferred. :param tx_fee: the transaction fee. :param tx_nonce: verifies the authenticity of the tx :param denom: the denomination of tx fee and amount :param gas: the gas used. :param memo: memo to include in tx. :param chain_id: the chain ID of the transaction. :param account_number: Account number :param sequence: Sequence :param tx_fee_denom: Denomination of tx_fee, identical with denom param when None :param kwargs: keyword arguments. :return: the transfer transaction """ denom = denom if denom is not None else self.denom chain_id = chain_id if chain_id is not None else self.chain_id tx_fee_denom = tx_fee_denom if tx_fee_denom is not None else denom if account_number is None or sequence is None: account_number, sequence = self._try_get_account_number_and_sequence( sender_address ) if account_number is None or sequence is None: return None # pragma: nocover tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] amount_coins = [Coin(denom=denom, amount=str(amount))] msg_send = MsgSend( from_address=str(sender_address), to_address=str(destination_address), amount=amount_coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") # type: ignore tx = self._get_transaction( account_numbers=[account_number], from_addresses=[str(sender_address)], chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=[sequence], msgs=[send_msg_packed], ) return tx def get_packed_exec_msg( self, sender_address: Address, contract_address: str, msg: JSONLike, funds: int = 0, denom: Optional[str] = None, ) -> ProtoAny: """ Create and pack MsgExecuteContract :param sender_address: Address of sender :param contract_address: Address of contract :param msg: Paramaters to be passed to smart contract :param funds: Funds to be sent to smart contract :param denom: the denomination of funds :return: Packed MsgExecuteContract """ denom = denom if denom is not None else self.denom if funds == 0: funds_coins = [] else: funds_coins = [Coin(denom=denom, amount=str(funds))] msg_send = MsgExecuteContract( sender=str(sender_address), contract=contract_address, msg=json.dumps(msg).encode("UTF8"), funds=funds_coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") return send_msg_packed def get_packed_send_msg( self, from_address: Address, to_address: Address, amount: int, denom: Optional[str] = None, ) -> ProtoAny: """ Generate and pack MsgSend :param from_address: Address of sender :param to_address: Address of recipient :param amount: amount of coins to be sent :param denom: the denomination of and amount :return: packer ProtoAny type message """ denom = denom if denom is not None else self.denom amount_coins = [Coin(denom=denom, amount=str(amount))] msg_send = MsgSend( from_address=str(from_address), to_address=str(to_address), amount=amount_coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") return send_msg_packed def get_multi_transaction( self, from_addresses: List[str], pub_keys: Optional[List[bytes]], msgs: List[ProtoAny], gas: int, tx_fee: int = 0, memo: str = "", chain_id: Optional[str] = None, denom: Optional[str] = None, tx_fee_denom: Optional[str] = None, ) -> JSONLike: """ Generate transaction with multiple messages :param from_addresses: Addresses of signers :param pub_keys: Public keys of signers :param msgs: Messages to be included in transaction :param gas: the gas used. :param tx_fee: the transaction fee. :param memo: memo to include in tx. :param chain_id: the chain ID of the transaction. :param denom: the denomination of tx fee :param tx_fee_denom: Denomination of tx_fee, identical with denom param when None :raises: RuntimeError if number of pubkeys is not equal to number of from_addresses :return: the transaction """ if pub_keys is not None and len(pub_keys) != len(from_addresses): raise RuntimeError( "Number of pubkeys is not equal to number of addresses" ) # pragma: nocover denom = denom if denom is not None else self.denom chain_id = chain_id if chain_id is not None else self.chain_id tx_fee_denom = tx_fee_denom if tx_fee_denom is not None else denom tx_fee_coins = [Coin(denom=tx_fee_denom, amount=str(tx_fee))] account_numbers: List[int] = [] sequences: List[int] = [] for address in from_addresses: account_number, sequence = self._try_get_account_number_and_sequence( address ) account_numbers.append(account_number) sequences.append(sequence) # Prevent requests overflow time.sleep(1) return self._get_transaction( account_numbers=account_numbers, from_addresses=from_addresses, chain_id=chain_id, tx_fee=tx_fee_coins, gas=gas, memo=memo, sequences=sequences, msgs=msgs, pub_keys=pub_keys, ) @staticmethod def _get_transaction( account_numbers: List[int], from_addresses: List[str], chain_id: str, tx_fee: List[Coin], gas: int, memo: str, sequences: List[int], msgs: List[ProtoAny], pub_keys: Optional[List[bytes]] = None, ) -> JSONLike: """ Get a transaction. :param account_numbers: Account numbers for each signer. :param from_addresses: Addresses of each sender :param chain_id: the chain ID of the transaction. :param tx_fee: the transaction fee. :param gas: the gas used. :param memo: memo to include in tx. :param sequences: Sequence for each sender. :param msgs: Messages to be part of transaction. :param pub_keys: Public keys of each sender :raises: RuntimeError :return: the transaction """ # Txs will fail if gas is higher than MAXIMUM_GAS_AMOUNT if gas > MAXIMUM_GAS_AMOUNT: _default_logger.warning( f"Gas limit {gas} is above maximum gas limit {MAXIMUM_GAS_AMOUNT}. Gas limit was truncated to maximum." ) gas = MAXIMUM_GAS_AMOUNT # Checks if pub_keys is None: if len(from_addresses) == 1: pub_keys = [b""] else: # In case when pubkey is inserted during signing would make second signer to change tx and make the first signature invalid raise RuntimeError( "Only transaction with one signer can be generated without pubkeys" ) if len(account_numbers) != len(from_addresses) or len(from_addresses) != len( sequences ): raise RuntimeError( "Amount of provided from_addresses, sequences and account_numbers is not equal" ) # Get account and signer info for each sender signer_infos: List[SignerInfo] = [] sign_data: JSONLike = {} for from_address, pub_key, sequence, account_number in zip( from_addresses, pub_keys, sequences, account_numbers ): from_pub_key_packed = ProtoAny() from_pub_key_pb = ProtoPubKey(key=pub_key) from_pub_key_packed.Pack(from_pub_key_pb, type_url_prefix="/") # type: ignore # Prepare auth info single = ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT) mode_info = ModeInfo(single=single) signer_info = SignerInfo( public_key=from_pub_key_packed, mode_info=mode_info, sequence=sequence, ) signer_infos.append(signer_info) sign_data[from_address] = { "account_number": account_number, "chain_id": chain_id, } # Prepare auth info auth_info = AuthInfo( signer_infos=signer_infos, fee=Fee(amount=tx_fee, gas_limit=gas), ) # Prepare Tx body tx_body = TxBody() tx_body.memo = memo tx_body.messages.extend(msgs) # Prepare Tx tx = Tx(body=tx_body, auth_info=auth_info) return {"tx": MessageToDict(tx), "sign_data": sign_data} @try_decorator( "Encountered exception when trying to get account number and sequence: {}", logger_method=_default_logger.warning, ) def _try_get_account_number_and_sequence( self, address: Address ) -> Tuple[Optional[int], Optional[int]]: """ Try get account number and sequence for an address. :param address: the address :return: a tuple of account number and sequence """ account_response = self.auth_client.Account( QueryAccountRequest(address=address) ) account = BaseAccount() if account_response.account.Is(BaseAccount.DESCRIPTOR): account_response.account.Unpack(account) else: raise TypeError("Unexpected account type") # pragma: nocover return account.account_number, account.sequence def send_signed_transaction(self, tx_signed: JSONLike) -> Optional[str]: """ Send a signed transaction and wait for confirmation. :param tx_signed: the signed transaction :return: tx_digest, if present """ tx = ParseDict(tx_signed["tx"], Tx()) tx_data = tx.SerializeToString() broad_tx_req = BroadcastTxRequest( tx_bytes=tx_data, mode=BroadcastMode.BROADCAST_MODE_SYNC ) broad_tx_resp = self.tx_client.BroadcastTx(broad_tx_req) if broad_tx_resp.tx_response.code != 0: raw_log = broad_tx_resp.tx_response.raw_log _default_logger.warning( f"Sending transaction failed: {raw_log} {broad_tx_resp}" ) tx_digest = None else: tx_digest = broad_tx_resp.tx_response.txhash return tx_digest def get_transaction_receipt(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction receipt for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ tx_with_receipt = self._try_get_transaction_with_receipt(tx_digest) if tx_with_receipt is None: return None return tx_with_receipt.get("txResponse") @try_decorator( "Encountered exception when trying to get transaction receipt: {}", logger_method=_default_logger.warning, ) def _try_get_transaction_with_receipt(self, tx_digest: str) -> Optional[JSONLike]: """ Try get the transaction receipt for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ tx_request = GetTxRequest(hash=tx_digest) tx_response = self.tx_client.GetTx(tx_request) return MessageToDict(tx_response) def get_transaction(self, tx_digest: str) -> Optional[JSONLike]: """ Get the transaction for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx, if present """ # Cosmos does not distinguish between transaction receipt and transaction tx_with_receipt = self._try_get_transaction_with_receipt(tx_digest) if tx_with_receipt is None: return None # pragma: nocover return {"tx": tx_with_receipt.get("tx")} def get_contract_instance( self, contract_interface: Dict[str, str], contract_address: Optional[str] = None ) -> Any: """ Get the instance of a contract. :param contract_interface: the contract interface. :param contract_address: the contract address. :return: the contract instance """ # Instance object not available for cosmwasm return None def update_with_gas_estimate(self, transaction: JSONLike) -> JSONLike: """ Attempts to update the transaction with a gas estimate :param transaction: the transaction :raises: NotImplementedError """ raise NotImplementedError( # pragma: nocover "No gas estimation has been implemented." ) class CosmosApi(_CosmosApi, CosmosHelper): """Class to interact with the Cosmos SDK via a HTTP APIs.""" """ Equivalent to: @dataclass class CosmosFaucetStatus: tx_digest: Optional[str] status: str """ CosmosFaucetStatus = namedtuple("CosmosFaucetStatus", ["tx_digest", "status"]) class CosmosFaucetApi(FaucetApi): """Cosmos testnet faucet API.""" FAUCET_STATUS_PENDING = "pending" # noqa: F841 FAUCET_STATUS_PROCESSING = "processing" # noqa: F841 FAUCET_STATUS_COMPLETED = "complete" # noqa: F841 FAUCET_STATUS_FAILED = "failed" # noqa: F841 identifier = _COSMOS testnet_faucet_url = DEFAULT_FAUCET_URL testnet_name = TESTNET_NAME max_retry_attempts = 15 def __init__( self, poll_interval: Optional[float] = None, final_wait_interval: Optional[float] = None, ): """Initialize CosmosFaucetApi.""" self._poll_interval = float(poll_interval or 2) self._final_wait_interval = float(final_wait_interval or 5) def get_wealth(self, address: Address, url: Optional[str] = None) -> None: """ Get wealth from the faucet for the provided address. :param address: the address. :param url: the url :raises: RuntimeError of explicit faucet failures """ uid = self._try_create_faucet_claim(address, url) if uid is None: # pragma: nocover raise RuntimeError("Unable to create faucet claim") retry_attempts = self.max_retry_attempts while retry_attempts > 0: retry_attempts -= 1 # lookup status form the claim uid status = self._try_check_faucet_claim(uid, url) if status is None: # pragma: nocover raise RuntimeError("Failed to check faucet claim status") # if the status is complete if status.status == self.FAUCET_STATUS_COMPLETED: break # if the status is failure if status.status not in ( self.FAUCET_STATUS_PENDING, self.FAUCET_STATUS_PROCESSING, ): # pragma: nocover raise RuntimeError(f"Failed to get wealth for {address}") # if the status is incomplete time.sleep(self._poll_interval) if retry_attempts == 0: raise ValueError("Faucet claim check timed out!") # pragma: nocover # Wait to ensure that balance is increased on chain time.sleep(self._final_wait_interval) @classmethod @try_decorator( "An error occured while attempting to request a faucet request:\n{}", logger_method=_default_logger.error, ) def _try_create_faucet_claim( cls, address: Address, url: Optional[str] = None ) -> Optional[str]: """ Create a token faucet claim request :param address: the address to request funds :param url: the url :return: None on failure, otherwise the request uid """ uri = cls._faucet_request_uri(url) response = requests.post(url=uri, json={"address": address}) uid = None if response.status_code == 200: try: uid = response.json()["uuid"] except KeyError: # pragma: nocover ValueError(f"key `uid` not found in response_json={response.json()}") _default_logger.info("Wealth claim generated, uid: {}".format(uid)) else: # pragma: no cover _default_logger.warning( "Response: {}, Text: {}".format(response.status_code, response.text) ) return uid @classmethod @try_decorator( "An error occured while attempting to request a faucet request:\n{}", logger_method=_default_logger.error, ) def _try_check_faucet_claim( cls, uid: str, url: Optional[str] = None ) -> Optional[CosmosFaucetStatus]: """ Check the status of a faucet request :param uid: The request uid to be checked :param url: the url :return: None on failure otherwise a CosmosFaucetStatus for the specified uid """ response = requests.get(cls._faucet_status_uri(uid, url)) if response.status_code != 200: # pragma: nocover _default_logger.warning( "Response: {}, Text: {}".format(response.status_code, response.text) ) return None # parse the response data = response.json() tx_digest = None if "txStatus" in data["claim"]: tx_digest = data["claim"]["txStatus"]["hash"] return CosmosFaucetStatus( tx_digest=tx_digest, status=data["claim"]["status"], ) @classmethod def _faucet_request_uri(cls, url: Optional[str] = None) -> str: """ Generates the request URI derived from `cls.faucet_base_url` or provided url. :param url: the url :return: the faucet request uri """ if cls.testnet_faucet_url is None: # pragma: nocover raise ValueError("Testnet faucet url not set.") url = cls.testnet_faucet_url if url is None else url return f"{url}/api/v3/claims" @classmethod def _faucet_status_uri(cls, uid: str, url: Optional[str] = None) -> str: """Generates the status URI derived from `cls.faucet_base_url`.""" return f"{cls._faucet_request_uri(url)}/{uid}" ================================================ FILE: plugins/aea-ledger-fetchai/aea_ledger_fetchai/fetchai.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Fetchai module wrapping the public and private key cryptography and ledger api.""" from typing import Any from aea_ledger_fetchai._cosmos import ( CosmosCrypto, CosmosFaucetApi, CosmosHelper, _CosmosApi, ) _FETCHAI = "fetchai" _FETCH = "fetch" TESTNET_NAME = "testnet" FETCHAI_TESTNET_FAUCET_URL = "https://faucet-dorado.fetch.ai" DEFAULT_ADDRESS = "https://rest-dorado.fetch.ai:443" DEFAULT_CURRENCY_DENOM = "atestfet" DEFAULT_CHAIN_ID = "dorado-1" class FetchAIHelper(CosmosHelper): """Helper class usable as Mixin for FetchAIApi or as standalone class.""" address_prefix = _FETCH class FetchAICrypto(CosmosCrypto): # pylint: disable=W0223 """Class wrapping the Entity Generation from Fetch.AI ledger.""" identifier = _FETCHAI helper = FetchAIHelper class FetchAIApi(_CosmosApi, FetchAIHelper): """Class to interact with the Fetch ledger APIs.""" identifier = _FETCHAI def __init__(self, **kwargs: Any) -> None: """Initialize the Fetch.ai ledger APIs.""" if "address" not in kwargs: kwargs["address"] = DEFAULT_ADDRESS # pragma: nocover if "denom" not in kwargs: kwargs["denom"] = DEFAULT_CURRENCY_DENOM if "chain_id" not in kwargs: kwargs["chain_id"] = DEFAULT_CHAIN_ID super().__init__(**kwargs) class FetchAIFaucetApi(CosmosFaucetApi): """Fetchai testnet faucet API.""" identifier = _FETCHAI testnet_name = TESTNET_NAME testnet_faucet_url = FETCHAI_TESTNET_FAUCET_URL ================================================ FILE: plugins/aea-ledger-fetchai/pyproject.toml ================================================ [build-system] requires = ["setuptools", "wheel"] build_backend = "setuptools.build_meta" ================================================ FILE: plugins/aea-ledger-fetchai/setup.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Setup script for "aea_ledger_fetchai" package.""" import os from setuptools import find_packages, setup here = os.path.abspath(os.path.dirname(__file__)) plugin_dir = os.path.abspath(os.path.join(here, "..")) setup( name="aea-ledger-fetchai", version="1.2.5", author="Fetch.AI Limited", license="Apache-2.0", description="Python package wrapping the public and private key cryptography and ledger API of Fetch.AI.", packages=find_packages(include=["aea_ledger_fetchai*"]), install_requires=[ "aea>=1.0.0, <2.0.0", "ecdsa>=0.15,<0.17.0", "bech32==1.2.0", "pycryptodome>=3.10.1,<4.0.0", "cosmpy>=0.6.2,<0.7.0", ], tests_require=["pytest"], entry_points={ "aea.cryptos": ["fetchai = aea_ledger_fetchai:FetchAICrypto"], "aea.ledger_apis": ["fetchai = aea_ledger_fetchai:FetchAIApi"], "aea.faucet_apis": ["fetchai = aea_ledger_fetchai:FetchAIFaucetApi"], }, classifiers=[ "Environment :: Console", "Environment :: Web Environment", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft", "Operating System :: Unix", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Communications", "Topic :: Internet", "Topic :: Software Development", ], ) ================================================ FILE: plugins/aea-ledger-fetchai/tests/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for the aea_ledger_fetchai package.""" ================================================ FILE: plugins/aea-ledger-fetchai/tests/conftest.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Conftest module for Pytest.""" import inspect import os from aea_ledger_fetchai import FetchAICrypto CUR_PATH = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore ROOT_DIR = os.path.join(CUR_PATH, "..") MAX_FLAKY_RERUNS = 3 FETCHAI = FetchAICrypto.identifier FETCHAI_DEFAULT_ADDRESS = "https://rest-dorado.fetch.ai:443" FETCHAI_DEFAULT_CURRENCY_DENOM = "atestfet" FETCHAI_DEFAULT_CHAIN_ID = "dorado-1" FETCHAI_TESTNET_CONFIG = {"address": FETCHAI_DEFAULT_ADDRESS} ================================================ FILE: plugins/aea-ledger-fetchai/tests/data/dummy_contract/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" ================================================ FILE: plugins/aea-ledger-fetchai/tests/data/dummy_contract/build/some.json ================================================ { "contractName": "erc1155", "abi": [ { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event" }, { "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor" }, { "name": "getAddress", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_addr" } ], "constant": true, "payable": false, "type": "function", "gas": 370 }, { "name": "getHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 11446 }, { "name": "getSingleHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 2041 }, { "name": "supportsInterface", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "bytes32", "name": "_interfaceID" } ], "constant": true, "payable": false, "type": "function", "gas": 868 }, { "name": "is_nonce_used", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "addr" }, { "type": "uint256", "name": "nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 1052 }, { "name": "is_token_id_exists", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "uint256", "name": "token_id" } ], "constant": true, "payable": false, "type": "function", "gas": 928 }, { "name": "safeTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_value" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 80803 }, { "name": "safeBatchTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_values" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 748184 }, { "name": "balanceOf", "outputs": [ { "type": "uint256", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "uint256", "name": "_id" } ], "constant": true, "payable": false, "type": "function", "gas": 1172 }, { "name": "balanceOfBatch", "outputs": [ { "type": "uint256[10]", "name": "out" } ], "inputs": [ { "type": "address[10]", "name": "_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": true, "payable": false, "type": "function", "gas": 7524 }, { "name": "setApprovalForAll", "outputs": [], "inputs": [ { "type": "address", "name": "_operator" }, { "type": "bool", "name": "_approved" } ], "constant": false, "payable": false, "type": "function", "gas": 38136 }, { "name": "isApprovedForAll", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "address", "name": "_operator" } ], "constant": true, "payable": false, "type": "function", "gas": 1301 }, { "name": "createSingle", "outputs": [], "inputs": [ { "type": "address", "name": "_item_owner" }, { "type": "uint256", "name": "_id" }, { "type": "string", "name": "_path" } ], "constant": false, "payable": false, "type": "function", "gas": 597461 }, { "name": "createBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_items_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": false, "payable": false, "type": "function", "gas": 927926 }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "burn", "outputs": [], "inputs": [ { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function", "gas": 40353 }, { "name": "burnBatch", "outputs": [], "inputs": [ { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function", "gas": 382149 }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "owner", "outputs": [ { "type": "address", "name": "out" } ], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1263 } ], "bytecode": "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6000600155600160006301ffc9a760e05260c052604060c020556001600063d9b67a2660e05260c052604060c020553360025561405a56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd5b6100d061405a036100d06000396100d061405a036000f3", "deployedBytecode": "0x600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd", "source": "# Author: Sören Steiger, github.com/ssteiger\n# Author: Fetch.ai, github.com/fetchai\n# License: MIT\n\n# ERC1155 Token Standard\n# https://eips.ethereum.org/EIPS/eip-1155\n\n########################EXTERNAL-CONTRACTS####################################\n\ncontract ERC1155TokenReceiver:\n # Note: The ERC-165 identifier for this interface is 0x4e2312e0.\n\n def onERC1155Received(_operator: address, _from: address, _id: uint256, _value: uint256,\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of a single ERC1155 token type.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))` (i.e. 0xf23a6e61) if it accepts the transfer.\n # This function MUST revert if it rejects the transfer.\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _id The ID of the token being transferred\n # @param _value The amount of tokens being transferred\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))`\n # \"\"\"\n\n def onERC1155BatchReceived(_operator: address, _from: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE],\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of multiple ERC1155 token types.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))` (i.e. 0xbc197c81) if it accepts the transfer(s).\n # This function MUST revert if it rejects the transfer(s).\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the batch transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _ids An array containing ids of each token being transferred (order and length must match _values array)\n # @param _values An array containing amounts of each token being transferred (order and length must match _ids array)\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))`\n # \"\"\"\n\n########################END-EXTERNAL-CONTRACTS####################################\n########################EVENTS####################################\n\nMAX_URI_SIZE: constant(uint256) = 1024\n\nTransferSingle: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address), _id: uint256,\n _value: uint256})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_id` argument MUST be the token type being transferred.\n# The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nTransferBatch: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address),\n _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE]})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_ids` argument MUST be the list of tokens being transferred.\n# The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nApprovalForAll: event({_owner: indexed(address), _operator: indexed(address), _approved: bool})\n# @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled).\n\n\nURI: event({_value: string[MAX_URI_SIZE], _id: indexed(uint256)})\n# @dev MUST emit when the URI is updated for a token ID.\n# URIs are defined in RFC 3986.\n# The URI MUST point to a JSON file that conforms to the \"ERC-1155 Metadata URI JSON Schema\".\n\n########################END-EVENTS####################################\n########################INITIALIZATION####################################\n\nsupportedInterfaces: map(bytes32, bool)\n# https://eips.ethereum.org/EIPS/eip-165\nERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7\nERC1155_INTERFACE_ID: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26\ntokensIdCount: uint256\nowner: public(address)\n\nbalancesOf: map(address, map(uint256, uint256))\nnoncesOf: map(address, map(uint256, bool))\nuri: map(uint256, string[256])\noperators: map(address, map(address, bool))\ntoken_ids: map(uint256, bool)\n\n# This is to be set before contract migration!\nBATCH_SIZE: constant(uint256) = 10\n\n\n@public\ndef __init__():\n \"\"\"\n @notice Called once and only upon contract deployment.\n \"\"\"\n self.tokensIdCount = convert(0, uint256)\n self.supportedInterfaces[ERC165_INTERFACE_ID] = True\n self.supportedInterfaces[ERC1155_INTERFACE_ID] = True\n self.owner = msg.sender\n\n########################END-INITIALIZATION####################################\n########################PRIVATE-FUNCTIONS####################################\n\n\n######### THIS IS A TEMPORARY SOLUTION #################\n@public\n@constant\ndef getAddress(_addr: address) -> bytes32:\n hash: bytes32 = convert(_addr, bytes32)\n return hash\n##################### END ##############################\n\n@private\n@constant\ndef _getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n aggregate_hash: bytes32 = keccak256(concat(convert(_ids[0], bytes32), convert(_from_supplies[0], bytes32), convert(_to_supplies[0], bytes32)))\n for i in range(BATCH_SIZE):\n if not i == 0:\n aggregate_hash = keccak256(concat(aggregate_hash, convert(_ids[i], bytes32), convert(_from_supplies[i], bytes32), convert(_to_supplies[i], bytes32)))\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n aggregate_hash,\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n\n@private\n@constant\ndef getHashOld(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_ids[0], bytes32),\n convert(_ids[1], bytes32),\n convert(_ids[2], bytes32),\n convert(_ids[3], bytes32),\n convert(_ids[4], bytes32),\n convert(_ids[5], bytes32),\n convert(_ids[6], bytes32),\n convert(_ids[7], bytes32),\n convert(_ids[8], bytes32),\n convert(_ids[9], bytes32),\n convert(_from_supplies[0], bytes32),\n convert(_from_supplies[1], bytes32),\n convert(_from_supplies[2], bytes32),\n convert(_from_supplies[3], bytes32),\n convert(_from_supplies[4], bytes32),\n convert(_from_supplies[5], bytes32),\n convert(_from_supplies[6], bytes32),\n convert(_from_supplies[7], bytes32),\n convert(_from_supplies[8], bytes32),\n convert(_from_supplies[9], bytes32),\n convert(_to_supplies[0], bytes32),\n convert(_to_supplies[1], bytes32),\n convert(_to_supplies[2], bytes32),\n convert(_to_supplies[3], bytes32),\n convert(_to_supplies[4], bytes32),\n convert(_to_supplies[5], bytes32),\n convert(_to_supplies[6], bytes32),\n convert(_to_supplies[7], bytes32),\n convert(_to_supplies[8], bytes32),\n convert(_to_supplies[9], bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@private\n@constant\ndef _getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_id, bytes32),\n convert(_from_supply, bytes32),\n convert(_to_supply, bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n\n@private\n@constant\ndef ecrecoverSig(_hash: bytes32, _sig: bytes[65]) -> address:\n \"\"\"\n @notice Check whether the the signature matches the hash.\n @param _hash The hash to be checked.\n @param _sig The signature which is meant to match the hash.\n @return the address which signed the signature or the zero address\n \"\"\"\n if len(_sig) != 65:\n return ZERO_ADDRESS\n # ref. https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d\n # The signature format is a compact form of:\n # {bytes32 r}{bytes32 s}{uint8 v}\n r: bytes32 = extract32(_sig, 0, type=bytes32)\n s: bytes32 = extract32(_sig, 32, type=bytes32)\n v: int128 = convert(slice(_sig, start=64, len=1), int128)\n # Version of signature should be 27 or 28, but 0 and 1 are also possible versions.\n # geth uses [0, 1] and some clients have followed. This might change, see:\n # https://github.com/ethereum/go-ethereum/issues/2053\n if v < 27:\n v += 27\n if v in [27, 28]:\n return ecrecover(_hash, convert(v, uint256), convert(r, uint256), convert(s, uint256))\n return ZERO_ADDRESS\n\n\n@private\n@constant\ndef decode_id(id: uint256) -> int128:\n \"\"\"\n @notice Decodes the id of the token inorder to find out if it NFT or FT.\n @param id: uint256\n @return token_id : int128 (Specified id for FT and NFT.)\n @dev shift(x, -y): returns x with the bits shifted to the right by y places, which is equivalent to dividing x by 2**y.\n \"\"\"\n decoded_token_id: int128 = convert(shift(id, -128), int128)\n decoded_index: int128 = convert(id % 2 ** 128, int128)\n return decoded_token_id\n\n########################END-PRIVATE-FUNCTIONS################################\n########################PUBLIC-FUNCTIONS#####################################\n\n@public\n@constant\ndef supportsInterface(_interfaceID: bytes32) -> bool:\n \"\"\"\n @notice Check whether the interface id is supported.\n @param _interfaceID The interface id\n @return True if the interface id is supported.\n \"\"\"\n return self.supportedInterfaces[_interfaceID]\n\n@public\n@constant\ndef is_nonce_used(addr: address, nonce: uint256) -> bool:\n \"\"\"\n @notice Checks if the given nonce for the give address is unused.\n @param nonce: uint256 the counter of the transaction\n @param address: the address that want to transact.\n \"\"\"\n return self.noncesOf[addr][nonce]\n\n@public\n@constant\ndef is_token_id_exists(token_id: uint256) -> bool:\n \"\"\"\n @notice Checks if the given token_id is already created.\n @param token_id: uint256 the id of the token.\n \"\"\"\n return self.token_ids[token_id]\n\n@public\ndef safeTransferFrom(_from: address, _to: address, _id: uint256, _value: uint256, _data: bytes[256]):\n \"\"\"\n @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if balance of holder for token `_id` is lower than the `_value` sent.\n MUST revert on any other error.\n MUST emit the `TransferSingle` event to reflect the balance change (see \"Safe Transfer Rules\" section of the standard).\n After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _id ID of the token type\n @param _value Transfer amount\n @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n assert self.balancesOf[_from][_id] >= _value, \"Not enough tokens.\"\n\n self.balancesOf[_from][_id] -= _value\n self.balancesOf[_to][_id] += _value\n\n log.TransferSingle(msg.sender, _from, _to, _id, _value)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef safeBatchTransferFrom(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE], _data: bytes[256]):\n \"\"\"\n @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if length of `_ids` is not the same as length of `_values`.\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _ids IDs of each token type (order and length must match _values array)\n @param _values Transfer amounts per token type (order and length must match _ids array)\n @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[_from][id] >= _values[i]\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _values)\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_from][id] -= _values[i]\n self.balancesOf[_to][id] += _values[i]\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _values, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256[BATCH_SIZE],uint256[BATCH_SIZE],bytes)\", bytes32)\n\n\n@public\n@constant\ndef balanceOf(_owner: address, _id: uint256) -> uint256:\n \"\"\"\n @notice Get the balance of an account's tokens.\n @param _owner The address of the token holder\n @param _id ID of the token\n @return The _owner's balance of the token type requested\n \"\"\"\n return self.balancesOf[_owner][_id]\n\n\n@public\n@constant\ndef balanceOfBatch( _owner: address[BATCH_SIZE], _ids: uint256[BATCH_SIZE]) -> uint256[BATCH_SIZE]:\n \"\"\"\n @notice Get the balance of multiple account/token pairs\n @param _owners The addresses of the token holders\n @param _ids ID of the tokens\n @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair)\n \"\"\"\n returnBalances: uint256[BATCH_SIZE]\n for i in range(BATCH_SIZE):\n returnBalances[i] = self.balancesOf[_owner[i]][_ids[i]]\n return returnBalances\n\n\n@public\ndef setApprovalForAll(_operator: address, _approved: bool):\n \"\"\"\n @notice Enable or disable approval for a third party (\"operator\") to manage all of the caller's tokens.\n @dev MUST emit the ApprovalForAll event on success.\n @param _operator Address to add to the set of authorized operators\n @param _approved True if the operator is approved, false to revoke approval\n @return None\n \"\"\"\n (self.operators[msg.sender])[_operator] = _approved\n log.ApprovalForAll(msg.sender, _operator, _approved)\n\n\n@public\n@constant\ndef isApprovedForAll(_owner: address, _operator: address) -> bool:\n \"\"\"\n @notice Queries the approval status of an operator for a given owner.\n @param _owner The owner of the tokens.\n @param _operator Address of authorized operator.\n @return True if the operator is approved, false if not\n \"\"\"\n return (self.operators[_owner])[_operator]\n\n\n@public\ndef createSingle(_item_owner: address, _id: uint256, _path: string[256]):\n \"\"\"\n @notice Create a new token type that we can mint later.\n @param _item_owner The owner of the item.\n @param _id The id of the token.\n @param _path The path to the token data.\n @return None\n \"\"\"\n assert _item_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create item.\"\n self.balancesOf[_item_owner][_id] = 0\n self.tokensIdCount += 1\n self.token_ids[_id] = True\n self.uri[_id] = _path\n log.URI(_path, _id)\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _item_owner, _id, 0)\n\n\n@public\ndef createBatch(_items_owner: address, _ids: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Create new token types that we can mint later.\n @param _items_owner The owner of the items.\n @param _ids The ids of the tokens.\n @return None\n \"\"\"\n assert _items_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create items.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_items_owner][id] = 0\n self.tokensIdCount += 1\n self.token_ids[id] = True\n zero_supply: uint256[BATCH_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _items_owner, _ids, zero_supply)\n\n\n@public\ndef mint(_to: address, _id: uint256, _supply: uint256, _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a token.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _id The id of the token.\n @param _supply The supply to be minted for the token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n decoded_id: int128 = self.decode_id(_id)\n assert decoded_id == 1 or decoded_id == 2\n if decoded_id == 1 :\n assert _supply == 1, \"Cannot mint NFT with _supply more than 1\"\n self.balancesOf[_to][_id] = _supply\n\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, _id, _supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _id, _supply, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef mintBatch(_to: address, _ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a batch of tokens.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _supplies The supply to be minted for each token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n decoded_id: int128 = self.decode_id(id)\n assert decoded_id == 1 or decoded_id == 2\n\n if decoded_id == 1 :\n assert _supplies[i] == 1\n\n self.balancesOf[_to][id] = _supplies[i]\n\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, _ids, _supplies)\n\n for i in range(BATCH_SIZE):\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _ids[i], _supplies[i], _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef burn(_id: uint256, _supply: uint256):\n \"\"\"\n @notice Burns the supply of the specified token.\n @param _id The id of the token\n @param _supply Supply to be burned\n @return None\n \"\"\"\n assert self.balancesOf[msg.sender][_id] >= _supply, \"Not enough tokens to burn.\"\n self.balancesOf[msg.sender][_id] -= _supply\n log.TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, _id, _supply)\n\n\n@public\ndef burnBatch(_ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Burns the supply of the specified tokens.\n @dev At this point anyone can burn items if they own it.\n @param _ids The ids of the token\n @param _supplies Supplies to be burned\n @return None\n \"\"\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[msg.sender][id] >= _supplies[i]\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[msg.sender][id] -= _supplies[i]\n log.TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, _ids, _supplies)\n\n\n@public\n@payable\ndef tradeBatch(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supplies[i] > 0 and _to_supplies[i] > 0\n MUST revert if len(_ids) != len(_from_supplies) != len(_to_supplies)\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `positive and negative values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n\n assert _from == msg.sender or (self.operators[_from])[msg.sender], \"_from must be the sender or approved address\"\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n assert _to_supplies[i] == 0\n assert self.balancesOf[_from][id] >= _from_supplies[i]\n else:\n assert _from_supplies[i] == 0\n assert self.balancesOf[_to][id] >= _to_supplies[i]\n\n # Create hash from variables.\n hash: bytes32 = self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n self.balancesOf[_from][id] -= _from_supplies[i]\n self.balancesOf[_to][id] += _from_supplies[i]\n else:\n self.balancesOf[_from][id] += _to_supplies[i]\n self.balancesOf[_to][id] -= _to_supplies[i]\n\n send(_to, msg.value)\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _from_supplies)\n log.TransferBatch(msg.sender, _to, _from, _ids, _to_supplies)\n\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _to, _ids, _from_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155BatchReceived(msg.sender, _from, _ids, _to_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n\n@public\n@payable\ndef trade(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supply > 0 and _to_supply > 0\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_id` is lower than the respective amount in `positive or negative value` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The from address (seller of eth, potential receiver of tokens).\n @param _to The receiver address (receiver of tokens).\n @param _id The id of the token\n @param _from_supply The change in value of token (for _from)\n @param _to_supply The change in value of token (for _to)\n @param _value_eth The value of the ETH sent to the _from address.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n if _from_supply > 0:\n assert _to_supply == 0\n assert self.balancesOf[_from][_id] >= _from_supply\n else:\n assert _from_supply == 0\n assert self.balancesOf[_to][_id] >= _to_supply\n\n # Create hash from variables.\n hash: bytes32 = self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n if _from_supply > 0:\n self.balancesOf[_from][_id] -= _from_supply\n self.balancesOf[_to][_id] += _from_supply\n else:\n self.balancesOf[_from][_id] += _to_supply\n self.balancesOf[_to][_id] -= _to_supply\n\n send(_to, msg.value)\n\n log.TransferSingle(msg.sender, _from, _to, _id, _from_supply)\n log.TransferSingle(msg.sender, _to, _from, _id, _to_supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _to, _id, _from_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155Received(msg.sender, _from, _id, _to_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n", "sourcePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/erc1155.vy", "compiler": { "name": "vyper", "version": "0.1.0b12+commit.a01cdc8" }, "networks": { "1583918911727": { "events": { "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" }, "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8": { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8" }, "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31" }, "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event", "signature": "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b" } }, "links": {}, "address": "0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7", "transactionHash": "0x816b272ebd644b189a3addfbd429b0ea0fa7b2403cca3aa038bcd59f09d9c116" } }, "schemaVersion": "3.0.19", "updatedAt": "2020-03-11T13:47:51.885Z", "networkType": "ethereum" } ================================================ FILE: plugins/aea-ledger-fetchai/tests/data/dummy_contract/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" from aea.contracts.base import Contract from aea.crypto.base import LedgerApi class DummyContract(Contract): """The some contract class.""" @classmethod def some_method(cls, ledger_api: LedgerApi, contract_address: str) -> None: """Some method.""" pass ================================================ FILE: plugins/aea-ledger-fetchai/tests/data/dummy_contract/contract.yaml ================================================ name: dummy author: fetchai version: 0.1.0 type: contract description: A test contract license: Apache-2.0 aea_version: '>=0.9.0, <0.10.0' fingerprint: __init__.py: QmWbNjFh6E5V4n2qBwyZyXZdmmHvcZSVnwKrcM34MAE56S build/some.json: Qma5n7au2NDCg1nLwYfYnmFNwWChFuXtu65w5DV7wAZRvw build/some.wasm: Qmc9gthbdwRSywinTHKjRVQdFzrKTxUuLDx2ryNfQp1xqf contract.py: QmYfn2V3tXv7MCyhT5Kz5ArtX2FZWHC1jfeRmqCNsRxDL5 fingerprint_ignore_patterns: [] build_entrypoint: path/to/script.py class_name: DummyContract contract_interface_paths: cosmos: build/some.wasm ethereum: build/some.json fetchai: build/some.wasm dependencies: {} ================================================ FILE: plugins/aea-ledger-fetchai/tests/test_fetchai.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the fetchai module.""" import base64 import json import logging import shutil import tempfile import time from collections import OrderedDict from pathlib import Path from typing import List from unittest import mock from unittest.mock import MagicMock, call, patch from uuid import uuid4 import pytest from aea_ledger_fetchai import FetchAIApi, FetchAICrypto, FetchAIFaucetApi from aea_ledger_fetchai._cosmos import MAXIMUM_GAS_AMOUNT from cosmpy.protos.cosmos.bank.v1beta1.tx_pb2 import MsgSend from cosmpy.protos.cosmos.base.v1beta1.coin_pb2 import Coin from google.protobuf.any_pb2 import Any as ProtoAny from aea.crypto.helpers import KeyIsIncorrect from tests.conftest import FETCHAI_TESTNET_CONFIG, MAX_FLAKY_RERUNS, ROOT_DIR @pytest.fixture def fetchai_private_key_file(): """Pytest fixture to create a temporary FetchAI private key file.""" crypto = FetchAICrypto() temp_dir = Path(tempfile.mkdtemp()) try: temp_file = temp_dir / "private.key" temp_file.write_text(crypto.private_key) yield str(temp_file) finally: shutil.rmtree(temp_dir) class MockRequestsResponse: """Mock of request response.""" def __init__(self, data, status_code=None): """Initialize mock of request response.""" self._data = data self._status_code = status_code or 200 @property def status_code(self): """Get status code.""" return 200 def json(self): """Get json.""" return self._data def test_creation(fetchai_private_key_file): """Test the creation of the crypto_objects.""" assert FetchAICrypto(), "Did not manage to initialise the crypto module" assert FetchAICrypto( fetchai_private_key_file ), "Did not manage to load the cosmos private key" def test_key_file_encryption_decryption(fetchai_private_key_file): """Test fetchai private key encrypted and decrypted correctly.""" fetchai = FetchAICrypto(fetchai_private_key_file) pk_data = Path(fetchai_private_key_file).read_text() password = uuid4().hex encrypted_data = fetchai.encrypt(password) decrypted_data = fetchai.decrypt(encrypted_data, password) assert encrypted_data != pk_data assert pk_data == decrypted_data with pytest.raises(ValueError, match="Decrypt error! Bad password?"): fetchai.decrypt(encrypted_data, "BaD_PassWord") with pytest.raises(ValueError, match="Bad encrypted key format!"): fetchai.decrypt("some_data" * 16, "BaD_PassWord") def test_initialization(): """Test the initialisation of the variables.""" account = FetchAICrypto() assert account.entity is not None, "The property must return the account." assert ( account.address is not None ), "After creation the display address must not be None" assert account.address.startswith("fetch") assert ( account.public_key is not None ), "After creation the public key must no be None" def test_sign_and_recover_message(fetchai_private_key_file): """Test the signing and the recovery of a message.""" account = FetchAICrypto(fetchai_private_key_file) sign_bytes = account.sign_message(message=b"hello") assert len(sign_bytes) > 0, "The len(signature) must not be 0" recovered_addresses = FetchAIApi.recover_message( message=b"hello", signature=sign_bytes ) assert ( account.address in recovered_addresses ), "Failed to recover the correct address." def test_get_hash(): """Test the get hash functionality.""" expected_hash = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" hash_ = FetchAIApi.get_hash(message=b"hello") assert expected_hash == hash_ def test_dump_positive(fetchai_private_key_file): """Test dump.""" account = FetchAICrypto(fetchai_private_key_file) account.dump(MagicMock()) def test_api_creation(): """Test api instantiation.""" assert FetchAIApi(**FETCHAI_TESTNET_CONFIG), "Failed to initialise the api" def test_api_none(): """Test the "api" of the cryptoApi is none.""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) assert fetchai_api.api is None, "The api property is not None." def test_generate_nonce(): """Test generate nonce.""" nonce = FetchAIApi.generate_tx_nonce( seller="some_seller_addr", client="some_buyer_addr" ) assert len(nonce) > 0 and int( nonce, 16 ), "The len(nonce) must not be 0 and must be hex" def test_get_address_from_public_key(): """Test the address from public key.""" fet_crypto = FetchAICrypto() address = FetchAIApi.get_address_from_public_key(fet_crypto.public_key) assert address == fet_crypto.address, "The address must be the same." def test_validate_address(): """Test the is_valid_address functionality.""" account = FetchAICrypto() assert FetchAIApi.is_valid_address(account.address) assert not FetchAIApi.is_valid_address(account.address + "wrong") # @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_construct_sign_and_submit_transfer_transaction(): """Test the construction, signing and submitting of a transfer transaction.""" account = FetchAICrypto() balance = get_wealth(account.address) assert balance > 0, "Failed to fund account." fc2 = FetchAICrypto() fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) amount = 10000 assert amount < balance, "Not enough funds." transfer_transaction = fetchai_api.get_transfer_transaction( sender_address=account.address, destination_address=fc2.address, amount=amount, tx_fee=7750000000000000, tx_nonce="something", ) assert ( isinstance(transfer_transaction, dict) and len(transfer_transaction) == 2 ), "Incorrect transfer_transaction constructed." signed_transaction = account.sign_transaction(transfer_transaction) assert ( isinstance(signed_transaction, dict) and len(signed_transaction["tx"]) == 3 and isinstance(signed_transaction["tx"]["signatures"], list) ), "Incorrect signed_transaction constructed." transaction_digest = fetchai_api.send_signed_transaction(signed_transaction) assert transaction_digest is not None, "Failed to submit transfer transaction!" not_settled = True elapsed_time = 0 while not_settled and elapsed_time < 20: elapsed_time += 1 time.sleep(2) transaction_receipt = fetchai_api.get_transaction_receipt(transaction_digest) if transaction_receipt is None: continue is_settled = fetchai_api.is_transaction_settled(transaction_receipt) not_settled = not is_settled assert transaction_receipt is not None, "Failed to retrieve transaction receipt." assert is_settled, "Failed to verify tx!" tx = fetchai_api.get_transaction(transaction_digest) is_valid = fetchai_api.is_transaction_valid( tx, fc2.address, account.address, "", amount ) assert is_valid, "Failed to settle tx correctly!" # @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_balance(): """Test the balance is zero for a new account.""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) fc = FetchAICrypto() balance = fetchai_api.get_balance(fc.address) assert balance == 0, "New account has a positive balance." balance = get_wealth(fc.address) assert balance > 0, "Existing account has no balance." # @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_state(): """Test that get_state() with 'blocks' function returns something containing the block height.""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) callable_name = "blocks" args = ("latest",) block = fetchai_api.get_state(callable_name, *args) assert block is not None, "No response to 'blocks/latest' query." assert ( block["block"]["header"]["height"] is not None ), "Block height not found in response." def get_wealth(address: str): """Get wealth for test.""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) FetchAIFaucetApi().get_wealth(address) balance = 0 timeout = 0 while timeout < 40 and balance == 0: time.sleep(1) timeout += 1 _balance = fetchai_api.get_balance(address) balance = _balance if _balance is not None else 0 return balance # @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_wealth_positive(caplog): """Test the balance is zero for a new account.""" with caplog.at_level(logging.DEBUG, logger="aea.crypto.fetchai._default_logger"): fetchai_faucet_api = FetchAIFaucetApi() fc = FetchAICrypto() fetchai_faucet_api.get_wealth(fc.address) @pytest.mark.ledger @mock.patch("aea_ledger_fetchai._cosmos.requests.get") @mock.patch("aea_ledger_fetchai._cosmos.requests.post") def test_successful_faucet_operation(mock_post, mock_get): """Test successful faucet operation.""" address = "a normal cosmos address would be here" mock_post.return_value = MockRequestsResponse({"uuid": "a-uuid-v4-would-be-here"}) mock_get.return_value = MockRequestsResponse( { "status": "ok", "claim": { "createdAt": "2021-08-13T15:18:50.420Z", "updatedAt": "2021-08-13T15:18:58.249Z", "status": FetchAIFaucetApi.FAUCET_STATUS_COMPLETED, "txStatus": { "hash": "0x transaction hash would be here", "height": 123456, }, }, } ) faucet = FetchAIFaucetApi() faucet.get_wealth(address) mock_post.assert_has_calls( [ call( url=f"{FetchAIFaucetApi.testnet_faucet_url}/api/v3/claims", json={"address": address}, ) ] ) mock_get.assert_has_calls( [ call( f"{FetchAIFaucetApi.testnet_faucet_url}/api/v3/claims/a-uuid-v4-would-be-here" ) ] ) @pytest.mark.ledger @mock.patch("aea_ledger_fetchai._cosmos.requests.get") @mock.patch("aea_ledger_fetchai._cosmos.requests.post") def test_successful_realistic_faucet_operation(mock_post, mock_get): """Test successful realistic faucet operation.""" address = "a normal cosmos address would be here" mock_post.return_value = MockRequestsResponse({"uuid": "a-uuid-v4-would-be-here"}) mock_get.side_effect = [ MockRequestsResponse( { "status": "ok", "claim": { "createdAt": "2021-08-13T15:18:50.420Z", "updatedAt": "2021-08-13T15:18:58.249Z", "status": FetchAIFaucetApi.FAUCET_STATUS_PENDING, }, } ), MockRequestsResponse( { "status": "ok", "claim": { "createdAt": "2021-08-13T15:18:50.420Z", "updatedAt": "2021-08-13T15:18:58.249Z", "status": FetchAIFaucetApi.FAUCET_STATUS_PENDING, }, } ), MockRequestsResponse( { "status": "ok", "claim": { "createdAt": "2021-08-13T15:18:50.420Z", "updatedAt": "2021-08-13T15:18:58.249Z", "status": FetchAIFaucetApi.FAUCET_STATUS_COMPLETED, "txStatus": { "hash": "0x transaction hash would be here", "height": 123456, }, }, } ), ] faucet = FetchAIFaucetApi(poll_interval=0) faucet.get_wealth(address) mock_post.assert_has_calls( [ call( url=f"{FetchAIFaucetApi.testnet_faucet_url}/api/v3/claims", json={"address": address}, ) ] ) mock_get.assert_has_calls( [ call( f"{FetchAIFaucetApi.testnet_faucet_url}/api/v3/claims/a-uuid-v4-would-be-here" ), call( f"{FetchAIFaucetApi.testnet_faucet_url}/api/v3/claims/a-uuid-v4-would-be-here" ), call( f"{FetchAIFaucetApi.testnet_faucet_url}/api/v3/claims/a-uuid-v4-would-be-here" ), ] ) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_format_default(): """Test if default CosmosSDK transaction is correctly formatted.""" account = FetchAICrypto() cc2 = FetchAICrypto() cosmos_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) amount = 10000 transfer_transaction = cosmos_api.get_transfer_transaction( sender_address=account.address, destination_address=cc2.address, amount=amount, tx_fee=1000, tx_nonce="something", account_number=1, sequence=0, ) signed_transaction = account.sign_transaction(transfer_transaction) assert "tx" in signed_transaction assert "signatures" in signed_transaction["tx"] assert len(signed_transaction["tx"]["signatures"]) == 1 assert "publicKey" in signed_transaction["tx"]["authInfo"]["signerInfos"][0] assert "key" in signed_transaction["tx"]["authInfo"]["signerInfos"][0]["publicKey"] base64_pbk = signed_transaction["tx"]["authInfo"]["signerInfos"][0]["publicKey"][ "key" ] pbk = base64.b64decode(base64_pbk) assert pbk == bytes.fromhex(account.public_key) assert len(signed_transaction["tx"]["signatures"][0]) == 88 signature = base64.b64decode(signed_transaction["tx"]["signatures"][0]) assert len(signature) == 64 @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_storage_transaction_cosmwasm(): """Test the get storage transaction method.""" cc2 = FetchAICrypto() cosmos_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) contract_interface = {"wasm_byte_code": "1234"} deployer_address = cc2.address deploy_transaction = cosmos_api.get_deploy_transaction( contract_interface, deployer_address, account_number=1, sequence=0, ) with patch.object( cosmos_api, "_try_get_account_number_and_sequence", return_value=(1, 0) ): deploy_transaction = cosmos_api.get_deploy_transaction( contract_interface, deployer_address ) assert type(deploy_transaction) == dict and len(deploy_transaction) == 2 # Check sign_data assert "account_number" in deploy_transaction["sign_data"][cc2.address] assert "chain_id" in deploy_transaction["sign_data"][cc2.address] # Check msg assert len(deploy_transaction["tx"]["body"]["messages"]) == 1 msg = deploy_transaction["tx"]["body"]["messages"][0] assert "@type" in msg and msg["@type"] == "/cosmwasm.wasm.v1.MsgStoreCode" assert msg["sender"] == deployer_address assert msg["wasmByteCode"] == contract_interface["wasm_byte_code"] @pytest.mark.integration @pytest.mark.ledger def test_get_init_transaction_cosmwasm(): """Test the get deploy transaction method.""" cc2 = FetchAICrypto() cosmos_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) init_msg = "init_msg" code_id = 1 deployer_address = cc2.address tx_fee = 1 amount = 10 gas_limit = 1234 contract_interface = {} init_transaction = cosmos_api.get_deploy_transaction( contract_interface, deployer_address, code_id=code_id, init_msg=init_msg, amount=amount, tx_fee=tx_fee, label="", account_number=1, sequence=0, gas=gas_limit, denom="abc", tx_fee_denom="def", ) assert type(init_transaction) == dict and len(init_transaction) == 2 # Check sign_data assert "account_number" in init_transaction["sign_data"][cc2.address] assert "chain_id" in init_transaction["sign_data"][cc2.address] # Check tx assert init_transaction["tx"]["authInfo"]["fee"]["amount"] == [ {"denom": "def", "amount": str(tx_fee)} ] # Check msg assert len(init_transaction["tx"]["body"]["messages"]) == 1 msg = init_transaction["tx"]["body"]["messages"][0] assert "@type" in msg and msg["@type"] == "/cosmwasm.wasm.v1.MsgInstantiateContract" assert msg["sender"] == deployer_address assert msg["codeId"] == str(code_id) assert base64.b64decode(msg["msg"]).decode() == f'"{init_msg}"' assert msg["funds"] == [{"denom": "abc", "amount": str(amount)}] @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_handle_transaction_cosmwasm(): """Test the get deploy transaction method.""" cc2 = FetchAICrypto() cosmos_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) handle_msg = "handle_msg" sender_address = cc2.address contract_address = "contract_address" tx_fee = 1 amount = 10 gas_limit = 1234 with patch.object( cosmos_api, "_try_get_account_number_and_sequence", return_value=(1, 0) ): handle_transaction = cosmos_api.get_handle_transaction( sender_address, contract_address, handle_msg, amount, tx_fee, gas=gas_limit, memo="memo", denom="abc", tx_fee_denom="def", ) assert type(handle_transaction) == dict and len(handle_transaction) == 2 # Check sign_data assert "account_number" in handle_transaction["sign_data"][cc2.address] assert "chain_id" in handle_transaction["sign_data"][cc2.address] # Check tx assert handle_transaction["tx"]["authInfo"]["fee"] == { "amount": [{"denom": "def", "amount": str(tx_fee)}], "gasLimit": str(gas_limit), } assert "memo" in handle_transaction["tx"]["body"] # Check msg assert len(handle_transaction["tx"]["body"]["messages"]) == 1 msg = handle_transaction["tx"]["body"]["messages"][0] assert "@type" in msg and msg["@type"] == "/cosmwasm.wasm.v1.MsgExecuteContract" assert msg["sender"] == sender_address assert msg["contract"] == contract_address assert base64.b64decode(msg["msg"]).decode() == f'"{handle_msg}"' assert msg["funds"] == [{"denom": "abc", "amount": str(amount)}] @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_try_execute_wasm_query(): """Test the execute wasm query method.""" cosmos_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) client_mock = mock.Mock() output_raw_mock = mock.Mock() output_raw_mock.data = '{"output": 1}' attrs = {"SmartContractState.return_value": output_raw_mock} client_mock.configure_mock(**attrs) cosmos_api.wasm_client = client_mock result = cosmos_api.execute_contract_query( contract_address="contract_address", query_msg={} ) assert result == json.loads(output_raw_mock.data) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_send_signed_transaction(): """Test the send_signed_transaction method""" cosmos_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) tx_signed = cosmos_api.get_transfer_transaction( sender_address="addr1", destination_address="addr2", amount=123, tx_fee=1000, tx_nonce="something", account_number=1, sequence=0, ) # Mock version of protobuf Tx response mock_return_value = mock.Mock() mock_tx_response = mock.Mock() mock_tx_response.code = 0 mock_tx_response.txhash = "digest" mock_return_value.tx_response = mock_tx_response with mock.patch.object( cosmos_api.tx_client, "BroadcastTx", return_value=mock_return_value ): result = cosmos_api.send_signed_transaction(tx_signed=tx_signed) assert result == "digest" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger def test_get_contract_instance(): """Test the get contract instance method.""" cosmos_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) assert cosmos_api.get_contract_instance("interface") is None def test_helper_get_code_id(): """Test CosmosHelper.is_transaction_settled.""" assert ( FetchAIApi.get_code_id( { "logs": [ { "msg_index": 0, "log": "", "events": [ { "type": "message", "attributes": [ {"key": "action", "value": "store-code"}, {"key": "module", "value": "wasm"}, { "key": "signer", "value": "fetch1pa7q6urt98dfe2rsvfaefj8zhh792sdfuzym2t", }, {"key": "code_id", "value": "631"}, ], } ], } ] } ) == 631 ) def test_helper_get_contract_address(): """Test CosmosHelper.is_transaction_settled.""" assert ( FetchAIApi.get_contract_address( { "logs": [ { "msg_index": 0, "log": "", "events": [ { "type": "message", "attributes": [ {"key": "action", "value": "instantiate"}, {"key": "module", "value": "wasm"}, { "key": "signer", "value": "fetch1pa7q6urt98dfe2rsvfaefj8zhh792sdfuzym2t", }, {"key": "code_id", "value": "631"}, { "key": "_contract_address", "value": "fetch1lhd5t8jdjn0n4q27hsah6c0907nxrswcp5l4nw", }, ], } ], } ] } ) == "fetch1lhd5t8jdjn0n4q27hsah6c0907nxrswcp5l4nw" ) def test_load_contract_interface(): """Test the load_contract_interface method.""" path = Path(ROOT_DIR, "tests", "data", "dummy_contract", "build", "some.wasm") result = FetchAIApi.load_contract_interface(path) assert "wasm_byte_code" in result @pytest.mark.integration @pytest.mark.ledger def test_construct_init_transaction(): """Test the construction of a contract instantiate transaction""" account = FetchAICrypto() fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) init_transaction = fetchai_api._get_init_transaction( deployer_address=account.address, denom="atestfet", chain_id="cosmoshub-3", account_number=1, sequence=1, amount=0, code_id=200, init_msg={}, label="something", tx_fee_denom="stake", ) assert ( isinstance(init_transaction, dict) and len(init_transaction) == 2 ), "Incorrect transfer_transaction constructed." assert ( init_transaction["tx"]["body"]["messages"][0]["@type"] == "/cosmwasm.wasm.v1.MsgInstantiateContract" ) @pytest.mark.integration @pytest.mark.ledger def test_construct_handle_transaction(): """Test the construction of a transfer transaction.""" account = FetchAICrypto() account2 = FetchAICrypto() fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) transaction = fetchai_api.get_handle_transaction( sender_address=account.address, contract_address=account2.address, handle_msg={}, amount=0, tx_fee=100, denom="atestfet", account_number=1, sequence=0, ) assert ( isinstance(transaction, dict) and len(transaction) == 2 ), "Incorrect transfer_transaction constructed." assert ( transaction["tx"]["body"]["messages"][0]["@type"] == "/cosmwasm.wasm.v1.MsgExecuteContract" ) def test_load_errors(): """Test load errors: bad password, no password specified.""" ec = FetchAICrypto() with patch.object(FetchAICrypto, "load", return_value="bad sTring"): with pytest.raises(KeyIsIncorrect, match="Try to specify `password`"): ec.load_private_key_from_path("any path") with pytest.raises(KeyIsIncorrect, match="Wrong password?"): ec.load_private_key_from_path("any path", password="some") def test_decrypt_error(): """Test bad password error on decrypt.""" ec = FetchAICrypto() ec._pritvate_key = FetchAICrypto.generate_private_key() password = "test" encrypted_data = ec.encrypt(password=password) with patch( "aea_ledger_fetchai._cosmos.DataEncrypt.decrypt", side_effect=UnicodeDecodeError("expected", b"", 2, 3, ""), ): with pytest.raises(ValueError, match="bad password?"): ec.decrypt(encrypted_data, password + "some") @pytest.mark.integration @pytest.mark.ledger def test_multiple_signatures_transaction(): """Test generating message with multiple signers""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) coins = [Coin(denom="DENOM", amount="1234")] msg_send = MsgSend( from_address=str("from"), to_address=str("to"), amount=coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") tx = fetchai_api._get_transaction( account_numbers=[1, 2], from_addresses=["adr1", "adr2"], pub_keys=[b"1", b"2"], chain_id="chain_id", tx_fee=coins, gas=1234, memo="MEMO", sequences=[1, 2], msgs=[send_msg_packed, send_msg_packed], ) assert ( isinstance(tx, dict) and len(tx) == 2 ), "Incorrect transfer_transaction constructed." assert tx["tx"]["body"]["messages"][0]["@type"] == "/cosmos.bank.v1beta1.MsgSend" assert tx["tx"]["body"]["messages"][1]["@type"] == "/cosmos.bank.v1beta1.MsgSend" @pytest.mark.integration @pytest.mark.ledger def test_multiple_signatures_transaction_missing_pubkeys(): """Test if generating message with multiple signers without pubkeys fails""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) coins = [Coin(denom="DENOM", amount="1234")] msg_send = MsgSend( from_address=str("from"), to_address=str("to"), amount=coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") with pytest.raises( RuntimeError, match="Only transaction with one signer can be generated without pubkeys", ): fetchai_api._get_transaction( account_numbers=[1, 2], from_addresses=["adr1", "adr2"], chain_id="chain_id", tx_fee=coins, gas=1234, memo="MEMO", sequences=[1, 2], msgs=[send_msg_packed, send_msg_packed], ) @pytest.mark.integration @pytest.mark.ledger def test_multiple_signatures_transaction_wrong_number_of_params(): """Test if generating message with wrong number of params fails""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) coins = [Coin(denom="DENOM", amount="1234")] msg_send = MsgSend( from_address=str("from"), to_address=str("to"), amount=coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") with pytest.raises( RuntimeError, match="Amount of provided from_addresses, sequences and account_numbers is not equal", ): fetchai_api._get_transaction( account_numbers=[1, 2], from_addresses=["adr1", "adr2"], chain_id="chain_id", tx_fee=coins, gas=1234, memo="MEMO", sequences=[1, 2, 3], pub_keys=[b"123"], msgs=[send_msg_packed], ) @pytest.mark.ledger def test_fail_sign_multisig(): """Test sign_transaction failed.""" tx = { "tx": { "body": { "messages": [ OrderedDict( [ ("@type", "/cosmos.bank.v1beta1.MsgSend"), ( "fromAddress", "fetch17yh6gwf48ac8m2rdmze0sy55l369x6t75972jf", ), ( "toAddress", "fetch1sf6xalwvvgafcn5lg80358dt8gn7sf4dt0d9vj", ), ("amount", [{"denom": "atestfet", "amount": "10000"}]), ] ) ] }, "authInfo": { "signerInfos": [ { "publicKey": OrderedDict( [("@type", "/cosmos.crypto.secp256k1.PubKey")] ), "modeInfo": {"single": {"mode": "SIGN_MODE_DIRECT"}}, }, { "publicKey": OrderedDict( [("@type", "/cosmos.crypto.secp256k1.PubKey")] ), "modeInfo": {"single": {"mode": "SIGN_MODE_DIRECT"}}, }, ], "fee": { "amount": [{"denom": "atestfet", "amount": "7750000000000000"}], "gasLimit": "1550000", }, }, }, "sign_data": { "fetch17yh6gwf48ac8m2rdmze0sy55l369x6t75972jf": { "account_number": 16964, "chain_id": "dorado-1", }, "fetch17yh6gwf48ac8m2rdmze0sy55l369x6t75972j1": { "account_number": 16964, "chain_id": "dorado-1", }, }, } ec = FetchAICrypto() ec._pritvate_key = FetchAICrypto.generate_private_key() with pytest.raises( RuntimeError, match=r"Public key can be added during singing only for single message transactions.", ): ec.sign_transaction(tx) @pytest.mark.ledger def test_send_signed_tx_failed(): """Test send signed tx failed.""" tx_signed = { "tx": { "body": { "messages": [ OrderedDict( [ ("@type", "/cosmos.bank.v1beta1.MsgSend"), ( "fromAddress", "fetch14a92pzm55djc80xhztkz5ccemnm2kem2g5dzvh", ), ( "toAddress", "fetch127emdsu23u7u8zy7dpjn25ng7f8v5fkmecae0s", ), ("amount", [{"denom": "atestfet", "amount": "10000"}]), ] ) ] }, "authInfo": { "signerInfos": [ { "publicKey": OrderedDict( [ ("@type", "/cosmos.crypto.secp256k1.PubKey"), ("key", "A+SP+gGzrTSNwZ3ntRInSVVhRrslSBRMCh3B7OI6oc75"), ] ), "modeInfo": {"single": {"mode": "SIGN_MODE_DIRECT"}}, } ], "fee": { "amount": [{"denom": "atestfet", "amount": "7750000000000000"}], "gasLimit": "1550000", }, }, "signatures": [ "GUy8kL26D3EbK6K4sY4OBbkXpP4PFKXXtO+IqunPoKBUOYV/+iI4sRShJS3uGAejNRGNP/fgM9AwJIwl8z4z+Q==" ], }, "sign_data": { "fetch14a92pzm55djc80xhztkz5ccemnm2kem2g5dzvh": { "account_number": 16991, "chain_id": "dorado-1", } }, } fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) resp_mock = MagicMock() resp_mock.tx_response.code = 10 with patch.object(fetchai_api.tx_client, "BroadcastTx", return_value=resp_mock): assert fetchai_api.send_signed_transaction(tx_signed) is None @pytest.mark.ledger def test_max_gas(): """Test max gas limit set.""" coins = [Coin(denom="DENOM", amount="1234")] msg_send = MsgSend( from_address=str("from"), to_address=str("to"), amount=coins, ) send_msg_packed = ProtoAny() send_msg_packed.Pack(msg_send, type_url_prefix="/") fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) tx = fetchai_api._get_transaction( account_numbers=[1, 2], from_addresses=["adr1", "adr2"], pub_keys=[b"1", b"2"], chain_id="chain_id", tx_fee=coins, gas=MAXIMUM_GAS_AMOUNT * 1.5, memo="MEMO", sequences=[1, 2], msgs=[send_msg_packed, send_msg_packed], ) assert tx["tx"]["authInfo"]["fee"]["gasLimit"] == str(MAXIMUM_GAS_AMOUNT) @pytest.mark.ledger def test_get_multi_transaction(): """Test get_multi_transaction.""" fetchai_api = FetchAIApi(**FETCHAI_TESTNET_CONFIG) msgs: List[ProtoAny] = [] gas = 100 from_address = "123123" to_address = "23423434" token_id = "sdfsf" from_supply = "23423443" contract_address = "some addr" to_pubkey = "" tx_fee = 123123 contract_msg = { "transfer_single": { "operator": str(from_address), "from_address": str(from_address), "to_address": str(to_address), "id": str(token_id), "value": str(from_supply), } } msgs.append( fetchai_api.get_packed_exec_msg( sender_address=from_address, contract_address=contract_address, msg=contract_msg, funds=10, denom="test", ) ) msgs.append( fetchai_api.get_packed_exec_msg( sender_address=from_address, contract_address=contract_address, msg=contract_msg, ) ) msgs.append( fetchai_api.get_packed_send_msg( from_address=from_address, to_address=contract_address, amount=10 ) ) with patch.object( fetchai_api, "_try_get_account_number_and_sequence", return_value=(1, 0) ): tx = fetchai_api.get_multi_transaction( from_addresses=[to_address], pub_keys=[bytes.fromhex(to_pubkey)], msgs=msgs, gas=gas, tx_fee=tx_fee, ) assert tx ================================================ FILE: protolint.yaml ================================================ lint: rules: remove: - MESSAGE_NAMES_UPPER_CAMEL_CASE - ENUM_FIELD_NAMES_ZERO_VALUE_END_WITH - PACKAGE_NAME_LOWER_CASE - REPEATED_FIELD_NAMES_PLURALIZED - FIELD_NAMES_LOWER_SNAKE_CASE - ENUM_FIELD_NAMES_PREFIX ================================================ FILE: pyproject.toml ================================================ [build-system] requires = ["poetry-core>=1.1.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] name = "aea" version = "1.2.5" description = "Autonomous Economic Agent framework" authors = ["Fetch.AI Limited"] readme = "README.md" license = "Apache-2.0" homepage = "https://github.com/fetchai/agents-aea" repository = "https://github.com/fetchai/agents-aea" documentation = "https://docs.fetch.ai/aea/" keywords = ["agent", "aea", "autonomous", "economic", "autonomous economic agent"] classifiers = [ "Environment :: Console", "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: End Users/Desktop", "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: Unix", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Topic :: Communications", "Topic :: Internet", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Software Development", "Topic :: System", ] [tool.poetry.scripts] aea = 'aea.cli:cli' [tool.poetry.dependencies] python = ">=3.8,<3.11" base58 = ">=1.0.3,<3.0.0" jsonschema = ">=3.2.0,<5" packaging = "^21.0" semver = ">=2.9.1,<3.0.0" protobuf = ">=3.19.4,<4" pymultihash = "==0.8.2" pyyaml = ">=4.2b1,<6.0" requests = ">=2.22.0,<3.0.0" python-dotenv = ">=0.14.0,<0.18.0" ecdsa = ">=0.15,<0.17.0" importlib-metadata = ">4,<5" pywin32 = { version = "==303", markers = "sys_platform == 'win32' or platform_system == 'Windows'" } # cli, test_tools extras click = { version = "^8.0.0", optional = true } [tool.poetry.extras] cli = ["click"] all = ["click"] [tool.poetry.group.dev] optional = true [tool.poetry.group.dev.dependencies] tox = "^3.26" py-sr25519-bindings = ">=0.1.5,<0.2" pylint = "==2.15.5" cosmpy = ">=0.6.2,<0.7.0" bandit = "==1.7.4" vulture = "==2.6" isort = "==5.10.1" safety = "==2.3.5" mypy = "==0.982" darglint = "==1.8.1" flake8 = "==5.0.4" flake8-bugbear = "==22.10.25" flake8-docstrings = "==1.6.0" flake8-eradicate = "==1.4.0" flake8-isort = "==5.0" liccheck = "==0.7.2" black = "^22.10" [tool.poetry.group.testing] optional = true [tool.poetry.group.testing.dependencies] pytest = "^7.2.0" pytest-asyncio = "^0.20.0" pytest-cov = "^4.0.0" pytest-custom-exit-code = "^0.3.0" pytest-randomly = "^3.12.0" pytest-rerunfailures = "^10.2" docker = "^4.2.2" pexpect = "^4.8.0" sqlalchemy = ">=1.4.41" # used in one test mistune = "^2.0.4" requests = "^2.28.0" web3 = "==5.31.1" [tool.poetry.group.docs] optional = true [tool.poetry.group.docs.dependencies] mkdocs-material = "^9.0.3" mkdocs-mermaid-plugin = { git = "https://github.com/pugong/mkdocs-mermaid-plugin.git" } pydoc-markdown = "^4.6.3" pydocstyle = "^6.1.1" pymdown-extensions = "^9.7" pygments = ">=2.7.4" [tool.poetry.group.packages] optional = true [tool.poetry.group.packages.dependencies] tensorflow = [ { version = ">=2.9,<2.11.0"}, ] # openapi-core = "==0.13.2" # problematic package for poetry. requirede by http server connection openapi-spec-validator = "==0.2.8" # required by openapi core and best to pin it here gym = "==0.15.6" aiohttp = "^3.8" aioprometheus = ">=20.0.0,<21.0.0" numpy = ">=1.18.1" oef = ">=0.8.1" defusedxml = ">=0.7.1" scikit-image = ">=0.17.2" colorlog = ">=4.1.0" temper-py = "==0.0.3" [tool.poetry.group.tools] optional = true [tool.poetry.group.tools.dependencies] #benchmark matplotlib = ">=3.3.0,<3.4" psutil = "^5.9.3" memory-profiler = ">=0.57.0" # scripts gitpython = "==3.1.14" ipfshttpclient = "==0.8.0a2" [tool.poetry.group.types] optional = true [tool.poetry.group.types.dependencies] # types defenitions for mypy types-certifi = "*" types-requests = "*" types-setuptools = "*" types-urllib3 = "*" types-click = "*" types-PyYAML = "*" [tool.mypy] python_version = 3.8 strict_optional = true [[tool.mypy.overrides]] module = [ "aea/mail/base_pb2", "aea/helpers/multiaddr/crypto_pb2", "aea/helpers/search/models_pb2", "aea/helpers/ipfs/pb/unixfs_pb2", "aea/helpers/ipfs/pb/merkledag_pb2", "tests/data/generator/t_protocol/*", "tests/data/generator/t_protocol_no_ct/*", "tests/data/dummy_aea/vendor/*", "packages/fetchai/protocols/acn/acn_pb2", "packages/fetchai/protocols/aggregation/aggregation_pb2", "packages/fetchai/protocols/contract_api/contract_api_pb2", "packages/fetchai/protocols/cosm_trade/cosm_trade_pb2", "packages/fetchai/protocols/default/default_pb2", "packages/fetchai/protocols/fipa/fipa_pb2", "packages/fetchai/protocols/gym/gym_pb2", "packages/fetchai/protocols/http/http_pb2", "packages/fetchai/protocols/ledger_api/ledger_api_pb2", "packages/fetchai/protocols/ml_trade/ml_trade_pb2", "packages/fetchai/protocols/prometheus/prometheus_pb2", "packages/fetchai/protocols/oef_search/oef_search_pb2", "packages/fetchai/protocols/signing/signing_pb2", "packages/fetchai/protocols/state_update/state_update_pb2", "packages/fetchai/protocols/tac/tac_pb2", "packages/fetchai/protocols/register/register_pb2", "packages/fetchai/protocols/yoti/yoti_pb2", ] ignore_errors = true [[tool.mypy.overrides]] module = [ "oef.*", "semver.*", "werkzeug.*", "eth_keys.*", "jsonschema.*", "dotenv", "connexion", "eth_account.*", "ipfshttpclient.*", "win32con.*", "win32file.*", "pywintypes.*", "ecdsa.*", "urllib3.*", "aea_ledger_fetchai.*", "aea_ledger_ethereum.*", "aea_ledger_cosmos.*", "numpy", "gym.*", "pytest", "docker.*", "mistune", "git.*", "tensorflow.*", "temper.*", "openapi_core.*", "openapi_spec_validator.*", "sqlalchemy", "defusedxml.*", "cosmpy.*", "google.*", ] ignore_missing_imports = true [[tool.mypy.overrides]] module = [ "packages/fetchai/connections/p2p_libp2p_mailbox/connection", "packages/fetchai/connections/p2p_libp2p_client/connection", ] disable_error_code = "attr-defined" [tool.isort] # for black compatibility multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true line_length = 88 # custom configurations order_by_type = false case_sensitive = true lines_after_imports = 2 skip = [ "tests/data/dummy_aea/vendor/", "tests/data/dummy_aea/skills/dummy" ] skip_glob = "**/*_pb2.py" known_first_party = "aea" known_packages = "packages" known_local_folder = "tests" sections = ["FUTURE",'STDLIB',"THIRDPARTY","FIRSTPARTY","PACKAGES","LOCALFOLDER"] [tool.pylint.'MASTER'] ignore-patterns = ["__main__.py",".*_pb2.py","tac.sh","tac_local.sh"] [tool.pylint.'MESSAGES CONTROL'] disable = ["C0103","C0201","C0301","C0302","W0105","W0707","W1202","W1203","R0801","C0209","R1735"] # See here for more options: https://www.codeac.io/documentation/pylint-configuration.html ## Eventually resolve these: # W0707: raise-missing-from ## Eventually decide on a logging policy: # W1202: logging-format-interpolation # W1203: logging-fstring-interpolation ## Keep the following: # C0103: invalid-name, # kept as no harm # C0201: consider-iterating-dictionary, # kept as no harm # C0301: http://pylint-messages.wikidot.com/messages:c0301 > Line too long (%s/%s), # kept as no harm # C0302: http://pylint-messages.wikidot.com/messages:c0302 > Too many lines in module (%s) , # kept as no harm # W0105: pointless-string-statement, # kept as no harm # R0801: similar lines, # too granular # C0209: Formatting a regular string which could be a f-string (consider-using-f-string) # to many usage atm # R1735: Consider using {} instead of dict() (use-dict-literal) [tool.pylint.'IMPORTS'] ignored-modules = ["bech32", "ecdsa", "lru", "eth_typing", "eth_keys", "eth_account", "ipfshttpclient", "werkzeug", "openapi_spec_validator", "aiohttp", "multidict", "yoti_python_sdk", "defusedxml", "gym", "fetch", "matplotlib", "memory_profiler", "numpy", "oef", "openapi_core", "psutil", "tensorflow", "temper", "skimage", "web3", "aioprometheus", "pyaes", "Crypto", "asn1crypto", "cosmpy", "google", "google.protobuf.any_pb2", "google.protobuf.struct_pb2"] [tool.pylint.'DESIGN'] min-public-methods = 1 max-public-methods = 36 max-returns = 10 max-bool-expr = 7 max-args = 27 max-locals = 31 max-statements = 80 max-parents = 11 max-branches = 24 max-attributes = 38 [tool.pylint.'REFACTORING'] max-nested-blocks = 6 [tool.pylint.'SPELLING'] # uncomment to enable # spelling-dict=en_US # List of comma separated words that should not be checked. spelling-ignore-words = ["nocover", "pragma", "params", "multiaddress", "multihash", "OEF", "wrt", "Protobuf", "protobuf", "backend", "coroutine", "noqa", "ascii", "asyncio", "awaitable", "kwargs", "multihashing", "interoperable", "inlining", "datamodel", "str", "sqlite", "sql", "async", "json", "boolean", "config", "pytest", "counterparty", "Unregister", "unregister", "behaviours", "crypto", "cryptos", "args", "url", "tx", "testnet", "decrypt", "validator", "env", "jsonschema", "URI", "uri", "entrypoint", "initialise", "ethereum", "traceback", "fetchai", "apis", "api", "TCPSocketProtocol", "instantiation", "ip", "Haversine", "instantiation", "enum", "nosec", "Init", "init", "Behaviour", "className", "AEA", "aea", "schemas", "vendorized", "subcommand", "filesystem", "workdir", "ctx", "yaml", "representer", "multiprocess", "Struct", "struct", "Serializers", "ValueType", "serializer", "filepath", "subprocesses", "Teardown", "namespace", "LF", "maddr", "profiler", "cpu", "myfunction", "prepend", "mydecorator", "CLI", "subprocess", "ComponentId", "bool", "satisfiable", "unsatisfiable", "dicts", "utils", "entrypoints", "prepended", "coroutines", "functools", "ctrl", "posix", "stdin", "Posix", "tcp", "AbstractServer", "StreamReaderProtocol", "StreamReader", "cli", "reraise", "SafeLoader", "SafeDumper", "pathlib", "coro", "runnable", "Runnable", "PublicId", "stdout", "netloc", "dest", "subgraph", "subdict", "behaviour", "Popen", "Interprocess", "datetime", "isort", "runtime", "toplevel", "callables", "Enqueue", "Kahn's", "myagent", "fn", "cwd", "disjunction", "cancelled", "Pythonic", "pythonic", "prepends", "subclasses", "protolint", "Protolint", "performatives", "programmatically", "behaviour's", "AsyncResult", "sys", "enqueued", "multithread", "teardown", "satisfiability", "dep", "overridables", "arg", "stderr", "multithreading", "configs", "getters", "getter", "classmethods", "enqueue", "interprocess", "exc", "pydocstyle", "linter", "programme", "compositional", "formatter", "counterparty's", "endstates", "EndState", "AgentContext", "disambiguated", "prepending", "dir", "tarfiles", "docstyle", "msg", "func", "ComponentType", "PosixNamedPipeProtocol", "ungrouped", "reformats", "protoc", "DialogueLabel", "Metaclass", "responder", "UtilityParams", "ExchangeParams", "GoodHoldings", "CurrencyHoldings", "rb", "auth", "dirs", "symlink", "BadParameter", "metavar", "readme", "multithreads", "upgrader", "src", "pid", "mypy", "outstream", "CliRunner", "semver", "VersionInfo", "reinstantiate", "pre", "ItemId", "serializable", "repo", "upgraders", "addr", "endstate", "performative's", "proto", "uncomment", "Deserialize", "fnctl", "Sym", "cd", "ACN", "os", "ok", "SDK", "subtypes", "JS", "fifos", "preprocess", "dst", "overridable", "Mixin", "unregistration", "multithreaded", "iterable", "txt", "ln", "py", "Util", "ClickException", "ai", "ABI", "approver", "deployer", "trustless", "wei", "AppRunner", "TCPSite", "webhook", "Webhook", "Webhooks", "hostname", "http", "ClientResponse", "TLS", "soef", "xml", "unregisters", "FET", "eth", "nft", "AbstractEventLoop", "aiohttp", "uris", "StreamWriter", "msgs", "oef", "watchdogging", "webhooks", "RequestValidator", "ACA", "alice", "faber", "RegisterDialogue", "fipa", "prometheus", "TAC", "fet", "tac", "CFP", "GymDialogue", "RL", "LedgerApiDialogue", "faber's", "AWx", "parametrized", "FipaDialogue", "MlTradeDialogue", "carpark", "blockchain", "counterparties", "dec", "mins", "Calc", "vyper", "SigningDialogue", "modelling", "ContractApiDialogue", "alice's", "quickfix", "StateUpdateDialogue", "hacky", "aea's", "dataset", "MessageId", "cfp", "rl", "TacDialogue", "BaseFipaDialogue", "von", "maths", "Deque", "unregistering", "yoti", "copyable", "deepcopy", "multiaddresses", "logfile", "Vous", "ipaddress", "clargs", "IPCChannel", "MultiAddr", "Rendez", "gcc", "aioprometheus", "getattr", "noop", "Noop", "multiagent", "ttfb", "rtt", "mem", "xaxis", "superclass", "docstring", "execreport", "benchmarked", "ReportPrinter", "plt", "kb", "num", "initialised", "bytecode", "wasm", "denom", "mainnet", "fp", "uid", "cosmwasm", "Conftest", "decrypted", "initialisation", "hmac", "plaintext", "aes", "ipfs", "unlinked", "ipfshttpclient", "gasstation", "Ganache", "hexbytes", "txs", "LRU"] [tool.coverage.run] omit = [ "*/.tox/*" ] ================================================ FILE: pytest.ini ================================================ [pytest] log_cli = 1 log_cli_level = DEBUG log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) log_cli_date_format=%Y-%m-%d %H:%M:%S markers = integration: marks end-to-end tests which require the oef, soef, ledger or other network services unstable: marks test as unstable (won't be run in CI) ledger: marks tests which require ledger test networks (ethereum, cosmos, fetchai); these tests should also be marked 'integration' flaky: marks tests which are flaky and worth re-running sync: marks test for run in sync mode filterwarnings = ignore:the imp module is deprecated in favour of importlib:DeprecationWarning ignore:Call to deprecated create function ignore:Couldn't parse ================================================ FILE: scripts/NOTES.md ================================================ # Notes for Development ## Threading - always join the thread. Setting no timeout means the calling thread's execution will block until the thread is terminated () ================================================ FILE: scripts/RELEASE_PROCESS.md ================================================ # Release Process from develop to main 1. Make sure all tests pass, coverage is at 100% and the local branch is in a clean state (nothing to commit). Make sure you have a clean develop virtual environment. 2. Determine the next AEA version (we use [semantic versioning v 2.0.0][semver]). Create a new release branch named "feature/release-" (e.g. feature/release-1.0.0). Switch to this branch. Run `python scripts/bump_aea_version.py --new-version `. Commit if satisfied. 3. Bump plugin versions if necessary by running `python scripts/update_plugin_versions.py --update ","`. Commit if satisfied. 4. Check the protocols are up-to-date by running `python scripts/generate_all_protocols.py`. Commit if changes occurred. 5. Bump all the packages to their latest versions by running `python scripts/update_package_versions.py`. 6. Check the package upgrades are correct by running `python scripts/check_packages.py` and `python scripts/check_package_versions_in_docs.py`. Commit if satisfied. 7. Check the docs are up-to-date by running `python scripts/generate_api_docs.py` and `python scripts/check_doc_links.py`. Ensure all API pages are added into `mkdocs.yaml`. Ensure documentation can be built: `make docs`. Commit if satisfied. 8. Write release notes and place them in `HISTORY.md`. Add upgrading tips in `upgrading.md`. If necessary, adjust version references in `SECURITY.md`. Commit if satisfied. 9. Run spell checker `./scripts/spell-check.sh`. Run `pylint --disable all --enable spelling ...`. Commit if required. 10. Open a PR from feature/release- and merge into develop. 11. Switch to the develop branch, open a PR from develop to main. If there are failures, fix them in a branch off of develop and merge into develop. Repeat until no failure in the develop to main PR. 12. Release packages into registry: `python scripts/deploy_to_registry.py`. You might have to run the script a few times until all packages are updated due to a specific dependency structure. 13. Merge the develop to main PR. 14. Tag version on main. 15. Pull main, make a clean environment (`make new-env` and `poetry shell`). 16. Create a distribution: `make dist`. 17. Publish to PyPI with twine: `twine upload dist/*`. Optionally, publish to Test-PyPI with twine: `twine upload --repository-url https://test.pypi.org/legacy/ dist/*`. 18. For each plugin: create a distribution (`python3 setup.py bdist_wheel sdist`) then perform step 17. > Note, the AEA develop docker image is automatically created as part of the CI process in the develop to main PR. > If something goes wrong and only needs a small fix, do `LAST_VERSION.post1` as version, apply fixes, push again to PyPI. [semver]: https://semver.org/spec/v2.0.0.html ================================================ FILE: scripts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Scripts for the AEA framework development and deployment.""" ================================================ FILE: scripts/acn/Dockerfile ================================================ FROM ubuntu:20.04 USER root RUN apt-get update && apt-get upgrade -y RUN apt install -y python3 python3-pip # utils RUN apt install -y wget subversion # golang RUN wget https://dl.google.com/go/go1.13.8.linux-amd64.tar.gz && \ tar -xzvf go1.13.8.linux-amd64.tar.gz -C /usr/local && \ export PATH=$PATH:/usr/local/go/bin && echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc && \ mkdir $HOME/go ENV PATH="${PATH}:/usr/local/go/bin" # deployment scripts deps: needed only if configuration checks are enabled RUN python3 -m pip install pymultihash ecdsa base58 WORKDIR /acn/ # get node source code RUN svn export https://github.com/fetchai/agents-aea/trunk/packages/fetchai/connections/p2p_libp2p/libp2p_node/ /acn/node # get deployment script RUN svn export https://github.com/fetchai/agents-aea/trunk/scripts/acn/run_acn_node_standalone.py /acn/ # build node RUN cd /acn/node && go build EXPOSE 9000 EXPOSE 11000 EXPOSE 8080 ENTRYPOINT [ "python3", "-u", "/acn/run_acn_node_standalone.py", "/acn/node/libp2p_node"] ================================================ FILE: scripts/acn/Dockerfile.dev ================================================ FROM ubuntu:20.04 USER root RUN apt-get update && apt-get upgrade -y RUN apt install -y python3 python3-pip # utils RUN apt install -y wget # golang RUN wget https://dl.google.com/go/go1.13.8.linux-amd64.tar.gz && \ tar -xzvf go1.13.8.linux-amd64.tar.gz -C /usr/local && \ export PATH=$PATH:/usr/local/go/bin && echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc && \ mkdir $HOME/go ENV PATH="${PATH}:/usr/local/go/bin" # deployment scripts deps: needed only if configuration checks are enabled RUN python3 -m pip install pymultihash ecdsa base58 WORKDIR /acn/ # get node source code COPY ./packages/fetchai/connections/p2p_libp2p/libp2p_node /acn/node # get deployment script COPY ./scripts/acn/run_acn_node_standalone.py /acn/ # build node RUN cd /acn/node && go build EXPOSE 9000 EXPOSE 11000 EXPOSE 8080 ENTRYPOINT [ "python3", "-u", "/acn/run_acn_node_standalone.py", "/acn/node/libp2p_node"] ================================================ FILE: scripts/acn/README.md ================================================ # Agent Communication Network (ACN) ## Installing the agent communication network (ACN) in Kubernetes using helm **Requirements:** `helm` needs to be installed. `helm` provides a quick way of installing and updating existing ACN deployments. **NOTE:** Please use the provided `values.yaml` file only for deploying test networks as it includes private keys. To deploy a test network do the following steps: 1. Build and upload the ACN node image by running `./build_upload_img.sh`. You have to execute this from this folder. 2. update the image tag (two instances) in the `helm-chart/values.yaml` 3. `cd helm-chart` **NOTE: Make sure to be in the `agents-p2p-dht-testnet` namespace before proceeding** 4. If: 1. this is the first time deploying: run `helm install agents-dht-test .` 2. you are upgrading an existing installation (see if there is one by `helm ls`): run `helm upgrade agents-dht-test .` ### Simple image update Run `./build_upload_img.sh` and take note of the image tag. Replace below `IMAGE_TAG_HERE` with the image tag. ``` bash kubens agents-p2p-dht-testnet kubectl set image sts/acn-node-9005 acn-node=gcr.io/fetch-ai-colearn/acn_node:{IMAGE_TAG_HERE} kubectl set image sts/acn-node-9003 acn-node=gcr.io/fetch-ai-colearn/acn_node:{IMAGE_TAG_HERE} kubectl set image sts/acn-node-9004 acn-node=gcr.io/fetch-ai-colearn/acn_node:{IMAGE_TAG_HERE} kubens agents-p2p-dht kubectl set image sts/acn-node-9002 acn-node=gcr.io/fetch-ai-colearn/acn_node:{IMAGE_TAG_HERE} kubectl set image sts/acn-node-9000 acn-node=gcr.io/fetch-ai-colearn/acn_node:{IMAGE_TAG_HERE} kubectl set image sts/acn-node-9001 acn-node=gcr.io/fetch-ai-colearn/acn_node:{IMAGE_TAG_HERE} ``` ## The agent communication network (ACN) Kubernetes deployment script The `k8s_deploy_acn_node.py` script provides a configurable, reproducible, and verifiable deployment of the ACN node to a Kubernetes cluster. Configuration of the ACN node, docker image, and Kubernetes is passed through command-line interface. The script will then verify it, generate the corresponding YAML deployment file and finally deploy it. The script can also delete a deployment by appending `--delete` to the CLI arguments used to create the deployment. The generated YAML deployment file includes: - a `statefulSet` to persist ACN node log file across runs - a service for restarting the node (pod) in case of failure - a `DNSEndpoint` to expose public port - a secret to safely upload the node's private key The generated YAML deployment file can be saved for future re-deployments by using CLI option `--from-file`. Options `--from-file` and `--delete` can be combined as quick way to delete a previous deployment from Kubernetes cluster. Option `--generate-only` can be used to generate the deployment file without submitting it to the cluster. To reduce the number of CLI arguments to pass, the script offers defaults for docker and Kubernetes configuration that can be used by setting `--k8s-fetchai-defaults`, `--docker-fetchai-defaults` or `--docker-fetchai-defaults-dev`. ### Usage examples - deploy a node using CLI options ```bash python3 scripts/acn/k8s_deploy_acn_node.py --acn-key-file fet_key_test_1.txt --acn-port 9009 --acn-port-delegate 11009 --k8s-fetchai-defaults --docker-fetchai-defaults-dev ``` - delete deployment using CLI options ```bash python3 scripts/acn/k8s_deploy_acn_node.py --acn-key-file fet_key_test_1.txt --acn-port 9009 --acn-port-delegate 11009 --k8s-fetchai-defaults --docker-fetchai-defaults-dev --delete ``` - redeploy using the generated deployment file ```bash python3 scripts/acn/k8s_deploy_acn_node.py --from-file .acn_deployment.yaml ``` - delete deployment using the generated deployment file ```bash python3 scripts/acn/k8s_deploy_acn_node.py --from-file .acn_deployment.yaml --delete ``` ================================================ FILE: scripts/acn/build_upload_img.sh ================================================ #!/usr/bin/env bash set -e VERSION=$(git rev-parse --short HEAD) # read -p 'Where to upload the image (prod, or sandbox)?: ' envvar envvar=${1:-"sandbox"} shopt -s nocasematch case "$envvar" in "prod" ) echo "Production config selected" REGISTRY="gcr.io/fetch-ai-images" DOCKERFILE="Dockerfile.dev" echo "Registry to upload is $REGISTRY" ;; "sandbox" ) echo "sandbox config selected" REGISTRY="gcr.io/fetch-ai-sandbox" DOCKERFILE="Dockerfile.dev" echo "Registry to upload is $REGISTRY" ;; *) echo "Wrong env selected. Try again" echo "Exiting with exit code 1" exit 1 ;; esac sleep 2 docker build -t ${REGISTRY}/acn_node:${VERSION} -f ./scripts/acn/${DOCKERFILE} ./ docker push ${REGISTRY}/acn_node:${VERSION} ================================================ FILE: scripts/acn/helm-chart/Chart.yaml ================================================ apiVersion: v1 description: The agents p2p DHT network name: agents-dht version: 0.0.1 appVersion: 0.0.1 keywords: - fetch - agents home: https://fetch.ai sources: - https://github.com/fetchai icon: https://avatars3.githubusercontent.com/u/40889903 ================================================ FILE: scripts/acn/helm-chart/templates/acnnode.yaml ================================================ {{- range $key, $spec := .Values }} {{- if eq $key "acnnodes" }} {{- if $spec.enabled }} {{- range $spec.config }} --- apiVersion: apps/v1 kind: StatefulSet metadata: name: acn-node-{{ .p2pport }} spec: replicas: 1 selector: matchLabels: app: acn-node-{{ .p2pport }} serviceName: acn-node-{{ .p2pport }} template: metadata: labels: app: acn-node-{{ .p2pport }} annotations: prometheus.io/scrape: 'true' prometheus.io/port: '8080' prometheus.io/path: '/metrics' spec: initContainers: - name: check-entry-peer image: subfuzion/netcat imagePullPolicy: IfNotPresent command: - sh - -c - if [ -z "${LATEST_ENTRY_PEER_HOST}" ]; then exit 0; fi; until nc -w 2 -zv ${LATEST_ENTRY_PEER_HOST} ${LATEST_ENTRY_PEER_PORT}; do echo waiting for ${LATEST_ENTRY_PEER_HOST}:${LATEST_ENTRY_PEER_PORT} ; sleep 2; done; env: - name: LATEST_ENTRY_PEER_HOST value: {{ $.Values.dns.publicdnsname }} - name: LATEST_ENTRY_PEER_PORT value: "{{ $.Values.acnnodes.bootstrap.p2pport }}" containers: - name: acn-node image: {{ $.Values.acnnodes.image }} imagePullPolicy: IfNotPresent args: ["--config-from-env"] env: - name: AEA_P2P_ID valueFrom: secretKeyRef: key: priv-key name: node-priv-key-{{ .p2pport }} - name: AEA_P2P_URI_PUBLIC value: {{ $.Values.dns.publicdnsname }}:{{ .p2pport }} - name: AEA_P2P_URI value: 127.0.0.1:9000 - name: AEA_P2P_DELEGATE_URI value: 127.0.0.1:11000 - name: AEA_P2P_URI_MONITORING value: 127.0.0.1:8080 - name: AEA_P2P_ENTRY_URIS value: /dns4/{{ $.Values.dns.publicdnsname }}/tcp/{{ $.Values.acnnodes.bootstrap.p2pport }}/p2p/{{ $.Values.acnnodes.bootstrap.peerid }} - name: ACN_LOG_FILE value: /acn_data/libp2p_node_{{ .p2pport }}.log - name: AEA_P2P_CFG_REGISTRATION_DELAY value: "3.0" - name: AEA_P2P_CFG_STORAGE_PATH value: /acn_data/agents_record_store_{{ .peerid }} {{- if $.Values.acnnodes.resources }} resources: {{- toYaml $.Values.acnnodes.resources | nindent 10 }} {{- end }} ports: - containerPort: 9000 - containerPort: 11000 - containerPort: 8080 volumeMounts: - mountPath: /acn_data name: acn-data volumeClaimTemplates: - kind: PersistentVolumeClaim apiVersion: v1 metadata: name: acn-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi --- apiVersion: v1 kind: Service metadata: name: acn-node-{{ .p2pport }} spec: selector: app: acn-node-{{ .p2pport }} ports: - name: tcp-libp2p protocol: TCP port: {{ .p2pport }} targetPort: 9000 - name: tcp-delegate protocol: TCP port: {{ .delegateport }} targetPort: 11000 - name: tcp-monitoring protocol: TCP port: 8080 targetPort: 8080 {{- end }} {{- end }} {{- end }} {{- end }} ================================================ FILE: scripts/acn/helm-chart/templates/boostrapistio.yaml ================================================ {{- range $key, $spec := .Values }} {{- if eq $key "bootstrap" }} {{- if $spec.enabled }} --- apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: agents-bootstrap-dht-net spec: selector: app: {{ $.Values.dns.ingressgw }} istio: ingressgateway servers: {{- range $spec.config }} - hosts: - {{ $.Values.dns.dnsname }} {{- if $.Values.dns.publicdnsname }} - {{ $.Values.dns.publicdnsname }} {{- end }} port: name: tcp-p2pport-{{ .p2pport }} number: {{ .p2pport }} protocol: TCP - hosts: - {{ $.Values.dns.dnsname }} {{- if $.Values.dns.publicdnsname }} - {{ $.Values.dns.publicdnsname }} {{- end }} port: name: tcp-delegateport-{{ .delegateport }} number: {{ .delegateport }} protocol: TCP {{- end }} --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: agents-bootstrap-dht-net spec: gateways: - agents-bootstrap-dht-net hosts: - {{ $.Values.dns.dnsname }} {{- if $.Values.dns.publicdnsname }} - {{ $.Values.dns.publicdnsname }} {{- end }} tcp: {{- range $spec.config }} - match: - port: {{ .p2pport }} route: - destination: host: acn-node-{{ .p2pport }} port: number: {{ .p2pport }} - match: - port: {{ .delegateport }} route: - destination: host: acn-node-{{ .p2pport }} port: number: {{ .delegateport }} {{- end }} {{- end }} {{- end }} {{- end }} ================================================ FILE: scripts/acn/helm-chart/templates/bootstrapnode.yaml ================================================ {{- range $key, $spec := .Values }} {{- if eq $key "bootstrap" }} {{- if $spec.enabled }} {{- range $spec.config }} --- apiVersion: apps/v1 kind: StatefulSet metadata: name: acn-node-{{ .p2pport }} spec: replicas: 1 selector: matchLabels: app: acn-node-{{ .p2pport }} serviceName: acn-node-{{ .p2pport }} template: metadata: labels: app: acn-node-{{ .p2pport }} annotations: prometheus.io/scrape: 'true' prometheus.io/port: '8080' prometheus.io/path: '/metrics' spec: containers: - name: acn-node image: {{ $.Values.bootstrap.image }} imagePullPolicy: IfNotPresent args: ["--config-from-env"] env: - name: AEA_P2P_ID valueFrom: secretKeyRef: key: priv-key name: node-priv-key-{{ .p2pport }} - name: AEA_P2P_URI_PUBLIC value: {{ $.Values.dns.publicdnsname }}:{{ .p2pport }} - name: AEA_P2P_URI value: 127.0.0.1:9000 - name: AEA_P2P_DELEGATE_URI value: 127.0.0.1:11000 - name: AEA_P2P_URI_MONITORING value: 127.0.0.1:8080 - name: ACN_LOG_FILE value: /acn_data/libp2p_node_{{ .p2pport }}.log {{- if $.Values.bootstrap.resources }} resources: {{- toYaml $.Values.bootstrap.resources | nindent 10 }} {{- end }} ports: - containerPort: 9000 - containerPort: 11000 - containerPort: 8080 volumeMounts: - mountPath: /acn_data name: acn-data volumeClaimTemplates: - kind: PersistentVolumeClaim apiVersion: v1 metadata: name: acn-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 2Gi --- apiVersion: v1 kind: Service metadata: name: acn-node-{{ .p2pport }} spec: selector: app: acn-node-{{ .p2pport }} ports: - name: tcp-libp2p protocol: TCP port: {{ .p2pport }} targetPort: 9000 - name: tcp-delegate protocol: TCP port: {{ .delegateport }} targetPort: 11000 - name: tcp-monitoring protocol: TCP port: 8080 targetPort: 8080 {{- end }} {{- end }} {{- end }} {{- end }} ================================================ FILE: scripts/acn/helm-chart/templates/bootstrapsecret.yaml ================================================ {{- range $key, $spec := .Values }} {{- if eq $key "bootstrap" }} {{- if $spec.enabled }} {{- range $spec.config }} --- apiVersion: v1 kind: Secret metadata: name: node-priv-key-{{ .p2pport }} type: Opaque data: priv-key: {{ .privkey }} {{- end }} {{- end }} {{- end }} {{- end }} ================================================ FILE: scripts/acn/helm-chart/templates/dns.yaml ================================================ {{- range $key, $spec := .Values }} {{- if eq $key "dns" }} {{- if $spec.enabled }} --- apiVersion: externaldns.k8s.io/v1alpha1 kind: DNSEndpoint metadata: name: agents-dht-net spec: endpoints: - dnsName: {{ $.Values.dns.dnsname}} recordTTL: 180 recordType: CNAME targets: - {{ $.Values.dns.targetgw }} {{- end }} {{- end }} {{- end }} ================================================ FILE: scripts/acn/helm-chart/templates/istio.yaml ================================================ {{- range $key, $spec := .Values }} {{- if eq $key "acnnodes" }} {{- if $spec.enabled }} --- apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: agents-dht-net spec: selector: app: {{ $.Values.dns.ingressgw }} istio: ingressgateway servers: {{- range $spec.config }} - hosts: - {{ $.Values.dns.dnsname }} {{- if $.Values.dns.publicdnsname }} - {{ $.Values.dns.publicdnsname }} {{- end }} port: name: tcp-p2pport-{{ .p2pport }} number: {{ .p2pport }} protocol: TCP - hosts: - {{ $.Values.dns.dnsname }} {{- if $.Values.dns.publicdnsname }} - {{ $.Values.dns.publicdnsname }} {{- end }} port: name: tcp-delegateport-{{ .delegateport }} number: {{ .delegateport }} protocol: TCP {{- end }} --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: agents-dht-net spec: gateways: - agents-dht-net hosts: - {{ $.Values.dns.dnsname }} {{- if $.Values.dns.publicdnsname }} - {{ $.Values.dns.publicdnsname }} {{- end }} tcp: {{- range $spec.config }} - match: - port: {{ .p2pport }} route: - destination: host: acn-node-{{ .p2pport }} port: number: {{ .p2pport }} - match: - port: {{ .delegateport }} route: - destination: host: acn-node-{{ .p2pport }} port: number: {{ .delegateport }} {{- end }} {{- end }} {{- end }} {{- end }} ================================================ FILE: scripts/acn/helm-chart/templates/secret.yaml ================================================ {{- range $key, $spec := .Values }} {{- if eq $key "acnnodes" }} {{- if $spec.enabled }} {{- range $spec.config }} --- apiVersion: v1 kind: Secret metadata: name: node-priv-key-{{ .p2pport }} type: Opaque data: priv-key: {{ .privkey }} {{- end }} {{- end }} {{- end }} {{- end }} ================================================ FILE: scripts/acn/helm-chart/values.yaml ================================================ # The values here are only for testing. These are not deployed in the production environment # DON'T add enything secret here and upload to the git repo dns: enabled: true dnsname: agents-dht-testnet.colearn.fetch-ai.com publicdnsname: acn.fetch-ai.com ingressgw: istio-agentsig targetgw: agentsig.colearn.fetch-ai.com acnnodes: enabled: true image: gcr.io/fetch-ai-colearn/acn_node:003d81c00 resources: limits: cpu: 500m memory: 512Mi requests: cpu: 150m memory: 64Mi config: - p2pport: 9003 delegateport: 11003 privkey: ODE5MzM0MWZhYTFiMjZmZWNmNGJlM2E3ZTdmYzJkZTc0MGE0MzA5OWIxNWMyZjQwNWI2OTBmOWM4NmJjMjAwNA== peerid: 16Uiu2HAkwRr8R4riKhxiCy2a5E4J6t4Cvxnkxasu9uRRyjJ9NGfS - p2pport: 9004 delegateport: 11004 privkey: ZDg5MWU4ZjAyMjAzYzk3YWE5YjlmODg4Nzk0MjM3NDU4YzRhODA3M2ViYTFlYzI1MzEzOGIxNDRjNDMyZGFkNg== peerid: 16Uiu2HAm1nE7eM1c3GJrbzKZCtBHkuTyoYU4vZo5i1Xo7HzziBYx bootstrap: p2pport: 9005 peerid: 16Uiu2HAm5TasPqYqLSBwKw6MKUHLZUmSsnXrbisSGNcXNEqVTpqH bootstrap: enabled: true image: gcr.io/fetch-ai-colearn/acn_node:003d81c00 resources: limits: cpu: 500m memory: 512Mi requests: cpu: 150m memory: 64Mi config: - p2pport: 9005 delegateport: 11005 privkey: MmM0MjE1MWRmNzQzNzFkZTJmZDQ1MzUyMGY4NzIzYzI1OWFiMTlhZjUxMTkxNDRiNmVhYjcwZjhmYTQzYTE1Zg== peerid: 16Uiu2HAm5TasPqYqLSBwKw6MKUHLZUmSsnXrbisSGNcXNEqVTpqH ================================================ FILE: scripts/acn/k8s/deployment.yaml ================================================ apiVersion: v1 kind: Service metadata: name: ph-deployment-name-here namespace: ph-deployment-namespace-here spec: selector: app: ph-deployment-name-here ports: - name: tcp-libp2p protocol: TCP port: ph-node-port-number-here targetPort: 9000 - name: tcp-delegate protocol: TCP port: ph-node-delegate-port-number-here targetPort: 11000 - name: tcp-monitoring protocol: TCP port: 8080 targetPort: 8080 --- apiVersion: apps/v1 kind: StatefulSet metadata: name: ph-deployment-name-here namespace: ph-deployment-namespace-here labels: app: ph-deployment-name-here spec: serviceName: ph-deployment-name-here replicas: number-of-replicas selector: matchLabels: app: ph-deployment-name-here template: metadata: labels: app: ph-deployment-name-here spec: terminationGracePeriodSeconds: 10 initContainers: - name: check-entry-peer image: subfuzion/netcat command: ['sh', '-c', 'if [ -z "${LATEST_ENTRY_PEER_HOST}" ]; then exit 0; fi; until nc -w 2 -zv ${LATEST_ENTRY_PEER_HOST} ${LATEST_ENTRY_PEER_PORT}; do echo waiting for ${LATEST_ENTRY_PEER_HOST}:${LATEST_ENTRY_PEER_PORT} ; sleep 2; done;'] env: - name: LATEST_ENTRY_PEER_HOST value: ph-latest-entry-peer-host-here - name: LATEST_ENTRY_PEER_PORT value: ph-latest-entry-peer-port-here containers: - image: ph-gcr-image-with-tag-here name: acn-node args: ["--config-from-env"] ports: - containerPort: ph-node-port-number-here - containerPort: ph-node-delegate-port-number-here resources: requests: memory: "64Mi" cpu: "150m" limits: memory: "4000Mi" cpu: "2000m" volumeMounts: - name: acn-data mountPath: /acn_data env: - name: AEA_P2P_ID valueFrom: secretKeyRef: name: ph-node-priv-key-name-here key: priv-key - name: AEA_P2P_URI_PUBLIC value: ph-node-external-uri-here - name: AEA_P2P_URI value: ph-node-local-uri-here - name: AEA_P2P_DELEGATE_URI value: ph-node-delegate-uri-here - name: AEA_P2P_URI_MONITORING value: ph-node-monitoring-uri-here - name: AEA_P2P_ENTRY_URIS value: ph-node-entry-peers-list-here - name: ACN_LOG_FILE value: ph-node-log-file-path-here restartPolicy: Always volumeClaimTemplates: - metadata: name: acn-data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 2Gi ================================================ FILE: scripts/acn/k8s/dns.yaml ================================================ --- apiVersion: externaldns.k8s.io/v1alpha1 kind: DNSEndpoint metadata: name: ph-deployment-name-here namespace: ph-deployment-namespace-here spec: endpoints: - dnsName: ph-deployment-dns-here recordTTL: 180 recordType: CNAME targets: - fetchpub.sandbox.fetch-ai.com ================================================ FILE: scripts/acn/k8s/istio.yaml ================================================ --- apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: ph-deployment-name-here namespace: ph-deployment-namespace-here spec: selector: app: istio-fetchpubig istio: ingressgateway servers: - port: name: libp2p number: ph-node-port-number-here protocol: TCP hosts: - ph-deployment-dns-here - port: name: tcp number: ph-node-delegate-port-number-here protocol: TCP hosts: - ph-deployment-dns-here --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ph-deployment-name-here namespace: ph-deployment-namespace-here spec: gateways: - ph-deployment-name-here hosts: - ph-deployment-dns-here tcp: - match: - port: ph-node-port-number-here route: - destination: host: ph-deployment-name-here port: number: ph-node-port-number-here - match: - port: ph-node-delegate-port-number-here route: - destination: host: ph-deployment-name-here port: number: ph-node-delegate-port-number-here ================================================ FILE: scripts/acn/k8s/secret.yaml ================================================ apiVersion: v1 kind: Secret metadata: name: ph-node-priv-key-name-here type: Opaque data: priv-key: ph-base64-encoded-private-key-here ================================================ FILE: scripts/acn/k8s_deploy_acn_node.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Deploy an ACN libp2p node to a kubernetes cluster""" import argparse import base64 import os import subprocess # nosec import sys from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Type SCRIPT_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) sys.path.append(SCRIPT_DIR) from run_acn_node_standalone import ( # noqa # pylint: disable=wrong-import-position # isort:skip AcnNodeConfig, ) # docker defaults DOCKER_FETCHAI_DEFAULT_FILE = os.path.join(SCRIPT_DIR, "Dockerfile") DOCKER_FETCHAI_DEFAULT_FILE_DEV = os.path.join(SCRIPT_DIR, "Dockerfile.dev") DOCKER_FETCHAI_DEFAULT_CTX = SCRIPT_DIR DOCKER_FETCHAI_DEFAULT_CTX_DEV = os.path.join(SCRIPT_DIR, "../../") DOCKER_FETCHAI_DEFAULT_IMG = "acn_node" DOCKER_FETCHAI_DEFAULT_REGISTRY = "gcr.io/fetch-ai-sandbox" # k8s defaults K8S_FETCHAI_DEFAULT_PUBLIC_HOST = "acn.fetch.ai" K8S_FETCHAI_DEFAULT_PUBLIC_TEMPLATE_DIR = os.path.join(SCRIPT_DIR, "k8s") K8S_FETCHAI_DEFAULT_NAMESPACE = "agents-p2p-dht" def _execute_cmd(cmd: List[str]) -> Tuple[str, bool]: """ Run command as subprocess and wait for its termination :param cmd: command with arguments to execute :return: output of the command execution and execution success """ print("-> Running: {}".format(cmd)) proc = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE) # nosec out, _ = proc.communicate() success = False try: if proc.wait() == 0: success = True except ( subprocess.CalledProcessError, # pylint: disable=broad-except Exception, ) as e: print("_excute_cmd caught exception: {}".format(str(e))) print("-| Output :\n{}".format(out.decode("ascii"))) return out.decode("ascii"), success class DockerDeployment: """Build and Publish a Dockerfile""" def __init__( self, dockerfile: str, context: str, image: str, tag: str, registry: str ): """ Initialize a DockerDeployment object :param dockerfile: path to the Dockerfile :param context: path to docker image context :param image: built image name :param tag: built image tag :param registry: registry to publish the image to """ self.dockerfile = dockerfile self.context = context self.image = image self.tag = tag self.registry = registry def build_and_publish(self) -> bool: """ Build the image and publich it to registry :return: success of the operation """ cmds: List[List[str]] = [] cmds.append( [ "docker", "build", "-t", "{}:{}".format(self.image, self.tag), "-f", self.dockerfile, self.context, ] ) cmds.append( [ "docker", "tag", "{}:{}".format(self.image, self.tag), "{}/{}:{}".format(self.registry, self.image, self.tag), ] ) cmds.append( ["docker", "push", "{}/{}:{}".format(self.registry, self.image, self.tag)] ) for cmd in cmds: _, ok = _execute_cmd(cmd) if not ok: return False return True class K8sPodDeployment: """An application-agnostic deployment object""" def __init__( self, deployments_files: List[Path], docker_deployment: Optional[DockerDeployment], ): """ Initialize a K8sPodDeployment object :param deployments_files: list of kubernetes yaml files to deploy :param docker_deployment: optional DockerDeployment to build and publish """ self.deployment_files = deployments_files self.docker_deployment = docker_deployment def deploy(self) -> bool: """ Deploy to k8s cluster :return: success of the operation """ ok = True if self.docker_deployment: ok = self.docker_deployment.build_and_publish() if not ok: return ok for yaml in self.deployment_files: cmd = ["kubectl", "apply", "-f", str(yaml)] _, ok = _execute_cmd(cmd) if not ok: break return ok def delete(self) -> bool: """ Delete deployment from k8s cluster :return: success of the operation """ ok = True for yaml in self.deployment_files: cmd = ["kubectl", "delete", "-f", str(yaml)] _, ok = _execute_cmd(cmd) if not ok: break return ok class AcnK8sPodConfig: """Store, parse, and generate kubernetes deployment for an ACN node""" K8S_DEPLOYMENT_NAME = "ph-deployment-name-here" K8S_NUMBER_OF_REPLICAS = "number-of-replicas" DEFAULT_K8S_NUMBER_OF_REPLICAS = 2 K8S_NAMESPACE = "ph-deployment-namespace-here" K8S_PUBLIC_DNS = "ph-deployment-dns-here" DOCKER_IMAGE_REMOTE_WITH_TAG = "ph-gcr-image-with-tag-here" NODE_PORT = "ph-node-port-number-here" NODE_PORT_DELEGATE = "ph-node-delegate-port-number-here" NODE_PORT_MONITORING = "ph-node-monitoring-port-number-here" NODE_URI_EXTERNAL = "ph-node-external-uri-here" NODE_URI = "ph-node-local-uri-here" NODE_URI_DELEGATE = "ph-node-delegate-uri-here" NODE_URI_MONITORING = "ph-node-monitoring-uri-here" NODE_ENTRY_PEERS = "ph-node-entry-peers-list-here" NODE_KEY_NAME = "ph-node-priv-key-name-here" NODE_KEY_ENCODED = "ph-base64-encoded-private-key-here" NODE_LAST_ENTRY_PEER_HOST = "ph-latest-entry-peer-host-here" NODE_LAST_ENTRY_PEER_PORT = "ph-latest-entry-peer-port-here" NODE_LOG_FILE = "ph-node-log-file-path-here" # class defaults Defaults: Dict[str, str] = { K8S_DEPLOYMENT_NAME: "acn-node", NODE_LOG_FILE: "/acn_data/libp2p_node", } def __init__( self, acn_key_file: str, acn_port: int, acn_delegate_port: int, acn_monitoring_port: int, acn_entry_peers: Optional[List[str]], docker_file: str, docker_context: str, docker_image: str, docker_registry: str, k8s_public_hostname: str, k8s_namespace: str, k8s_template_files_dir: str, k8s_number_of_replicas: Optional[int], enable_checks: bool = True, ): """ Initialize a AcnK8sPodConfig, populate the config dictionary :param acn_key_file: path to the acn node private key :param acn_port: acn node port number :param acn_delegate_port: acn node delegate service port number :param acn_monitoring_port: acn node monitoring service port number :param acn_entry_peers: optional list of acn node entry peers multiaddresses :param docker_file: path to Dockerfile :param docker_context: path to Dockerfile context :param docker_image: docker image name :param docker_registry: url of remote docker registry to push image to :param k8s_public_hostname: public dns for acn node's external uri :param k8s_namespace: k8s namespace to deploy node to :param k8s_template_files_dir: path to directory containing k8s yaml deployment templates :param enable_checks: enable configuration checks :param k8s_number_of_replicas: number of replica pods to run """ config: Dict[str, str] = {} cls: Type[AcnK8sPodConfig] = AcnK8sPodConfig k8s_number_of_replicas = ( k8s_number_of_replicas or self.DEFAULT_K8S_NUMBER_OF_REPLICAS ) # acn node configuration config[cls.NODE_KEY_NAME] = "node-priv-key-{}".format(acn_port) config[cls.NODE_PORT] = str(acn_port) config[cls.NODE_PORT_DELEGATE] = str(acn_delegate_port) config[cls.NODE_PORT_MONITORING] = str(acn_monitoring_port) config[cls.NODE_ENTRY_PEERS] = ( ",".join(acn_entry_peers) if acn_entry_peers is not None else "" ) config[cls.NODE_ENTRY_PEERS] = '"{}"'.format(config[cls.NODE_ENTRY_PEERS]) peer_host, peer_port = cls._uri_from_multiaddr( acn_entry_peers[-1] if acn_entry_peers is not None and len(acn_entry_peers) > 0 else "" ) config[cls.NODE_LAST_ENTRY_PEER_HOST] = '"{}"'.format(peer_host) config[cls.NODE_LAST_ENTRY_PEER_PORT] = '"{}"'.format(peer_port) config[cls.NODE_URI] = "127.0.0.1:9000" config[cls.NODE_URI_DELEGATE] = "127.0.0.1:11000" config[cls.NODE_URI_MONITORING] = "127.0.0.1:8080" config[cls.NODE_URI_EXTERNAL] = "{}:{}".format(k8s_public_hostname, acn_port) config[cls.NODE_LOG_FILE] = '"{}_{}.log"'.format( cls.Defaults[cls.NODE_LOG_FILE], str(acn_port) ) with open(acn_key_file, "r") as f: key = f.read().strip() config[cls.NODE_KEY_ENCODED] = base64.b64encode(key.encode("ascii")).decode( "ascii" ) # k8s configuration config[cls.K8S_NUMBER_OF_REPLICAS] = str(k8s_number_of_replicas) config[cls.K8S_DEPLOYMENT_NAME] = "{}-{}".format( cls.Defaults[cls.K8S_DEPLOYMENT_NAME], str(acn_port) ) config[cls.K8S_NAMESPACE] = k8s_namespace config[cls.K8S_PUBLIC_DNS] = k8s_public_hostname files: List[Path] = [] for path in [ Path(os.path.join(k8s_template_files_dir, p)) for p in os.listdir(k8s_template_files_dir) ]: if path.is_file() and path.suffix == ".yaml": files.append(path) assert ( len(files) > 0 ), f"Couldn't find any template deployment file at {k8s_template_files_dir}" # docker configuration cmd = ["git", "describe", "--no-match", "--always", "--dirty"] docker_tag, ok = _execute_cmd(cmd) if not ok: docker_tag = datetime.now().strftime("%d-%m-%Y_%H-%M-%S") else: docker_tag = docker_tag.strip() config[cls.DOCKER_IMAGE_REMOTE_WITH_TAG] = "{}/{}:{}".format( docker_registry, docker_image, docker_tag.strip() ) self.config = config self.template_files = files self.docker_deployment = DockerDeployment( docker_file, docker_context, docker_image, docker_tag, docker_registry ) if enable_checks: cls.check_config(self.config) @staticmethod def _uri_from_multiaddr(maddr: str) -> Tuple[str, str]: if maddr != "": parts = maddr.split("/") if len(parts) == 7: return parts[2], parts[4] return "", "" @staticmethod def check_config(config: Dict[str, str]) -> None: """ Check an AcnK8sPodConfig deployment for correct configuration :param config: dictionary of configuration to check """ AcnNodeConfig( base64.b64decode( config[AcnK8sPodConfig.NODE_KEY_ENCODED].encode("ascii") ).decode("ascii"), config[AcnK8sPodConfig.NODE_URI], config[AcnK8sPodConfig.NODE_URI_EXTERNAL], config[AcnK8sPodConfig.NODE_URI_DELEGATE], config[AcnK8sPodConfig.NODE_URI_MONITORING], config[AcnK8sPodConfig.NODE_ENTRY_PEERS].strip('"').split(","), "", True, ) def generate_deployment(self) -> K8sPodDeployment: """ Generate deployment for the current configuration :return: deployment object """ deployment_file = ".acn_deployment.yaml" out = open(deployment_file, "w") for path in self.template_files: with open(path, "r") as f: content = f.read() for placeholder, value in self.config.items(): content = content.replace(placeholder, value) out.write(content) out.write("\n---\n") out.close() return K8sPodDeployment([Path(deployment_file)], self.docker_deployment) def parse_commandline(): """Parse script cl arguments""" parser = argparse.ArgumentParser() parser.add_argument( "--from-file", action="store", type=str, dest="from_file", required=False, help="Use previously generated deployment", ) parser.add_argument( "--delete", action="store_true", dest="delete_deployment", required=False, help="Delete an already deployed node with the same configuration", ) parser.add_argument( "--generate-only", action="store_true", dest="generate_only", required=False, help="Don't deploy the generated yaml file", ) parser.add_argument( "--acn-key-file", action="store", type=str, dest="key", required=False, help="acn node's private key file", ) parser.add_argument( "--acn-port", action="store", type=int, dest="port", required=False, help="acn node's external port number. Internal is 9000", ) parser.add_argument( "--acn-port-delegate", action="store", type=int, dest="delegate_port", required=False, help="acn node's delegate external service port number. Internal is 11000", ) parser.add_argument( "--acn-port-monitoring", action="store", type=int, dest="monitoring_port", required=False, default=8080, help="acn node's monitoring service port number (local only)", ) parser.add_argument( "--acn-entry-peers-maddrs", action="store", nargs="*", dest="entry_peers_maddrs", help="acn node's entry peers in libp2p multiaddress format", ) parser.add_argument( "--k8s-fetchai-defaults", action="store_true", dest="k8s_fetchai_defaults", required=False, help="Use FetchAI defaults for k8s configuration", ) parser.add_argument( "--k8s-public-hostname", action="store", type=str, dest="k8s_public_hostname", required=False, help="K8s public hostname to use for acn node's external uri", ) parser.add_argument( "--k8s-namespace", action="store", type=str, dest="k8s_namespace", required=False, help="K8s deployment namespace", ) parser.add_argument( "--k8s-template-files-dir", action="store", type=str, dest="k8s_template_files_dir", required=False, help="Directory containing k8s template yaml deployment files", ) parser.add_argument( "--docker-fetchai-defaults", action="store_true", dest="docker_fetchai_defaults", required=False, help="Use FetchAI defaults for docker configuration", ) parser.add_argument( "--docker-fetchai-defaults-dev", action="store_true", dest="docker_fetchai_defaults_dev", required=False, help="Use FetchAI Dev defaults for docker configuration", ) parser.add_argument( "--docker-file", action="store", type=str, dest="docker_file", required=False, help="Path to Dockerfile to build", ) parser.add_argument( "--docker-ctx", action="store", type=str, dest="docker_ctx", required=False, help="Path to Dockerfile context", ) parser.add_argument( "--docker-image", action="store", type=str, dest="docker_image", required=False, help="Docker image name", ) parser.add_argument( "--docker-registry", action="store", type=str, dest="docker_registry", required=False, help="Docker remote registry", ) parser.add_argument( "--number-of-replicas", action="store", type=int, dest="number_of_replicas", required=False, help="Number of replicas", ) args = parser.parse_args() # checks if args.from_file is None and ( args.key is None or args.port is None or args.delegate_port is None or args.monitoring_port is None ): parser.error( "--acn-key-file, --acn-port, --acn-port-delegate, --acn-port-monitoring are required when --from-file is not used" ) if ( not args.from_file and not args.k8s_fetchai_defaults and ( args.k8s_public_hostname is None or args.k8s_namespace is None or args.k8s_template_files_dir is None ) ): parser.error( "--k8s-public-hostname, --k8s-namespace, --k8s-template-files-dir are required when --k8s-fetchai-defaults is not set" ) if ( not args.from_file and not args.docker_fetchai_defaults and not args.docker_fetchai_defaults_dev and ( args.docker_file is None or args.docker_ctx is None or args.docker_image is None or args.docker_registry is None ) ): parser.error( "--docker-file, --docker-ctx, --docker-image, --docker-registry are required when --docker-fetchai-defaults[-dev] is not set" ) if args.docker_fetchai_defaults and args.docker_fetchai_defaults_dev: parser.error( "--docker-fetchai-defaults and --docker-fetchai-defaults-dev are mutually exclusive" ) if args.generate_only and (args.delete_deployment or args.from_file): parser.error("--generate-only can not be used with --delete or --from-file") return args def main(): """K8s deploy acn node""" args = parse_commandline() pod_deployment: Optional[K8sPodDeployment] = None if args.from_file: pod_deployment = K8sPodDeployment([Path(args.from_file)], None) else: dargs: List[Any] = [ args.key, args.port, args.delegate_port, args.monitoring_port, ] dargs.append( args.entry_peers_maddrs if args.entry_peers_maddrs is not None else "" ) docker_config: List[str] = [] if args.docker_fetchai_defaults_dev: docker_config = [ DOCKER_FETCHAI_DEFAULT_FILE_DEV, DOCKER_FETCHAI_DEFAULT_CTX_DEV, DOCKER_FETCHAI_DEFAULT_IMG, DOCKER_FETCHAI_DEFAULT_REGISTRY, ] elif args.docker_fetchai_defaults: docker_config = [ DOCKER_FETCHAI_DEFAULT_FILE, DOCKER_FETCHAI_DEFAULT_CTX, DOCKER_FETCHAI_DEFAULT_IMG, DOCKER_FETCHAI_DEFAULT_REGISTRY, ] if args.docker_file is not None: docker_config[0] = args.docker_file if args.docker_ctx is not None: docker_config[1] = args.docker_ctx if args.docker_image is not None: docker_config[2] = args.docker_image if args.docker_registry is not None: docker_config[3] = args.docker_registry dargs.extend(docker_config) k8s_config: List[str] = [] if args.k8s_fetchai_defaults: k8s_config = [ K8S_FETCHAI_DEFAULT_PUBLIC_HOST, K8S_FETCHAI_DEFAULT_NAMESPACE, K8S_FETCHAI_DEFAULT_PUBLIC_TEMPLATE_DIR, ] if args.k8s_public_hostname is not None: k8s_config[0] = args.k8s_public_hostname if args.k8s_namespace is not None: k8s_config[1] = args.k8s_namespace if args.k8s_template_files_dir is not None: k8s_config[2] = args.k8s_template_files_dir dargs.extend(k8s_config) pod_deployment = AcnK8sPodConfig( dargs[0], dargs[1], dargs[2], dargs[3], dargs[4], dargs[5], dargs[6], dargs[7], dargs[8], dargs[9], dargs[10], dargs[11], k8s_number_of_replicas=args.number_of_replicas, ).generate_deployment() if args.generate_only: return try: if args.delete_deployment: pod_deployment.delete() else: pod_deployment.deploy() except Exception as e: raise e if __name__ == "__main__": main() ================================================ FILE: scripts/acn/run_acn_node_standalone.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Run an ACN libp2p node without requiring the agents framework.""" import argparse import os import subprocess # nosec import sys from binascii import unhexlify from typing import Dict, List, Optional # following imports needed only if checks are enabled # isort:skip from base58 import b58decode from ecdsa import SigningKey, curves from multihash import decode as multihashdecode # type: ignore class AcnNodeConfig: """Store the configuration of an acn node as a dictionary.""" KEY = "AEA_P2P_ID" URI = "AEA_P2P_URI" EXTERNAL_URI = "AEA_P2P_URI_PUBLIC" DELEGATE_URI = "AEA_P2P_DELEGATE_URI" MONITORING_URI = "AEA_P2P_URI_MONITORING" ENTRY_PEERS_MADDRS = "AEA_P2P_ENTRY_URIS" IPC_IN = "AEA_TO_NODE" IPC_OUT = "NODE_TO_AEA" AEA_ADDRESS = "AEA_AGENT_ADDR" ACN_LOG_FILE = "ACN_LOG_FILE" LIST_SEPARATOR = "," def __init__( self, key: str, uri: str, external_uri: Optional[str] = None, delegate_uri: Optional[str] = None, monitoring_uri: Optional[str] = None, entry_peers_maddrs: Optional[List[str]] = None, log_file: Optional[str] = None, enable_checks: bool = True, ): """ Initialize a new ACN configuration from arguments :param key: node private key to use as identity :param uri: node local uri to bind to :param external_uri: node external uri, needed to be reached by others :param delegate_uri: node local uri for delegate service :param monitoring_uri: node monitoring uri :param entry_peers_maddrs: multiaddresses of peers to join their network :param log_file: path to log file, opened in append mode :param enable_checks: to check if provided configuration is valid """ self.config: Dict[str, str] = {} self.config[AcnNodeConfig.KEY] = key self.config[AcnNodeConfig.URI] = uri self.config[AcnNodeConfig.EXTERNAL_URI] = ( external_uri if external_uri is not None else "" ) self.config[AcnNodeConfig.DELEGATE_URI] = ( delegate_uri if delegate_uri is not None else "" ) self.config[AcnNodeConfig.MONITORING_URI] = ( monitoring_uri if monitoring_uri is not None else "" ) entry_peers_maddrs_list = ( AcnNodeConfig.LIST_SEPARATOR.join(entry_peers_maddrs) if entry_peers_maddrs is not None else "" ) self.config[AcnNodeConfig.ENTRY_PEERS_MADDRS] = entry_peers_maddrs_list self.config[AcnNodeConfig.ACN_LOG_FILE] = ( log_file if log_file is not None else "" ) self.config[AcnNodeConfig.AEA_ADDRESS] = "" self.config[AcnNodeConfig.IPC_IN] = "" self.config[AcnNodeConfig.IPC_OUT] = "" if enable_checks: AcnNodeConfig.check_config(self.config) def dump(self, file_path: str) -> None: """Write current configuration to file.""" with open(file_path, "w") as f: for key, value in self.config.items(): f.write("{}={}\n".format(key, value)) @classmethod def from_file(cls, file_path: str, enable_checks: bool = True) -> "AcnNodeConfig": """ Create a new AcnNodeConfig objet from file. :param file_path: path to the file containing the configuration :param enable_checks: whether or not to enable checks :return: newly created AcnNodeConfig object, if successful """ lines: List[str] = [] with open(file_path, "r") as f: lines = f.readlines() config = {} for nbr, line in enumerate(lines): parts = line.strip().split("=") if len(parts) != 2: raise ValueError( "Malformed configuration line {}: {}".format(nbr + 1, line) ) config[parts[0]] = parts[1] key = config[AcnNodeConfig.KEY] uri = config[AcnNodeConfig.URI] external_uri = config.get(AcnNodeConfig.EXTERNAL_URI, None) delegate_uri = config.get(AcnNodeConfig.DELEGATE_URI, None) monitoring_uri = config.get(AcnNodeConfig.MONITORING_URI, None) entry_peers = config.get(AcnNodeConfig.ENTRY_PEERS_MADDRS, "") log_file = config.get(AcnNodeConfig.ACN_LOG_FILE, "") return cls( key, uri, external_uri, delegate_uri, monitoring_uri, entry_peers.split(","), log_file, enable_checks, ) @staticmethod def check_config(config: Dict[str, str]) -> None: """ Validate an ACN node configuration. :param config: dictionary containing the configuration to check """ SigningKey.from_string( unhexlify(config[AcnNodeConfig.KEY]), curve=curves.SECP256k1 ) AcnNodeConfig._check_uri(config[AcnNodeConfig.URI]) if config[AcnNodeConfig.EXTERNAL_URI] != "": AcnNodeConfig._check_uri(config[AcnNodeConfig.EXTERNAL_URI]) if config[AcnNodeConfig.DELEGATE_URI] != "": AcnNodeConfig._check_uri(config[AcnNodeConfig.DELEGATE_URI]) if config[AcnNodeConfig.MONITORING_URI] != "": AcnNodeConfig._check_uri(config[AcnNodeConfig.MONITORING_URI]) maddrs = config[AcnNodeConfig.ENTRY_PEERS_MADDRS].split( AcnNodeConfig.LIST_SEPARATOR ) for maddr in maddrs: AcnNodeConfig._check_maddr(maddr) @staticmethod def _check_uri(uri: str) -> None: """Check uri.""" if uri == "": return parts = uri.split(":") if len(parts) != 2: raise ValueError("Malformed uri '{}'".format(uri)) int(parts[1]) @staticmethod def _check_maddr(maddr: str) -> None: """Check multiaddress.""" if maddr == "": return parts = maddr.split("/") if len(parts) != 7: raise ValueError("Malformed multiaddress '{}'".format(maddr)) multihashdecode(b58decode(parts[-1])) class AcnNodeStandalone: """Deploy an acn node in standalone mode.""" def __init__(self, config: AcnNodeConfig, libp2p_node_binary: str): """ Initialize a new AcnNodeStandalone object. :param config: node's configuration :param libp2p_node_binary: path to libp2p node binary """ self.config = config self.binary = libp2p_node_binary self._proc = None # type: Optional[subprocess.Popen] def run(self): """Run the node.""" config_file = ".acn_config" self.config.dump(config_file) cmd = [self.binary, config_file] if self.config.config[AcnNodeConfig.ACN_LOG_FILE] != "": self._proc = subprocess.Popen( # nosec cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, ) with open( self.config.config[AcnNodeConfig.ACN_LOG_FILE], "ab", 1 ) as log_file: for line in self._proc.stdout: sys.stdout.buffer.write(line) log_file.write(line) log_file.flush() else: self._proc = subprocess.Popen( # nosec cmd, shell=False, ) try: self._proc.wait() except KeyboardInterrupt: pass def stop(self): """Stop the node.""" if self._proc is not None: self._proc.terminate() self._proc.wait() def parse_commandline(): """Parse script cl arguments.""" parser = argparse.ArgumentParser() parser.add_argument("libp2p_node") config = parser.add_mutually_exclusive_group(required=False) config.add_argument( "--config-from-env", action="store_true", dest="config_from_env", help="get node configuration from environment variables", ) config.add_argument( "--config-from-file", action="store", type=str, dest="config_from_file", help="node configuration file", ) parser.add_argument( "--key-file", action="store", type=str, dest="key", help="node's private key file", ) parser.add_argument( "--uri", action="store", type=str, dest="uri", help="node's local uri in format {ip_address:port}", ) parser.add_argument( "--uri-external", action="store", type=str, dest="external_uri", required=False, help="node's external uri in format {ip_address:port}", ) parser.add_argument( "--uri-delegate", action="store", type=str, dest="delegate_uri", required=False, help="node's delegate service uri in format {ip_address:port}", ) parser.add_argument( "--uri-monitoring", action="store", type=str, dest="monitoring_uri", required=False, help="node's monitoring service uri in format {ip_address:port}", ) parser.add_argument( "--entry-peers-maddrs", action="store", nargs="*", dest="entry_peers_maddrs", help="node's entry peer uri in libp2p multiaddress fromat", ) parser.add_argument( "--log-file", action="store", type=str, dest="log_file", required=False, help="path to node logging file", ) args = parser.parse_args() if ( args.config_from_env is False and args.config_from_file is None and (args.key is None or args.uri is None or args.external_uri is None) ): parser.error( "--key-file, --uri, and --uri-external are required when configuration is not passed through env or file" ) return args if __name__ == "__main__": args = parse_commandline() node_config: Optional[AcnNodeConfig] = None if args.config_from_env: key = os.environ[AcnNodeConfig.KEY] uri = os.environ[AcnNodeConfig.URI] external_uri = os.environ.get(AcnNodeConfig.EXTERNAL_URI) delegate_uri = os.environ.get(AcnNodeConfig.DELEGATE_URI) monitoring_uri = os.environ.get(AcnNodeConfig.MONITORING_URI) entry_peers = os.environ.get(AcnNodeConfig.ENTRY_PEERS_MADDRS) entry_peers_list = entry_peers.split(",") if entry_peers is not None else [] log_file = os.environ.get(AcnNodeConfig.ACN_LOG_FILE, "") node_config = AcnNodeConfig( key, uri, external_uri, delegate_uri, monitoring_uri, entry_peers_list, log_file, ) elif args.config_from_file is not None: node_config = AcnNodeConfig.from_file(args.config_from_file) else: with open(args.key, "r") as f: key = f.read().strip() node_config = AcnNodeConfig( key, args.uri, args.external_uri, args.delegate_uri, args.monitoring_uri, args.entry_peers_maddrs, args.log_file, ) node = AcnNodeStandalone(node_config, args.libp2p_node) try: node.run() except Exception: node.stop() raise ================================================ FILE: scripts/bump_aea_version.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Bump the AEA version throughout the code base. usage: bump_aea_version [-h] [--new-version NEW_VERSION] [-p KEY=VALUE [KEY=VALUE ...]] [--no-fingerprints] [--only-check] optional arguments: -h, --help show this help message and exit --new-version NEW_VERSION The new AEA version. -p KEY=VALUE [KEY=VALUE ...], --plugin-new-version KEY=VALUE [KEY=VALUE ...] Set a number of key-value pairs plugin-name=new- plugin-version --no-fingerprints Skip the computation of fingerprints. --only-check Only check the need of upgrade. Example of usage: python scripts/bump_aea_version.py --new-version 1.1.0 -p aea-ledger-fetchai=2.0.0 -p aea-ledger-ethereum=3.0.0 python scripts/bump_aea_version.py --only-check """ import argparse import inspect import logging import operator import os import re import sys from functools import wraps from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Sequence, cast from git import Repo from packaging.specifiers import SpecifierSet from packaging.version import Version from aea.helpers.base import compute_specifier_from_version from scripts.generate_ipfs_hashes import update_hashes logging.basicConfig( level=logging.INFO, format="[%(asctime)s][%(name)s][%(levelname)s] %(message)s" ) # if the key is a file, just process it # if the key is a directory, process all files below it PatternByPath = Dict[Path, str] AEA_DIR = Path("aea") CUR_PATH = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore ROOT_DIR = Path(os.path.join(CUR_PATH, "..")) PYPROJECT_TOML = ROOT_DIR / "pyproject.toml" PLUGINS_DIR = Path("plugins") ALL_PLUGINS = tuple(PLUGINS_DIR.iterdir()) """ This pattern captures a specifier set in the dependencies section of an AEA package configuration file, e.g.: dependencies: ... aea-ledger-fetchai: version: >=1.0.0,<2.0.0 """ YAML_DEPENDENCY_SPECIFIER_SET_PATTERN = ( "(?<={package_name}:\n version: )({specifier_set})" ) """ This pattern captures a specifier set for PyPI dependencies in JSON format. e.g.: "aea-ledger-fetchai": {"version": ">=2.0.0, <3.0.0"} """ JSON_DEPENDENCY_SPECIFIER_SET_PATTERN = ( '(?<="{package_name}": ."version": ")({specifier_set})(?=".)' ) _AEA_ALL_PATTERN = r"(?<={package_name}\[all\]==){version}" AEA_PATHS: PatternByPath = { Path("deploy-image", "Dockerfile"): _AEA_ALL_PATTERN, Path("develop-image", "docker-env.sh"): "(?<=aea-develop:){version}", Path("docs", "quickstart.md"): "(?<=v){version}", Path("examples", "tac_deploy", "Dockerfile"): _AEA_ALL_PATTERN, Path("scripts", "install.ps1"): _AEA_ALL_PATTERN, Path("scripts", "install.sh"): _AEA_ALL_PATTERN, Path( "tests", "test_docs", "test_bash_yaml", "md_files", "bash-quickstart.md" ): "(?<=v){version}", Path("user-image", "docker-env.sh"): "(?<=aea-user:){version}", } def check_executed(func: Callable) -> Callable: """Check a functor has been already executed; if yes, raise error.""" @wraps(func) def wrapper(self: Any, *args: Any, **kwargs: Any) -> None: if self.is_executed: raise ValueError("already executed") self._executed = True # pylint: disable=protected-access self._result = func(self, *args, **kwargs) # pylint: disable=protected-access return wrapper def compute_specifier_from_version_custom(version: Version) -> str: """ Post-process aea.helpers.compute_specifier_from_version The output is post-process in the following way: - remove spaces between specifier sets - put upper bound before lower bound :param version: the version :return: the specifier set according to the version and semantic versioning. """ specifier_set_str = compute_specifier_from_version(version) specifiers = SpecifierSet(specifier_set_str) upper, lower = sorted(specifiers, key=str) return f"{upper},{lower}" def get_regex_from_specifier_set(specifier_set: str) -> str: """ Get the regex for specifier sets. This function accepts input of the form: ">={lower_bound_version}, <{upper_bound_version}" And computes a regex pattern: ">={lower_bound_version}, *<{upper_bound_version}|<{upper_bound_version}, *>={lower_bound_version}" i.e. not considering the order of the specifiers. :param specifier_set: The string representation of the specifier set :return: a regex pattern """ specifiers = SpecifierSet(specifier_set) upper, lower = sorted(specifiers, key=str) alternatives = [] alternatives.append(f"{upper} *, *{lower}") alternatives.append(f"{lower} *, *{upper}") return "|".join(alternatives) class PythonPackageVersionBumper: """Utility class to bump Python package versions.""" IGNORE_DIRS = (Path(".git"),) def __init__( self, root_dir: Path, python_pkg_dir: Path, new_version: Version, files_to_pattern: PatternByPath, specifier_set_patterns: Sequence[str], package_name: Optional[str] = None, ignore_dirs: Sequence[Path] = (), ): """ Initialize the utility class. :param root_dir: the root directory from which to look for files. :param python_pkg_dir: the path to the Python package to upgrade. :param new_version: the new version. :param files_to_pattern: a list of pairs. :param specifier_set_patterns: a list of patterns for specifier sets. :param package_name: the Python package name aliases (defaults to dirname of python_pkg_dir). :param ignore_dirs: a list of paths to ignore during the substitution. """ self.root_dir = root_dir self.python_pkg_dir = python_pkg_dir self.new_version = new_version self.files_to_pattern = files_to_pattern self.specifier_set_patterns = specifier_set_patterns self.package_name = package_name or self.python_pkg_dir.name self.ignore_dirs = ignore_dirs or self.IGNORE_DIRS self.repo = Repo(self.root_dir) self._current_version: Optional[str] = None # functor pattern self._executed: bool = False self._result: Optional[bool] = None @property def is_executed(self) -> bool: """ Return true if the functor has been executed; false otherwise. :return: True if it has been executed, False otherwise. """ return self._executed @property def result(self) -> bool: """Get the result.""" if not self.is_executed: raise ValueError("not executed yet") return cast(bool, self._result) @check_executed def run(self) -> bool: """Main entrypoint.""" if not self.is_different_from_latest_tag(): logging.info( f"The package {self.python_pkg_dir} has no changes since last tag." ) return False new_version_string = str(self.new_version) current_version_str = self.update_version_for_package(new_version_string) # validate current version current_version: Version = Version(current_version_str) current_version_str = str(current_version) self._current_version = current_version_str self.update_version_for_files() return self.update_version_specifiers(current_version, self.new_version) def update_version_for_files(self) -> None: """Update the version.""" for filepath, regex_template in self.files_to_pattern.items(): self.update_version_for_file( filepath, cast(str, self._current_version), str(self.new_version), version_regex_template=regex_template, ) def update_version_for_package(self, new_version: str) -> str: """ Update version for file. If __version__.py is available, parse it and check for __version__ variable. Otherwise, try to parse pyproject.py. Otherwise, raise error. :param new_version: the new version :return: the current version """ version_path = self.python_pkg_dir / Path("__version__.py") path_regexp = [] if version_path.exists(): regex_template = '(?<=__version__ = [\'"])({version})(?=")' path = version_path path_regexp.append((path, regex_template)) if PYPROJECT_TOML.exists(): regex_template = r"(?<=\nversion = \")(?P{version})(?=\"\n)" path = PYPROJECT_TOML path_regexp.append((path, regex_template)) for path, regex_template in path_regexp: content = path.read_text() pattern = regex_template.format(version=".*") current_version_candidates = re.findall(pattern, content) more_than_one_match = len(current_version_candidates) > 1 if more_than_one_match: raise ValueError( f"find more than one match for current version in {path}: {current_version_candidates}" ) current_version = current_version_candidates[0] self.update_version_for_file( path, current_version, new_version, version_regex_template=regex_template, ) return current_version def update_version_for_file( self, path: Path, current_version: str, new_version: str, version_regex_template: Optional[str] = None, ) -> None: """ Update version for file. :param path: the file path :param current_version: the regex for the current version :param new_version: the new version :param version_regex_template: the regex template to replace with the current version. Defaults to exactly the current version. """ if version_regex_template is not None: regex_str = version_regex_template.format( package_name=self.package_name, version=current_version ) else: regex_str = current_version pattern = re.compile(regex_str) content = path.read_text() content = pattern.sub(new_version, content) path.write_text(content) def update_version_specifiers( self, old_version: Version, new_version: Version ) -> bool: """ Update specifier set. :param old_version: the old version. :param new_version: the new version. :return: True if the update has been done, False otherwise. """ old_specifier_set = compute_specifier_from_version_custom(old_version) new_specifier_set = compute_specifier_from_version_custom(new_version) logging.info(f"Old version specifier: {old_specifier_set}") logging.info(f"New version specifier: {new_specifier_set}") if old_specifier_set == new_specifier_set: logging.info("Not updating version specifier - they haven't changed.") return False for file in filter(lambda p: not p.is_dir(), self.root_dir.rglob("*")): dir_root = Path(file.parts[0]) if dir_root in self.ignore_dirs: logging.info(f"Skipping '{file}'...") continue logging.info( f"Replacing '{old_specifier_set}' with '{new_specifier_set}' in '{file}'... ", ) try: content = file.read_text() except UnicodeDecodeError as e: logging.info(f"Cannot read {file}: {str(e)}. Continue...") else: content = self._replace_specifier_sets( old_specifier_set, new_specifier_set, content ) file.write_text(content) return True def _replace_specifier_sets( self, old_specifier_set: str, new_specifier_set: str, content: str ) -> str: old_specifier_set_regex = get_regex_from_specifier_set(old_specifier_set) for pattern_template in self.specifier_set_patterns: regex = pattern_template.format( package_name=self.package_name, specifier_set=old_specifier_set_regex, ) pattern = re.compile(regex) if pattern.search(content) is not None: content = pattern.sub(new_specifier_set, content) return content def is_different_from_latest_tag(self) -> bool: """Check whether the package has changes since the latest tag.""" assert len(self.repo.tags) > 0, "no git tags found" latest_tag_str = str(self.repo.tags[-1]) args = latest_tag_str, "--", str(self.python_pkg_dir) logging.info(f"Running 'git diff {' '.join(args)}'") diff = self.repo.git.diff(*args) return diff != "" def parse_args() -> argparse.Namespace: """Parse arguments.""" parser = argparse.ArgumentParser("bump_aea_version") parser.add_argument( "--new-version", type=str, required=False, help="The new AEA version." ) parser.add_argument( "-p", "--plugin-new-version", metavar="KEY=VALUE", nargs="+", help="Set a number of key-value pairs plugin-name=new-plugin-version", default={}, ) parser.add_argument( "--no-fingerprints", action="store_true", help="Skip the computation of fingerprints.", ) parser.add_argument( "--only-check", action="store_true", help="Only check the need of upgrade." ) arguments_ = parser.parse_args() return arguments_ def make_aea_bumper(new_aea_version: Version) -> PythonPackageVersionBumper: """Build the AEA Python package version bumper.""" aea_version_bumper = PythonPackageVersionBumper( ROOT_DIR, AEA_DIR, new_aea_version, specifier_set_patterns=[ "(?<=aea_version:) *({specifier_set})", "(?<={package_name})({specifier_set})", ], files_to_pattern=AEA_PATHS, ) return aea_version_bumper def make_plugin_bumper( plugin_dir: Path, new_version: Version ) -> PythonPackageVersionBumper: """Build the plugin Python package version bumper.""" plugin_package_dir = plugin_dir / plugin_dir.name.replace("-", "_") plugin_version_bumper = PythonPackageVersionBumper( ROOT_DIR, plugin_package_dir, new_version, files_to_pattern={}, specifier_set_patterns=[ YAML_DEPENDENCY_SPECIFIER_SET_PATTERN, JSON_DEPENDENCY_SPECIFIER_SET_PATTERN, ], package_name=plugin_dir.name, ) return plugin_version_bumper def process_plugins(new_versions: Dict[str, Version]) -> bool: """Process plugins.""" result = False for plugin_dir in ALL_PLUGINS: plugin_dir_name = plugin_dir.name if plugin_dir_name not in new_versions: logging.info( f"Skipping {plugin_dir_name} as it is not specified in input {new_versions}" ) continue new_version = new_versions[plugin_dir_name] logging.info( f"Processing {plugin_dir_name}: upgrading to version {new_version}" ) plugin_bumper = make_plugin_bumper(plugin_dir, new_version) plugin_bumper.run() result |= plugin_bumper.result return result def parse_plugin_versions(key_value_strings: List[str]) -> Dict[str, Version]: """Parse plugin versions.""" return { plugin_name: Version(version) for plugin_name, version in map( operator.methodcaller("split", "="), key_value_strings ) } def only_check_bump_needed() -> int: """ Check whether a version bump is needed for AEA and plugins. :return: the return code """ bumpers: List[PythonPackageVersionBumper] = [] to_upgrade: List[Path] = [] bumpers.append(make_aea_bumper(None)) # type: ignore for plugin_dir in ALL_PLUGINS: bumpers.append(make_plugin_bumper(plugin_dir, None)) # type: ignore latest_tag = str(bumpers[0].repo.tags[-1]) logging.info( f"Checking packages that have changes from tag {latest_tag} and that require a new release..." ) for bumper in bumpers: if bumper.is_different_from_latest_tag(): logging.info( f"Package {bumper.python_pkg_dir} is different from latest tag {latest_tag}." ) to_upgrade.append(bumper.python_pkg_dir) if len(to_upgrade) > 0: logging.info("Packages to upgrade:") for path in to_upgrade: logging.info(path) else: logging.info("No packages to upgrade.") return 0 def bump(arguments: argparse.Namespace) -> int: """ Bump versions. :param arguments: arguments from argparse :return: the return code """ new_plugin_versions = parse_plugin_versions(arguments.plugin_new_version) logging.info(f"Parsed arguments: {arguments}") logging.info(f"Parsed plugin versions: {new_plugin_versions}") have_updated_specifier_set = False if arguments.new_version is not None: new_aea_version = Version(arguments.new_version) aea_version_bumper = make_aea_bumper(new_aea_version) aea_version_bumper.run() have_updated_specifier_set = aea_version_bumper.result logging.info("AEA package processed.") else: logging.info("AEA package not processed - no version provided.") logging.info("Processing plugins:") have_updated_specifier_set |= process_plugins(new_plugin_versions) logging.info("OK") return_code = 0 if arguments.no_fingerprints: logging.info( "Not updating fingerprints, since --no-fingerprints was specified." ) elif have_updated_specifier_set is False: logging.info( "Not updating fingerprints, since no specifier set has been updated." ) else: logging.info("Updating hashes and fingerprints.") return_code = update_hashes() return return_code def main() -> None: """Run the script.""" repo = Repo(str(ROOT_DIR)) if repo.is_dirty(): logging.info( "Repository is dirty. Please clean it up before running this script." ) sys.exit(1) arguments = parse_args() if arguments.only_check: sys.exit(only_check_bump_needed()) return_code = bump(arguments) sys.exit(return_code) if __name__ == "__main__": main() ================================================ FILE: scripts/bump_year.sh ================================================ #!/bin/sh if [ -z "$1" ]; then echo Please specify year as argument exit 1 fi if [ -z `echo $1|grep -E "^[0-9]{4}$"` ]; then echo Please specify year as 4 digits exit 1 fi YEAR=$1 SEARCH_RE="Copyright 2018-[0-9]{4} Fetch.AI Limited" UPDATE_STR="Copyright 2018-${YEAR} Fetch.AI Limited" echo $UPDATE_STR for i in `grep -l -E -R "${SEARCH_RE}" --include "*.py" ../`; do sed -E -e "s/${SEARCH_RE}/${UPDATE_STR}/" -i $i echo Updated: $i done ================================================ FILE: scripts/check_copyright_notice.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This script checks that all the Python files of the repository have: - (optional) the Python shebang - the encoding header; - the copyright notice; It is assumed the script is run from the repository root. """ import datetime import itertools import re import sys from pathlib import Path SUPPORTED_YEARS = [str(i) for i in range(2018, datetime.datetime.now().year + 1)] HEADER_REGEX = rf"""(#!/usr/bin/env python3 )?# -\*- coding: utf-8 -\*- # ------------------------------------------------------------------------------ # # (Copyright 2018-({"|".join(SUPPORTED_YEARS)}) Fetch.AI Limited|Copyright [0-9]{{4}}(-[0-9]{{4}})? [a-zA-Z_]+) # # Licensed under the Apache License, Version 2\.0 \(the \"License\"\); # you may not use this file except in compliance with the License\. # You may obtain a copy of the License at # # http://www\.apache\.org/licenses/LICENSE-2\.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \"AS IS\" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\. # See the License for the specific language governing permissions and # limitations under the License\. # # ------------------------------------------------------------------------------ """ def check_copyright(file: Path) -> bool: """ Given a file, check if the header stuff is in place. Return True if the files has the encoding header and the copyright notice, optionally prefixed by the shebang. Return False otherwise. :param file: the file to check. :return: True if the file is compliant with the checks, False otherwise. """ content = file.read_text() header_regex = re.compile(HEADER_REGEX, re.MULTILINE) return re.match(header_regex, content) is not None if __name__ == "__main__": python_files = itertools.chain( Path("aea").glob("**/*.py"), Path("packages").glob("**/*.py"), Path("tests").glob("**/*.py"), Path("plugins").glob("**/*.py"), Path("scripts").glob("**/*.py"), Path("examples", "gym_ex").glob("**/*.py"), Path("examples", "ml_ex").glob("**/*.py"), ) # filter out protobuf files (*_pb2.py) python_files_filtered = filter( lambda x: not str(x).endswith("_pb2.py") and "data/reference_protocols/" not in str(x), python_files, ) bad_files = [ filepath for filepath in python_files_filtered if not check_copyright(filepath) ] if len(bad_files) > 0: print("The following files are not well formatted:") print("\n".join(map(str, bad_files))) sys.exit(1) else: print("OK") sys.exit(0) ================================================ FILE: scripts/check_doc_links.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Script to check that all internal doc links are valid.""" import re import sys import xml.etree.ElementTree as ET # nosec from pathlib import Path from typing import Pattern, Set from aea.helpers import http_requests as requests LINK_PATTERN_MD = re.compile(r"\[([^]]+)]\(\s*([^]]+)\s*\)") LINK_PATTERN = re.compile(r'(?<=]+src="([^">]+)"') RELATIVE_PATH_STR = "../" RELATIVE_PATH_STR_LEN = len(RELATIVE_PATH_STR) INDEX_FILE_PATH = Path("docs/index.md") WHITELIST_URL_TO_CODE = { "https://dl.acm.org/doi/10.1145/3212734.3212736": 302, "https://s-oef.fetch.ai:443": 405, "https://golang.org/dl/": 403, "https://www.wiley.com/en-gb/An+Introduction+to+MultiAgent+Systems%2C+2nd+Edition-p-9781119959519": 403, "https://colab.research.google.com": 403, "https://github.com/fetchai/networks-capricorn": 404, } IGNORE: Set[str] = {"https://faucet.metamask.io/"} def is_url_reachable(url: str) -> bool: """ Check if a url is reachable. :param url: the url to check :return: bool """ if url.startswith("http://localhost") or url.startswith("http://127.0.0.1"): return True if url in IGNORE: return True try: response = requests.head(url, timeout=20) if response.status_code == 200: return True if response.status_code in [403, 405, 302, 404]: return WHITELIST_URL_TO_CODE.get(url, 404) in [403, 405, 302, 404] return False except Exception as e: # pylint: disable=broad-except,redefined-outer-name print(e) return False def check_header_in_file(header: str, file: Path) -> None: """ Check if the string is present in the file. :param header: the header :param file: the file path """ with open(file, encoding="utf-8") as f: s = f.read() if header not in s: raise ValueError( "Header={} not found in file={}!".format(header, str(file)) ) def validate_internal_url(file: Path, url: str, all_files: Set[Path]) -> None: """ Validate whether the url is a valid path to a file in docs. :param file: the file path :param url: the url to check :param all_files: all the docs files. """ is_index_file = file == INDEX_FILE_PATH if not url.startswith(RELATIVE_PATH_STR) and not is_index_file: raise ValueError("Invalid relative path={} in file={}!".format(url, str(file))) md_index = url.find(".md") if md_index != -1: raise ValueError( "Path={} contains invalid `.md` in file={}!".format(url, str(file)) ) hash_index = url.find("#") if hash_index == -1: n_url = url[RELATIVE_PATH_STR_LEN:] if not is_index_file else url n_url = n_url[:-1] if n_url[-1] == "/" else n_url path = Path("docs/{}.md".format(n_url)) header = "" else: n_url = url[RELATIVE_PATH_STR_LEN:hash_index] if not is_index_file else url n_url = n_url[:-1] if n_url[-1] == "/" else n_url path = Path("docs/{}.md".format(n_url)) header = url[hash_index:] if path not in all_files: raise ValueError( "Path={} found in file={} does not exist!".format(str(path), str(file)) ) if header != "": check_header_in_file(header, file) def _checks_all_html(file: Path, regex: Pattern = LINK_PATTERN_MD) -> None: """ Checks a file for matches to a pattern. :param file: the file path :param regex: the regex to check for in the file. """ matches = regex.finditer(file.read_text()) for _ in matches: raise ValueError("Markdown link found in file={}!".format(str(file))) def is_external_url(url: str) -> bool: """ Check if an URL is an external URL. :param url: the URL :return: true if it is external, false otherwise. """ return url.startswith("https://") or url.startswith("http://") def validate_external_url(url: str, file: Path) -> None: """ Validate external URL. :param url: the URL. :param file: the file where the URL is found. """ if not is_url_reachable(url): raise ValueError("Could not reach url={} in file={}!".format(url, str(file))) def _checks_link( file: Path, all_files: Set[Path], regex: Pattern = LINK_PATTERN ) -> None: """ Checks a file for matches to a pattern. :param file: the file path :param all_files: all the doc file paths :param regex: the regex to check for in the file. """ matches = regex.finditer(file.read_text()) for match in matches: result = match.group() if is_external_url(result): validate_external_url(result, file) else: validate_internal_url(file, result, all_files) def _checks_image(file: Path, regex: Pattern = IMAGE_PATTERN) -> None: """ Checks a file for matches to a pattern. :param file: the file path :param regex: the regex to check for in the file. """ if file in [Path("docs/version.md"), Path("docs/install.md")]: return matches = regex.finditer(file.read_text()) for match in matches: result = match.group(1) png_index = result.find(".png") jpg_index = result.find(".jpg") if png_index != -1 or jpg_index != -1: img_path = Path("docs/{}".format(result[RELATIVE_PATH_STR_LEN:])) if not img_path.exists(): raise ValueError( "Image path={} in file={} not found!".format(img_path, str(file)) ) return if result.startswith("https") or result.startswith("http"): if not is_url_reachable(result): raise ValueError( "Could not reach url={} in file={}!".format(result, str(file)) ) raise ValueError("Image path={} in file={} not `.png` or `.jpg`!") def _checks_target_blank(file: Path) -> None: """ Check target blank. :param file: the file. """ matches = re.finditer("(.+?)", file.read_text()) for match in matches: tag = ET.fromstring(match.group()) # nosec href = tag.attrib.get("href") target = tag.attrib.get("target") if href is not None and is_external_url(href) and target != "_blank": raise ValueError( f"Anchor tag with href={href} and target={target} in file {str(file)} is not valid." ) def check_file(file: Path, all_files: Set[Path]) -> None: """ Check the links in the file. :param file: the file path :param all_files: all the doc file paths """ _checks_all_html(file) _checks_link(file, all_files) _checks_target_blank(file) _checks_image(file) def get_all_docs_files() -> Set[Path]: """ Get all file paths to docs or api docs. :return: list of all paths """ all_files = Path("docs").glob("**/*.md") return set(all_files) if __name__ == "__main__": all_docs_files = get_all_docs_files() docs_files = Path("docs").glob("*.md") try: for file_ in docs_files: print("Processing " + str(file_)) check_file(file_, all_docs_files) except Exception as e: # pylint: disable=broad-except print(e) sys.exit(1) print("Done!") sys.exit(0) ================================================ FILE: scripts/check_imports_and_dependencies.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Check aea dependencies.""" import copy import dis import importlib import re import sys from collections import defaultdict from functools import wraps from pathlib import Path from typing import Any, Callable, Dict, Generator, List, Optional, Set, Tuple, Union AEA_ROOT_DIR = Path(__file__).parent.parent sys.path.append(str(AEA_ROOT_DIR)) from aea.crypto.registries import ( # noqa # pylint: disable=wrong-import-position crypto_registry, ) IGNORE: Set[str] = {"pkg_resources", "distutils.dir_util"} DEP_NAME_RE = re.compile(r"(^[^=><\[]+)", re.I) # type: ignore def list_decorator(fn: Callable) -> Callable: """Wraps generator to return list.""" @wraps(fn) def wrapper(*args: Any, **kwargs: Any) -> List[Any]: return list(fn(*args, **kwargs)) return wrapper class DependenciesTool: """Tool to work with setup.py dependencies.""" @staticmethod def get_package_files(package_name: str) -> List[Path]: """Get package files list.""" from pip._internal.commands.show import ( # type: ignore # noqa # pylint: disable=import-outside-toplevel search_packages_info, ) packages_info = list(search_packages_info([package_name])) if len(packages_info) == 0: raise Exception(f"package {package_name} not found") if isinstance(packages_info[0], dict): files = packages_info[0]["files"] location = packages_info[0]["location"] else: files = packages_info[0].files # type: ignore location = packages_info[0].location # type: ignore return [Path(location) / i for i in files] # type: ignore @staticmethod def clean_dependency_name(dependecy_specification: str) -> str: """Get dependency name from dependency specification.""" match = DEP_NAME_RE.match(dependecy_specification) if not match: raise ValueError(f"Bad dependency specification: {dependecy_specification}") return match.groups()[0] class ImportsTool: """Tool to work with 3rd part imports in source code.""" @staticmethod def get_imports_for_file(pyfile: Union[str, Path]) -> List[str]: """Get all imported modules for python source file.""" with open(pyfile, "r", encoding="utf-8") as f: statements = f.read() instructions = dis.get_instructions(statements) # type: ignore imports = [i for i in instructions if "IMPORT" in i.opname] grouped: Dict[str, List[str]] = defaultdict(list) for instr in imports: grouped[instr.opname].append(instr.argval) return grouped["IMPORT_NAME"] @staticmethod def get_module_file(module_name: str) -> str: """Get module source file name.""" try: mod = importlib.import_module(module_name) return getattr(mod, "__file__", "") except (AttributeError, ModuleNotFoundError): return "" @staticmethod @list_decorator def list_all_pyfiles( root_path: Union[Path, str], pattern: str = "**/*.py" ) -> Generator: """List all python files in directory.""" root_path = Path(root_path) for path in root_path.glob(pattern): yield path.relative_to(root_path) @classmethod @list_decorator def get_third_part_imports_for_file(cls, pyfile: str) -> Generator: """Get list of third part modules imported for source file.""" imports = cls.get_imports_for_file(pyfile) for module_name in imports: pyfile = cls.get_module_file(module_name) if not pyfile: continue if "site-packages" not in Path(pyfile).parts: continue yield module_name, Path(pyfile) @classmethod @list_decorator def list_all_pyfiles_with_3rdpart_imports( cls, root_path: Union[str, Path], pattern: str = "**/*.py" ) -> Generator: """Get list of all python sources with 3rd party modules imported.""" for pyfile in cls.list_all_pyfiles(root_path, pattern=pattern): mods = list(cls.get_third_part_imports_for_file(root_path / pyfile)) if not mods: continue yield Path(pyfile), list(set(mods)) class CheckTool: """Tool to check imports in sources match dependencies in setup.py.""" @classmethod def get_section_dependencies_from_setup(cls) -> Dict[str, Dict[str, List[Path]]]: """Get sections with dependencies with files lists.""" spec = importlib.util.spec_from_file_location( "setup", str(AEA_ROOT_DIR / "setup.py") ) assert spec setup = importlib.util.module_from_spec(spec) sys.modules[spec.name] = setup spec.loader.exec_module(setup) # type: ignore sections_dependencies = copy.deepcopy(setup.all_extras) # type: ignore sections_dependencies.pop("all") base = setup.base_deps # type: ignore for crypto_id in crypto_registry.supported_ids: # type: ignore if crypto_id == "fetchai": crypto_id = "fetch" if crypto_id in sections_dependencies: sections_dependencies.pop(crypto_id) sections_dependencies["base"] = base return cls.sections_dependencies_add_files(sections_dependencies) @staticmethod def sections_dependencies_add_files( sections_dependencies: Dict[str, List[str]] ) -> Dict[str, Dict[str, List[Path]]]: """Add packages file lists to dependencies in sections.""" result: Dict[str, Dict[str, List[Path]]] = defaultdict( lambda: defaultdict(list) ) for section, deps in sections_dependencies.items(): for dep in deps: dep = DependenciesTool.clean_dependency_name(dep) result[section][dep] = DependenciesTool.get_package_files(dep) return result @classmethod def run(cls) -> None: """Run dependency check.""" print("Dependencies check report:") print("==========================") files_and_modules = ImportsTool.list_all_pyfiles_with_3rdpart_imports( AEA_ROOT_DIR, pattern="aea/**/*.py" ) sections_dependencies = cls.get_section_dependencies_from_setup() sections_imports = cls.make_sections_with_3rdpart_imports( files_and_modules, set(sections_dependencies.keys()) ) missed_deps_for_imports, deps_not_imported_directly = cls.check_imports( sections_imports, sections_dependencies # type: ignore ) for section, unresolved_imports in missed_deps_for_imports.items(): print( f"Section `{section}` unresolved imports: {', '.join(unresolved_imports)}" ) if deps_not_imported_directly: if missed_deps_for_imports: print() print( f"Dependencies not imported in code directly: {', '.join(deps_not_imported_directly)}" ) if missed_deps_for_imports or deps_not_imported_directly: sys.exit(1) else: print("All good!") @staticmethod def make_sections_with_3rdpart_imports( files_and_modules: List[Tuple[str, List[Tuple[str, Path]]]], section_names: Set[str], ) -> Dict[str, Set[Tuple[str, Path]]]: """Make sections with list of 3r part imports.""" sections_imports: Dict[str, Set[Tuple[str, Path]]] = defaultdict(set) for pyfile, imports in files_and_modules: section_name = Path(pyfile).parts[1] if section_name not in section_names: section_name = "base" sections_imports[section_name].update(imports) return sections_imports @staticmethod def check_imports( sections_imports: Dict[str, Set[Tuple[str, Path]]], sections_dependencies: Dict[str, Dict[str, List[str]]], ) -> Tuple[Dict[str, List[str]], List[str]]: """Find missing dependencies for imports and not imported dependencies.""" def _find_dependency_for_module( dependencies: Dict[str, List[str]], pyfile: str ) -> Optional[str]: for package, files in dependencies.items(): if pyfile in files: return package return None sections_imports_packages: Dict[str, Dict[str, Optional[str]]] = defaultdict( dict ) for section, modules in sections_imports.items(): for module, pyfile in modules: package = _find_dependency_for_module( sections_dependencies.get(section, {}), pyfile # type: ignore ) if module not in IGNORE: sections_imports_packages[section][module] = package all_dependencies_set = set( sum((list(i.keys()) for _, i in sections_dependencies.items()), []) ) used_dependencies_set = set( sum( [ list(section.values()) for section in sections_imports_packages.values() ], [], ) ) deps_not_imported_directly: List[str] = list( all_dependencies_set - used_dependencies_set ) missed_deps_for_imports: Dict[str, List[str]] = { section: [k for k, v in modules.items() if v is None] for section, modules in sections_imports_packages.items() if None in modules.values() } return missed_deps_for_imports, deps_not_imported_directly if __name__ == "__main__": CheckTool.run() ================================================ FILE: scripts/check_package_versions_in_docs.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Check that package ids are in sync with the current packages. Run this script from the root of the project directory: python scripts/check_package_versions_in_docs.py """ import re import sys from itertools import chain from pathlib import Path from typing import Any, Callable, Dict, Generator, List, Match, Pattern, Set import yaml from aea.configurations.base import ComponentType, PackageId, PackageType, PublicId from aea.configurations.constants import ( AGENTS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, ) PUBLIC_ID_REGEX = PublicId.PUBLIC_ID_REGEX[1:-1] """This regex removes the '^' and '$' respectively, at the beginning and at the end.""" ADD_COMMAND_IN_DOCS = re.compile( "aea +add +({}) +({})".format("|".join(map(str, ComponentType)), PUBLIC_ID_REGEX) ) """ This regex matches strings of the form: aea add (protocol|connection|contract|skill) some_author/some_package:some_version_number """ FETCH_COMMAND_IN_DOCS = re.compile("aea +fetch +({})".format(PUBLIC_ID_REGEX)) """ This regex matches strings of the form: aea fetch some_author/some_package:some_version_number """ class PackageIdNotFound(Exception): """Custom exception for package id not found.""" def __init__( self, file: Path, package_id: PackageId, match_obj: Any, *args: Any ) -> None: """ Initialize PackageIdNotFound exception. :param file: path to the file checked. :param package_id: package id not found. :param match_obj: re.Match object. :param args: super class args. """ super().__init__(*args) self.file = file self.package_id = package_id self.match_obj = match_obj DEFAULT_CONFIG_FILE_PATHS = [] # type: List[Path] CONFIG_FILE_NAMES = [ DEFAULT_AEA_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, ] # type: List[str] def default_config_file_paths() -> Generator: """Get (generator) the default config file paths.""" for item in DEFAULT_CONFIG_FILE_PATHS: yield item def unified_yaml_load(configuration_file: Path) -> Dict: """ Load YAML file, unified (both single- and multi-paged). :param configuration_file: the configuration file path. :return: the data. """ package_type = configuration_file.parent.parent.name with configuration_file.open() as fp: if package_type != AGENTS: return yaml.safe_load(fp) # when it is an agent configuration file, # we are interested only in the first page of the YAML, # because the dependencies are contained only there. data = yaml.safe_load_all(fp) return list(data)[0] def get_public_id_from_yaml(configuration_file: Path) -> PublicId: """ Get the public id from yaml. :param configuration_file: the path to the config yaml :return: public id """ data = unified_yaml_load(configuration_file) author = data.get("author", None) if not author: raise KeyError(f"No author field in {str(configuration_file)}") # handle the case when it's a package or agent config file. try: name = data["name"] if "name" in data else data["agent_name"] except KeyError: print(f"No name or agent_name field in {str(configuration_file)}") raise version = data.get("version", None) if not version: raise KeyError(f"No version field in {str(configuration_file)}") return PublicId(author, name, version) def find_all_packages_ids() -> Set[PackageId]: """Find all packages ids.""" package_ids: Set[PackageId] = set() packages_dir = Path("packages") config_files = [ path for path in packages_dir.glob("*/*/*/*.yaml") if any(file in str(path) for file in CONFIG_FILE_NAMES) ] for configuration_file in chain(config_files, default_config_file_paths()): package_type = PackageType(configuration_file.parts[-3][:-1]) package_public_id = get_public_id_from_yaml(configuration_file) package_id = PackageId(package_type, package_public_id) package_ids.add(package_id) return package_ids ALL_PACKAGE_IDS: Set[PackageId] = find_all_packages_ids() def _checks( file: Path, regex: Pattern, extract_package_id_from_match: Callable[["re.Match"], PackageId], ) -> None: matches = regex.finditer(file.read_text()) for match in matches: package_id = extract_package_id_from_match(match) if package_id not in ALL_PACKAGE_IDS: raise PackageIdNotFound( file, package_id, match, "Package {} not found.".format(package_id) ) print(str(package_id), "OK!") def check_add_commands(file: Path) -> None: """ Check that 'aea add' commands of the documentation file contains known package ids. :param file: path to the file. """ def extract_package_id(match: Match) -> PackageId: package_type, package = match.group(1), match.group(2) package_id = PackageId(PackageType(package_type), PublicId.from_str(package)) return package_id _checks(file, ADD_COMMAND_IN_DOCS, extract_package_id) def check_fetch_commands(file: Path) -> None: """ Check that 'aea fetch' commands of the documentation file contains known package ids. :param file: path to the file. """ def extract_package_id(match: Match) -> PackageId: package_public_id = match.group(1) package_id = PackageId(PackageType.AGENT, PublicId.from_str(package_public_id)) return package_id _checks(file, FETCH_COMMAND_IN_DOCS, extract_package_id) def check_file(file: Path) -> None: """ Check documentation file. :param file: path to the file to check. """ check_add_commands(file) check_fetch_commands(file) def handle_package_not_found(e: PackageIdNotFound) -> None: """Handle PackageIdNotFound errors.""" print("=" * 50) print("Package {} not found.".format(e.package_id)) print("Path to file: ", e.file) print("Span: ", e.match_obj.span(0)) print("Full Match: ", e.match_obj.group(0)) sys.exit(1) if __name__ == "__main__": docs_files = Path("docs").glob("**/*.md") try: for file_ in docs_files: print("Processing " + str(file_)) check_file(file_) except PackageIdNotFound as e_: handle_package_not_found(e_) sys.exit(1) print("Done!") sys.exit(0) ================================================ FILE: scripts/check_packages.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Run different checks on AEA packages. Namely: - Check that every package has existing dependencies - Check that every package has non-empty description Run this script from the root of the project directory: python scripts/check_packages.py """ import pprint import sys from functools import partial from itertools import chain from pathlib import Path from typing import Any, Dict, Generator, List, Set import yaml from aea.configurations.base import PackageId, PackageType, PublicId from aea.configurations.constants import ( AGENTS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, ) DEFAULT_CONFIG_FILE_PATHS = [] # type: List[Path] CONFIG_FILE_NAMES = [ DEFAULT_AEA_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, ] # type: List[str] class DependencyNotFound(Exception): """Custom exception for dependencies not found.""" def __init__( self, configuration_file: Path, expected_deps: Set[PackageId], missing_dependencies: Set[PackageId], *args: Any, ) -> None: """ Initialize DependencyNotFound exception. :param configuration_file: path to the checked file. :param expected_deps: expected dependencies. :param missing_dependencies: missing dependencies. :param args: super class args. """ super().__init__(*args) self.configuration_file = configuration_file self.expected_dependencies = expected_deps self.missing_dependencies = missing_dependencies class EmptyPackageDescription(Exception): """Custom exception for empty description field.""" def __init__( self, configuration_file: Path, *args: Any, ) -> None: """ Initialize EmptyPackageDescription exception. :param configuration_file: path to the checked file. :param args: super class args. """ super().__init__(*args) self.configuration_file = configuration_file def find_all_configuration_files() -> List: """Find all configuration files.""" packages_dir = Path("packages") config_files = [ path for path in packages_dir.glob("*/*/*/*.yaml") if any(file in str(path) for file in CONFIG_FILE_NAMES) ] return list(chain(config_files, default_config_file_paths())) def default_config_file_paths() -> Generator: """Get (generator) the default config file paths.""" for item in DEFAULT_CONFIG_FILE_PATHS: yield item def get_public_id_from_yaml(configuration_file: Path) -> PublicId: """ Get the public id from yaml. :param configuration_file: the path to the config yaml :return: public id """ data = unified_yaml_load(configuration_file) author = data.get("author", None) if not author: raise KeyError(f"No author field in {str(configuration_file)}") # handle the case when it's a package or agent config file. try: name = data["name"] if "name" in data else data["agent_name"] except KeyError: print(f"No name or agent_name field in {str(configuration_file)}") raise version = data.get("version", None) if not version: raise KeyError(f"No version field in {str(configuration_file)}") return PublicId(author, name, version) def find_all_packages_ids() -> Set[PackageId]: """Find all packages ids.""" package_ids: Set[PackageId] = set() for configuration_file in find_all_configuration_files(): package_type = PackageType(configuration_file.parts[-3][:-1]) package_public_id = get_public_id_from_yaml(configuration_file) package_id = PackageId(package_type, package_public_id) package_ids.add(package_id) return package_ids def handle_dependency_not_found(e: DependencyNotFound) -> None: """Handle PackageIdNotFound errors.""" sorted_expected = list(map(str, sorted(e.expected_dependencies))) sorted_missing = list(map(str, sorted(e.missing_dependencies))) print("=" * 50) print(f"Package {e.configuration_file}:") print(f"Expected: {pprint.pformat(sorted_expected)}") print(f"Missing: {pprint.pformat(sorted_missing)}") print("=" * 50) def handle_empty_package_description(e: EmptyPackageDescription) -> None: """Handle EmptyPackageDescription errors.""" print("=" * 50) print(f"Package '{e.configuration_file}' has empty description field.") print("=" * 50) def unified_yaml_load(configuration_file: Path) -> Dict: """ Load YAML file, unified (both single- and multi-paged). :param configuration_file: the configuration file path. :return: the data. """ package_type = configuration_file.parent.parent.name with configuration_file.open() as fp: if package_type != AGENTS: return yaml.safe_load(fp) # when it is an agent configuration file, # we are interested only in the first page of the YAML, # because the dependencies are contained only there. data = yaml.safe_load_all(fp) return list(data)[0] def check_dependencies( configuration_file: Path, all_packages_ids: Set[PackageId] ) -> None: """ Check dependencies of configuration file. :param configuration_file: path to a package configuration file. :param all_packages_ids: all the package ids. """ data = unified_yaml_load(configuration_file) def _add_package_type(package_type: PackageType, public_id_str: str) -> PackageId: return PackageId(package_type, PublicId.from_str(public_id_str)) def _get_package_ids( package_type: PackageType, public_ids: Set[PublicId] ) -> Set[PackageId]: return set(map(partial(_add_package_type, package_type), public_ids)) dependencies: Set[PackageId] = set.union( *[ _get_package_ids(package_type, data.get(package_type.to_plural(), set())) for package_type in list(PackageType) ] ) diff = dependencies.difference(all_packages_ids) if len(diff) > 0: raise DependencyNotFound(configuration_file, dependencies, diff) def check_description(configuration_file: Path) -> None: """Check description field of a package is non-empty.""" yaml_object = unified_yaml_load(configuration_file) description = yaml_object.get("description") if description == "": raise EmptyPackageDescription(configuration_file) if __name__ == "__main__": all_packages_ids_ = find_all_packages_ids() failed: bool = False for file in find_all_configuration_files(): try: print("Processing " + str(file)) check_dependencies(file, all_packages_ids_) check_description(file) except DependencyNotFound as e_: handle_dependency_not_found(e_) failed = True except EmptyPackageDescription as e_: handle_empty_package_description(e_) failed = True if failed: print("Failed!") sys.exit(1) else: print("OK!") sys.exit(0) ================================================ FILE: scripts/check_pipfile_and_toxini.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This script checks that dependencies in tox.ini and Pipfile match.""" import re import sys from typing import Dict # specified in setup.py WHITELIST = {"base58": ">=1.0.3"} # fix for python 3.6 and tox EXCLUSIONS_LIST = [("tensorflow", "==2.4.0")] DEP_NAME_RE = re.compile(r"(^[^=><\[]+)", re.I) # type: ignore def get_deps_in_pipfile(file: str = "Pipfile") -> Dict[str, str]: """ Get the dependencies of the Pipfile. :param file: the file to check. :return: dictionary with dependencies and their versions """ result: Dict[str, str] = WHITELIST with open(file, "r", encoding="utf-8") as f: is_dev_dependency = False for line in f: if line == "[dev-packages]\n": is_dev_dependency = True continue if line == "[packages]\n": is_dev_dependency = True continue if not is_dev_dependency: continue try: package, version = line.split(" = ") result[package] = version.strip("\n").strip('"') except Exception: # nosec # pylint: disable=broad-except pass return result def check_versions_in_tox_correct(file: str = "tox.ini") -> None: """ Check the versions in tox are matching the ones in Pipfile. :param file: the file to check. """ dependencies = get_deps_in_pipfile() with open(file, "r", encoding="utf-8") as f: for line in f: line = line.strip() looks_like_deps = False for match_type in ["==", ">=", "<"]: if match_type in line: looks_like_deps = True break if not looks_like_deps: continue m = DEP_NAME_RE.match(line) if not m: continue name_part = m.groups()[0] version_part = line.replace(name_part, "").strip() check_match( name_part.strip(" "), version_part.strip("\n"), dependencies, ) def check_match( name_part: str, version_part: str, dependencies: Dict[str, str] ) -> None: """Check for a match independencies.""" if (name_part, version_part) in EXCLUSIONS_LIST: return result = False for package, version_and_match_type in dependencies.items(): if package == name_part: if version_and_match_type == f"{version_part}": result = True break print( f"Non-matching versions for package={package}, {name_part}. Expected='{version_and_match_type}', found='{version_part}'." ) sys.exit(1) if not result: print(f"Package not found for: {name_part}") sys.exit(1) if __name__ == "__main__": check_versions_in_tox_correct() print("OK") sys.exit(0) ================================================ FILE: scripts/common.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Common utils for scripts.""" import logging import re import subprocess # nosec import sys from io import StringIO from pathlib import Path from typing import Match, cast from aea.configurations.base import ProtocolSpecification from aea.configurations.loader import ConfigLoader SPECIFICATION_REGEX = re.compile(r"(---\nname.*\.\.\.)", re.DOTALL) PROTOCOL_SPECIFICATION_ID_IN_SPECIFICATION_REGEX = re.compile( "^protocol_specification_id: (.*)$", re.MULTILINE ) PACKAGES_DIR = Path("packages") def setup_logger(name: str) -> logging.Logger: """Set up the logger.""" FORMAT = "[%(asctime)s][%(levelname)s] %(message)s" logging.basicConfig(format=FORMAT) logger_ = logging.getLogger(name) logger_.setLevel(logging.INFO) return logger_ logger = setup_logger(__name__) def enforce(condition: bool, message: str = "") -> None: """Custom assertion.""" if not condition: raise AssertionError(message) def check_working_tree_is_dirty() -> None: """Check if the current Git working tree is dirty.""" print("Checking whether the Git working tree is dirty...") result = subprocess.check_output(["git", "diff", "--stat"]) # nosec if len(result) > 0: print("Git working tree is dirty:") print(result.decode("utf-8")) sys.exit(1) else: print("All good!") def load_protocol_specification_from_string( specification_content: str, ) -> ProtocolSpecification: """Load a protocol specification from string.""" file = StringIO(initial_value=specification_content) config_loader = ConfigLoader( "protocol-specification_schema.json", ProtocolSpecification ) protocol_spec = config_loader.load_protocol_specification(file) return protocol_spec def get_protocol_specification_from_readme(package_path: Path) -> str: """Get the protocol specification from the package README.""" logger.info(f"Get protocol specification from README {package_path}") readme = package_path / "README.md" readme_content = readme.read_text() enforce( "## Specification" in readme_content, f"Cannot find specification section in {package_path}", ) search_result = SPECIFICATION_REGEX.search(readme_content) enforce( search_result is not None, f"Cannot find specification section in README of {package_path}", ) specification_content = cast(Match, search_result).group(0) # just for validation of the parsed string load_protocol_specification_from_string(specification_content) return specification_content def get_protocol_specification_id_from_specification(specification: str) -> str: """Get the protocol specification id from the protocol specification.""" matches = PROTOCOL_SPECIFICATION_ID_IN_SPECIFICATION_REGEX.findall(specification) enforce( len(matches) == 1, f"Expected exactly one protocol specification id, found: {matches}", ) spec_id = matches[0] return spec_id ================================================ FILE: scripts/deploy_to_registry.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This script deploys all new packages to registry.""" import os import shutil import sys import time from itertools import chain from pathlib import Path from typing import Dict, Generator, List, Set import yaml from click.testing import CliRunner from aea.cli import cli from aea.configurations.base import PackageId, PackageType, PublicId from aea.configurations.constants import ( AGENTS, DEFAULT_AEA_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, ) CONFIG_FILE_NAMES = [ DEFAULT_AEA_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_CONTRACT_CONFIG_FILE, DEFAULT_PROTOCOL_CONFIG_FILE, ] # type: List[str] CLI_LOG_OPTION = ["-v", "OFF"] DEFAULT_CONFIG_FILE_PATHS = [] # type: List[Path] def default_config_file_paths() -> Generator: """Get (generator) the default config file paths.""" for item in DEFAULT_CONFIG_FILE_PATHS: yield item def unified_yaml_load(configuration_file: Path) -> Dict: """ Load YAML file, unified (both single- and multi-paged). :param configuration_file: the configuration file path. :return: the data. """ package_type = configuration_file.parent.parent.name with configuration_file.open() as fp: if package_type != AGENTS: return yaml.safe_load(fp) # when it is an agent configuration file, # we are interested only in the first page of the YAML, # because the dependencies are contained only there. data = yaml.safe_load_all(fp) return list(data)[0] def get_public_id_from_yaml(configuration_file: Path) -> PublicId: """ Get the public id from yaml. :param configuration_file: the path to the config yaml :return: public id """ data = unified_yaml_load(configuration_file) author = data["author"] # handle the case when it's a package or agent config file. name = data["name"] if "name" in data else data["agent_name"] version = data["version"] return PublicId(author, name, version) def find_all_packages_ids() -> Set[PackageId]: """Find all packages ids.""" package_ids: Set[PackageId] = set() packages_dir = Path("packages") config_files = [ path for path in packages_dir.glob("*/*/*/*.yaml") if any(file in str(path) for file in CONFIG_FILE_NAMES) ] for configuration_file in chain(config_files, default_config_file_paths()): package_type = PackageType(configuration_file.parts[-3][:-1]) package_public_id = get_public_id_from_yaml(configuration_file) package_id = PackageId(package_type, package_public_id) package_ids.add(package_id) return package_ids ALL_PACKAGE_IDS: Set[PackageId] = find_all_packages_ids() def check_correct_author(runner: CliRunner) -> None: """ Check whether the correct author is locally configured. :param runner: the cli runner """ result = runner.invoke( cli, [*CLI_LOG_OPTION, "init"], standalone_mode=False, ) if "{'author': 'fetchai'}" not in result.output: print("Log in with fetchai credentials. Stopping...") sys.exit(0) else: print("Logged in with fetchai credentials. Continuing...") def push_package(package_id: PackageId, runner: CliRunner) -> None: """ Pushes a package (protocol/contract/connection/skill) to registry. Specifically: - creates an empty agent project - adds the relevant package from local 'packages' dir (and its dependencies) - moves the relevant package out of vendor dir - pushes the relevant package to registry :param package_id: the package id :param runner: the cli runner """ print( "Trying to push {}: {}".format( package_id.package_type.value, str(package_id.public_id) ) ) cwd = os.getcwd() try: agent_name = "some_agent" result = runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", "--empty", agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(agent_name) result = runner.invoke( cli, [ *CLI_LOG_OPTION, "add", "--local", package_id.package_type.value, str(package_id.public_id), ], standalone_mode=False, ) assert result.exit_code == 0 src = os.path.join( "vendor", package_id.public_id.author, package_id.package_type.value + "s", package_id.public_id.name, ) dest = os.path.join( package_id.package_type.value + "s", package_id.public_id.name ) shutil.copytree(src, dest) result = runner.invoke( cli, [ *CLI_LOG_OPTION, "push", package_id.package_type.value, str(package_id.public_id), ], standalone_mode=False, ) assert ( result.exit_code == 0 ), "Publishing {} with public_id '{}' failed with: {}".format( package_id.package_type, package_id.public_id, result.output ) except Exception as e: # pylint: disable=broad-except print("\n\nAn exception occured: {}\n\n".format(e)) finally: os.chdir(cwd) result = runner.invoke( cli, [*CLI_LOG_OPTION, "delete", agent_name], standalone_mode=False, ) assert result.exit_code == 0 print( "Successfully pushed {}: {}".format( package_id.package_type.value, str(package_id.public_id) ) ) def publish_agent(package_id: PackageId, runner: CliRunner) -> None: """ Publishes an agent to registry. Specifically: - fetches an agent project from local 'packages' dir (and its dependencies) - publishes the agent project to registry :param package_id: the package id :param runner: the cli runner :return: None """ if os.path.isdir(package_id.public_id.name): print( f"\n\nFolder with name '{package_id.public_id.name}' already exists. Skipping publication of {str(package_id.public_id)}\n\n" ) return print( "Trying to push {}: {}".format( package_id.package_type.value, str(package_id.public_id) ) ) cwd = os.getcwd() try: result = runner.invoke( cli, [*CLI_LOG_OPTION, "fetch", "--local", str(package_id.public_id)], standalone_mode=False, ) assert result.exit_code == 0, "Local fetch failed." os.chdir(str(package_id.public_id.name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "publish", "--remote"], standalone_mode=False, ) assert ( result.exit_code == 0 ), "Pushing {} with public_id '{}' failed with: {}".format( package_id.package_type, package_id.public_id, str(result.exception) ) print( "Successfully pushed {}: {}".format( package_id.package_type.value, str(package_id.public_id) ) ) except Exception as e: # pylint: disable=broad-except print("\n\nAn exception occured: {}\n\n".format(e)) finally: os.chdir(cwd) result = runner.invoke( cli, [*CLI_LOG_OPTION, "delete", str(package_id.public_id.name)], standalone_mode=False, ) if result.exit_code != 0: print("Unsuccessful delete code: {}".format(str(result.exception))) time.sleep(1.0) def check_and_upload(package_id: PackageId, runner: CliRunner) -> None: """ Check and upload. Checks whether a package is missing from registry. If it is missing, uploads it. :param package_id: the package id :param runner: the cli runner """ result = runner.invoke( cli, [ *CLI_LOG_OPTION, "search", package_id.package_type.value + "s", "--query", package_id.public_id.name, ], standalone_mode=False, ) if not str(package_id.public_id) in result.output: if package_id.package_type == PackageType.AGENT: publish_agent(package_id, runner) else: push_package(package_id, runner) else: print( "The {} '{}' is already in the registry".format( package_id.package_type.value, str(package_id.public_id) ) ) def upload_new_packages(runner: CliRunner) -> None: """ Upload new packages. Checks whether packages are missing from registry in the dependency order. :param runner: the cli runner """ print("\nPushing protocols:") for package_id in ALL_PACKAGE_IDS: if package_id.package_type != PackageType.PROTOCOL: continue check_and_upload(package_id, runner) print("\nPushing connections and contracts:") for package_id in ALL_PACKAGE_IDS: if package_id.package_type not in { PackageType.CONNECTION, PackageType.CONTRACT, }: continue check_and_upload(package_id, runner) print("\nPushing skills:") for package_id in ALL_PACKAGE_IDS: if package_id.package_type != PackageType.SKILL: continue check_and_upload(package_id, runner) print("\nPublishing agents:") for package_id in ALL_PACKAGE_IDS: if package_id.package_type != PackageType.AGENT: continue check_and_upload(package_id, runner) if __name__ == "__main__": runner_ = CliRunner() check_correct_author(runner_) upload_new_packages(runner_) print("Done!") sys.exit(0) ================================================ FILE: scripts/freeze_dependencies.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This CLI tool freezes the dependencies.""" import argparse import re import subprocess # nosec def parse_args() -> argparse.Namespace: """Parse CLI arguments.""" parser = argparse.ArgumentParser("freeze_dependencies") parser.add_argument("-o", "--output", type=argparse.FileType("w"), default=None) return parser.parse_args() if __name__ == "__main__": arguments = parse_args() with subprocess.Popen( # nosec ["pip", "freeze"], stdout=subprocess.PIPE ) as pip_freeze_call: (stdout, stderr) = pip_freeze_call.communicate() requirements = stdout.decode("utf-8") # remove 'aea' itself regex = re.compile("^aea(==.*| .*)?$", re.MULTILINE) requirements = re.sub(regex, "", requirements) if arguments.output is None: print(requirements) else: arguments.output.write(requirements) ================================================ FILE: scripts/generate_all_protocols.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Generate all the protocols from their specifications. This script takes all the protocol specification (scraped from the protocol README) and calls the `aea generate protocol` command. Currently, it does a lot of assumptions, and might not be useful for all use cases. However, with not much work, can be customized to achieve the desired outcomes. It requires the `aea` package, `black` and `isort` tools. """ import argparse import datetime import logging import os import pprint import re import shutil import subprocess # nosec import sys import tempfile from itertools import chain from operator import methodcaller from pathlib import Path from typing import Any, Iterator, List, Optional, Tuple, cast import click import semver from aea.cli.registry.utils import download_file, extract, request_api from aea.common import JSONLike from aea.configurations.base import ComponentType, ProtocolConfig from aea.configurations.constants import DEFAULT_PROTOCOL_CONFIG_FILE from aea.configurations.data_types import PackageId, PublicId from aea.configurations.loader import ConfigLoaders, load_component_configuration from aea.protocols.generator.base import copy_right_str from scripts.common import ( check_working_tree_is_dirty, enforce, get_protocol_specification_from_readme, setup_logger, ) LIBPROTOC_VERSION = "libprotoc 3.19.4" CUSTOM_TYPE_MODULE_NAME = "custom_types.py" README_FILENAME = "README.md" PACKAGES_DIR = Path("packages") TEST_DATA = Path("tests", "data").absolute() PROTOCOLS_PLURALS = "protocols" ROOT_DIR = Path(".").absolute() PROTOCOL_GENERATOR_DOCSTRING_REGEX = "It was created with protocol buffer compiler version `libprotoc .*` and aea version `.*`." def subdirs(path: Path) -> Iterator[Path]: """Get subdirectories of a path.""" return filter(methodcaller("is_dir"), path.iterdir()) def find_protocols_in_local_registry() -> Iterator[Path]: """Find all protocols in local registry.""" authors = subdirs(PACKAGES_DIR) component_parents = chain(*map(subdirs, authors)) protocols_parent = filter(lambda p: p.name == PROTOCOLS_PLURALS, component_parents) protocols = chain(*map(subdirs, protocols_parent)) return map(methodcaller("absolute"), protocols) def log(message: str, level: int = logging.INFO) -> None: """Produce a logging message.""" logger.log(level, message) logger = setup_logger("generate_all_protocols") def run_cli(*args: Any, **kwargs: Any) -> None: """Run a CLI command.""" log(f"Calling command {args} with kwargs {kwargs}") return_code = subprocess.check_call(args, **kwargs) # nosec enforce( return_code == 0, f"Return code of {pprint.pformat(args)} is {return_code} != 0.", ) def run_aea(*args: Any, **kwargs: Any) -> None: """ Run an AEA command. :param args: the AEA command :param kwargs: keyword arguments to subprocess function """ run_cli(sys.executable, "-m", "aea.cli", *args, **kwargs) class AEAProject: """A context manager class to create and delete an AEA project.""" old_cwd: str temp_dir: str def __init__(self, name: str = "my_aea", parent_dir: Optional[str] = None): """ Initialize an AEA project. :param name: the name of the AEA project. :param parent_dir: the parent directory. """ self.name = name self.parent_dir = parent_dir def __enter__(self) -> None: """Create and enter into the project.""" self.old_cwd = os.getcwd() self.temp_dir = tempfile.mkdtemp(dir=self.parent_dir) os.chdir(self.temp_dir) run_aea("create", "--local", "--empty", self.name, "--author", "fetchai") os.chdir(self.name) def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore """Exit the context manager.""" os.chdir(self.old_cwd) shutil.rmtree(self.temp_dir) def _save_specification_in_temporary_file( name: str, specification_content: str ) -> None: """ Save the specification in a temporary file. :param name: the name of the package. :param specification_content: the specification content. """ # here, the cwd is the temporary AEA project # hence, we are writing in a temporary directory spec_path = Path("..", name + ".yaml") log(f"Save specification '{name}' in temporary file {spec_path}") spec_path.write_text(specification_content) # pylint: disable=unspecified-encoding def _generate_protocol(package_path: Path) -> None: """ Generate the protocol. :param package_path: package to the path. """ cmd = ["generate", "protocol", os.path.join("..", package_path.name) + ".yaml"] log(f"Generate the protocol. Command: {pprint.pformat(cmd)}") run_aea(*cmd) def run_isort_and_black(directory: Path, **kwargs: Any) -> None: """Run black and isort against a directory.""" run_cli( sys.executable, "-m", "black", "--verbose", str(directory.absolute()), **kwargs, ) run_cli( sys.executable, "-m", "isort", "--settings-path", "pyproject.toml", "--verbose", str(directory.absolute()), **kwargs, ) def replace_in_directory(name: str, replacement_pairs: List[Tuple[str, str]]) -> None: """ Replace text in directory. :param name: the protocol name. :param replacement_pairs: a list of pairs of strings (to_replace, replacement). """ log(f"Replace prefix of import statements in directory '{name}'") package_dir = Path(PROTOCOLS_PLURALS, name) for submodule in package_dir.rglob("*.py"): log(f"Process submodule {submodule.relative_to(package_dir)}") for to_replace, replacement in replacement_pairs: if to_replace not in submodule.read_text(): continue submodule.write_text(submodule.read_text().replace(to_replace, replacement)) def _fix_generated_protocol(package_path: Path) -> None: """ Fix the generated protocol. That means: - replacing the prefix of import statements for default protocols; - restore the original custom types, if any. - copy the README, if any. :param package_path: path to the protocol package. Used also to recover the protocol name. """ log(f"Restore original custom types in {package_path}") custom_types_module = package_path / CUSTOM_TYPE_MODULE_NAME if custom_types_module.exists(): file_to_replace = Path( PROTOCOLS_PLURALS, package_path.name, CUSTOM_TYPE_MODULE_NAME ) file_to_replace.write_text( # pylint: disable=unspecified-encoding custom_types_module.read_text() # pylint: disable=unspecified-encoding ) package_readme_file = package_path / README_FILENAME if package_readme_file.exists(): log(f"Copy the README {package_readme_file} into the new generated protocol.") shutil.copyfile( package_readme_file, Path(PROTOCOLS_PLURALS, package_path.name, README_FILENAME), ) COPYRIGHT_STR = f"""# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-{datetime.datetime.now().year} Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ def _set_copyright_header(package_path: Path) -> None: """Set copyright header for every python file in the package path.""" for filename in package_path.absolute().glob("**/*.py"): if str(filename).endswith("_pb2.py"): continue filepath: Path = package_path.absolute() / filename content = filepath.read_text() if re.search(r"\#\s+Copyright", content): # header already here continue new_content = content.replace(copy_right_str, COPYRIGHT_STR) assert "Copyright 2018-" in new_content filepath.write_text(new_content) print(filepath, "copyright set") def _update_original_protocol(package_path: Path) -> None: """ Update the original protocol. :param package_path: the path to the original package. Used to recover the protocol name. """ log(f"Copy the new protocol into the original directory {package_path}") shutil.rmtree(package_path) shutil.copytree(Path(PROTOCOLS_PLURALS, package_path.name), package_path) def _fingerprint_protocol(name: str) -> None: """Fingerprint the generated (and modified) protocol.""" log(f"Fingerprint the generated (and modified) protocol '{name}'") protocol_config = load_component_configuration( ComponentType.PROTOCOL, Path(PROTOCOLS_PLURALS, name), skip_consistency_check=True, ) run_aea("fingerprint", "protocol", str(protocol_config.public_id)) def _parse_generator_docstring(package_path: Path) -> str: """ Parse protocol generator docstring. The docstring this function searches is in the __init__.py module and it is of the form: It was created with protocol buffer compiler version `libprotoc ...` and aea version `...`. :param package_path: path to the protocol package :return: the docstring """ content = (package_path / "__init__.py").read_text() regex = re.compile(PROTOCOL_GENERATOR_DOCSTRING_REGEX) match = regex.search(content) if match is None: raise ValueError("protocol generator docstring not found") return match.group(0) def _replace_generator_docstring(package_path: Path, replacement: str) -> None: """ Replace the generator docstring in the __init__.py module. (see _parse_generator_docstring for more details). :param package_path: path to the :param replacement: the replacement to use. """ protocol_name = package_path.name init_module = Path(PROTOCOLS_PLURALS) / protocol_name / "__init__.py" content = init_module.read_text() content = re.sub(PROTOCOL_GENERATOR_DOCSTRING_REGEX, replacement, content) init_module.write_text(content) def _process_packages_protocol( package_path: Path, preserve_generator_docstring: bool = False ) -> None: """ Process protocol package from local registry. If the flag '--no-bump' is specified, it means the protocol generator string that records the AEA and the protoc version used, i.e.: It was created with protocol buffer compiler version `libprotoc ...` and aea version `...`. must not be updated, as the 'AEA' version could have been changed. It means: - extract protocol specification from README - generate the protocol in the current AEA project - fix the generated protocol (e.g. import prefixed, custom types, ...) - update the original protocol with the newly generated one. It assumes the working directory is an AEA project. :param package_path: path to the package. :param preserve_generator_docstring: if True, the protocol generator docstring is preserved (see above). """ if preserve_generator_docstring: # save the old protocol generator docstring old_protocol_generator_docstring = _parse_generator_docstring(package_path) specification_content = get_protocol_specification_from_readme(package_path) _save_specification_in_temporary_file(package_path.name, specification_content) _generate_protocol(package_path) _fix_generated_protocol(package_path) if preserve_generator_docstring: _replace_generator_docstring(package_path, old_protocol_generator_docstring) _set_copyright_header(Path(PROTOCOLS_PLURALS, package_path.name)) run_isort_and_black(Path(PROTOCOLS_PLURALS, package_path.name), cwd=str(ROOT_DIR)) _fingerprint_protocol(package_path.name) _update_original_protocol(package_path) def _check_preliminaries() -> None: """Check that the required packages are installed.""" try: import aea # noqa: F401 # pylint: disable=import-outside-toplevel,unused-import except ModuleNotFoundError: enforce(False, "'aea' package not installed.") enforce(shutil.which("black") is not None, "black command line tool not found.") enforce(shutil.which("isort") is not None, "isort command line tool not found.") enforce(shutil.which("protoc") is not None, "protoc command line tool not found.") result = subprocess.run( # nosec ["protoc", "--version"], stdout=subprocess.PIPE, check=True ) result_str = result.stdout.decode("utf-8") enforce( LIBPROTOC_VERSION in result_str, f"Invalid version for protoc. Found: {result_str}. Required: {LIBPROTOC_VERSION}.", ) def _process_test_protocol(specification: Path, package_path: Path) -> None: """ Process a test protocol. :param specification: path to specification. :param package_path: the output directory. """ specification_content = specification.read_text() _save_specification_in_temporary_file(package_path.name, specification_content) _generate_protocol(package_path) _fix_generated_protocol(package_path) replacements = [ ( f"from packages.fetchai.protocols.{package_path.name}", f"from tests.data.generator.{package_path.name}", ) ] replace_in_directory(package_path.name, replacements) _set_copyright_header(Path(PROTOCOLS_PLURALS, package_path.name)) run_isort_and_black(Path(PROTOCOLS_PLURALS, package_path.name), cwd=str(ROOT_DIR)) _fingerprint_protocol(package_path.name) _update_original_protocol(package_path) def download_package(package_id: PackageId, destination_path: str) -> None: """Download a package into a directory.""" api_path = f"/{package_id.package_type.to_plural()}/{package_id.author}/{package_id.name}/{package_id.public_id.LATEST_VERSION}" resp = cast(JSONLike, request_api("GET", api_path)) file_url = cast(str, resp["file"]) filepath = download_file(file_url, destination_path) extract(filepath, destination_path) def _bump_protocol_specification_id( package_path: Path, configuration: ProtocolConfig ) -> None: """Bump spec id version.""" spec_id: PublicId = configuration.protocol_specification_id # type: ignore old_version = semver.VersionInfo.parse(spec_id.version) new_version = str(old_version.bump_minor()) new_spec_id = PublicId(spec_id.author, spec_id.name, new_version) configuration.protocol_specification_id = new_spec_id with (package_path / DEFAULT_PROTOCOL_CONFIG_FILE).open("w") as file_output: ConfigLoaders.from_package_type(configuration.package_type).dump( configuration, file_output ) def _bump_protocol_specification_id_if_needed(package_path: Path) -> None: """ Check if protocol specification id needs to be bumped. Workflow: - extract protocol specification file from README - download latest protocol id and extract its protocol specification as above - if different, bump protocol specification version, else don't. :param package_path: path to the protocol package. """ # extract protocol specification file from README current_specification_content = get_protocol_specification_from_readme(package_path) # download latest protocol id and extract its protocol specification as above configuration: ProtocolConfig = cast( ProtocolConfig, load_component_configuration(ComponentType.PROTOCOL, package_path), ) temp_directory = Path(tempfile.mkdtemp()) try: download_package(configuration.package_id, str(temp_directory)) except click.ClickException: log("Protocol specification id not bumped - new protocol.") return downloaded_package_directory = temp_directory / configuration.name old_specification_content = get_protocol_specification_from_readme( downloaded_package_directory ) old_configuration: ProtocolConfig = cast( ProtocolConfig, load_component_configuration( ComponentType.PROTOCOL, downloaded_package_directory, skip_consistency_check=True, ), ) # if different, bump protocol specification version, else don't. public_id_version_is_newer = ( old_configuration.public_id.package_version # type: ignore <= configuration.public_id.package_version ) content_is_different = current_specification_content != old_specification_content if public_id_version_is_newer and content_is_different: log( f"Bumping protocol specification id from '{old_configuration.protocol_specification_id}' to '{configuration.protocol_specification_id}'" ) _bump_protocol_specification_id(package_path, configuration) return log( "Protocol specification id not bumped - content is not different, or version is not newer." ) def main(no_bump: bool = False) -> None: """ Run the script. :param no_bump: if True, the (default: False) """ _check_preliminaries() all_protocols = list(find_protocols_in_local_registry()) with AEAProject(): log("=" * 100) _process_test_protocol( TEST_DATA / "sample_specification.yaml", TEST_DATA / "generator" / "t_protocol", ) log("=" * 100) _process_test_protocol( TEST_DATA / "sample_specification_no_custom_types.yaml", TEST_DATA / "generator" / "t_protocol_no_ct", ) for package_path in all_protocols: log("=" * 100) log(f"Processing protocol at path {package_path}") if not no_bump: _bump_protocol_specification_id_if_needed(package_path) # no_bump implies to ignore the docstring: # 'It was created with protocol buffer compiler ... and aea version ...' _process_packages_protocol(package_path, no_bump) if __name__ == "__main__": parser = argparse.ArgumentParser("generate_all_protocols") parser.add_argument( "--check-clean", action="store_true", help="Check if the working tree is clean." ) parser.add_argument("--no-bump", action="store_true", help="Prevent version bump.") arguments = parser.parse_args() main(arguments.no_bump) if arguments.check_clean: check_working_tree_is_dirty() ================================================ FILE: scripts/generate_api_docs.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This tool generates the API docs.""" import argparse import re import shutil import subprocess # nosec import sys from pathlib import Path from aea.configurations.base import ComponentType, PublicId from aea.configurations.constants import ( DEFAULT_PROTOCOL, PACKAGES, SIGNING_PROTOCOL, STATE_UPDATE_PROTOCOL, _FETCHAI_IDENTIFIER, ) from scripts.common import check_working_tree_is_dirty DOCS_DIR = Path("docs/") API_DIR = DOCS_DIR / "api/" AEA_DIR = Path("aea") PACKAGES_DIR = Path(PACKAGES) PLUGIN_DIR = Path("plugins") FETCHAI_PACKAGES = PACKAGES_DIR / _FETCHAI_IDENTIFIER DEFAULT_PACKAGES = { (ComponentType.PROTOCOL, DEFAULT_PROTOCOL), (ComponentType.PROTOCOL, SIGNING_PROTOCOL), (ComponentType.PROTOCOL, STATE_UPDATE_PROTOCOL), } IGNORE_NAMES = {r"^__init__\.py$", r"^__version__\.py$", r"^py\.typed$", r"^.*_pb2.py$"} IGNORE_PREFIXES = { Path("aea", "cli"), Path("aea", "connections", "scaffold"), Path("aea", "contracts", "scaffold"), Path("aea", "protocols", "scaffold"), Path("aea", "skills", "scaffold"), Path("aea", "decision_maker", "scaffold.py"), Path("aea", "error_handler", "scaffold.py"), Path("aea", "test_tools", "click_testing.py"), } def create_subdir(path: str) -> None: """ Create a subdirectory. :param path: the directory path """ directory = "/".join(path.split("/")[:-1]) Path(directory).mkdir(parents=True, exist_ok=True) def replace_underscores(text: str) -> str: """ Replace escaped underscores in a text. :param text: the text to replace underscores in :return: the processed text """ text_a = text.replace("\\_\\_", "`__`") text_b = text_a.replace("\\_", "`_`") return text_b def is_relative_to(p1: Path, p2: Path) -> bool: """Check if a path is relative to another path.""" return str(p1).startswith(str(p2)) def is_not_dir(p: Path) -> bool: """Call p.is_dir() method and negate the result.""" return not p.is_dir() def should_skip(module_path: Path) -> bool: """Return true if the file should be skipped.""" if any(re.search(pattern, module_path.name) for pattern in IGNORE_NAMES): print("Skipping, it's in ignore patterns") return True if module_path.suffix != ".py": print("Skipping, it's not a Python module.") return True if any(is_relative_to(module_path, prefix) for prefix in IGNORE_PREFIXES): print(f"Ignoring prefix {module_path}") return True return False def _generate_apidocs_aea_modules() -> None: """Generate API docs for aea.* modules.""" for module_path in filter(is_not_dir, Path(AEA_DIR).rglob("*")): print(f"Processing {module_path}... ", end="") if should_skip(module_path): continue parents = module_path.parts[:-1] parents_without_root = module_path.parts[1:-1] last = module_path.stem doc_file = API_DIR / Path(*parents_without_root) / f"{last}.md" dotted_path = ".".join(parents) + "." + last make_pydoc(dotted_path, doc_file) def _generate_apidocs_default_packages() -> None: """Generate API docs for Fetch.AI default packages.""" for component_type, default_package in DEFAULT_PACKAGES: public_id = PublicId.from_str(default_package) author = public_id.author name = public_id.name type_plural = component_type.to_plural() package_dir = PACKAGES_DIR / author / type_plural / name for module_path in package_dir.rglob("*.py"): print(f"Processing {module_path}...", end="") if should_skip(module_path): continue suffix = Path(str(module_path.relative_to(package_dir))[:-3] + ".md") dotted_path = ".".join(module_path.parts)[:-3] doc_file = API_DIR / type_plural / name / suffix make_pydoc(dotted_path, doc_file) def _generate_apidocs_plugins() -> None: """Generate API docs for cyrpto plugins.""" for plugin in PLUGIN_DIR.iterdir(): plugin_name = plugin.name plugin_module_name = plugin_name.replace("-", "_") python_package_root = plugin / plugin_module_name for module_path in python_package_root.rglob("*.py"): print(f"Processing {module_path}...", end="") if should_skip(module_path): continue # remove ".py" relative_module_path = module_path.relative_to(python_package_root) suffix = Path(str(relative_module_path)[:-3] + ".md") dotted_path = ".".join(module_path.parts)[:-3] doc_file = API_DIR / "plugins" / plugin_module_name / suffix make_pydoc(dotted_path, doc_file) def make_pydoc(dotted_path: str, dest_file: Path) -> None: """Make a PyDoc file.""" print( f"Running with dotted path={dotted_path} and dest_file={dest_file}... ", end="" ) try: api_doc_content = run_pydoc_markdown(dotted_path) dest_file.parent.mkdir(parents=True, exist_ok=True) dest_file.write_text(api_doc_content) except Exception as e: # pylint: disable=broad-except print(f"Error: {str(e)}") return print("Done!") def run_pydoc_markdown(module: str) -> str: """ Run pydoc-markdown. :param module: the dotted path. :return: the PyDoc content (pre-processed). """ with subprocess.Popen( # nosec ["pydoc-markdown", "-m", module, "-I", "."], stdout=subprocess.PIPE ) as pydoc: stdout, _ = pydoc.communicate() pydoc.wait() stdout_text = stdout.decode("utf-8") text = replace_underscores(stdout_text) return text def generate_api_docs() -> None: """Generate the api docs.""" shutil.rmtree(API_DIR, ignore_errors=True) API_DIR.mkdir() _generate_apidocs_default_packages() _generate_apidocs_aea_modules() _generate_apidocs_plugins() def install(package: str) -> int: """ Install a PyPI package by calling pip. :param package: the package name and version specifier. :return: the return code. """ return subprocess.check_call( # nosec [sys.executable, "-m", "pip", "install", package] ) if __name__ == "__main__": parser = argparse.ArgumentParser("generate_api_docs") parser.add_argument( "--check-clean", action="store_true", help="Check if the working tree is clean." ) arguments = parser.parse_args() res = shutil.which("pydoc-markdown") if res is None: install("pydoc-markdown==3.3.0") sys.exit(1) generate_api_docs() if arguments.check_clean: check_working_tree_is_dirty() ================================================ FILE: scripts/generate_ipfs_hashes.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This script generates the IPFS hashes for all packages. This script requires that you have IPFS installed: - https://docs.ipfs.io/guides/guides/install/ """ import argparse import collections import csv import operator import os import pprint import re import shutil import signal import socket import subprocess # nosec import sys import time import traceback from contextlib import suppress from pathlib import Path from subprocess import TimeoutExpired # nosec from typing import Collection, Dict, List, Optional, Tuple, Type, cast import ipfshttpclient from aea.configurations.base import ( AgentConfig, ConnectionConfig, ContractConfig, PackageConfiguration, PackageType, ProtocolConfig, SkillConfig, _compute_fingerprint, ) from aea.configurations.loader import ConfigLoaders from aea.helpers.yaml_utils import yaml_dump, yaml_dump_all AUTHOR = "fetchai" CORE_PATH = Path("aea") TEST_PATH = Path("tests") / "data" PACKAGE_HASHES_PATH = "packages/hashes.csv" TEST_PACKAGE_HASHES_PATH = "tests/data/hashes.csv" type_to_class_config = { PackageType.AGENT: AgentConfig, PackageType.PROTOCOL: ProtocolConfig, PackageType.CONNECTION: ConnectionConfig, PackageType.SKILL: SkillConfig, PackageType.CONTRACT: ContractConfig, } # type: Dict[PackageType, Type[PackageConfiguration]] def _get_all_packages() -> List[Tuple[PackageType, Path]]: """ Get all the hashable package of the repository. In particular, get them from: - aea/* - packages/* - tests/data/* :return: pairs of (package-type, path-to-the-package) """ def package_type_and_path(package_path: Path) -> Tuple[PackageType, Path]: """Extract the package type from the path.""" item_type_plural = package_path.parent.name item_type_singular = item_type_plural[:-1] return PackageType(item_type_singular), package_path CORE_PACKAGES = list( map( package_type_and_path, [ CORE_PATH / "protocols" / "scaffold", CORE_PATH / "connections" / "scaffold", CORE_PATH / "contracts" / "scaffold", CORE_PATH / "skills" / "scaffold", ], ) ) PACKAGES = list( map( package_type_and_path, filter(operator.methodcaller("is_dir"), Path("packages").glob("*/*/*/")), ) ) TEST_PACKAGES = [ (PackageType.AGENT, TEST_PATH / "dummy_aea"), (PackageType.CONNECTION, TEST_PATH / "dummy_connection"), (PackageType.CONTRACT, TEST_PATH / "dummy_contract"), (PackageType.PROTOCOL, TEST_PATH / "generator" / "t_protocol"), (PackageType.PROTOCOL, TEST_PATH / "generator" / "t_protocol_no_ct"), (PackageType.SKILL, TEST_PATH / "dependencies_skill"), (PackageType.SKILL, TEST_PATH / "exception_skill"), (PackageType.SKILL, TEST_PATH / "dummy_skill"), ] ALL_PACKAGES = CORE_PACKAGES + PACKAGES + TEST_PACKAGES return ALL_PACKAGES def sort_configuration_file(config: PackageConfiguration) -> None: """Sort the order of the fields in the configuration files.""" # load config file to get ignore patterns, dump again immediately to impose ordering assert config.directory is not None configuration_filepath = config.directory / config.default_configuration_filename if config.package_type == PackageType.AGENT: json_data = config.ordered_json component_configurations = json_data.pop("component_configurations") yaml_dump_all( [json_data] + component_configurations, configuration_filepath.open("w", encoding="utf-8"), ) else: yaml_dump( config.ordered_json, configuration_filepath.open("w", encoding="utf-8") ) def ipfs_hashing( client: ipfshttpclient.Client, configuration: PackageConfiguration, package_type: PackageType, ) -> Tuple[str, str, List[Dict]]: """ Hashes a package and its components. :param client: a connected IPFS client. :param configuration: the package configuration. :param package_type: the package type. :return: the identifier of the hash (e.g. 'fetchai/protocols/default') | and the hash of the whole package. """ # hash again to get outer hash (this time all files) # we still need to ignore some files # use ignore patterns somehow # ignore_patterns = configuration.fingerprint_ignore_patterns # noqa: E800 assert configuration.directory is not None result_list = client.add( configuration.directory, recursive=True, period_special=False, follow_symlinks=False, ) key = os.path.join( configuration.author, package_type.to_plural(), configuration.directory.name, ) # check that the last result of the list is for the whole package directory assert result_list[-1]["Name"] == configuration.directory.name directory_hash = result_list[-1]["Hash"] return key, directory_hash, result_list def to_csv(package_hashes: Dict[str, str], path: str) -> None: """Outputs a dictionary to CSV.""" try: ordered = collections.OrderedDict(sorted(package_hashes.items())) with open(path, "w", encoding="utf-8") as csv_file: writer = csv.writer(csv_file) writer.writerows(ordered.items()) except IOError: print("I/O error") def from_csv(path: str) -> Dict[str, str]: """Load a CSV into a dictionary.""" result = collections.OrderedDict({}) # type: Dict[str, str] with open(path, "r", encoding="utf-8") as csv_file: reader = csv.reader(csv_file) for row in reader: assert len(row) == 2 key, value = row result[key] = value return result def is_port_open(host: str, port: int) -> bool: """Check is port open or not.""" try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = sock.connect_ex((host, port)) finally: sock.close() return result == 0 class IPFSDaemon: """ Set up the IPFS daemon. :raises Exception: if IPFS is not installed. """ def __init__(self, timeout: float = 15.0, port: int = 5001): """Initialise IPFS daemon.""" # check we have ipfs self.timeout = timeout self.port = port res = shutil.which("ipfs") if res is None: raise Exception("Please install IPFS first!") with subprocess.Popen( # nosec ["ipfs", "--version"], stdout=subprocess.PIPE, env=os.environ.copy(), ) as process: output, _ = process.communicate() if b"0.6.0" not in output: raise Exception( "Please ensure you have version 0.6.0 of IPFS daemon installed." ) self.process = None # type: Optional[subprocess.Popen] def __enter__(self) -> None: """Run the ipfs daemon.""" self.process = subprocess.Popen( # nosec # pylint: disable=consider-using-with ["ipfs", "daemon"], stdout=subprocess.PIPE, env=os.environ.copy(), ) print("Waiting for {} seconds the IPFS daemon to be up.".format(self.timeout)) t = time.time() while time.time() - t < self.timeout: if is_port_open(host="localhost", port=self.port): return time.sleep(1) raise ValueError("failed to connect") def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore """Terminate the ipfs daemon.""" if self.process is None: return self.process.send_signal(signal.SIGTERM) with suppress(TimeoutExpired): self.process.wait(timeout=30) poll = self.process.poll() if poll is None: self.process.terminate() self.process.wait(2) def load_configuration( package_type: PackageType, package_path: Path ) -> PackageConfiguration: """ Load a configuration, knowing the type and the path to the package root. :param package_type: the package type. :param package_path: the path to the package root. :return: the configuration object. """ configuration_class = type_to_class_config[package_type] configuration_filepath = ( package_path / configuration_class.default_configuration_filename ) loader = ConfigLoaders.from_package_type(package_type) with configuration_filepath.open() as fp: configuration_obj = loader.load(fp) configuration_obj._directory = package_path # pylint: disable=protected-access return cast(PackageConfiguration, configuration_obj) def assert_hash_consistency( fingerprint: Dict[str, str], path_prefix: Path, client: ipfshttpclient.Client ) -> None: """ Check that our implementation of IPFS hashing for a package is correct against the true IPFS. :param fingerprint: the fingerprint dictionary. :param path_prefix: the path prefix to prepend. :param client: the client. """ # confirm ipfs only generates same hash: for file_name, ipfs_hash in fingerprint.items(): path = path_prefix / file_name expected_ipfs_hash = client.add(path)["Hash"] assert ( expected_ipfs_hash == ipfs_hash ), "WARNING, hashes don't match for: {}".format(path) def _replace_fingerprint_non_invasive( fingerprint_dict: Dict[str, str], text: str ) -> str: """ Replace the fingerprint in a configuration file (not invasive). We need this function because libraries like `yaml` may modify the content of the .yaml file when loading/dumping. Instead, working with the content of the file gives us finer granularity. :param fingerprint_dict: the fingerprint dictionary. :param text: the content of a configuration file. :return: the updated content of the configuration file. """ def to_row(x: Tuple[str, str]) -> str: return x[0] + ": " + x[1] replacement = "\nfingerprint:\n {}\n".format( "\n ".join(map(to_row, sorted(fingerprint_dict.items()))) ) return re.sub(r"\nfingerprint:\W*\n(?:\W+.*\n)*", replacement, text) def compute_fingerprint( # pylint: disable=unsubscriptable-object package_path: Path, fingerprint_ignore_patterns: Optional[Collection[str]], client: ipfshttpclient.Client, ) -> Dict[str, str]: """ Compute the fingerprint of a package. :param package_path: path to the package. :param fingerprint_ignore_patterns: filename patterns whose matches will be ignored. :param client: the IPFS Client. It is used to compare our implementation with the true implementation of IPFS hashing. :return: the fingerprint """ fingerprint = _compute_fingerprint( package_path, ignore_patterns=fingerprint_ignore_patterns, ) assert_hash_consistency(fingerprint, package_path, client) return fingerprint def update_fingerprint( configuration: PackageConfiguration, client: ipfshttpclient.Client ) -> None: """ Update the fingerprint of a package. :param configuration: the configuration object. :param client: the IPFS Client. It is used to compare our implementation with the true implementation of IPFS hashing. :return: None """ # we don't process agent configurations if isinstance(configuration, AgentConfig): return assert configuration.directory is not None fingerprint = compute_fingerprint( configuration.directory, configuration.fingerprint_ignore_patterns, client ) config_filepath = ( configuration.directory / configuration.default_configuration_filename ) old_content = config_filepath.read_text() new_content = _replace_fingerprint_non_invasive(fingerprint, old_content) config_filepath.write_text(new_content) def check_fingerprint( configuration: PackageConfiguration, client: ipfshttpclient.Client ) -> bool: """ Check the fingerprint of a package, given the loaded configuration file. :param configuration: the configuration object. :param client: the IPFS Client. It is used to compare our implementation with the true implementation of IPFS hashing. :return: True if the fingerprint match, False otherwise. """ # we don't process agent configurations if isinstance(configuration, AgentConfig): return True assert configuration.directory is not None expected_fingerprint = compute_fingerprint( configuration.directory, configuration.fingerprint_ignore_patterns, client ) actual_fingerprint = configuration.fingerprint result = expected_fingerprint == actual_fingerprint if not result: print( "Fingerprints do not match for {} in {}".format( configuration.name, configuration.directory ) ) return result def parse_arguments() -> argparse.Namespace: """Parse arguments.""" script_name = Path(__file__).name parser = argparse.ArgumentParser( script_name, description="Generate/check hashes of packages." ) parser.add_argument( "--check", action="store_true", default=False, help="Only check if the hashes are up-to-date.", ) parser.add_argument( "--timeout", type=float, default=15.0, help="Time to wait before IPFS daemon is up and running.", ) arguments_ = parser.parse_args() return arguments_ def update_hashes(timeout: float = 15.0) -> int: """ Process all AEA packages, update fingerprint, and update hashes.csv files. :param timeout: timeout to the update. :return: exit code. 0 for success, 1 if an exception occurred. """ return_code_ = 0 package_hashes = {} # type: Dict[str, str] test_package_hashes = {} # type: Dict[str, str] # run the ipfs daemon with IPFSDaemon(timeout=timeout): try: # connect ipfs client client = ipfshttpclient.connect( "/ip4/127.0.0.1/tcp/5001/http" ) # type: ipfshttpclient.Client # ipfs hash the packages for package_type, package_path in _get_all_packages(): print( "Processing package {} of type {}".format( package_path.name, package_type ) ) configuration_obj = load_configuration(package_type, package_path) sort_configuration_file(configuration_obj) update_fingerprint(configuration_obj, client) key, package_hash, _ = ipfs_hashing( client, configuration_obj, package_type ) if TEST_PATH in package_path.parents: test_package_hashes[key] = package_hash else: package_hashes[key] = package_hash # output the package hashes to_csv(package_hashes, PACKAGE_HASHES_PATH) to_csv(test_package_hashes, TEST_PACKAGE_HASHES_PATH) print("Done!") except Exception: # pylint: disable=broad-except traceback.print_exc() return_code_ = 1 return return_code_ def check_same_ipfs_hash( client: ipfshttpclient, configuration: PackageConfiguration, package_type: PackageType, all_expected_hashes: Dict[str, str], ) -> bool: """ Compute actual package hash and compare with expected hash. :param client: the IPFS client. :param configuration: the configuration object of the package. :param package_type: the type of package. :param all_expected_hashes: the dictionary of all the expected hashes. :return: True if the IPFS hash match, False otherwise. """ # if configuration.name in [ # "erc1155", # "carpark_detection", # "p2p_libp2p", # "Agent0", # "dummy", # ]: # noqa: E800 # return True # packages with nested dirs or symlinks, kept for reference # noqa: E800 key, actual_hash, result_list = ipfs_hashing(client, configuration, package_type) expected_hash = all_expected_hashes[key] result = actual_hash == expected_hash if not result: print( f"IPFS Hashes do not match for {configuration.name} in {configuration.directory}" ) print(f"Expected: {expected_hash}") print(f"Actual: {actual_hash}") print("All the hashes: ", pprint.pformat(result_list)) return result def check_hashes(timeout: float = 15.0) -> int: """ Check fingerprints and outer hash of all AEA packages. :param timeout: timeout to the check. :return: exit code. 1 if some fingerprint/hash don't match or if an exception occurs, 0 in case of success. """ return_code_ = 0 failed = False expected_package_hashes = from_csv(PACKAGE_HASHES_PATH) # type: Dict[str, str] expected_test_package_hashes = from_csv( TEST_PACKAGE_HASHES_PATH ) # type: Dict[str, str] all_expected_hashes = {**expected_package_hashes, **expected_test_package_hashes} with IPFSDaemon(timeout=timeout): try: # connect ipfs client client = ipfshttpclient.connect( "/ip4/127.0.0.1/tcp/5001/http" ) # type: ipfshttpclient.Client for package_type, package_path in _get_all_packages(): configuration_obj = load_configuration(package_type, package_path) failed = failed or not check_fingerprint(configuration_obj, client) failed = failed or not check_same_ipfs_hash( client, configuration_obj, package_type, all_expected_hashes ) except Exception: # pylint: disable=broad-except traceback.print_exc() failed = True if failed: return_code_ = 1 else: print("OK!") return return_code_ def clean_directory() -> None: """Clean the directory.""" clean_command = ["make", "clean"] with subprocess.Popen(clean_command, stdout=subprocess.PIPE) as process: # nosec _, _ = process.communicate() if __name__ == "__main__": arguments = parse_arguments() if arguments.check: return_code = check_hashes(arguments.timeout) else: clean_directory() return_code = update_hashes(arguments.timeout) sys.exit(return_code) ================================================ FILE: scripts/install.ps1 ================================================ # usage # from cmd: @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/fetchai/agents-aea/main/scripts/install.ps1'))" # from powershell: iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/fetchai/agents-aea/main/scripts/install.ps1')) function install_python { echo "Installing python" Invoke-WebRequest https://www.python.org/ftp/python/3.8.6/python-3.8.6-amd64-webinstall.exe -OutFile python-3.8.6-amd64-webinstall.exe ./python-3.8.6-amd64-webinstall.exe /install /passive PrependPath=1 Include_test=0 Include_tcltk=0| Out-Null rm ./python-3.8.6-amd64-webinstall.exe } function install_build_tools { $output=pip install wheel --force --no-cache-dir 2>&1 |out-string; $output=pip wheel cytoolz --no-cache-dir 2>&1 |out-string; if ($LastExitCode -ne 0) { echo "Installing visual studio build tools" Invoke-WebRequest https://download.microsoft.com/download/5/f/7/5f7acaeb-8363-451f-9425-68a90f98b238/visualcppbuildtools_full.exe -OutFile visualcppbuildtools_full.exe ./visualcppbuildtools_full.exe /NoRestart /Passive | Out-Null rm ./visualcppbuildtools_full.exe } else{ echo "Visual studio build tools are already installed" } } function instal_choco_golang_gcc { echo "Choco, golang and gcc will be installed" echo "You'll be asked for admin shell" sleep 5 Start-Process powershell -Verb runAs -ArgumentList "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')); choco install -y golang mingw" } function install_aea { echo "Install aea" $output=pip install aea[all]==1.1.1 --force --no-cache-dir 2>&1 |out-string; if ($LastExitCode -ne 0) { echo $output echo "AEA install failed!" exit 1 } aea --help 2>&1 |out-null; if ($LastExitCode -eq 0) { echo "AEA successfully installed" }else{ echo "AEA installed but can not be runned!" } } function refresh-path { $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") } function check_python { try{ if (((python -V)|Out-String) -match "Python 3\.[678]\.") { echo "Python installed and supported!" }else{ install_python } }catch{ install_python } } function main{ refresh-path check_python install_build_tools refresh-path install_aea instal_choco_golang_gcc pause } main ================================================ FILE: scripts/install.sh ================================================ #!/bin/bash ### usage # mac/linux: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/fetchai/agents-aea/main/scripts/install.sh)" function bad_os_type() { echo "OS $OSTYPE is not supported!" exit 1 } function check_linux() { # check any deb distribution! is_ubuntu=`cat /etc/issue|grep -i Ubuntu` if [[ -z $is_ubuntu ]]; then echo "Only ubuntu, macos are supported at the moment with this script. please use install.ps1 for windows 10" exit 1 fi install_on_ubuntu } function is_python3(){ return `which python3` } function is_python_version_ok() { if which python3 2>&1 >/dev/null; then version=`python3 -V 2>/dev/null` if [[ -z `echo $version|grep -E 'Python 3\.([(89]|10)\.[0-9]+'` ]]; then echo "Python3 version: ${version} is not supported. Supported versions are 3.8, 3.9, and 3.10." return 1 fi return 0 else echo "Python is not installed" return 1 fi } function install_aea (){ echo "Install AEA" output=$(pip3 install --user aea[all]==1.2.5 --force --no-cache-dir) if [[ $? -ne 0 ]]; then echo "$output" echo 'Failed to install aea' exit 1 fi touch ~/.profile py_user_base=`python3 -m site --user-base` echo >>~/.bashrc echo 'export PATH=$PATH'":${py_user_base}/bin" >>~/.bashrc echo >>~/.zshrc echo 'export PATH=$PATH'":${py_user_base}/bin" >>~/.zshrc source ~/.bashrc # sometimes ~/.local/bin is not in PATH output=`aea --help 2>&1` if [[ $? -ne 0 ]]; then echo "$output" echo 'Test run of aea failed!' exit 1 fi echo "AEA successfully installed!" echo "It's recommended to open a new shell to work with AEA." } function install_ubuntu_deps(){ # always install it cause python3-dev can be missing! also it's not consuming much time. echo "Install python3 and dependencies" output=$(sudo bash -c "apt update && apt install python3 python3-pip python3-dev -y" 2>&1) if [[ $? -ne 0 ]]; then echo "$output" echo -n '\n\nFailed to install required packages!' exit 1 fi } function check_python_version(){ output=$(is_python_version_ok) if [[ $? -eq 1 ]]; then echo "$output" echo "Can not install supported python version. probably distribution is too old. Exit." exit 1 fi } function install_on_ubuntu(){ install_ubuntu_deps check_python_version install_aea } function ensure_brew(){ output=`which brew` if [[ $? -ne 0 ]]; then echo "Installing homebrew. Please pay attention, it can ask for the password and agree to install xcode tools." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" if [[ $? -eq 0 ]]; then echo "Homebrew was installed!" else echo "Homebrew failed to install!" fi fi } function mac_install_python(){ output=`is_python_version_ok` if [[ $? -eq 0 ]]; then echo "Python supported version already installed!" return 0 fi ensure_brew echo "Install python3.8. It takes long time." output=$(brew install python@3.8 2>&1) if [[ $? -eq 0 ]]; then echo "Python was successfully installed!" return 0 else echo "$output" echo "Python failed to install!" exit 1 fi } function install_on_mac(){ mac_install_python check_python_version install_aea } function main(){ echo "Welcome to AEA installer!" case "$OSTYPE" in darwin*) install_on_mac ;; linux*) check_linux ;; *) bad_os_type ;; esac } main ================================================ FILE: scripts/ledger_network_update.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Setup script to update ledger network.""" import re from pathlib import Path from typing import Optional import click # type: ignore ROOT_DIR = Path(__file__).parent / "../" AEA_LEDGER_MODULE_FILE = ROOT_DIR / "aea/crypto/ledger_apis.py" FETCHAI_LEDGER_FILE = ( ROOT_DIR / "plugins/aea-ledger-fetchai/aea_ledger_fetchai/fetchai.py" ) LEDGER_INTEGRATION_MD = ROOT_DIR / "docs/ledger-integration.md" class NetworkConfig: """Ledger network configuration data class.""" net_name: str chain_id: str denom: str rest_api_address: str rpc_api_address: str faucet_url: Optional[str] explorer_url: Optional[str] def __init__( self, net_name: str, chain_id: str, denom: str, rest_api_address: str, rpc_api_address: str, faucet_url: Optional[str] = None, explorer_url: Optional[str] = None, ): """ Set network config. :param net_name: str :param chain_id: str :param denom: str :param rest_api_address: str :param rpc_api_address: str :param faucet_url: optional str :param explorer_url: optional str """ self.net_name = net_name self.chain_id = chain_id self.denom = denom self.rest_api_address = rest_api_address self.rpc_api_address = rpc_api_address self.faucet_url = faucet_url or self.make_faucet_url(net_name) self.explorer_url = explorer_url or self.make_explorer_url(net_name) @staticmethod def make_faucet_url(net_name) -> str: """Make default faucet url based on net name.""" return f"https://faucet-{net_name}.t-v2-london-c.fetch-ai.com" @staticmethod def make_explorer_url(net_name) -> str: """Make default explorer url based on net name.""" return f"https://explore-{net_name}.fetch.ai" def __str__(self) -> str: """Return lines of network configration to be printed.""" return ( f"Net name: {self.net_name}\n" f"Chain id: {self.chain_id}\n" f"Denom: {self.denom}\n" f"REST API address: {self.rest_api_address}\n" f"RPC address: {self.rpc_api_address}\n" f"Testnet faucet address: {self.faucet_url}\n" f"Block explorer address: {self.explorer_url}\n" ) def _get_value(variable_name: str, text: str) -> str: m = re.search(rf'{variable_name} = "(.*)"', text, re.MULTILINE) if m: return m.groups()[0] raise ValueError("Value not found") class NetworkUpdate: """Ledger network update tool.""" cur_config: NetworkConfig new_config: NetworkConfig cosmpy_version: str @staticmethod def get_current_config() -> NetworkConfig: """ Get current ledger network configuration. :return: NetworkConfig instance """ code_text = FETCHAI_LEDGER_FILE.read_text() rest_api_addr = _get_value("DEFAULT_ADDRESS", code_text) chain_id = _get_value("DEFAULT_CHAIN_ID", code_text) denom = _get_value("DEFAULT_CURRENCY_DENOM", code_text) faucet_url = _get_value("FETCHAI_TESTNET_FAUCET_URL", code_text) m = re.match(r"https://rest-(\w+).fetch.ai:443", rest_api_addr) if not m: raise ValueError( f"can not determine network name from address: {rest_api_addr}" ) net_name = m.groups()[0] rpc_api_addr = f"https://rpc-{net_name}.fetch.ai:443" m = re.search( r'\| Block Explorer \| =0.2.0)", default=cur_version ) def run(self): """Do update.""" self.cur_config = self.get_current_config() click.echo("Current network config:") click.echo(self.cur_config) self.cosmpy_version = self.get_cosmpy_version() click.echo("") self.new_config = self.get_new_config() click.echo("\n\n------------") click.echo("New network config:") click.echo(self.new_config) click.echo(f"cosmpy version is cosmpy{self.cosmpy_version}") click.echo() if not click.confirm("Do you want to continue?"): click.echo("Exit") return click.echo("Do the update") self.update_protobuf() self.update_spelling() self.update_cosmpy_version() self.update_docs() self.update_conftest() self.update_packages() self.update_plugins() self.update_aea_ledger_crypto() self.print_footer() @staticmethod def update_protobuf(): """Update protobuf dependency.""" click.echo("protobuf is not updating at the moment") def update_spelling(self): """Add network name to spelling file.""" click.echo("Add network name to spelling") spelling = ROOT_DIR / ".spelling" spelling.write_text( spelling.read_text() + f"\n{self.new_config.net_name}\n{self.new_config.chain_id}" ) def update_cosmpy_version(self): """Set new cosmpy version.""" click.echo("Update cosmpy version") # pipenv pipenv = ROOT_DIR / "Pipfile" pipenv.write_text( re.sub( 'cosmpy = ".*"', f'cosmpy = "{self.cosmpy_version}"', pipenv.read_text() ) ) # aea ledger fetchai plugin plugin_setup_py = ROOT_DIR / "plugins/aea-ledger-fetchai/setup.py" plugin_setup_py.write_text( re.sub( '"cosmpy.*"', f'"cosmpy{self.cosmpy_version}"', plugin_setup_py.read_text(), ) ) # aea ledger cosmos plugin plugin_setup_py = ROOT_DIR / "plugins/aea-ledger-cosmos/setup.py" plugin_setup_py.write_text( re.sub( '"cosmpy.*"', f'"cosmpy{self.cosmpy_version}"', plugin_setup_py.read_text(), ) ) # tox tox_file = ROOT_DIR / "tox.ini" tox_file.write_text( re.sub("cosmpy.*", f"cosmpy{self.cosmpy_version}", tox_file.read_text()) ) def update_aea_ledger_crypto(self): """Update aea/ledger/crypto.py with new defaults.""" click.echo("Update aea/ledger/crypto.py") content = AEA_LEDGER_MODULE_FILE.read_text() content = content.replace( f'FETCHAI_DEFAULT_ADDRESS = "{self.cur_config.rest_api_address}"', f'FETCHAI_DEFAULT_ADDRESS = "{self.new_config.rest_api_address}"', ) content = content.replace( f'FETCHAI_DEFAULT_CURRENCY_DENOM = "{self.cur_config.denom}"', f'FETCHAI_DEFAULT_CURRENCY_DENOM = "{self.new_config.denom}"', ) content = content.replace( f'FETCHAI_DEFAULT_CHAIN_ID = "{self.cur_config.chain_id}"', f'FETCHAI_DEFAULT_CHAIN_ID = "{self.new_config.chain_id}"', ) AEA_LEDGER_MODULE_FILE.write_text(content) def update_docs(self): """Update documentation.""" click.echo("Update docs") docs_files = (ROOT_DIR / "docs").glob("**/*.md") for f in docs_files: content = f.read_text() content = content.replace( self.cur_config.explorer_url, self.new_config.explorer_url, ) content = content.replace( f"Fetch.ai `{self.cur_config.net_name.capitalize()}`", f"Fetch.ai `{self.new_config.net_name.capitalize()}`", ) content = content.replace( f"Fetchai {self.cur_config.net_name.capitalize()} or a local Ganache ", f"Fetchai {self.new_config.net_name.capitalize()} or a local Ganache ", ) content = content.replace( f"{self.cur_config.net_name.capitalize()} block explorer", f"{self.new_config.net_name.capitalize()} block explorer", ) content = content.replace( f"{self.cur_config.net_name.capitalize()} block explorer", f"{self.new_config.net_name.capitalize()} block explorer", ) content = content.replace( f"{self.cur_config.net_name.capitalize()} testnet", f"{self.new_config.net_name.capitalize()} testnet", ) content = content.replace( f"| Chain ID | {self.cur_config.chain_id}", f"| Chain ID | {self.new_config.chain_id}", ) content = content.replace( f"| RPC Endpoint | {self.cur_config.rpc_api_address}", f"| RPC Endpoint | {self.new_config.rpc_api_address}", ) content = content.replace( f"| REST Endpoint | {self.cur_config.rest_api_address}", f"| REST Endpoint | {self.new_config.rest_api_address}", ) f.write_text(content) def update_conftest(self): """Update tests/conftest.py.""" click.echo("Update tests/conftest.py") f = ROOT_DIR / "tests/conftest.py" content = f.read_text() content = content.replace( f'DEFAULT_FETCH_ADDR_REMOTE = "{self.cur_config.rest_api_address}"', f'DEFAULT_FETCH_ADDR_REMOTE = "{self.new_config.rest_api_address}"', ) content = content.replace( f'DEFAULT_FETCH_CHAIN_ID = "{self.cur_config.chain_id}"', f'DEFAULT_FETCH_CHAIN_ID = "{self.new_config.chain_id}"', ) f.write_text(content) def update_packages(self): """Update packages.""" click.echo("Update packages") configs_files = (ROOT_DIR / "packages").glob("**/*.yaml") for f in configs_files: content = f.read_text() content = content.replace( f"address: {self.cur_config.rest_api_address}", f"address: {self.new_config.rest_api_address}", ) content = content.replace( f"chain_id: {self.cur_config.chain_id}", f"chain_id: {self.new_config.chain_id}", ) f.write_text(content) def update_plugins(self): """Update plugins.""" click.echo("Update plugins") files = (ROOT_DIR / "plugins").glob("**/*.py") for f in files: content = f.read_text() content = content.replace( f'DEFAULT_CHAIN_ID = "{self.cur_config.chain_id}"', f'DEFAULT_CHAIN_ID = "{self.new_config.chain_id}"', ) content = content.replace( f'DEFAULT_ADDRESS = "{self.cur_config.rest_api_address}"', f'DEFAULT_ADDRESS = "{self.new_config.rest_api_address}"', ) content = content.replace( f'FETCHAI_DEFAULT_CHAIN_ID = "{self.cur_config.chain_id}"', f'FETCHAI_DEFAULT_CHAIN_ID = "{self.new_config.chain_id}"', ) content = content.replace( f'FETCHAI_DEFAULT_ADDRESS = "{self.cur_config.rest_api_address}"', f'FETCHAI_DEFAULT_ADDRESS = "{self.new_config.rest_api_address}"', ) content = content.replace( f'FETCHAI_TESTNET_FAUCET_URL = "{self.cur_config.faucet_url}"', f'FETCHAI_TESTNET_FAUCET_URL = "{self.new_config.faucet_url}"', ) content = content.replace( f'"chain_id": "{self.cur_config.chain_id}"', f'"chain_id": "{self.new_config.chain_id}"', ) f.write_text(content) @staticmethod def print_footer(): """Print footer after everything was done.""" click.echo("Update completed!") click.echo("Please check wasm files are correct") if __name__ == "__main__": NetworkUpdate().run() ================================================ FILE: scripts/oef/launch.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ from __future__ import print_function import argparse import json import os import subprocess # nosec import sys def run(cmd): print(" ".join(cmd)) c = subprocess.Popen(cmd) # nosec try: c.wait() except KeyboardInterrupt: pass finally: poll = c.poll() if poll is None: c.terminate() c.wait(2) return c.returncode def error(*x): print("".join([str(xx) for xx in x]), file=sys.stderr) def fail(*x): error(x) exit(1) def pull_image(run_sudo, img): c = [] if run_sudo: c += ["sudo"] c += [ "docker", "pull", img, ] r = run(c) if r != 0: error("can't pull " + img) def parse_command(j): cmd = [] used_keys = ["positional_args"] for key in j["positional_args"]: cmd.append(str(j[key])) used_keys.append(key) for key in j: if key in used_keys: continue cmd.extend(["--" + key, str(j[key])]) return cmd def launch_job(args, j): img = j["image"] if "/" in img: pull_image(args.sudo, img) c = [] if args.sudo: c += ["sudo"] c += ["docker", "run"] if args.background: c += ["-d"] elif not args.disable_stdin: c += ["-it"] else: c += ["-t"] if args.name: c += ["--name"] c += [args.name] work_dir = os.path.abspath(os.path.dirname(__file__)) project_dir = os.path.abspath(os.path.join(work_dir, "..", "..")) print("Work dir: ", work_dir) c += ["-v", work_dir + ":/config", "-v", project_dir + "/data/oef-logs:/logs"] for arg in j["params"]: c += map(lambda x: x.replace("$PWD", project_dir), arg) c += [img] cmd_config = j["cmd"].get(args.cmd, None) if not cmd_config: fail("Selected command {} not configured in config file!".format(args.cmd)) c.extend(parse_command(cmd_config)) extra_args = [a for a in args.rest if a != "--"] print("Extra arguments to search: ", extra_args) c += extra_args r = run(c) if r != 0: fail("can't launch " + img) def main(args): with open(args.config, "r") as f: config = json.load(f) launch_job(args, config) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-c", "--config", required=True, type=str, help="Publish the image to GCR" ) parser.add_argument( "--sudo", required=False, action="store_true", help="Run docker as root" ) parser.add_argument( "--background", required=False, action="store_true", help="Run image in background.", ) parser.add_argument( "--disable_stdin", required=False, action="store_true", help="Disable disable_stdin.", ) parser.add_argument( "-n", "--name", required=False, type=str, help="give thre container a name" ) parser.add_argument( "--cmd", required=False, type=str, default="oef-search", help="The available commands are defined" " in the config file " "('cmd' dictionary) ", ) parser.add_argument("rest", nargs=argparse.REMAINDER) main(parser.parse_args()) ================================================ FILE: scripts/oef/launch_config.json ================================================ { "image": "fetchai/oef-search:0.7", "params": [ [ "--rm", "--cap-add", "sys_ptrace", "-p", "20000:20000", "-p", "10000:10000", "-p", "40000:40000", "-p", "7500:7500" ] ], "cmd": { "oef-search": { "positional_args": ["config_file"], "config_file": "/config/node_config.json" } } } ================================================ FILE: scripts/oef/node_config.json ================================================ { "host": "127.0.0.1", "search_key": "London-search", "search_port": 20000, "search_broadcast_cache_lifetime_sec": 6, "director_port": 40000, "html_dir": "api/src/resources/website", "prometheus_api_path": "/metrics", "prometheus_log_file": "./fetch-logs/core-vars.txt", "search_prometheus_log_file": "./fetch-logs/search-vars.txt", "http_port": 7500, "log_dir": "./fetch-logs/", "search_log": "/logs/search.log", "core_log": "/logs/core.log", "ssl_certificate": "", "core": { "core_binary": "/oef-mt-core/bazel-bin/mt-core/main/src/cpp/app", "config": { "core_key": "CoreKey", "core_uri": "tcp://127.0.0.1:10000", "ws_uri": "", "ssl_uri": "ssl://127.0.0.1:15000", "white_list_file":"", "core_cert_pk_file": "/app/ssl/core.pem", "core_pubkey_file": "/app/ssl/core_pub.pem", "tmp_dh_file": "/app/ssl/dh2048.pem", "search_uri": "tcp://127.0.0.1:20000", "tasks_thread_count": 10, "comms_thread_count": 10, "karma_policy": { }, "prometheus_log_interval": 3, "prometheus_log_file": "/logs/core-vars.txt" } }, "search_peers": ["KEY:HOST:PORT"], "bootstrap": { "bootstrap-url": "", "network-name": "oeftestnet", "client-name": "oef-node", "client-version": "0.1.0", "remote-host": "$EXTERNAL_HOSTNAME", "remote-port": -1, "private-key": "", "user-token": "", "notify-period-in-sec": 180 }, "state": { "file": "/storage/state.json", "fields": ["bootstrap:private-key"] }, "search_config": { "search_prometheus_log_file": "/logs/search-vars.txt", "daps": { "network_search": { "class": "DapERNetwork", "config": { "module": "dap_e_r_network.src.python.DapERNetwork", "structure": { "locations": { } } } }, "geo_search": { "class": "DapGeo", "config": { "module": "dap_2d_geo.src.python.DapGeo", "structure": { "location": { "location.location": { "type": "location", "options": [ "plane", "os-grid" ] }, "latlon": { "latlon.location": { "type": "location", "options": [ ] } } } } } }, "address_registry": { "class": "AddressRegistry", "config": { "module": "dap_in_memory.src.python.AddressRegistry", "structure": { "address_registry_table": { "address_field": "address" } } } }, "in_memory_dap": { "class": "exe.InMemoryDap", "config": { "binary": "cpp_dap_in_memory/src/cpp/cpp_dap_in_memory_server", "host": "127.0.0.1", "port": 30000, "structure": { "value_table": { "field": "string" } } } }, "attrs": { "class": "DapAttributeStore", "config": { "module": "dap_attribute_store.src.python.DapAttributeStore", "structure": { }, "options": [ "lazy" ] } }, "uniquer": { "class": "DapUniquer", "config": { "module": "uniqer.src.python.DapUniquer", "structure": { } } }, "data_model_searcher": { "class": "SearchEngine", "config": { "module": "ai_search_engine.src.python.SearchEngine", "structure": { "data_model_table": { "data_model": "embedding" } } } } }, "attribute_tbfld_map": { "latlon": ["latlon", "latlon"], "NETWORK_ADDRESS": ["address_registry_table", "address_field"] } } } ================================================ FILE: scripts/parse_main_dependencies_from_lock.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This CLI tool takes the main dependencies of the Pipfile.lock and prints it to stdout in requirements.txt format.""" import argparse import json def parse_args() -> argparse.Namespace: """Parse CLI arguments.""" parser = argparse.ArgumentParser("parse_main_dependencies_from_lock") parser.add_argument( "pipfile_lock_path", type=argparse.FileType("r"), help="Path to Pipfile.lock." ) parser.add_argument("-o", "--output", type=argparse.FileType("w"), default=None) return parser.parse_args() if __name__ == "__main__": arguments = parse_args() pipfile_lock_content = json.load(arguments.pipfile_lock_path) requirements = sorted( map( lambda x: x[0] + x[1]["version"], pipfile_lock_content.get("default").items(), ) ) requirements_content = "\n".join(requirements) if arguments.output is None: print(requirements_content) else: arguments.output.write(requirements_content) ================================================ FILE: scripts/spell-check.sh ================================================ #!/bin/bash # This script requires `mdspell`: # # https://www.npmjs.com/package/markdown-spellcheck # # Run this script from the root directory. # Usage: # ./scripts/spell-check.sh # MDSPELL_PATH="$(which mdspell)" if [ -z "${MDSPELL_PATH}" ]; then echo "Cannot find executable 'mdspell'. Please install it to run this script: npm i markdown-spellcheck -g" exit 127 else echo "Found 'mdspell' executable at ${MDSPELL_PATH}" mdspell -n -a --en-gb '**/*.md' '!docker-images/*.md' '!docs/api/**/*.md' fi ================================================ FILE: scripts/update_package_versions.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Updates package versions relative to last release. Run this script from the root of the project directory: python scripts/update_package_versions.py """ import argparse import operator import os import re import subprocess # nosec import sys from collections import Counter from pathlib import Path from typing import Any, Dict, List, Optional, Pattern, Set import click import requests import semver import yaml from click.testing import CliRunner from aea.cli import cli from aea.configurations.base import PackageId, PackageType, PublicId from aea.configurations.loader import ConfigLoader from scripts.common import ( PACKAGES_DIR, get_protocol_specification_from_readme, get_protocol_specification_id_from_specification, ) from scripts.generate_ipfs_hashes import update_hashes DIRECTORIES = ["packages", "aea", "docs", "benchmark", "examples", "tests"] CLI_LOG_OPTION = ["-v", "OFF"] TYPES = set(map(lambda x: x.to_plural(), PackageType)) HASHES_CSV = "hashes.csv" TYPE_TO_CONFIG_FILE = { "connections": "connection.yaml", "protocols": "protocol.yaml", "contracts": "contract.yaml", "skills": "skill.yaml", "agents": "aea-config.yaml", } PUBLIC_ID_REGEX = PublicId.PUBLIC_ID_REGEX[1:-1] TEST_PROTOCOLS = ["t_protocol", "t_protocol_no_ct"] FILE_DOWNLOAD_TIMEOUT = 180 def get_protocol_specification_header_regex(public_id: PublicId) -> Pattern: """Get the regex to match.""" return re.compile( rf"(name: {public_id.name}\n" + rf"author: {public_id.author}\n)" + rf"version: {public_id.version}\n" + r"(description:)", re.MULTILINE, ) def check_positive(value: Any) -> int: """Check value is an int.""" try: ivalue = int(value) assert ivalue <= 0 except (AssertionError, ValueError): raise argparse.ArgumentTypeError(f"{value} is an invalid positive int value") return ivalue def parse_arguments() -> argparse.Namespace: """Parse command-line arguments.""" parser = argparse.ArgumentParser() parser.add_argument( "-n", "--no-interactive", action="store_true", default=False, help="Don't ask user confirmation for replacement.", ) parser.add_argument( "-C", "--context", type=check_positive, default=3, help="The number of above/below rows to display", ) parser.add_argument( "-r", "--replace-by-default", action="store_true", default=False, help="If --no-interactive is set, apply the replacement (default: False).", ) return parser.parse_args() arguments: argparse.Namespace = None # type: ignore def get_hashes_from_last_release() -> Dict[str, str]: """Get hashes from last release.""" hashes = {} # Dict[str, str] resp = requests.get( url="https://raw.githubusercontent.com/fetchai/agents-aea/main/packages/hashes.csv", timeout=FILE_DOWNLOAD_TIMEOUT, ) hashes_raw = resp.text for line in hashes_raw.splitlines(): split = line.split(",") hashes[split[0]] = split[1].rstrip() return hashes def get_hashes_from_current_release() -> Dict[str, str]: """Get hashes from last release.""" hashes = {} # Dict[str, str] with open(os.path.join("packages", HASHES_CSV), encoding="utf-8") as f: for line in f: split = line.split(",") hashes[split[0]] = split[1].rstrip() return hashes def split_hashes_by_type(all_hashes: Dict[str, str]) -> Dict[str, Dict[str, str]]: """Split hashes by type.""" result = { "agents": {}, "protocols": {}, "contracts": {}, "connections": {}, "skills": {}, } # type: Dict[str, Dict[str, str]] for key, value in all_hashes.items(): if "fetchai" not in key: print("Non-fetchai packages not allowed!") sys.exit(1) _, type_, name = key.split("/") result[type_][name] = value return result def get_configuration_file_path(type_: str, name: str) -> Path: """Get the configuration file path.""" fp = os.path.join("packages", "fetchai", type_, name, TYPE_TO_CONFIG_FILE[type_]) if os.path.isfile(fp): return Path(fp) fp = os.path.join("aea", type_, name, TYPE_TO_CONFIG_FILE[type_]) if os.path.isfile(fp): return Path(fp) print("Cannot find folder for package `{}` of type `{}`".format(name, type_)) sys.exit(1) def unified_yaml_load(configuration_file: Path) -> Dict: """ Load YAML file, unified (both single- and multi-paged). :param configuration_file: the configuration file path. :return: the data. """ package_type = configuration_file.parent.parent.name with configuration_file.open(encoding="utf-8") as fp: if package_type != "agents": return yaml.safe_load(fp) # when it is an agent configuration file, # we are interested only in the first page of the YAML, # because the dependencies are contained only there. data = yaml.safe_load_all(fp) return list(data)[0] def get_public_id_from_yaml(configuration_file_path: Path) -> PublicId: """ Get the public id from yaml. :param configuration_file_path: the path to the config yaml :return: public id """ data = unified_yaml_load(configuration_file_path) author = data["author"] # handle the case when it's a package or agent config file. name = data["name"] if "name" in data else data["agent_name"] version = data["version"] return PublicId(author, name, version) def public_id_in_registry(type_: str, name: str) -> PublicId: """ Check if a package id is in the registry. :param type_: the package type :param name: the name of the package :return: public id """ runner = CliRunner() result = runner.invoke( cli, [*CLI_LOG_OPTION, "search", type_, "--query", name], standalone_mode=False, ) reg = r"({}/{}:{})".format("fetchai", name, PublicId.VERSION_REGEX) ids = re.findall( reg, result.output, ) p_ids = [] highest = PublicId.from_str("fetchai/{}:0.1.0".format(name)) for id_ in ids: p_id = PublicId.from_str(id_[0]) p_ids.append(p_id) if p_id > highest: highest = p_id return highest def get_all_protocol_spec_ids() -> Set[PublicId]: """ Get all protocol specification ids. We return package ids with type "protocol" even though they are not exactly protocol. The reason is that they are only used to find clashes with protocol ids. :return: a set of package ids. """ result: Set[PublicId] = set() protocol_packages = set(PACKAGES_DIR.rglob("**/**/protocols/**")) - set( PACKAGES_DIR.rglob("**/**/protocols") ) for protocol_package_path in protocol_packages: if "connections" in str(protocol_package_path): continue content = get_protocol_specification_from_readme(protocol_package_path) spec_id = get_protocol_specification_id_from_specification(content) result.add(PublicId.from_str(spec_id)) return result def get_all_package_ids() -> Set[PackageId]: """Get all the package ids in the local repository.""" result: Set[PackageId] = set() now = get_hashes_from_current_release() now_by_type = split_hashes_by_type(now) for type_, name_to_hashes in now_by_type.items(): for name, _ in name_to_hashes.items(): if name in TEST_PROTOCOLS: continue configuration_file_path = get_configuration_file_path(type_, name) public_id = get_public_id_from_yaml(configuration_file_path) package_id = PackageId(PackageType(type_[:-1]), public_id) result.add(package_id) return result def get_public_ids_to_update() -> Set[PackageId]: """ Get all the public ids to be updated. In particular, a package DOES NOT NEED a version bump if: - the package is a "scaffold" package; - the package is no longer present - the package hasn't change since the last release; - the public ids of the local package and the package in the registry are already the same. :return: set of package ids to update """ result: Set[PackageId] = set() last = get_hashes_from_last_release() now = get_hashes_from_current_release() last_by_type = split_hashes_by_type(last) now_by_type = split_hashes_by_type(now) for type_ in TYPES: for key, value in last_by_type[type_].items(): # if the package is a "scaffold" package, skip; if key == "scaffold": print("Package `{}` of type `{}` is never bumped!".format(key, type_)) continue # if the package is no longer present, skip; if key not in now_by_type[type_]: print("Package `{}` of type `{}` no longer present!".format(key, type_)) continue # if the package hasn't change since the last release, skip; if now_by_type[type_][key] == value: print( "Package `{}` of type `{}` has not changed since last release!".format( key, type_ ) ) continue # load public id in the registry if any name = key configuration_file_path = get_configuration_file_path(type_, name) current_public_id = get_public_id_from_yaml(configuration_file_path) deployed_public_id = public_id_in_registry(type_, name) difference = minor_version_difference(current_public_id, deployed_public_id) # check if the public ids of the local package and the package in the registry are already the same. package_info = f"Package `{name}` of type `{type_}`" public_id_info = f"current id `{current_public_id}` and deployed id `{deployed_public_id}`" if difference == 0: print(f"{package_info} needs to be bumped!") result.add(PackageId(type_[:-1], current_public_id)) elif difference == 1: print(f"{package_info} already at correct version!") continue else: print(f"{package_info} has {public_id_info}. Error!") sys.exit(1) return result def _get_ambiguous_public_ids() -> Set[PublicId]: """Get the public ids that are the public ids of more than one package id.""" all_package_ids = get_all_package_ids() result: Set[PublicId] = set( map( operator.itemgetter(0), filter( lambda x: x[0].name != "scaffold" and x[1] > 1, Counter(id_.public_id for id_ in all_package_ids).items(), ), ) ) return result def _sort_in_update_order(package_ids: Set[PackageId]) -> List[PackageId]: """ Sort the set of package id in the order of update. In particular, they are sorted from the greatest version number to the lowest. The reason is to avoid that consecutive package ids (i.e. whose minors difference is 1) gets updated in ascending order, resulting in all the updates collapsing to the greatest version. For example, consider two package ids with prefix 'author/package' and with versions 0.1.0 and 0.2.0, respectively. If we bump first the former and then the latter, the new replacements associated to the first updated are taken into account in the second update. :param package_ids: set of package ids :return: sorted list of package ids """ return sorted( package_ids, key=lambda x: ( semver.VersionInfo.parse(x.public_id.version), x.public_id.author, x.public_id.name, x.package_type.value, ), reverse=True, ) def minor_version_difference( current_public_id: PublicId, deployed_public_id: PublicId ) -> int: """Check the minor version difference.""" diff = semver.compare(current_public_id.version, deployed_public_id.version) return diff def _can_disambiguate_from_context( line: str, old_string: str, type_: str ) -> Optional[bool]: """ Check whether we can disambiguate the public id given contextual information. For example: - whether the public id appears in a line of the form 'aea fetch ...' (we know it's an agent) - whether the public id appears in a line of the form 'aea add ...' (we know the component type) - whether the type appears in the same line where the public id occurs. :param line: the line :param old_string: the old string :param type_: the type of package :return: if True/False, the old string can/cannot be replaced. If None, we don't know. """ match = re.search( rf"aea +add +(skill|protocol|connection|contract) +{old_string}", line ) if match is not None: return match.group(1) == type_[:-1] if re.search(rf"aea +fetch +{old_string}", line) is not None: return type_ == "agents" match = re.search( "(skill|SKILL|" + "protocol|PROTOCOL|" + "connection|CONNECTION|" + "contract|CONTRACT|" + "agent|AGENT" + f")s?.*{old_string}", line, ) if match is not None: return (match.group(1) + "s") == type_ # for protocol specification id only: # - if the line contains 'protocol_specification_id: {old_public_id}' or # - if the line contains 'protocol_specification_id = PublicId.from_str("{old_public_id}")' # then DON'T replace it (we only bump protocol ids here). # otherwise, we don't know -> return None and ask to user. case_1 = f"protocol_specification_id: {old_string}" case_2 = rf'protocol_specification_id = PublicId.from_str([\'"]{old_string}[\'"])' if re.search(case_1, line) or re.search(case_2, line): return True return None def _ask_user( lines: List[str], line: str, idx: int, old_string: str, type_: str, lines_num: int ) -> str: print("=" * 50) above_rows = lines[idx - lines_num : idx] below_rows = lines[idx + 1 : idx + lines_num] print("".join(above_rows)) print(line.rstrip().replace(old_string, "\033[91m" + old_string + "\033[0m")) print("".join(below_rows)) answer = input( f"Replace for component ({type_}, {old_string})? [y/N]: ", ) # nosec return answer def replace_aea_fetch_statements( content: str, old_string: str, new_string: str, type_: str ) -> str: """Replace statements of the type: 'aea fetch '.""" if type_ == "agents": content = re.sub( rf"aea +fetch +{old_string}", f"aea fetch {new_string}", content ) return content def replace_aea_add_statements( content: str, old_string: str, new_string: str, type_: str ) -> str: """Replace statements of the type: 'aea add '.""" if type_ != "agents": content = re.sub( rf"aea +add +{type_} +{old_string}", f"aea add {type_} {new_string}", content, ) return content def replace_type_and_public_id_occurrences( line: str, old_string: str, new_string: str, type_: str ) -> str: """Replace the public id whenever the type and the id occur in the same row, and NOT when other type names occur.""" if re.match(f"{type_}.*{old_string}", line) and all( _type not in line for _type in TYPES.difference({type_}) ): line = line.replace(old_string, new_string) return line def replace_in_yamls( content: str, old_public_id: PublicId, new_public_id: PublicId, type_: str ) -> str: """ Replace the public id in configuration files (also nested in .md files). 1) replace package dependencies: |protocols: |- author/name:version |... |- old_string 2) replace in configuration headers: |name: package_name |author: package_author |version: package_version -> bump up |type: package_type :param content: the content :param old_public_id: the old public id :param new_public_id: the new public id :param type_: the type of the package :return: replaced content """ # case 1: regex = re.compile(f"({type_}:\n(-.*\n)*)(- *{str(old_public_id)})", re.MULTILINE) content = regex.sub(rf"\g<1>- {str(new_public_id)}", content) # case 2: regex = re.compile( rf"(name: {old_public_id.name}\nauthor: {old_public_id.author}\n)version: {old_public_id.version}\n(type: {type_[:-1]})", re.MULTILINE, ) content = regex.sub(rf"\g<1>version: {new_public_id.version}\n\g<2>", content) return content def replace_in_protocol_readme( fp: Path, content: str, old_public_id: PublicId, new_public_id: PublicId, type_: str ) -> str: """ Replace the version id in the protocol specification in the protcol's README. That is, bump the version in cases like: |name: package_name |author: package_author |version: package_version -> bump up ... :param fp: path to the file being edited. :param content: the content of the file. :param old_public_id: the old public id. :param new_public_id: the new public id. :param type_: the type of the package. :return: the new content. """ if ( type_ == fp.parent.parent.name == "protocols" and fp.name == "README.md" and fp.parent.name == old_public_id.name ): regex = get_protocol_specification_header_regex(old_public_id) content = regex.sub(rf"\g<1>version: {new_public_id.version}\n\g<2>", content) return content def file_should_be_processed(content: str, old_public_id: PublicId) -> bool: """Check if the file should be processed.""" old_string = str(old_public_id) return ( old_string in content or get_protocol_specification_header_regex(old_public_id).search(content) is not None ) def bump_version_in_yaml( configuration_file_path: Path, type_: str, version: str ) -> None: """Bump the package version in the package yaml.""" loader = ConfigLoader.from_configuration_type(type_[:-1]) with configuration_file_path.open(encoding="utf-8") as f: config = loader.load(f) config.version = version with open(configuration_file_path, "w", encoding="utf-8") as f: loader.dump(config, f) class Updater: """Package versions updter tool.""" def __init__( self, new_version: str, replace_by_default: bool, context: int ) -> None: """Init updater.""" self.option_new_version = new_version self.option_replace_by_default = replace_by_default self.option_context = context @staticmethod def run_hashing() -> None: """Run hashes update.""" hashing_call = update_hashes() if hashing_call == 1: raise Exception("Problem when running IPFS script!") @staticmethod def check_if_running_allowed() -> None: """ Check if we can run the script. Script should only be run on a clean branch. """ with subprocess.Popen( # nosec ["git", "diff"], stdout=subprocess.PIPE ) as git_call: (stdout, _) = git_call.communicate() git_call.wait() if len(stdout) > 0: raise Exception("Cannot run script in unclean git state.") def _checks(self) -> None: self.run_hashing() self.check_if_running_allowed() def run(self) -> None: """Run package versions update process.""" self._checks() self._run_hashing() def _run_once(self) -> bool: """Run the upgrade logic once.""" all_package_ids_to_update = get_public_ids_to_update() if len(all_package_ids_to_update) == 0: print("No packages to update. Done!") return False ambiguous_public_ids = _get_ambiguous_public_ids() self.process_packages(all_package_ids_to_update, ambiguous_public_ids) return True def process_packages( self, all_package_ids_to_update: Set[PackageId], ambiguous_public_ids: Set[PublicId], ) -> None: """Process the package versions.""" print("*" * 100) conflicts = {p.public_id for p in all_package_ids_to_update}.intersection( ambiguous_public_ids ) print(f"Ambiguous public ids: {ambiguous_public_ids}") print( f"Conflicts with public ids to update: {conflicts}", ) print("*" * 100) print("Start processing.") # we need to include this in case some protocol id == spec id of that protocol. spec_protocol_ids = get_all_protocol_spec_ids() sorted_package_ids_list = _sort_in_update_order(all_package_ids_to_update) for package_id in sorted_package_ids_list: print("#" * 50) print(f"Processing {package_id}") is_ambiguous = package_id.public_id in ambiguous_public_ids.union( spec_protocol_ids ) self.process_package(package_id, is_ambiguous) def process_package(self, package_id: PackageId, is_ambiguous: bool) -> None: """ Process a package. - check version in registry - make sure, version is exactly one above the one in registry - change all occurrences in packages/tests/aea/examples/benchmark/docs to new reference - change yaml version number :param package_id: the id of the package :param is_ambiguous: whether the public id is ambiguous. """ type_plural = package_id.package_type.to_plural() configuration_file_path = get_configuration_file_path( type_plural, package_id.name ) current_public_id = get_public_id_from_yaml(configuration_file_path) self.bump_package_version( current_public_id, configuration_file_path, type_plural, is_ambiguous ) def get_new_package_version(self, current_public_id: PublicId) -> str: """Get new package version according to command line options provided.""" ver = semver.VersionInfo.parse(current_public_id.version) if self.option_new_version == ASK_VERSION: while True: new_version = click.prompt( f"Please enter a new version for {current_public_id}", type=str ) try: new_ver = semver.VersionInfo.parse(new_version) if new_ver <= ver: print("Version is lower or the same. Enter a new one.") continue break except Exception as e: # pylint: disable=broad-except print(f"Version parse error: {e}. Please enter a new version.") continue elif self.option_new_version == UPDATE_MINOR: new_version = ver.bump_minor() elif self.option_new_version == UPDATE_PATCH: new_version = ver.bump_patch() else: raise Exception("unknown version update mode") return str(new_version) def bump_package_version( self, current_public_id: PublicId, configuration_file_path: Path, type_: str, is_ambiguous: bool = False, ) -> None: """ Bump the version references of the package in the repo. Includes, bumping the package itself. :param current_public_id: the current public id :param configuration_file_path: the path to the configuration file :param type_: the type of package :param is_ambiguous: whether or not the package id is ambiguous """ new_version = self.get_new_package_version(current_public_id) new_public_id = PublicId( current_public_id.author, current_public_id.name, new_version ) for rootdir in DIRECTORIES: for path in Path(rootdir).glob("**/*"): if path.is_file() and str(path).endswith( (".py", ".yaml", ".md", ".sh") ): self.inplace_change( path, current_public_id, new_public_id, type_, is_ambiguous, ) bump_version_in_yaml(configuration_file_path, type_, new_public_id.version) def _run_hashing(self) -> None: while self._run_once(): self._run_hashing() def inplace_change( self, fp: Path, old_public_id: PublicId, new_public_id: PublicId, type_: str, is_ambiguous: bool, ) -> None: """Replace the occurrence of a string with a new one in the provided file.""" content = fp.read_text() if not file_should_be_processed(content, old_public_id): return old_string = str(old_public_id) new_string = str(new_public_id) print( f"Processing file {fp} for replacing {old_string} with {new_string} (is_ambiguous: {is_ambiguous})" ) content = replace_in_yamls(content, old_public_id, new_public_id, type_) content = replace_in_protocol_readme( fp, content, old_public_id, new_public_id, type_ ) if not is_ambiguous: content = content.replace(old_string, new_string) else: content = self._ask_user_and_replace_if_allowed( content, old_string, new_string, type_ ) with fp.open(mode="w") as f: f.write(content) def _ask_user_and_replace_if_allowed( self, content: str, old_string: str, new_string: str, type_: str ) -> str: """ Ask user if the line should be replaced or not, if the script arguments allow that. :param content: the content. :param old_string: the old string. :param new_string: the new string. :param type_: the type of the package. :return: the updated content. """ if self.option_replace_by_default: content = content.replace(old_string, new_string) return content lines = content.splitlines(keepends=True) for idx, line in enumerate(lines[:]): if old_string not in line: continue can_replace = _can_disambiguate_from_context(line, old_string, type_) # if we managed to replace all the occurrences, then save this line and continue if can_replace is not None: lines[idx] = ( line.replace(old_string, new_string) if can_replace else line ) continue # otherwise, forget the attempts and ask the user. answer = _ask_user(lines, line, idx, old_string, type_, self.option_context) if answer == "y": lines[idx] = line.replace(old_string, new_string) return "".join(lines) UPDATE_PATCH = "bump_patch" UPDATE_MINOR = "bump_minor" ASK_VERSION = "ask" NEW_VERSION_OPTIONS = [ASK_VERSION, UPDATE_PATCH, UPDATE_MINOR] @click.command() @click.option( "--new-version", "-n", type=click.Choice(NEW_VERSION_OPTIONS), help=f"Mode to determine a new package version: {', '.join(NEW_VERSION_OPTIONS)}", default=ASK_VERSION, ) @click.option( "--context", "-C", type=click.IntRange(0, 5), help="Number of lines above and below the reference to display.", default=1, ) @click.option( "--replace-by-default", "-r", is_flag=True, help="Automatically replace package reference (default: False).", ) def command(new_version, replace_by_default, context): """Run cli command.""" Updater(new_version, replace_by_default, context).run() if __name__ == "__main__": command() # pylint: disable=no-value-for-parameter ================================================ FILE: scripts/update_plugin_versions.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ Bump the versions of AEA plugins throughout the code base. python scripts/update_plugin_versions.py --update "plugin-name,version" [--update ...] Example of usage: python scripts/update_plugin_versions.py --update "aea-ledger-fetchai,0.2.0" --update "aea-ledger-ethereum,0.3.0" """ import argparse import pprint import re import sys from pathlib import Path from typing import Dict, List, Tuple from packaging.specifiers import SpecifierSet from packaging.version import Version from aea.helpers.base import compute_specifier_from_version from scripts.generate_ipfs_hashes import update_hashes PLUGINS_DIR = Path("plugins") SETUP_PY_NAME_REGEX = re.compile(r"\Wname=\"(.*)\",") SETUP_PY_VERSION_REGEX = re.compile(r"\Wversion=\"(.*)\",") IGNORE_DIRS = [Path(".git")] def update_plugin_setup( plugin_name: str, old_version: Version, new_version: Version ) -> bool: """Update plugin setup.py script with new version. :param plugin_name: the plugin name. :param old_version: the old version. :param new_version: the new version. :return: True if an update has been done, False otherwise. """ setup_file = PLUGINS_DIR / plugin_name / "setup.py" content = setup_file.read_text() new_content = re.sub( rf"version=['\"]{old_version}['\"],", f'version="{new_version}",', content ) setup_file.write_text(new_content) return content != new_content def process_plugin( plugin_name: str, old_version: Version, new_version: Version ) -> bool: """ Process the plugin version. :param plugin_name: the plugin name. :param old_version: the old version. :param new_version: the new version. :return: True if an update has been done, False otherwise. """ result = False result = update_plugin_setup(plugin_name, old_version, new_version) or result result = ( update_plugin_version_specifiers(plugin_name, old_version, new_version) or result ) return result def update_plugin_version_specifiers( plugin_name: str, old_version: Version, new_version: Version ) -> bool: """ Update aea_version specifier set in docs. :param plugin_name: the plugin name. :param old_version: the old version. :param new_version: the new version. :return: True if the update has been done, False otherwise. """ old_specifier_set = compute_specifier_from_version(old_version) new_specifier_set = compute_specifier_from_version(new_version) print(f"Old version specifier: {old_specifier_set}") print(f"New version specifier: {new_specifier_set}") if old_specifier_set == new_specifier_set: print("Not updating version specifier - they haven't changed.") return False has_changed = False new_specifier_set = str(SpecifierSet(new_specifier_set)) old_specifier_set = str(SpecifierSet(old_specifier_set)) old_specifier_set_regex = re.compile(str(old_specifier_set).replace(" ", " *")) for file in filter(lambda p: not p.is_dir(), Path(".").rglob("*")): dir_root = Path(file.parts[0]) if dir_root in IGNORE_DIRS: print(f"Skipping '{file}'...") continue print( f"Replacing '{old_specifier_set}' with '{new_specifier_set}' in '{file}'... ", end="", ) try: content = file.read_text() except UnicodeDecodeError as e: print(f"Cannot read {file}: {str(e)}. Continue...") else: if old_specifier_set_regex.search(content) is None: print("No version to update found.") continue new_content = _replace_patterns( content, plugin_name, old_specifier_set, new_specifier_set ) has_changed = has_changed or content != new_content file.write_text(new_content) print("Done!") return has_changed def _replace_patterns( content: str, plugin_name: str, old_specifier: str, new_specifier: str ) -> str: """ Replace specific patterns. It identifies three patterns: 1) strings of the form: 2) YAML strings of the form: plugin-name: version: 3) strings of the form "": {"version": ""} :param content: the file content :param plugin_name: the plugin name :param old_specifier: the old specifier :param new_specifier: the new specifier :return: the new content. """ # check pattern (1) content = re.sub( f"{plugin_name}{old_specifier}", f"{plugin_name}{new_specifier}", content ) # check pattern (2) content = re.sub( f"({plugin_name}:\n *version: ){old_specifier}", rf"\g<1>{new_specifier}", content, ) # check pattern (3) content = re.sub( f'"{plugin_name}": {{"version": "{old_specifier}"}}', f'"{plugin_name}": {{"version": "{new_specifier}"}}', content, ) return content def exit_with_message(message: str, exit_code: int = 1) -> None: """Exit the program with a message and an exit code.""" print(message) sys.exit(exit_code) def get_plugin_names_and_versions() -> Dict[str, Version]: """Get all the plugins names and versions.""" result: Dict[str, Version] = {} for plugin_setup_script in PLUGINS_DIR.glob("*/setup.py"): content = plugin_setup_script.read_text() name_matches: List[str] = SETUP_PY_NAME_REGEX.findall(content) version_matches: List[str] = SETUP_PY_VERSION_REGEX.findall(content) if len(name_matches) != 1 or len(version_matches) != 1: exit_with_message( f"Unexpected result: in {result}, found plugin names: {name_matches} and versions: {version_matches}" ) name, version = name_matches[0], version_matches[0] if name in result: print(f"Warning, duplicate plugin name: '{name}'.") result[name] = Version(version) return result def name_version_pair(s: str) -> Tuple[str, str]: """ Parse a name-version pair. :param s: the parameter string. :return: a pair of string (name, new_version) """ try: name, version = [part.strip() for part in s.split(",")] return name, version except Exception: raise argparse.ArgumentTypeError(f"Name-version pair not correct: '{s}'") def parse_args() -> argparse.Namespace: """Parse arguments.""" parser = argparse.ArgumentParser("bump_aea_version") parser.add_argument( "--update", type=name_version_pair, metavar="'NAME,VERSION'", required=True, action="append", help="A comma-separated pair: 'plugin-name, new-version'.", ) parser.add_argument("--no-fingerprint", action="store_true") arguments_ = parser.parse_args() return arguments_ def main() -> None: """Run the script.""" arguments = parse_args() current_versions_by_name: Dict[str, Version] = get_plugin_names_and_versions() new_versions_by_name: Dict[str, Version] = dict( (name, Version(version)) for name, version in arguments.update ) print( f"Found plugin names and versions:\n{pprint.pformat(current_versions_by_name)}" ) print(f"Plugins to update:\n{pprint.pformat(new_versions_by_name)}") not_found_plugins = set(new_versions_by_name.keys()).difference( current_versions_by_name.keys() ) if len(not_found_plugins) > 0: exit_with_message( f"Error: These plugins have not been found:\n{pprint.pformat(not_found_plugins)}" ) have_updated_specifier_set = False for current_plugin_name, new_version in new_versions_by_name.items(): old_version = current_versions_by_name[current_plugin_name] print( f"Processing {current_plugin_name}, old_version={old_version}, new_version={new_version}" ) if new_version == old_version: print("Skipping, as old and new versions are equal.") continue have_updated_specifier_set = ( process_plugin(current_plugin_name, old_version, new_version) or have_updated_specifier_set ) return_code = 0 if arguments.no_fingerprint: print("Not updating fingerprints, since --no-fingerprint was specified.") elif not have_updated_specifier_set: print("Not updating fingerprints, since no specifier set has been updated.") else: print("Updating hashes and fingerprints.") return_code = update_hashes() exit_with_message("Done!", exit_code=return_code) if __name__ == "__main__": main() ================================================ FILE: scripts/update_symlinks_cross_platform.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ # pylint: disable=cyclic-import """This script will update the symlinks of the project, cross-platform compatible.""" import contextlib import inspect import os import sys import traceback from functools import reduce from pathlib import Path from typing import Generator, List, Tuple, Union SCRIPTS_PATH = Path(os.path.dirname(inspect.getfile(inspect.currentframe()))) # type: ignore ROOT_PATH = SCRIPTS_PATH.parent.absolute() TEST_DATA = ROOT_PATH / "tests" / "data" TEST_DUMMY_AEA_DIR = TEST_DATA / "dummy_aea" FETCHAI_PACKAGES = ROOT_PATH / "packages" / "fetchai" SYMLINKS = [ (TEST_DUMMY_AEA_DIR / "skills" / "dummy", TEST_DATA / "dummy_skill"), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "protocols" / "default", FETCHAI_PACKAGES / "protocols" / "default", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "protocols" / "signing", FETCHAI_PACKAGES / "protocols" / "signing", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "protocols" / "state_update", FETCHAI_PACKAGES / "protocols" / "state_update", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "protocols" / "fipa", FETCHAI_PACKAGES / "protocols" / "fipa", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "protocols" / "oef_search", FETCHAI_PACKAGES / "protocols" / "oef_search", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "connections" / "local", FETCHAI_PACKAGES / "connections" / "local", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "connections" / "p2p_libp2p", FETCHAI_PACKAGES / "connections" / "p2p_libp2p", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "contracts" / "erc1155", FETCHAI_PACKAGES / "contracts" / "erc1155", ), ( TEST_DUMMY_AEA_DIR / "vendor" / "fetchai" / "skills" / "error", FETCHAI_PACKAGES / "skills" / "error", ), ] # type: List[Tuple[Path, Path]] """A list of pairs: (link_path, target_path)""" def make_symlink(link_name: str, target: str) -> None: """ Make a symbolic link, cross platform. :param link_name: the link name. :param target: the target. """ try: Path(link_name).unlink() except FileNotFoundError: pass Path(link_name).symlink_to(target, target_is_directory=True) @contextlib.contextmanager def cd(path: Union[Path, str]) -> Generator: """Change directory with context manager.""" old_cwd = os.getcwd() try: os.chdir(path) yield os.chdir(old_cwd) except Exception as e: # pylint: disable=broad-except os.chdir(old_cwd) raise e from e def create_symlink(link_path: Path, target_path: Path, root_path: Path) -> int: """ Change directory and call the cross-platform script. The working directory must be the parent of the symbolic link name when executing 'create_symlink_crossplatform.sh'. Hence, we need to translate target_path into the relatve path from the symbolic link directory to the target directory. So: 1) from link_path, extract the number of jumps to the parent directory in order to reach the repository root directory, and chain many "../" paths. 2) from target_path, compute the relative path to the root 3) relative_target_path is just the concatenation of the results from step (1) and (2). For instance, given - link_path: './directory_1//symbolic_link - target_path: './directory_2/target_path we want to compute: - link_path: 'symbolic_link' (just the last bit) - relative_target_path: '../../directory_1/target_path' The resulting command on UNIX systems will be: cd directory_1 && ln -s ../../directory_1/target_path symbolic_link :param link_path: the link path :param target_path: the target path :param root_path: the root path :return: exit code """ working_directory = link_path.parent target_relative_to_root = target_path.relative_to(root_path) cwd_relative_to_root = working_directory.relative_to(root_path) nb_parents = len(cwd_relative_to_root.parents) root_relative_to_cwd = reduce( lambda x, y: x / y, [Path("../")] * nb_parents, Path(".") ) link_name = link_path.name target = root_relative_to_cwd / target_relative_to_root with cd(working_directory.absolute()): make_symlink(str(link_name), str(target)) return 0 def main() -> None: """Run main script.""" failed = False for link_name, target in SYMLINKS: print("Linking {} to {}".format(link_name, target)) try: link_name.unlink() except FileNotFoundError: pass try: return_code = create_symlink(link_name, target, ROOT_PATH) except Exception as e: # pylint: disable=broad-except exception = e return_code = 1 traceback.print_exc() print( "Last command failed with return code {} and exception {}".format( return_code, exception ) ) failed = True sys.exit(1 if failed else 0) if __name__ == "__main__": main() ================================================ FILE: scripts/whitelist.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ # flake8: noqa # type: ignore # pylint: skip-file # To update; run: vulture aea --exclude "*_pb2.py" --make-whitelist > tests/whitelist.py _.dependencies_highest_version # unused property (aea/aea_builder.py:116) _.set_search_service_address # unused method (aea/aea_builder.py:484) _.set_required_ledgers # unused method (aea/aea_builder.py:850) _.remove_private_key # unused method (aea/aea_builder.py:580) _.add_component_instance # unused method (aea/aea_builder.py:643) _.set_context_namespace # unused method (aea/aea_builder.py:663) _.remove_protocol # unused method (aea/aea_builder.py:694) _.remove_connection # unused method (aea/aea_builder.py:714) _.remove_skill # unused method (aea/aea_builder.py:734) _.remove_contract # unused method (aea/aea_builder.py:754) _.tick # unused property (aea/agent.py:186) AgentLoopException # unused class (aea/agent_loop.py:134) set_command # unused function (aea/cli/config.py:53) all_command # unused function (aea/cli/list.py:47) _.type_cast_value # unused method (aea/cli/utils/click_utils.py:37) _.get_metavar # unused method (aea/cli/utils/click_utils.py:79) _.convert # unused method (aea/cli/utils/click_utils.py:83) _.get_metavar # unused method (aea/cli/utils/click_utils.py:100) _.convert # unused method (aea/cli/utils/click_utils.py:104) _.convert # unused method (aea/cli/utils/click_utils.py:129) _.formatter # unused attribute (aea/cli/utils/loggers.py:92) is_item_present_unified # unused function (aea/cli/utils/package_utils:394) _.latest # unused property (aea/configurations/base.py:384) _.to_any # unused property (aea/configurations/base.py:442) _.to_latest # unused property (aea/configurations/base.py:442) _.to_uri_path # unused property (aea/configurations/base.py:445) _.to_uri_path # unused property (aea/configurations/base.py:605) _._ensure_connected # unused method (aea/connections/base.py:104) _._ensure_valid_envelope_for_external_comms # unused method (aea/connections/base.py:109) _._connect_context # unused method (aea/connections/base.py:125) _.has_crypto_store # unused property (aea/connections/base.py:138) _.from_dir # unused method (aea/connections/base.py:200) MyScaffoldAsyncConnection # unused class (aea/connections/scaffold/connection.py:31) _.get_instance # unused method (aea/contracts/base.py:64) _.from_dir # unused method (aea/contracts/base.py:81) _.get_raw_transaction # unused method (aea/contracts/base.py:147) _.get_raw_message # unused method (aea/contracts/base.py:163) MyScaffoldContract # unused class (aea/contracts/scaffold/contract.py:25) _.is_valid_address # aea/crypto/cosmos.py:170: unused method 'is_valid_address' (60% confidence) _.get_handle_transaction # unused method (aea/crypto/cosmos.py:491) _.execute_contract_query # unused method (aea/crypto/cosmos.py:571) _.get_code_id # unused method (aea/crypto/cosmos.py:837) _.get_contract_address # unused method (aea/crypto/cosmos.py:849) CosmosFaucetApi # unused class (aea/crypto/cosmos.py:867) testnet_name # unused variable (aea/crypto/cosmos.py:871) EthereumFaucetApi # unused class (aea/crypto/ethereum.py:507) testnet_name # unused variable (aea/crypto/ethereum.py:511) FetchAIFaucetApi # unused class (aea/crypto/fetchai.py:404) testnet_name # unused variable (aea/crypto/fetchai.py:408) _.has_ledger # unused method (aea/crypto/ledger_apis.py:55) _.get_api # unused method (aea/crypto/ledger_apis.py:60) _.has_spec # unused method (aea/crypto/registries/base.py:236) _.main_cryptos # unused property (aea/crypto/wallet.py:130) locate # unused function (aea/helpers/base.py:139) sigint_crossplatform # unused function (aea/helpers/base.py:236) _.dwFlags # unused attribute (aea/helpers/base.py:269) retry_decorator # unused function (aea/helpers/base.py:386) _.is_empty # unused property (aea/helpers/dialogue/base.py:431) _.self_initiated # unused property (aea/helpers/dialogue/base.py:687) _.other_initiated # unused property (aea/helpers/dialogue/base.py:692) _.add_dialogue_endstate # unused method (aea/helpers/dialogue/base.py:697) _.dialogue_stats # unused property (aea/helpers/dialogue/base.py:767) _.is_cancelled_by_timeout # unused method (aea/helpers/exec_timeout.py:51) exc_tb # unused variable (aea/helpers/exec_timeout.py:102) exc_type # unused variable (aea/helpers/exec_timeout.py:102) ExecTimeoutSigAlarm # unused class (aea/helpers/exec_timeout.py:138) envelope_from_bytes # unused function (aea/helpers/file_lock.py:112) LOCK_SH # unused variable (aea/helpers/file_lock.py:32) LOCK_NB # unused variable (aea/helpers/file_lock.py:33) _.filesize # unused attribute (aea/helpers/ipfs/base.py:92) MultiAddr # unused class (aea/helpers/multiaddr/base.py:82) _.in_path # unused property (aea/helpers/pipe.py:70) _.out_path # unused property (aea/helpers/pipe.py:77) _.in_path # unused property (aea/helpers/pipe.py:171) _.out_path # unused property (aea/helpers/pipe.py:175) _.in_path # unused property (aea/helpers/pipe.py:305) _.out_path # unused property (aea/helpers/pipe.py:309) make_ipc_channel # unused function (aea/helpers/pipe.py:647) make_ipc_channel_client # unused function (aea/helpers/pipe.py:663) to_set_specifier # unused function (aea/helpers/pypi.py:199) GenericDataModel # unused class (aea/helpers/search/generic.py:29) AGENT_LOCATION_MODEL # unused variable (aea/helpers/search/generic.py:54) AGENT_PERSONALITY_MODEL # unused variable (aea/helpers/search/generic.py:61) AGENT_SET_SERVICE_MODEL # unused variable (aea/helpers/search/generic.py:71) SIMPLE_SERVICE_MODEL # unused variable (aea/helpers/search/generic.py:81) SIMPLE_DATA_MODEL # unused variable (aea/helpers/search/generic.py:88) AGENT_REMOVE_SERVICE_MODEL # unused variable (aea/helpers/search/generic.py:95) _.counterparty_hash # unused property (aea/helpers/transaction/base.py:529) _.sender_payable_amount_incl_fee # unused property (aea/helpers/transaction/base.py:598) _.counterparty_payable_amount_incl_fee # unused property (aea/helpers/transaction/base.py:623) _.sender_fee # unused property (aea/helpers/transaction/base.py:673) _.counterparty_fee # unused property (aea/helpers/transaction/base.py:679) _.is_connecting # unused attribute (aea/multiplexer.py:53) _.put_message # unused method (aea/multiplexer.py:821) _.has_to # unused property (aea/protocols/base.py:100) ProtobufSerializer # unused class (aea/protocols/base.py:278) JSONSerializer # unused class (aea/protocols/base.py:304) _.from_dir # unused method (aea/protocols/base.py:359) INVALID_MESSAGE # unused variable (aea/protocols/default/custom_types.py:30) INVALID_DIALOGUE # unused variable (aea/protocols/default/custom_types.py:32) _.get_all_protocols # unused method (aea/registries/resources.py:124) _.remove_protocol # unused method (aea/registries/resources.py:133) _.get_contract # unused method (aea/registries/resources.py:153) _.get_all_contracts # unused method (aea/registries/resources.py:165) _.remove_contract # unused method (aea/registries/resources.py:174) _.get_connection # unused method (aea/registries/resources.py:194) _.remove_connection # unused method (aea/registries/resources.py:215) _.get_skill # unused method (aea/registries/resources.py:248) _.remove_skill # unused method (aea/registries/resources.py:269) _.get_all_handlers # unused method (aea/registries/resources.py:309) _.get_behaviour # unused method (aea/registries/resources.py:318) _.get_behaviours # unused method (aea/registries/resources.py:331) PUBLIC_ID # unused variable (aea/skills/__init__.py:25) _.agent_addresses # unused property (aea/skills/base.py:154) _.from_dir # unused method (aea/skills/base.py:683) CyclicBehaviour # unused class (aea/skills/behaviours.py:57) _.number_of_executions # unused property (aea/skills/behaviours.py:66) OneShotBehaviour # unused class (aea/skills/behaviours.py:80) TickerBehaviour # unused class (aea/skills/behaviours.py:99) _.last_act_time # unused property (aea/skills/behaviours.py:135) SequenceBehaviour # unused class (aea/skills/behaviours.py:159) FSMBehaviour # unused class (aea/skills/behaviours.py:243) _.is_started # unused property (aea/skills/behaviours.py:258) _.register_state # unused method (aea/skills/behaviours.py:263) _.register_final_state # unused method (aea/skills/behaviours.py:280) _.unregister_state # unused method (aea/skills/behaviours.py:294) _.final_states # unused property (aea/skills/behaviours.py:331) _.register_transition # unused method (aea/skills/behaviours.py:364) _.unregister_transition # unused method (aea/skills/behaviours.py:383) MyScaffoldBehaviour # unused class (aea/skills/scaffold/behaviours.py:25) MyScaffoldHandler # unused class (aea/skills/scaffold/handlers.py:29) MyModel # unused class (aea/skills/scaffold/my_model.py:25) _.is_executed # unused property (aea/skills/tasks.py:68) _.is_started # unused property (aea/skills/tasks.py:146) _.disable_aea_logging # unused method (aea/test_tools/test_cases.py:134) _.start_subprocess # unused method (aea/test_tools/test_cases.py:198) _.difference_to_fetched_agent # unused method (aea/test_tools/test_cases.py:256) _.delete_agents # unused method (aea/test_tools/test_cases.py:328) _.run_agent # unused method (aea/test_tools/test_cases.py:341) _.run_interaction # unused method (aea/test_tools/test_cases.py:354) _.is_successfully_terminated # unused method (aea/test_tools/test_cases.py:407) _.fingerprint_item # unused method (aea/test_tools/test_cases.py:457) _.eject_item # unused method (aea/test_tools/test_cases.py:473) _.run_install # unused method (aea/test_tools/test_cases.py:488) _.replace_private_key_in_file # unused method (aea/test_tools/test_cases.py:549) _.replace_file_content # unused method (aea/test_tools/test_cases.py:596) _.send_envelope_to_agent # unused method (aea/test_tools/test_cases.py:685) _.read_envelope_from_agent # unused method (aea/test_tools/test_cases.py:694) _._is_teardown_class_called # unused attribute (aea/test_tools/test_cases.py:801) _._start_oef_node # unused method (aea/test_tools/test_cases.py:808) network_node # unused variable (aea/test_tools/test_cases.py:809) _.get_dialogues_with_counterparty # unused method (aea/protocols/dialogue/base.py:999) receiver_address # unused variable (aea/decision_maker/default.py:70) receiver_address # unused variable (aea/decision_maker/default.py:99) create_with_message # unused method (aea/helpers/dialogue/base.py:1054) _.is_disconnecting # unused property (aea/multiplexer.py:67) _.get_quantity_in_outbox # unused method (aea/test_tools/test_skills.py:52) _.get_message_from_outbox # unused method (aea/test_tools/test_skills.py:56) _.drop_messages_from_outbox # unused method (aea/test_tools/test_skills.py:70) _.drop_messages_from_decision_maker_inbox # unused method (aea/test_tools/test_skills.py:86) _.get_quantity_in_decision_maker_inbox # unused method (aea/test_tools/test_skills.py:69) _.get_message_from_decision_maker_inbox # unused method (aea/test_tools/test_skills.py:73) _.assert_quantity_in_outbox # unused method (aea/test_tools/test_skills.py:79) _.assert_quantity_in_decision_making_queue # unused method (aea/test_tools/test_skills.py:86) _.message_has_attributes # unused method (aea/test_tools/test_skills.py:69) _.build_incoming_message_for_skill_dialogue # unused method (aea/test_tools/test_skills.py:155) _.prepare_skill_dialogue # unused method (aea/test_tools/test_skills.py:290) _.__defaults__ # unused attribute (aea/protocols/dialogue/base.py:49) _.build_incoming_message_for_dialogue # unused method (aea/test_tools/test_skills.py:155) _._has_message # unused method (aea/protocols/dialogue/base.py:491) MultiAgentManager # unused class (aea/manager.py:127) _.add_error_callback # unused method (aea/manager.py:202) _.start_manager # unused method (aea/manager.py:208) _.stop_manager # unused method (aea/manager.py:221) _.projects # unused property (aea/manager.py:228) _.add_project # unused method (aea/manager.py:263) _.list_projects # unused method (aea/manager.py:283) _.list_agents_info # unused method (aea/manager.py:283) _.add_agent # unused method (aea/manager.py:291) _.start_all_agents # unused method (aea/manager.py:396) _.get_agent_alias # unused method (aea/manager.py:486) _.DEFAULT_PYPI_INDEX_URL # unused variable (aea/configurations/base.py:85) AEARunner # unused class (aea/runner.py:84) _.join_thread # unused method (aea/helpers/multiple_executor.py:430) _.valid_performatives # unused property (aea/protocols/base.py:90) _.has_dialogue_info # unused property (aea/protocols/base.py:244) get_state # aea/crypto/base.py:251: unused method 'get_state' (60% confidence) _.load_agent_config # unused method (aea/test_tools/test_cases.py:801) _.UseGanache # unused class (aea/test_tools/test_cases.py:871) _._start_ganache # unused method (aea/test_tools/test_cases.py:875) _.ganache # unused variable (aea/test_tools/test_cases.py:876:) _.unsupported_protocol_count _.unsupported_skill_count _.decoding_error_count _.ErrorHandler # unused class (aea/error_handler/scaffold.py:27) ensure_dir # unused function (aea/helpers/base.py:561) _.get_agent_overridables # unused method (aea/manager/manager.py:419) _.set_agent_overrides # unused method (aea/manager/manager.py:432) by_path # unused function (aea/cli/fingerprint.py:94) AgentRecord # unused class (aea/helpers/acn/agent_record.py:49) check_validity # unused method (aea/helpers/acn/agent_record.py:96) signature_from_cert_request # unused function (aea/helpers/acn/agent_record.py:51) Uri # unused class (aea/helpers/acn/uri.py:26) not_before_string # unused property (aea/helpers/base.py:724) not_after_string # unused property (aea/helpers/base.py:729) from_cert_request # unused method (aea/helpers/acn/agent_record.py:136) FetchAICrypto # unused class (aea/crypto/fetchai.py:40) entity # unused property (aea/crypto/base.py:77) callable_name # unused variable (aea/crypto/base.py:277) update_with_gas_estimate # unused method (aea/crypto/base.py:362) try_decorator # unused function (aea/helpers/base.py:284) from_string # unused method (aea/helpers/multiaddr/base.py:163) BaseSyncConnection # unused class (aea/connections/sync_connection.py:33) _.put_envelope # unused method (aea/connections/sync_connection.py:84) MyScaffoldSyncConnection # unused class (aea/connections/scaffold/connection.py:89) run_count # unused variable (aea/test_tools/test_cases.py:998) _.get_addresses # unused method (aea/manager/project.py:284) _.get_connections_addresses # unused method (aea/manager/project.py:289) _.check_protobuf_using_protoc # unused function (aea/protocols/generator/common.py:420) _.last_start_status # unused property (aea/manager/manager.py:296) from_uri_path # unused method (aea/configurations/data_types.py:339) from_uri_path # unused method (aea/configurations/data_types.py:497) no_active_handler_count # unused attribute (aea/error_handler/default.py:76) parse_module # unused method (aea/skills/base.py:359) parse_module # unused method (aea/skills/base.py:419) parse_module # unused method (aea/skills/base.py:463) parse_module # unused method (aea/skills/base.py:520) _.__path__ # unused attribute (aea/components/base.py:137) _.__path__ # unused attribute (aea/components/base.py:140) _.__path__ # unused attribute (aea/components/base.py:143) short_help # unused method (aea/cli/plugin.py:83) parse_args # unused method (aea/cli/plugin.py:99) encrypt # unused method (aea/crypto/base.py:148) decrypt # unused method (aea/crypto/base.py:158) keyfile_json # unused variable (aea/crypto/base.py:159) DecryptError # unused class (aea/crypto/helpers.py:195) hex_to_bytes_for_key # unused function (aea/crypto/helpers.py:209) item_owner_crypto # unused attribute 'item_owner_crypto' (aea/test_tools/test_contract.py:70) TCPSocketChannelClientTLS # unused class (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/pipe.py:597) _.check_hostname # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/pipe.py:603) _.verify_mode # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/pipe.py:604) _.last_exception # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/pipe.py:539) _.last_exception # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/pipe.py:567) _.last_exception # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/pipe.py:630) _.last_exception # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/pipe.py:649) run_install_subprocess # unused function (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/install_dependency.py:75) _.deployment_tx_receipt # unused attribute (agents-aea/aea/test_tools/test_contract.py:105) _.Hash # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/ipfs/base.py:124) _.Tsize # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/ipfs/base.py:125) _.Name # unused attribute (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/ipfs/base.py:126) init_worker # unused function (/home/solarw/MyData/work/fetchai/agents-aea/aea/skills/tasks.py:108) _._generate_hash # unused method (/home/solarw/MyData/work/fetchai/agents-aea/aea/helpers/ipfs/base.py:161) ================================================ FILE: setup.cfg ================================================ [flake8] paths=aea,examples,packages,scripts,tests exclude=.md, *_pb2.py, aea/__init__.py, aea/cli/__init__.py, tests/common/oef_search_pluto_scripts, tests/common/click_testing.py, scripts/oef/launch.py max-line-length = 88 select = B,C,D,E,F,I,W, ignore = B014,D202,D400,D401,E501,E203,W503,DAR002,DAR003,DAR101,DAR102,DAR201,DAR202,DAR301,DAR401,DAR402 application-import-names = aea,packages,tests,scripts docstring_style=sphinx strictness=short # ignore as too restrictive for our needs: # B014: redundant exception # D202: blank lines # D400: First line should end with a period # D401: First line should be in imperative mood # E501: https://www.flake8rules.com/rules/E501.html (Line too long) # E203: https://www.flake8rules.com/rules/E203.html (Whitespace) # W503: https://www.flake8rules.com/rules/W503.html (Line break) # DAR002 Empty description: e # DAR003: Incorrect indentation: ~< # DAR101: Missing parameter(s) in Docstring: - **kwargs # DAR102 Excess parameter(s) in Docstring: + component # DAR201: Missing "Returns" in Docstring: - return # DAR202: Excess "Returns" in Docstring: + return # DAR301: Missing "Yields" in Docstring: - yield # DAR401 Missing exception(s) in Raises section: -r Exception # DAR402 Excess exception(s) in Raises section: +r ValueError ================================================ FILE: strategy.ini ================================================ ; some useful links: ; - https://janelia-flyem.github.io/licenses.html ; - https://dwheeler.com/essays/floss-license-slide.html ; Authorized and unauthorized licenses in LOWER CASE [Licenses] authorized_licenses: ; aliases for MIT License MIT MIT license https://opensource.org/licenses/MIT License :: OSI Approved :: MIT ; aliases for BSD License (and variants) BSD BSD license new BSD (new) BSD new BDS license simplified BSD 3-Clause BSD BSD-3-Clause BSD 3-Clause BSD-2-Clause BSD-like BSD-2-Clause or Apache-2.0 BSD, Public Domain ; aliases for Apache License version 2.0 Apache 2.0 Apache-2.0 Apache License 2.0 Apache License, Version 2.0 Apache License Version 2.0 Apache2 ASL 2 ; some packages use 'Apache Software' as license string, ; which is ambiguous. However, 'Apache Software' ; will likely match with 'Apache 2.0' Apache Software BSD, Public Domain, Apache http://www.apache.org/licenses/LICENSE-2.0 ; PSF (BSD-style) Python Software Foundation PSF ; other permissive licenses Historical Permission Notice and Disclaimer (HPND) HPND ISC ISCL ISC License (ISCL) BSD or Apache License, Version 2.0 Modified BSD Expat unauthorized_licenses: ; aliases for MPL 2.0 MPL-2.0 MPL 2.0 Mozilla Public License 2.0 (MPL 2.0) ; Section 8 of https://www.mozilla.org/en-US/MPL/2.0/Revision-FAQ/ MPL 1.1 MPL-1.1 ; http://www.gnu.org/licenses/license-list.en.html#apache2 GPLv2 GPLv2+ GNU General Public License v2 or later (GPLv2+) ; LGPL LGPL GNU Library or Lesser General Public License (LGPL) ; LGPLv2.1 LGPLv2.1 LGPLv2.1+ ; LGPLv3 GNU Lesser General Public License v3 (LGPLv3) LGPLv3 ; GPL v3 GPL v3 GPLv3+ [Authorized Packages] gym: >=0.15 ;filelock is public domain filelock: >=3.0.12 fetchai-ledger-api: >=0.0.1 chardet: >=3.0.4 certifi: >=2019.11.28 pathable: * ================================================ FILE: tests/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the AEA package.""" ================================================ FILE: tests/common/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the common modules.""" ================================================ FILE: tests/common/docker_image.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains testing utilities.""" import asyncio import logging import os import re import shutil import subprocess # nosec import sys import tempfile import time from abc import ABC, abstractmethod from threading import Timer from typing import Dict, List, Optional import docker import pytest from docker import DockerClient from docker.models.containers import Container from oef.agents import OEFAgent from oef.core import AsyncioCore from aea.exceptions import enforce from aea.helpers import http_requests as requests logger = logging.getLogger(__name__) class DockerImage(ABC): """A class to wrap interatction with a Docker image.""" MINIMUM_DOCKER_VERSION = (19, 0, 0) def __init__(self, client: docker.DockerClient): """Initialize.""" self._client = client def check_skip(self): """ Check whether the test should be skipped. By default, nothing happens. """ self._check_docker_binary_available() def _check_docker_binary_available(self): """Check the 'Docker' CLI tool is in the OS PATH.""" result = shutil.which("docker") if result is None: pytest.skip("Docker not in the OS Path; skipping the test") result = subprocess.run( # nosec ["docker", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if result.returncode != 0: pytest.skip(f"'docker --version' failed with exit code {result.returncode}") match = re.search( r"Docker version ([0-9]+)\.([0-9]+)\.([0-9]+)", result.stdout.decode("utf-8"), ) if match is None: pytest.skip("cannot read version from the output of 'docker --version'") version = (int(match.group(1)), int(match.group(2)), int(match.group(3))) if version < self.MINIMUM_DOCKER_VERSION: pytest.skip( f"expected Docker version to be at least {'.'.join(self.MINIMUM_DOCKER_VERSION)}, found {'.'.join(version)}" ) @property @abstractmethod def tag(self) -> str: """Return the tag of the image.""" def pull_image(self, retries=3) -> None: """Pull image from remote repo.""" for _ in range(retries): try: self._client.images.pull(self.tag) return except Exception as e: # nosec print("failed to pull image", e) def stop_if_already_running(self): """Stop the running images with the same tag, if any.""" client = docker.from_env() for container in client.containers.list(): if self.tag in container.image.tags: logger.info(f"Stopping image {self.tag}...") container.stop() container.wait() @abstractmethod def create(self) -> Container: """Instantiate the image in a container.""" @abstractmethod def wait(self, max_attempts: int = 15, sleep_rate: float = 1.0) -> bool: """ Wait until the image is running. :param max_attempts: max number of attempts. :param sleep_rate: the amount of time to sleep between different requests. :return: True if the wait was successful, False otherwise. """ return True class OEFHealthCheck(object): """A health check class.""" def __init__( self, oef_addr: str, oef_port: int, loop: Optional[asyncio.AbstractEventLoop] = None, ): """ Initialize. :param oef_addr: IP address of the OEF node. :param oef_port: Port of the OEF node. """ self.oef_addr = oef_addr self.oef_port = oef_port self._result = False self._stop = False self._core = AsyncioCore() self.agent = OEFAgent( "check", core=self._core, oef_addr=self.oef_addr, oef_port=self.oef_port ) self.agent.on_connect_success = self.on_connect_ok self.agent.on_connection_terminated = self.on_connect_terminated self.agent.on_connect_failed = self.exception_handler def exception_handler(self, url=None, ex=None): """Handle exception during a connection attempt.""" print("An error occurred. Exception: {}".format(ex)) self._stop = True def on_connect_ok(self, url=None): """Handle a successful connection.""" print("Connection OK!") self._result = True self._stop = True def on_connect_terminated(self, url=None): """Handle a connection failure.""" print("Connection terminated.") self._stop = True def run(self) -> bool: """ Run the check, asynchronously. :return: True if the check is successful, False otherwise. """ self._result = False self._stop = False def stop_connection_attempt(self): if self.agent.state == "connecting": self.agent.state = "failed" t = Timer(1.5, stop_connection_attempt, args=(self,)) try: print("Connecting to {}:{}...".format(self.oef_addr, self.oef_port)) self._core.run_threaded() t.start() self._result = self.agent.connect() self._stop = True if self._result: print("Connection established. Tearing down connection...") self.agent.disconnect() t.cancel() else: print("A problem occurred. Exiting...") return self._result except Exception as e: print(str(e)) return self._result finally: t.join() self.agent.stop() self.agent.disconnect() self._core.stop() class OEFSearchDockerImage(DockerImage): """Wrapper to OEF Search Docker image.""" def __init__(self, client: DockerClient, oef_addr: str, oef_port: int): """Initialize the OEF Search Docker image.""" super().__init__(client) self._oef_addr = oef_addr self._oef_port = oef_port @property def tag(self) -> str: """Get the image tag.""" return "fetchai/oef-search:0.7" def check_skip(self): """Check if the test should be skipped.""" super().check_skip() if sys.version_info < (3, 7): pytest.skip("Python version < 3.7 not supported by the OEF.") return def create(self) -> Container: """Create an instance of the OEF Search image.""" from tests.conftest import ROOT_DIR # pylint: disable self.pull_image() logger.info(ROOT_DIR + "/tests/common/oef_search_pluto_scripts") ports = { "20000/tcp": ("0.0.0.0", 20000), # nosec "30000/tcp": ("0.0.0.0", 30000), # nosec "{}/tcp".format(self._oef_port): ("0.0.0.0", self._oef_port), # nosec } volumes = { ROOT_DIR + "/tests/common/oef_search_pluto_scripts": { "bind": "/config", "mode": "rw", }, ROOT_DIR + "/data/oef-logs": {"bind": "/logs", "mode": "rw"}, } c = self._client.containers.run( self.tag, "/config/node_config.json", detach=True, ports=ports, volumes=volumes, ) return c def wait(self, max_attempts: int = 15, sleep_rate: float = 1.0) -> bool: """Wait until the image is up.""" success = False attempt = 0 while not success and attempt < max_attempts: attempt += 1 logger.info("Attempt {}...".format(attempt)) oef_healthcheck = OEFHealthCheck("127.0.0.1", 10000) result = oef_healthcheck.run() if result: success = True else: logger.info( "OEF not available yet - sleeping for {} second...".format( sleep_rate ) ) time.sleep(sleep_rate) return success class GanacheDockerImage(DockerImage): """Wrapper to Ganache Docker image.""" def __init__( self, client: DockerClient, addr: str, port: int, config: Optional[Dict] = None, gas_limit: int = 10000000000000, ): """ Initialize the Ganache Docker image. :param client: the Docker client. :param addr: the address. :param port: the port. :param config: optional configuration to command line. """ super().__init__(client) self._addr = addr self._port = port self._config = config or {} self._gas_limit = gas_limit @property def tag(self) -> str: """Get the image tag.""" return "trufflesuite/ganache-cli:latest" def _make_ports(self) -> Dict: """Make ports dictionary for Docker.""" return {f"{self._port}/tcp": ("0.0.0.0", self._port)} # nosec def _build_command(self) -> List[str]: """Build command.""" cmd = ["ganache-cli"] cmd += ["--gasLimit=" + str(self._gas_limit)] accounts_balances = self._config.get("accounts_balances", []) for account, balance in accounts_balances: cmd += [f"--account='{account},{balance}'"] return cmd def create(self) -> Container: """Create the container.""" cmd = self._build_command() container = self._client.containers.run( self.tag, command=cmd, detach=True, ports=self._make_ports() ) return container def wait(self, max_attempts: int = 15, sleep_rate: float = 1.0) -> bool: """Wait until the image is up.""" request = dict(jsonrpc=2.0, method="web3_clientVersion", params=[], id=1) for i in range(max_attempts): try: response = requests.post(f"{self._addr}:{self._port}", json=request) enforce(response.status_code == 200, "") return True except Exception: logger.info( "Attempt %s failed. Retrying in %s seconds...", i, sleep_rate ) time.sleep(sleep_rate) return False class SOEFDockerImage(DockerImage): """Wrapper to SOEF Docker image.""" PORT = 12002 SOEF_MOUNT_PATH = os.path.abspath(os.path.join(os.sep, "etc", "soef")) SOEF_CONFIG_FILE_NAME = "soef.conf" def __init__( self, client: DockerClient, addr: str, port: int = PORT, ): """ Initialize the SOEF Docker image. :param client: the Docker client. :param addr: the address. :param port: the port. """ super().__init__(client) self._addr = addr self._port = port @property def tag(self) -> str: """Get the image tag.""" return "gcr.io/fetch-ai-images/soef:9e78611" def _make_soef_config_file(self, tmpdirname) -> None: """Make a temporary soef_config file to setup and run the an soef instance.""" soef_config_lines = [ "# SIMPLE OEF CONFIGURATION FILE", "# Save as /etc/soef/soef.conf", "#", "# 27th May 2020", "# (Author Toby Simpson)", "#", "# Port we're listening on", "port 9000", "#", "# Our declared location", "latitude 52.205278", "longitude 0.119167", "#", "# Various API keys", "agent_registration_api_key TwiCIriSl0mLahw17pyqoA", "get_log_api_key TwigsriSl0mLahw48pyqoA", "get_agent_partial_list_api_key SnakesiSl0mLahw48pyqoA", "#", "# Start cold being 1 means 'do not load agents'", "start_cold 0", "#", "# End.", ] soef_config_file = os.path.join(tmpdirname, self.SOEF_CONFIG_FILE_NAME) with open(soef_config_file, "w") as file: file.writelines(line + "\n" for line in soef_config_lines) os.chmod(soef_config_file, 400) # nosec def _make_ports(self) -> Dict: """Make ports dictionary for Docker.""" return {"9000/tcp": ("0.0.0.0", self._port)} # nosec def create(self) -> Container: """Create the container.""" self.pull_image() with tempfile.TemporaryDirectory() as tmpdirname: self._make_soef_config_file(tmpdirname) volumes = {tmpdirname: {"bind": self.SOEF_MOUNT_PATH, "mode": "ro"}} container = self._client.containers.run( self.tag, detach=True, volumes=volumes, ports=self._make_ports() ) return container def wait(self, max_attempts: int = 15, sleep_rate: float = 1.0) -> bool: """Wait until the image is up.""" for i in range(max_attempts): try: response = requests.get(f"{self._addr}:{self._port}") enforce(response.status_code == 200, "") return True except Exception as e: logger.info( f"Attempt {i} failed. Retrying in {sleep_rate} seconds... exception {e}" ) time.sleep(sleep_rate) return False class FetchLedgerDockerImage(DockerImage): """Wrapper to Fetch ledger Docker image.""" PORTS = {1317: 1317, 26657: 26657} def __init__( self, client: DockerClient, addr: str, port: int, tag: str, config: Optional[Dict] = None, ): """ Initialize the Fetch ledger Docker image. :param client: the Docker client. :param addr: the address. :param port: the port. :param config: optional configuration to command line. """ super().__init__(client) self._addr = addr self._port = port self._image_tag = tag self._config = config or {} @property def tag(self) -> str: """Get the image tag.""" return self._image_tag def _make_entrypoint_file(self, tmpdirname) -> None: """Make a temporary entrypoint file to setup and run the test ledger node""" run_node_lines = ( "#!/usr/bin/env bash", # variables f'export VALIDATOR_KEY_NAME={self._config["genesis_account"]}', f'export VALIDATOR_MNEMONIC="{self._config["mnemonic"]}"', 'export PASSWORD="12345678"', f'export CHAIN_ID={self._config["chain_id"]}', f'export MONIKER={self._config["moniker"]}', f'export DENOM={self._config["denom"]}', # Add key '( echo "$VALIDATOR_MNEMONIC"; echo "$PASSWORD"; echo "$PASSWORD"; ) |fetchd keys add $VALIDATOR_KEY_NAME --recover', # Configure node "fetchd init --chain-id=$CHAIN_ID $MONIKER", 'echo "$PASSWORD" |fetchd add-genesis-account $(fetchd keys show $VALIDATOR_KEY_NAME -a) 100000000000000000000000$DENOM', 'echo "$PASSWORD" |fetchd gentx $VALIDATOR_KEY_NAME 10000000000000000000000$DENOM --chain-id $CHAIN_ID', "fetchd collect-gentxs", # Enable rest-api 'sed -i "s/stake/atestfet/" ~/.fetchd/config/genesis.json', 'sed -i "s/enable = false/enable = true/" ~/.fetchd/config/app.toml', 'sed -i "s/swagger = false/swagger = true/" ~/.fetchd/config/app.toml', "fetchd start", ) entrypoint_file = os.path.join(tmpdirname, "run-node.sh") with open(entrypoint_file, "w") as file: file.writelines(line + "\n" for line in run_node_lines) os.chmod(entrypoint_file, 300) # nosec def create(self) -> Container: """Create the container.""" self.pull_image() with tempfile.TemporaryDirectory() as tmpdirname: self._make_entrypoint_file(tmpdirname) mount_path = "/mnt" volumes = {tmpdirname: {"bind": mount_path, "mode": "rw"}} entrypoint = os.path.join(mount_path, "run-node.sh") container = self._client.containers.run( self.tag, detach=True, network="host", volumes=volumes, entrypoint=str(entrypoint), ports=self.PORTS, ) return container def wait(self, max_attempts: int = 15, sleep_rate: float = 1.0) -> bool: """Wait until the image is up.""" for i in range(max_attempts): try: url = f"{self._addr}:{self._port}/net_info?" response = requests.get(url) enforce(response.status_code == 200, "") return True except Exception: logger.info( "Attempt %s failed. Retrying in %s seconds...", i, sleep_rate ) time.sleep(sleep_rate) return False ================================================ FILE: tests/common/mocks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains mocking utils testing purposes.""" import re import unittest from contextlib import contextmanager from typing import Any, Generator from unittest.mock import MagicMock class AnyStringWith(str): """ Helper class to assert calls of mocked method with string arguments. It will use string inclusion as equality comparator. """ def __eq__(self, other): """Check equality.""" return self in other class RegexComparator(str): """ Helper class to assert calls of mocked method with string arguments. It will use regex matching as equality comparator. """ def __eq__(self, other): """Check equality.""" regex = re.compile(str(self), re.MULTILINE | re.DOTALL) s = str(other) return bool(regex.search(s)) @contextmanager def ctx_mock_Popen() -> Generator[Any, Any, Any]: """ Mock subprocess.Popen. Act as context manager. :return: mock object. """ return_value = MagicMock() return_value.communicate.return_value = (MagicMock(), MagicMock()) with unittest.mock.patch("subprocess.Popen", return_value=return_value) as mocked: yield mocked ================================================ FILE: tests/common/oef_search_pluto_scripts/launch.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the launch script for the oef.""" from __future__ import print_function import argparse import json import os import subprocess # nosec import sys def run(cmd): """Run.""" print(" ".join(cmd)) c = subprocess.Popen(cmd) # nosec c.wait() return c.returncode def error(*x): """Error.""" print("".join([str(xx) for xx in x]), file=sys.stderr) def fail(*x): """Fail.""" error(x) exit(1) def pull_image(run_sudo, img): """Pull the image.""" c = [] if run_sudo: c += ["sudo"] c += [ "docker", "pull", img, ] r = run(c) if r != 0: error("can't pull " + img) def parse_command(j): """Parse the command.""" cmd = [] used_keys = ["positional_args"] for key in j["positional_args"]: cmd.append(str(j[key])) used_keys.append(key) for key in j: if key in used_keys: continue cmd.extend(["--" + key, str(j[key])]) return cmd def launch_job(args, j): """Launch the job.""" img = j["image"] if "/" in img: pull_image(args.sudo, img) c = [] if args.sudo: c += ["sudo"] c += ["docker", "run"] if args.background: c += ["-d"] else: c += ["-it"] work_dir = os.path.abspath(os.path.dirname(__file__)) project_dir = os.path.abspath(os.path.join(work_dir, "..")) print("Work dir: ", work_dir) c += ["-v", work_dir + ":/config", "-v", project_dir + "/data/oef-logs:/logs"] for arg in j["params"]: c += map(lambda x: x.replace("$PWD", project_dir), arg) c += [img] cmd_config = j["cmd"].get(args.cmd, None) if not cmd_config: fail("Selected command {} not configured in config file!".format(args.cmd)) c.extend(parse_command(cmd_config)) extra_args = [a for a in args.rest if a != "--"] print("Extra arguments to search: ", extra_args) c += extra_args r = run(c) if r != 0: fail("can't launch " + img) def main(args): """Run the script.""" with open(args.config, "r") as f: config = json.load(f) launch_job(args, config) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-c", "--config", required=True, type=str, help="Publish the image to GCR" ) parser.add_argument( "--sudo", required=False, action="store_true", help="Run docker as root" ) parser.add_argument( "--background", required=False, action="store_true", help="Run image in background.", ) parser.add_argument( "--cmd", required=False, type=str, default="oef-search", help="The available commands are defined in the config file ('cmd' dictionary) ", ) parser.add_argument("rest", nargs=argparse.REMAINDER) main(parser.parse_args()) ================================================ FILE: tests/common/oef_search_pluto_scripts/launch_config.json ================================================ { "image": "fetchai/oef-search:0.7", "params": [ [ "--rm", "--cap-add", "sys_ptrace", "-p", "20000:20000", "-p", "10000:10000", "-p", "40000:40000", "-p", "7500:7500" ] ], "cmd": { "oef-search": { "positional_args": ["config_file"], "config_file": "/config/node_config.json" } } } ================================================ FILE: tests/common/oef_search_pluto_scripts/node_config.json ================================================ { "host": "127.0.0.1", "search_key": "London-search", "search_port": 20000, "search_broadcast_cache_lifetime_sec": 6, "director_port": 40000, "html_dir": "api/src/resources/website", "prometheus_api_path": "/metrics", "prometheus_log_file": "./fetch-logs/core-vars.txt", "search_prometheus_log_file": "./fetch-logs/search-vars.txt", "http_port": 7500, "log_dir": "./fetch-logs/", "search_log": "/logs/search.log", "core_log": "/logs/core.log", "ssl_certificate": "", "core": { "core_binary": "/oef-mt-core/bazel-bin/mt-core/main/src/cpp/app", "config": { "core_key": "CoreKey", "core_uri": "tcp://127.0.0.1:10000", "ws_uri": "", "ssl_uri": "ssl://127.0.0.1:15000", "white_list_file":"", "core_cert_pk_file": "/app/ssl/core.pem", "core_pubkey_file": "/app/ssl/core_pub.pem", "tmp_dh_file": "/app/ssl/dh2048.pem", "search_uri": "tcp://127.0.0.1:20000", "tasks_thread_count": 10, "comms_thread_count": 10, "karma_policy": { }, "prometheus_log_interval": 3, "prometheus_log_file": "/logs/core-vars.txt" } }, "search_peers": ["KEY:HOST:PORT"], "bootstrap": { "bootstrap-url": "", "network-name": "oeftestnet", "client-name": "oef-node", "client-version": "0.1.0", "remote-host": "$EXTERNAL_HOSTNAME", "remote-port": -1, "private-key": "", "user-token": "", "notify-period-in-sec": 180 }, "state": { "file": "/storage/state.json", "fields": ["bootstrap:private-key"] }, "search_config": { "search_prometheus_log_file": "/logs/search-vars.txt", "daps": { "network_search": { "class": "DapERNetwork", "config": { "module": "dap_e_r_network.src.python.DapERNetwork", "structure": { "locations": { } } } }, "geo_search": { "class": "DapGeo", "config": { "module": "dap_2d_geo.src.python.DapGeo", "structure": { "location": { "location.location": { "type": "location", "options": [ "plane", "os-grid" ] }, "latlon": { "latlon.location": { "type": "location", "options": [ ] } } } } } }, "address_registry": { "class": "AddressRegistry", "config": { "module": "dap_in_memory.src.python.AddressRegistry", "structure": { "address_registry_table": { "address_field": "address" } } } }, "in_memory_dap": { "class": "exe.InMemoryDap", "config": { "binary": "cpp_dap_in_memory/src/cpp/cpp_dap_in_memory_server", "host": "127.0.0.1", "port": 30000, "structure": { "value_table": { "field": "string" } } } }, "attrs": { "class": "DapAttributeStore", "config": { "module": "dap_attribute_store.src.python.DapAttributeStore", "structure": { }, "options": [ "lazy" ] } }, "uniquer": { "class": "DapUniquer", "config": { "module": "uniqer.src.python.DapUniquer", "structure": { } } }, "data_model_searcher": { "class": "SearchEngine", "config": { "module": "ai_search_engine.src.python.SearchEngine", "structure": { "data_model_table": { "data_model": "embedding" } } } } }, "attribute_tbfld_map": { "latlon": ["latlon", "latlon"], "NETWORK_ADDRESS": ["address_registry_table", "address_field"] } } } ================================================ FILE: tests/common/pexpect_popen.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Wrapper for Pexpect to use in tests.""" import os import platform import signal import sys import time from typing import List, Optional, Union from pexpect.exceptions import EOF, TIMEOUT # type: ignore from pexpect.popen_spawn import PopenSpawn # type: ignore from aea.helpers.base import send_control_c class PexpectWrapper(PopenSpawn): """Utility class to make aea cli test easier.""" def __init__(self, *args, **kwargs): """Init pexpect.spawn.""" if platform.system() != "Windows": kwargs["preexec_fn"] = os.setsid super().__init__(*args, **kwargs) def control_c(self) -> None: """Send control c to process started.""" time.sleep(0.1) # sometimes it's better to wait a bit send_control_c(self.proc, True) @property def returncode(self) -> Optional[Union[int, str]]: """Get return code of finished process.""" return self.proc.poll() # type: ignore def wait_to_complete(self, timeout: float = 5) -> None: """Wait process to complete. Terminate automatically after timeout. Set returncode to terminated if terminated. :param timeout: how many seconds wait process to finish before kill. """ if self.proc.poll() is not None: # type: ignore return start_time = time.time() while start_time + timeout > time.time() and self.proc.poll() is None: # type: ignore time.sleep(0.001) if self.proc.poll() is None: # type: ignore self.terminate(force=True) self.wait() self.exitstatus = "Terminated!" # type: ignore @classmethod def aea_cli(cls, args, **kwargs) -> "PexpectWrapper": """Start aea.cli. :param args: list of arguments for aea.cli. :return: PexpectWrapper """ return cls( [sys.executable, "-m", "aea.cli", "-v", "DEBUG", *args], env=os.environ.copy(), encoding="utf-8", logfile=sys.stdout, **kwargs, ) def expect_all( self, pattern_list: List[str], timeout: float = 10, strict: bool = True ) -> None: """ Wait for all patterns appear in process output. :param pattern_list: list of string to expect :param timeout: timeout in seconds :param strict: if non strict, it allows regular expression :return: None """ pattern_list = list(pattern_list) start_time = time.time() while pattern_list: time_spent = time.time() - start_time if time_spent > timeout: raise TIMEOUT(timeout) if strict: idx = self.expect_exact(pattern_list, timeout - time_spent) else: idx = self.expect(pattern_list, timeout - time_spent) pattern_list.pop(idx) def wait_eof(self, timeout: float = 10) -> None: """ Wait for EOF of the process. :param timeout: timeout in seconds :return: None """ self.expect(EOF, timeout=timeout) def terminate(self, *args, **kwargs) -> None: """Terminate process.""" if self.proc.poll() is None: self.kill(signal.SIGTERM) ================================================ FILE: tests/common/utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains some utils for testing purposes.""" import asyncio import filecmp import os import subprocess # nosec import sys import time from contextlib import contextmanager from functools import wraps from threading import Thread from typing import Any, Callable, List, Optional, Set, Tuple, Type, Union import pytest from aea.aea import AEA from aea.configurations.base import PublicId from aea.mail.base import Envelope from aea.protocols.base import Message from aea.skills.base import Behaviour, Handler from packages.fetchai.protocols.default.message import DefaultMessage from tests.conftest import ROOT_DIR DEFAULT_SLEEP = 0.0001 DEFAULT_TIMEOUT = 3 class TimeItResult: """Class to store execution time for timeit_context.""" def __init__(self): """Init with time passed = -1.""" self.time_passed = -1 @contextmanager def timeit_context(): """ Context manager to measure execution time of code in context. :return TimeItResult example: with timeit_context() as result: do_long_code() print("Long code takes ", result.time_passed) """ result = TimeItResult() started_time = time.time() try: yield result finally: result.time_passed = time.time() - started_time class AeaTool: """ AEA test wrapper tool. To make testing AEA instances easier """ def __init__(self, aea: AEA): """ Instantiate AeaTool. :param aea: AEA instance to wrap for tests. """ self.aea = aea def setup(self) -> "AeaTool": """Call AEA._start_setup.""" self.aea.setup() return self def teardown(self) -> "AeaTool": """Call AEA.teardown.""" self.aea.teardown() return self def wait_outbox_empty( self, sleep: float = DEFAULT_SLEEP, timeout: float = DEFAULT_TIMEOUT ) -> "AeaTool": """ Wait till agent's outbox consumed completely. :return: AeaTool """ start_time = time.time() while not self.aea.outbox.empty(): time.sleep(sleep) if time.time() - start_time > timeout: raise Exception("timeout") return self def wait_inbox( self, sleep: float = DEFAULT_SLEEP, timeout: float = DEFAULT_TIMEOUT ) -> "AeaTool": """ Wait till something appears on agents inbox and spin loop. :return: AeaTool """ start_time = time.time() while self.aea.inbox.empty(): time.sleep(sleep) if time.time() - start_time > timeout: raise Exception("timeout") return self def handle_envelope(self, envelope) -> "AeaTool": """ Run AEA.react once to process inbox messages. :return: AeaTool """ self.aea.handle_envelope(envelope) return self def act_one(self) -> "AeaTool": """ Run AEA.act once to process behaviours act. :return: AeaTool """ self.aea.act() return self @classmethod def dummy_default_message( cls, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, performative: DefaultMessage.Performative = DefaultMessage.Performative.BYTES, content: Union[str, bytes] = "hello world!", ) -> Message: """ Construct simple message, all arguments are optional. :return: Message """ if isinstance(content, str): content = content.encode("utf-8") return DefaultMessage( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=performative, content=content, ) @classmethod def dummy_envelope( cls, to: str = "test", sender: str = "test", protocol_specification_id: PublicId = DefaultMessage.protocol_specification_id, message: Message = None, ) -> Envelope: """ Create envelope, if message is not passed use .dummy_message method. :return: Envelope """ message = message or cls.dummy_default_message() message.sender = sender message.to = to return Envelope( to=to, sender=sender, protocol_specification_id=protocol_specification_id, message=message, ) def put_inbox(self, envelope: Envelope) -> None: """Add an envelope to agent's inbox.""" self.aea.runtime.multiplexer.in_queue.put(envelope) def is_inbox_empty(self) -> bool: """Check there is no messages in inbox.""" return self.aea.runtime.multiplexer.in_queue.empty() def set_execution_timeout(self, timeout: float) -> None: """Set act/handle exeution timeout for AEE. :param timeout: amount of time to limit single act/handle to execute. """ self.aea._execution_timeout = timeout def stop(self) -> None: """Stop AEA instance.""" self.aea.stop() def make_handler_cls_from_funcion(func: Callable) -> Type[Handler]: """Make Handler class with handler function call `func`. :param func: function or callable to be called from Handler.handle method :return: Handler class """ # pydocstyle: ignore # case conflicts with black # noqa: E800 class TestHandler(Handler): SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self): pass def teardown(self): pass def handle(self, msg): func(self) return TestHandler def make_behaviour_cls_from_funcion(func: Callable) -> Type[Behaviour]: """Make Behaviour class with act function call `func`. :param func: function or callable to be called from Behaviour.act method :return: Behaviour class """ # pydocstyle: ignore # case conflicts with black # noqa: E800 class TestBehaviour(Behaviour): def act(self) -> None: func(self) def setup(self): self._completed = False def teardown(self): pass return TestBehaviour def run_in_root_dir(fn) -> Callable: """ Chdir to ROOT DIR and return back during tests. Decorator. :param fn: function to decorate :return: wrapped function """ # pydocstyle: ignore # case conflicts with black # noqa: E800 @wraps(fn) def wrap(*args, **kwargs) -> Any: """Do a chdir.""" cwd = os.getcwd() os.chdir(ROOT_DIR) try: return fn(*args, **kwargs) finally: os.chdir(cwd) return wrap @contextmanager def run_in_thread(fn, timeout=10, on_exit=None, **kwargs): """Run a function in contextmanager and test and awaits it completed.""" thread = Thread(target=fn, **kwargs) thread.daemon = True thread.start() try: yield finally: if on_exit: on_exit() thread.join(timeout) if thread.is_alive(): raise Exception("Thread was not stopped!") def wait_for_condition(condition_checker, timeout=2, error_msg="Timeout", period=0.001): """Wait for condition occures in selected timeout.""" start_time = time.time() while not condition_checker(): time.sleep(period) if time.time() > start_time + timeout: raise TimeoutError(error_msg) async def wait_for_condition_async( condition_checker, timeout=2, error_msg="Timeout", period=0.001 ): # pragma: nocover """Wait for condition occures in selected timeout.""" start_time = time.time() while not condition_checker(): await asyncio.sleep(period) if time.time() > start_time + timeout: raise TimeoutError(error_msg) def are_dirs_equal( dir1: Union[str, os.PathLike], dir2: Union[str, os.PathLike], ignore: Optional[List[str]] = None, ) -> bool: """ Compare the content of two directories, recursively. :param dir1: the left operand. :param dir2: the right operand. :param ignore: is a list of names to ignore (see dircmp docs regarding 'ignore'). :return: True if the directories are equal, False otherwise. """ ignore = ignore or None left_only, right_only, diff = dircmp_recursive( filecmp.dircmp(dir1, dir2, ignore=ignore) ) return left_only == right_only == diff == set() def dircmp_recursive(dircmp_obj: filecmp.dircmp) -> Tuple[Set[str], Set[str], Set[str]]: """ Compare the content of two directories, recursively. :param dircmp_obj: the filecmp.dircmp object. :return: three sets: - the set of files that are only in the left operand - the set of files that are only in the right operand - the set of files in both operands, but that differ. """ def _dircmp_recursive( dircmp_obj: filecmp.dircmp, prefix: str = "" ) -> Tuple[Set[str], Set[str], Set[str]]: """ Helper private function that also accepts the 'prefix' parameter. It is used to keep track of the path prefix during the recursive calls. """ def join_with_prefix(suffix: str) -> str: return os.path.join(prefix, suffix) left_only: Set[str] = set(map(join_with_prefix, dircmp_obj.left_only)) right_only: Set[str] = set(map(join_with_prefix, dircmp_obj.right_only)) diff_files: Set[str] = set(map(join_with_prefix, dircmp_obj.diff_files)) for name, sub_dircmp_obj in dircmp_obj.subdirs.items(): subprefix = join_with_prefix(name) subleft, subright, subdiff = _dircmp_recursive( sub_dircmp_obj, prefix=subprefix ) left_only.update(subleft) right_only.update(subright) diff_files.update(subdiff) return left_only, right_only, diff_files return _dircmp_recursive(dircmp_obj, "") def run_aea_subprocess(*args, cwd: str = ".") -> Tuple[subprocess.Popen, str, str]: """ Run subprocess, bypassing ClickRunner.invoke. The reason is that for some reason ClickRunner.invoke doesn't capture well the stdout/stderr of nephew processes - children processes of children processes. """ result = subprocess.Popen( # type: ignore # nosec [sys.executable, "-m", "aea.cli", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, ) result.wait() stdout, stderr = result.communicate() return result, stdout.decode("utf-8"), stderr.decode("utf-8") @pytest.mark.integration class UseOef: # pylint: disable=too-few-public-methods """Inherit from this class to launch an OEF node.""" @pytest.fixture(autouse=True) def _start_oef_node(self, network_node: Callable) -> None: """Start an oef node.""" ================================================ FILE: tests/conftest.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Conftest module for Pytest.""" import difflib import inspect import logging import os import platform import random import shutil import socket import string import sys import tempfile import threading import time from contextlib import contextmanager from functools import WRAPPER_ASSIGNMENTS, wraps from pathlib import Path from types import FunctionType, MethodType from typing import ( Callable, Dict, Generator, List, Optional, Sequence, Tuple, Union, cast, ) from unittest.mock import MagicMock, patch import docker as docker import gym import pytest import pytest_asyncio from _pytest.monkeypatch import MonkeyPatch # type: ignore from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAIApi, FetchAICrypto, FetchAIFaucetApi from cosmpy.aerial.client import LedgerClient, NetworkConfig from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.address import Address as CosmpyAddress from cosmpy.crypto.keypairs import PrivateKey from aea import AEA_DIR from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.cli.utils.config import _init_cli_config from aea.common import Address from aea.configurations.base import ComponentType, ConnectionConfig, ContractConfig from aea.configurations.base import DEFAULT_AEA_CONFIG_FILE as AGENT_YAML from aea.configurations.base import DEFAULT_CONNECTION_CONFIG_FILE as CONNECTION_YAML from aea.configurations.base import DEFAULT_CONTRACT_CONFIG_FILE as CONTRACT_YAML from aea.configurations.base import DEFAULT_PROTOCOL_CONFIG_FILE as PROTOCOL_YAML from aea.configurations.base import DEFAULT_SKILL_CONFIG_FILE as SKILL_YAML from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER, PRIVATE_KEY_PATH_SCHEMA from aea.configurations.loader import load_component_configuration from aea.connections.base import Connection from aea.contracts.base import Contract, contract_registry from aea.crypto.base import Crypto from aea.crypto.ledger_apis import ( COSMOS_DEFAULT_ADDRESS, DEFAULT_LEDGER_CONFIGS, ETHEREUM_DEFAULT_ADDRESS, ETHEREUM_DEFAULT_CHAIN_ID, ETHEREUM_DEFAULT_CURRENCY_DENOM, FETCHAI_DEFAULT_ADDRESS, ) from aea.crypto.registries import ledger_apis_registry, make_crypto, make_ledger_api from aea.crypto.wallet import CryptoStore from aea.exceptions import enforce from aea.helpers.base import CertRequest, SimpleId, cd from aea.identity.base import Identity from aea.test_tools.click_testing import CliRunner as ImportedCliRunner from aea.test_tools.constants import DEFAULT_AUTHOR from aea.test_tools.test_cases import BaseAEATestCase from packages.fetchai.connections.local.connection import LocalNode, OEFLocalConnection from packages.fetchai.connections.oef.connection import OEFConnection from packages.fetchai.connections.p2p_libp2p.check_dependencies import build_node from packages.fetchai.connections.p2p_libp2p.connection import ( LIBP2P_NODE_MODULE_NAME, MultiAddr, P2PLibp2pConnection, POR_DEFAULT_SERVICE_ID, ) from packages.fetchai.connections.p2p_libp2p_client.connection import ( P2PLibp2pClientConnection, ) from packages.fetchai.connections.p2p_libp2p_mailbox.connection import ( P2PLibp2pMailboxConnection, ) from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.connections.tcp.tcp_client import TCPClientConnection from packages.fetchai.connections.tcp.tcp_server import TCPServerConnection from tests.common.docker_image import ( DockerImage, FetchLedgerDockerImage, GanacheDockerImage, OEFSearchDockerImage, SOEFDockerImage, ) from tests.data.dummy_connection.connection import DummyConnection # type: ignore logger = logging.getLogger(__name__) CliRunner = ImportedCliRunner CUR_PATH = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore ROOT_DIR = os.path.join(CUR_PATH, "..") CLI_LOG_OPTION = ["-v", "OFF"] AUTHOR = DEFAULT_AUTHOR CONFIGURATION_SCHEMA_DIR = os.path.join(AEA_DIR, "configurations", "schemas") AGENT_CONFIGURATION_SCHEMA = os.path.join( CONFIGURATION_SCHEMA_DIR, "aea-config_schema.json" ) SKILL_CONFIGURATION_SCHEMA = os.path.join( CONFIGURATION_SCHEMA_DIR, "skill-config_schema.json" ) CONNECTION_CONFIGURATION_SCHEMA = os.path.join( CONFIGURATION_SCHEMA_DIR, "connection-config_schema.json" ) PROTOCOL_CONFIGURATION_SCHEMA = os.path.join( CONFIGURATION_SCHEMA_DIR, "protocol-config_schema.json" ) CONTRACT_CONFIGURATION_SCHEMA = os.path.join( CONFIGURATION_SCHEMA_DIR, "contract-config_schema.json" ) PROTOCOL_SPEC_CONFIGURATION_SCHEMA = os.path.join( CONFIGURATION_SCHEMA_DIR, "protocol-specification_schema.json" ) DUMMY_ENV = gym.GoalEnv # type: ignore # URL to local Ganache instance DEFAULT_GANACHE_ADDR = "http://127.0.0.1" DEFAULT_GANACHE_PORT = 8545 DEFAULT_GANACHE_CHAIN_ID = 1337 # URL to local Fetch ledger instance DEFAULT_FETCH_DOCKER_IMAGE_TAG = "fetchai/fetchd:0.10.2" DEFAULT_FETCH_LEDGER_ADDR = "http://127.0.0.1" DEFAULT_FETCH_LEDGER_RPC_PORT = 26657 DEFAULT_FETCH_LEDGER_REST_PORT = 1317 DEFAULT_FETCH_ADDR_REMOTE = "https://rest-dorado.fetch.ai:443" DEFAULT_FETCH_MNEMONIC = "gap bomb bulk border original scare assault pelican resemble found laptop skin gesture height inflict clinic reject giggle hurdle bubble soldier hurt moon hint" DEFAULT_MONIKER = "test-node" DEFAULT_FETCH_CHAIN_ID = "dorado-1" DEFAULT_GENESIS_ACCOUNT = "validator" DEFAULT_DENOMINATION = "atestfet" FETCHD_INITIAL_TX_SLEEP = 6 COSMOS_PRIVATE_KEY_FILE_CONNECTION = "cosmos_connection_private_key.txt" FETCHAI_PRIVATE_KEY_FILE_CONNECTION = "fetchai_connection_private_key.txt" COSMOS_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(CosmosCrypto.identifier) ETHEREUM_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(EthereumCrypto.identifier) FETCHAI_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(FetchAICrypto.identifier) ETHEREUM_PRIVATE_KEY_TWO_FILE = "ethereum_private_key_two.txt" DEFAULT_AMOUNT = 1000000000000000000000 GAS_PRICE_API_KEY = "" # private keys with value on testnet COSMOS_PRIVATE_KEY_PATH = os.path.join( ROOT_DIR, "tests", "data", COSMOS_PRIVATE_KEY_FILE ) ETHEREUM_PRIVATE_KEY_PATH = os.path.join( ROOT_DIR, "tests", "data", ETHEREUM_PRIVATE_KEY_FILE ) ETHEREUM_PRIVATE_KEY_TWO_PATH = os.path.join( ROOT_DIR, "tests", "data", ETHEREUM_PRIVATE_KEY_TWO_FILE ) FETCHAI_PRIVATE_KEY_PATH = os.path.join( ROOT_DIR, "tests", "data", FETCHAI_PRIVATE_KEY_FILE ) DEFAULT_PRIVATE_KEY_PATH = COSMOS_PRIVATE_KEY_PATH FUNDED_ETH_PRIVATE_KEY_1 = ( "0xa337a9149b4e1eafd6c21c421254cf7f98130233595db25f0f6f0a545fb08883" ) FUNDED_ETH_PRIVATE_KEY_2 = ( "0x04b4cecf78288f2ab09d1b4c60219556928f86220f0fb2dcfc05e6a1c1149dbf" ) FUNDED_ETH_PRIVATE_KEY_3 = ( "0x6F611408F7EF304947621C51A4B7D84A13A2B9786E9F984DA790A096E8260C64" ) NON_FUNDED_COSMOS_PRIVATE_KEY_1 = ( "81b0352f99a08a754b56e529dda965c4ce974edb6db7e90035e01ed193e1b7bc" ) NON_FUNDED_FETCHAI_PRIVATE_KEY_1 = ( "b6ef49c3078f300efe2d4480e179362bd39f20cbb2087e970c8f345473661aa5" ) FUNDED_FETCHAI_PRIVATE_KEY_1 = ( "bbaef7511f275dc15f47436d14d6d3c92d4d01befea073d23d0c2750a46f6cb3" ) FUNDED_FETCHAI_PRIVATE_KEY_2 = ( "9d6459d1f93dd153335291af940f6b5224a34a9a1e1062e2158a45fa4901ed3f" ) # addresses with no value on testnet COSMOS_ADDRESS_ONE = "cosmos1z4ftvuae5pe09jy2r7udmk6ftnmx504alwd5qf" COSMOS_ADDRESS_TWO = "cosmos1gssy8pmjdx8v4reg7lswvfktsaucp0w95nk78m" ETHEREUM_ADDRESS_ONE = "0x46F415F7BF30f4227F98def9d2B22ff62738fD68" ETHEREUM_ADDRESS_TWO = "0x7A1236d5195e31f1F573AD618b2b6FEFC85C5Ce6" FETCHAI_ADDRESS_ONE = "fetch1paqxtqnfh7da7z9c05l3y3lahe8rhd0nm0jk98" FETCHAI_ADDRESS_TWO = "fetch19j4dc3e6fgle98pj06l5ehhj6zdejcddx7teac" FUNDED_FETCHAI_ADDRESS_ONE = "fetch1k9dns2fd74644g0q9mfpsmfeqg0h2ym2cm6wdh" FUNDED_FETCHAI_ADDRESS_TWO = "fetch1x2vfp8ec2yk8nnlzn52agflpmpwtucm6yj2hw4" # P2P addresses COSMOS_P2P_ADDRESS = "/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmAzvu5uNbcnD2qaqrkSULhJsc6GJUg3iikWerJkoD72pr" # relates to NON_FUNDED_COSMOS_PRIVATE_KEY_1 FETCHAI_P2P_ADDRESS = "/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmLBCAqHL8SuFosyDhAKYsLKXBZBWXBsB9oFw2qU4Kckun" # relates to NON_FUNDED_FETCHAI_PRIVATE_KEY_1 NON_GENESIS_CONFIG = { "delegate_uri": "127.0.0.1:11001", "entry_peers": [FETCHAI_P2P_ADDRESS], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001", "ledger_id": "fetchai", } NON_GENESIS_CONFIG_TWO = { "delegate_uri": "127.0.0.1:11002", "entry_peers": [FETCHAI_P2P_ADDRESS], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002", "ledger_id": "fetchai", } PUBLIC_DHT_P2P_MADDR_1 = "/dns4/acn.fetch.ai/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx" PUBLIC_DHT_P2P_MADDR_2 = "/dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW" PUBLIC_DHT_DELEGATE_URI_1 = "acn.fetch.ai:11000" PUBLIC_DHT_DELEGATE_URI_2 = "acn.fetch.ai:11001" PUBLIC_DHT_P2P_PUBLIC_KEY_1 = ( "0217a59bd805c310aca4febe0e99ce22ee3712ae085dc1e5630430b1e15a584bb7" ) PUBLIC_DHT_P2P_PUBLIC_KEY_2 = ( "03fa7cfae1037cba5218f0f5743802eced8de3247c55ecebaae46c7d3679e3f91d" ) PUBLIC_STAGING_DHT_P2P_MADDR_1 = "/dns4/acn.fetch-ai.com/tcp/9003/p2p/16Uiu2HAmQo6EHbmwhkMJkyhjz1DCxE8Ahsy5zFZtw97tWCFckLUp" PUBLIC_STAGING_DHT_P2P_MADDR_2 = "/dns4/acn.fetch-ai.com/tcp/9004/p2p/16Uiu2HAmEvey5siPHzdEb5QcTYCkh16squbeFHYHvRCWP9Jzp4bV" PUBLIC_STAGING_DHT_DELEGATE_URI_1 = "acn.fetch-ai.com:11003" PUBLIC_STAGING_DHT_DELEGATE_URI_2 = "acn.fetch-ai.com:11004" PUBLIC_STAGING_DHT_P2P_PUBLIC_KEY_1 = ( "03b45f898bde437ace4728b3ba097988306930b1600b7991d384e6d08452e340e1" ) PUBLIC_STAGING_DHT_P2P_PUBLIC_KEY_2 = ( "0321bac023b7f7cf655cf5e0f988a4c1cf758f7b530528362c4ba8d563f7b090c4" ) # testnets COSMOS_TESTNET_CONFIG = {"address": COSMOS_DEFAULT_ADDRESS} ETHEREUM_TESTNET_CONFIG = { "address": ETHEREUM_DEFAULT_ADDRESS, "chain_id": ETHEREUM_DEFAULT_CHAIN_ID, "gas_price": 50, } FETCHAI_TESTNET_CONFIG = {"address": FETCHAI_DEFAULT_ADDRESS} # common public ids used in the tests UNKNOWN_PROTOCOL_PUBLIC_ID = PublicId("unknown_author", "unknown_protocol", "0.1.0") UNKNOWN_CONNECTION_PUBLIC_ID = PublicId("unknown_author", "unknown_connection", "0.1.0") MY_FIRST_AEA_PUBLIC_ID = PublicId.from_str("fetchai/my_first_aea:0.28.5") DUMMY_SKILL_PATH = os.path.join(CUR_PATH, "data", "dummy_skill", SKILL_YAML) MAX_FLAKY_RERUNS = 3 MAX_FLAKY_RERUNS_ETH = 1 MAX_FLAKY_RERUNS_INTEGRATION = 1 PACKAGES_DIR = os.path.join(ROOT_DIR, "packages") FETCHAI_PREF = os.path.join(ROOT_DIR, "packages", "fetchai") PROTOCOL_SPECS_PREF_1 = os.path.join(ROOT_DIR, "examples", "protocol_specification_ex") PROTOCOL_SPECS_PREF_2 = os.path.join(ROOT_DIR, "tests", "data") contract_config_files = [ os.path.join(FETCHAI_PREF, "contracts", "erc1155", CONTRACT_YAML), os.path.join(FETCHAI_PREF, "contracts", "staking_erc20", CONTRACT_YAML), os.path.join(ROOT_DIR, "tests", "data", "dummy_contract", CONTRACT_YAML), ] protocol_config_files = [ os.path.join(ROOT_DIR, "aea", "protocols", "scaffold", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "contract_api", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "default", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "fipa", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "gym", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "http", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "ledger_api", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "ml_trade", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "oef_search", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "register", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "signing", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "state_update", PROTOCOL_YAML), os.path.join(FETCHAI_PREF, "protocols", "tac", PROTOCOL_YAML), os.path.join(CUR_PATH, "data", "dummy_protocol", PROTOCOL_YAML), ] connection_config_files = [ os.path.join(ROOT_DIR, "aea", "connections", "scaffold", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "gym", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "http_client", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "http_server", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "ledger", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "local", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "oef", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "p2p_libp2p", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "p2p_libp2p_client", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "p2p_stub", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "soef", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "stub", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "tcp", CONNECTION_YAML), os.path.join(FETCHAI_PREF, "connections", "webhook", CONNECTION_YAML), os.path.join(CUR_PATH, "data", "dummy_connection", CONNECTION_YAML), os.path.join(CUR_PATH, "data", "gym-connection.yaml"), ] skill_config_files = [ os.path.join(ROOT_DIR, "aea", "skills", "scaffold", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "aries_alice", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "aries_faber", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "carpark_client", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "carpark_detection", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "confirmation_aw1", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "echo", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "erc1155_client", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "erc1155_deploy", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "error", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "generic_buyer", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "generic_seller", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "gym", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "http_echo", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "ml_data_provider", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "ml_train", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "registration_aw1", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "simple_service_registration", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "simple_service_search", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "tac_control", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "tac_control_contract", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "tac_negotiation", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "tac_participation", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "thermometer", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "thermometer_client", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "weather_client", SKILL_YAML), os.path.join(FETCHAI_PREF, "skills", "weather_station", SKILL_YAML), DUMMY_SKILL_PATH, os.path.join(CUR_PATH, "data", "dummy_aea", "skills", "dummy", SKILL_YAML), os.path.join(CUR_PATH, "data", "dependencies_skill", SKILL_YAML), os.path.join(CUR_PATH, "data", "exception_skill", SKILL_YAML), ] agent_config_files = [ os.path.join(CUR_PATH, "data", "dummy_aea", AGENT_YAML), os.path.join(CUR_PATH, "data", "aea-config.example.yaml"), os.path.join(CUR_PATH, "data", "aea-config.example_w_keys.yaml"), os.path.join(CUR_PATH, "data", "aea-config.example_multipage.yaml"), os.path.join(FETCHAI_PREF, "agents", "aries_alice", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "aries_faber", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "car_data_buyer", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "car_detector", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "confirmation_aea_aw1", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "erc1155_client", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "erc1155_deployer", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "generic_buyer", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "generic_seller", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "gym_aea", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "ml_data_provider", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "ml_model_trainer", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "my_first_aea", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "registration_aea_aw1", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "simple_service_registration", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "tac_controller", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "tac_controller_contract", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "tac_participant", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "tac_participant_contract", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "thermometer_aea", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "thermometer_client", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "weather_client", AGENT_YAML), os.path.join(FETCHAI_PREF, "agents", "weather_station", AGENT_YAML), ] protocol_specification_files = [ os.path.join( PROTOCOL_SPECS_PREF_1, "sample.yaml", ), os.path.join( PROTOCOL_SPECS_PREF_2, "sample_specification.yaml", ), os.path.join( PROTOCOL_SPECS_PREF_2, "sample_specification_no_custom_types.yaml", ), ] @contextmanager def project_root_pythonpath(): """Set pythonpath to project root.""" old_python_path = os.environ.get("PYTHONPATH", None) os.environ["PYTHONPATH"] = ":".join( filter(None, [os.path.abspath(ROOT_DIR), old_python_path]) ) yield if old_python_path is None: os.environ.pop("PYTHONPATH") else: os.environ["PYTHONPATH"] = old_python_path def match_files(fname1: str, fname2: str) -> Tuple[bool, str]: """ Find out whether two text files match. :param fname1: string path to file 1 :param fname2: string path to file 2 :return: whether files match (True) or not (False) and a string of their difference ("" if they match) """ with open(fname1, "r") as f1, open(fname2, "r") as f2: difference = set(f1).difference(f2) are_identical = difference == set() diff = "" if not are_identical: diff = find_difference(fname1, fname2) return are_identical, diff def find_difference(fname1: str, fname2: str) -> str: """Find the difference between two text files.""" diff = "" with open(fname1) as f1, open(fname2) as f2: differ = difflib.Differ() for line in differ.compare(f1.readlines(), f2.readlines()): if not (line.startswith(" ") or line.startswith("? ")): line = line[2:].lstrip() diff += line return diff def number_of_diff_lines(diff: str) -> int: """Give number of lines in a diff string.""" return diff.count("\n") if diff != "" else 0 def only_windows(fn: Callable) -> Callable: """ Decorate a pytest method to run a test only in a case we are on Windows. :return: decorated method. """ return action_for_platform("Windows", skip=False)(fn) def skip_test_windows(fn: Callable) -> Callable: """ Decorate a pytest method to skip a test in a case we are on Windows. :return: decorated method. """ return action_for_platform("Windows", skip=True)(fn) def skip_test_macos(fn: Callable) -> Callable: """ Decorate a pytest method to skip a test in a case we are on MacOS. :return: decorated method. """ return action_for_platform("Darwin", skip=True)(fn) def action_for_platform(platform_name: str, skip: bool = True) -> Callable: """ Decorate a pytest class or method to skip on certain platform. :param platform_name: check `platform.system()` for available platforms. :param skip: if True, the test will be skipped; if False, the test will be run ONLY on the chosen platform. :return: decorated object """ # for docstyle. def decorator(pytest_func): """ For the sake of clarity, assume the chosen platform for the action is "Windows". If the following condition is true: - the current system is not Windows (is_different) AND we want to skip it (skip) OR - the current system is Windows (not is_different) AND we want to run only on it (not skip) we run the test, else we skip the test. logically, the condition is a boolean equivalence between the variables "is_different" and "skip" Hence, the condition becomes: """ is_different = platform.system() != platform_name if is_different is skip: return pytest_func def action(*args, **kwargs): if skip: pytest.skip( f"Skipping the test since it doesn't work on {platform_name}." ) else: pytest.skip( f"Skipping the test since it works only on {platform_name}." ) if isinstance(pytest_func, type): return type( pytest_func.__name__, (pytest_func,), { "setup_class": action, "setup": action, "setUp": action, "_skipped": True, }, ) @wraps(pytest_func) def wrapper(*args, **kwargs): # type: ignore action(*args, **kwargs) return wrapper return decorator @pytest.fixture(scope="session") def oef_addr() -> str: """IP address pointing to the OEF Node to use during the tests.""" return "127.0.0.1" @pytest.fixture(scope="session") def oef_port() -> int: """Port of the connection to the OEF Node to use during the tests.""" return 10000 @pytest.fixture(scope="session") def ganache_addr() -> str: """HTTP address to the Ganache node.""" return DEFAULT_GANACHE_ADDR @pytest.fixture(scope="session") def ganache_port() -> int: """Port of the connection to the Ganache Node to use during the tests.""" return DEFAULT_GANACHE_PORT def tcpping(ip, port, log_exception: bool = True) -> bool: """Ping TCP port.""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((ip, int(port))) s.shutdown(2) return True except Exception as e: if log_exception: logger.exception(e) return False def wait_for_localhost_ports_to_close( ports: List[int], timeout: int = 120, sleep_time: int = 2 ) -> None: """Wait for ports to close with timeout.""" open_ports = ports elapsed = 0 while len(open_ports) > 0 and elapsed < timeout: closed = [] for port in open_ports: if not tcpping("127.0.0.1", port, log_exception=False): closed.append(port) open_ports = [port for port in open_ports if port not in closed] if len(open_ports) > 0: time.sleep(sleep_time) elapsed += sleep_time if open_ports != []: raise ValueError("Some ports are open: {}!".format(open_ports)) def pytest_addoption(parser) -> None: """Add --aea-loop option.""" parser.addoption( "--aea-loop", action="store", default="async", help="aea loop to use: async[default] or sync", ) # disable inernet connection parser.addoption( "--no-inet", action="store_true", default=False, help="block socket connect outside of 127.x.x.x", ) parser.addoption( "--check-threads", action="store_true", default=False, help="check non closed threads i started during test", ) @pytest.fixture(scope="session", autouse=True) def inet_disable(request) -> None: """Disable internet access via socket.""" if not request.config.getoption("--no-inet"): return orig_connect = socket.socket.connect def socket_connect(*args): host = args[1][0] if host == "localhost" or host.startswith("127."): return orig_connect(*args) raise socket.error("Internet disabled by pytest option --no-inet") p = patch.object(socket.socket, "connect", new=socket_connect) p.start() @pytest.fixture(scope="session", autouse=True) def increase_aea_builder_build_timeout(request) -> Generator: """Increase build timeout for aea builder.""" old_timeout = AEABuilder.BUILD_TIMEOUT AEABuilder.BUILD_TIMEOUT = 420 try: yield finally: AEABuilder.BUILD_TIMEOUT = old_timeout @pytest.fixture(scope="session", autouse=True) def apply_aea_loop(request) -> None: """Patch AEA.DEFAULT_RUN_LOOP using pytest option `--aea-loop`.""" loop = request.config.getoption("--aea-loop") assert loop in AEA.RUN_LOOPS AEA.DEFAULT_RUN_LOOP = loop @pytest.fixture(scope="session") def network_node( oef_addr, oef_port, pytestconfig, timeout: float = 2.0, max_attempts: int = 10 ): """Network node initialization.""" client = docker.from_env() image = OEFSearchDockerImage(client, oef_addr, oef_port) yield from _launch_image(image, timeout, max_attempts) @pytest.fixture(scope="session") def ganache_configuration(): """Get the Ganache configuration for testing purposes.""" return dict( accounts_balances=[ (FUNDED_ETH_PRIVATE_KEY_1, DEFAULT_AMOUNT), (FUNDED_ETH_PRIVATE_KEY_2, DEFAULT_AMOUNT), (FUNDED_ETH_PRIVATE_KEY_3, DEFAULT_AMOUNT), (Path(ETHEREUM_PRIVATE_KEY_PATH).read_text().strip(), DEFAULT_AMOUNT), ], ) @pytest.fixture(scope="session") def fetchd_configuration(): """Get the Fetch ledger configuration for testing purposes.""" return dict( mnemonic=DEFAULT_FETCH_MNEMONIC, moniker=DEFAULT_MONIKER, chain_id=DEFAULT_FETCH_CHAIN_ID, genesis_account=DEFAULT_GENESIS_ACCOUNT, denom=DEFAULT_DENOMINATION, ) @pytest.fixture(scope="session") def ethereum_testnet_config(ganache_addr, ganache_port): """Get Ethereum ledger api configurations using Ganache.""" new_uri = f"{ganache_addr}:{ganache_port}" new_config = { "address": new_uri, "chain_id": DEFAULT_GANACHE_CHAIN_ID, "denom": ETHEREUM_DEFAULT_CURRENCY_DENOM, "gas_price_api_key": GAS_PRICE_API_KEY, } return new_config @pytest.fixture(scope="function") def update_default_ethereum_ledger_api(ethereum_testnet_config): """Change temporarily default Ethereum ledger api configurations to interact with local Ganache.""" old_config = DEFAULT_LEDGER_CONFIGS.pop(EthereumCrypto.identifier, None) DEFAULT_LEDGER_CONFIGS[EthereumCrypto.identifier] = ethereum_testnet_config yield DEFAULT_LEDGER_CONFIGS.pop(EthereumCrypto.identifier) DEFAULT_LEDGER_CONFIGS[EthereumCrypto.identifier] = old_config @pytest.mark.integration @pytest.mark.ledger @pytest.fixture(scope="class") def ganache( ganache_configuration, ganache_addr, ganache_port, timeout: float = 2.0, max_attempts: int = 10, ): """Launch the Ganache image.""" client = docker.from_env() image = GanacheDockerImage( client, "http://127.0.0.1", 8545, config=ganache_configuration ) yield from _launch_image(image, timeout=timeout, max_attempts=max_attempts) @pytest.mark.integration @pytest.fixture(scope="class") def soef( soef_addr: str = "http://127.0.0.1", soef_port: int = 12002, timeout: float = 2.0, max_attempts: int = 50, ): """Launch the soef image.""" client = docker.from_env() image = SOEFDockerImage(client, soef_addr, soef_port) yield from _launch_image(image, timeout=timeout, max_attempts=max_attempts) @pytest.mark.integration @pytest.mark.ledger @pytest.fixture(scope="class") @action_for_platform("Linux", skip=False) def fetchd( fetchd_configuration, timeout: float = 2.0, max_attempts: int = 20, ): """Launch the Fetch ledger image.""" client = docker.from_env() image = FetchLedgerDockerImage( client, DEFAULT_FETCH_LEDGER_ADDR, DEFAULT_FETCH_LEDGER_RPC_PORT, DEFAULT_FETCH_DOCKER_IMAGE_TAG, config=fetchd_configuration, ) yield from _launch_image(image, timeout=timeout, max_attempts=max_attempts) def _launch_image(image: DockerImage, timeout: float = 2.0, max_attempts: int = 10): """ Launch image. :param image: an instance of Docker image. :return: None """ image.check_skip() image.pull_image(30) for _ in range(10): image.stop_if_already_running() # sleep after stop called time.sleep(1) container = image.create() logger.info(f"Setting up image {image.tag}...") try: container.start() except Exception: logger.exception("Error on container start") continue time.sleep(1) container.reload() if container.status == "running": break logger.info("Retry to start the container") else: logger.info("Failed to start the container") logger.info(container.logs()) raise Exception("Failed to start container") success = image.wait(max_attempts, timeout) if not success: logger.info( "containers list: {}".format( [f"{i.image}:{i.status}" for i in image._client.containers.list()], ) ) logger.info(container.logs()) container.stop() container.remove() pytest.fail(f"{image.tag} doesn't work. Exiting...") else: try: logger.info("Done!") time.sleep(timeout) yield finally: logger.info(f"Stopping the image {image.tag}...") container.stop() container.remove() @pytest.fixture(scope="session", autouse=True) def reset_aea_cli_config() -> None: """Reset the cli config for each test.""" _init_cli_config() def get_unused_tcp_port(): """Get an unused TCP port.""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 0)) s.listen(1) port = s.getsockname()[1] s.close() return port def get_host(): """Get the host.""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # doesn't even have to be reachable s.connect(("10.255.255.255", 1)) IP = s.getsockname()[0] except Exception: IP = "127.0.0.1" finally: s.close() return IP def double_escape_windows_path_separator(path): r"""Doubleescape Windows path separator '\'.""" return path.replace("\\", "\\\\") def _make_dummy_connection() -> Connection: configuration = ConnectionConfig( connection_id=DummyConnection.connection_id, ) dummy_connection = DummyConnection( configuration=configuration, data_dir=MagicMock(), identity=Identity("name", "address", "public_key"), ) return dummy_connection def _make_local_connection( address: Address, public_key: str, node: LocalNode, restricted_to_protocols=None, excluded_protocols=None, ) -> Connection: configuration = ConnectionConfig( restricted_to_protocols=restricted_to_protocols, excluded_protocols=excluded_protocols, connection_id=OEFLocalConnection.connection_id, ) oef_local_connection = OEFLocalConnection( configuration=configuration, data_dir=MagicMock(), identity=Identity("name", address, public_key), local_node=node, ) return oef_local_connection def _make_oef_connection( address: Address, public_key: str, oef_addr: str, oef_port: int ): configuration = ConnectionConfig( addr=oef_addr, port=oef_port, connection_id=OEFConnection.connection_id ) oef_connection = OEFConnection( configuration=configuration, data_dir=MagicMock(), identity=Identity("name", address, public_key), ) oef_connection._default_logger_name = "aea.packages.fetchai.connections.oef" return oef_connection def _make_tcp_server_connection(address: str, public_key: str, host: str, port: int): configuration = ConnectionConfig( address=host, port=port, connection_id=TCPServerConnection.connection_id ) tcp_connection = TCPServerConnection( configuration=configuration, data_dir=MagicMock(), identity=Identity("name", address, public_key), ) tcp_connection._default_logger_name = ( "aea.packages.fetchai.connections.tcp.tcp_server" ) return tcp_connection def _make_tcp_client_connection(address: str, public_key: str, host: str, port: int): configuration = ConnectionConfig( address=host, port=port, connection_id=TCPClientConnection.connection_id ) tcp_connection = TCPClientConnection( configuration=configuration, data_dir=MagicMock(), identity=Identity("name", address, public_key), ) tcp_connection._default_logger_name = ( "aea.packages.fetchai.connections.tcp.tcp_client" ) return tcp_connection def _make_stub_connection(input_file_path: str, output_file_path: str): configuration = ConnectionConfig( input_file=input_file_path, output_file=output_file_path, connection_id=StubConnection.connection_id, ) connection = StubConnection(configuration=configuration, data_dir=MagicMock()) return connection def _process_cert(key: Crypto, cert: CertRequest, path_prefix: str): # must match aea/cli/issue_certificates.py:_process_certificate assert cert.public_key is not None message = cert.get_message(cert.public_key) signature = key.sign_message(message).encode("ascii").hex() Path(cert.get_absolute_save_path(path_prefix)).write_bytes( signature.encode("ascii") ) def _make_libp2p_connection( data_dir: str, port: int = 10234, host: str = "127.0.0.1", relay: bool = True, delegate: bool = False, mailbox: bool = False, entry_peers: Optional[Sequence[MultiAddr]] = None, delegate_port: int = 11234, delegate_host: str = "127.0.0.1", mailbox_port: int = 8888, mailbox_host: str = "127.0.0.1", node_key_file: Optional[str] = None, agent_key: Optional[Crypto] = None, build_directory: Optional[str] = None, peer_registration_delay: str = "0.0", ) -> P2PLibp2pConnection: if not os.path.isdir(data_dir) or not os.path.exists(data_dir): raise ValueError("Data dir must be directory and exist!") log_file = os.path.join(data_dir, "libp2p_node_{}.log".format(port)) if os.path.exists(log_file): os.remove(log_file) key = agent_key if key is None: key = make_crypto(DEFAULT_LEDGER) identity = Identity("identity", address=key.address, public_key=key.public_key) conn_crypto_store = None if node_key_file is not None: conn_crypto_store = CryptoStore({DEFAULT_LEDGER: node_key_file}) else: node_key = make_crypto(DEFAULT_LEDGER) node_key_path = os.path.join(data_dir, f"{node_key.public_key}.txt") node_key.dump(node_key_path) conn_crypto_store = CryptoStore({DEFAULT_LEDGER: node_key_path}) cert_request = CertRequest( conn_crypto_store.public_keys[DEFAULT_LEDGER], POR_DEFAULT_SERVICE_ID, key.identifier, "2021-01-01", "2021-01-02", "{public_key}", f"./{key.address}_cert.txt", ) _process_cert(key, cert_request, path_prefix=data_dir) if not build_directory: build_directory = os.getcwd() if relay and delegate: configuration = ConnectionConfig( node_key_file=node_key_file, local_uri="{}:{}".format(host, port), public_uri="{}:{}".format(host, port), entry_peers=entry_peers, log_file=log_file, delegate_uri="{}:{}".format(delegate_host, delegate_port), peer_registration_delay=peer_registration_delay, connection_id=P2PLibp2pConnection.connection_id, build_directory=build_directory, cert_requests=[cert_request], ) elif relay and not delegate: configuration = ConnectionConfig( node_key_file=node_key_file, local_uri="{}:{}".format(host, port), public_uri="{}:{}".format(host, port), entry_peers=entry_peers, log_file=log_file, peer_registration_delay=peer_registration_delay, connection_id=P2PLibp2pConnection.connection_id, build_directory=build_directory, cert_requests=[cert_request], ) else: configuration = ConnectionConfig( node_key_file=node_key_file, local_uri="{}:{}".format(host, port), entry_peers=entry_peers, log_file=log_file, peer_registration_delay=peer_registration_delay, connection_id=P2PLibp2pConnection.connection_id, build_directory=build_directory, cert_requests=[cert_request], ) if mailbox: configuration.config["mailbox_uri"] = f"{mailbox_host}:{mailbox_port}" else: configuration.config["mailbox_uri"] = "" if not os.path.exists(os.path.join(build_directory, LIBP2P_NODE_MODULE_NAME)): build_node(build_directory) connection = P2PLibp2pConnection( configuration=configuration, data_dir=data_dir, identity=identity, crypto_store=conn_crypto_store, ) return connection def _make_libp2p_client_connection( peer_public_key: str, data_dir: str, node_port: int = 11234, node_host: str = "127.0.0.1", uri: Optional[str] = None, ledger_api_id: Union[SimpleId, str] = DEFAULT_LEDGER, ) -> P2PLibp2pClientConnection: if not os.path.isdir(data_dir) or not os.path.exists(data_dir): raise ValueError("Data dir must be directory and exist!") crypto = make_crypto(ledger_api_id) identity = Identity( "identity", address=crypto.address, public_key=crypto.public_key ) cert_request = CertRequest( peer_public_key, POR_DEFAULT_SERVICE_ID, ledger_api_id, "2021-01-01", "2021-01-02", "{public_key}", f"./{crypto.address}_cert.txt", ) _process_cert(crypto, cert_request, path_prefix=data_dir) configuration = ConnectionConfig( tcp_key_file=None, nodes=[ { "uri": str(uri) if uri is not None else "{}:{}".format(node_host, node_port), "public_key": peer_public_key, }, ], connection_id=P2PLibp2pClientConnection.connection_id, cert_requests=[cert_request], ) return P2PLibp2pClientConnection( configuration=configuration, data_dir=data_dir, identity=identity ) def _make_libp2p_mailbox_connection( peer_public_key: str, data_dir: str, node_port: int = 8888, node_host: str = "127.0.0.1", uri: Optional[str] = None, ledger_api_id: Union[SimpleId, str] = DEFAULT_LEDGER, ) -> P2PLibp2pMailboxConnection: if not os.path.isdir(data_dir) or not os.path.exists(data_dir): raise ValueError("Data dir must be directory and exist!") crypto = make_crypto(ledger_api_id) identity = Identity( "identity", address=crypto.address, public_key=crypto.public_key ) cert_request = CertRequest( peer_public_key, POR_DEFAULT_SERVICE_ID, ledger_api_id, "2021-01-01", "2021-01-02", "{public_key}", f"./{crypto.address}_cert.txt", ) _process_cert(crypto, cert_request, path_prefix=data_dir) configuration = ConnectionConfig( tcp_key_file=None, nodes=[ { "uri": str(uri) if uri is not None else "{}:{}".format(node_host, node_port), "public_key": peer_public_key, }, ], connection_id=P2PLibp2pMailboxConnection.connection_id, cert_requests=[cert_request], ) return P2PLibp2pMailboxConnection( configuration=configuration, data_dir=data_dir, identity=identity ) def libp2p_log_on_failure(fn: Callable) -> Callable: """ Decorate a pytest method running a libp2p node to print its logs in case test fails. :return: decorated method. """ # for pydcostyle @wraps(fn) def wrapper(self, *args, **kwargs): try: return fn(self, *args, **kwargs) except Exception: for log_file in self.log_files: print("libp2p log file ======================= {}".format(log_file)) try: with open(log_file, "r") as f: print(f.read()) except FileNotFoundError: pass print("=======================================") raise return wrapper def libp2p_log_on_failure_all(cls): """ Decorate every method of a class with `libp2p_log_on_failure`. :return: class with decorated methods. """ for name, fn in inspect.getmembers(cls): if isinstance(fn, FunctionType): setattr(cls, name, libp2p_log_on_failure(fn)) continue if isinstance(fn, MethodType): if fn.im_self is None: wrapped_fn = libp2p_log_on_failure(fn.im_func) method = MethodType(wrapped_fn, None, cls) setattr(cls, name, method) else: wrapped_fn = libp2p_log_on_failure(fn.im_func) clsmethod = MethodType(wrapped_fn, cls, type) setattr(cls, name, clsmethod) return cls def _do_for_all(method_decorator): def class_decorator(cls): class GetAttributeMetaClass(type): def __getattribute__(cls, name): attr = super().__getattribute__(name) return method_decorator(attr) class DecoratedClass(cls, metaclass=GetAttributeMetaClass): def __getattribute__(self, name): attr = super().__getattribute__(name) return method_decorator(attr) for attr in WRAPPER_ASSIGNMENTS: if not hasattr(cls, attr): continue setattr(DecoratedClass, attr, getattr(cls, attr)) DecoratedClass.__wrapped__ = cls return DecoratedClass return class_decorator class CwdException(Exception): """Exception to raise if cwd was not restored by test.""" def __init__(self): """Init expcetion with default message.""" super().__init__("CWD was not restored") @pytest.fixture(scope="class", autouse=True) def aea_testcase_teardown_check(request): """Check BaseAEATestCase.teardown_class for BaseAEATestCase based test cases.""" from aea.test_tools.test_cases import BaseAEATestCase # cause circular import yield if ( request.cls and issubclass(request.cls, BaseAEATestCase) and getattr(request.cls, "_skipped", False) is False ): assert getattr( request.cls, "_is_teardown_class_called", None ), "No BaseAEATestCase.teardown_class was called!" @pytest.fixture(scope="class", autouse=True) def check_test_class_cwd(): """Check test case class restore CWD.""" os.chdir(ROOT_DIR) old_cwd = os.getcwd() yield if old_cwd != os.getcwd(): raise CwdException() @pytest.fixture(autouse=True) def check_test_cwd(request): """Check particular test restore CWD.""" if request.cls: yield return os.chdir(ROOT_DIR) old_cwd = os.getcwd() yield if old_cwd != os.getcwd(): os.chdir(ROOT_DIR) raise CwdException() @pytest.fixture(autouse=True) def set_logging_to_debug(request): """Set aea logger to debug.""" aea_logger = logging.getLogger("aea") aea_logger.setLevel(logging.DEBUG) @pytest.fixture(autouse=True) def check_test_threads(request): """Check particular test close all spawned threads.""" if not request.config.getoption("--check-threads"): yield return if request.cls: yield return num_threads = threading.activeCount() yield new_num_threads = threading.activeCount() assert num_threads >= new_num_threads, "Non closed threads!" @pytest_asyncio.fixture async def ledger_apis_connection(request, ethereum_testnet_config): """Make a connection.""" crypto = make_crypto(DEFAULT_LEDGER) identity = Identity("name", crypto.address, crypto.public_key) crypto_store = CryptoStore() directory = Path(ROOT_DIR, "packages", "fetchai", "connections", "ledger") connection = Connection.from_dir( directory, data_dir=MagicMock(), identity=identity, crypto_store=crypto_store ) connection = cast(Connection, connection) connection._logger = logging.getLogger("aea.packages.fetchai.connections.ledger") # use testnet config connection.configuration.config.get("ledger_apis", {})[ "ethereum" ] = ethereum_testnet_config await connection.connect() yield connection await connection.disconnect() @pytest.fixture() def ledger_api(ethereum_testnet_config, ganache): """Ledger api fixture.""" ledger_id, config = EthereumCrypto.identifier, ethereum_testnet_config api = ledger_apis_registry.make(ledger_id, **config) yield api def get_register_erc1155() -> Contract: """Get and register the erc1155 contract package.""" directory = Path(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155") configuration = load_component_configuration(ComponentType.CONTRACT, directory) configuration._directory = directory configuration = cast(ContractConfig, configuration) if str(configuration.public_id) not in contract_registry.specs: # load contract into sys modules Contract.from_config(configuration) contract = contract_registry.make(str(configuration.public_id)) return contract @pytest.fixture() def erc1155_contract(ledger_api, ganache, ganache_addr, ganache_port): """ Instantiate an ERC1155 contract instance. As a side effect, register it to the registry, if not already registered. """ contract = get_register_erc1155() # deploy contract crypto = make_crypto( EthereumCrypto.identifier, private_key_path=ETHEREUM_PRIVATE_KEY_PATH ) tx = contract.get_deploy_transaction( ledger_api=ledger_api, deployer_address=crypto.address, gas=5000000 ) gas = ledger_api.api.eth.estimateGas(transaction=tx) tx["gas"] = gas tx_signed = crypto.sign_transaction(tx) tx_receipt = ledger_api.send_signed_transaction(tx_signed) receipt = ledger_api.get_transaction_receipt(tx_receipt) contract_address = cast(Dict, receipt)["contractAddress"] yield contract, contract_address @pytest.fixture() def erc20_contract(ledger_api, ganache, ganache_addr, ganache_port): """Instantiate an ERC20 contract.""" directory = Path(ROOT_DIR, "packages", "fetchai", "contracts", "fet_erc20") configuration = load_component_configuration(ComponentType.CONTRACT, directory) configuration._directory = directory configuration = cast(ContractConfig, configuration) if str(configuration.public_id) not in contract_registry.specs: # load contract into sys modules Contract.from_config(configuration) contract = contract_registry.make(str(configuration.public_id)) # get two accounts account1 = make_crypto( EthereumCrypto.identifier, private_key_path=ETHEREUM_PRIVATE_KEY_PATH ) account2 = make_crypto( EthereumCrypto.identifier, private_key_path=ETHEREUM_PRIVATE_KEY_TWO_PATH ) tx = contract.get_deploy_transaction( ledger_api=ledger_api, deployer_address=account1.address, gas=5000000, name="FetERC20Mock", symbol="MFET", initialSupply=int(1e23), decimals_=18, ) gas = ledger_api.api.eth.estimateGas(transaction=tx) tx["gas"] = gas tx_signed = account1.sign_transaction(tx) tx_receipt = ledger_api.send_signed_transaction(tx_signed) receipt = ledger_api.get_transaction_receipt(tx_receipt) contract_address = cast(Dict, receipt)["contractAddress"] # Transfer some MFET to another default account tx = contract.get_transfer_transaction( ledger_api=ledger_api, contract_address=contract_address, from_address=account1.address, gas=200000, receiver=account2.address, amount=int(1e20), ) tx_signed = account1.sign_transaction(tx) ledger_api.send_signed_transaction(tx_signed) yield contract, contract_address @pytest.fixture() def oracle_contract(ledger_api, ganache, ganache_addr, ganache_port, erc20_contract): """Instantiate a Fetch Oracle contract.""" directory = Path(ROOT_DIR, "packages", "fetchai", "contracts", "oracle") configuration = load_component_configuration(ComponentType.CONTRACT, directory) configuration._directory = directory configuration = cast(ContractConfig, configuration) if str(configuration.public_id) not in contract_registry.specs: # load contract into sys modules Contract.from_config(configuration) contract = contract_registry.make(str(configuration.public_id)) _, erc20_address = erc20_contract # deploy contract crypto = make_crypto( EthereumCrypto.identifier, private_key_path=ETHEREUM_PRIVATE_KEY_PATH ) tx = contract.get_deploy_transaction( ledger_api=ledger_api, deployer_address=crypto.address, gas=5000000, ERC20Address=erc20_address, initialFee=10000000000, ) tx_signed = crypto.sign_transaction(tx) tx_receipt = ledger_api.send_signed_transaction(tx_signed) receipt = ledger_api.get_transaction_receipt(tx_receipt) contract_address = cast(Dict, receipt)["contractAddress"] yield contract, contract_address def docker_exec_cmd(image_tag: str, cmd: str, **kwargs): """Execute a command in running docker containers matching image tag.""" client = docker.from_env() for container in client.containers.list(): if image_tag in container.image.tags: logger.info(f"Running command '{cmd}' in docker container {image_tag}") resp = container.exec_run(cmd, **kwargs) logger.info(resp) def fund_accounts_from_local_validator( addresses: List[str], amount: int, denom: str = DEFAULT_DENOMINATION ): """Send funds to local accounts from the local genesis validator.""" pk = PrivateKey(bytes.fromhex(FUNDED_FETCHAI_PRIVATE_KEY_1)) wallet = LocalWallet(pk) ledger = LedgerClient( NetworkConfig( chain_id=DEFAULT_FETCH_CHAIN_ID, url=f"rest+{DEFAULT_FETCH_LEDGER_ADDR}:{DEFAULT_FETCH_LEDGER_REST_PORT}", fee_minimum_gas_price=5000000000, fee_denomination=DEFAULT_DENOMINATION, staking_denomination=DEFAULT_DENOMINATION, ) ) for address in addresses: tx = ledger.send_tokens(CosmpyAddress(address), amount, denom, wallet) tx.wait_to_complete() @pytest.fixture() def fund_fetchai_accounts(fetchd): """Fund test accounts from local validator.""" for _ in range(5): try: # retry, cause possible race condition with fetchd docker image init fund_accounts_from_local_validator( [FUNDED_FETCHAI_ADDRESS_ONE, FUNDED_FETCHAI_ADDRESS_TWO], 10000000000000000000, ) return except Exception: # pylint: disable=broad-except time.sleep(3) def env_path_separator() -> str: """ Get the separator between path items in PATH variables, cross platform. E.g. on Linux and MacOS, it returns ':', whereas on Windows ';'. """ if sys.platform == "win32": return ";" else: return ":" def random_string(length: int = 8) -> str: """Generate a random string. :param length: how long random string should be :return: random chars str """ return "".join( random.choice(string.ascii_lowercase) for _ in range(length) # nosec ) def make_uri(addr: str, port: int): """Make uri from address and port.""" return f"{addr}:{port}" @pytest.mark.integration class UseGanache: """Inherit from this class to use Ganache.""" @pytest.fixture(autouse=True) def _start_ganache(self, ganache): """Start a Ganache image.""" @pytest.mark.integration class UseSOEF: """Inherit from this class to use SOEF.""" @pytest.fixture(autouse=True) def _start_soef(self, soef): """Start an SOEF image.""" @pytest.mark.integration class UseLocalFetchNode: """Inherit from this class to use a local Fetch ledger node.""" @pytest.fixture(autouse=True) def _start_fetchd(self, fetchd): """Start a Fetch ledger image.""" @pytest.fixture() def change_directory(): """Change directory and execute the test.""" temporary_directory = tempfile.mkdtemp() try: with cd(temporary_directory): yield temporary_directory finally: shutil.rmtree(temporary_directory) @pytest.fixture(params=[None, "fake-password"]) def password_or_none(request) -> Optional[str]: """ Return a password for testing purposes, including None. Note that this is a parametrized fixture. """ return request.param def method_scope(cls): """ Class decorator to make the setup/teardown to have the 'method' scope. :param cls: the class. It must be a subclass of :return: """ enforce( issubclass(cls, BaseAEATestCase), "cannot use decorator if class is not instance of BaseAEATestCase", ) old_setup_class = cls.setup_class old_teardown_class = cls.teardown_class cls.setup_class = classmethod(lambda _cls: None) cls.teardown_class = classmethod(lambda _cls: None) cls.setup = lambda self: old_setup_class() cls.teardown = lambda self: old_teardown_class() return cls def get_wealth_if_needed(address: Address, fetchai_api: FetchAIApi = None): """ Get wealth from fetch.ai faucet to specific address :param: address: Addresse to be funded from faucet """ if fetchai_api is None: fetchai_api = make_ledger_api( FetchAICrypto.identifier, **FETCHAI_TESTNET_CONFIG ) balance = fetchai_api.get_balance(address) if balance == 0: FetchAIFaucetApi().get_wealth(address) timeout = 0 while timeout < 40 and balance == 0: time.sleep(1) timeout += 1 _balance = fetchai_api.get_balance(address) balance = _balance if _balance is not None else 0 @pytest.fixture(scope="session", autouse=True) def disable_logging_handlers_cleanup(request) -> Generator: """ Fix for pytest flaky crash, disable handlers cleanup. Check https://github.com/fetchai/agents-aea/issues/2431 """ def do_nothing(*args): pass with MonkeyPatch().context() as mp: mp.setattr(logging.config, "_clearExistingHandlers", do_nothing) yield ================================================ FILE: tests/data/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the test data.""" ================================================ FILE: tests/data/aea-config.example.yaml ================================================ agent_name: myagent author: fetchai version: 0.2.0 description: An example of agent configuration file for testing purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/oef:0.22.6 contracts: [] protocols: - fetchai/oef_search:1.1.7 - fetchai/default:1.1.7 - fetchai/tac:1.1.7 - fetchai/fipa:1.1.7 skills: - fetchai/echo:0.20.6 default_connection: fetchai/oef:0.22.6 default_ledger: cosmos required_ledgers: - cosmos logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} data_dir: . dependencies: {} ================================================ FILE: tests/data/aea-config.example_multipage.yaml ================================================ agent_name: myagent author: fetchai version: 0.2.0 description: An example of agent configuration file for testing purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/oef:0.7.0 contracts: [] protocols: - fetchai/oef_search:0.4.0 - fetchai/default:0.4.0 - fetchai/tac:0.5.0 - fetchai/fipa:0.5.0 skills: - fetchai/echo:0.5.0 - dummy_author/dummy:0.1.0 default_connection: fetchai/oef:0.7.0 default_ledger: cosmos required_ledgers: - cosmos logging_config: disable_existing_loggers: false version: 1 private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt connection_private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt dependencies: {} --- public_id: dummy_author/dummy:0.1.0 type: skill behaviours: dummy: args: behaviour_arg_1: 1 behaviour_arg_2: '2' handlers: dummy: args: handler_arg_1: 1 handler_arg_2: '2' dummy_internal: args: handler_arg_1: 1 handler_arg_2: '2' models: dummy: args: model_arg_1: 1 model_arg_2: '2' ... ================================================ FILE: tests/data/aea-config.example_w_keys.yaml ================================================ agent_name: myagent author: fetchai version: 0.2.0 description: An example of agent configuration file for testing purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/oef:0.22.6 contracts: [] protocols: - fetchai/oef_search:1.1.7 - fetchai/default:1.1.7 - fetchai/tac:1.1.7 - fetchai/fipa:1.1.7 skills: - fetchai/echo:0.20.6 default_connection: fetchai/oef:0.22.6 default_ledger: cosmos required_ledgers: - cosmos logging_config: disable_existing_loggers: false version: 1 private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt connection_private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt dependencies: {} ================================================ FILE: tests/data/certs/server.crt ================================================ -----BEGIN CERTIFICATE----- MIICsTCCAZkCFGqNcoSHEADDJ5wZBtczunKoFWBVMA0GCSqGSIb3DQEBCwUAMBQx EjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yMTA5MDYwOTU2MTVaGA8yMTIxMDgxMzA5 NTYxNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAxX8TpWN5cE9oGotbTlEDVst1at0ZgYlgKwCVconrOQ77AhD4 MyJZ++Poy2i9N1JKYclT9ZrSRtqh+djILaCVeHHgwqFgwpGJuupunv/5o8fX9k1/ YUs3MQou9STiOLS+B/MK25Fi/LUvr1ZYJzAViekgV1yv7Sn/n2mpQe66ByyNZt3D Tzid9f3d7FHY6HBkxaoaUxkSIJeHcHy6qUXdiEeoPWBbkp6JjFqKW6mynwnDP4Wc zGerRtLo69rxtaaES0aboOjA6bk1Ab+4XmSH+Kv6r7CzArLFHJO+ZUU6Pqxd2ehR L9qicTzOQyN7ygVlBwZWpioOSRzGROgrNhQniQIDAQABMA0GCSqGSIb3DQEBCwUA A4IBAQBxSEAWtXQUq8qW8XHRcw1loBoxIus1+ajkMX9/pN0SFuNBq8P61TLtMjM1 G0czcgyXVqjMFtudY+CJa6yn+WWnp6oqDkjcu8Z5SdFfM677pQwF4R1nylb1aLP+ X+7NxVIs2pHNzYvxRm/OQ1Q6rVWVq3omludCcxHd3+y5kW5enG1Dp2yLXsdatNjj 2DzLF2WNMVXuXmFC9GVZpSsP5qCYD8rfqNvLaV6oxZ+woJ+UX8Q5YlEB1/2LtRBf x+XiiKXY2h4ZTHGjCO+Pu6FKOxf4JxWoBirCe7WOjfOJUdQYLOk6nT+qSP8iLFaS /DBRpBw/kJjec5xedh2CZOEC8NLw -----END CERTIFICATE----- ================================================ FILE: tests/data/certs/server.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEAxX8TpWN5cE9oGotbTlEDVst1at0ZgYlgKwCVconr OQ77AhD4MyJZ++Poy2i9N1JKYclT9ZrSRtqh+djILaCVeHHgwqFgwpGJuupunv/5 o8fX9k1/YUs3MQou9STiOLS+B/MK25Fi/LUvr1ZYJzAViekgV1yv7Sn/n2mpQe66 ByyNZt3DTzid9f3d7FHY6HBkxaoaUxkSIJeHcHy6qUXdiEeoPWBbkp6JjFqKW6my nwnDP4WczGerRtLo69rxtaaES0aboOjA6bk1Ab+4XmSH+Kv6r7CzArLFHJO+ZUU6 Pqxd2ehRL9qicTzOQyN7ygVlBwZWpioOSRzGROgrNhQniQIDAQABoAAwDQYJKoZI hvcNAQELBQADggEBAH9MeBQNrb081LoPDk3C/C4zH6BoVtxkQXoPXGv6+wijNju5 wAUZc26VGdawW3Scqj9kboObi5NwI7o8ZMsOx2V8MT5FjSl79DoAszwww19J48J4 6qGRPuph3c48Gz1eZCCQscaEG4pYqdL3aS6Jt/HmKkZGIAZvNGwzaBOGTIJTZHSU 9W9gV3RAL1unLgZKYAmKVStFtCOjARbn0wlBOeT2nkqRsckACgxxDNAjDPmKVmUK x6BSPttFWIVc/FXvkIBpwbxKAlwK9amtko0VGgw3WC7kBq13/ijNeQV/PaqFbSWF MshUv0YLv0aKVwFN1oRYiDYuwSXyRK5PYW4VNhQ= -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/data/certs/server.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAxX8TpWN5cE9oGotbTlEDVst1at0ZgYlgKwCVconrOQ77AhD4 MyJZ++Poy2i9N1JKYclT9ZrSRtqh+djILaCVeHHgwqFgwpGJuupunv/5o8fX9k1/ YUs3MQou9STiOLS+B/MK25Fi/LUvr1ZYJzAViekgV1yv7Sn/n2mpQe66ByyNZt3D Tzid9f3d7FHY6HBkxaoaUxkSIJeHcHy6qUXdiEeoPWBbkp6JjFqKW6mynwnDP4Wc zGerRtLo69rxtaaES0aboOjA6bk1Ab+4XmSH+Kv6r7CzArLFHJO+ZUU6Pqxd2ehR L9qicTzOQyN7ygVlBwZWpioOSRzGROgrNhQniQIDAQABAoIBAHao81Tbf4tLKnFI aYOUiT0M4W9jiH+b2nv7zc8TrpCJv6ZuK7INYaNGPAh61bT3bFl0bU2Tx+NqWQeU iDFh2myTf0dxToGYj/gOAojlo0gUOl1yEqaSWobMZ4pCrukDL2n3TP6/S4oqEox2 hGCHM2m4+AWFWu5T3ZIaGefTV1IXEqghj7L+53UI4qksnJDEQ9GT34KnlJhQKZmw E6St5F2xfXTKq6EWBIC23RNo4kAgDHBTE8MVRZdUEPmipf564NEVtxLFTKUi+tue GbiagtCWeGeEGZ2uFyr+HWIQUz4qLcMR6hp+hxh2VP3U52/0I5PKngSO0G6IemMD Hxj6DEECgYEA/A7Vjy9Or7DEIWZcsm44kyl79qUbzAlqDtG2RmOc6DDiWM3jQ/OR bOW4wNCFxv/KOsoeB7FxP7kKTupwj2u1Y9krAsHifXyFtHilZy9yTo4h8wHy40ae 4B8sP4+xEhppfjoFg5ySdF1BF1prwh34YSlFEkD+Q3Uu4Hr3+Ph8Pk8CgYEAyJXL TKT3zUUD6BhtJ74jtmwSW7SZx+lgbgzEtAHTwcZZllaTCdBY6H3im5pJ9LettymT PtSVn2EYVULtJJiT0qm3rqkqDQ8QImdx+T2V/GL9Oo8T1LdYud2J+f7+pEAn9Ud3 /XL6kZUXm/CpwIGIx3tB09kSsLxCGRnyrdhR3qcCgYEA3TCLWg5yp5ygUIsKV45/ 2SxzWzsCzKeKSZzgrp5lqCCV0MZEZHIOsRhaa+HRM5NuPO73MVsWfYv9Lsluo30q fYeqxc2s2t/2WSvyQj2RurvhsOWJ5sYnT5grdU+8XJ2O67Uw95DjuHfJUhwIKh2w xFq6AU3Fkx73VwiyKOqt5OMCgYBiIh7/VWpCy/QYVfL5UaXpNsBYi2f9DSl3Tdni c05lbCQiUCLJ11vYCtaV6Asspbxgcv+t6pV1Dyy3cfHRSLBxjUTnN63yC5+KJW/2 T3IUs11Oi/dYx4aqED/TxjRQqW6jKp8CqYD7PqT5TunN29HOPng7K+VgAAqaez5m XQHY2wKBgErzDsGHH+0XktR7HCZJMmuc/1HnDzfENnfpRIjjlwJF8xn/HymS0OP+ 3AsfN0g60Qutng62wthkzeUheD3UM2nwpmatuP4UXUFGkGcwmCdvzMf/SvyKIjmQ Mxiu8flSlCWlvP5e+Rrmd3RitOXeCqZLKbywMnlWHHfL33d1LWlf -----END RSA PRIVATE KEY----- ================================================ FILE: tests/data/cosmos_private_key.txt ================================================ 81b0352f99a08a754b56e529dda965c4ce974edb6db7e90035e01ed193e1b7bc ================================================ FILE: tests/data/custom_crypto.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a custom crypto class for testing purposes.""" from typing import Any, Optional, Tuple from aea.common import Address from aea.crypto.base import Crypto, EntityClass class CustomCrypto(Crypto[EntityClass]): """This is a custom crypto class for testing purposes..""" @classmethod def generate_private_key(cls) -> EntityClass: """Generare private key.""" pass @classmethod def load_private_key_from_path( cls, file_name: str, password: Optional[str] = None ) -> Any: """ Load a private key in hex format from a file. :param file_name: the path to the hex file. :param password: the password to encrypt/decrypt the private key. :return: the Entity. """ pass @property def public_key(self) -> str: """Get public key.""" pass @property def address(self) -> str: """Get address.""" pass @property def private_key(self) -> str: """Get private key.""" pass @classmethod def get_address_from_public_key(cls, public_key: str) -> str: """ Get address from public key. :param public_key: the public key. :return: the address """ pass def sign_message(self, message: bytes, is_deprecated_mode: bool = False) -> str: """ Sign message. :param message: the message :param is_deprecated_mode: whether or not deprecated signing mode is used. :return: signed message string """ pass def sign_transaction(self, transaction: Any) -> Any: """ Sign transaction. :param transaction: the transaction to be signed :return: the signed transaction """ pass def recover_message( self, message: bytes, signature: str, is_deprecated_mode: bool = False ) -> Tuple[Address, ...]: """ Recover message. :param message: the message :param signature: the signature :param is_deprecated_mode: whether or not it is deprecated """ pass def encrypt(self, password: str) -> str: """ Encrypt the private key and return in json. :param private_key: the raw private key. :param password: the password to decrypt. :return: json string containing encrypted private key. """ @classmethod def decrypt(cls, keyfile_json: str, password: str) -> str: """ Decrypt the private key and return in raw form. :param keyfile_json: json string containing encrypted private key. :param password: the password to decrypt. :return: the raw private key. """ ================================================ FILE: tests/data/dependencies_skill/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a skill to test dependencies format.""" ================================================ FILE: tests/data/dependencies_skill/skill.yaml ================================================ name: dummy author: fetchai version: 0.1.0 type: skill description: a skill for testing purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmVLEKUHaEdU1fcEvwiPYogLC8FjoKaZDzmq8UXr1rf9zM fingerprint_ignore_patterns: [] contracts: [] protocols: - fetchai/default:1.1.7 skills: [] behaviours: {} handlers: {} models: {} dependencies: dep1: version: ==1.0.0 dep2: version: ~=1.1.2 dep3: version: '>=1.10.11a1' dep4: version: ==1.11.12b2 dep5: version: '>=1.4.5.0' dep6: index: https://test.pypi.org/simple dep7: git: https://github.com/a-random-username/a-repository.git ref: master dep8: git: https://github.com/a-random-username/a-repository.git index: https://test.pypi.org/simple ref: master is_abstract: false connections: [] ================================================ FILE: tests/data/dot_env_file ================================================ TEST=yes ================================================ FILE: tests/data/dummy_aea/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2020 fetchai # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy aea.""" ================================================ FILE: tests/data/dummy_aea/aea-config.yaml ================================================ agent_name: Agent0 author: dummy_author version: 1.0.0 description: dummy_aea agent description license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/local:0.21.6 - fetchai/p2p_libp2p:0.27.5 contracts: - fetchai/erc1155:0.23.3 protocols: - fetchai/acn:1.1.7 - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: - dummy_author/dummy:0.1.0 - fetchai/error:0.18.6 default_connection: fetchai/local:0.21.6 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt fetchai: fetchai_private_key.txt connection_private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt fetchai: fetchai_private_key.txt default_routing: {} dependencies: {} ================================================ FILE: tests/data/dummy_aea/bad_requirements.txt ================================================ @#%$^&*^ ================================================ FILE: tests/data/dummy_aea/connections/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the connections.""" ================================================ FILE: tests/data/dummy_aea/contracts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the contracts.""" ================================================ FILE: tests/data/dummy_aea/cosmos_private_key.txt ================================================ 0988d2460bea9d156aea4bb0664183291a0ba959fe3ca59780faa0e4d1f15686 ================================================ FILE: tests/data/dummy_aea/default_private_key.pem ================================================ -----BEGIN EC PRIVATE KEY----- MIGkAgEBBDBPDMr3mGOklmP20XAcuJWXyi7MrqEpXnIpLMSrlxRfCt+xToUULRuc 13ZfEf6/h+ygBwYFK4EEACKhZANiAASCAxxhmfAN7IU/7TBnmadwFJzNYuIcBCZW 0vyazEyxuZCR0PeSJELVNNr0XCjV65ph+2g48rv/RvrBLC60fglCOVBkZcccWSLD S6yukJFBG+z27TE3+O4M0HwC83mLKFc= -----END EC PRIVATE KEY----- ================================================ FILE: tests/data/dummy_aea/ethereum_private_key.txt ================================================ 0x6F611408F7EF304947621C51A4B7D84A13A2B9786E9F984DA790A096E8260C64 ================================================ FILE: tests/data/dummy_aea/fetchai_private_key.txt ================================================ 66cec3f67a5fa81b6eb1c3c678dd60bb6959e3930c452397196bd63b45af5f00 ================================================ FILE: tests/data/dummy_aea/protocols/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the protocols.""" ================================================ FILE: tests/data/dummy_aea/requirements.txt ================================================ protobuf ================================================ FILE: tests/data/dummy_aea/skills/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the skills.""" ================================================ FILE: tests/data/dummy_aea/vendor/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Vendor dependencies.""" ================================================ FILE: tests/data/dummy_aea/vendor/fetchai/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Vendor dependencies from 'fetchai'.""" ================================================ FILE: tests/data/dummy_aea/vendor/fetchai/connections/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the connections.""" ================================================ FILE: tests/data/dummy_aea/vendor/fetchai/contracts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the contracts.""" ================================================ FILE: tests/data/dummy_aea/vendor/fetchai/protocols/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the protocols.""" ================================================ FILE: tests/data/dummy_aea/vendor/fetchai/skills/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of the skills.""" ================================================ FILE: tests/data/dummy_connection/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy connection for an AEA.""" ================================================ FILE: tests/data/dummy_connection/connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the implementation of a 'dummy' connection useful for testing.""" import asyncio from concurrent.futures._base import CancelledError from typing import Optional from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope class DummyConnection(Connection): """A dummy connection that just stores the messages.""" connection_id = PublicId.from_str("fetchai/dummy:0.1.0") def __init__(self, **kwargs): """Initialize.""" super().__init__(**kwargs) self.state = ConnectionStates.disconnected self._queue = None async def connect(self, *args, **kwargs): """Connect.""" self._queue = asyncio.Queue() self.state = ConnectionStates.connected async def disconnect(self, *args, **kwargs): """Disconnect.""" assert self._queue is not None await self._queue.put(None) self.state = ConnectionStates.disconnected async def send(self, envelope: "Envelope"): """Send an envelope.""" assert self._queue is not None self._queue.put_nowait(envelope) async def receive(self, *args, **kwargs) -> Optional["Envelope"]: """Receive an envelope.""" try: assert self._queue is not None envelope = await self._queue.get() if envelope is None: return None return envelope except CancelledError: return None except Exception as e: print(str(e)) return None def put(self, envelope: Envelope): """Put an envelope in the queue.""" assert self._queue is not None self._queue.put_nowait(envelope) ================================================ FILE: tests/data/dummy_connection/connection.yaml ================================================ name: dummy author: fetchai version: 0.1.0 type: connection description: dummy_connection connection description. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmVLvYQsVuMRPvU3w9UzZzEGjGa5iP5SWwDwTKCuRMzRrQ connection.py: QmergefLxWSmAPqn4DZiKEa8xS4u6F8Wp46QqsCv6UMnKq fingerprint_ignore_patterns: [] build_entrypoint: path/to/script.py connections: [] protocols: [] class_name: DummyConnection config: {} excluded_protocols: [] restricted_to_protocols: - fetchai/default:1.1.7 dependencies: dep1: version: ==1.0.0 dep2: version: ~=1.1.2 dep3: version: '>=1.10.11a1' dep4: version: <=1.11.12b2 is_abstract: false cert_requests: - identifier: cert_id_1 ledger_id: some_ledger_id message_format: '{public_key}' not_after: '2020-01-02' not_before: '2020-01-01' public_key: key_id save_path: /source/some/path_1 - identifier: cert_id_2 ledger_id: some_ledger_id message_format: '{public_key}' not_after: '2020-01-02' not_before: '2020-01-01' public_key: '0xABCDEF123456' save_path: /source/some/path_2 ================================================ FILE: tests/data/dummy_contract/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" ================================================ FILE: tests/data/dummy_contract/build/some.json ================================================ { "contractName": "erc1155", "abi": [ { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event" }, { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event" }, { "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor" }, { "name": "getAddress", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_addr" } ], "constant": true, "payable": false, "type": "function", "gas": 370 }, { "name": "getHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 11446 }, { "name": "getSingleHash", "outputs": [ { "type": "bytes32", "name": "out" } ], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 2041 }, { "name": "supportsInterface", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "bytes32", "name": "_interfaceID" } ], "constant": true, "payable": false, "type": "function", "gas": 868 }, { "name": "is_nonce_used", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "addr" }, { "type": "uint256", "name": "nonce" } ], "constant": true, "payable": false, "type": "function", "gas": 1052 }, { "name": "is_token_id_exists", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "uint256", "name": "token_id" } ], "constant": true, "payable": false, "type": "function", "gas": 928 }, { "name": "safeTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_value" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 80803 }, { "name": "safeBatchTransferFrom", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_values" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function", "gas": 748184 }, { "name": "balanceOf", "outputs": [ { "type": "uint256", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "uint256", "name": "_id" } ], "constant": true, "payable": false, "type": "function", "gas": 1172 }, { "name": "balanceOfBatch", "outputs": [ { "type": "uint256[10]", "name": "out" } ], "inputs": [ { "type": "address[10]", "name": "_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": true, "payable": false, "type": "function", "gas": 7524 }, { "name": "setApprovalForAll", "outputs": [], "inputs": [ { "type": "address", "name": "_operator" }, { "type": "bool", "name": "_approved" } ], "constant": false, "payable": false, "type": "function", "gas": 38136 }, { "name": "isApprovedForAll", "outputs": [ { "type": "bool", "name": "out" } ], "inputs": [ { "type": "address", "name": "_owner" }, { "type": "address", "name": "_operator" } ], "constant": true, "payable": false, "type": "function", "gas": 1301 }, { "name": "createSingle", "outputs": [], "inputs": [ { "type": "address", "name": "_item_owner" }, { "type": "uint256", "name": "_id" }, { "type": "string", "name": "_path" } ], "constant": false, "payable": false, "type": "function", "gas": 597461 }, { "name": "createBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_items_owner" }, { "type": "uint256[10]", "name": "_ids" } ], "constant": false, "payable": false, "type": "function", "gas": 927926 }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mint", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function" }, { "name": "mintBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": false, "type": "function" }, { "name": "burn", "outputs": [], "inputs": [ { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_supply" } ], "constant": false, "payable": false, "type": "function", "gas": 40353 }, { "name": "burnBatch", "outputs": [], "inputs": [ { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_supplies" } ], "constant": false, "payable": false, "type": "function", "gas": 382149 }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "tradeBatch", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256[10]", "name": "_ids" }, { "type": "uint256[10]", "name": "_from_supplies" }, { "type": "uint256[10]", "name": "_to_supplies" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" } ], "constant": false, "payable": true, "type": "function" }, { "name": "trade", "outputs": [], "inputs": [ { "type": "address", "name": "_from" }, { "type": "address", "name": "_to" }, { "type": "uint256", "name": "_id" }, { "type": "uint256", "name": "_from_supply" }, { "type": "uint256", "name": "_to_supply" }, { "type": "uint256", "name": "_value_eth" }, { "type": "uint256", "name": "_nonce" }, { "type": "bytes", "name": "_signature" }, { "type": "bytes", "name": "_data" } ], "constant": false, "payable": true, "type": "function" }, { "name": "owner", "outputs": [ { "type": "address", "name": "out" } ], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 1263 } ], "bytecode": "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6000600155600160006301ffc9a760e05260c052604060c020556001600063d9b67a2660e05260c052604060c020553360025561405a56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd5b6100d061405a036100d06000396100d061405a036000f3", "deployedBytecode": "0x600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263ae22c57d60005114156100d45734156100ac57600080fd5b60043560205181106100bd57600080fd5b50600435610140526101405160005260206000f350005b600015610313575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610180516020826105c00101526020810190506102c0516020826105c0010152602081019050610400516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526106606000600a818352015b610660511515156102765760006105a05160208261068001015260208101905061018061066051600a81106101fa57600080fd5b60200201516020826106800101526020810190506102c061066051600a811061022257600080fd5b602002015160208261068001015260208101905061040061066051600a811061024a57600080fd5b6020020151602082610680010152602081019050806106805261068090508051602082012090506105a0525b5b81516001018083528114156101c6575b5050600061014051602082610760010152602081019050610160516020826107600101526020810190506105a0516020826107600101526020810190506105405160208261076001015260208101905061056051602082610760010152602081019050806107605261076090508051602082012090506107405261074051600052600051610580515650005b634a6f823360005114156105c457341561032c57600080fd5b600435602051811061033d57600080fd5b50602435602051811061034f57600080fd5b506000610120525b602061012051016101205261012061012051101561037457610357565b6000610120525b60206101205101610120526101206101205110156103985761037b565b6000610120525b60206101205101610120526101206101205110156103bc5761039f565b6373ad25716101405260043561016052602435610180526101a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506102e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104206102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506104043561056052610424356105805261058051610560516105405161052051610500516104e0516104c0516104a05161048051610460516104405161042051610400516103e0516103c0516103a05161038051610360516103405161032051610300516102e0516102c0516102a05161028051610260516102405161022051610200516101e0516101c0516101a0516101805161016051600658016100dc565b6105e0526105e05160005260206000f350005b600015610909575b610580526101405261016052610180526101a0526101c0526101e05261020052610220526102405261026052610280526102a0526102c0526102e05261030052610320526103405261036052610380526103a0526103c0526103e05261040052610420526104405261046052610480526104a0526104c0526104e052610500526105205261054052610560526000610140516020826105c0010152602081019050610160516020826105c0010152602081019050610180516020826105c00101526020810190506101a0516020826105c00101526020810190506101c0516020826105c00101526020810190506101e0516020826105c0010152602081019050610200516020826105c0010152602081019050610220516020826105c0010152602081019050610240516020826105c0010152602081019050610260516020826105c0010152602081019050610280516020826105c00101526020810190506102a0516020826105c00101526020810190506102c0516020826105c00101526020810190506102e0516020826105c0010152602081019050610300516020826105c0010152602081019050610320516020826105c0010152602081019050610340516020826105c0010152602081019050610360516020826105c0010152602081019050610380516020826105c00101526020810190506103a0516020826105c00101526020810190506103c0516020826105c00101526020810190506103e0516020826105c0010152602081019050610400516020826105c0010152602081019050610420516020826105c0010152602081019050610440516020826105c0010152602081019050610460516020826105c0010152602081019050610480516020826105c00101526020810190506104a0516020826105c00101526020810190506104c0516020826105c00101526020810190506104e0516020826105c0010152602081019050610500516020826105c0010152602081019050610520516020826105c0010152602081019050610540516020826105c0010152602081019050610560516020826105c0010152602081019050806105c0526105c090508051602082012090506105a0526105a051600052600051610580515650005b6000156109e1575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101405160208261026001015260208101905061016051602082610260010152602081019050610180516020826102600101526020810190506101a0516020826102600101526020810190506101c0516020826102600101526020810190506101e05160208261026001015260208101905061020051602082610260010152602081019050806102605261026090508051602082012090506102405261024051600052600051610220515650005b6310660e866000511415610a905734156109fa57600080fd5b6004356020518110610a0b57600080fd5b506024356020518110610a1d57600080fd5b5063155960636101405260043561016052602435610180526044356101a0526064356101c0526084356101e05260a4356102005260c4356102205261022051610200516101e0516101c0516101a051610180516101605160065801610911565b610280526102805160005260206000f350005b600015610cf2575b6101805261014052610160526101a0526000610240525b6101a05160206001820306601f820103905061024051101515610ad157610aea565b610240516101c001526102405160200161024052610aaf565b60005060416101a0511815610b0757600060005260005161018051565b6101a0602060006020835103811315610b1f57600080fd5b046020026020018101519050610280526101a0602060206020835103811315610b4757600080fd5b0460200260200181015190506102a05260406001602082066103a0016101a0518284011115610b7557600080fd5b6041806103c0826020602088068803016101a001600060046018f1505081815280905090509050806020015160008251806020901315610bb457600080fd5b8091901215610bc257600080fd5b606051816020036101000a830480604051901315610bdf57600080fd5b8091901215610bed57600080fd5b9050905090506102c052601b6102c0511215610c30576102c0606051601b82510180604051901315610c1e57600080fd5b8091901215610c2c57600080fd5b8152505b610480601b8152601c81602001525060006104605261046061012060006002818352015b6101205160200261048001516102c0511415610c735760018352610c84565b5b8151600101808352811415610c54575b5050506104605160011415610ce257610140516104c0526102c0516000811215610cad57600080fd5b6104e05261028051610500526102a05161052052602060c060806104c060006001610bb8f15060c05160005260005161018051565b6000600052600051610180515650005b600015610db3575b6101605261014052610140517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806000811215610d3e578060000360020a8204610d45565b8060020a82025b90509050604051811115610d5857600080fd5b61018052700100000000000000000000000000000000610d7757600080fd5b7001000000000000000000000000000000006101405106604051811115610d9d57600080fd5b6101a05261018051600052600051610160515650005b63f17535506000511415610de8573415610dcc57600080fd5b600060043560e05260c052604060c0205460005260206000f350005b63ac2a2e216000511415610e3d573415610e0157600080fd5b6004356020518110610e1257600080fd5b50600460043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b634026b7556000511415610e72573415610e5657600080fd5b600760043560e05260c052604060c0205460005260206000f350005b63f242432a6000511415611135573415610e8b57600080fd5b6004356020518110610e9c57600080fd5b506024356020518110610eae57600080fd5b5061012060843560040161014037610100608435600401351115610ed157600080fd5b600660043560e05260c052604060c0203360e05260c052604060c02054336004351417610efd57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c050600060243518610f4c57608461029cfd5b6308c379a0610320526020610340526012610360527f4e6f7420656e6f75676820746f6b656e732e00000000000000000000000000006103805261036050606435600360043560e05260c052604060c02060443560e05260c052604060c020541015610fb957608461033cfd5b600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015610fe757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c0208054606435825401101561102157600080fd5b6064358154018155506044356103c0526064356103e052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103c0a460006024353b1115611133576024353b61107f57600080fd5b602435301861108d57600080fd5b60206106406101c460a063f23a6e6161042052336104405260043561046052604435610480526064356104a052806104c052610140808051602001808461044001828460006004600a8704601201f16110e557600080fd5b50508051820160206001820306601f820103905060200191505061043c905060006024355af161111457600080fd5b600050610640516104005263f23a6e61610400511461113257600080fd5b5b005b63cf36e535600051141561163c57341561114e57600080fd5b600435602051811061115f57600080fd5b50602435602051811061117157600080fd5b506000610120525b602061012051016101205261012061012051101561119657611179565b6000610120525b60206101205101610120526101206101205110156111ba5761119d565b6101206102c435600401610140376101006102c4356004013511156111de57600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761120a57600080fd5b6308c379a06102805260206102a05260206102c0527f43616e6e6f74207472616e7366657220746f207a65726f20616464726573732e6102e0526102c05060006024351861125957608461029cfd5b6103206000600a818352015b604461032051600a811061127857600080fd5b60200201356103405261018461032051600a811061129557600080fd5b6020020135600360043560e05260c052604060c0206103405160e05260c052604060c0205410156112c557600080fd5b5b8151600101808352811415611265575b505060443561036052606435610380526084356103a05260a4356103c05260c4356103e05260e435610400526101043561042052610124356104405261014435610460526101643561048052610184356104a0526101a4356104c0526101c4356104e0526101e435610500526102043561052052610224356105405261024435610560526102643561058052610284356105a0526102a4356105c052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a46105e06000600a818352015b60446105e051600a81106113c057600080fd5b602002013561060052600360043560e05260c052604060c0206106005160e05260c052604060c0206101846105e051600a81106113fc57600080fd5b60200201358154101561140e57600080fd5b6101846105e051600a811061142257600080fd5b6020020135815403815550600360243560e05260c052604060c0206106005160e05260c052604060c02080546101846105e051600a811061146257600080fd5b6020020135825401101561147557600080fd5b6101846105e051600a811061148957600080fd5b60200201358154018155505b81516001018083528114156113ad575b505060006024353b111561163a576024353b6114c057600080fd5b60243530186114ce57600080fd5b6020610aa06104046102e063a3bfc206610640523361066052600435610680526106a0604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506107e061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250508061092052610140808051602001808461066001828460006004600a8704601201f16115ec57600080fd5b50508051820160206001820306601f820103905060200191505061065c905060006024355af161161b57600080fd5b600050610aa0516106205263e324a00d610620511461163957600080fd5b5b005b62fdd58e600051141561169057341561165457600080fd5b600435602051811061166557600080fd5b50600360043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63fbe31aea60005114156117935734156116a957600080fd5b6000610120525b610120516004013560205181106116c657600080fd5b5060206101205101610120526101206101205110156116e4576116b0565b6000610120525b6020610120510161012052610120610120511015611708576116eb565b6102806000600a818352015b6003600461028051600a811061172957600080fd5b602002013560e05260c052604060c02061014461028051600a811061174d57600080fd5b602002013560e05260c052604060c0205461014061028051600a811061177257600080fd5b60200201525b8151600101808352811415611714575b5050610140610140f3005b63a22cb46560005114156118235734156117ac57600080fd5b60043560205181106117bd57600080fd5b50602435600281106117ce57600080fd5b5060243560063360e05260c052604060c02060043560e05260c052604060c0205560243561014052600435337f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c316020610140a3005b63e985e9c5600051141561188a57341561183c57600080fd5b600435602051811061184d57600080fd5b50602435602051811061185f57600080fd5b50600660043560e05260c052604060c02060243560e05260c052604060c0205460005260206000f350005b63a70b070f6000511415611af85734156118a357600080fd5b60043560205181106118b457600080fd5b50610120604435600401610140376101006044356004013511156118d757600080fd5b6000600435186118e657600080fd5b6308c379a06102805260206102a052601b6102c0527f4f776e6572206f6e6c792063616e20637265617465206974656d2e00000000006102e0526102c050336002541461193457608461029cfd5b6000600360043560e05260c052604060c02060243560e05260c052604060c02055600180546001825401101561196957600080fd5b60018154018155506001600760243560e05260c052604060c0205561014080600560243560e05260c052604060c02060c052602060c020602082510161012060006009818352015b826101205160200211156119c4576119e6565b61012051602002850151610120518501555b81516001018083528114156119b1575b50505050505060206103405261034051610380526101408051602001806103405161038001828460006004600a8704601201f1611a2257600080fd5b5050610320610340516103800151610440818352015b610440610320511115611a4a57611a6b565b600061032051610340516103a00101535b8151600101808352811415611a38575b5050602061034051610380015160206001820306601f8201039050610340510101610340526024357f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b61034051610380a26024356103a05260006103c0526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406103a0a4005b6319a35d7c6000511415611d43573415611b1157600080fd5b6004356020518110611b2257600080fd5b506000610120525b6020610120510161012052610120610120511015611b4757611b2a565b600060043518611b5657600080fd5b6308c379a061014052602061016052601c610180527f4f776e6572206f6e6c792063616e20637265617465206974656d732e000000006101a052610180503360025414611ba457608461015cfd5b6101e06000600a818352015b60246101e051600a8110611bc357600080fd5b6020020135610200526000600360043560e05260c052604060c0206102005160e05260c052604060c020556001805460018254011015611c0257600080fd5b6001815401815550600160076102005160e05260c052604060c020555b8151600101808352811415611bb0575b5050610220600081526000816020015260008160400152600081606001526000816080015260008160a0015260008160c0015260008160e00152600081610100015260008161012001525060243561036052604435610380526064356103a0526084356103c05260a4356103e05260c4356104005260e43561042052610104356104405261012435610460526101443561048052610220516104a052610240516104c052610260516104e05261028051610500526102a051610520526102c051610540526102e051610560526103005161058052610320516105a052610340516105c0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610360a4005b63156e29f66000511415611d8157600061028052610280805160200180610140828460006004600a8704601201f1611d7a57600080fd5b5050611dce565b63731133e96000511415611dc65761012060643560040161014037610100606435600401351115611db157600080fd5b61014060643560040161014037600050611dce565b60001561208a575b3415611dd957600080fd5b6004356020518110611dea57600080fd5b50600060043518611dfa57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e00000000000061032052610300503360025414611e485760846102dcfd5b610140610380525b61038051516020610380510161038052610380610380511015611e7257611e50565b6320171bef6103a0526024356103c0526103c05160065801610cfa565b61042052610360610380525b6103805152602061038051036103805261014061038051101515611ebe57611e9b565b6104205161036052600261036051146001610360511417611ede57600080fd5b6001610360511415611f5f576308c379a0610440526020610460526028610480527f43616e6e6f74206d696e74204e46542077697468205f737570706c79206d6f726104a0527f65207468616e20310000000000000000000000000000000000000000000000006104c05261048050600160443514611f5e5760a461045cfd5b5b604435600360043560e05260c052604060c02060243560e05260c052604060c0205560243561050052604435610520526004356000337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610500a460006004353b1115612088576004353b611fd557600080fd5b6004353018611fe357600080fd5b60206107806101c460a063f23a6e6161056052336105805260006105a0526024356105c0526044356105e0528061060052610140808051602001808461058001828460006004600a8704601201f161203a57600080fd5b50508051820160206001820306601f820103905060200191505061057c905060006004355af161206957600080fd5b600050610780516105405263f23a6e61610540511461208757600080fd5b5b005b63c671854d60005114156120c857600061028052610280805160200180610140828460006004600a8704601201f16120c157600080fd5b5050612118565b63b07e58756000511415612110576101206102a435600401610140376101006102a4356004013511156120fa57600080fd5b6101406102a43560040161014037600050612118565b6000156124f6575b341561212357600080fd5b600435602051811061213457600080fd5b506000610120525b60206101205101610120526101206101205110156121595761213c565b6000610120525b602061012051016101205261012061012051101561217d57612160565b60006004351861218c57600080fd5b6308c379a06102c05260206102e052601a610300527f4f776e6572206f6e6c792063616e206d696e74206974656d732e000000000000610320526103005033600254146121da5760846102dcfd5b6103606000600a818352015b602461036051600a81106121f957600080fd5b6020020135610380526101406103c0525b6103c0515160206103c051016103c0526103c06103c051101561222c5761220a565b6320171bef6103e05261038051610400526104005160065801610cfa565b610460526103a06103c0525b6103c0515260206103c051036103c0526101406103c05110151561227957612256565b610460516103a05260026103a0511460016103a051141761229957600080fd5b60016103a05114156122cb57600161016461036051600a81106122bb57600080fd5b6020020135146122ca57600080fd5b5b61016461036051600a81106122df57600080fd5b6020020135600360043560e05260c052604060c0206103805160e05260c052604060c020555b81516001018083528114156121e6575b5050602435610480526044356104a0526064356104c0526084356104e05260a4356105005260c4356105205260e4356105405261010435610560526101243561058052610144356105a052610164356105c052610184356105e0526101a435610600526101c435610620526101e4356106405261020435610660526102243561068052610244356106a052610264356106c052610284356106e0526004356000337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610480a46107006000600a818352015b60006004353b11156124e1576004353b61240357600080fd5b600435301861241157600080fd5b60206109606101c460a063f23a6e61610740523361076052600061078052602461070051600a811061244257600080fd5b60200201356107a05261016461070051600a811061245f57600080fd5b60200201356107c052806107e052610140808051602001808461076001828460006004600a8704601201f161249357600080fd5b50508051820160206001820306601f820103905060200191505061075c905060006004355af16124c257600080fd5b600050610960516107205263f23a6e6161072051146124e057600080fd5b5b5b81516001018083528114156123ea575b5050005b63b390c0ab60005114156125ea57341561250f57600080fd5b6308c379a061014052602061016052601a610180527f4e6f7420656e6f75676820746f6b656e7320746f206275726e2e0000000000006101a0526101805060243560033360e05260c052604060c02060043560e05260c052604060c02054101561257a57608461015cfd5b60033360e05260c052604060c02060043560e05260c052604060c020602435815410156125a657600080fd5b6024358154038155506004356101e05260243561020052600033337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406101e0a4005b63b59afe52600051141561282b57341561260357600080fd5b6000610120525b60206101205101610120526101206101205110156126275761260a565b6000610120525b602061012051016101205261012061012051101561264b5761262e565b6101406000600a818352015b600461014051600a811061266a57600080fd5b60200201356101605261014461014051600a811061268757600080fd5b602002013560033360e05260c052604060c0206101605160e05260c052604060c0205410156126b557600080fd5b5b8151600101808352811415612657575b50506101806000600a818352015b600461018051600a81106126e757600080fd5b60200201356101a05260033360e05260c052604060c0206101a05160e05260c052604060c02061014461018051600a811061272157600080fd5b60200201358154101561273357600080fd5b61014461018051600a811061274757600080fd5b60200201358154038155505b81516001018083528114156126d4575b50506004356101c0526024356101e05260443561020052606435610220526084356102405260a4356102605260c4356102805260e4356102a052610104356102c052610124356102e0526101443561030052610164356103205261018435610340526101a435610360526101c435610380526101e4356103a052610204356103c052610224356103e05261024435610400526102643561042052600033337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa86102806101c0a4005b63f2526ada6000511415612869576000610320526103208051602001806101e0828460006004600a8704601201f161286257600080fd5b50506128b9565b63a835c36960005114156128b157610120610464356004016101e0376101006104643560040135111561289b57600080fd5b610140610464356004016101e0376000506128b9565b600015613725575b60043560205181106128ca57600080fd5b5060243560205181106128dc57600080fd5b506000610120525b6020610120510161012052610120610120511015612901576128e4565b6000610120525b602061012051016101205261012061012051101561292557612908565b6000610120525b60206101205101610120526101206101205110156129495761292c565b6061610444356004016101403760416104443560040135111561296b57600080fd5b6308c379a061036052602061038052602c6103a0527f5f66726f6d206d757374206265207468652073656e646572206f7220617070726103c0527f6f766564206164647265737300000000000000000000000000000000000000006103e0526103a050600660043560e05260c052604060c0203360e05260c052604060c020543360043514176129fc5760a461037cfd5b6308c379a0610420526020610440526025610460527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d610480527f7a65726f2e0000000000000000000000000000000000000000000000000000006104a05261046050600060243518612a705760a461043cfd5b6308c379a06104e0526020610500526015610520527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006105405261052050600460043560e05260c052604060c0206104243560e05260c052604060c0205415612ada5760846104fcfd5b6308c379a06105805260206105a05260256105c0527f53656e64657220686173206e6f742070726f766964656420656e6f75676820656105e0527f746865722e000000000000000000000000000000000000000000000000000000610600526105c050346104043514612b4e5760a461059cfd5b6106406000600a818352015b604461064051600a8110612b6d57600080fd5b602002013561066052600061018461064051600a8110612b8c57600080fd5b60200201351115612c03576102c461064051600a8110612bab57600080fd5b602002013515612bba57600080fd5b61018461064051600a8110612bce57600080fd5b6020020135600360043560e05260c052604060c0206106605160e05260c052604060c020541015612bfe57600080fd5b612c6b565b61018461064051600a8110612c1757600080fd5b602002013515612c2657600080fd5b6102c461064051600a8110612c3a57600080fd5b6020020135600360243560e05260c052604060c0206106605160e05260c052604060c020541015612c6a57600080fd5b5b5b8151600101808352811415612b5a575b50506101406106a0525b6106a0515160206106a051016106a0526106a06106a0511015612ca857612c86565b6373ad25716106c0526004356106e05260243561070052610720604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061086061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506109a06102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505061040435610ae05261042435610b0052610b0051610ae051610ac051610aa051610a8051610a6051610a4051610a2051610a00516109e0516109c0516109a05161098051610960516109405161092051610900516108e0516108c0516108a05161088051610860516108405161082051610800516107e0516107c0516107a05161078051610760516107405161072051610700516106e051600658016100dc565b610b60526106806106a0525b6106a0515260206106a051036106a0526101406106a051101515612ecc57612ea9565b610b605161068052610140610ba0525b610ba051516020610ba05101610ba052610ba0610ba0511015612efe57612edc565b6040636f868168610bc05261068051610be05280610c00526101408080516020018084610be001828460006004600a8704601201f1612f3c57600080fd5b50508051820160206001820306601f820103905060200191505050610c005180610be00180518060206001820306601f82010390508201610ce0525050505b610c20610ce0511015612f8d57612fa2565b610ce051516020610ce05103610ce052612f7b565b610c0051610be05160065801610a98565b610d0052610b80610ba0525b610ba051526020610ba05103610ba052610140610ba051101515612fe257612fbf565b610d0051610b80526308c379a0610d20526020610d40526020610d60527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e610d8052610d6050602435610b80511461303b576084610d3cfd5b600160043360e05260c052604060c0206104243560e05260c052604060c02055610dc06000600a818352015b6044610dc051600a811061307a57600080fd5b6020020135610de0526000610184610dc051600a811061309957600080fd5b6020020135111561317457600360043560e05260c052604060c020610de05160e05260c052604060c020610184610dc051600a81106130d757600080fd5b6020020135815410156130e957600080fd5b610184610dc051600a81106130fd57600080fd5b6020020135815403815550600360243560e05260c052604060c020610de05160e05260c052604060c0208054610184610dc051600a811061313d57600080fd5b6020020135825401101561315057600080fd5b610184610dc051600a811061316457600080fd5b6020020135815401815550613240565b600360043560e05260c052604060c020610de05160e05260c052604060c02080546102c4610dc051600a81106131a957600080fd5b602002013582540110156131bc57600080fd5b6102c4610dc051600a81106131d057600080fd5b6020020135815401815550600360243560e05260c052604060c020610de05160e05260c052604060c0206102c4610dc051600a811061320e57600080fd5b60200201358154101561322057600080fd5b6102c4610dc051600a811061323457600080fd5b60200201358154038155505b5b8151600101808352811415613067575b50506000600060006000346024356000f161326b57600080fd5b604435610e0052606435610e2052608435610e405260a435610e605260c435610e805260e435610ea05261010435610ec05261012435610ee05261014435610f005261016435610f205261018435610f40526101a435610f60526101c435610f80526101e435610fa05261020435610fc05261022435610fe0526102443561100052610264356110205261028435611040526102a43561106052602435600435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280610e00a4604435611080526064356110a0526084356110c05260a4356110e05260c4356111005260e43561112052610104356111405261012435611160526101443561118052610164356111a0526102c4356111c0526102e4356111e052610304356112005261032435611220526103443561124052610364356112605261038435611280526103a4356112a0526103c4356112c0526103e4356112e052600435602435337f514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8610280611080a460006024353b1115613590576024353b61341657600080fd5b602435301861342457600080fd5b60206117806104046102e063a3bfc20661132052336113405260243561136052611380604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506114c061018480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611600526101e0808051602001808461134001828460006004600a8704601201f161354257600080fd5b50508051820160206001820306601f820103905060200191505061133c905060006024355af161357157600080fd5b6000506117805161130052630c97e564611300511461358f57600080fd5b5b60006004353b1115613723576004353b6135a957600080fd5b60043530186135b757600080fd5b6020611c206104046102e063a3bfc2066117c052336117e05260043561180052611820604480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e0015280610100013582610100015280610120013582610120015250506119606102c480358252806020013582602001528060400135826040015280606001358260600152806080013582608001528060a001358260a001528060c001358260c001528060e001358260e00152806101000135826101000152806101200135826101200152505080611aa0526101e080805160200180846117e001828460006004600a8704601201f16136d557600080fd5b50508051820160206001820306601f82010390506020019150506117dc905060006004355af161370457600080fd5b600050611c20516117a052630c97e5646117a0511461372257600080fd5b5b005b636e8205b26000511415613763576000610320526103208051602001806101e0828460006004600a8704601201f161375c57600080fd5b50506137b3565b63183f5ec560005114156137ab57610120610104356004016101e0376101006101043560040135111561379557600080fd5b610140610104356004016101e0376000506137b3565b600015613f5d575b60043560205181106137c457600080fd5b5060243560205181106137d657600080fd5b50606160e43560040161014037604160e4356004013511156137f757600080fd5b600660043560e05260c052604060c0203360e05260c052604060c0205433600435141761382357600080fd5b6308c379a06103605260206103805260256103a0527f44657374696e6174696f6e2061646472657373206d757374206265206e6f6e2d6103c0527f7a65726f2e0000000000000000000000000000000000000000000000000000006103e0526103a0506000602435186138975760a461037cfd5b6308c379a0610420526020610440526015610460527f4e6f6e6365206d75737420626520756e757365642e00000000000000000000006104805261046050600460043560e05260c052604060c02060c43560e05260c052604060c020541561390057608461043cfd5b6308c379a06104c05260206104e0526025610500527f53656e64657220686173206e6f742070726f766964656420656e6f7567682065610520527f746865722e00000000000000000000000000000000000000000000000000000061054052610500503460a435146139735760a46104dcfd5b600060643511156139bd576084351561398b57600080fd5b606435600360043560e05260c052604060c02060443560e05260c052604060c0205410156139b857600080fd5b6139f8565b606435156139ca57600080fd5b608435600360243560e05260c052604060c02060443560e05260c052604060c0205410156139f757600080fd5b5b6101406105a0525b6105a0515160206105a051016105a0526105a06105a0511015613a2257613a00565b63155960636105c0526004356105e0526024356106005260443561062052606435610640526084356106605260a4356106805260c4356106a0526106a05161068051610660516106405161062051610600516105e05160065801610911565b610700526105806105a0525b6105a0515260206105a051036105a0526101406105a051101515613ab057613a8d565b6107005161058052610140610740525b61074051516020610740510161074052610740610740511015613ae257613ac0565b6040636f868168610760526105805161078052806107a052610140808051602001808461078001828460006004600a8704601201f1613b2057600080fd5b50508051820160206001820306601f8201039050602001915050506107a051806107800180518060206001820306601f82010390508201610880525050505b6107c0610880511015613b7157613b86565b61088051516020610880510361088052613b5f565b6107a0516107805160065801610a98565b6108a052610720610740525b6107405152602061074051036107405261014061074051101515613bc657613ba3565b6108a051610720526308c379a06108c05260206108e0526020610900527f5369676e657220646f6573206e6f74206d61746368207369676e61747572652e61092052610900506024356107205114613c1f5760846108dcfd5b600160043360e05260c052604060c02060c43560e05260c052604060c0205560006064351115613cbf57600360043560e05260c052604060c02060443560e05260c052604060c02060643581541015613c7757600080fd5b606435815403815550600360243560e05260c052604060c02060443560e05260c052604060c02080546064358254011015613cb157600080fd5b606435815401815550613d31565b600360043560e05260c052604060c02060443560e05260c052604060c02080546084358254011015613cf057600080fd5b608435815401815550600360243560e05260c052604060c02060443560e05260c052604060c02060843581541015613d2757600080fd5b6084358154038155505b6000600060006000346024356000f1613d4957600080fd5b6044356109605260643561098052602435600435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f626040610960a46044356109a0526084356109c052600435602435337fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6260406109a0a460006024353b1115613e8e576024353b613dda57600080fd5b6024353018613de857600080fd5b6020610c206101c460a063f23a6e61610a005233610a2052602435610a4052604435610a6052606435610a805280610aa0526101e08080516020018084610a2001828460006004600a8704601201f1613e4057600080fd5b50508051820160206001820306601f8201039050602001915050610a1c905060006024355af1613e6f57600080fd5b600050610c20516109e052630c97e5646109e05114613e8d57600080fd5b5b60006004353b1115613f5b576004353b613ea757600080fd5b6004353018613eb557600080fd5b6020610e806101c460a063f23a6e61610c605233610c8052600435610ca052604435610cc052608435610ce05280610d00526101e08080516020018084610c8001828460006004600a8704601201f1613f0d57600080fd5b50508051820160206001820306601f8201039050602001915050610c7c905060006004355af1613f3c57600080fd5b600050610e8051610c4052630c97e564610c405114613f5a57600080fd5b5b005b638da5cb5b6000511415613f84573415613f7657600080fd5b60025460005260206000f350005b60006000fd", "source": "# Author: Sören Steiger, github.com/ssteiger\n# Author: Fetch.ai, github.com/fetchai\n# License: MIT\n\n# ERC1155 Token Standard\n# https://eips.ethereum.org/EIPS/eip-1155\n\n########################EXTERNAL-CONTRACTS####################################\n\ncontract ERC1155TokenReceiver:\n # Note: The ERC-165 identifier for this interface is 0x4e2312e0.\n\n def onERC1155Received(_operator: address, _from: address, _id: uint256, _value: uint256,\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of a single ERC1155 token type.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))` (i.e. 0xf23a6e61) if it accepts the transfer.\n # This function MUST revert if it rejects the transfer.\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _id The ID of the token being transferred\n # @param _value The amount of tokens being transferred\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155Received(address,address,uint256,uint256,bytes)\"))`\n # \"\"\"\n\n def onERC1155BatchReceived(_operator: address, _from: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE],\n _data: bytes[256]) -> bytes32: modifying # TODO: should return bytes4\n # \"\"\"\n # @notice Handle the receipt of multiple ERC1155 token types.\n # @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. \n # This function MUST return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))` (i.e. 0xbc197c81) if it accepts the transfer(s).\n # This function MUST revert if it rejects the transfer(s).\n # Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.\n # @param _operator The address which initiated the batch transfer (i.e. msg.sender)\n # @param _from The address which previously owned the token\n # @param _ids An array containing ids of each token being transferred (order and length must match _values array)\n # @param _values An array containing amounts of each token being transferred (order and length must match _ids array)\n # @param _data Additional data with no specified format\n # @return `bytes4(keccak256(\"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)\"))`\n # \"\"\"\n\n########################END-EXTERNAL-CONTRACTS####################################\n########################EVENTS####################################\n\nMAX_URI_SIZE: constant(uint256) = 1024\n\nTransferSingle: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address), _id: uint256,\n _value: uint256})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_id` argument MUST be the token type being transferred.\n# The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nTransferBatch: event({_operator: indexed(address), _from: indexed(address), _to: indexed(address),\n _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE]})\n# @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see \"Safe Transfer Rules\" section of the standard).\n# The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender).\n# The `_from` argument MUST be the address of the holder whose balance is decreased.\n# The `_to` argument MUST be the address of the recipient whose balance is increased.\n# The `_ids` argument MUST be the list of tokens being transferred.\n# The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by.\n# When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address).\n# When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address).\n\n\nApprovalForAll: event({_owner: indexed(address), _operator: indexed(address), _approved: bool})\n# @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled).\n\n\nURI: event({_value: string[MAX_URI_SIZE], _id: indexed(uint256)})\n# @dev MUST emit when the URI is updated for a token ID.\n# URIs are defined in RFC 3986.\n# The URI MUST point to a JSON file that conforms to the \"ERC-1155 Metadata URI JSON Schema\".\n\n########################END-EVENTS####################################\n########################INITIALIZATION####################################\n\nsupportedInterfaces: map(bytes32, bool)\n# https://eips.ethereum.org/EIPS/eip-165\nERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7\nERC1155_INTERFACE_ID: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26\ntokensIdCount: uint256\nowner: public(address)\n\nbalancesOf: map(address, map(uint256, uint256))\nnoncesOf: map(address, map(uint256, bool))\nuri: map(uint256, string[256])\noperators: map(address, map(address, bool))\ntoken_ids: map(uint256, bool)\n\n# This is to be set before contract migration!\nBATCH_SIZE: constant(uint256) = 10\n\n\n@public\ndef __init__():\n \"\"\"\n @notice Called once and only upon contract deployment.\n \"\"\"\n self.tokensIdCount = convert(0, uint256)\n self.supportedInterfaces[ERC165_INTERFACE_ID] = True\n self.supportedInterfaces[ERC1155_INTERFACE_ID] = True\n self.owner = msg.sender\n\n########################END-INITIALIZATION####################################\n########################PRIVATE-FUNCTIONS####################################\n\n\n######### THIS IS A TEMPORARY SOLUTION #################\n@public\n@constant\ndef getAddress(_addr: address) -> bytes32:\n hash: bytes32 = convert(_addr, bytes32)\n return hash\n##################### END ##############################\n\n@private\n@constant\ndef _getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n aggregate_hash: bytes32 = keccak256(concat(convert(_ids[0], bytes32), convert(_from_supplies[0], bytes32), convert(_to_supplies[0], bytes32)))\n for i in range(BATCH_SIZE):\n if not i == 0:\n aggregate_hash = keccak256(concat(aggregate_hash, convert(_ids[i], bytes32), convert(_from_supplies[i], bytes32), convert(_to_supplies[i], bytes32)))\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n aggregate_hash,\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getHash(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n\n@private\n@constant\ndef getHashOld(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_ids[0], bytes32),\n convert(_ids[1], bytes32),\n convert(_ids[2], bytes32),\n convert(_ids[3], bytes32),\n convert(_ids[4], bytes32),\n convert(_ids[5], bytes32),\n convert(_ids[6], bytes32),\n convert(_ids[7], bytes32),\n convert(_ids[8], bytes32),\n convert(_ids[9], bytes32),\n convert(_from_supplies[0], bytes32),\n convert(_from_supplies[1], bytes32),\n convert(_from_supplies[2], bytes32),\n convert(_from_supplies[3], bytes32),\n convert(_from_supplies[4], bytes32),\n convert(_from_supplies[5], bytes32),\n convert(_from_supplies[6], bytes32),\n convert(_from_supplies[7], bytes32),\n convert(_from_supplies[8], bytes32),\n convert(_from_supplies[9], bytes32),\n convert(_to_supplies[0], bytes32),\n convert(_to_supplies[1], bytes32),\n convert(_to_supplies[2], bytes32),\n convert(_to_supplies[3], bytes32),\n convert(_to_supplies[4], bytes32),\n convert(_to_supplies[5], bytes32),\n convert(_to_supplies[6], bytes32),\n convert(_to_supplies[7], bytes32),\n convert(_to_supplies[8], bytes32),\n convert(_to_supplies[9], bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@private\n@constant\ndef _getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n hash: bytes32 = keccak256(concat(convert(_from, bytes32),\n convert(_to, bytes32),\n convert(_id, bytes32),\n convert(_from_supply, bytes32),\n convert(_to_supply, bytes32),\n convert(_value_eth, bytes32),\n convert(_nonce, bytes32)))\n return hash\n\n\n@public\n@constant\ndef getSingleHash(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256) -> bytes32:\n \"\"\"\n @notice Get the hash from the tx values.\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _id The id of the tokens.\n @param _from_supply The from token value. (_from sends)\n @param _to_supply The to token value (_to sends).\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @return the hash\n \"\"\"\n return self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n\n@private\n@constant\ndef ecrecoverSig(_hash: bytes32, _sig: bytes[65]) -> address:\n \"\"\"\n @notice Check whether the the signature matches the hash.\n @param _hash The hash to be checked.\n @param _sig The signature which is meant to match the hash.\n @return the address which signed the signature or the zero address\n \"\"\"\n if len(_sig) != 65:\n return ZERO_ADDRESS\n # ref. https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d\n # The signature format is a compact form of:\n # {bytes32 r}{bytes32 s}{uint8 v}\n r: bytes32 = extract32(_sig, 0, type=bytes32)\n s: bytes32 = extract32(_sig, 32, type=bytes32)\n v: int128 = convert(slice(_sig, start=64, len=1), int128)\n # Version of signature should be 27 or 28, but 0 and 1 are also possible versions.\n # geth uses [0, 1] and some clients have followed. This might change, see:\n # https://github.com/ethereum/go-ethereum/issues/2053\n if v < 27:\n v += 27\n if v in [27, 28]:\n return ecrecover(_hash, convert(v, uint256), convert(r, uint256), convert(s, uint256))\n return ZERO_ADDRESS\n\n\n@private\n@constant\ndef decode_id(id: uint256) -> int128:\n \"\"\"\n @notice Decodes the id of the token inorder to find out if it NFT or FT.\n @param id: uint256\n @return token_id : int128 (Specified id for FT and NFT.)\n @dev shift(x, -y): returns x with the bits shifted to the right by y places, which is equivalent to dividing x by 2**y.\n \"\"\"\n decoded_token_id: int128 = convert(shift(id, -128), int128)\n decoded_index: int128 = convert(id % 2 ** 128, int128)\n return decoded_token_id\n\n########################END-PRIVATE-FUNCTIONS################################\n########################PUBLIC-FUNCTIONS#####################################\n\n@public\n@constant\ndef supportsInterface(_interfaceID: bytes32) -> bool:\n \"\"\"\n @notice Check whether the interface id is supported.\n @param _interfaceID The interface id\n @return True if the interface id is supported.\n \"\"\"\n return self.supportedInterfaces[_interfaceID]\n\n@public\n@constant\ndef is_nonce_used(addr: address, nonce: uint256) -> bool:\n \"\"\"\n @notice Checks if the given nonce for the give address is unused.\n @param nonce: uint256 the counter of the transaction\n @param address: the address that want to transact.\n \"\"\"\n return self.noncesOf[addr][nonce]\n\n@public\n@constant\ndef is_token_id_exists(token_id: uint256) -> bool:\n \"\"\"\n @notice Checks if the given token_id is already created.\n @param token_id: uint256 the id of the token.\n \"\"\"\n return self.token_ids[token_id]\n\n@public\ndef safeTransferFrom(_from: address, _to: address, _id: uint256, _value: uint256, _data: bytes[256]):\n \"\"\"\n @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if balance of holder for token `_id` is lower than the `_value` sent.\n MUST revert on any other error.\n MUST emit the `TransferSingle` event to reflect the balance change (see \"Safe Transfer Rules\" section of the standard).\n After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _id ID of the token type\n @param _value Transfer amount\n @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n assert self.balancesOf[_from][_id] >= _value, \"Not enough tokens.\"\n\n self.balancesOf[_from][_id] -= _value\n self.balancesOf[_to][_id] += _value\n\n log.TransferSingle(msg.sender, _from, _to, _id, _value)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef safeBatchTransferFrom(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _values: uint256[BATCH_SIZE], _data: bytes[256]):\n \"\"\"\n @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call).\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if length of `_ids` is not the same as length of `_values`.\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from Source address\n @param _to Target address\n @param _ids IDs of each token type (order and length must match _values array)\n @param _values Transfer amounts per token type (order and length must match _ids array)\n @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to`\n @return None\n \"\"\"\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Cannot transfer to zero address.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[_from][id] >= _values[i]\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _values)\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_from][id] -= _values[i]\n self.balancesOf[_to][id] += _values[i]\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _values, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256[BATCH_SIZE],uint256[BATCH_SIZE],bytes)\", bytes32)\n\n\n@public\n@constant\ndef balanceOf(_owner: address, _id: uint256) -> uint256:\n \"\"\"\n @notice Get the balance of an account's tokens.\n @param _owner The address of the token holder\n @param _id ID of the token\n @return The _owner's balance of the token type requested\n \"\"\"\n return self.balancesOf[_owner][_id]\n\n\n@public\n@constant\ndef balanceOfBatch( _owner: address[BATCH_SIZE], _ids: uint256[BATCH_SIZE]) -> uint256[BATCH_SIZE]:\n \"\"\"\n @notice Get the balance of multiple account/token pairs\n @param _owners The addresses of the token holders\n @param _ids ID of the tokens\n @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair)\n \"\"\"\n returnBalances: uint256[BATCH_SIZE]\n for i in range(BATCH_SIZE):\n returnBalances[i] = self.balancesOf[_owner[i]][_ids[i]]\n return returnBalances\n\n\n@public\ndef setApprovalForAll(_operator: address, _approved: bool):\n \"\"\"\n @notice Enable or disable approval for a third party (\"operator\") to manage all of the caller's tokens.\n @dev MUST emit the ApprovalForAll event on success.\n @param _operator Address to add to the set of authorized operators\n @param _approved True if the operator is approved, false to revoke approval\n @return None\n \"\"\"\n (self.operators[msg.sender])[_operator] = _approved\n log.ApprovalForAll(msg.sender, _operator, _approved)\n\n\n@public\n@constant\ndef isApprovedForAll(_owner: address, _operator: address) -> bool:\n \"\"\"\n @notice Queries the approval status of an operator for a given owner.\n @param _owner The owner of the tokens.\n @param _operator Address of authorized operator.\n @return True if the operator is approved, false if not\n \"\"\"\n return (self.operators[_owner])[_operator]\n\n\n@public\ndef createSingle(_item_owner: address, _id: uint256, _path: string[256]):\n \"\"\"\n @notice Create a new token type that we can mint later.\n @param _item_owner The owner of the item.\n @param _id The id of the token.\n @param _path The path to the token data.\n @return None\n \"\"\"\n assert _item_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create item.\"\n self.balancesOf[_item_owner][_id] = 0\n self.tokensIdCount += 1\n self.token_ids[_id] = True\n self.uri[_id] = _path\n log.URI(_path, _id)\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _item_owner, _id, 0)\n\n\n@public\ndef createBatch(_items_owner: address, _ids: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Create new token types that we can mint later.\n @param _items_owner The owner of the items.\n @param _ids The ids of the tokens.\n @return None\n \"\"\"\n assert _items_owner != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can create items.\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[_items_owner][id] = 0\n self.tokensIdCount += 1\n self.token_ids[id] = True\n zero_supply: uint256[BATCH_SIZE] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _items_owner, _ids, zero_supply)\n\n\n@public\ndef mint(_to: address, _id: uint256, _supply: uint256, _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a token.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _id The id of the token.\n @param _supply The supply to be minted for the token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n decoded_id: int128 = self.decode_id(_id)\n assert decoded_id == 1 or decoded_id == 2\n if decoded_id == 1 :\n assert _supply == 1, \"Cannot mint NFT with _supply more than 1\"\n self.balancesOf[_to][_id] = _supply\n\n log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, _id, _supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _id, _supply, _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef mintBatch(_to: address, _ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Mint a batch of tokens.\n @dev This is not part of the standard.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _supplies The supply to be minted for each token.\n @param _data The data.\n @return None\n \"\"\"\n assert _to != ZERO_ADDRESS\n assert self.owner == msg.sender, \"Owner only can mint items.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n decoded_id: int128 = self.decode_id(id)\n assert decoded_id == 1 or decoded_id == 2\n\n if decoded_id == 1 :\n assert _supplies[i] == 1\n\n self.balancesOf[_to][id] = _supplies[i]\n\n log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, _ids, _supplies)\n\n for i in range(BATCH_SIZE):\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, ZERO_ADDRESS, _ids[i], _supplies[i], _data)\n assert returnValue == method_id(\"onERC1155Received(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n@public\ndef burn(_id: uint256, _supply: uint256):\n \"\"\"\n @notice Burns the supply of the specified token.\n @param _id The id of the token\n @param _supply Supply to be burned\n @return None\n \"\"\"\n assert self.balancesOf[msg.sender][_id] >= _supply, \"Not enough tokens to burn.\"\n self.balancesOf[msg.sender][_id] -= _supply\n log.TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, _id, _supply)\n\n\n@public\ndef burnBatch(_ids: uint256[BATCH_SIZE], _supplies: uint256[BATCH_SIZE]):\n \"\"\"\n @notice Burns the supply of the specified tokens.\n @dev At this point anyone can burn items if they own it.\n @param _ids The ids of the token\n @param _supplies Supplies to be burned\n @return None\n \"\"\"\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n assert self.balancesOf[msg.sender][id] >= _supplies[i]\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n self.balancesOf[msg.sender][id] -= _supplies[i]\n log.TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, _ids, _supplies)\n\n\n@public\n@payable\ndef tradeBatch(_from: address, _to: address, _ids: uint256[BATCH_SIZE], _from_supplies: uint256[BATCH_SIZE], _to_supplies: uint256[BATCH_SIZE], _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supplies[i] > 0 and _to_supplies[i] > 0\n MUST revert if len(_ids) != len(_from_supplies) != len(_to_supplies)\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `positive and negative values` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The address of the sender.\n @param _to The address of the receiver.\n @param _ids The ids of the tokens.\n @param _from_supplies The supply of token values that will send the _from.\n @param _to_supplies The supply of token values that will send the _from.\n @param _value_eth The value of the ether.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n\n assert _from == msg.sender or (self.operators[_from])[msg.sender], \"_from must be the sender or approved address\"\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n assert _to_supplies[i] == 0\n assert self.balancesOf[_from][id] >= _from_supplies[i]\n else:\n assert _from_supplies[i] == 0\n assert self.balancesOf[_to][id] >= _to_supplies[i]\n\n # Create hash from variables.\n hash: bytes32 = self._getHash(_from, _to, _ids, _from_supplies, _to_supplies, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n for i in range(BATCH_SIZE):\n id: uint256 = _ids[i]\n if _from_supplies[i] > 0:\n self.balancesOf[_from][id] -= _from_supplies[i]\n self.balancesOf[_to][id] += _from_supplies[i]\n else:\n self.balancesOf[_from][id] += _to_supplies[i]\n self.balancesOf[_to][id] -= _to_supplies[i]\n\n send(_to, msg.value)\n\n log.TransferBatch(msg.sender, _from, _to, _ids, _from_supplies)\n log.TransferBatch(msg.sender, _to, _from, _ids, _to_supplies)\n\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _to, _ids, _from_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155BatchReceived(msg.sender, _from, _ids, _to_supplies, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n\n\n\n@public\n@payable\ndef trade(_from: address, _to: address, _id: uint256, _from_supply: uint256, _to_supply: uint256, _value_eth: uint256, _nonce: uint256, _signature: bytes[65], _data: bytes[256]=\"\"):\n \"\"\"\n @notice Trade (atomically swap) tokens with tokens or eth.\n @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see \"Approval\" section of the standard).\n MUST revert if `_to` is the zero address.\n MUST revert if _from_supply > 0 and _to_supply > 0\n MUST revert if _value_eth != msg.value\n MUST revert if any of the balance(s) of the holder(s) for token(s) in `_id` is lower than the respective amount in `positive or negative value` sent to the recipient.\n MUST revert on any other error.\n MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see \"Safe Transfer Rules\" section of the standard).\n Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc).\n After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see \"Safe Transfer Rules\" section of the standard).\n @param _from The from address (seller of eth, potential receiver of tokens).\n @param _to The receiver address (receiver of tokens).\n @param _id The id of the token\n @param _from_supply The change in value of token (for _from)\n @param _to_supply The change in value of token (for _to)\n @param _value_eth The value of the ETH sent to the _from address.\n @param _nonce The nonce.\n @param _signature The signature of the _to address.\n @param _data The data.\n @return None\n \"\"\"\n # Assert the value of the transaction is less than the balance of A.\n assert _from == msg.sender or (self.operators[_from])[msg.sender]\n assert _to != ZERO_ADDRESS, \"Destination address must be non-zero.\"\n assert self.noncesOf[_from][_nonce] == False, \"Nonce must be unused.\"\n assert _value_eth == msg.value, \"Sender has not provided enough ether.\"\n if _from_supply > 0:\n assert _to_supply == 0\n assert self.balancesOf[_from][_id] >= _from_supply\n else:\n assert _from_supply == 0\n assert self.balancesOf[_to][_id] >= _to_supply\n\n # Create hash from variables.\n hash: bytes32 = self._getSingleHash(_from, _to, _id, _from_supply, _to_supply, _value_eth, _nonce)\n\n # Assert that the ecrecover(address,signature) returns true.\n recovered_to: address = self.ecrecoverSig(hash, _signature)\n assert recovered_to == _to, \"Signer does not match signature.\"\n\n # Store the nonce\n self.noncesOf[msg.sender][_nonce] = True\n\n # Update the balances\n if _from_supply > 0:\n self.balancesOf[_from][_id] -= _from_supply\n self.balancesOf[_to][_id] += _from_supply\n else:\n self.balancesOf[_from][_id] += _to_supply\n self.balancesOf[_to][_id] -= _to_supply\n\n send(_to, msg.value)\n\n log.TransferSingle(msg.sender, _from, _to, _id, _from_supply)\n log.TransferSingle(msg.sender, _to, _from, _id, _to_supply)\n\n if _to.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _to, _id, _from_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n if _from.is_contract:\n returnValue: bytes32 = ERC1155TokenReceiver(_from).onERC1155Received(msg.sender, _from, _id, _to_supply, _data)\n assert returnValue == method_id(\"onERC1155BatchReceived(address,address,uint256,uint256,bytes)\", bytes32)\n", "sourcePath": "/Users/aristotelistriantafyllidis/Documents/agents-research/erc1155/contracts/erc1155.vy", "compiler": { "name": "vyper", "version": "0.1.0b12+commit.a01cdc8" }, "networks": { "1583918911727": { "events": { "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": { "name": "TransferSingle", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256", "name": "_id", "indexed": false }, { "type": "uint256", "name": "_value", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" }, "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8": { "name": "TransferBatch", "inputs": [ { "type": "address", "name": "_operator", "indexed": true }, { "type": "address", "name": "_from", "indexed": true }, { "type": "address", "name": "_to", "indexed": true }, { "type": "uint256[10]", "name": "_ids", "indexed": false }, { "type": "uint256[10]", "name": "_values", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x514c24e4790d7c31a095a1688a73a01f28d936a9a85d806ba95970d8ee88efa8" }, "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": { "name": "ApprovalForAll", "inputs": [ { "type": "address", "name": "_owner", "indexed": true }, { "type": "address", "name": "_operator", "indexed": true }, { "type": "bool", "name": "_approved", "indexed": false } ], "anonymous": false, "type": "event", "signature": "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31" }, "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": { "name": "URI", "inputs": [ { "type": "string", "name": "_value", "indexed": false }, { "type": "uint256", "name": "_id", "indexed": true } ], "anonymous": false, "type": "event", "signature": "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b" } }, "links": {}, "address": "0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7", "transactionHash": "0x816b272ebd644b189a3addfbd429b0ea0fa7b2403cca3aa038bcd59f09d9c116" } }, "schemaVersion": "3.0.19", "updatedAt": "2020-03-11T13:47:51.885Z", "networkType": "ethereum" } ================================================ FILE: tests/data/dummy_contract/contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy contract for an AEA.""" from aea.contracts.base import Contract from aea.crypto.base import LedgerApi class DummyContract(Contract): """The some contract class.""" @classmethod def some_method(cls, ledger_api: LedgerApi, contract_address: str) -> None: """Some method.""" pass ================================================ FILE: tests/data/dummy_contract/contract.yaml ================================================ name: dummy author: fetchai version: 0.1.0 type: contract description: A test contract license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmURXWttkpyyuCk7YfhmvWyRx9xNXzZUmrXaZDJeucajzn build/some.json: Qma5n7au2NDCg1nLwYfYnmFNwWChFuXtu65w5DV7wAZRvw build/some.wasm: Qmc9gthbdwRSywinTHKjRVQdFzrKTxUuLDx2ryNfQp1xqf contract.py: QmUw1VRch2sJGAzS5nb8JMFN2xYL5ovsm3SpnZMw9nV8zg fingerprint_ignore_patterns: [] build_entrypoint: path/to/script.py class_name: DummyContract contract_interface_paths: cosmos: build/some.wasm ethereum: build/some.json fetchai: build/some.wasm dependencies: {} ================================================ FILE: tests/data/dummy_protocol/protocol.yaml ================================================ name: dummy_protocol author: default_author version: 0.1.0 protocol_specification_id: some_author/some_protocol_name:1.0.0 type: protocol description: The scaffold protocol scaffolds a protocol to be implemented by the developer. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: Qmc9Ln8THrWmwou4nr3Acag7vcZ1fv8v5oRSkCWtv1aH6t message.py: QmWPrVTSHeKANCaVA4VaQyMGLix7yiMALbytsKZppAG2VU serialization.py: QmaAf5fppirUWe8JaeBbsqfbeofTHe8DDGHJooe2X389qo fingerprint_ignore_patterns: [] build_entrypoint: path/to/script.py dependencies: {} ================================================ FILE: tests/data/dummy_skill/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy skill for an AEA.""" from aea.configurations.base import PublicId PUBLIC_ID = PublicId.from_str("dummy_author/dummy:0.1.0") ================================================ FILE: tests/data/dummy_skill/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours for the 'dummy' skill.""" from aea.skills.base import Behaviour class DummyBehaviour(Behaviour): """Dummy behaviour.""" def __init__(self, **kwargs): """Initialize the echo behaviour.""" super().__init__(**kwargs) self.kwargs = kwargs self.nb_act_called = 0 self.nb_teardown_called = 0 def setup(self) -> None: """ Implement the setup. :return: None """ pass def act(self) -> None: """Act according to the behaviour.""" self.nb_act_called += 1 def teardown(self) -> None: """Teardown the behaviour.""" self.nb_teardown_called += 1 ================================================ FILE: tests/data/dummy_skill/dummy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy model.""" from aea.skills.base import Model class DummyModel(Model): """This class is a dummy model.""" ================================================ FILE: tests/data/dummy_skill/dummy_subpackage/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This is a skill subpackage (for testing purposes).""" ================================================ FILE: tests/data/dummy_skill/dummy_subpackage/foo.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module is in a skill sub-package (for testing purposes).""" from aea.protocols.base import Message from aea.skills.base import Behaviour, Handler from packages.fetchai.protocols.state_update import StateUpdateMessage def bar(): """A bar function.""" return 42 class DummyBehaviour(Behaviour): """Dummy behaviour.""" def __init__(self, **kwargs): """Initialize the dummy behaviour.""" super().__init__(**kwargs) self.kwargs = kwargs def setup(self) -> None: """ Implement the setup. :return: None """ pass def act(self) -> None: """Act according to the behaviour.""" def teardown(self) -> None: """Teardown the behaviour.""" class DummyStateUpdateHandler(Handler): """Dummy handler.""" SUPPORTED_PROTOCOL = StateUpdateMessage.protocol_id def __init__(self, **kwargs): """Initialize the handler.""" super().__init__(**kwargs) self.kwargs = kwargs def setup(self) -> None: """ Implement the setup. :return: None """ pass def handle(self, message: Message) -> None: """ Handle message. :param message: the message :return: None """ def teardown(self) -> None: """ Teardown the handler. :return: None """ ================================================ FILE: tests/data/dummy_skill/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the handler for the 'dummy' skill.""" from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.signing.message import SigningMessage class DummyHandler(Handler): """Dummy handler.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def __init__(self, **kwargs): """Initialize the handler.""" super().__init__(**kwargs) self.kwargs = kwargs self.handled_messages = [] self.nb_teardown_called = 0 def setup(self) -> None: """ Implement the setup. :return: None """ pass def handle(self, message: Message) -> None: """ Handle message. :param message: the message :return: None """ self.handled_messages.append(message) def teardown(self) -> None: """ Teardown the handler. :return: None """ self.nb_teardown_called += 1 class DummyInternalHandler(Handler): """Dummy internal handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id def __init__(self, **kwargs): """Initialize the handler.""" super().__init__(**kwargs) self.kwargs = kwargs self.handled_internal_messages = [] self.nb_teardown_called = 0 def setup(self) -> None: """ Implement the setup. :return: None """ pass def handle(self, message: Message) -> None: """ Handle message. :param message: the message :return: None """ self.handled_internal_messages.append(message) def teardown(self) -> None: """ Teardown the handler. :return: None """ self.nb_teardown_called += 1 ================================================ FILE: tests/data/dummy_skill/skill.yaml ================================================ name: dummy author: dummy_author version: 0.1.0 type: skill description: a dummy_skill for testing purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmbTjuDJuSgXvD6guu4GJEyZC3BwwtTaSN7HkqgzkCTypX behaviours.py: QmdN12JcAxikGXueDjuwyuGuvbcpihcznG2SLoRr24MDL4 dummy.py: QmW6RFSAACA7dy9N6ux22LRpsyCMWtFUXrgjy8YwoPf1XS dummy_subpackage/__init__.py: QmUgN7JbokfWGK6NHxYKN7oSkqcpwupM2FQRVMANwkzjoA dummy_subpackage/foo.py: QmXTMpmhi4qd7VH7rBFaSzDEFnYyj2r7EvALQzcwpK9PcN handlers.py: QmTpiZ1B5RoAKGF1t6TYuhdPemAZ75YuveQpZNETqjphVS tasks.py: QmeEex6NZvzHDSkQakPRTjymAYvyxd4t65mU6RiYAn8DVv fingerprint_ignore_patterns: [] contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/signing:1.1.7 - fetchai/state_update:1.1.7 skills: [] behaviours: dummy: args: behaviour_arg_1: 1 behaviour_arg_2: '2' class_name: DummyBehaviour dummy_behaviour_same_classname: args: behaviour_arg_1: 1 behaviour_arg_2: '2' class_name: DummyBehaviour file_path: dummy_subpackage/foo.py handlers: another_dummy_handler: args: handler_arg_1: 1 handler_arg_2: '2' class_name: DummyStateUpdateHandler dummy: args: handler_arg_1: 1 handler_arg_2: '2' class_name: DummyHandler dummy_internal: args: handler_arg_1: 1 handler_arg_2: '2' class_name: DummyInternalHandler models: dummy: args: model_arg_1: 1 model_arg_2: '2' class_name: DummyModel dependencies: {} is_abstract: false connections: [] ================================================ FILE: tests/data/dummy_skill/tasks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tasks for the 'dummy' skill.""" from aea.skills.tasks import Task class DummyTask(Task): """Dummy task.""" def __init__(self, **kwargs): """Initialize the task.""" super().__init__(**kwargs) self.kwargs = kwargs self.nb_execute_called = 0 self.nb_teardown_called = 0 def setup(self) -> None: """ Implement the setup. :return: None """ pass def execute(self, *args, **kwargs) -> int: """Execute the task.""" self.nb_execute_called += 1 return self.nb_execute_called def teardown(self) -> None: """Teardown the task.""" self.nb_teardown_called += 1 ================================================ FILE: tests/data/ethereum_private_key.txt ================================================ 0x6F611408F7EF304947621C51A4B7D84A13A2B9786E9F984DA790A096E8260C64 ================================================ FILE: tests/data/ethereum_private_key_two.txt ================================================ 0x04b4cecf78288f2ab09d1b4c60219556928f86220f0fb2dcfc05e6a1c1149dbf ================================================ FILE: tests/data/exception_skill/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a dummy 'exception' skill for an AEA.""" ================================================ FILE: tests/data/exception_skill/behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the behaviours for the 'exception' skill.""" from aea.skills.base import Behaviour class ExceptionBehaviour(Behaviour): """A behaviour that raises an exception..""" def setup(self) -> None: """ Implement the setup. :return: None """ def act(self) -> None: """Act.""" raise Exception("Expected exception!") def teardown(self) -> None: """Teardown the task.""" ================================================ FILE: tests/data/exception_skill/handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the handler for the 'exception' skill.""" ================================================ FILE: tests/data/exception_skill/skill.yaml ================================================ name: exception author: fetchai version: 0.1.0 type: skill description: Raise an exception, at some point. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmRLyScuC6bY5DANwG1Mk9zNCfiD6arrP2uoSd3ruz7hXQ behaviours.py: QmYnyEnHA73V488mTg5j1i9drtbnd2JBbQa71ypU1uaYuo handlers.py: QmZnd7QMHo9pmwouLcfRFdy3daT4xMP1Cn1FfxYYbzmcXx tasks.py: QmcXkhi79S4AJynSPQtFMqHr7NPT3NDb6EMDSMM3UqAVZZ fingerprint_ignore_patterns: [] build_entrypoint: path/to/script.py contracts: [] protocols: [] skills: [] behaviours: exception: args: {} class_name: ExceptionBehaviour handlers: {} models: {} dependencies: {} is_abstract: false connections: [] ================================================ FILE: tests/data/exception_skill/tasks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tasks for the 'exception' skill.""" ================================================ FILE: tests/data/fetchai_private_key.txt ================================================ 6d56fd47e98465824aa85dfe620ad3dbf092b772abc6c6a182e458b5c56ad13b ================================================ FILE: tests/data/fetchai_private_key_wrong.txt ================================================ WRONG!!!!! 6d56fd47e98465824aa85dfe620ad3dbf092b772abc6c6a182e458b5c56ad13b ================================================ FILE: tests/data/generator/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the generator test data.""" ================================================ FILE: tests/data/generator/t_protocol/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the t_protocol protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from tests.data.generator.t_protocol.message import TProtocolMessage from tests.data.generator.t_protocol.serialization import TProtocolSerializer TProtocolMessage.serializer = TProtocolSerializer ================================================ FILE: tests/data/generator/t_protocol/custom_types.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains class representations corresponding to every custom type in the protocol specification.""" from typing import Dict, List, Set class DataModel: """This class represents an instance of DataModel.""" def __init__( self, bytes_field: bytes, int_field: int, float_field: float, bool_field: bool, str_field: str, set_field: Set[int], list_field: List[str], dict_field: Dict[int, bool], ): """Initialise an instance of DataModel.""" self.bytes_field = bytes_field self.int_field = int_field self.float_field = float_field self.bool_field = bool_field self.str_field = str_field self.set_field = set_field self.list_field = list_field self.dict_field = dict_field @staticmethod def encode(data_model_protobuf_object, data_model_object: "DataModel") -> None: """ Encode an instance of this class into the protocol buffer object. The protocol buffer object in the data_model_protobuf_object argument is matched with the instance of this class in the 'data_model_object' argument. :param data_model_protobuf_object: the protocol buffer object whose type corresponds with this class. :param data_model_object: an instance of this class to be encoded in the protocol buffer object. :return: None """ data_model_protobuf_object.bytes_field = data_model_object.bytes_field data_model_protobuf_object.int_field = data_model_object.int_field data_model_protobuf_object.float_field = data_model_object.float_field data_model_protobuf_object.bool_field = data_model_object.bool_field data_model_protobuf_object.str_field = data_model_object.str_field data_model_protobuf_object.set_field.extend(data_model_object.set_field) data_model_protobuf_object.list_field.extend(data_model_object.list_field) data_model_protobuf_object.dict_field.update(data_model_object.dict_field) @classmethod def decode(cls, data_model_protobuf_object) -> "DataModel": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. A new instance of this class is created that matches the protocol buffer object in the 'data_model_protobuf_object' argument. :param data_model_protobuf_object: the protocol buffer object whose type corresponds with this class. :return: A new instance of this class that matches the protocol buffer object in the 'data_model_protobuf_object' argument. """ return cls( bytes_field=data_model_protobuf_object.bytes_field, int_field=data_model_protobuf_object.int_field, float_field=data_model_protobuf_object.float_field, bool_field=data_model_protobuf_object.bool_field, str_field=data_model_protobuf_object.str_field, set_field=set(data_model_protobuf_object.set_field), list_field=data_model_protobuf_object.list_field, dict_field=data_model_protobuf_object.dict_field, ) def __eq__(self, other): """Overrides the default implementation""" if not isinstance(other, self.__class__): return False return ( self.bytes_field == other.bytes_field and self.int_field == other.int_field # floats seem to lose some precision when serialised then deserialised using protobuf # and self.float_field == other.float_field and self.bool_field == other.bool_field and self.str_field == other.str_field and self.set_field == other.set_field and self.list_field == other.list_field and self.dict_field == other.dict_field ) class DataModel1(DataModel): """This class represents an instance of DataModel1.""" class DataModel2(DataModel): """This class represents an instance of DataModel2.""" class DataModel3(DataModel): """This class represents an instance of DataModel3.""" class DataModel4(DataModel): """This class represents an instance of DataModel3.""" ================================================ FILE: tests/data/generator/t_protocol/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for t_protocol dialogue management. - TProtocolDialogue: The dialogue class maintains state of a dialogue and manages it. - TProtocolDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from tests.data.generator.t_protocol.message import TProtocolMessage class TProtocolDialogue(Dialogue): """The t_protocol dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { TProtocolMessage.Performative.PERFORMATIVE_CT, TProtocolMessage.Performative.PERFORMATIVE_PT, } ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { TProtocolMessage.Performative.PERFORMATIVE_MT, TProtocolMessage.Performative.PERFORMATIVE_O, } ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { TProtocolMessage.Performative.PERFORMATIVE_CT: frozenset( {TProtocolMessage.Performative.PERFORMATIVE_PCT} ), TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS: frozenset( {TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS} ), TProtocolMessage.Performative.PERFORMATIVE_MT: frozenset(), TProtocolMessage.Performative.PERFORMATIVE_O: frozenset(), TProtocolMessage.Performative.PERFORMATIVE_PCT: frozenset( { TProtocolMessage.Performative.PERFORMATIVE_MT, TProtocolMessage.Performative.PERFORMATIVE_O, } ), TProtocolMessage.Performative.PERFORMATIVE_PMT: frozenset( { TProtocolMessage.Performative.PERFORMATIVE_MT, TProtocolMessage.Performative.PERFORMATIVE_O, } ), TProtocolMessage.Performative.PERFORMATIVE_PT: frozenset( { TProtocolMessage.Performative.PERFORMATIVE_PT, TProtocolMessage.Performative.PERFORMATIVE_PMT, } ), } class Role(Dialogue.Role): """This class defines the agent's role in a t_protocol dialogue.""" ROLE_1 = "role_1" ROLE_2 = "role_2" class EndState(Dialogue.EndState): """This class defines the end states of a t_protocol dialogue.""" END_STATE_1 = 0 END_STATE_2 = 1 END_STATE_3 = 2 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[TProtocolMessage] = TProtocolMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class TProtocolDialogues(Dialogues, ABC): """This class keeps track of all t_protocol dialogues.""" END_STATES = frozenset( { TProtocolDialogue.EndState.END_STATE_1, TProtocolDialogue.EndState.END_STATE_2, TProtocolDialogue.EndState.END_STATE_3, } ) _keep_terminal_state_dialogues = True def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[TProtocolDialogue] = TProtocolDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=TProtocolMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: tests/data/generator/t_protocol/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains t_protocol's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, FrozenSet, Optional, Set, Tuple, Union, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message from tests.data.generator.t_protocol.custom_types import DataModel as CustomDataModel from tests.data.generator.t_protocol.custom_types import DataModel1 as CustomDataModel1 from tests.data.generator.t_protocol.custom_types import DataModel2 as CustomDataModel2 from tests.data.generator.t_protocol.custom_types import DataModel3 as CustomDataModel3 from tests.data.generator.t_protocol.custom_types import DataModel4 as CustomDataModel4 _default_logger = logging.getLogger("aea.packages.fetchai.protocols.t_protocol.message") DEFAULT_BODY_SIZE = 4 class TProtocolMessage(Message): """A protocol for testing purposes.""" protocol_id = PublicId.from_str("fetchai/t_protocol:0.1.0") protocol_specification_id = PublicId.from_str( "some_author/some_protocol_name:1.0.0" ) DataModel = CustomDataModel DataModel1 = CustomDataModel1 DataModel2 = CustomDataModel2 DataModel3 = CustomDataModel3 DataModel4 = CustomDataModel4 class Performative(Message.Performative): """Performatives for the t_protocol protocol.""" PERFORMATIVE_CT = "performative_ct" PERFORMATIVE_EMPTY_CONTENTS = "performative_empty_contents" PERFORMATIVE_MT = "performative_mt" PERFORMATIVE_O = "performative_o" PERFORMATIVE_PCT = "performative_pct" PERFORMATIVE_PMT = "performative_pmt" PERFORMATIVE_PT = "performative_pt" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "performative_ct", "performative_empty_contents", "performative_mt", "performative_o", "performative_pct", "performative_pmt", "performative_pt", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "content_bool", "content_bytes", "content_ct", "content_dict_bool_bool", "content_dict_bool_bytes", "content_dict_bool_float", "content_dict_bool_int", "content_dict_bool_str", "content_dict_int_bool", "content_dict_int_bytes", "content_dict_int_float", "content_dict_int_int", "content_dict_int_str", "content_dict_str_bool", "content_dict_str_bytes", "content_dict_str_float", "content_dict_str_int", "content_dict_str_str", "content_float", "content_int", "content_list_bool", "content_list_bytes", "content_list_float", "content_list_int", "content_list_str", "content_o_bool", "content_o_ct", "content_o_dict_str_int", "content_o_list_bytes", "content_o_set_int", "content_set_bool", "content_set_bytes", "content_set_float", "content_set_int", "content_set_str", "content_str", "content_union_1", "content_union_2", "content_union_3", "dialogue_reference", "message_id", "performative", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of TProtocolMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=TProtocolMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(TProtocolMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def content_bool(self) -> bool: """Get the 'content_bool' content from the message.""" enforce(self.is_set("content_bool"), "'content_bool' content is not set.") return cast(bool, self.get("content_bool")) @property def content_bytes(self) -> bytes: """Get the 'content_bytes' content from the message.""" enforce(self.is_set("content_bytes"), "'content_bytes' content is not set.") return cast(bytes, self.get("content_bytes")) @property def content_ct(self) -> CustomDataModel: """Get the 'content_ct' content from the message.""" enforce(self.is_set("content_ct"), "'content_ct' content is not set.") return cast(CustomDataModel, self.get("content_ct")) @property def content_dict_bool_bool(self) -> Dict[bool, bool]: """Get the 'content_dict_bool_bool' content from the message.""" enforce( self.is_set("content_dict_bool_bool"), "'content_dict_bool_bool' content is not set.", ) return cast(Dict[bool, bool], self.get("content_dict_bool_bool")) @property def content_dict_bool_bytes(self) -> Dict[bool, bytes]: """Get the 'content_dict_bool_bytes' content from the message.""" enforce( self.is_set("content_dict_bool_bytes"), "'content_dict_bool_bytes' content is not set.", ) return cast(Dict[bool, bytes], self.get("content_dict_bool_bytes")) @property def content_dict_bool_float(self) -> Dict[bool, float]: """Get the 'content_dict_bool_float' content from the message.""" enforce( self.is_set("content_dict_bool_float"), "'content_dict_bool_float' content is not set.", ) return cast(Dict[bool, float], self.get("content_dict_bool_float")) @property def content_dict_bool_int(self) -> Dict[bool, int]: """Get the 'content_dict_bool_int' content from the message.""" enforce( self.is_set("content_dict_bool_int"), "'content_dict_bool_int' content is not set.", ) return cast(Dict[bool, int], self.get("content_dict_bool_int")) @property def content_dict_bool_str(self) -> Dict[bool, str]: """Get the 'content_dict_bool_str' content from the message.""" enforce( self.is_set("content_dict_bool_str"), "'content_dict_bool_str' content is not set.", ) return cast(Dict[bool, str], self.get("content_dict_bool_str")) @property def content_dict_int_bool(self) -> Dict[int, bool]: """Get the 'content_dict_int_bool' content from the message.""" enforce( self.is_set("content_dict_int_bool"), "'content_dict_int_bool' content is not set.", ) return cast(Dict[int, bool], self.get("content_dict_int_bool")) @property def content_dict_int_bytes(self) -> Dict[int, bytes]: """Get the 'content_dict_int_bytes' content from the message.""" enforce( self.is_set("content_dict_int_bytes"), "'content_dict_int_bytes' content is not set.", ) return cast(Dict[int, bytes], self.get("content_dict_int_bytes")) @property def content_dict_int_float(self) -> Dict[int, float]: """Get the 'content_dict_int_float' content from the message.""" enforce( self.is_set("content_dict_int_float"), "'content_dict_int_float' content is not set.", ) return cast(Dict[int, float], self.get("content_dict_int_float")) @property def content_dict_int_int(self) -> Dict[int, int]: """Get the 'content_dict_int_int' content from the message.""" enforce( self.is_set("content_dict_int_int"), "'content_dict_int_int' content is not set.", ) return cast(Dict[int, int], self.get("content_dict_int_int")) @property def content_dict_int_str(self) -> Dict[int, str]: """Get the 'content_dict_int_str' content from the message.""" enforce( self.is_set("content_dict_int_str"), "'content_dict_int_str' content is not set.", ) return cast(Dict[int, str], self.get("content_dict_int_str")) @property def content_dict_str_bool(self) -> Dict[str, bool]: """Get the 'content_dict_str_bool' content from the message.""" enforce( self.is_set("content_dict_str_bool"), "'content_dict_str_bool' content is not set.", ) return cast(Dict[str, bool], self.get("content_dict_str_bool")) @property def content_dict_str_bytes(self) -> Dict[str, bytes]: """Get the 'content_dict_str_bytes' content from the message.""" enforce( self.is_set("content_dict_str_bytes"), "'content_dict_str_bytes' content is not set.", ) return cast(Dict[str, bytes], self.get("content_dict_str_bytes")) @property def content_dict_str_float(self) -> Dict[str, float]: """Get the 'content_dict_str_float' content from the message.""" enforce( self.is_set("content_dict_str_float"), "'content_dict_str_float' content is not set.", ) return cast(Dict[str, float], self.get("content_dict_str_float")) @property def content_dict_str_int(self) -> Dict[str, int]: """Get the 'content_dict_str_int' content from the message.""" enforce( self.is_set("content_dict_str_int"), "'content_dict_str_int' content is not set.", ) return cast(Dict[str, int], self.get("content_dict_str_int")) @property def content_dict_str_str(self) -> Dict[str, str]: """Get the 'content_dict_str_str' content from the message.""" enforce( self.is_set("content_dict_str_str"), "'content_dict_str_str' content is not set.", ) return cast(Dict[str, str], self.get("content_dict_str_str")) @property def content_float(self) -> float: """Get the 'content_float' content from the message.""" enforce(self.is_set("content_float"), "'content_float' content is not set.") return cast(float, self.get("content_float")) @property def content_int(self) -> int: """Get the 'content_int' content from the message.""" enforce(self.is_set("content_int"), "'content_int' content is not set.") return cast(int, self.get("content_int")) @property def content_list_bool(self) -> Tuple[bool, ...]: """Get the 'content_list_bool' content from the message.""" enforce( self.is_set("content_list_bool"), "'content_list_bool' content is not set." ) return cast(Tuple[bool, ...], self.get("content_list_bool")) @property def content_list_bytes(self) -> Tuple[bytes, ...]: """Get the 'content_list_bytes' content from the message.""" enforce( self.is_set("content_list_bytes"), "'content_list_bytes' content is not set.", ) return cast(Tuple[bytes, ...], self.get("content_list_bytes")) @property def content_list_float(self) -> Tuple[float, ...]: """Get the 'content_list_float' content from the message.""" enforce( self.is_set("content_list_float"), "'content_list_float' content is not set.", ) return cast(Tuple[float, ...], self.get("content_list_float")) @property def content_list_int(self) -> Tuple[int, ...]: """Get the 'content_list_int' content from the message.""" enforce( self.is_set("content_list_int"), "'content_list_int' content is not set." ) return cast(Tuple[int, ...], self.get("content_list_int")) @property def content_list_str(self) -> Tuple[str, ...]: """Get the 'content_list_str' content from the message.""" enforce( self.is_set("content_list_str"), "'content_list_str' content is not set." ) return cast(Tuple[str, ...], self.get("content_list_str")) @property def content_o_bool(self) -> Optional[bool]: """Get the 'content_o_bool' content from the message.""" return cast(Optional[bool], self.get("content_o_bool")) @property def content_o_ct(self) -> Optional[CustomDataModel4]: """Get the 'content_o_ct' content from the message.""" return cast(Optional[CustomDataModel4], self.get("content_o_ct")) @property def content_o_dict_str_int(self) -> Optional[Dict[str, int]]: """Get the 'content_o_dict_str_int' content from the message.""" return cast(Optional[Dict[str, int]], self.get("content_o_dict_str_int")) @property def content_o_list_bytes(self) -> Optional[Tuple[bytes, ...]]: """Get the 'content_o_list_bytes' content from the message.""" return cast(Optional[Tuple[bytes, ...]], self.get("content_o_list_bytes")) @property def content_o_set_int(self) -> Optional[FrozenSet[int]]: """Get the 'content_o_set_int' content from the message.""" return cast(Optional[FrozenSet[int]], self.get("content_o_set_int")) @property def content_set_bool(self) -> FrozenSet[bool]: """Get the 'content_set_bool' content from the message.""" enforce( self.is_set("content_set_bool"), "'content_set_bool' content is not set." ) return cast(FrozenSet[bool], self.get("content_set_bool")) @property def content_set_bytes(self) -> FrozenSet[bytes]: """Get the 'content_set_bytes' content from the message.""" enforce( self.is_set("content_set_bytes"), "'content_set_bytes' content is not set." ) return cast(FrozenSet[bytes], self.get("content_set_bytes")) @property def content_set_float(self) -> FrozenSet[float]: """Get the 'content_set_float' content from the message.""" enforce( self.is_set("content_set_float"), "'content_set_float' content is not set." ) return cast(FrozenSet[float], self.get("content_set_float")) @property def content_set_int(self) -> FrozenSet[int]: """Get the 'content_set_int' content from the message.""" enforce(self.is_set("content_set_int"), "'content_set_int' content is not set.") return cast(FrozenSet[int], self.get("content_set_int")) @property def content_set_str(self) -> FrozenSet[str]: """Get the 'content_set_str' content from the message.""" enforce(self.is_set("content_set_str"), "'content_set_str' content is not set.") return cast(FrozenSet[str], self.get("content_set_str")) @property def content_str(self) -> str: """Get the 'content_str' content from the message.""" enforce(self.is_set("content_str"), "'content_str' content is not set.") return cast(str, self.get("content_str")) @property def content_union_1( self, ) -> Union[ CustomDataModel1, bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int], ]: """Get the 'content_union_1' content from the message.""" enforce(self.is_set("content_union_1"), "'content_union_1' content is not set.") return cast( Union[ CustomDataModel1, bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int], ], self.get("content_union_1"), ) @property def content_union_2( self, ) -> Union[ FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes], int, ]: """Get the 'content_union_2' content from the message.""" enforce(self.is_set("content_union_2"), "'content_union_2' content is not set.") return cast( Union[ FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes], int, ], self.get("content_union_2"), ) @property def content_union_3(self) -> Union[CustomDataModel2, CustomDataModel3]: """Get the 'content_union_3' content from the message.""" enforce(self.is_set("content_union_3"), "'content_union_3' content is not set.") return cast( Union[CustomDataModel2, CustomDataModel3], self.get("content_union_3") ) def _is_consistent(self) -> bool: """Check that the message follows the t_protocol protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, TProtocolMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == TProtocolMessage.Performative.PERFORMATIVE_CT: expected_nb_of_contents = 1 enforce( isinstance(self.content_ct, CustomDataModel), "Invalid type for content 'content_ct'. Expected 'DataModel'. Found '{}'.".format( type(self.content_ct) ), ) elif self.performative == TProtocolMessage.Performative.PERFORMATIVE_PT: expected_nb_of_contents = 5 enforce( isinstance(self.content_bytes, bytes), "Invalid type for content 'content_bytes'. Expected 'bytes'. Found '{}'.".format( type(self.content_bytes) ), ) enforce( type(self.content_int) is int, "Invalid type for content 'content_int'. Expected 'int'. Found '{}'.".format( type(self.content_int) ), ) enforce( isinstance(self.content_float, float), "Invalid type for content 'content_float'. Expected 'float'. Found '{}'.".format( type(self.content_float) ), ) enforce( isinstance(self.content_bool, bool), "Invalid type for content 'content_bool'. Expected 'bool'. Found '{}'.".format( type(self.content_bool) ), ) enforce( isinstance(self.content_str, str), "Invalid type for content 'content_str'. Expected 'str'. Found '{}'.".format( type(self.content_str) ), ) elif self.performative == TProtocolMessage.Performative.PERFORMATIVE_PCT: expected_nb_of_contents = 10 enforce( isinstance(self.content_set_bytes, frozenset), "Invalid type for content 'content_set_bytes'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_bytes) ), ) enforce( all( isinstance(element, bytes) for element in self.content_set_bytes ), "Invalid type for frozenset elements in content 'content_set_bytes'. Expected 'bytes'.", ) enforce( isinstance(self.content_set_int, frozenset), "Invalid type for content 'content_set_int'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_int) ), ) enforce( all(type(element) is int for element in self.content_set_int), "Invalid type for frozenset elements in content 'content_set_int'. Expected 'int'.", ) enforce( isinstance(self.content_set_float, frozenset), "Invalid type for content 'content_set_float'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_float) ), ) enforce( all( isinstance(element, float) for element in self.content_set_float ), "Invalid type for frozenset elements in content 'content_set_float'. Expected 'float'.", ) enforce( isinstance(self.content_set_bool, frozenset), "Invalid type for content 'content_set_bool'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_bool) ), ) enforce( all(isinstance(element, bool) for element in self.content_set_bool), "Invalid type for frozenset elements in content 'content_set_bool'. Expected 'bool'.", ) enforce( isinstance(self.content_set_str, frozenset), "Invalid type for content 'content_set_str'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_str) ), ) enforce( all(isinstance(element, str) for element in self.content_set_str), "Invalid type for frozenset elements in content 'content_set_str'. Expected 'str'.", ) enforce( isinstance(self.content_list_bytes, tuple), "Invalid type for content 'content_list_bytes'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_bytes) ), ) enforce( all( isinstance(element, bytes) for element in self.content_list_bytes ), "Invalid type for tuple elements in content 'content_list_bytes'. Expected 'bytes'.", ) enforce( isinstance(self.content_list_int, tuple), "Invalid type for content 'content_list_int'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_int) ), ) enforce( all(type(element) is int for element in self.content_list_int), "Invalid type for tuple elements in content 'content_list_int'. Expected 'int'.", ) enforce( isinstance(self.content_list_float, tuple), "Invalid type for content 'content_list_float'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_float) ), ) enforce( all( isinstance(element, float) for element in self.content_list_float ), "Invalid type for tuple elements in content 'content_list_float'. Expected 'float'.", ) enforce( isinstance(self.content_list_bool, tuple), "Invalid type for content 'content_list_bool'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_bool) ), ) enforce( all( isinstance(element, bool) for element in self.content_list_bool ), "Invalid type for tuple elements in content 'content_list_bool'. Expected 'bool'.", ) enforce( isinstance(self.content_list_str, tuple), "Invalid type for content 'content_list_str'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_str) ), ) enforce( all(isinstance(element, str) for element in self.content_list_str), "Invalid type for tuple elements in content 'content_list_str'. Expected 'str'.", ) elif self.performative == TProtocolMessage.Performative.PERFORMATIVE_PMT: expected_nb_of_contents = 15 enforce( isinstance(self.content_dict_int_bytes, dict), "Invalid type for content 'content_dict_int_bytes'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_bytes) ), ) for ( key_of_content_dict_int_bytes, value_of_content_dict_int_bytes, ) in self.content_dict_int_bytes.items(): enforce( type(key_of_content_dict_int_bytes) is int, "Invalid type for dictionary keys in content 'content_dict_int_bytes'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_bytes) ), ) enforce( isinstance(value_of_content_dict_int_bytes, bytes), "Invalid type for dictionary values in content 'content_dict_int_bytes'. Expected 'bytes'. Found '{}'.".format( type(value_of_content_dict_int_bytes) ), ) enforce( isinstance(self.content_dict_int_int, dict), "Invalid type for content 'content_dict_int_int'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_int) ), ) for ( key_of_content_dict_int_int, value_of_content_dict_int_int, ) in self.content_dict_int_int.items(): enforce( type(key_of_content_dict_int_int) is int, "Invalid type for dictionary keys in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_int) ), ) enforce( type(value_of_content_dict_int_int) is int, "Invalid type for dictionary values in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_dict_int_int) ), ) enforce( isinstance(self.content_dict_int_float, dict), "Invalid type for content 'content_dict_int_float'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_float) ), ) for ( key_of_content_dict_int_float, value_of_content_dict_int_float, ) in self.content_dict_int_float.items(): enforce( type(key_of_content_dict_int_float) is int, "Invalid type for dictionary keys in content 'content_dict_int_float'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_float) ), ) enforce( isinstance(value_of_content_dict_int_float, float), "Invalid type for dictionary values in content 'content_dict_int_float'. Expected 'float'. Found '{}'.".format( type(value_of_content_dict_int_float) ), ) enforce( isinstance(self.content_dict_int_bool, dict), "Invalid type for content 'content_dict_int_bool'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_bool) ), ) for ( key_of_content_dict_int_bool, value_of_content_dict_int_bool, ) in self.content_dict_int_bool.items(): enforce( type(key_of_content_dict_int_bool) is int, "Invalid type for dictionary keys in content 'content_dict_int_bool'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_bool) ), ) enforce( isinstance(value_of_content_dict_int_bool, bool), "Invalid type for dictionary values in content 'content_dict_int_bool'. Expected 'bool'. Found '{}'.".format( type(value_of_content_dict_int_bool) ), ) enforce( isinstance(self.content_dict_int_str, dict), "Invalid type for content 'content_dict_int_str'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_str) ), ) for ( key_of_content_dict_int_str, value_of_content_dict_int_str, ) in self.content_dict_int_str.items(): enforce( type(key_of_content_dict_int_str) is int, "Invalid type for dictionary keys in content 'content_dict_int_str'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_str) ), ) enforce( isinstance(value_of_content_dict_int_str, str), "Invalid type for dictionary values in content 'content_dict_int_str'. Expected 'str'. Found '{}'.".format( type(value_of_content_dict_int_str) ), ) enforce( isinstance(self.content_dict_bool_bytes, dict), "Invalid type for content 'content_dict_bool_bytes'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_bytes) ), ) for ( key_of_content_dict_bool_bytes, value_of_content_dict_bool_bytes, ) in self.content_dict_bool_bytes.items(): enforce( isinstance(key_of_content_dict_bool_bytes, bool), "Invalid type for dictionary keys in content 'content_dict_bool_bytes'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_bytes) ), ) enforce( isinstance(value_of_content_dict_bool_bytes, bytes), "Invalid type for dictionary values in content 'content_dict_bool_bytes'. Expected 'bytes'. Found '{}'.".format( type(value_of_content_dict_bool_bytes) ), ) enforce( isinstance(self.content_dict_bool_int, dict), "Invalid type for content 'content_dict_bool_int'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_int) ), ) for ( key_of_content_dict_bool_int, value_of_content_dict_bool_int, ) in self.content_dict_bool_int.items(): enforce( isinstance(key_of_content_dict_bool_int, bool), "Invalid type for dictionary keys in content 'content_dict_bool_int'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_int) ), ) enforce( type(value_of_content_dict_bool_int) is int, "Invalid type for dictionary values in content 'content_dict_bool_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_dict_bool_int) ), ) enforce( isinstance(self.content_dict_bool_float, dict), "Invalid type for content 'content_dict_bool_float'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_float) ), ) for ( key_of_content_dict_bool_float, value_of_content_dict_bool_float, ) in self.content_dict_bool_float.items(): enforce( isinstance(key_of_content_dict_bool_float, bool), "Invalid type for dictionary keys in content 'content_dict_bool_float'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_float) ), ) enforce( isinstance(value_of_content_dict_bool_float, float), "Invalid type for dictionary values in content 'content_dict_bool_float'. Expected 'float'. Found '{}'.".format( type(value_of_content_dict_bool_float) ), ) enforce( isinstance(self.content_dict_bool_bool, dict), "Invalid type for content 'content_dict_bool_bool'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_bool) ), ) for ( key_of_content_dict_bool_bool, value_of_content_dict_bool_bool, ) in self.content_dict_bool_bool.items(): enforce( isinstance(key_of_content_dict_bool_bool, bool), "Invalid type for dictionary keys in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_bool) ), ) enforce( isinstance(value_of_content_dict_bool_bool, bool), "Invalid type for dictionary values in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( type(value_of_content_dict_bool_bool) ), ) enforce( isinstance(self.content_dict_bool_str, dict), "Invalid type for content 'content_dict_bool_str'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_str) ), ) for ( key_of_content_dict_bool_str, value_of_content_dict_bool_str, ) in self.content_dict_bool_str.items(): enforce( isinstance(key_of_content_dict_bool_str, bool), "Invalid type for dictionary keys in content 'content_dict_bool_str'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_str) ), ) enforce( isinstance(value_of_content_dict_bool_str, str), "Invalid type for dictionary values in content 'content_dict_bool_str'. Expected 'str'. Found '{}'.".format( type(value_of_content_dict_bool_str) ), ) enforce( isinstance(self.content_dict_str_bytes, dict), "Invalid type for content 'content_dict_str_bytes'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_bytes) ), ) for ( key_of_content_dict_str_bytes, value_of_content_dict_str_bytes, ) in self.content_dict_str_bytes.items(): enforce( isinstance(key_of_content_dict_str_bytes, str), "Invalid type for dictionary keys in content 'content_dict_str_bytes'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_bytes) ), ) enforce( isinstance(value_of_content_dict_str_bytes, bytes), "Invalid type for dictionary values in content 'content_dict_str_bytes'. Expected 'bytes'. Found '{}'.".format( type(value_of_content_dict_str_bytes) ), ) enforce( isinstance(self.content_dict_str_int, dict), "Invalid type for content 'content_dict_str_int'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_int) ), ) for ( key_of_content_dict_str_int, value_of_content_dict_str_int, ) in self.content_dict_str_int.items(): enforce( isinstance(key_of_content_dict_str_int, str), "Invalid type for dictionary keys in content 'content_dict_str_int'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_int) ), ) enforce( type(value_of_content_dict_str_int) is int, "Invalid type for dictionary values in content 'content_dict_str_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_dict_str_int) ), ) enforce( isinstance(self.content_dict_str_float, dict), "Invalid type for content 'content_dict_str_float'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_float) ), ) for ( key_of_content_dict_str_float, value_of_content_dict_str_float, ) in self.content_dict_str_float.items(): enforce( isinstance(key_of_content_dict_str_float, str), "Invalid type for dictionary keys in content 'content_dict_str_float'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_float) ), ) enforce( isinstance(value_of_content_dict_str_float, float), "Invalid type for dictionary values in content 'content_dict_str_float'. Expected 'float'. Found '{}'.".format( type(value_of_content_dict_str_float) ), ) enforce( isinstance(self.content_dict_str_bool, dict), "Invalid type for content 'content_dict_str_bool'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_bool) ), ) for ( key_of_content_dict_str_bool, value_of_content_dict_str_bool, ) in self.content_dict_str_bool.items(): enforce( isinstance(key_of_content_dict_str_bool, str), "Invalid type for dictionary keys in content 'content_dict_str_bool'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_bool) ), ) enforce( isinstance(value_of_content_dict_str_bool, bool), "Invalid type for dictionary values in content 'content_dict_str_bool'. Expected 'bool'. Found '{}'.".format( type(value_of_content_dict_str_bool) ), ) enforce( isinstance(self.content_dict_str_str, dict), "Invalid type for content 'content_dict_str_str'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_str) ), ) for ( key_of_content_dict_str_str, value_of_content_dict_str_str, ) in self.content_dict_str_str.items(): enforce( isinstance(key_of_content_dict_str_str, str), "Invalid type for dictionary keys in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_str) ), ) enforce( isinstance(value_of_content_dict_str_str, str), "Invalid type for dictionary values in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( type(value_of_content_dict_str_str) ), ) elif self.performative == TProtocolMessage.Performative.PERFORMATIVE_MT: expected_nb_of_contents = 3 enforce( isinstance(self.content_union_1, CustomDataModel1) or isinstance(self.content_union_1, bool) or isinstance(self.content_union_1, bytes) or isinstance(self.content_union_1, dict) or isinstance(self.content_union_1, float) or isinstance(self.content_union_1, frozenset) or type(self.content_union_1) is int or isinstance(self.content_union_1, str) or isinstance(self.content_union_1, tuple), "Invalid type for content 'content_union_1'. Expected either of '['DataModel1', 'bool', 'bytes', 'dict', 'float', 'frozenset', 'int', 'str', 'tuple']'. Found '{}'.".format( type(self.content_union_1) ), ) if isinstance(self.content_union_1, frozenset): enforce( all(type(element) is int for element in self.content_union_1), "Invalid type for elements of content 'content_union_1'. Expected 'int'.", ) if isinstance(self.content_union_1, tuple): enforce( all( isinstance(element, bool) for element in self.content_union_1 ), "Invalid type for tuple elements in content 'content_union_1'. Expected 'bool'.", ) if isinstance(self.content_union_1, dict): for ( key_of_content_union_1, value_of_content_union_1, ) in self.content_union_1.items(): enforce( ( isinstance(key_of_content_union_1, str) and type(value_of_content_union_1) is int ), "Invalid type for dictionary key, value in content 'content_union_1'. Expected 'str', 'int'.", ) enforce( isinstance(self.content_union_2, dict) or isinstance(self.content_union_2, frozenset) or type(self.content_union_2) is int or isinstance(self.content_union_2, tuple), "Invalid type for content 'content_union_2'. Expected either of '['dict', 'frozenset', 'int', 'tuple']'. Found '{}'.".format( type(self.content_union_2) ), ) if isinstance(self.content_union_2, frozenset): enforce( all( isinstance(element, bytes) for element in self.content_union_2 ) or all(type(element) is int for element in self.content_union_2) or all( isinstance(element, str) for element in self.content_union_2 ), "Invalid type for frozenset elements in content 'content_union_2'. Expected either 'bytes' or 'int' or 'str'.", ) if isinstance(self.content_union_2, tuple): enforce( all( isinstance(element, bool) for element in self.content_union_2 ) or all( isinstance(element, bytes) for element in self.content_union_2 ) or all( isinstance(element, float) for element in self.content_union_2 ), "Invalid type for tuple elements in content 'content_union_2'. Expected either 'bool' or 'bytes' or 'float'.", ) if isinstance(self.content_union_2, dict): for ( key_of_content_union_2, value_of_content_union_2, ) in self.content_union_2.items(): enforce( ( isinstance(key_of_content_union_2, bool) and isinstance(value_of_content_union_2, bytes) ) or ( type(key_of_content_union_2) is int and isinstance(value_of_content_union_2, float) ) or ( isinstance(key_of_content_union_2, str) and type(value_of_content_union_2) is int ), "Invalid type for dictionary key, value in content 'content_union_2'. Expected 'bool','bytes' or 'int','float' or 'str','int'.", ) enforce( isinstance(self.content_union_3, CustomDataModel2) or isinstance(self.content_union_3, CustomDataModel3), "Invalid type for content 'content_union_3'. Expected either of '['DataModel2', 'DataModel3']'. Found '{}'.".format( type(self.content_union_3) ), ) elif self.performative == TProtocolMessage.Performative.PERFORMATIVE_O: expected_nb_of_contents = 0 if self.is_set("content_o_ct"): expected_nb_of_contents += 1 content_o_ct = cast(CustomDataModel4, self.content_o_ct) enforce( isinstance(content_o_ct, CustomDataModel4), "Invalid type for content 'content_o_ct'. Expected 'DataModel4'. Found '{}'.".format( type(content_o_ct) ), ) if self.is_set("content_o_bool"): expected_nb_of_contents += 1 content_o_bool = cast(bool, self.content_o_bool) enforce( isinstance(content_o_bool, bool), "Invalid type for content 'content_o_bool'. Expected 'bool'. Found '{}'.".format( type(content_o_bool) ), ) if self.is_set("content_o_set_int"): expected_nb_of_contents += 1 content_o_set_int = cast(FrozenSet[int], self.content_o_set_int) enforce( isinstance(content_o_set_int, frozenset), "Invalid type for content 'content_o_set_int'. Expected 'frozenset'. Found '{}'.".format( type(content_o_set_int) ), ) enforce( all(type(element) is int for element in content_o_set_int), "Invalid type for frozenset elements in content 'content_o_set_int'. Expected 'int'.", ) if self.is_set("content_o_list_bytes"): expected_nb_of_contents += 1 content_o_list_bytes = cast( Tuple[bytes, ...], self.content_o_list_bytes ) enforce( isinstance(content_o_list_bytes, tuple), "Invalid type for content 'content_o_list_bytes'. Expected 'tuple'. Found '{}'.".format( type(content_o_list_bytes) ), ) enforce( all( isinstance(element, bytes) for element in content_o_list_bytes ), "Invalid type for tuple elements in content 'content_o_list_bytes'. Expected 'bytes'.", ) if self.is_set("content_o_dict_str_int"): expected_nb_of_contents += 1 content_o_dict_str_int = cast( Dict[str, int], self.content_o_dict_str_int ) enforce( isinstance(content_o_dict_str_int, dict), "Invalid type for content 'content_o_dict_str_int'. Expected 'dict'. Found '{}'.".format( type(content_o_dict_str_int) ), ) for ( key_of_content_o_dict_str_int, value_of_content_o_dict_str_int, ) in content_o_dict_str_int.items(): enforce( isinstance(key_of_content_o_dict_str_int, str), "Invalid type for dictionary keys in content 'content_o_dict_str_int'. Expected 'str'. Found '{}'.".format( type(key_of_content_o_dict_str_int) ), ) enforce( type(value_of_content_o_dict_str_int) is int, "Invalid type for dictionary values in content 'content_o_dict_str_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_o_dict_str_int) ), ) elif ( self.performative == TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS ): expected_nb_of_contents = 0 # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: tests/data/generator/t_protocol/protocol.yaml ================================================ name: t_protocol author: fetchai version: 0.1.0 protocol_specification_id: some_author/some_protocol_name:1.0.0 type: protocol description: A protocol for testing purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmNg4u2z7LVfhFYWiGjvabHoNQ26pyE1a3Z8df8nMVYjKu custom_types.py: QmYe8yi1PB7aGqEJ3z6LqTVUhQyf57Q6U1nCPsEfnEfTVQ dialogues.py: QmerAFMZq4g5fwTdC4CZ3hAG3XA7egXkPeYz95vBSLYHCZ message.py: QmZDVSvvrwrN6tNfipxPUjr6q4N9cqDTMQpenZPZMTtxEg serialization.py: QmXcTuZAbSDXFaCfajXPfFofT2koguvCLriJDx9wAHtSXT t_protocol.proto: QmY6Gw3Y7iKY2zR4kK6cx8cR1jjJivhLsSnP6HDk8843si t_protocol_pb2.py: QmWW1wUqN4X5VCQCmPP1TiHgpNpcNM1z59TnDPZuQ4L8aR fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: tests/data/generator/t_protocol/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for t_protocol protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from tests.data.generator.t_protocol import t_protocol_pb2 from tests.data.generator.t_protocol.custom_types import ( DataModel, DataModel1, DataModel2, DataModel3, DataModel4, ) from tests.data.generator.t_protocol.message import TProtocolMessage class TProtocolSerializer(Serializer): """Serialization for the 't_protocol' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'TProtocol' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(TProtocolMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() t_protocol_msg = t_protocol_pb2.TProtocolMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == TProtocolMessage.Performative.PERFORMATIVE_CT: performative = t_protocol_pb2.TProtocolMessage.Performative_Ct_Performative() # type: ignore content_ct = msg.content_ct DataModel.encode(performative.content_ct, content_ct) t_protocol_msg.performative_ct.CopyFrom(performative) elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PT: performative = t_protocol_pb2.TProtocolMessage.Performative_Pt_Performative() # type: ignore content_bytes = msg.content_bytes performative.content_bytes = content_bytes content_int = msg.content_int performative.content_int = content_int content_float = msg.content_float performative.content_float = content_float content_bool = msg.content_bool performative.content_bool = content_bool content_str = msg.content_str performative.content_str = content_str t_protocol_msg.performative_pt.CopyFrom(performative) elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PCT: performative = t_protocol_pb2.TProtocolMessage.Performative_Pct_Performative() # type: ignore content_set_bytes = msg.content_set_bytes performative.content_set_bytes.extend(content_set_bytes) content_set_int = msg.content_set_int performative.content_set_int.extend(content_set_int) content_set_float = msg.content_set_float performative.content_set_float.extend(content_set_float) content_set_bool = msg.content_set_bool performative.content_set_bool.extend(content_set_bool) content_set_str = msg.content_set_str performative.content_set_str.extend(content_set_str) content_list_bytes = msg.content_list_bytes performative.content_list_bytes.extend(content_list_bytes) content_list_int = msg.content_list_int performative.content_list_int.extend(content_list_int) content_list_float = msg.content_list_float performative.content_list_float.extend(content_list_float) content_list_bool = msg.content_list_bool performative.content_list_bool.extend(content_list_bool) content_list_str = msg.content_list_str performative.content_list_str.extend(content_list_str) t_protocol_msg.performative_pct.CopyFrom(performative) elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PMT: performative = t_protocol_pb2.TProtocolMessage.Performative_Pmt_Performative() # type: ignore content_dict_int_bytes = msg.content_dict_int_bytes performative.content_dict_int_bytes.update(content_dict_int_bytes) content_dict_int_int = msg.content_dict_int_int performative.content_dict_int_int.update(content_dict_int_int) content_dict_int_float = msg.content_dict_int_float performative.content_dict_int_float.update(content_dict_int_float) content_dict_int_bool = msg.content_dict_int_bool performative.content_dict_int_bool.update(content_dict_int_bool) content_dict_int_str = msg.content_dict_int_str performative.content_dict_int_str.update(content_dict_int_str) content_dict_bool_bytes = msg.content_dict_bool_bytes performative.content_dict_bool_bytes.update(content_dict_bool_bytes) content_dict_bool_int = msg.content_dict_bool_int performative.content_dict_bool_int.update(content_dict_bool_int) content_dict_bool_float = msg.content_dict_bool_float performative.content_dict_bool_float.update(content_dict_bool_float) content_dict_bool_bool = msg.content_dict_bool_bool performative.content_dict_bool_bool.update(content_dict_bool_bool) content_dict_bool_str = msg.content_dict_bool_str performative.content_dict_bool_str.update(content_dict_bool_str) content_dict_str_bytes = msg.content_dict_str_bytes performative.content_dict_str_bytes.update(content_dict_str_bytes) content_dict_str_int = msg.content_dict_str_int performative.content_dict_str_int.update(content_dict_str_int) content_dict_str_float = msg.content_dict_str_float performative.content_dict_str_float.update(content_dict_str_float) content_dict_str_bool = msg.content_dict_str_bool performative.content_dict_str_bool.update(content_dict_str_bool) content_dict_str_str = msg.content_dict_str_str performative.content_dict_str_str.update(content_dict_str_str) t_protocol_msg.performative_pmt.CopyFrom(performative) elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_MT: performative = t_protocol_pb2.TProtocolMessage.Performative_Mt_Performative() # type: ignore if msg.is_set("content_union_1"): if isinstance(msg.content_union_1, DataModel1): performative.content_union_1_type_DataModel1_is_set = True content_union_1_type_DataModel1 = msg.content_union_1 DataModel1.encode( performative.content_union_1_type_DataModel1, content_union_1_type_DataModel1, ) elif isinstance(msg.content_union_1, bytes): performative.content_union_1_type_bytes_is_set = True content_union_1_type_bytes = msg.content_union_1 performative.content_union_1_type_bytes = content_union_1_type_bytes elif isinstance(msg.content_union_1, int): performative.content_union_1_type_int_is_set = True content_union_1_type_int = msg.content_union_1 performative.content_union_1_type_int = content_union_1_type_int elif isinstance(msg.content_union_1, float): performative.content_union_1_type_float_is_set = True content_union_1_type_float = msg.content_union_1 performative.content_union_1_type_float = content_union_1_type_float elif isinstance(msg.content_union_1, bool): performative.content_union_1_type_bool_is_set = True content_union_1_type_bool = msg.content_union_1 performative.content_union_1_type_bool = content_union_1_type_bool elif isinstance(msg.content_union_1, str): performative.content_union_1_type_str_is_set = True content_union_1_type_str = msg.content_union_1 performative.content_union_1_type_str = content_union_1_type_str elif isinstance(msg.content_union_1, (set, frozenset)) and all( map(lambda x: isinstance(x, int), msg.content_union_1) ): performative.content_union_1_type_set_of_int_is_set = True content_union_1 = msg.content_union_1 performative.content_union_1_type_set_of_int.extend(content_union_1) elif isinstance(msg.content_union_1, (list, tuple)) and all( map(lambda x: isinstance(x, bool), msg.content_union_1) ): performative.content_union_1_type_list_of_bool_is_set = True content_union_1 = msg.content_union_1 performative.content_union_1_type_list_of_bool.extend( content_union_1 ) elif isinstance(msg.content_union_1, dict) and all( map( lambda x: isinstance(x[0], str) and isinstance(x[1], int), msg.content_union_1.items(), ) ): performative.content_union_1_type_dict_of_str_int_is_set = True content_union_1 = msg.content_union_1 performative.content_union_1_type_dict_of_str_int.update( content_union_1 ) elif msg.content_union_1 is None: pass else: raise ValueError( f"Bad value set to `content_union_1` {msg.content_union_1 }" ) if msg.is_set("content_union_2"): if isinstance(msg.content_union_2, (set, frozenset)) and all( map(lambda x: isinstance(x, bytes), msg.content_union_2) ): performative.content_union_2_type_set_of_bytes_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_set_of_bytes.extend( content_union_2 ) elif isinstance(msg.content_union_2, (set, frozenset)) and all( map(lambda x: isinstance(x, int), msg.content_union_2) ): performative.content_union_2_type_set_of_int_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_set_of_int.extend(content_union_2) elif isinstance(msg.content_union_2, (set, frozenset)) and all( map(lambda x: isinstance(x, str), msg.content_union_2) ): performative.content_union_2_type_set_of_str_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_set_of_str.extend(content_union_2) elif isinstance(msg.content_union_2, (list, tuple)) and all( map(lambda x: isinstance(x, float), msg.content_union_2) ): performative.content_union_2_type_list_of_float_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_list_of_float.extend( content_union_2 ) elif isinstance(msg.content_union_2, (list, tuple)) and all( map(lambda x: isinstance(x, bool), msg.content_union_2) ): performative.content_union_2_type_list_of_bool_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_list_of_bool.extend( content_union_2 ) elif isinstance(msg.content_union_2, (list, tuple)) and all( map(lambda x: isinstance(x, bytes), msg.content_union_2) ): performative.content_union_2_type_list_of_bytes_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_list_of_bytes.extend( content_union_2 ) elif isinstance(msg.content_union_2, dict) and all( map( lambda x: isinstance(x[0], str) and isinstance(x[1], int), msg.content_union_2.items(), ) ): performative.content_union_2_type_dict_of_str_int_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_dict_of_str_int.update( content_union_2 ) elif isinstance(msg.content_union_2, dict) and all( map( lambda x: isinstance(x[0], int) and isinstance(x[1], float), msg.content_union_2.items(), ) ): performative.content_union_2_type_dict_of_int_float_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_dict_of_int_float.update( content_union_2 ) elif isinstance(msg.content_union_2, dict) and all( map( lambda x: isinstance(x[0], bool) and isinstance(x[1], bytes), msg.content_union_2.items(), ) ): performative.content_union_2_type_dict_of_bool_bytes_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_dict_of_bool_bytes.update( content_union_2 ) elif isinstance(msg.content_union_2, int): performative.content_union_2_type_int_is_set = True content_union_2_type_int = msg.content_union_2 performative.content_union_2_type_int = content_union_2_type_int elif msg.content_union_2 is None: pass else: raise ValueError( f"Bad value set to `content_union_2` {msg.content_union_2 }" ) if msg.is_set("content_union_3"): if isinstance(msg.content_union_3, DataModel2): performative.content_union_3_type_DataModel2_is_set = True content_union_3_type_DataModel2 = msg.content_union_3 DataModel2.encode( performative.content_union_3_type_DataModel2, content_union_3_type_DataModel2, ) elif isinstance(msg.content_union_3, DataModel3): performative.content_union_3_type_DataModel3_is_set = True content_union_3_type_DataModel3 = msg.content_union_3 DataModel3.encode( performative.content_union_3_type_DataModel3, content_union_3_type_DataModel3, ) elif msg.content_union_3 is None: pass else: raise ValueError( f"Bad value set to `content_union_3` {msg.content_union_3 }" ) t_protocol_msg.performative_mt.CopyFrom(performative) elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_O: performative = t_protocol_pb2.TProtocolMessage.Performative_O_Performative() # type: ignore if msg.is_set("content_o_ct"): performative.content_o_ct_is_set = True content_o_ct = msg.content_o_ct DataModel4.encode(performative.content_o_ct, content_o_ct) if msg.is_set("content_o_bool"): performative.content_o_bool_is_set = True content_o_bool = msg.content_o_bool performative.content_o_bool = content_o_bool if msg.is_set("content_o_set_int"): performative.content_o_set_int_is_set = True content_o_set_int = msg.content_o_set_int performative.content_o_set_int.extend(content_o_set_int) if msg.is_set("content_o_list_bytes"): performative.content_o_list_bytes_is_set = True content_o_list_bytes = msg.content_o_list_bytes performative.content_o_list_bytes.extend(content_o_list_bytes) if msg.is_set("content_o_dict_str_int"): performative.content_o_dict_str_int_is_set = True content_o_dict_str_int = msg.content_o_dict_str_int performative.content_o_dict_str_int.update(content_o_dict_str_int) t_protocol_msg.performative_o.CopyFrom(performative) elif ( performative_id == TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS ): performative = t_protocol_pb2.TProtocolMessage.Performative_Empty_Contents_Performative() # type: ignore t_protocol_msg.performative_empty_contents.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = t_protocol_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'TProtocol' message. :param obj: the bytes object. :return: the 'TProtocol' message. """ message_pb = ProtobufMessage() t_protocol_pb = t_protocol_pb2.TProtocolMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target t_protocol_pb.ParseFromString(message_pb.dialogue_message.content) performative = t_protocol_pb.WhichOneof("performative") performative_id = TProtocolMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == TProtocolMessage.Performative.PERFORMATIVE_CT: pb2_content_ct = t_protocol_pb.performative_ct.content_ct content_ct = DataModel.decode(pb2_content_ct) performative_content["content_ct"] = content_ct elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PT: content_bytes = t_protocol_pb.performative_pt.content_bytes performative_content["content_bytes"] = content_bytes content_int = t_protocol_pb.performative_pt.content_int performative_content["content_int"] = content_int content_float = t_protocol_pb.performative_pt.content_float performative_content["content_float"] = content_float content_bool = t_protocol_pb.performative_pt.content_bool performative_content["content_bool"] = content_bool content_str = t_protocol_pb.performative_pt.content_str performative_content["content_str"] = content_str elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PCT: content_set_bytes = t_protocol_pb.performative_pct.content_set_bytes content_set_bytes_frozenset = frozenset(content_set_bytes) performative_content["content_set_bytes"] = content_set_bytes_frozenset content_set_int = t_protocol_pb.performative_pct.content_set_int content_set_int_frozenset = frozenset(content_set_int) performative_content["content_set_int"] = content_set_int_frozenset content_set_float = t_protocol_pb.performative_pct.content_set_float content_set_float_frozenset = frozenset(content_set_float) performative_content["content_set_float"] = content_set_float_frozenset content_set_bool = t_protocol_pb.performative_pct.content_set_bool content_set_bool_frozenset = frozenset(content_set_bool) performative_content["content_set_bool"] = content_set_bool_frozenset content_set_str = t_protocol_pb.performative_pct.content_set_str content_set_str_frozenset = frozenset(content_set_str) performative_content["content_set_str"] = content_set_str_frozenset content_list_bytes = t_protocol_pb.performative_pct.content_list_bytes content_list_bytes_tuple = tuple(content_list_bytes) performative_content["content_list_bytes"] = content_list_bytes_tuple content_list_int = t_protocol_pb.performative_pct.content_list_int content_list_int_tuple = tuple(content_list_int) performative_content["content_list_int"] = content_list_int_tuple content_list_float = t_protocol_pb.performative_pct.content_list_float content_list_float_tuple = tuple(content_list_float) performative_content["content_list_float"] = content_list_float_tuple content_list_bool = t_protocol_pb.performative_pct.content_list_bool content_list_bool_tuple = tuple(content_list_bool) performative_content["content_list_bool"] = content_list_bool_tuple content_list_str = t_protocol_pb.performative_pct.content_list_str content_list_str_tuple = tuple(content_list_str) performative_content["content_list_str"] = content_list_str_tuple elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PMT: content_dict_int_bytes = ( t_protocol_pb.performative_pmt.content_dict_int_bytes ) content_dict_int_bytes_dict = dict(content_dict_int_bytes) performative_content["content_dict_int_bytes"] = content_dict_int_bytes_dict content_dict_int_int = t_protocol_pb.performative_pmt.content_dict_int_int content_dict_int_int_dict = dict(content_dict_int_int) performative_content["content_dict_int_int"] = content_dict_int_int_dict content_dict_int_float = ( t_protocol_pb.performative_pmt.content_dict_int_float ) content_dict_int_float_dict = dict(content_dict_int_float) performative_content["content_dict_int_float"] = content_dict_int_float_dict content_dict_int_bool = t_protocol_pb.performative_pmt.content_dict_int_bool content_dict_int_bool_dict = dict(content_dict_int_bool) performative_content["content_dict_int_bool"] = content_dict_int_bool_dict content_dict_int_str = t_protocol_pb.performative_pmt.content_dict_int_str content_dict_int_str_dict = dict(content_dict_int_str) performative_content["content_dict_int_str"] = content_dict_int_str_dict content_dict_bool_bytes = ( t_protocol_pb.performative_pmt.content_dict_bool_bytes ) content_dict_bool_bytes_dict = dict(content_dict_bool_bytes) performative_content[ "content_dict_bool_bytes" ] = content_dict_bool_bytes_dict content_dict_bool_int = t_protocol_pb.performative_pmt.content_dict_bool_int content_dict_bool_int_dict = dict(content_dict_bool_int) performative_content["content_dict_bool_int"] = content_dict_bool_int_dict content_dict_bool_float = ( t_protocol_pb.performative_pmt.content_dict_bool_float ) content_dict_bool_float_dict = dict(content_dict_bool_float) performative_content[ "content_dict_bool_float" ] = content_dict_bool_float_dict content_dict_bool_bool = ( t_protocol_pb.performative_pmt.content_dict_bool_bool ) content_dict_bool_bool_dict = dict(content_dict_bool_bool) performative_content["content_dict_bool_bool"] = content_dict_bool_bool_dict content_dict_bool_str = t_protocol_pb.performative_pmt.content_dict_bool_str content_dict_bool_str_dict = dict(content_dict_bool_str) performative_content["content_dict_bool_str"] = content_dict_bool_str_dict content_dict_str_bytes = ( t_protocol_pb.performative_pmt.content_dict_str_bytes ) content_dict_str_bytes_dict = dict(content_dict_str_bytes) performative_content["content_dict_str_bytes"] = content_dict_str_bytes_dict content_dict_str_int = t_protocol_pb.performative_pmt.content_dict_str_int content_dict_str_int_dict = dict(content_dict_str_int) performative_content["content_dict_str_int"] = content_dict_str_int_dict content_dict_str_float = ( t_protocol_pb.performative_pmt.content_dict_str_float ) content_dict_str_float_dict = dict(content_dict_str_float) performative_content["content_dict_str_float"] = content_dict_str_float_dict content_dict_str_bool = t_protocol_pb.performative_pmt.content_dict_str_bool content_dict_str_bool_dict = dict(content_dict_str_bool) performative_content["content_dict_str_bool"] = content_dict_str_bool_dict content_dict_str_str = t_protocol_pb.performative_pmt.content_dict_str_str content_dict_str_str_dict = dict(content_dict_str_str) performative_content["content_dict_str_str"] = content_dict_str_str_dict elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_MT: if t_protocol_pb.performative_mt.content_union_1_type_DataModel1_is_set: pb2_content_union_1_type_DataModel1 = ( t_protocol_pb.performative_mt.content_union_1_type_DataModel1 ) content_union_1 = DataModel1.decode(pb2_content_union_1_type_DataModel1) performative_content["content_union_1"] = content_union_1 if t_protocol_pb.performative_mt.content_union_1_type_bytes_is_set: content_union_1 = ( t_protocol_pb.performative_mt.content_union_1_type_bytes ) performative_content["content_union_1"] = content_union_1 if t_protocol_pb.performative_mt.content_union_1_type_int_is_set: content_union_1 = t_protocol_pb.performative_mt.content_union_1_type_int performative_content["content_union_1"] = content_union_1 if t_protocol_pb.performative_mt.content_union_1_type_float_is_set: content_union_1 = ( t_protocol_pb.performative_mt.content_union_1_type_float ) performative_content["content_union_1"] = content_union_1 if t_protocol_pb.performative_mt.content_union_1_type_bool_is_set: content_union_1 = ( t_protocol_pb.performative_mt.content_union_1_type_bool ) performative_content["content_union_1"] = content_union_1 if t_protocol_pb.performative_mt.content_union_1_type_str_is_set: content_union_1 = t_protocol_pb.performative_mt.content_union_1_type_str performative_content["content_union_1"] = content_union_1 if t_protocol_pb.performative_mt.content_union_1_type_set_of_int_is_set: content_union_1 = ( t_protocol_pb.performative_mt.content_union_1_type_set_of_int ) content_union_1_frozenset = frozenset(content_union_1) performative_content["content_union_1"] = content_union_1_frozenset if t_protocol_pb.performative_mt.content_union_1_type_list_of_bool_is_set: content_union_1 = ( t_protocol_pb.performative_mt.content_union_1_type_list_of_bool ) content_union_1_tuple = tuple(content_union_1) performative_content["content_union_1"] = content_union_1_tuple if ( t_protocol_pb.performative_mt.content_union_1_type_dict_of_str_int_is_set ): content_union_1 = ( t_protocol_pb.performative_mt.content_union_1_type_dict_of_str_int ) content_union_1_dict = dict(content_union_1) performative_content["content_union_1"] = content_union_1_dict if t_protocol_pb.performative_mt.content_union_2_type_set_of_bytes_is_set: content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_set_of_bytes ) content_union_2_frozenset = frozenset(content_union_2) performative_content["content_union_2"] = content_union_2_frozenset if t_protocol_pb.performative_mt.content_union_2_type_set_of_int_is_set: content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_set_of_int ) content_union_2_frozenset = frozenset(content_union_2) performative_content["content_union_2"] = content_union_2_frozenset if t_protocol_pb.performative_mt.content_union_2_type_set_of_str_is_set: content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_set_of_str ) content_union_2_frozenset = frozenset(content_union_2) performative_content["content_union_2"] = content_union_2_frozenset if t_protocol_pb.performative_mt.content_union_2_type_list_of_float_is_set: content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_list_of_float ) content_union_2_tuple = tuple(content_union_2) performative_content["content_union_2"] = content_union_2_tuple if t_protocol_pb.performative_mt.content_union_2_type_list_of_bool_is_set: content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_list_of_bool ) content_union_2_tuple = tuple(content_union_2) performative_content["content_union_2"] = content_union_2_tuple if t_protocol_pb.performative_mt.content_union_2_type_list_of_bytes_is_set: content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_list_of_bytes ) content_union_2_tuple = tuple(content_union_2) performative_content["content_union_2"] = content_union_2_tuple if ( t_protocol_pb.performative_mt.content_union_2_type_dict_of_str_int_is_set ): content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_dict_of_str_int ) content_union_2_dict = dict(content_union_2) performative_content["content_union_2"] = content_union_2_dict if ( t_protocol_pb.performative_mt.content_union_2_type_dict_of_int_float_is_set ): content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_dict_of_int_float ) content_union_2_dict = dict(content_union_2) performative_content["content_union_2"] = content_union_2_dict if ( t_protocol_pb.performative_mt.content_union_2_type_dict_of_bool_bytes_is_set ): content_union_2 = ( t_protocol_pb.performative_mt.content_union_2_type_dict_of_bool_bytes ) content_union_2_dict = dict(content_union_2) performative_content["content_union_2"] = content_union_2_dict if t_protocol_pb.performative_mt.content_union_2_type_int_is_set: content_union_2 = t_protocol_pb.performative_mt.content_union_2_type_int performative_content["content_union_2"] = content_union_2 if t_protocol_pb.performative_mt.content_union_3_type_DataModel2_is_set: pb2_content_union_3_type_DataModel2 = ( t_protocol_pb.performative_mt.content_union_3_type_DataModel2 ) content_union_3 = DataModel2.decode(pb2_content_union_3_type_DataModel2) performative_content["content_union_3"] = content_union_3 if t_protocol_pb.performative_mt.content_union_3_type_DataModel3_is_set: pb2_content_union_3_type_DataModel3 = ( t_protocol_pb.performative_mt.content_union_3_type_DataModel3 ) content_union_3 = DataModel3.decode(pb2_content_union_3_type_DataModel3) performative_content["content_union_3"] = content_union_3 elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_O: if t_protocol_pb.performative_o.content_o_ct_is_set: pb2_content_o_ct = t_protocol_pb.performative_o.content_o_ct content_o_ct = DataModel4.decode(pb2_content_o_ct) performative_content["content_o_ct"] = content_o_ct if t_protocol_pb.performative_o.content_o_bool_is_set: content_o_bool = t_protocol_pb.performative_o.content_o_bool performative_content["content_o_bool"] = content_o_bool if t_protocol_pb.performative_o.content_o_set_int_is_set: content_o_set_int = t_protocol_pb.performative_o.content_o_set_int content_o_set_int_frozenset = frozenset(content_o_set_int) performative_content["content_o_set_int"] = content_o_set_int_frozenset if t_protocol_pb.performative_o.content_o_list_bytes_is_set: content_o_list_bytes = t_protocol_pb.performative_o.content_o_list_bytes content_o_list_bytes_tuple = tuple(content_o_list_bytes) performative_content[ "content_o_list_bytes" ] = content_o_list_bytes_tuple if t_protocol_pb.performative_o.content_o_dict_str_int_is_set: content_o_dict_str_int = ( t_protocol_pb.performative_o.content_o_dict_str_int ) content_o_dict_str_int_dict = dict(content_o_dict_str_int) performative_content[ "content_o_dict_str_int" ] = content_o_dict_str_int_dict elif ( performative_id == TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS ): pass else: raise ValueError("Performative not valid: {}.".format(performative_id)) return TProtocolMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content, ) ================================================ FILE: tests/data/generator/t_protocol/t_protocol.proto ================================================ syntax = "proto3"; package aea.some_author.some_protocol_name.v1_0_0; message TProtocolMessage{ // Custom Types message DataModel{ bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; } message DataModel1{ bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; } message DataModel2{ bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; } message DataModel3{ bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; } message DataModel4{ bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; } // Performatives and contents message Performative_Ct_Performative{ DataModel content_ct = 1; } message Performative_Pt_Performative{ bytes content_bytes = 1; int64 content_int = 2; float content_float = 3; bool content_bool = 4; string content_str = 5; } message Performative_Pct_Performative{ repeated bytes content_set_bytes = 1; repeated int64 content_set_int = 2; repeated float content_set_float = 3; repeated bool content_set_bool = 4; repeated string content_set_str = 5; repeated bytes content_list_bytes = 6; repeated int64 content_list_int = 7; repeated float content_list_float = 8; repeated bool content_list_bool = 9; repeated string content_list_str = 10; } message Performative_Pmt_Performative{ map content_dict_int_bytes = 1; map content_dict_int_int = 2; map content_dict_int_float = 3; map content_dict_int_bool = 4; map content_dict_int_str = 5; map content_dict_bool_bytes = 6; map content_dict_bool_int = 7; map content_dict_bool_float = 8; map content_dict_bool_bool = 9; map content_dict_bool_str = 10; map content_dict_str_bytes = 11; map content_dict_str_int = 12; map content_dict_str_float = 13; map content_dict_str_bool = 14; map content_dict_str_str = 15; } message Performative_Mt_Performative{ DataModel1 content_union_1_type_DataModel1 = 1; bool content_union_1_type_DataModel1_is_set = 2; bytes content_union_1_type_bytes = 3; bool content_union_1_type_bytes_is_set = 4; int64 content_union_1_type_int = 5; bool content_union_1_type_int_is_set = 6; float content_union_1_type_float = 7; bool content_union_1_type_float_is_set = 8; bool content_union_1_type_bool = 9; bool content_union_1_type_bool_is_set = 10; string content_union_1_type_str = 11; bool content_union_1_type_str_is_set = 12; repeated int64 content_union_1_type_set_of_int = 13; bool content_union_1_type_set_of_int_is_set = 14; repeated bool content_union_1_type_list_of_bool = 15; bool content_union_1_type_list_of_bool_is_set = 16; map content_union_1_type_dict_of_str_int = 17; bool content_union_1_type_dict_of_str_int_is_set = 18; repeated bytes content_union_2_type_set_of_bytes = 19; bool content_union_2_type_set_of_bytes_is_set = 20; repeated int64 content_union_2_type_set_of_int = 21; bool content_union_2_type_set_of_int_is_set = 22; repeated string content_union_2_type_set_of_str = 23; bool content_union_2_type_set_of_str_is_set = 24; repeated float content_union_2_type_list_of_float = 25; bool content_union_2_type_list_of_float_is_set = 26; repeated bool content_union_2_type_list_of_bool = 27; bool content_union_2_type_list_of_bool_is_set = 28; repeated bytes content_union_2_type_list_of_bytes = 29; bool content_union_2_type_list_of_bytes_is_set = 30; map content_union_2_type_dict_of_str_int = 31; bool content_union_2_type_dict_of_str_int_is_set = 32; map content_union_2_type_dict_of_int_float = 33; bool content_union_2_type_dict_of_int_float_is_set = 34; map content_union_2_type_dict_of_bool_bytes = 35; bool content_union_2_type_dict_of_bool_bytes_is_set = 36; int64 content_union_2_type_int = 37; bool content_union_2_type_int_is_set = 38; DataModel2 content_union_3_type_DataModel2 = 39; bool content_union_3_type_DataModel2_is_set = 40; DataModel3 content_union_3_type_DataModel3 = 41; bool content_union_3_type_DataModel3_is_set = 42; } message Performative_O_Performative{ DataModel4 content_o_ct = 1; bool content_o_ct_is_set = 2; bool content_o_bool = 3; bool content_o_bool_is_set = 4; repeated int64 content_o_set_int = 5; bool content_o_set_int_is_set = 6; repeated bytes content_o_list_bytes = 7; bool content_o_list_bytes_is_set = 8; map content_o_dict_str_int = 9; bool content_o_dict_str_int_is_set = 10; } message Performative_Empty_Contents_Performative{ } oneof performative{ Performative_Ct_Performative performative_ct = 5; Performative_Empty_Contents_Performative performative_empty_contents = 6; Performative_Mt_Performative performative_mt = 7; Performative_O_Performative performative_o = 8; Performative_Pct_Performative performative_pct = 9; Performative_Pmt_Performative performative_pmt = 10; Performative_Pt_Performative performative_pt = 11; } } ================================================ FILE: tests/data/generator/t_protocol/t_protocol_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: t_protocol.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\x10t_protocol.proto\x12)aea.some_author.some_protocol_name.v1_0_0\"\xcbK\n\x10TProtocolMessage\x12s\n\x0fperformative_ct\x18\x05 \x01(\x0b\x32X.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Ct_PerformativeH\x00\x12\x8b\x01\n\x1bperformative_empty_contents\x18\x06 \x01(\x0b\x32\x64.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Empty_Contents_PerformativeH\x00\x12s\n\x0fperformative_mt\x18\x07 \x01(\x0b\x32X.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_PerformativeH\x00\x12q\n\x0eperformative_o\x18\x08 \x01(\x0b\x32W.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_O_PerformativeH\x00\x12u\n\x10performative_pct\x18\t \x01(\x0b\x32Y.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pct_PerformativeH\x00\x12u\n\x10performative_pmt\x18\n \x01(\x0b\x32Y.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_PerformativeH\x00\x12s\n\x0fperformative_pt\x18\x0b \x01(\x0b\x32X.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pt_PerformativeH\x00\x1a\xb2\x02\n\tDataModel\x12\x13\n\x0b\x62ytes_field\x18\x01 \x01(\x0c\x12\x11\n\tint_field\x18\x02 \x01(\x03\x12\x13\n\x0b\x66loat_field\x18\x03 \x01(\x02\x12\x12\n\nbool_field\x18\x04 \x01(\x08\x12\x11\n\tstr_field\x18\x05 \x01(\t\x12\x11\n\tset_field\x18\x06 \x03(\x03\x12\x12\n\nlist_field\x18\x07 \x03(\t\x12h\n\ndict_field\x18\x08 \x03(\x0b\x32T.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel.DictFieldEntry\x1a\x30\n\x0e\x44ictFieldEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\xb4\x02\n\nDataModel1\x12\x13\n\x0b\x62ytes_field\x18\x01 \x01(\x0c\x12\x11\n\tint_field\x18\x02 \x01(\x03\x12\x13\n\x0b\x66loat_field\x18\x03 \x01(\x02\x12\x12\n\nbool_field\x18\x04 \x01(\x08\x12\x11\n\tstr_field\x18\x05 \x01(\t\x12\x11\n\tset_field\x18\x06 \x03(\x03\x12\x12\n\nlist_field\x18\x07 \x03(\t\x12i\n\ndict_field\x18\x08 \x03(\x0b\x32U.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel1.DictFieldEntry\x1a\x30\n\x0e\x44ictFieldEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\xb4\x02\n\nDataModel2\x12\x13\n\x0b\x62ytes_field\x18\x01 \x01(\x0c\x12\x11\n\tint_field\x18\x02 \x01(\x03\x12\x13\n\x0b\x66loat_field\x18\x03 \x01(\x02\x12\x12\n\nbool_field\x18\x04 \x01(\x08\x12\x11\n\tstr_field\x18\x05 \x01(\t\x12\x11\n\tset_field\x18\x06 \x03(\x03\x12\x12\n\nlist_field\x18\x07 \x03(\t\x12i\n\ndict_field\x18\x08 \x03(\x0b\x32U.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel2.DictFieldEntry\x1a\x30\n\x0e\x44ictFieldEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\xb4\x02\n\nDataModel3\x12\x13\n\x0b\x62ytes_field\x18\x01 \x01(\x0c\x12\x11\n\tint_field\x18\x02 \x01(\x03\x12\x13\n\x0b\x66loat_field\x18\x03 \x01(\x02\x12\x12\n\nbool_field\x18\x04 \x01(\x08\x12\x11\n\tstr_field\x18\x05 \x01(\t\x12\x11\n\tset_field\x18\x06 \x03(\x03\x12\x12\n\nlist_field\x18\x07 \x03(\t\x12i\n\ndict_field\x18\x08 \x03(\x0b\x32U.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel3.DictFieldEntry\x1a\x30\n\x0e\x44ictFieldEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\xb4\x02\n\nDataModel4\x12\x13\n\x0b\x62ytes_field\x18\x01 \x01(\x0c\x12\x11\n\tint_field\x18\x02 \x01(\x03\x12\x13\n\x0b\x66loat_field\x18\x03 \x01(\x02\x12\x12\n\nbool_field\x18\x04 \x01(\x08\x12\x11\n\tstr_field\x18\x05 \x01(\t\x12\x11\n\tset_field\x18\x06 \x03(\x03\x12\x12\n\nlist_field\x18\x07 \x03(\t\x12i\n\ndict_field\x18\x08 \x03(\x0b\x32U.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel4.DictFieldEntry\x1a\x30\n\x0e\x44ictFieldEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1ay\n\x1cPerformative_Ct_Performative\x12Y\n\ncontent_ct\x18\x01 \x01(\x0b\x32\x45.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel\x1a\x8c\x01\n\x1cPerformative_Pt_Performative\x12\x15\n\rcontent_bytes\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ontent_int\x18\x02 \x01(\x03\x12\x15\n\rcontent_float\x18\x03 \x01(\x02\x12\x14\n\x0c\x63ontent_bool\x18\x04 \x01(\x08\x12\x13\n\x0b\x63ontent_str\x18\x05 \x01(\t\x1a\xa8\x02\n\x1dPerformative_Pct_Performative\x12\x19\n\x11\x63ontent_set_bytes\x18\x01 \x03(\x0c\x12\x17\n\x0f\x63ontent_set_int\x18\x02 \x03(\x03\x12\x19\n\x11\x63ontent_set_float\x18\x03 \x03(\x02\x12\x18\n\x10\x63ontent_set_bool\x18\x04 \x03(\x08\x12\x17\n\x0f\x63ontent_set_str\x18\x05 \x03(\t\x12\x1a\n\x12\x63ontent_list_bytes\x18\x06 \x03(\x0c\x12\x18\n\x10\x63ontent_list_int\x18\x07 \x03(\x03\x12\x1a\n\x12\x63ontent_list_float\x18\x08 \x03(\x02\x12\x19\n\x11\x63ontent_list_bool\x18\t \x03(\x08\x12\x18\n\x10\x63ontent_list_str\x18\n \x03(\t\x1a\xc0\x18\n\x1dPerformative_Pmt_Performative\x12\x92\x01\n\x16\x63ontent_dict_int_bytes\x18\x01 \x03(\x0b\x32r.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry\x12\x8e\x01\n\x14\x63ontent_dict_int_int\x18\x02 \x03(\x0b\x32p.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry\x12\x92\x01\n\x16\x63ontent_dict_int_float\x18\x03 \x03(\x0b\x32r.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry\x12\x90\x01\n\x15\x63ontent_dict_int_bool\x18\x04 \x03(\x0b\x32q.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry\x12\x8e\x01\n\x14\x63ontent_dict_int_str\x18\x05 \x03(\x0b\x32p.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry\x12\x94\x01\n\x17\x63ontent_dict_bool_bytes\x18\x06 \x03(\x0b\x32s.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry\x12\x90\x01\n\x15\x63ontent_dict_bool_int\x18\x07 \x03(\x0b\x32q.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry\x12\x94\x01\n\x17\x63ontent_dict_bool_float\x18\x08 \x03(\x0b\x32s.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry\x12\x92\x01\n\x16\x63ontent_dict_bool_bool\x18\t \x03(\x0b\x32r.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry\x12\x90\x01\n\x15\x63ontent_dict_bool_str\x18\n \x03(\x0b\x32q.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry\x12\x92\x01\n\x16\x63ontent_dict_str_bytes\x18\x0b \x03(\x0b\x32r.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry\x12\x8e\x01\n\x14\x63ontent_dict_str_int\x18\x0c \x03(\x0b\x32p.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry\x12\x92\x01\n\x16\x63ontent_dict_str_float\x18\r \x03(\x0b\x32r.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry\x12\x90\x01\n\x15\x63ontent_dict_str_bool\x18\x0e \x03(\x0b\x32q.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry\x12\x8e\x01\n\x14\x63ontent_dict_str_str\x18\x0f \x03(\x0b\x32p.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry\x1a:\n\x18\x43ontentDictIntBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a:\n\x18\x43ontentDictIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictIntBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a:\n\x18\x43ontentDictBoolBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictStrBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xdd\x16\n\x1cPerformative_Mt_Performative\x12o\n\x1f\x63ontent_union_1_type_DataModel1\x18\x01 \x01(\x0b\x32\x46.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel1\x12.\n&content_union_1_type_DataModel1_is_set\x18\x02 \x01(\x08\x12\"\n\x1a\x63ontent_union_1_type_bytes\x18\x03 \x01(\x0c\x12)\n!content_union_1_type_bytes_is_set\x18\x04 \x01(\x08\x12 \n\x18\x63ontent_union_1_type_int\x18\x05 \x01(\x03\x12'\n\x1f\x63ontent_union_1_type_int_is_set\x18\x06 \x01(\x08\x12\"\n\x1a\x63ontent_union_1_type_float\x18\x07 \x01(\x02\x12)\n!content_union_1_type_float_is_set\x18\x08 \x01(\x08\x12!\n\x19\x63ontent_union_1_type_bool\x18\t \x01(\x08\x12(\n content_union_1_type_bool_is_set\x18\n \x01(\x08\x12 \n\x18\x63ontent_union_1_type_str\x18\x0b \x01(\t\x12'\n\x1f\x63ontent_union_1_type_str_is_set\x18\x0c \x01(\x08\x12'\n\x1f\x63ontent_union_1_type_set_of_int\x18\r \x03(\x03\x12.\n&content_union_1_type_set_of_int_is_set\x18\x0e \x01(\x08\x12)\n!content_union_1_type_list_of_bool\x18\x0f \x03(\x08\x12\x30\n(content_union_1_type_list_of_bool_is_set\x18\x10 \x01(\x08\x12\xa9\x01\n$content_union_1_type_dict_of_str_int\x18\x11 \x03(\x0b\x32{.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry\x12\x33\n+content_union_1_type_dict_of_str_int_is_set\x18\x12 \x01(\x08\x12)\n!content_union_2_type_set_of_bytes\x18\x13 \x03(\x0c\x12\x30\n(content_union_2_type_set_of_bytes_is_set\x18\x14 \x01(\x08\x12'\n\x1f\x63ontent_union_2_type_set_of_int\x18\x15 \x03(\x03\x12.\n&content_union_2_type_set_of_int_is_set\x18\x16 \x01(\x08\x12'\n\x1f\x63ontent_union_2_type_set_of_str\x18\x17 \x03(\t\x12.\n&content_union_2_type_set_of_str_is_set\x18\x18 \x01(\x08\x12*\n\"content_union_2_type_list_of_float\x18\x19 \x03(\x02\x12\x31\n)content_union_2_type_list_of_float_is_set\x18\x1a \x01(\x08\x12)\n!content_union_2_type_list_of_bool\x18\x1b \x03(\x08\x12\x30\n(content_union_2_type_list_of_bool_is_set\x18\x1c \x01(\x08\x12*\n\"content_union_2_type_list_of_bytes\x18\x1d \x03(\x0c\x12\x31\n)content_union_2_type_list_of_bytes_is_set\x18\x1e \x01(\x08\x12\xa9\x01\n$content_union_2_type_dict_of_str_int\x18\x1f \x03(\x0b\x32{.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry\x12\x33\n+content_union_2_type_dict_of_str_int_is_set\x18 \x01(\x08\x12\xad\x01\n&content_union_2_type_dict_of_int_float\x18! \x03(\x0b\x32}.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry\x12\x35\n-content_union_2_type_dict_of_int_float_is_set\x18\" \x01(\x08\x12\xaf\x01\n'content_union_2_type_dict_of_bool_bytes\x18# \x03(\x0b\x32~.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry\x12\x36\n.content_union_2_type_dict_of_bool_bytes_is_set\x18$ \x01(\x08\x12 \n\x18\x63ontent_union_2_type_int\x18% \x01(\x03\x12'\n\x1f\x63ontent_union_2_type_int_is_set\x18& \x01(\x08\x12o\n\x1f\x63ontent_union_3_type_DataModel2\x18' \x01(\x0b\x32\x46.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel2\x12.\n&content_union_3_type_DataModel2_is_set\x18( \x01(\x08\x12o\n\x1f\x63ontent_union_3_type_DataModel3\x18) \x01(\x0b\x32\x46.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel3\x12.\n&content_union_3_type_DataModel3_is_set\x18* \x01(\x08\x1a\x44\n\"ContentUnion1TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x44\n\"ContentUnion2TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x46\n$ContentUnion2TypeDictOfIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1aG\n%ContentUnion2TypeDictOfBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\xc3\x04\n\x1bPerformative_O_Performative\x12\\\n\x0c\x63ontent_o_ct\x18\x01 \x01(\x0b\x32\x46.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel4\x12\x1b\n\x13\x63ontent_o_ct_is_set\x18\x02 \x01(\x08\x12\x16\n\x0e\x63ontent_o_bool\x18\x03 \x01(\x08\x12\x1d\n\x15\x63ontent_o_bool_is_set\x18\x04 \x01(\x08\x12\x19\n\x11\x63ontent_o_set_int\x18\x05 \x03(\x03\x12 \n\x18\x63ontent_o_set_int_is_set\x18\x06 \x01(\x08\x12\x1c\n\x14\x63ontent_o_list_bytes\x18\x07 \x03(\x0c\x12#\n\x1b\x63ontent_o_list_bytes_is_set\x18\x08 \x01(\x08\x12\x8f\x01\n\x16\x63ontent_o_dict_str_int\x18\t \x03(\x0b\x32o.aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry\x12%\n\x1d\x63ontent_o_dict_str_int_is_set\x18\n \x01(\x08\x1a\x39\n\x17\x43ontentODictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a*\n(Performative_Empty_Contents_PerformativeB\x0e\n\x0cperformativeb\x06proto3" ) _TPROTOCOLMESSAGE = DESCRIPTOR.message_types_by_name["TProtocolMessage"] _TPROTOCOLMESSAGE_DATAMODEL = _TPROTOCOLMESSAGE.nested_types_by_name["DataModel"] _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY = ( _TPROTOCOLMESSAGE_DATAMODEL.nested_types_by_name["DictFieldEntry"] ) _TPROTOCOLMESSAGE_DATAMODEL1 = _TPROTOCOLMESSAGE.nested_types_by_name["DataModel1"] _TPROTOCOLMESSAGE_DATAMODEL1_DICTFIELDENTRY = ( _TPROTOCOLMESSAGE_DATAMODEL1.nested_types_by_name["DictFieldEntry"] ) _TPROTOCOLMESSAGE_DATAMODEL2 = _TPROTOCOLMESSAGE.nested_types_by_name["DataModel2"] _TPROTOCOLMESSAGE_DATAMODEL2_DICTFIELDENTRY = ( _TPROTOCOLMESSAGE_DATAMODEL2.nested_types_by_name["DictFieldEntry"] ) _TPROTOCOLMESSAGE_DATAMODEL3 = _TPROTOCOLMESSAGE.nested_types_by_name["DataModel3"] _TPROTOCOLMESSAGE_DATAMODEL3_DICTFIELDENTRY = ( _TPROTOCOLMESSAGE_DATAMODEL3.nested_types_by_name["DictFieldEntry"] ) _TPROTOCOLMESSAGE_DATAMODEL4 = _TPROTOCOLMESSAGE.nested_types_by_name["DataModel4"] _TPROTOCOLMESSAGE_DATAMODEL4_DICTFIELDENTRY = ( _TPROTOCOLMESSAGE_DATAMODEL4.nested_types_by_name["DictFieldEntry"] ) _TPROTOCOLMESSAGE_PERFORMATIVE_CT_PERFORMATIVE = _TPROTOCOLMESSAGE.nested_types_by_name[ "Performative_Ct_Performative" ] _TPROTOCOLMESSAGE_PERFORMATIVE_PT_PERFORMATIVE = _TPROTOCOLMESSAGE.nested_types_by_name[ "Performative_Pt_Performative" ] _TPROTOCOLMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE = ( _TPROTOCOLMESSAGE.nested_types_by_name["Performative_Pct_Performative"] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE = ( _TPROTOCOLMESSAGE.nested_types_by_name["Performative_Pmt_Performative"] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntBytesEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntIntEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntFloatEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntBoolEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntStrEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolBytesEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolIntEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolFloatEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolBoolEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolStrEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrBytesEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrIntEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrFloatEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrBoolEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrStrEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE = _TPROTOCOLMESSAGE.nested_types_by_name[ "Performative_Mt_Performative" ] _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion1TypeDictOfStrIntEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion2TypeDictOfStrIntEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion2TypeDictOfIntFloatEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion2TypeDictOfBoolBytesEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE = _TPROTOCOLMESSAGE.nested_types_by_name[ "Performative_O_Performative" ] _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY = ( _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE.nested_types_by_name[ "ContentODictStrIntEntry" ] ) _TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE = ( _TPROTOCOLMESSAGE.nested_types_by_name["Performative_Empty_Contents_Performative"] ) TProtocolMessage = _reflection.GeneratedProtocolMessageType( "TProtocolMessage", (_message.Message,), { "DataModel": _reflection.GeneratedProtocolMessageType( "DataModel", (_message.Message,), { "DictFieldEntry": _reflection.GeneratedProtocolMessageType( "DictFieldEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel.DictFieldEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel) }, ), "DataModel1": _reflection.GeneratedProtocolMessageType( "DataModel1", (_message.Message,), { "DictFieldEntry": _reflection.GeneratedProtocolMessageType( "DictFieldEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL1_DICTFIELDENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel1.DictFieldEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL1, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel1) }, ), "DataModel2": _reflection.GeneratedProtocolMessageType( "DataModel2", (_message.Message,), { "DictFieldEntry": _reflection.GeneratedProtocolMessageType( "DictFieldEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL2_DICTFIELDENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel2.DictFieldEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL2, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel2) }, ), "DataModel3": _reflection.GeneratedProtocolMessageType( "DataModel3", (_message.Message,), { "DictFieldEntry": _reflection.GeneratedProtocolMessageType( "DictFieldEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL3_DICTFIELDENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel3.DictFieldEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL3, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel3) }, ), "DataModel4": _reflection.GeneratedProtocolMessageType( "DataModel4", (_message.Message,), { "DictFieldEntry": _reflection.GeneratedProtocolMessageType( "DictFieldEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL4_DICTFIELDENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel4.DictFieldEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL4, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.DataModel4) }, ), "Performative_Ct_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Ct_Performative", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_CT_PERFORMATIVE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Ct_Performative) }, ), "Performative_Pt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pt_Performative", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PT_PERFORMATIVE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pt_Performative) }, ), "Performative_Pct_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pct_Performative", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pct_Performative) }, ), "Performative_Pmt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pmt_Performative", (_message.Message,), { "ContentDictIntBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry) }, ), "ContentDictIntIntEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry) }, ), "ContentDictIntFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry) }, ), "ContentDictIntBoolEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntBoolEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry) }, ), "ContentDictIntStrEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntStrEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry) }, ), "ContentDictBoolBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry) }, ), "ContentDictBoolIntEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry) }, ), "ContentDictBoolFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry) }, ), "ContentDictBoolBoolEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolBoolEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry) }, ), "ContentDictBoolStrEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolStrEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry) }, ), "ContentDictStrBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry) }, ), "ContentDictStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry) }, ), "ContentDictStrFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry) }, ), "ContentDictStrBoolEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrBoolEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry) }, ), "ContentDictStrStrEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrStrEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Pmt_Performative) }, ), "Performative_Mt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Mt_Performative", (_message.Message,), { "ContentUnion1TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion1TypeDictOfStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry) }, ), "ContentUnion2TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry) }, ), "ContentUnion2TypeDictOfIntFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfIntFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry) }, ), "ContentUnion2TypeDictOfBoolBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfBoolBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Mt_Performative) }, ), "Performative_O_Performative": _reflection.GeneratedProtocolMessageType( "Performative_O_Performative", (_message.Message,), { "ContentODictStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentODictStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_O_Performative) }, ), "Performative_Empty_Contents_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Empty_Contents_Performative", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage.Performative_Empty_Contents_Performative) }, ), "DESCRIPTOR": _TPROTOCOLMESSAGE, "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolMessage) }, ) _sym_db.RegisterMessage(TProtocolMessage) _sym_db.RegisterMessage(TProtocolMessage.DataModel) _sym_db.RegisterMessage(TProtocolMessage.DataModel.DictFieldEntry) _sym_db.RegisterMessage(TProtocolMessage.DataModel1) _sym_db.RegisterMessage(TProtocolMessage.DataModel1.DictFieldEntry) _sym_db.RegisterMessage(TProtocolMessage.DataModel2) _sym_db.RegisterMessage(TProtocolMessage.DataModel2.DictFieldEntry) _sym_db.RegisterMessage(TProtocolMessage.DataModel3) _sym_db.RegisterMessage(TProtocolMessage.DataModel3.DictFieldEntry) _sym_db.RegisterMessage(TProtocolMessage.DataModel4) _sym_db.RegisterMessage(TProtocolMessage.DataModel4.DictFieldEntry) _sym_db.RegisterMessage(TProtocolMessage.Performative_Ct_Performative) _sym_db.RegisterMessage(TProtocolMessage.Performative_Pt_Performative) _sym_db.RegisterMessage(TProtocolMessage.Performative_Pct_Performative) _sym_db.RegisterMessage(TProtocolMessage.Performative_Pmt_Performative) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry ) _sym_db.RegisterMessage(TProtocolMessage.Performative_Mt_Performative) _sym_db.RegisterMessage( TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry ) _sym_db.RegisterMessage( TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry ) _sym_db.RegisterMessage(TProtocolMessage.Performative_O_Performative) _sym_db.RegisterMessage( TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry ) _sym_db.RegisterMessage(TProtocolMessage.Performative_Empty_Contents_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY._options = None _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY._serialized_options = b"8\001" _TPROTOCOLMESSAGE_DATAMODEL1_DICTFIELDENTRY._options = None _TPROTOCOLMESSAGE_DATAMODEL1_DICTFIELDENTRY._serialized_options = b"8\001" _TPROTOCOLMESSAGE_DATAMODEL2_DICTFIELDENTRY._options = None _TPROTOCOLMESSAGE_DATAMODEL2_DICTFIELDENTRY._serialized_options = b"8\001" _TPROTOCOLMESSAGE_DATAMODEL3_DICTFIELDENTRY._options = None _TPROTOCOLMESSAGE_DATAMODEL3_DICTFIELDENTRY._serialized_options = b"8\001" _TPROTOCOLMESSAGE_DATAMODEL4_DICTFIELDENTRY._options = None _TPROTOCOLMESSAGE_DATAMODEL4_DICTFIELDENTRY._serialized_options = b"8\001" _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._options = ( None ) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLMESSAGE._serialized_start = 64 _TPROTOCOLMESSAGE._serialized_end = 9739 _TPROTOCOLMESSAGE_DATAMODEL._serialized_start = 931 _TPROTOCOLMESSAGE_DATAMODEL._serialized_end = 1237 _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY._serialized_start = 1189 _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY._serialized_end = 1237 _TPROTOCOLMESSAGE_DATAMODEL1._serialized_start = 1240 _TPROTOCOLMESSAGE_DATAMODEL1._serialized_end = 1548 _TPROTOCOLMESSAGE_DATAMODEL1_DICTFIELDENTRY._serialized_start = 1189 _TPROTOCOLMESSAGE_DATAMODEL1_DICTFIELDENTRY._serialized_end = 1237 _TPROTOCOLMESSAGE_DATAMODEL2._serialized_start = 1551 _TPROTOCOLMESSAGE_DATAMODEL2._serialized_end = 1859 _TPROTOCOLMESSAGE_DATAMODEL2_DICTFIELDENTRY._serialized_start = 1189 _TPROTOCOLMESSAGE_DATAMODEL2_DICTFIELDENTRY._serialized_end = 1237 _TPROTOCOLMESSAGE_DATAMODEL3._serialized_start = 1862 _TPROTOCOLMESSAGE_DATAMODEL3._serialized_end = 2170 _TPROTOCOLMESSAGE_DATAMODEL3_DICTFIELDENTRY._serialized_start = 1189 _TPROTOCOLMESSAGE_DATAMODEL3_DICTFIELDENTRY._serialized_end = 1237 _TPROTOCOLMESSAGE_DATAMODEL4._serialized_start = 2173 _TPROTOCOLMESSAGE_DATAMODEL4._serialized_end = 2481 _TPROTOCOLMESSAGE_DATAMODEL4_DICTFIELDENTRY._serialized_start = 1189 _TPROTOCOLMESSAGE_DATAMODEL4_DICTFIELDENTRY._serialized_end = 1237 _TPROTOCOLMESSAGE_PERFORMATIVE_CT_PERFORMATIVE._serialized_start = 2483 _TPROTOCOLMESSAGE_PERFORMATIVE_CT_PERFORMATIVE._serialized_end = 2604 _TPROTOCOLMESSAGE_PERFORMATIVE_PT_PERFORMATIVE._serialized_start = 2607 _TPROTOCOLMESSAGE_PERFORMATIVE_PT_PERFORMATIVE._serialized_end = 2747 _TPROTOCOLMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE._serialized_start = 2750 _TPROTOCOLMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE._serialized_end = 3046 _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE._serialized_start = 3049 _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE._serialized_end = 6185 _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._serialized_start = ( 5297 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._serialized_end = ( 5355 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._serialized_start = ( 5357 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._serialized_end = ( 5413 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._serialized_start = ( 5415 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._serialized_end = ( 5473 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._serialized_start = ( 5475 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._serialized_end = ( 5532 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._serialized_start = ( 5534 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._serialized_end = ( 5590 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._serialized_start = ( 5592 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._serialized_end = ( 5651 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._serialized_start = ( 5653 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._serialized_end = ( 5710 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._serialized_start = ( 5712 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._serialized_end = ( 5771 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._serialized_start = ( 5773 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._serialized_end = ( 5831 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._serialized_start = ( 5833 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._serialized_end = ( 5890 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._serialized_start = ( 5892 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._serialized_end = ( 5950 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._serialized_start = ( 5952 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._serialized_end = ( 6008 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._serialized_start = ( 6010 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._serialized_end = ( 6068 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._serialized_start = ( 6070 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._serialized_end = ( 6127 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._serialized_start = ( 6129 ) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._serialized_end = ( 6185 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE._serialized_start = 6188 _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE._serialized_end = 9097 _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._serialized_start = ( 8814 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._serialized_end = ( 8882 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._serialized_start = ( 8884 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._serialized_end = ( 8952 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._serialized_start = ( 8954 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._serialized_end = ( 9024 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._serialized_start = ( 9026 ) _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._serialized_end = ( 9097 ) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE._serialized_start = 9100 _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE._serialized_end = 9679 _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._serialized_start = ( 9622 ) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._serialized_end = ( 9679 ) _TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE._serialized_start = 9681 _TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE._serialized_end = 9723 # @@protoc_insertion_point(module_scope) ================================================ FILE: tests/data/generator/t_protocol_no_ct/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the support resources for the t_protocol_no_ct protocol. It was created with protocol buffer compiler version `libprotoc 3.19.4` and aea version `1.2.5`. """ from tests.data.generator.t_protocol_no_ct.message import TProtocolNoCtMessage from tests.data.generator.t_protocol_no_ct.serialization import TProtocolNoCtSerializer TProtocolNoCtMessage.serializer = TProtocolNoCtSerializer ================================================ FILE: tests/data/generator/t_protocol_no_ct/dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the classes required for t_protocol_no_ct dialogue management. - TProtocolNoCtDialogue: The dialogue class maintains state of a dialogue and manages it. - TProtocolNoCtDialogues: The dialogues class keeps track of all dialogues. """ from abc import ABC from typing import Callable, Dict, FrozenSet, Type, cast from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, Dialogues from tests.data.generator.t_protocol_no_ct.message import TProtocolNoCtMessage class TProtocolNoCtDialogue(Dialogue): """The t_protocol_no_ct dialogue class maintains state of a dialogue and manages it.""" INITIAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( {TProtocolNoCtMessage.Performative.PERFORMATIVE_PT} ) TERMINAL_PERFORMATIVES: FrozenSet[Message.Performative] = frozenset( { TProtocolNoCtMessage.Performative.PERFORMATIVE_MT, TProtocolNoCtMessage.Performative.PERFORMATIVE_O, } ) VALID_REPLIES: Dict[Message.Performative, FrozenSet[Message.Performative]] = { TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS: frozenset( {TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS} ), TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: frozenset(), TProtocolNoCtMessage.Performative.PERFORMATIVE_O: frozenset(), TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT: frozenset( { TProtocolNoCtMessage.Performative.PERFORMATIVE_MT, TProtocolNoCtMessage.Performative.PERFORMATIVE_O, } ), TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT: frozenset( { TProtocolNoCtMessage.Performative.PERFORMATIVE_MT, TProtocolNoCtMessage.Performative.PERFORMATIVE_O, } ), TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: frozenset( { TProtocolNoCtMessage.Performative.PERFORMATIVE_PT, TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT, TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT, } ), } class Role(Dialogue.Role): """This class defines the agent's role in a t_protocol_no_ct dialogue.""" ROLE_1 = "role_1" ROLE_2 = "role_2" class EndState(Dialogue.EndState): """This class defines the end states of a t_protocol_no_ct dialogue.""" END_STATE_1 = 0 END_STATE_2 = 1 END_STATE_3 = 2 def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: Dialogue.Role, message_class: Type[TProtocolNoCtMessage] = TProtocolNoCtMessage, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :param message_class: the message class used """ Dialogue.__init__( self, dialogue_label=dialogue_label, message_class=message_class, self_address=self_address, role=role, ) class TProtocolNoCtDialogues(Dialogues, ABC): """This class keeps track of all t_protocol_no_ct dialogues.""" END_STATES = frozenset( { TProtocolNoCtDialogue.EndState.END_STATE_1, TProtocolNoCtDialogue.EndState.END_STATE_2, TProtocolNoCtDialogue.EndState.END_STATE_3, } ) _keep_terminal_state_dialogues = True def __init__( self, self_address: Address, role_from_first_message: Callable[[Message, Address], Dialogue.Role], dialogue_class: Type[TProtocolNoCtDialogue] = TProtocolNoCtDialogue, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :param dialogue_class: the dialogue class used :param role_from_first_message: the callable determining role from first message """ Dialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), message_class=TProtocolNoCtMessage, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, ) ================================================ FILE: tests/data/generator/t_protocol_no_ct/message.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains t_protocol_no_ct's message definition.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,too-many-branches,not-an-iterable,unidiomatic-typecheck,unsubscriptable-object import logging from typing import Any, Dict, FrozenSet, Optional, Set, Tuple, Union, cast from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message _default_logger = logging.getLogger( "aea.packages.fetchai.protocols.t_protocol_no_ct.message" ) DEFAULT_BODY_SIZE = 4 class TProtocolNoCtMessage(Message): """A protocol for testing purposes.""" protocol_id = PublicId.from_str("fetchai/t_protocol_no_ct:0.1.0") protocol_specification_id = PublicId.from_str( "some_author/some_protocol_name:1.0.0" ) class Performative(Message.Performative): """Performatives for the t_protocol_no_ct protocol.""" PERFORMATIVE_EMPTY_CONTENTS = "performative_empty_contents" PERFORMATIVE_MT = "performative_mt" PERFORMATIVE_O = "performative_o" PERFORMATIVE_PCT = "performative_pct" PERFORMATIVE_PMT = "performative_pmt" PERFORMATIVE_PT = "performative_pt" def __str__(self) -> str: """Get the string representation.""" return str(self.value) _performatives = { "performative_empty_contents", "performative_mt", "performative_o", "performative_pct", "performative_pmt", "performative_pt", } __slots__: Tuple[str, ...] = tuple() class _SlotsCls: __slots__ = ( "content_bool", "content_bytes", "content_dict_bool_bool", "content_dict_bool_bytes", "content_dict_bool_float", "content_dict_bool_int", "content_dict_bool_str", "content_dict_int_bool", "content_dict_int_bytes", "content_dict_int_float", "content_dict_int_int", "content_dict_int_str", "content_dict_str_bool", "content_dict_str_bytes", "content_dict_str_float", "content_dict_str_int", "content_dict_str_str", "content_float", "content_int", "content_list_bool", "content_list_bytes", "content_list_float", "content_list_int", "content_list_str", "content_o_bool", "content_o_dict_str_int", "content_o_list_bytes", "content_o_set_int", "content_set_bool", "content_set_bytes", "content_set_float", "content_set_int", "content_set_str", "content_str", "content_union_1", "content_union_2", "dialogue_reference", "message_id", "performative", "target", ) def __init__( self, performative: Performative, dialogue_reference: Tuple[str, str] = ("", ""), message_id: int = 1, target: int = 0, **kwargs: Any, ): """ Initialise an instance of TProtocolNoCtMessage. :param message_id: the message id. :param dialogue_reference: the dialogue reference. :param target: the message target. :param performative: the message performative. :param **kwargs: extra options. """ super().__init__( dialogue_reference=dialogue_reference, message_id=message_id, target=target, performative=TProtocolNoCtMessage.Performative(performative), **kwargs, ) @property def valid_performatives(self) -> Set[str]: """Get valid performatives.""" return self._performatives @property def dialogue_reference(self) -> Tuple[str, str]: """Get the dialogue_reference of the message.""" enforce(self.is_set("dialogue_reference"), "dialogue_reference is not set.") return cast(Tuple[str, str], self.get("dialogue_reference")) @property def message_id(self) -> int: """Get the message_id of the message.""" enforce(self.is_set("message_id"), "message_id is not set.") return cast(int, self.get("message_id")) @property def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" enforce(self.is_set("performative"), "performative is not set.") return cast(TProtocolNoCtMessage.Performative, self.get("performative")) @property def target(self) -> int: """Get the target of the message.""" enforce(self.is_set("target"), "target is not set.") return cast(int, self.get("target")) @property def content_bool(self) -> bool: """Get the 'content_bool' content from the message.""" enforce(self.is_set("content_bool"), "'content_bool' content is not set.") return cast(bool, self.get("content_bool")) @property def content_bytes(self) -> bytes: """Get the 'content_bytes' content from the message.""" enforce(self.is_set("content_bytes"), "'content_bytes' content is not set.") return cast(bytes, self.get("content_bytes")) @property def content_dict_bool_bool(self) -> Dict[bool, bool]: """Get the 'content_dict_bool_bool' content from the message.""" enforce( self.is_set("content_dict_bool_bool"), "'content_dict_bool_bool' content is not set.", ) return cast(Dict[bool, bool], self.get("content_dict_bool_bool")) @property def content_dict_bool_bytes(self) -> Dict[bool, bytes]: """Get the 'content_dict_bool_bytes' content from the message.""" enforce( self.is_set("content_dict_bool_bytes"), "'content_dict_bool_bytes' content is not set.", ) return cast(Dict[bool, bytes], self.get("content_dict_bool_bytes")) @property def content_dict_bool_float(self) -> Dict[bool, float]: """Get the 'content_dict_bool_float' content from the message.""" enforce( self.is_set("content_dict_bool_float"), "'content_dict_bool_float' content is not set.", ) return cast(Dict[bool, float], self.get("content_dict_bool_float")) @property def content_dict_bool_int(self) -> Dict[bool, int]: """Get the 'content_dict_bool_int' content from the message.""" enforce( self.is_set("content_dict_bool_int"), "'content_dict_bool_int' content is not set.", ) return cast(Dict[bool, int], self.get("content_dict_bool_int")) @property def content_dict_bool_str(self) -> Dict[bool, str]: """Get the 'content_dict_bool_str' content from the message.""" enforce( self.is_set("content_dict_bool_str"), "'content_dict_bool_str' content is not set.", ) return cast(Dict[bool, str], self.get("content_dict_bool_str")) @property def content_dict_int_bool(self) -> Dict[int, bool]: """Get the 'content_dict_int_bool' content from the message.""" enforce( self.is_set("content_dict_int_bool"), "'content_dict_int_bool' content is not set.", ) return cast(Dict[int, bool], self.get("content_dict_int_bool")) @property def content_dict_int_bytes(self) -> Dict[int, bytes]: """Get the 'content_dict_int_bytes' content from the message.""" enforce( self.is_set("content_dict_int_bytes"), "'content_dict_int_bytes' content is not set.", ) return cast(Dict[int, bytes], self.get("content_dict_int_bytes")) @property def content_dict_int_float(self) -> Dict[int, float]: """Get the 'content_dict_int_float' content from the message.""" enforce( self.is_set("content_dict_int_float"), "'content_dict_int_float' content is not set.", ) return cast(Dict[int, float], self.get("content_dict_int_float")) @property def content_dict_int_int(self) -> Dict[int, int]: """Get the 'content_dict_int_int' content from the message.""" enforce( self.is_set("content_dict_int_int"), "'content_dict_int_int' content is not set.", ) return cast(Dict[int, int], self.get("content_dict_int_int")) @property def content_dict_int_str(self) -> Dict[int, str]: """Get the 'content_dict_int_str' content from the message.""" enforce( self.is_set("content_dict_int_str"), "'content_dict_int_str' content is not set.", ) return cast(Dict[int, str], self.get("content_dict_int_str")) @property def content_dict_str_bool(self) -> Dict[str, bool]: """Get the 'content_dict_str_bool' content from the message.""" enforce( self.is_set("content_dict_str_bool"), "'content_dict_str_bool' content is not set.", ) return cast(Dict[str, bool], self.get("content_dict_str_bool")) @property def content_dict_str_bytes(self) -> Dict[str, bytes]: """Get the 'content_dict_str_bytes' content from the message.""" enforce( self.is_set("content_dict_str_bytes"), "'content_dict_str_bytes' content is not set.", ) return cast(Dict[str, bytes], self.get("content_dict_str_bytes")) @property def content_dict_str_float(self) -> Dict[str, float]: """Get the 'content_dict_str_float' content from the message.""" enforce( self.is_set("content_dict_str_float"), "'content_dict_str_float' content is not set.", ) return cast(Dict[str, float], self.get("content_dict_str_float")) @property def content_dict_str_int(self) -> Dict[str, int]: """Get the 'content_dict_str_int' content from the message.""" enforce( self.is_set("content_dict_str_int"), "'content_dict_str_int' content is not set.", ) return cast(Dict[str, int], self.get("content_dict_str_int")) @property def content_dict_str_str(self) -> Dict[str, str]: """Get the 'content_dict_str_str' content from the message.""" enforce( self.is_set("content_dict_str_str"), "'content_dict_str_str' content is not set.", ) return cast(Dict[str, str], self.get("content_dict_str_str")) @property def content_float(self) -> float: """Get the 'content_float' content from the message.""" enforce(self.is_set("content_float"), "'content_float' content is not set.") return cast(float, self.get("content_float")) @property def content_int(self) -> int: """Get the 'content_int' content from the message.""" enforce(self.is_set("content_int"), "'content_int' content is not set.") return cast(int, self.get("content_int")) @property def content_list_bool(self) -> Tuple[bool, ...]: """Get the 'content_list_bool' content from the message.""" enforce( self.is_set("content_list_bool"), "'content_list_bool' content is not set." ) return cast(Tuple[bool, ...], self.get("content_list_bool")) @property def content_list_bytes(self) -> Tuple[bytes, ...]: """Get the 'content_list_bytes' content from the message.""" enforce( self.is_set("content_list_bytes"), "'content_list_bytes' content is not set.", ) return cast(Tuple[bytes, ...], self.get("content_list_bytes")) @property def content_list_float(self) -> Tuple[float, ...]: """Get the 'content_list_float' content from the message.""" enforce( self.is_set("content_list_float"), "'content_list_float' content is not set.", ) return cast(Tuple[float, ...], self.get("content_list_float")) @property def content_list_int(self) -> Tuple[int, ...]: """Get the 'content_list_int' content from the message.""" enforce( self.is_set("content_list_int"), "'content_list_int' content is not set." ) return cast(Tuple[int, ...], self.get("content_list_int")) @property def content_list_str(self) -> Tuple[str, ...]: """Get the 'content_list_str' content from the message.""" enforce( self.is_set("content_list_str"), "'content_list_str' content is not set." ) return cast(Tuple[str, ...], self.get("content_list_str")) @property def content_o_bool(self) -> Optional[bool]: """Get the 'content_o_bool' content from the message.""" return cast(Optional[bool], self.get("content_o_bool")) @property def content_o_dict_str_int(self) -> Optional[Dict[str, int]]: """Get the 'content_o_dict_str_int' content from the message.""" return cast(Optional[Dict[str, int]], self.get("content_o_dict_str_int")) @property def content_o_list_bytes(self) -> Optional[Tuple[bytes, ...]]: """Get the 'content_o_list_bytes' content from the message.""" return cast(Optional[Tuple[bytes, ...]], self.get("content_o_list_bytes")) @property def content_o_set_int(self) -> Optional[FrozenSet[int]]: """Get the 'content_o_set_int' content from the message.""" return cast(Optional[FrozenSet[int]], self.get("content_o_set_int")) @property def content_set_bool(self) -> FrozenSet[bool]: """Get the 'content_set_bool' content from the message.""" enforce( self.is_set("content_set_bool"), "'content_set_bool' content is not set." ) return cast(FrozenSet[bool], self.get("content_set_bool")) @property def content_set_bytes(self) -> FrozenSet[bytes]: """Get the 'content_set_bytes' content from the message.""" enforce( self.is_set("content_set_bytes"), "'content_set_bytes' content is not set." ) return cast(FrozenSet[bytes], self.get("content_set_bytes")) @property def content_set_float(self) -> FrozenSet[float]: """Get the 'content_set_float' content from the message.""" enforce( self.is_set("content_set_float"), "'content_set_float' content is not set." ) return cast(FrozenSet[float], self.get("content_set_float")) @property def content_set_int(self) -> FrozenSet[int]: """Get the 'content_set_int' content from the message.""" enforce(self.is_set("content_set_int"), "'content_set_int' content is not set.") return cast(FrozenSet[int], self.get("content_set_int")) @property def content_set_str(self) -> FrozenSet[str]: """Get the 'content_set_str' content from the message.""" enforce(self.is_set("content_set_str"), "'content_set_str' content is not set.") return cast(FrozenSet[str], self.get("content_set_str")) @property def content_str(self) -> str: """Get the 'content_str' content from the message.""" enforce(self.is_set("content_str"), "'content_str' content is not set.") return cast(str, self.get("content_str")) @property def content_union_1( self, ) -> Union[ bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int] ]: """Get the 'content_union_1' content from the message.""" enforce(self.is_set("content_union_1"), "'content_union_1' content is not set.") return cast( Union[ bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int], ], self.get("content_union_1"), ) @property def content_union_2( self, ) -> Union[ FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes], ]: """Get the 'content_union_2' content from the message.""" enforce(self.is_set("content_union_2"), "'content_union_2' content is not set.") return cast( Union[ FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes], ], self.get("content_union_2"), ) def _is_consistent(self) -> bool: """Check that the message follows the t_protocol_no_ct protocol.""" try: enforce( isinstance(self.dialogue_reference, tuple), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( type(self.dialogue_reference) ), ) enforce( isinstance(self.dialogue_reference[0], str), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[0]) ), ) enforce( isinstance(self.dialogue_reference[1], str), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( type(self.dialogue_reference[1]) ), ) enforce( type(self.message_id) is int, "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( type(self.message_id) ), ) enforce( type(self.target) is int, "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( type(self.target) ), ) # Light Protocol Rule 2 # Check correct performative enforce( isinstance(self.performative, TProtocolNoCtMessage.Performative), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( self.valid_performatives, self.performative ), ) # Check correct contents actual_nb_of_contents = len(self._body) - DEFAULT_BODY_SIZE expected_nb_of_contents = 0 if self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: expected_nb_of_contents = 5 enforce( isinstance(self.content_bytes, bytes), "Invalid type for content 'content_bytes'. Expected 'bytes'. Found '{}'.".format( type(self.content_bytes) ), ) enforce( type(self.content_int) is int, "Invalid type for content 'content_int'. Expected 'int'. Found '{}'.".format( type(self.content_int) ), ) enforce( isinstance(self.content_float, float), "Invalid type for content 'content_float'. Expected 'float'. Found '{}'.".format( type(self.content_float) ), ) enforce( isinstance(self.content_bool, bool), "Invalid type for content 'content_bool'. Expected 'bool'. Found '{}'.".format( type(self.content_bool) ), ) enforce( isinstance(self.content_str, str), "Invalid type for content 'content_str'. Expected 'str'. Found '{}'.".format( type(self.content_str) ), ) elif ( self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT ): expected_nb_of_contents = 10 enforce( isinstance(self.content_set_bytes, frozenset), "Invalid type for content 'content_set_bytes'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_bytes) ), ) enforce( all( isinstance(element, bytes) for element in self.content_set_bytes ), "Invalid type for frozenset elements in content 'content_set_bytes'. Expected 'bytes'.", ) enforce( isinstance(self.content_set_int, frozenset), "Invalid type for content 'content_set_int'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_int) ), ) enforce( all(type(element) is int for element in self.content_set_int), "Invalid type for frozenset elements in content 'content_set_int'. Expected 'int'.", ) enforce( isinstance(self.content_set_float, frozenset), "Invalid type for content 'content_set_float'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_float) ), ) enforce( all( isinstance(element, float) for element in self.content_set_float ), "Invalid type for frozenset elements in content 'content_set_float'. Expected 'float'.", ) enforce( isinstance(self.content_set_bool, frozenset), "Invalid type for content 'content_set_bool'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_bool) ), ) enforce( all(isinstance(element, bool) for element in self.content_set_bool), "Invalid type for frozenset elements in content 'content_set_bool'. Expected 'bool'.", ) enforce( isinstance(self.content_set_str, frozenset), "Invalid type for content 'content_set_str'. Expected 'frozenset'. Found '{}'.".format( type(self.content_set_str) ), ) enforce( all(isinstance(element, str) for element in self.content_set_str), "Invalid type for frozenset elements in content 'content_set_str'. Expected 'str'.", ) enforce( isinstance(self.content_list_bytes, tuple), "Invalid type for content 'content_list_bytes'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_bytes) ), ) enforce( all( isinstance(element, bytes) for element in self.content_list_bytes ), "Invalid type for tuple elements in content 'content_list_bytes'. Expected 'bytes'.", ) enforce( isinstance(self.content_list_int, tuple), "Invalid type for content 'content_list_int'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_int) ), ) enforce( all(type(element) is int for element in self.content_list_int), "Invalid type for tuple elements in content 'content_list_int'. Expected 'int'.", ) enforce( isinstance(self.content_list_float, tuple), "Invalid type for content 'content_list_float'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_float) ), ) enforce( all( isinstance(element, float) for element in self.content_list_float ), "Invalid type for tuple elements in content 'content_list_float'. Expected 'float'.", ) enforce( isinstance(self.content_list_bool, tuple), "Invalid type for content 'content_list_bool'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_bool) ), ) enforce( all( isinstance(element, bool) for element in self.content_list_bool ), "Invalid type for tuple elements in content 'content_list_bool'. Expected 'bool'.", ) enforce( isinstance(self.content_list_str, tuple), "Invalid type for content 'content_list_str'. Expected 'tuple'. Found '{}'.".format( type(self.content_list_str) ), ) enforce( all(isinstance(element, str) for element in self.content_list_str), "Invalid type for tuple elements in content 'content_list_str'. Expected 'str'.", ) elif ( self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT ): expected_nb_of_contents = 15 enforce( isinstance(self.content_dict_int_bytes, dict), "Invalid type for content 'content_dict_int_bytes'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_bytes) ), ) for ( key_of_content_dict_int_bytes, value_of_content_dict_int_bytes, ) in self.content_dict_int_bytes.items(): enforce( type(key_of_content_dict_int_bytes) is int, "Invalid type for dictionary keys in content 'content_dict_int_bytes'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_bytes) ), ) enforce( isinstance(value_of_content_dict_int_bytes, bytes), "Invalid type for dictionary values in content 'content_dict_int_bytes'. Expected 'bytes'. Found '{}'.".format( type(value_of_content_dict_int_bytes) ), ) enforce( isinstance(self.content_dict_int_int, dict), "Invalid type for content 'content_dict_int_int'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_int) ), ) for ( key_of_content_dict_int_int, value_of_content_dict_int_int, ) in self.content_dict_int_int.items(): enforce( type(key_of_content_dict_int_int) is int, "Invalid type for dictionary keys in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_int) ), ) enforce( type(value_of_content_dict_int_int) is int, "Invalid type for dictionary values in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_dict_int_int) ), ) enforce( isinstance(self.content_dict_int_float, dict), "Invalid type for content 'content_dict_int_float'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_float) ), ) for ( key_of_content_dict_int_float, value_of_content_dict_int_float, ) in self.content_dict_int_float.items(): enforce( type(key_of_content_dict_int_float) is int, "Invalid type for dictionary keys in content 'content_dict_int_float'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_float) ), ) enforce( isinstance(value_of_content_dict_int_float, float), "Invalid type for dictionary values in content 'content_dict_int_float'. Expected 'float'. Found '{}'.".format( type(value_of_content_dict_int_float) ), ) enforce( isinstance(self.content_dict_int_bool, dict), "Invalid type for content 'content_dict_int_bool'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_bool) ), ) for ( key_of_content_dict_int_bool, value_of_content_dict_int_bool, ) in self.content_dict_int_bool.items(): enforce( type(key_of_content_dict_int_bool) is int, "Invalid type for dictionary keys in content 'content_dict_int_bool'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_bool) ), ) enforce( isinstance(value_of_content_dict_int_bool, bool), "Invalid type for dictionary values in content 'content_dict_int_bool'. Expected 'bool'. Found '{}'.".format( type(value_of_content_dict_int_bool) ), ) enforce( isinstance(self.content_dict_int_str, dict), "Invalid type for content 'content_dict_int_str'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_int_str) ), ) for ( key_of_content_dict_int_str, value_of_content_dict_int_str, ) in self.content_dict_int_str.items(): enforce( type(key_of_content_dict_int_str) is int, "Invalid type for dictionary keys in content 'content_dict_int_str'. Expected 'int'. Found '{}'.".format( type(key_of_content_dict_int_str) ), ) enforce( isinstance(value_of_content_dict_int_str, str), "Invalid type for dictionary values in content 'content_dict_int_str'. Expected 'str'. Found '{}'.".format( type(value_of_content_dict_int_str) ), ) enforce( isinstance(self.content_dict_bool_bytes, dict), "Invalid type for content 'content_dict_bool_bytes'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_bytes) ), ) for ( key_of_content_dict_bool_bytes, value_of_content_dict_bool_bytes, ) in self.content_dict_bool_bytes.items(): enforce( isinstance(key_of_content_dict_bool_bytes, bool), "Invalid type for dictionary keys in content 'content_dict_bool_bytes'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_bytes) ), ) enforce( isinstance(value_of_content_dict_bool_bytes, bytes), "Invalid type for dictionary values in content 'content_dict_bool_bytes'. Expected 'bytes'. Found '{}'.".format( type(value_of_content_dict_bool_bytes) ), ) enforce( isinstance(self.content_dict_bool_int, dict), "Invalid type for content 'content_dict_bool_int'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_int) ), ) for ( key_of_content_dict_bool_int, value_of_content_dict_bool_int, ) in self.content_dict_bool_int.items(): enforce( isinstance(key_of_content_dict_bool_int, bool), "Invalid type for dictionary keys in content 'content_dict_bool_int'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_int) ), ) enforce( type(value_of_content_dict_bool_int) is int, "Invalid type for dictionary values in content 'content_dict_bool_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_dict_bool_int) ), ) enforce( isinstance(self.content_dict_bool_float, dict), "Invalid type for content 'content_dict_bool_float'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_float) ), ) for ( key_of_content_dict_bool_float, value_of_content_dict_bool_float, ) in self.content_dict_bool_float.items(): enforce( isinstance(key_of_content_dict_bool_float, bool), "Invalid type for dictionary keys in content 'content_dict_bool_float'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_float) ), ) enforce( isinstance(value_of_content_dict_bool_float, float), "Invalid type for dictionary values in content 'content_dict_bool_float'. Expected 'float'. Found '{}'.".format( type(value_of_content_dict_bool_float) ), ) enforce( isinstance(self.content_dict_bool_bool, dict), "Invalid type for content 'content_dict_bool_bool'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_bool) ), ) for ( key_of_content_dict_bool_bool, value_of_content_dict_bool_bool, ) in self.content_dict_bool_bool.items(): enforce( isinstance(key_of_content_dict_bool_bool, bool), "Invalid type for dictionary keys in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_bool) ), ) enforce( isinstance(value_of_content_dict_bool_bool, bool), "Invalid type for dictionary values in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( type(value_of_content_dict_bool_bool) ), ) enforce( isinstance(self.content_dict_bool_str, dict), "Invalid type for content 'content_dict_bool_str'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_bool_str) ), ) for ( key_of_content_dict_bool_str, value_of_content_dict_bool_str, ) in self.content_dict_bool_str.items(): enforce( isinstance(key_of_content_dict_bool_str, bool), "Invalid type for dictionary keys in content 'content_dict_bool_str'. Expected 'bool'. Found '{}'.".format( type(key_of_content_dict_bool_str) ), ) enforce( isinstance(value_of_content_dict_bool_str, str), "Invalid type for dictionary values in content 'content_dict_bool_str'. Expected 'str'. Found '{}'.".format( type(value_of_content_dict_bool_str) ), ) enforce( isinstance(self.content_dict_str_bytes, dict), "Invalid type for content 'content_dict_str_bytes'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_bytes) ), ) for ( key_of_content_dict_str_bytes, value_of_content_dict_str_bytes, ) in self.content_dict_str_bytes.items(): enforce( isinstance(key_of_content_dict_str_bytes, str), "Invalid type for dictionary keys in content 'content_dict_str_bytes'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_bytes) ), ) enforce( isinstance(value_of_content_dict_str_bytes, bytes), "Invalid type for dictionary values in content 'content_dict_str_bytes'. Expected 'bytes'. Found '{}'.".format( type(value_of_content_dict_str_bytes) ), ) enforce( isinstance(self.content_dict_str_int, dict), "Invalid type for content 'content_dict_str_int'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_int) ), ) for ( key_of_content_dict_str_int, value_of_content_dict_str_int, ) in self.content_dict_str_int.items(): enforce( isinstance(key_of_content_dict_str_int, str), "Invalid type for dictionary keys in content 'content_dict_str_int'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_int) ), ) enforce( type(value_of_content_dict_str_int) is int, "Invalid type for dictionary values in content 'content_dict_str_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_dict_str_int) ), ) enforce( isinstance(self.content_dict_str_float, dict), "Invalid type for content 'content_dict_str_float'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_float) ), ) for ( key_of_content_dict_str_float, value_of_content_dict_str_float, ) in self.content_dict_str_float.items(): enforce( isinstance(key_of_content_dict_str_float, str), "Invalid type for dictionary keys in content 'content_dict_str_float'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_float) ), ) enforce( isinstance(value_of_content_dict_str_float, float), "Invalid type for dictionary values in content 'content_dict_str_float'. Expected 'float'. Found '{}'.".format( type(value_of_content_dict_str_float) ), ) enforce( isinstance(self.content_dict_str_bool, dict), "Invalid type for content 'content_dict_str_bool'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_bool) ), ) for ( key_of_content_dict_str_bool, value_of_content_dict_str_bool, ) in self.content_dict_str_bool.items(): enforce( isinstance(key_of_content_dict_str_bool, str), "Invalid type for dictionary keys in content 'content_dict_str_bool'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_bool) ), ) enforce( isinstance(value_of_content_dict_str_bool, bool), "Invalid type for dictionary values in content 'content_dict_str_bool'. Expected 'bool'. Found '{}'.".format( type(value_of_content_dict_str_bool) ), ) enforce( isinstance(self.content_dict_str_str, dict), "Invalid type for content 'content_dict_str_str'. Expected 'dict'. Found '{}'.".format( type(self.content_dict_str_str) ), ) for ( key_of_content_dict_str_str, value_of_content_dict_str_str, ) in self.content_dict_str_str.items(): enforce( isinstance(key_of_content_dict_str_str, str), "Invalid type for dictionary keys in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( type(key_of_content_dict_str_str) ), ) enforce( isinstance(value_of_content_dict_str_str, str), "Invalid type for dictionary values in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( type(value_of_content_dict_str_str) ), ) elif self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: expected_nb_of_contents = 2 enforce( isinstance(self.content_union_1, bool) or isinstance(self.content_union_1, bytes) or isinstance(self.content_union_1, dict) or isinstance(self.content_union_1, float) or isinstance(self.content_union_1, frozenset) or type(self.content_union_1) is int or isinstance(self.content_union_1, str) or isinstance(self.content_union_1, tuple), "Invalid type for content 'content_union_1'. Expected either of '['bool', 'bytes', 'dict', 'float', 'frozenset', 'int', 'str', 'tuple']'. Found '{}'.".format( type(self.content_union_1) ), ) if isinstance(self.content_union_1, frozenset): enforce( all(type(element) is int for element in self.content_union_1), "Invalid type for elements of content 'content_union_1'. Expected 'int'.", ) if isinstance(self.content_union_1, tuple): enforce( all( isinstance(element, bool) for element in self.content_union_1 ), "Invalid type for tuple elements in content 'content_union_1'. Expected 'bool'.", ) if isinstance(self.content_union_1, dict): for ( key_of_content_union_1, value_of_content_union_1, ) in self.content_union_1.items(): enforce( ( isinstance(key_of_content_union_1, str) and type(value_of_content_union_1) is int ), "Invalid type for dictionary key, value in content 'content_union_1'. Expected 'str', 'int'.", ) enforce( isinstance(self.content_union_2, dict) or isinstance(self.content_union_2, frozenset) or isinstance(self.content_union_2, tuple), "Invalid type for content 'content_union_2'. Expected either of '['dict', 'frozenset', 'tuple']'. Found '{}'.".format( type(self.content_union_2) ), ) if isinstance(self.content_union_2, frozenset): enforce( all( isinstance(element, bytes) for element in self.content_union_2 ) or all(type(element) is int for element in self.content_union_2) or all( isinstance(element, str) for element in self.content_union_2 ), "Invalid type for frozenset elements in content 'content_union_2'. Expected either 'bytes' or 'int' or 'str'.", ) if isinstance(self.content_union_2, tuple): enforce( all( isinstance(element, bool) for element in self.content_union_2 ) or all( isinstance(element, bytes) for element in self.content_union_2 ) or all( isinstance(element, float) for element in self.content_union_2 ), "Invalid type for tuple elements in content 'content_union_2'. Expected either 'bool' or 'bytes' or 'float'.", ) if isinstance(self.content_union_2, dict): for ( key_of_content_union_2, value_of_content_union_2, ) in self.content_union_2.items(): enforce( ( isinstance(key_of_content_union_2, bool) and isinstance(value_of_content_union_2, bytes) ) or ( type(key_of_content_union_2) is int and isinstance(value_of_content_union_2, float) ) or ( isinstance(key_of_content_union_2, str) and type(value_of_content_union_2) is int ), "Invalid type for dictionary key, value in content 'content_union_2'. Expected 'bool','bytes' or 'int','float' or 'str','int'.", ) elif self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_O: expected_nb_of_contents = 0 if self.is_set("content_o_bool"): expected_nb_of_contents += 1 content_o_bool = cast(bool, self.content_o_bool) enforce( isinstance(content_o_bool, bool), "Invalid type for content 'content_o_bool'. Expected 'bool'. Found '{}'.".format( type(content_o_bool) ), ) if self.is_set("content_o_set_int"): expected_nb_of_contents += 1 content_o_set_int = cast(FrozenSet[int], self.content_o_set_int) enforce( isinstance(content_o_set_int, frozenset), "Invalid type for content 'content_o_set_int'. Expected 'frozenset'. Found '{}'.".format( type(content_o_set_int) ), ) enforce( all(type(element) is int for element in content_o_set_int), "Invalid type for frozenset elements in content 'content_o_set_int'. Expected 'int'.", ) if self.is_set("content_o_list_bytes"): expected_nb_of_contents += 1 content_o_list_bytes = cast( Tuple[bytes, ...], self.content_o_list_bytes ) enforce( isinstance(content_o_list_bytes, tuple), "Invalid type for content 'content_o_list_bytes'. Expected 'tuple'. Found '{}'.".format( type(content_o_list_bytes) ), ) enforce( all( isinstance(element, bytes) for element in content_o_list_bytes ), "Invalid type for tuple elements in content 'content_o_list_bytes'. Expected 'bytes'.", ) if self.is_set("content_o_dict_str_int"): expected_nb_of_contents += 1 content_o_dict_str_int = cast( Dict[str, int], self.content_o_dict_str_int ) enforce( isinstance(content_o_dict_str_int, dict), "Invalid type for content 'content_o_dict_str_int'. Expected 'dict'. Found '{}'.".format( type(content_o_dict_str_int) ), ) for ( key_of_content_o_dict_str_int, value_of_content_o_dict_str_int, ) in content_o_dict_str_int.items(): enforce( isinstance(key_of_content_o_dict_str_int, str), "Invalid type for dictionary keys in content 'content_o_dict_str_int'. Expected 'str'. Found '{}'.".format( type(key_of_content_o_dict_str_int) ), ) enforce( type(value_of_content_o_dict_str_int) is int, "Invalid type for dictionary values in content 'content_o_dict_str_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_o_dict_str_int) ), ) elif ( self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS ): expected_nb_of_contents = 0 # Check correct content count enforce( expected_nb_of_contents == actual_nb_of_contents, "Incorrect number of contents. Expected {}. Found {}".format( expected_nb_of_contents, actual_nb_of_contents ), ) # Light Protocol Rule 3 if self.message_id == 1: enforce( self.target == 0, "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( self.target ), ) except (AEAEnforceError, ValueError, KeyError) as e: _default_logger.error(str(e)) return False return True ================================================ FILE: tests/data/generator/t_protocol_no_ct/protocol.yaml ================================================ name: t_protocol_no_ct author: fetchai version: 0.1.0 protocol_specification_id: some_author/some_protocol_name:1.0.0 type: protocol description: A protocol for testing purposes. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: __init__.py: QmcxVa3xRNtDc5tmaeDiN3KMHC7dENFrrgmnufQ3Ypqk9w dialogues.py: QmRTxDfiHH5CS65g9wtz1mqWDmENDpMvgmmczsn1tWemSr message.py: QmYR1AwBvuVUFdQdLagHastVduKJB3WPUX8hWiyAozsEwG serialization.py: Qmbem38xwYtcGsFfn6vd8xuMeE8FVFrJXGUXNEhY5V3zpR t_protocol_no_ct.proto: QmapyiDZBjF3K8yLZvCBYhjm3dFVwaLBKL8PLWUoYpTLez t_protocol_no_ct_pb2.py: QmUeCxpxg2SYZfB26rjFentsmpFtJLgwU1ueMvPNQA4YbP fingerprint_ignore_patterns: [] dependencies: protobuf: {} ================================================ FILE: tests/data/generator/t_protocol_no_ct/serialization.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Serialization module for t_protocol_no_ct protocol.""" # pylint: disable=too-many-statements,too-many-locals,no-member,too-few-public-methods,redefined-builtin from typing import Any, Dict, cast from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Serializer from tests.data.generator.t_protocol_no_ct import t_protocol_no_ct_pb2 from tests.data.generator.t_protocol_no_ct.message import TProtocolNoCtMessage class TProtocolNoCtSerializer(Serializer): """Serialization for the 't_protocol_no_ct' protocol.""" @staticmethod def encode(msg: Message) -> bytes: """ Encode a 'TProtocolNoCt' message into bytes. :param msg: the message object. :return: the bytes. """ msg = cast(TProtocolNoCtMessage, msg) message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() t_protocol_no_ct_msg = t_protocol_no_ct_pb2.TProtocolNoCtMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative_id = msg.performative if performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Pt_Performative() # type: ignore content_bytes = msg.content_bytes performative.content_bytes = content_bytes content_int = msg.content_int performative.content_int = content_int content_float = msg.content_float performative.content_float = content_float content_bool = msg.content_bool performative.content_bool = content_bool content_str = msg.content_str performative.content_str = content_str t_protocol_no_ct_msg.performative_pt.CopyFrom(performative) elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT: performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Pct_Performative() # type: ignore content_set_bytes = msg.content_set_bytes performative.content_set_bytes.extend(content_set_bytes) content_set_int = msg.content_set_int performative.content_set_int.extend(content_set_int) content_set_float = msg.content_set_float performative.content_set_float.extend(content_set_float) content_set_bool = msg.content_set_bool performative.content_set_bool.extend(content_set_bool) content_set_str = msg.content_set_str performative.content_set_str.extend(content_set_str) content_list_bytes = msg.content_list_bytes performative.content_list_bytes.extend(content_list_bytes) content_list_int = msg.content_list_int performative.content_list_int.extend(content_list_int) content_list_float = msg.content_list_float performative.content_list_float.extend(content_list_float) content_list_bool = msg.content_list_bool performative.content_list_bool.extend(content_list_bool) content_list_str = msg.content_list_str performative.content_list_str.extend(content_list_str) t_protocol_no_ct_msg.performative_pct.CopyFrom(performative) elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT: performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Pmt_Performative() # type: ignore content_dict_int_bytes = msg.content_dict_int_bytes performative.content_dict_int_bytes.update(content_dict_int_bytes) content_dict_int_int = msg.content_dict_int_int performative.content_dict_int_int.update(content_dict_int_int) content_dict_int_float = msg.content_dict_int_float performative.content_dict_int_float.update(content_dict_int_float) content_dict_int_bool = msg.content_dict_int_bool performative.content_dict_int_bool.update(content_dict_int_bool) content_dict_int_str = msg.content_dict_int_str performative.content_dict_int_str.update(content_dict_int_str) content_dict_bool_bytes = msg.content_dict_bool_bytes performative.content_dict_bool_bytes.update(content_dict_bool_bytes) content_dict_bool_int = msg.content_dict_bool_int performative.content_dict_bool_int.update(content_dict_bool_int) content_dict_bool_float = msg.content_dict_bool_float performative.content_dict_bool_float.update(content_dict_bool_float) content_dict_bool_bool = msg.content_dict_bool_bool performative.content_dict_bool_bool.update(content_dict_bool_bool) content_dict_bool_str = msg.content_dict_bool_str performative.content_dict_bool_str.update(content_dict_bool_str) content_dict_str_bytes = msg.content_dict_str_bytes performative.content_dict_str_bytes.update(content_dict_str_bytes) content_dict_str_int = msg.content_dict_str_int performative.content_dict_str_int.update(content_dict_str_int) content_dict_str_float = msg.content_dict_str_float performative.content_dict_str_float.update(content_dict_str_float) content_dict_str_bool = msg.content_dict_str_bool performative.content_dict_str_bool.update(content_dict_str_bool) content_dict_str_str = msg.content_dict_str_str performative.content_dict_str_str.update(content_dict_str_str) t_protocol_no_ct_msg.performative_pmt.CopyFrom(performative) elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Mt_Performative() # type: ignore if msg.is_set("content_union_1"): if isinstance(msg.content_union_1, bytes): performative.content_union_1_type_bytes_is_set = True content_union_1_type_bytes = msg.content_union_1 performative.content_union_1_type_bytes = content_union_1_type_bytes elif isinstance(msg.content_union_1, int): performative.content_union_1_type_int_is_set = True content_union_1_type_int = msg.content_union_1 performative.content_union_1_type_int = content_union_1_type_int elif isinstance(msg.content_union_1, float): performative.content_union_1_type_float_is_set = True content_union_1_type_float = msg.content_union_1 performative.content_union_1_type_float = content_union_1_type_float elif isinstance(msg.content_union_1, bool): performative.content_union_1_type_bool_is_set = True content_union_1_type_bool = msg.content_union_1 performative.content_union_1_type_bool = content_union_1_type_bool elif isinstance(msg.content_union_1, str): performative.content_union_1_type_str_is_set = True content_union_1_type_str = msg.content_union_1 performative.content_union_1_type_str = content_union_1_type_str elif isinstance(msg.content_union_1, (set, frozenset)) and all( map(lambda x: isinstance(x, int), msg.content_union_1) ): performative.content_union_1_type_set_of_int_is_set = True content_union_1 = msg.content_union_1 performative.content_union_1_type_set_of_int.extend(content_union_1) elif isinstance(msg.content_union_1, (list, tuple)) and all( map(lambda x: isinstance(x, bool), msg.content_union_1) ): performative.content_union_1_type_list_of_bool_is_set = True content_union_1 = msg.content_union_1 performative.content_union_1_type_list_of_bool.extend( content_union_1 ) elif isinstance(msg.content_union_1, dict) and all( map( lambda x: isinstance(x[0], str) and isinstance(x[1], int), msg.content_union_1.items(), ) ): performative.content_union_1_type_dict_of_str_int_is_set = True content_union_1 = msg.content_union_1 performative.content_union_1_type_dict_of_str_int.update( content_union_1 ) elif msg.content_union_1 is None: pass else: raise ValueError( f"Bad value set to `content_union_1` {msg.content_union_1 }" ) if msg.is_set("content_union_2"): if isinstance(msg.content_union_2, (set, frozenset)) and all( map(lambda x: isinstance(x, bytes), msg.content_union_2) ): performative.content_union_2_type_set_of_bytes_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_set_of_bytes.extend( content_union_2 ) elif isinstance(msg.content_union_2, (set, frozenset)) and all( map(lambda x: isinstance(x, int), msg.content_union_2) ): performative.content_union_2_type_set_of_int_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_set_of_int.extend(content_union_2) elif isinstance(msg.content_union_2, (set, frozenset)) and all( map(lambda x: isinstance(x, str), msg.content_union_2) ): performative.content_union_2_type_set_of_str_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_set_of_str.extend(content_union_2) elif isinstance(msg.content_union_2, (list, tuple)) and all( map(lambda x: isinstance(x, float), msg.content_union_2) ): performative.content_union_2_type_list_of_float_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_list_of_float.extend( content_union_2 ) elif isinstance(msg.content_union_2, (list, tuple)) and all( map(lambda x: isinstance(x, bool), msg.content_union_2) ): performative.content_union_2_type_list_of_bool_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_list_of_bool.extend( content_union_2 ) elif isinstance(msg.content_union_2, (list, tuple)) and all( map(lambda x: isinstance(x, bytes), msg.content_union_2) ): performative.content_union_2_type_list_of_bytes_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_list_of_bytes.extend( content_union_2 ) elif isinstance(msg.content_union_2, dict) and all( map( lambda x: isinstance(x[0], str) and isinstance(x[1], int), msg.content_union_2.items(), ) ): performative.content_union_2_type_dict_of_str_int_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_dict_of_str_int.update( content_union_2 ) elif isinstance(msg.content_union_2, dict) and all( map( lambda x: isinstance(x[0], int) and isinstance(x[1], float), msg.content_union_2.items(), ) ): performative.content_union_2_type_dict_of_int_float_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_dict_of_int_float.update( content_union_2 ) elif isinstance(msg.content_union_2, dict) and all( map( lambda x: isinstance(x[0], bool) and isinstance(x[1], bytes), msg.content_union_2.items(), ) ): performative.content_union_2_type_dict_of_bool_bytes_is_set = True content_union_2 = msg.content_union_2 performative.content_union_2_type_dict_of_bool_bytes.update( content_union_2 ) elif msg.content_union_2 is None: pass else: raise ValueError( f"Bad value set to `content_union_2` {msg.content_union_2 }" ) t_protocol_no_ct_msg.performative_mt.CopyFrom(performative) elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_O: performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_O_Performative() # type: ignore if msg.is_set("content_o_bool"): performative.content_o_bool_is_set = True content_o_bool = msg.content_o_bool performative.content_o_bool = content_o_bool if msg.is_set("content_o_set_int"): performative.content_o_set_int_is_set = True content_o_set_int = msg.content_o_set_int performative.content_o_set_int.extend(content_o_set_int) if msg.is_set("content_o_list_bytes"): performative.content_o_list_bytes_is_set = True content_o_list_bytes = msg.content_o_list_bytes performative.content_o_list_bytes.extend(content_o_list_bytes) if msg.is_set("content_o_dict_str_int"): performative.content_o_dict_str_int_is_set = True content_o_dict_str_int = msg.content_o_dict_str_int performative.content_o_dict_str_int.update(content_o_dict_str_int) t_protocol_no_ct_msg.performative_o.CopyFrom(performative) elif ( performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS ): performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Empty_Contents_Performative() # type: ignore t_protocol_no_ct_msg.performative_empty_contents.CopyFrom(performative) else: raise ValueError("Performative not valid: {}".format(performative_id)) dialogue_message_pb.content = t_protocol_no_ct_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) message_bytes = message_pb.SerializeToString() return message_bytes @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a 'TProtocolNoCt' message. :param obj: the bytes object. :return: the 'TProtocolNoCt' message. """ message_pb = ProtobufMessage() t_protocol_no_ct_pb = t_protocol_no_ct_pb2.TProtocolNoCtMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target t_protocol_no_ct_pb.ParseFromString(message_pb.dialogue_message.content) performative = t_protocol_no_ct_pb.WhichOneof("performative") performative_id = TProtocolNoCtMessage.Performative(str(performative)) performative_content = {} # type: Dict[str, Any] if performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: content_bytes = t_protocol_no_ct_pb.performative_pt.content_bytes performative_content["content_bytes"] = content_bytes content_int = t_protocol_no_ct_pb.performative_pt.content_int performative_content["content_int"] = content_int content_float = t_protocol_no_ct_pb.performative_pt.content_float performative_content["content_float"] = content_float content_bool = t_protocol_no_ct_pb.performative_pt.content_bool performative_content["content_bool"] = content_bool content_str = t_protocol_no_ct_pb.performative_pt.content_str performative_content["content_str"] = content_str elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT: content_set_bytes = t_protocol_no_ct_pb.performative_pct.content_set_bytes content_set_bytes_frozenset = frozenset(content_set_bytes) performative_content["content_set_bytes"] = content_set_bytes_frozenset content_set_int = t_protocol_no_ct_pb.performative_pct.content_set_int content_set_int_frozenset = frozenset(content_set_int) performative_content["content_set_int"] = content_set_int_frozenset content_set_float = t_protocol_no_ct_pb.performative_pct.content_set_float content_set_float_frozenset = frozenset(content_set_float) performative_content["content_set_float"] = content_set_float_frozenset content_set_bool = t_protocol_no_ct_pb.performative_pct.content_set_bool content_set_bool_frozenset = frozenset(content_set_bool) performative_content["content_set_bool"] = content_set_bool_frozenset content_set_str = t_protocol_no_ct_pb.performative_pct.content_set_str content_set_str_frozenset = frozenset(content_set_str) performative_content["content_set_str"] = content_set_str_frozenset content_list_bytes = t_protocol_no_ct_pb.performative_pct.content_list_bytes content_list_bytes_tuple = tuple(content_list_bytes) performative_content["content_list_bytes"] = content_list_bytes_tuple content_list_int = t_protocol_no_ct_pb.performative_pct.content_list_int content_list_int_tuple = tuple(content_list_int) performative_content["content_list_int"] = content_list_int_tuple content_list_float = t_protocol_no_ct_pb.performative_pct.content_list_float content_list_float_tuple = tuple(content_list_float) performative_content["content_list_float"] = content_list_float_tuple content_list_bool = t_protocol_no_ct_pb.performative_pct.content_list_bool content_list_bool_tuple = tuple(content_list_bool) performative_content["content_list_bool"] = content_list_bool_tuple content_list_str = t_protocol_no_ct_pb.performative_pct.content_list_str content_list_str_tuple = tuple(content_list_str) performative_content["content_list_str"] = content_list_str_tuple elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT: content_dict_int_bytes = ( t_protocol_no_ct_pb.performative_pmt.content_dict_int_bytes ) content_dict_int_bytes_dict = dict(content_dict_int_bytes) performative_content["content_dict_int_bytes"] = content_dict_int_bytes_dict content_dict_int_int = ( t_protocol_no_ct_pb.performative_pmt.content_dict_int_int ) content_dict_int_int_dict = dict(content_dict_int_int) performative_content["content_dict_int_int"] = content_dict_int_int_dict content_dict_int_float = ( t_protocol_no_ct_pb.performative_pmt.content_dict_int_float ) content_dict_int_float_dict = dict(content_dict_int_float) performative_content["content_dict_int_float"] = content_dict_int_float_dict content_dict_int_bool = ( t_protocol_no_ct_pb.performative_pmt.content_dict_int_bool ) content_dict_int_bool_dict = dict(content_dict_int_bool) performative_content["content_dict_int_bool"] = content_dict_int_bool_dict content_dict_int_str = ( t_protocol_no_ct_pb.performative_pmt.content_dict_int_str ) content_dict_int_str_dict = dict(content_dict_int_str) performative_content["content_dict_int_str"] = content_dict_int_str_dict content_dict_bool_bytes = ( t_protocol_no_ct_pb.performative_pmt.content_dict_bool_bytes ) content_dict_bool_bytes_dict = dict(content_dict_bool_bytes) performative_content[ "content_dict_bool_bytes" ] = content_dict_bool_bytes_dict content_dict_bool_int = ( t_protocol_no_ct_pb.performative_pmt.content_dict_bool_int ) content_dict_bool_int_dict = dict(content_dict_bool_int) performative_content["content_dict_bool_int"] = content_dict_bool_int_dict content_dict_bool_float = ( t_protocol_no_ct_pb.performative_pmt.content_dict_bool_float ) content_dict_bool_float_dict = dict(content_dict_bool_float) performative_content[ "content_dict_bool_float" ] = content_dict_bool_float_dict content_dict_bool_bool = ( t_protocol_no_ct_pb.performative_pmt.content_dict_bool_bool ) content_dict_bool_bool_dict = dict(content_dict_bool_bool) performative_content["content_dict_bool_bool"] = content_dict_bool_bool_dict content_dict_bool_str = ( t_protocol_no_ct_pb.performative_pmt.content_dict_bool_str ) content_dict_bool_str_dict = dict(content_dict_bool_str) performative_content["content_dict_bool_str"] = content_dict_bool_str_dict content_dict_str_bytes = ( t_protocol_no_ct_pb.performative_pmt.content_dict_str_bytes ) content_dict_str_bytes_dict = dict(content_dict_str_bytes) performative_content["content_dict_str_bytes"] = content_dict_str_bytes_dict content_dict_str_int = ( t_protocol_no_ct_pb.performative_pmt.content_dict_str_int ) content_dict_str_int_dict = dict(content_dict_str_int) performative_content["content_dict_str_int"] = content_dict_str_int_dict content_dict_str_float = ( t_protocol_no_ct_pb.performative_pmt.content_dict_str_float ) content_dict_str_float_dict = dict(content_dict_str_float) performative_content["content_dict_str_float"] = content_dict_str_float_dict content_dict_str_bool = ( t_protocol_no_ct_pb.performative_pmt.content_dict_str_bool ) content_dict_str_bool_dict = dict(content_dict_str_bool) performative_content["content_dict_str_bool"] = content_dict_str_bool_dict content_dict_str_str = ( t_protocol_no_ct_pb.performative_pmt.content_dict_str_str ) content_dict_str_str_dict = dict(content_dict_str_str) performative_content["content_dict_str_str"] = content_dict_str_str_dict elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: if t_protocol_no_ct_pb.performative_mt.content_union_1_type_bytes_is_set: content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_bytes ) performative_content["content_union_1"] = content_union_1 if t_protocol_no_ct_pb.performative_mt.content_union_1_type_int_is_set: content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_int ) performative_content["content_union_1"] = content_union_1 if t_protocol_no_ct_pb.performative_mt.content_union_1_type_float_is_set: content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_float ) performative_content["content_union_1"] = content_union_1 if t_protocol_no_ct_pb.performative_mt.content_union_1_type_bool_is_set: content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_bool ) performative_content["content_union_1"] = content_union_1 if t_protocol_no_ct_pb.performative_mt.content_union_1_type_str_is_set: content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_str ) performative_content["content_union_1"] = content_union_1 if ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_set_of_int_is_set ): content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_set_of_int ) content_union_1_frozenset = frozenset(content_union_1) performative_content["content_union_1"] = content_union_1_frozenset if ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_list_of_bool_is_set ): content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_list_of_bool ) content_union_1_tuple = tuple(content_union_1) performative_content["content_union_1"] = content_union_1_tuple if ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_dict_of_str_int_is_set ): content_union_1 = ( t_protocol_no_ct_pb.performative_mt.content_union_1_type_dict_of_str_int ) content_union_1_dict = dict(content_union_1) performative_content["content_union_1"] = content_union_1_dict if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_bytes_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_bytes ) content_union_2_frozenset = frozenset(content_union_2) performative_content["content_union_2"] = content_union_2_frozenset if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_int_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_int ) content_union_2_frozenset = frozenset(content_union_2) performative_content["content_union_2"] = content_union_2_frozenset if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_str_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_str ) content_union_2_frozenset = frozenset(content_union_2) performative_content["content_union_2"] = content_union_2_frozenset if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_float_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_float ) content_union_2_tuple = tuple(content_union_2) performative_content["content_union_2"] = content_union_2_tuple if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_bool_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_bool ) content_union_2_tuple = tuple(content_union_2) performative_content["content_union_2"] = content_union_2_tuple if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_bytes_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_bytes ) content_union_2_tuple = tuple(content_union_2) performative_content["content_union_2"] = content_union_2_tuple if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_str_int_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_str_int ) content_union_2_dict = dict(content_union_2) performative_content["content_union_2"] = content_union_2_dict if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_int_float_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_int_float ) content_union_2_dict = dict(content_union_2) performative_content["content_union_2"] = content_union_2_dict if ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_bool_bytes_is_set ): content_union_2 = ( t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_bool_bytes ) content_union_2_dict = dict(content_union_2) performative_content["content_union_2"] = content_union_2_dict elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_O: if t_protocol_no_ct_pb.performative_o.content_o_bool_is_set: content_o_bool = t_protocol_no_ct_pb.performative_o.content_o_bool performative_content["content_o_bool"] = content_o_bool if t_protocol_no_ct_pb.performative_o.content_o_set_int_is_set: content_o_set_int = t_protocol_no_ct_pb.performative_o.content_o_set_int content_o_set_int_frozenset = frozenset(content_o_set_int) performative_content["content_o_set_int"] = content_o_set_int_frozenset if t_protocol_no_ct_pb.performative_o.content_o_list_bytes_is_set: content_o_list_bytes = ( t_protocol_no_ct_pb.performative_o.content_o_list_bytes ) content_o_list_bytes_tuple = tuple(content_o_list_bytes) performative_content[ "content_o_list_bytes" ] = content_o_list_bytes_tuple if t_protocol_no_ct_pb.performative_o.content_o_dict_str_int_is_set: content_o_dict_str_int = ( t_protocol_no_ct_pb.performative_o.content_o_dict_str_int ) content_o_dict_str_int_dict = dict(content_o_dict_str_int) performative_content[ "content_o_dict_str_int" ] = content_o_dict_str_int_dict elif ( performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS ): pass else: raise ValueError("Performative not valid: {}.".format(performative_id)) return TProtocolNoCtMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content, ) ================================================ FILE: tests/data/generator/t_protocol_no_ct/t_protocol_no_ct.proto ================================================ syntax = "proto3"; package aea.some_author.some_protocol_name.v1_0_0; message TProtocolNoCtMessage{ // Performatives and contents message Performative_Pt_Performative{ bytes content_bytes = 1; int64 content_int = 2; float content_float = 3; bool content_bool = 4; string content_str = 5; } message Performative_Pct_Performative{ repeated bytes content_set_bytes = 1; repeated int64 content_set_int = 2; repeated float content_set_float = 3; repeated bool content_set_bool = 4; repeated string content_set_str = 5; repeated bytes content_list_bytes = 6; repeated int64 content_list_int = 7; repeated float content_list_float = 8; repeated bool content_list_bool = 9; repeated string content_list_str = 10; } message Performative_Pmt_Performative{ map content_dict_int_bytes = 1; map content_dict_int_int = 2; map content_dict_int_float = 3; map content_dict_int_bool = 4; map content_dict_int_str = 5; map content_dict_bool_bytes = 6; map content_dict_bool_int = 7; map content_dict_bool_float = 8; map content_dict_bool_bool = 9; map content_dict_bool_str = 10; map content_dict_str_bytes = 11; map content_dict_str_int = 12; map content_dict_str_float = 13; map content_dict_str_bool = 14; map content_dict_str_str = 15; } message Performative_Mt_Performative{ bytes content_union_1_type_bytes = 1; bool content_union_1_type_bytes_is_set = 2; int64 content_union_1_type_int = 3; bool content_union_1_type_int_is_set = 4; float content_union_1_type_float = 5; bool content_union_1_type_float_is_set = 6; bool content_union_1_type_bool = 7; bool content_union_1_type_bool_is_set = 8; string content_union_1_type_str = 9; bool content_union_1_type_str_is_set = 10; repeated int64 content_union_1_type_set_of_int = 11; bool content_union_1_type_set_of_int_is_set = 12; repeated bool content_union_1_type_list_of_bool = 13; bool content_union_1_type_list_of_bool_is_set = 14; map content_union_1_type_dict_of_str_int = 15; bool content_union_1_type_dict_of_str_int_is_set = 16; repeated bytes content_union_2_type_set_of_bytes = 17; bool content_union_2_type_set_of_bytes_is_set = 18; repeated int64 content_union_2_type_set_of_int = 19; bool content_union_2_type_set_of_int_is_set = 20; repeated string content_union_2_type_set_of_str = 21; bool content_union_2_type_set_of_str_is_set = 22; repeated float content_union_2_type_list_of_float = 23; bool content_union_2_type_list_of_float_is_set = 24; repeated bool content_union_2_type_list_of_bool = 25; bool content_union_2_type_list_of_bool_is_set = 26; repeated bytes content_union_2_type_list_of_bytes = 27; bool content_union_2_type_list_of_bytes_is_set = 28; map content_union_2_type_dict_of_str_int = 29; bool content_union_2_type_dict_of_str_int_is_set = 30; map content_union_2_type_dict_of_int_float = 31; bool content_union_2_type_dict_of_int_float_is_set = 32; map content_union_2_type_dict_of_bool_bytes = 33; bool content_union_2_type_dict_of_bool_bytes_is_set = 34; } message Performative_O_Performative{ bool content_o_bool = 1; bool content_o_bool_is_set = 2; repeated int64 content_o_set_int = 3; bool content_o_set_int_is_set = 4; repeated bytes content_o_list_bytes = 5; bool content_o_list_bytes_is_set = 6; map content_o_dict_str_int = 7; bool content_o_dict_str_int_is_set = 8; } message Performative_Empty_Contents_Performative{ } oneof performative{ Performative_Empty_Contents_Performative performative_empty_contents = 5; Performative_Mt_Performative performative_mt = 6; Performative_O_Performative performative_o = 7; Performative_Pct_Performative performative_pct = 8; Performative_Pmt_Performative performative_pmt = 9; Performative_Pt_Performative performative_pt = 10; } } ================================================ FILE: tests/data/generator/t_protocol_no_ct/t_protocol_no_ct_pb2.py ================================================ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: t_protocol_no_ct.proto """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x16t_protocol_no_ct.proto\x12)aea.some_author.some_protocol_name.v1_0_0"\x8f\x39\n\x14TProtocolNoCtMessage\x12\x8f\x01\n\x1bperformative_empty_contents\x18\x05 \x01(\x0b\x32h.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Empty_Contents_PerformativeH\x00\x12w\n\x0fperformative_mt\x18\x06 \x01(\x0b\x32\\.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_PerformativeH\x00\x12u\n\x0eperformative_o\x18\x07 \x01(\x0b\x32[.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_O_PerformativeH\x00\x12y\n\x10performative_pct\x18\x08 \x01(\x0b\x32].aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pct_PerformativeH\x00\x12y\n\x10performative_pmt\x18\t \x01(\x0b\x32].aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_PerformativeH\x00\x12w\n\x0fperformative_pt\x18\n \x01(\x0b\x32\\.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pt_PerformativeH\x00\x1a\x8c\x01\n\x1cPerformative_Pt_Performative\x12\x15\n\rcontent_bytes\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ontent_int\x18\x02 \x01(\x03\x12\x15\n\rcontent_float\x18\x03 \x01(\x02\x12\x14\n\x0c\x63ontent_bool\x18\x04 \x01(\x08\x12\x13\n\x0b\x63ontent_str\x18\x05 \x01(\t\x1a\xa8\x02\n\x1dPerformative_Pct_Performative\x12\x19\n\x11\x63ontent_set_bytes\x18\x01 \x03(\x0c\x12\x17\n\x0f\x63ontent_set_int\x18\x02 \x03(\x03\x12\x19\n\x11\x63ontent_set_float\x18\x03 \x03(\x02\x12\x18\n\x10\x63ontent_set_bool\x18\x04 \x03(\x08\x12\x17\n\x0f\x63ontent_set_str\x18\x05 \x03(\t\x12\x1a\n\x12\x63ontent_list_bytes\x18\x06 \x03(\x0c\x12\x18\n\x10\x63ontent_list_int\x18\x07 \x03(\x03\x12\x1a\n\x12\x63ontent_list_float\x18\x08 \x03(\x02\x12\x19\n\x11\x63ontent_list_bool\x18\t \x03(\x08\x12\x18\n\x10\x63ontent_list_str\x18\n \x03(\t\x1a\xfc\x18\n\x1dPerformative_Pmt_Performative\x12\x96\x01\n\x16\x63ontent_dict_int_bytes\x18\x01 \x03(\x0b\x32v.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry\x12\x92\x01\n\x14\x63ontent_dict_int_int\x18\x02 \x03(\x0b\x32t.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry\x12\x96\x01\n\x16\x63ontent_dict_int_float\x18\x03 \x03(\x0b\x32v.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry\x12\x94\x01\n\x15\x63ontent_dict_int_bool\x18\x04 \x03(\x0b\x32u.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry\x12\x92\x01\n\x14\x63ontent_dict_int_str\x18\x05 \x03(\x0b\x32t.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry\x12\x98\x01\n\x17\x63ontent_dict_bool_bytes\x18\x06 \x03(\x0b\x32w.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry\x12\x94\x01\n\x15\x63ontent_dict_bool_int\x18\x07 \x03(\x0b\x32u.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry\x12\x98\x01\n\x17\x63ontent_dict_bool_float\x18\x08 \x03(\x0b\x32w.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry\x12\x96\x01\n\x16\x63ontent_dict_bool_bool\x18\t \x03(\x0b\x32v.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry\x12\x94\x01\n\x15\x63ontent_dict_bool_str\x18\n \x03(\x0b\x32u.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry\x12\x96\x01\n\x16\x63ontent_dict_str_bytes\x18\x0b \x03(\x0b\x32v.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry\x12\x92\x01\n\x14\x63ontent_dict_str_int\x18\x0c \x03(\x0b\x32t.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry\x12\x96\x01\n\x16\x63ontent_dict_str_float\x18\r \x03(\x0b\x32v.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry\x12\x94\x01\n\x15\x63ontent_dict_str_bool\x18\x0e \x03(\x0b\x32u.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry\x12\x92\x01\n\x14\x63ontent_dict_str_str\x18\x0f \x03(\x0b\x32t.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry\x1a:\n\x18\x43ontentDictIntBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a:\n\x18\x43ontentDictIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictIntBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a:\n\x18\x43ontentDictBoolBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictStrBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xc1\x12\n\x1cPerformative_Mt_Performative\x12"\n\x1a\x63ontent_union_1_type_bytes\x18\x01 \x01(\x0c\x12)\n!content_union_1_type_bytes_is_set\x18\x02 \x01(\x08\x12 \n\x18\x63ontent_union_1_type_int\x18\x03 \x01(\x03\x12\'\n\x1f\x63ontent_union_1_type_int_is_set\x18\x04 \x01(\x08\x12"\n\x1a\x63ontent_union_1_type_float\x18\x05 \x01(\x02\x12)\n!content_union_1_type_float_is_set\x18\x06 \x01(\x08\x12!\n\x19\x63ontent_union_1_type_bool\x18\x07 \x01(\x08\x12(\n content_union_1_type_bool_is_set\x18\x08 \x01(\x08\x12 \n\x18\x63ontent_union_1_type_str\x18\t \x01(\t\x12\'\n\x1f\x63ontent_union_1_type_str_is_set\x18\n \x01(\x08\x12\'\n\x1f\x63ontent_union_1_type_set_of_int\x18\x0b \x03(\x03\x12.\n&content_union_1_type_set_of_int_is_set\x18\x0c \x01(\x08\x12)\n!content_union_1_type_list_of_bool\x18\r \x03(\x08\x12\x30\n(content_union_1_type_list_of_bool_is_set\x18\x0e \x01(\x08\x12\xad\x01\n$content_union_1_type_dict_of_str_int\x18\x0f \x03(\x0b\x32\x7f.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry\x12\x33\n+content_union_1_type_dict_of_str_int_is_set\x18\x10 \x01(\x08\x12)\n!content_union_2_type_set_of_bytes\x18\x11 \x03(\x0c\x12\x30\n(content_union_2_type_set_of_bytes_is_set\x18\x12 \x01(\x08\x12\'\n\x1f\x63ontent_union_2_type_set_of_int\x18\x13 \x03(\x03\x12.\n&content_union_2_type_set_of_int_is_set\x18\x14 \x01(\x08\x12\'\n\x1f\x63ontent_union_2_type_set_of_str\x18\x15 \x03(\t\x12.\n&content_union_2_type_set_of_str_is_set\x18\x16 \x01(\x08\x12*\n"content_union_2_type_list_of_float\x18\x17 \x03(\x02\x12\x31\n)content_union_2_type_list_of_float_is_set\x18\x18 \x01(\x08\x12)\n!content_union_2_type_list_of_bool\x18\x19 \x03(\x08\x12\x30\n(content_union_2_type_list_of_bool_is_set\x18\x1a \x01(\x08\x12*\n"content_union_2_type_list_of_bytes\x18\x1b \x03(\x0c\x12\x31\n)content_union_2_type_list_of_bytes_is_set\x18\x1c \x01(\x08\x12\xad\x01\n$content_union_2_type_dict_of_str_int\x18\x1d \x03(\x0b\x32\x7f.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry\x12\x33\n+content_union_2_type_dict_of_str_int_is_set\x18\x1e \x01(\x08\x12\xb2\x01\n&content_union_2_type_dict_of_int_float\x18\x1f \x03(\x0b\x32\x81\x01.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry\x12\x35\n-content_union_2_type_dict_of_int_float_is_set\x18 \x01(\x08\x12\xb4\x01\n\'content_union_2_type_dict_of_bool_bytes\x18! \x03(\x0b\x32\x82\x01.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry\x12\x36\n.content_union_2_type_dict_of_bool_bytes_is_set\x18" \x01(\x08\x1a\x44\n"ContentUnion1TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x44\n"ContentUnion2TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a\x46\n$ContentUnion2TypeDictOfIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1aG\n%ContentUnion2TypeDictOfBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\xcc\x03\n\x1bPerformative_O_Performative\x12\x16\n\x0e\x63ontent_o_bool\x18\x01 \x01(\x08\x12\x1d\n\x15\x63ontent_o_bool_is_set\x18\x02 \x01(\x08\x12\x19\n\x11\x63ontent_o_set_int\x18\x03 \x03(\x03\x12 \n\x18\x63ontent_o_set_int_is_set\x18\x04 \x01(\x08\x12\x1c\n\x14\x63ontent_o_list_bytes\x18\x05 \x03(\x0c\x12#\n\x1b\x63ontent_o_list_bytes_is_set\x18\x06 \x01(\x08\x12\x93\x01\n\x16\x63ontent_o_dict_str_int\x18\x07 \x03(\x0b\x32s.aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry\x12%\n\x1d\x63ontent_o_dict_str_int_is_set\x18\x08 \x01(\x08\x1a\x39\n\x17\x43ontentODictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\x1a*\n(Performative_Empty_Contents_PerformativeB\x0e\n\x0cperformativeb\x06proto3' ) _TPROTOCOLNOCTMESSAGE = DESCRIPTOR.message_types_by_name["TProtocolNoCtMessage"] _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE = ( _TPROTOCOLNOCTMESSAGE.nested_types_by_name["Performative_Pt_Performative"] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE = ( _TPROTOCOLNOCTMESSAGE.nested_types_by_name["Performative_Pct_Performative"] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE = ( _TPROTOCOLNOCTMESSAGE.nested_types_by_name["Performative_Pmt_Performative"] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntBytesEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntIntEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntFloatEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntBoolEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictIntStrEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolBytesEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolIntEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolFloatEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolBoolEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictBoolStrEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrBytesEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrIntEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrFloatEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrBoolEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.nested_types_by_name[ "ContentDictStrStrEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE = ( _TPROTOCOLNOCTMESSAGE.nested_types_by_name["Performative_Mt_Performative"] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion1TypeDictOfStrIntEntry" ] _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion2TypeDictOfStrIntEntry" ] _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion2TypeDictOfIntFloatEntry" ] _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.nested_types_by_name[ "ContentUnion2TypeDictOfBoolBytesEntry" ] _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE = ( _TPROTOCOLNOCTMESSAGE.nested_types_by_name["Performative_O_Performative"] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY = ( _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE.nested_types_by_name[ "ContentODictStrIntEntry" ] ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE = ( _TPROTOCOLNOCTMESSAGE.nested_types_by_name[ "Performative_Empty_Contents_Performative" ] ) TProtocolNoCtMessage = _reflection.GeneratedProtocolMessageType( "TProtocolNoCtMessage", (_message.Message,), { "Performative_Pt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pt_Performative", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pt_Performative) }, ), "Performative_Pct_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pct_Performative", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pct_Performative) }, ), "Performative_Pmt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pmt_Performative", (_message.Message,), { "ContentDictIntBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry) }, ), "ContentDictIntIntEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry) }, ), "ContentDictIntFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry) }, ), "ContentDictIntBoolEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntBoolEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry) }, ), "ContentDictIntStrEntry": _reflection.GeneratedProtocolMessageType( "ContentDictIntStrEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry) }, ), "ContentDictBoolBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry) }, ), "ContentDictBoolIntEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry) }, ), "ContentDictBoolFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry) }, ), "ContentDictBoolBoolEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolBoolEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry) }, ), "ContentDictBoolStrEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolStrEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry) }, ), "ContentDictStrBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry) }, ), "ContentDictStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry) }, ), "ContentDictStrFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry) }, ), "ContentDictStrBoolEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrBoolEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry) }, ), "ContentDictStrStrEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrStrEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry) }, ), "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Pmt_Performative) }, ), "Performative_Mt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Mt_Performative", (_message.Message,), { "ContentUnion1TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion1TypeDictOfStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry) }, ), "ContentUnion2TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry) }, ), "ContentUnion2TypeDictOfIntFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfIntFloatEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry) }, ), "ContentUnion2TypeDictOfBoolBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfBoolBytesEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry) }, ), "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Mt_Performative) }, ), "Performative_O_Performative": _reflection.GeneratedProtocolMessageType( "Performative_O_Performative", (_message.Message,), { "ContentODictStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentODictStrIntEntry", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry) }, ), "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_O_Performative) }, ), "Performative_Empty_Contents_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Empty_Contents_Performative", (_message.Message,), { "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage.Performative_Empty_Contents_Performative) }, ), "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE, "__module__": "t_protocol_no_ct_pb2" # @@protoc_insertion_point(class_scope:aea.some_author.some_protocol_name.v1_0_0.TProtocolNoCtMessage) }, ) _sym_db.RegisterMessage(TProtocolNoCtMessage) _sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Pt_Performative) _sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Pct_Performative) _sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Pmt_Performative) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry ) _sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Mt_Performative) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry ) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry ) _sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_O_Performative) _sym_db.RegisterMessage( TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry ) _sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Empty_Contents_Performative) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._options = ( None ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._serialized_options = ( b"8\001" ) _TPROTOCOLNOCTMESSAGE._serialized_start = 70 _TPROTOCOLNOCTMESSAGE._serialized_end = 7381 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE._serialized_start = 848 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE._serialized_end = 988 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE._serialized_start = 991 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE._serialized_end = 1287 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE._serialized_start = 1290 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE._serialized_end = 4486 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._serialized_start = ( 3598 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._serialized_end = ( 3656 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._serialized_start = ( 3658 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._serialized_end = ( 3714 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._serialized_start = ( 3716 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._serialized_end = ( 3774 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._serialized_start = ( 3776 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._serialized_end = ( 3833 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._serialized_start = ( 3835 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._serialized_end = ( 3891 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._serialized_start = ( 3893 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._serialized_end = ( 3952 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._serialized_start = ( 3954 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._serialized_end = ( 4011 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._serialized_start = ( 4013 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._serialized_end = ( 4072 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._serialized_start = ( 4074 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._serialized_end = ( 4132 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._serialized_start = ( 4134 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._serialized_end = ( 4191 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._serialized_start = ( 4193 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._serialized_end = ( 4251 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._serialized_start = ( 4253 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._serialized_end = ( 4309 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._serialized_start = ( 4311 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._serialized_end = ( 4369 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._serialized_start = ( 4371 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._serialized_end = ( 4428 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._serialized_start = ( 4430 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._serialized_end = ( 4486 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE._serialized_start = 4489 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE._serialized_end = 6858 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._serialized_start = ( 6575 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._serialized_end = ( 6643 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._serialized_start = ( 6645 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._serialized_end = ( 6713 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._serialized_start = ( 6715 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._serialized_end = ( 6785 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._serialized_start = ( 6787 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._serialized_end = ( 6858 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE._serialized_start = 6861 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE._serialized_end = 7321 _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._serialized_start = ( 7264 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._serialized_end = ( 7321 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE._serialized_start = ( 7323 ) _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE._serialized_end = ( 7365 ) # @@protoc_insertion_point(module_scope) ================================================ FILE: tests/data/gym-connection.yaml ================================================ name: gym author: fetchai version: 0.1.0 type: connection license: Apache-2.0 fingerprint: __init__.py: QmWwxj1hGGZNteCvRtZxwtY9PuEKsrWsEmMWCKwiYCdvRR connection.py: QmPgSzbkwRE9CJ6sve7gvS62M3VdcBMfTozHdSgCnb7FPY fingerprint_ignore_patterns: [] aea_version: '>=1.0.0, <2.0.0' description: "The gym connection wraps an OpenAI gym." class_name: GymConnection connections: [] protocols: - fetchai/gym:1.1.7 restricted_to_protocols: ["fetchai/gym:1.1.7"] excluded_protocols: [] config: env: 'gyms.env.BanditNArmedRandom' dependencies: gym: {} is_abstract: false ================================================ FILE: tests/data/hashes.csv ================================================ dummy_author/agents/dummy_aea,Qmdh3dsyHEGYVyXgjbgnMd32D7zPrr6MqNGJ4Ye2gxPsVv dummy_author/skills/dummy_skill,QmX4qGcgk6Rs7mXszSWGXknzEabQJ44PAcw8YinWe2nRyB fetchai/connections/dummy_connection,QmNwX9YPbjQER5hypZk7P6kmi4Lt3Z3VuATXzNiCtzrTSR fetchai/contracts/dummy_contract,QmSMqYSAdM6mfA5beVS8XLjqYoCyzyv8aqq1YQtKZFf6vm fetchai/protocols/t_protocol,QmcsHPDZjVL4SfVJhyyJfHgUxqfhDA7JM6rR7DwtRraRdj fetchai/protocols/t_protocol_no_ct,QmfS8z6CiTRFUwduco5F5iFKtj5CKDaeKPdrvqbhyE7Qvn fetchai/skills/dependencies_skill,QmYmZt4oYbtRod6rjeGyeojCoPDmgzFaWTK322oFS5vf3a fetchai/skills/exception_skill,QmX7kNkQkcz23vtFExC9rpBN119atNHF8T8viqo2u9yrp8 ================================================ FILE: tests/data/petstore_sim.yaml ================================================ openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore license: name: MIT servers: - url: '' paths: /pets: get: summary: List all pets operationId: listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer format: int32 responses: '200': description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/Pets" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" post: summary: Create a pet operationId: createPets tags: - pets responses: '201': description: Null response default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById tags: - pets parameters: - name: petId in: path required: true description: The id of the pet to retrieve schema: type: string responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "#/components/schemas/Pet" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" components: schemas: Pet: type: object required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string Pets: type: array items: $ref: "#/components/schemas/Pet" Error: type: object required: - code - message properties: code: type: integer format: int32 message: type: string ================================================ FILE: tests/data/sample_specification.yaml ================================================ --- name: t_protocol author: fetchai version: 0.1.0 license: Apache-2.0 protocol_specification_id: some_author/some_protocol_name:1.0.0 aea_version: '>=1.0.0, <2.0.0' description: 'A protocol for testing purposes.' speech_acts: performative_ct: content_ct: ct:DataModel performative_pt: content_bytes: pt:bytes content_int: pt:int content_float: pt:float content_bool: pt:bool content_str: pt:str performative_pct: # content_set_ct: pt:set[ct:DataModel] # custom type inside of set, list, and dict isn't allowed. content_set_bytes: pt:set[pt:bytes] content_set_int: pt:set[pt:int] content_set_float: pt:set[pt:float] content_set_bool: pt:set[pt:bool] content_set_str: pt:set[pt:str] # content_list_ct: pt:list[ct:DataModel] # custom type inside of set, list, and dict isn't allowed. content_list_bytes: pt:list[pt:bytes] content_list_int: pt:list[pt:int] content_list_float: pt:list[pt:float] content_list_bool: pt:list[pt:bool] content_list_str: pt:list[pt:str] performative_pmt: # custom type inside of set, list, and dict isn't allowed. # content_dict_int_ct: pt:dict[pt:int, ct:DataModel] # content_dict_ct_ct: pt:dict[ct:DataModel, ct:DataModel] # content_dict_ct_bool: pt:dict[ct:DataModel, pt:bool] # invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') # content_dict_bytes_bytes: pt:dict[pt:bytes, pt:bytes] # content_dict_bytes_int: pt:dict[pt:bytes, pt:int] # content_dict_bytes_float: pt:dict[pt:bytes, pt:float] # content_dict_bytes_bool: pt:dict[pt:bytes, pt:bool] # content_dict_bytes_str: pt:dict[pt:bytes, pt:str] content_dict_int_bytes: pt:dict[pt:int, pt:bytes] content_dict_int_int: pt:dict[pt:int, pt:int] content_dict_int_float: pt:dict[pt:int, pt:float] content_dict_int_bool: pt:dict[pt:int, pt:bool] content_dict_int_str: pt:dict[pt:int, pt:str] # invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') # content_dict_float_bytes: pt:dict[pt:float, pt:bytes] # content_dict_float_int: pt:dict[pt:float, pt:int] # content_dict_float_float: pt:dict[pt:float, pt:float] # content_dict_float_bool: pt:dict[pt:float, pt:bool] # content_dict_float_str: pt:dict[pt:float, pt:str] content_dict_bool_bytes: pt:dict[pt:bool, pt:bytes] content_dict_bool_int: pt:dict[pt:bool, pt:int] content_dict_bool_float: pt:dict[pt:bool, pt:float] content_dict_bool_bool: pt:dict[pt:bool, pt:bool] content_dict_bool_str: pt:dict[pt:bool, pt:str] content_dict_str_bytes: pt:dict[pt:str, pt:bytes] content_dict_str_int: pt:dict[pt:str, pt:int] content_dict_str_float: pt:dict[pt:str, pt:float] content_dict_str_bool: pt:dict[pt:str, pt:bool] content_dict_str_str: pt:dict[pt:str, pt:str] performative_mt: content_union_1: pt:union[ct:DataModel1, pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str, pt:int]] content_union_2: pt:union[pt:set[pt:bytes], pt:set[pt:int], pt:set[pt:str], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:bool, pt:bytes], pt:int] content_union_3: pt:union[ct:DataModel2, ct:DataModel3] performative_o: content_o_ct: pt:optional[ct:DataModel4] content_o_bool: pt:optional[pt:bool] content_o_set_int: pt:optional[pt:set[pt:int]] content_o_list_bytes: pt:optional[pt:list[pt:bytes]] content_o_dict_str_int: pt:optional[pt:dict[pt:str, pt:int]] # union does not work properly in the generator # content_o_union: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:int], pt:set[pt:int], pt:set[pt:bytes], pt:list[pt:bool], pt:dict[pt:str, pt:float]]] performative_empty_contents: {} ... --- ct:DataModel: | bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; ct:DataModel1: | bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; ct:DataModel2: | bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; ct:DataModel3: | bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; ct:DataModel4: | bytes bytes_field = 1; int64 int_field = 2; float float_field = 3; bool bool_field = 4; string str_field = 5; repeated int64 set_field = 6; repeated string list_field = 7; map dict_field = 8; ... --- initiation: [performative_ct, performative_pt] reply: performative_ct: [performative_pct] performative_pt: [performative_pt, performative_pmt] performative_pct: [performative_mt, performative_o] performative_pmt: [performative_mt, performative_o] performative_mt: [] performative_o: [] performative_empty_contents: [performative_empty_contents] termination: [performative_mt, performative_o] roles: {role_1, role_2} end_states: [end_state_1, end_state_2, end_state_3] keep_terminal_state_dialogues: true ... ================================================ FILE: tests/data/sample_specification_no_custom_types.yaml ================================================ --- name: t_protocol_no_ct author: fetchai version: 0.1.0 license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' protocol_specification_id: some_author/some_protocol_name:1.0.0 description: 'A protocol for testing purposes.' speech_acts: performative_pt: content_bytes: pt:bytes content_int: pt:int content_float: pt:float content_bool: pt:bool content_str: pt:str performative_pct: # content_set_ct: pt:set[ct:DataModel] # custom type inside of set, list, and dict isn't allowed. content_set_bytes: pt:set[pt:bytes] content_set_int: pt:set[pt:int] content_set_float: pt:set[pt:float] content_set_bool: pt:set[pt:bool] content_set_str: pt:set[pt:str] # content_list_ct: pt:list[ct:DataModel] # custom type inside of set, list, and dict isn't allowed. content_list_bytes: pt:list[pt:bytes] content_list_int: pt:list[pt:int] content_list_float: pt:list[pt:float] content_list_bool: pt:list[pt:bool] content_list_str: pt:list[pt:str] performative_pmt: # custom type inside of set, list, and dict isn't allowed. # content_dict_int_ct: pt:dict[pt:int, ct:DataModel] # content_dict_ct_ct: pt:dict[ct:DataModel, ct:DataModel] # content_dict_ct_bool: pt:dict[ct:DataModel, pt:bool] # invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') # content_dict_bytes_bytes: pt:dict[pt:bytes, pt:bytes] # content_dict_bytes_int: pt:dict[pt:bytes, pt:int] # content_dict_bytes_float: pt:dict[pt:bytes, pt:float] # content_dict_bytes_bool: pt:dict[pt:bytes, pt:bool] # content_dict_bytes_str: pt:dict[pt:bytes, pt:str] content_dict_int_bytes: pt:dict[pt:int, pt:bytes] content_dict_int_int: pt:dict[pt:int, pt:int] content_dict_int_float: pt:dict[pt:int, pt:float] content_dict_int_bool: pt:dict[pt:int, pt:bool] content_dict_int_str: pt:dict[pt:int, pt:str] # invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') # content_dict_float_bytes: pt:dict[pt:float, pt:bytes] # content_dict_float_int: pt:dict[pt:float, pt:int] # content_dict_float_float: pt:dict[pt:float, pt:float] # content_dict_float_bool: pt:dict[pt:float, pt:bool] # content_dict_float_str: pt:dict[pt:float, pt:str] content_dict_bool_bytes: pt:dict[pt:bool, pt:bytes] content_dict_bool_int: pt:dict[pt:bool, pt:int] content_dict_bool_float: pt:dict[pt:bool, pt:float] content_dict_bool_bool: pt:dict[pt:bool, pt:bool] content_dict_bool_str: pt:dict[pt:bool, pt:str] content_dict_str_bytes: pt:dict[pt:str, pt:bytes] content_dict_str_int: pt:dict[pt:str, pt:int] content_dict_str_float: pt:dict[pt:str, pt:float] content_dict_str_bool: pt:dict[pt:str, pt:bool] content_dict_str_str: pt:dict[pt:str, pt:str] performative_mt: content_union_1: pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str, pt:int]] content_union_2: pt:union[pt:set[pt:bytes], pt:set[pt:int], pt:set[pt:str], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:bool, pt:bytes]] performative_o: content_o_bool: pt:optional[pt:bool] content_o_set_int: pt:optional[pt:set[pt:int]] content_o_list_bytes: pt:optional[pt:list[pt:bytes]] content_o_dict_str_int: pt:optional[pt:dict[pt:str, pt:int]] # union does not work properly in the generator # content_o_union: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:int], pt:set[pt:int], pt:set[pt:bytes], pt:list[pt:bool], pt:dict[pt:str, pt:float]]] performative_empty_contents: {} ... --- ... --- initiation: [performative_pt] reply: performative_pt: [performative_pt, performative_pct, performative_pmt] performative_pct: [performative_mt, performative_o] performative_pmt: [performative_mt, performative_o] performative_mt: [] performative_o: [] performative_empty_contents: [performative_empty_contents] termination: [performative_mt, performative_o] roles: {role_1, role_2} end_states: [end_state_1, end_state_2, end_state_3] keep_terminal_state_dialogues: true ... ================================================ FILE: tests/list_of_packages_for_aea_tests.txt ================================================ ./fetchai/skills/weather_client/skill.yaml ./fetchai/skills/generic_buyer/skill.yaml ./fetchai/skills/generic_seller/skill.yaml ./fetchai/skills/erc1155_client/skill.yaml ./fetchai/skills/weather_station/skill.yaml ./fetchai/skills/error_test_skill/skill.yaml ./fetchai/skills/error/skill.yaml ./fetchai/skills/gym/skill.yaml ./fetchai/skills/echo/skill.yaml ./fetchai/skills/task_test_skill/skill.yaml ./fetchai/protocols/fipa/protocol.yaml ./fetchai/protocols/http/protocol.yaml ./fetchai/protocols/contract_api/protocol.yaml ./fetchai/protocols/state_update/protocol.yaml ./fetchai/protocols/tac/protocol.yaml ./fetchai/protocols/acn/protocol.yaml ./fetchai/protocols/register/protocol.yaml ./fetchai/protocols/ledger_api/protocol.yaml ./fetchai/protocols/oef_search/protocol.yaml ./fetchai/protocols/signing/protocol.yaml ./fetchai/protocols/default/protocol.yaml ./fetchai/protocols/gym/protocol.yaml ./fetchai/contracts/erc1155/contract.yaml ./fetchai/agents/weather_client/aea-config.yaml ./fetchai/agents/generic_buyer/aea-config.yaml ./fetchai/agents/generic_seller/aea-config.yaml ./fetchai/agents/error_test/aea-config.yaml ./fetchai/agents/my_first_aea/aea-config.yaml ./fetchai/agents/weather_station/aea-config.yaml ./fetchai/connections/soef/connection.yaml ./fetchai/connections/oef/connection.yaml ./fetchai/connections/tcp/connection.yaml ./fetchai/connections/ledger/connection.yaml ./fetchai/connections/p2p_libp2p_client/connection.yaml ./fetchai/connections/p2p_stub/connection.yaml ./fetchai/connections/p2p_libp2p_mailbox/connection.yaml ./fetchai/connections/stub/connection.yaml ./fetchai/connections/http_client/connection.yaml ./fetchai/connections/local/connection.yaml ./fetchai/connections/p2p_libp2p/connection.yaml ./fetchai/connections/p2p_libp2p/libp2p_node/protocols/acn/v1_0_0/acn.yaml ./fetchai/connections/gym/connection.yaml ./fetchai/connections/http_server/connection.yaml ================================================ FILE: tests/oracle_contract_address.txt ================================================ fetch14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9szlkpka ================================================ FILE: tests/test_aea/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the AEA package.""" ================================================ FILE: tests/test_aea/test_act_storage.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests behaviour storage access.""" import os from typing import List, Set import pytest from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.configurations.base import SkillConfig from aea.configurations.constants import DEFAULT_LEDGER from aea.mail.base import Envelope from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import ( BasicDialoguesStorage, Dialogue, DialogueLabel, PersistDialoguesStorage, ) from aea.skills.base import Handler, Skill, SkillContext from aea.skills.behaviours import TickerBehaviour from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue, DefaultDialogues, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.skills.echo import PUBLIC_ID from tests.common.utils import wait_for_condition class TBehaviour(TickerBehaviour): """Simple behaviour to count how many acts were called.""" OBJ_ID = "some" OBJ_BODY = {"data": 12} COL_NAME = "test" def setup(self) -> None: """Set up behaviour.""" self.counter = 0 def act(self) -> None: """Make an action.""" if self.context.storage and self.context.storage.is_connected: col = self.context.storage.get_sync_collection(self.COL_NAME) col.put(self.OBJ_ID, self.OBJ_BODY) self.counter += 1 class THandler(Handler): """Simple behaviour to count how many acts were called.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id OBJ_ID = "some" OBJ_BODY = {"data": 12} COL_NAME = "test" def setup(self) -> None: """Set up behaviour.""" self.counter = 0 def teardown(self) -> None: """Tear down handler.""" def handle(self, *args, **kwargs) -> None: """Handle an evelope.""" if self.context.storage and self.context.storage.is_connected: col = self.context.storage.get_sync_collection(self.COL_NAME) col.put(self.OBJ_ID, self.OBJ_BODY) self.counter += 1 def test_storage_access_from_behaviour(): """Test storage access from behaviour component.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key(DEFAULT_LEDGER) skill_context = SkillContext() behaviour = TBehaviour(name="behaviour", skill_context=skill_context) test_skill = Skill( SkillConfig(name="test_skill", author="fetchai"), skill_context=skill_context, handlers={}, behaviours={"behaviour": behaviour}, ) builder.add_component_instance(test_skill) builder.set_storage_uri("sqlite://:memory:") aea = builder.build() skill_context.set_agent_context(aea.context) aea.runtime._threaded = True aea.runtime.start() try: wait_for_condition(lambda: aea.is_running, timeout=10) wait_for_condition(lambda: behaviour.counter > 0, timeout=10) col = skill_context.storage.get_sync_collection(behaviour.COL_NAME) assert col.get(behaviour.OBJ_ID) == behaviour.OBJ_BODY finally: aea.runtime.stop() aea.runtime.wait_completed(sync=True, timeout=10) def test_storage_access_from_handler(): """Test storage access from handler component.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key(DEFAULT_LEDGER) skill_context = SkillContext() handler = THandler(name="behaviour", skill_context=skill_context) test_skill = Skill( SkillConfig(name="test_skill", author="fetchai"), skill_context=skill_context, handlers={"handler": handler}, behaviours={}, ) builder.add_component_instance(test_skill) builder.set_storage_uri("sqlite://:memory:") aea = builder.build() skill_context.set_agent_context(aea.context) aea.runtime._threaded = True aea.runtime.start() msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = aea.identity.address msg.sender = aea.identity.address envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) try: wait_for_condition(lambda: aea.is_running, timeout=10) aea.runtime.multiplexer.in_queue.put(envelope) wait_for_condition(lambda: handler.counter > 0, timeout=10) col = skill_context.storage.get_sync_collection(handler.COL_NAME) assert col.get(handler.OBJ_ID) == handler.OBJ_BODY finally: aea.runtime.stop() aea.runtime.wait_completed(sync=True, timeout=10) def _get_labels(dialogues: List[Dialogue]) -> Set[DialogueLabel]: return set([i.dialogue_label for i in dialogues]) def _storage_all_dialogues_labels(storage: BasicDialoguesStorage) -> Set[DialogueLabel]: return _get_labels( storage.dialogues_in_active_state + storage.dialogues_in_terminal_state ) class TestDialogueModelSaveLoad(AEATestCaseEmpty): """Test dialogues sved and loaded on agent restart.""" def setup(self): """Set up the test case.""" self.add_item("skill", "fetchai/echo:latest", local=True) pkey_file = os.path.join(self._get_cwd(), "privkey") self.generate_private_key("fetchai", pkey_file) self.add_private_key("fetchai", pkey_file, False) self.add_private_key("fetchai", pkey_file, True) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT self.dialogues = DefaultDialogues( self_address="another_agent", role_from_first_message=role_from_first_message, ) def _build_aea(self) -> AEA: """Build an AEA.""" builder = AEABuilder.from_aea_project(self._get_cwd()) builder.set_storage_uri("sqlite://some_file.db") aea = builder.build() aea.runtime._threaded = True return aea def test_dialogues_dumped_and_restored_properly(self): """Test dialogues restored during restart of agent.""" aea = self._build_aea() aea.runtime.start() try: wait_for_condition(lambda: aea.is_running, timeout=10) echo_skill = aea.resources.get_skill(PUBLIC_ID) assert ( not echo_skill.skill_context.default_dialogues._dialogues_storage._dialogues_by_dialogue_label ) msg, dialogue = self.dialogues.create( aea.name, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) aea.runtime.multiplexer.in_queue.put(envelope) dialogue_storage: PersistDialoguesStorage = ( echo_skill.skill_context.default_dialogues._dialogues_storage ) wait_for_condition( lambda: _storage_all_dialogues_labels(dialogue_storage), timeout=3, ) dialogues_for_check = _storage_all_dialogues_labels(dialogue_storage) finally: aea.runtime.stop() aea.runtime.wait_completed(sync=True, timeout=10) aea = self._build_aea() aea.runtime.start() try: wait_for_condition(lambda: aea.is_running, timeout=10) echo_skill = aea.resources.get_skill(PUBLIC_ID) dialogue_storage: PersistDialoguesStorage = ( echo_skill.skill_context.default_dialogues._dialogues_storage ) wait_for_condition( lambda: _storage_all_dialogues_labels(dialogue_storage), timeout=3, ) assert ( _storage_all_dialogues_labels(dialogue_storage) == dialogues_for_check ) finally: aea.runtime.stop() aea.runtime.wait_completed(sync=True, timeout=10) if __name__ == "__main__": pytest.main([os.path.basename(__file__)]) ================================================ FILE: tests/test_aea/test_aea.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for aea/aea.py.""" import os import tempfile import time import unittest from pathlib import Path from threading import Thread from typing import Callable from unittest.case import TestCase from unittest.mock import MagicMock, PropertyMock, patch import pytest import aea # noqa: F401 from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.configurations.base import SkillConfig from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE from aea.crypto.wallet import Wallet from aea.exceptions import AEAActException, AEAException, AEAHandleException from aea.helpers.base import cd from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Protocol from aea.registries.resources import Resources from aea.runtime import RuntimeStates from aea.skills.base import Skill, SkillContext from packages.fetchai.connections.local.connection import LocalNode, OEFLocalConnection from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.default.serialization import DefaultSerializer from packages.fetchai.protocols.fipa.message import FipaMessage from tests.common.utils import ( AeaTool, make_behaviour_cls_from_funcion, make_handler_cls_from_funcion, run_in_thread, timeit_context, wait_for_condition, ) from tests.conftest import ( CUR_PATH, FETCHAI_PRIVATE_KEY_PATH, ROOT_DIR, UNKNOWN_PROTOCOL_PUBLIC_ID, _make_local_connection, ) from tests.data.dummy_aea.skills.dummy.tasks import DummyTask # type: ignore from tests.data.dummy_skill import PUBLIC_ID as DUMMY_SKILL_PUBLIC_ID from tests.data.dummy_skill.behaviours import DummyBehaviour # type: ignore def test_setup_aea(): """Tests the initialisation of the AEA.""" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name("my_name").add_private_key(DEFAULT_LEDGER, private_key_path) my_AEA = builder.build() assert my_AEA.context == my_AEA._context, "Cannot access the Agent's Context" assert ( not my_AEA.context.connection_status.is_connected ), "AEA should not be connected." my_AEA.setup() assert my_AEA.resources is not None, "Resources must not be None after setup" my_AEA.resources = Resources() assert my_AEA.resources is not None, "Resources must not be None after set" assert ( my_AEA.context.shared_state is not None ), "Shared state must not be None after set" assert my_AEA.context.task_manager is not None assert my_AEA.context.identity is not None, "Identity must not be None after set." my_AEA.teardown() def test_act(): """Tests the act function of the AEA.""" agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) agent = builder.build() with run_in_thread(agent.start, timeout=20): wait_for_condition(lambda: agent.is_running, timeout=20) behaviour = agent.resources.get_behaviour(DUMMY_SKILL_PUBLIC_ID, "dummy") time.sleep(1) wait_for_condition(lambda: behaviour.nb_act_called > 0, timeout=20) agent.stop() def test_start_stop(): """Tests the act function of the AEA.""" agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) agent = builder.build() with run_in_thread(agent.start, timeout=20): wait_for_condition(lambda: agent.is_running, timeout=20) agent.stop() def test_double_start(): """Tests the act function of the AEA.""" agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) agent = builder.build() with run_in_thread(agent.start, timeout=20): try: wait_for_condition(lambda: agent.is_running, timeout=20) t = Thread(target=agent.start) t.start() time.sleep(1) assert not t.is_alive() finally: agent.stop() t.join() def test_react(): """Tests income messages.""" with LocalNode() as node: agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") ) builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) local_connection_id = OEFLocalConnection.connection_id builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) agent = builder.build(connection_ids=[local_connection_id]) # This is a temporary workaround to feed the local node to the OEF Local connection # TODO remove it. local_connection = agent.resources.get_connection(local_connection_id) local_connection._local_node = node msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = agent.identity.address msg.sender = agent.identity.address envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) with run_in_thread(agent.start, timeout=20, on_exit=agent.stop): wait_for_condition(lambda: agent.is_running, timeout=20) agent.outbox.put(envelope) default_protocol_public_id = DefaultMessage.protocol_id dummy_skill_public_id = DUMMY_SKILL_PUBLIC_ID handler = agent.resources.get_handler( default_protocol_public_id, dummy_skill_public_id ) assert handler is not None, "Handler is not set." wait_for_condition( lambda: len(handler.handled_messages) > 0, timeout=20, error_msg="The message is not inside the handled_messages.", ) def test_handle(): """Tests handle method of an agent.""" with LocalNode() as node: agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_protocol(Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa")) builder.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") ) builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) local_connection_id = OEFLocalConnection.connection_id builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) an_aea = builder.build(connection_ids=[local_connection_id]) # This is a temporary workaround to feed the local node to the OEF Local connection # TODO remove it. local_connection = an_aea.resources.get_connection(local_connection_id) local_connection._local_node = node msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = an_aea.identity.address msg.sender = an_aea.identity.address encoded_msg = DefaultSerializer.encode(msg) error_handler = an_aea._error_handler with run_in_thread(an_aea.start, timeout=5): wait_for_condition(lambda: an_aea.is_running, timeout=10) dummy_skill = an_aea.resources.get_skill(DUMMY_SKILL_PUBLIC_ID) dummy_handler = dummy_skill.skill_context.handlers.dummy # UNSUPPORTED PROTOCOL envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) envelope._protocol_specification_id = UNKNOWN_PROTOCOL_PUBLIC_ID # send envelope via localnode back to agent/bypass `outbox` put consistency checks assert error_handler.unsupported_protocol_count == 0 an_aea.outbox.put(envelope) wait_for_condition( lambda: error_handler.unsupported_protocol_count == 1, timeout=2, ) # DECODING ERROR envelope = Envelope( to=an_aea.identity.address, sender=an_aea.identity.address, protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", ) assert error_handler.decoding_error_count == 0 an_aea.runtime.multiplexer.put(envelope) wait_for_condition( lambda: error_handler.decoding_error_count == 1, timeout=5, ) # UNSUPPORTED SKILL msg = FipaMessage( performative=FipaMessage.Performative.ACCEPT, message_id=1, dialogue_reference=(str(0), ""), target=0, ) msg.to = an_aea.identity.address msg.sender = an_aea.identity.address envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) # send envelope via localnode back to agent/bypass `outbox` put consistency checks assert error_handler.no_active_handler_count == 0 an_aea.outbox.put(envelope) wait_for_condition( lambda: error_handler.no_active_handler_count == 1, timeout=5, ) # DECODING OK envelope = Envelope( to=msg.to, sender=msg.sender, protocol_specification_id=DefaultMessage.protocol_specification_id, message=encoded_msg, ) # send envelope via localnode back to agent/bypass `outbox` put consistency checks assert len(dummy_handler.handled_messages) == 0 an_aea.runtime.multiplexer.put(envelope) wait_for_condition( lambda: len(dummy_handler.handled_messages) == 1, timeout=5, ) an_aea.stop() def test_initialize_aea_programmatically(): """Test that we can initialize an AEA programmatically.""" with LocalNode() as node: agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") ) builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) local_connection_id = OEFLocalConnection.connection_id builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) an_aea = builder.build(connection_ids=[local_connection_id]) local_connection = an_aea.resources.get_connection(local_connection_id) local_connection._local_node = node expected_message = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_message.to = an_aea.identity.address expected_message.sender = an_aea.identity.address envelope = Envelope( to=expected_message.to, sender=expected_message.sender, message=expected_message, ) with run_in_thread(an_aea.start, timeout=5, on_exit=an_aea.stop): wait_for_condition(lambda: an_aea.is_running, timeout=10) an_aea.outbox.put(envelope) dummy_skill_id = DUMMY_SKILL_PUBLIC_ID dummy_behaviour_name = "dummy" dummy_behaviour = an_aea.resources.get_behaviour( dummy_skill_id, dummy_behaviour_name ) wait_for_condition(lambda: dummy_behaviour is not None, timeout=10) wait_for_condition(lambda: dummy_behaviour.nb_act_called > 0, timeout=10) # TODO the previous code caused an error: # _pickle.PicklingError: Can't pickle : import of module 'tasks' failed dummy_task = DummyTask() task_id = an_aea.enqueue_task(dummy_task) async_result = an_aea.get_task_result(task_id) expected_result = async_result.get(10.0) assert expected_result == 1 dummy_handler = an_aea.resources.get_handler( DefaultMessage.protocol_id, dummy_skill_id ) dummy_handler_alt = an_aea.resources._handler_registry.fetch( (dummy_skill_id, "dummy") ) wait_for_condition(lambda: dummy_handler == dummy_handler_alt, timeout=10) wait_for_condition(lambda: dummy_handler is not None, timeout=10) wait_for_condition( lambda: len(dummy_handler.handled_messages) == 1, timeout=10 ) wait_for_condition( lambda: dummy_handler.handled_messages[0] == expected_message, timeout=10, ) def test_initialize_aea_programmatically_build_resources(): """Test that we can initialize the agent by building the resource object.""" try: temp = tempfile.mkdtemp(prefix="test_aea_resources") with LocalNode() as node: agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) wallet = Wallet({DEFAULT_LEDGER: private_key_path}) identity = Identity( agent_name, address=wallet.addresses[DEFAULT_LEDGER], public_key=wallet.public_keys[DEFAULT_LEDGER], ) connection = _make_local_connection( agent_name, agent_name + "_public_key", node ) resources = Resources() default_protocol = Protocol.from_dir( str(Path("packages", "fetchai", "protocols", "default")) ) resources.add_protocol(default_protocol) resources.add_connection(connection) an_aea = AEA( identity, wallet, resources=resources, data_dir=MagicMock(), default_connection=connection.public_id, ) error_skill = Skill.from_dir( str(Path("packages", "fetchai", "skills", "error")), agent_context=an_aea.context, ) dummy_skill = Skill.from_dir( str(Path(CUR_PATH, "data", "dummy_skill")), agent_context=an_aea.context ) resources.add_skill(dummy_skill) resources.add_skill(error_skill) expected_message = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_message.to = agent_name expected_message.sender = agent_name with run_in_thread(an_aea.start, timeout=5, on_exit=an_aea.stop): wait_for_condition(lambda: an_aea.is_running, timeout=10) an_aea.outbox.put( Envelope( to=agent_name, sender=agent_name, message=expected_message, ) ) dummy_skill_id = DUMMY_SKILL_PUBLIC_ID dummy_behaviour_name = "dummy" dummy_behaviour = an_aea.resources.get_behaviour( dummy_skill_id, dummy_behaviour_name ) wait_for_condition(lambda: dummy_behaviour is not None, timeout=10) wait_for_condition( lambda: dummy_behaviour.nb_act_called > 0, timeout=10 ) dummy_task = DummyTask() task_id = an_aea.enqueue_task(dummy_task) async_result = an_aea.get_task_result(task_id) expected_result = async_result.get(10.0) assert expected_result == 1 dummy_handler_name = "dummy" dummy_handler = an_aea.resources._handler_registry.fetch( (dummy_skill_id, dummy_handler_name) ) dummy_handler_alt = an_aea.resources.get_handler( DefaultMessage.protocol_id, dummy_skill_id ) wait_for_condition( lambda: dummy_handler == dummy_handler_alt, timeout=10 ) wait_for_condition(lambda: dummy_handler is not None, timeout=10) wait_for_condition( lambda: len(dummy_handler.handled_messages) == 1, timeout=10 ) wait_for_condition( lambda: dummy_handler.handled_messages[0] == expected_message, timeout=10, ) finally: Path(temp).rmdir() def test_add_behaviour_dynamically(): """Test that we can add a behaviour dynamically.""" agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) wallet = Wallet({DEFAULT_LEDGER: private_key_path}) data_dir = MagicMock() resources = Resources() identity = Identity( agent_name, address=wallet.addresses[DEFAULT_LEDGER], public_key=wallet.public_keys[DEFAULT_LEDGER], ) with LocalNode() as local_node: connection = _make_local_connection( identity.address, identity.public_key, local_node ) resources.add_connection(connection) agent = AEA( identity, wallet, resources, data_dir, default_connection=connection.public_id, ) resources.add_component( Skill.from_dir( Path(CUR_PATH, "data", "dummy_skill"), agent_context=agent.context ) ) for skill in resources.get_all_skills(): skill.skill_context.set_agent_context(agent.context) dummy_skill_id = DUMMY_SKILL_PUBLIC_ID old_nb_behaviours = len(agent.resources.get_behaviours(dummy_skill_id)) with run_in_thread(agent.start, timeout=5, on_exit=agent.stop): wait_for_condition(lambda: agent.is_running, timeout=10) dummy_skill = agent.resources.get_skill(dummy_skill_id) wait_for_condition(lambda: dummy_skill is not None, timeout=10) new_behaviour = DummyBehaviour( name="dummy2", skill_context=dummy_skill.skill_context ) dummy_skill.skill_context.new_behaviours.put(new_behaviour) wait_for_condition(lambda: new_behaviour.nb_act_called > 0, timeout=10) wait_for_condition( lambda: len(agent.resources.get_behaviours(dummy_skill_id)) == old_nb_behaviours + 1, timeout=10, ) def test_no_handlers_registered(): """Test no handlers are registered for message processing.""" agent_name = "MyAgent" builder = AEABuilder() private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) an_aea = builder.build() with patch.object(an_aea.logger, "warning") as mock_logger: msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = an_aea.identity.address envelope = Envelope( to=an_aea.identity.address, sender=an_aea.identity.address, message=msg, ) with patch( "aea.registries.filter.Filter.get_active_handlers", new_callable=PropertyMock, ): with patch.object( an_aea.runtime.multiplexer, "put", ): an_aea.handle_envelope(envelope) mock_logger.assert_any_call( f"Cannot handle envelope: no active handler for protocol={msg.protocol_id}. Sender={envelope.sender}, to={envelope.sender}." ) class TestContextNamespace: """Test that the keyword arguments to AEA constructor can be accessible from the skill context.""" @classmethod def setup_class(cls): """Set the test up.""" agent_name = "my_agent" data_dir = MagicMock() private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) wallet = Wallet({DEFAULT_LEDGER: private_key_path}) identity = Identity( agent_name, address=wallet.addresses[DEFAULT_LEDGER], public_key=wallet.public_keys[DEFAULT_LEDGER], ) connection = _make_local_connection( identity.address, identity.public_key, LocalNode() ) resources = Resources() resources.add_connection(connection) cls.context_namespace = {"key1": 1, "key2": 2} cls.agent = AEA(identity, wallet, resources, data_dir, **cls.context_namespace) resources.add_component( Skill.from_dir( Path(CUR_PATH, "data", "dummy_skill"), agent_context=cls.agent.context ) ) for skill in resources.get_all_skills(): skill.skill_context.set_agent_context(cls.agent.context) def test_access_context_namespace(self): """Test that we can access the context namespace.""" assert self.agent.context.namespace.key1 == 1 assert self.agent.context.namespace.key2 == 2 for skill in self.agent.resources.get_all_skills(): assert skill.skill_context.namespace.key1 == 1 assert skill.skill_context.namespace.key2 == 2 def test_start_stop_and_start_stop_again(): """Tests AEA can be started/stopped twice.""" agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) agent = builder.build() with run_in_thread(agent.start, timeout=20): wait_for_condition(lambda: agent.is_running, timeout=10) behaviour = agent.resources.get_behaviour(DUMMY_SKILL_PUBLIC_ID, "dummy") time.sleep(1) wait_for_condition(lambda: behaviour.nb_act_called > 0, timeout=5) agent.stop() wait_for_condition(lambda: agent.is_stopped, timeout=10) behaviour.nb_act_called = 0 time.sleep(2) assert behaviour.nb_act_called == 0 with run_in_thread(agent.start, timeout=20): wait_for_condition(lambda: agent.is_running, timeout=10) time.sleep(1) wait_for_condition(lambda: behaviour.nb_act_called > 0, timeout=5) agent.stop() wait_for_condition(lambda: agent.is_stopped, timeout=10) class ExpectedExcepton(Exception): """Exception for testing.""" class TestAeaExceptionPolicy: """Tests for exception policies.""" @staticmethod def raise_exception(*args, **kwargs) -> None: """Raise exception for tests.""" raise ExpectedExcepton("we wait it!") def setup(self) -> None: """Set test cae instance.""" agent_name = "MyAgent" builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, FETCHAI_PRIVATE_KEY_PATH) self.handler_called = 0 def handler_func(*args, **kwargs): self.handler_called += 1 skill_context = SkillContext() handler_cls = make_handler_cls_from_funcion(handler_func) behaviour_cls = make_behaviour_cls_from_funcion(handler_func) self.handler = handler_cls(name="handler1", skill_context=skill_context) self.behaviour = behaviour_cls(name="behaviour1", skill_context=skill_context) test_skill = Skill( SkillConfig(name="test_skill", author="fetchai"), skill_context=skill_context, handlers={"handler": self.handler}, behaviours={"behaviour": self.behaviour}, ) skill_context._skill = test_skill # weird hack builder.add_component_instance(test_skill) self.aea = builder.build() self.aea_tool = AeaTool(self.aea) def test_no_exceptions(self) -> None: """Test act and handle works if no exception raised.""" t = Thread(target=self.aea.start) t.start() self.aea_tool.put_inbox(self.aea_tool.dummy_envelope()) time.sleep(1) try: assert self.handler_called >= 2 finally: self.aea.stop() t.join() def test_handle_propagate(self) -> None: """Test propagate policy on message handle.""" self.aea._skills_exception_policy = ExceptionPolicyEnum.propagate self.handler.handle = self.raise_exception # type: ignore # cause error: Cannot assign to a method self.aea_tool.put_inbox(self.aea_tool.dummy_envelope()) with pytest.raises(AEAHandleException): with pytest.raises(ExpectedExcepton): self.aea.start() assert not self.aea.is_running def test_handle_stop_and_exit(self) -> None: """Test stop and exit policy on message handle.""" self.aea._skills_exception_policy = ExceptionPolicyEnum.stop_and_exit self.handler.handle = self.raise_exception # type: ignore # cause error: Cannot assign to a method self.aea_tool.put_inbox(self.aea_tool.dummy_envelope()) with pytest.raises( AEAException, match=r"AEA was terminated cause exception .*" ): self.aea.start() assert not self.aea.is_running def test_handle_just_log(self) -> None: """Test just log policy on message handle.""" self.aea._skills_exception_policy = ExceptionPolicyEnum.just_log self.handler.handle = self.raise_exception # type: ignore # cause error: Cannot assign to a method with patch.object(self.aea._logger, "exception") as patched: t = Thread(target=self.aea.start) t.start() self.aea_tool.put_inbox(self.aea_tool.dummy_envelope()) self.aea_tool.put_inbox(self.aea_tool.dummy_envelope()) time.sleep(1) try: assert self.aea.is_running assert patched.call_count == 2 finally: self.aea.stop() t.join() def test_act_propagate(self) -> None: """Test propagate policy on behaviour act.""" self.aea._skills_exception_policy = ExceptionPolicyEnum.propagate self.behaviour.act = self.raise_exception # type: ignore # cause error: Cannot assign to a method with pytest.raises(AEAActException): with pytest.raises(ExpectedExcepton): self.aea.start() assert self.aea.runtime.state == RuntimeStates.error def test_act_stop_and_exit(self) -> None: """Test stop and exit policy on behaviour act.""" self.aea._skills_exception_policy = ExceptionPolicyEnum.stop_and_exit self.behaviour.act = self.raise_exception # type: ignore # cause error: Cannot assign to a method with pytest.raises( AEAException, match=r"AEA was terminated cause exception .*" ): self.aea.start() assert not self.aea.is_running def test_act_just_log(self) -> None: """Test just log policy on behaviour act.""" self.aea._skills_exception_policy = ExceptionPolicyEnum.just_log self.behaviour.act = self.raise_exception # type: ignore # cause error: Cannot assign to a method with patch.object(self.aea.logger, "exception") as patched: t = Thread(target=self.aea.start) t.start() time.sleep(1) try: assert self.aea.is_running assert patched.call_count > 1 finally: self.aea.stop() t.join() def test_act_bad_policy(self) -> None: """Test propagate policy on behaviour act.""" self.aea._skills_exception_policy = "non exists policy" # type: ignore self.behaviour.act = self.raise_exception # type: ignore # cause error: Cannot assign to a method with pytest.raises(AEAException, match=r"Unsupported exception policy.*"): self.aea.start() assert not self.aea.is_running def teardown(self) -> None: """Stop AEA if not stopped.""" self.aea.stop() def sleep_a_bit(sleep_time: float = 0.1, num_of_sleeps: int = 1) -> None: """Sleep num_of_sleeps time for sleep_time. :param sleep_time: time to sleep. :param num_of_sleeps: how many time sleep for sleep_time. :return: None """ for _ in range(num_of_sleeps): time.sleep(sleep_time) class BaseTimeExecutionCase(TestCase): """Base Test case for code execute timeout.""" BASE_TIMEOUT = 0.35 @classmethod def setUpClass(cls) -> None: """Set up.""" if cls is BaseTimeExecutionCase: raise unittest.SkipTest("Skip BaseTest tests, it's a base class") def tearDown(self) -> None: """Tear down.""" self.aea_tool.teardown() self.aea_tool.aea.runtime.agent_loop._teardown() def prepare(self, function: Callable) -> None: """Prepare aea_tool for testing. :param function: function be called on react handle or/and Behaviour.act :return: None """ agent_name = "MyAgent" builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, FETCHAI_PRIVATE_KEY_PATH) self.function_finished = False def handler_func(*args, **kwargs): function() self.function_finished = True skill_context = SkillContext() handler_cls = make_handler_cls_from_funcion(handler_func) behaviour_cls = make_behaviour_cls_from_funcion(handler_func) self.behaviour = behaviour_cls(name="behaviour1", skill_context=skill_context) test_skill = Skill( SkillConfig(name="test_skill", author="fetchai"), skill_context=skill_context, handlers={ "handler1": handler_cls(name="handler1", skill_context=skill_context) }, behaviours={"behaviour1": self.behaviour}, ) skill_context._skill = test_skill # weird hack builder.add_component_instance(test_skill) my_aea = builder.build() self.aea_tool = AeaTool(my_aea) self.envelope = AeaTool.dummy_envelope() self.aea_tool.aea.runtime.agent_loop._setup() def test_long_handler_cancelled_by_timeout(self): """Test long function terminated by timeout.""" num_sleeps = 10 sleep_time = self.BASE_TIMEOUT function_sleep_time = num_sleeps * sleep_time execution_timeout = self.BASE_TIMEOUT * 2 assert execution_timeout < function_sleep_time self.prepare(lambda: sleep_a_bit(sleep_time, num_sleeps)) self.aea_tool.set_execution_timeout(execution_timeout) with timeit_context() as timeit: self.aea_action() assert execution_timeout <= timeit.time_passed <= function_sleep_time assert not self.function_finished def test_short_handler_not_cancelled_by_timeout(self): """Test short function NOT terminated by timeout.""" num_sleeps = 1 sleep_time = self.BASE_TIMEOUT function_sleep_time = num_sleeps * sleep_time execution_timeout = self.BASE_TIMEOUT * 2 assert function_sleep_time <= execution_timeout self.prepare(lambda: sleep_a_bit(sleep_time, num_sleeps)) self.aea_tool.set_execution_timeout(execution_timeout) self.aea_tool.setup() with timeit_context() as timeit: self.aea_action() assert function_sleep_time <= timeit.time_passed <= execution_timeout assert self.function_finished def test_no_timeout(self): """Test function NOT terminated by timeout cause timeout == 0.""" num_sleeps = 1 sleep_time = self.BASE_TIMEOUT function_sleep_time = num_sleeps * sleep_time execution_timeout = 0 self.prepare(lambda: sleep_a_bit(sleep_time, num_sleeps)) self.aea_tool.set_execution_timeout(execution_timeout) self.aea_tool.setup() with timeit_context() as timeit: self.aea_action() assert function_sleep_time <= timeit.time_passed assert self.function_finished class HandleTimeoutExecutionCase(BaseTimeExecutionCase): """Test handle envelope timeout.""" def aea_action(self): """Spin react on AEA.""" self.aea_tool.aea.runtime.agent_loop._execution_control( self.aea_tool.handle_envelope, [self.envelope] ) class ActTimeoutExecutionCase(BaseTimeExecutionCase): """Test act timeout.""" def aea_action(self): """Spin act on AEA.""" self.aea_tool.aea.runtime.agent_loop._execution_control( self.behaviour.act_wrapper ) def test_skill2skill_message(): """Tests message can be sent directly to any skill.""" with tempfile.TemporaryDirectory() as dir_name: with cd(dir_name): agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder(registry_dir=Path(ROOT_DIR, "packages")) builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "stub") ) agent = builder.build() msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = str(DUMMY_SKILL_PUBLIC_ID) msg.sender = "some_author/some_skill:0.1.0" envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) with run_in_thread(agent.start, timeout=20, on_exit=agent.stop): wait_for_condition(lambda: agent.is_running, timeout=20) default_protocol_public_id = DefaultMessage.protocol_id handler = agent.resources.get_handler( default_protocol_public_id, DUMMY_SKILL_PUBLIC_ID ) assert handler is not None, "Handler is not set." # send an envelope to itself handler.context.send_to_skill(envelope) wait_for_condition( lambda: len(handler.handled_messages) == 1, timeout=5, error_msg="The message is not inside the handled_messages.", ) ================================================ FILE: tests/test_aea/test_aea_builder.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea/aea_builder.py.""" import os import re import sys from importlib import import_module from pathlib import Path from textwrap import dedent, indent from typing import Collection from unittest import mock from unittest.mock import MagicMock, Mock, patch import pytest import yaml from aea.aea import AEA from aea.aea_builder import AEABuilder, _DependenciesManager from aea.components.base import Component from aea.configurations.base import ( ComponentId, ComponentType, ConnectionConfig, DEFAULT_AEA_CONFIG_FILE, Dependency, ProtocolConfig, SkillConfig, ) from aea.configurations.constants import ( DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE, DOTTED_PATH_MODULE_ELEMENT_SEPARATOR, ) from aea.configurations.data_types import PublicId from aea.configurations.loader import load_component_configuration from aea.contracts.base import Contract from aea.exceptions import AEAEnforceError, AEAException, AEAWalletNoAddressException from aea.helpers.base import cd from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.helpers.install_dependency import call_pip from aea.protocols.base import Protocol from aea.registries.resources import Resources from aea.skills.base import Skill from aea.test_tools.test_cases import AEATestCase, AEATestCaseEmpty from packages.fetchai.connections.oef.connection import ( PUBLIC_ID as OEF_CONNECTION_PUBLIC_ID, ) from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.protocols.default import DefaultMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from tests.common.mocks import RegexComparator from tests.conftest import ( CUR_PATH, DEFAULT_PRIVATE_KEY_PATH, ROOT_DIR, _make_dummy_connection, ) from tests.data.dummy_skill import PUBLIC_ID as DUMMY_SKILL_PUBLIC_ID dummy_skill_path = os.path.join(CUR_PATH, "data", "dummy_skill") contract_path = os.path.join(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155") def test_default_timeout_for_agent(): """Tests agents loop sleep timeout set by AEABuilder.DEFAULT_AGENT_LOOP_TIMEOUT.""" agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) aea = builder.build() assert aea._period == builder.DEFAULT_AGENT_ACT_PERIOD builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.set_period(100) aea = builder.build() assert aea.period == 100 def test_add_package_already_existing(): """ Test the case when we try to add a package (already added) to the AEA builder. It should fail because the package is already present into the builder. """ builder = AEABuilder() fipa_package_path = Path(ROOT_DIR) / "packages" / "fetchai" / "protocols" / "fipa" builder.add_component(ComponentType.PROTOCOL, fipa_package_path) expected_message = re.escape( "Component 'fetchai/fipa:1.1.7' of type 'protocol' already added." ) with pytest.raises(AEAException, match=expected_message): builder.add_component(ComponentType.PROTOCOL, fipa_package_path) def test_when_package_has_missing_dependency(): """Test the case when the builder tries to load the packages, but fails because of a missing dependency.""" builder = AEABuilder() expected_message = re.escape( f"Package '{str(OEF_CONNECTION_PUBLIC_ID)}' of type 'connection' cannot be added. " f"Missing dependencies: ['(protocol, {str(OefSearchMessage.protocol_id)})']" ) with pytest.raises(AEAException, match=expected_message): # connection "fetchai/oef" requires # "fetchai/oef_search" and "fetchai/fipa" protocols. builder.add_component( ComponentType.CONNECTION, Path(ROOT_DIR) / "packages" / "fetchai" / "connections" / "oef", ) class TestReentrancy: """ Test the reentrancy of the AEABuilder class, when the components are loaded from directories. Namely, it means that multiple calls to the AEABuilder class should instantiate different AEAs in all their components. For example: builder = AEABuilder() ... # add components etc. aea1 = builder.build() aea2 = builder.build() Instances of components of aea1 are not shared with the aea2's ones. """ @classmethod def setup_class(cls): """Set up the test.""" protocol_path = os.path.join( ROOT_DIR, "packages", "fetchai", "protocols", "oef_search" ) connection_path = os.path.join( ROOT_DIR, "packages", "fetchai", "connections", "soef" ) builder = AEABuilder() builder.set_name("aea1") builder.add_private_key(DEFAULT_LEDGER) builder.add_protocol(protocol_path) builder.add_contract(contract_path) builder.add_connection(connection_path) builder.add_skill(dummy_skill_path) cls.aea1 = builder.build() builder.set_name("aea2") cls.aea2 = builder.build() @staticmethod def are_components_different( components_a: Collection[Component], components_b: Collection[Component], is_including_config: bool = True, ) -> None: """ Compare collections of component instances. It only makes sense if they have the same number of elements and the same component ids. """ assert len(components_a) == len( components_b ), "Cannot compare, number of components is different." assert {c.component_id for c in components_a} == { c.component_id for c in components_b }, "Cannot compare, component ids are different." d1 = {c.component_id: c for c in components_a} d2 = {c.component_id: c for c in components_b} assert all(d1[k] is not d2[k] for k in d1.keys()) if is_including_config: c1 = {c.component_id: c.configuration for c in components_a} c2 = {c.component_id: c.configuration for c in components_b} assert all(c1[k] is not c2[k] for k in c1.keys()) def test_skills_instances_are_different(self): """Test that skill instances are different.""" aea1_skills = self.aea1.resources.get_all_skills() aea2_skills = self.aea2.resources.get_all_skills() self.are_components_different(aea1_skills, aea2_skills) def test_protocols_instances_are_different(self): """Test that protocols instances are different.""" aea1_protocols = self.aea1.resources.get_all_protocols() aea2_protocols = self.aea2.resources.get_all_protocols() self.are_components_different(aea1_protocols, aea2_protocols) def test_contracts_instances_are_different(self): """Test that contract instances are different.""" aea1_contracts = self.aea1.resources.get_all_contracts() aea2_contracts = self.aea2.resources.get_all_contracts() self.are_components_different( aea1_contracts, aea2_contracts, is_including_config=False ) def test_connections_instances_are_different(self): """Test that connection instances are different.""" aea1_connections = self.aea1.runtime.multiplexer.connections aea2_connections = self.aea2.runtime.multiplexer.connections self.are_components_different(aea1_connections, aea2_connections) def test_multiple_builds_with_private_keys(): """Test multiple calls to the 'build()' method when adding custom private keys.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key(DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_PATH) # the first call works aea_1 = builder.build() assert isinstance(aea_1, AEA) # the second call fails with pytest.raises(ValueError, match="Cannot build.*"): builder.build() # after reset, it works builder.reset() builder.set_name("aea_1") builder.add_private_key(DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_PATH) aea_2 = builder.build() assert isinstance(aea_2, AEA) def test_multiple_builds_with_component_instance(): """Test multiple calls to the 'build()' method when adding component instances.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key(DEFAULT_LEDGER) a_protocol = Protocol( ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0", ), DefaultMessage, ) builder.add_component_instance(a_protocol) # the first call works aea_1 = builder.build() assert isinstance(aea_1, AEA) # the second call fails with pytest.raises(ValueError, match="Cannot build.*"): builder.build() # after reset, it works builder.reset() builder.set_name("aea_1") builder.add_private_key(DEFAULT_LEDGER) builder.add_component_instance(a_protocol) aea_2 = builder.build() assert isinstance(aea_2, AEA) def test_dependency_manager_highest_version(): """Test dependency version priority.""" dep_manager = _DependenciesManager() dep_manager.add_component( ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0", ) ) dep_manager.add_component( ProtocolConfig( "a_protocol", "author", "0.2.0", protocol_specification_id="some/author:0.1.0", ) ) assert len(dep_manager.dependencies_highest_version) == 1 assert list(dep_manager.dependencies_highest_version)[0].version == "0.2.0" assert len(dep_manager.protocols) == 2 assert len(dep_manager.skills) == 0 assert len(dep_manager.contracts) == 0 def test_remove_component_not_exists(): """Test component remove not exists.""" dep_manager = _DependenciesManager() with pytest.raises(ValueError, match=r"Component .* of type .* not present."): dep_manager.remove_component( ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0", ).component_id ) def test_remove_component_depends_on_fail(): """Test component remove fails cause dependency.""" dep_manager = _DependenciesManager() protocol = ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) dep_manager.add_component(protocol) dep_manager.add_component( SkillConfig("skill", "author", "0.1.0", protocols=[protocol.public_id]) ) with pytest.raises( ValueError, match=r"Cannot remove component .* of type .*. Other components depends on it: .*", ): dep_manager.remove_component(protocol.component_id) def test_remove_component_success(): """Test remove registered component.""" dep_manager = _DependenciesManager() protocol = ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) skill = SkillConfig("skill", "author", "0.1.0", protocols=[protocol.public_id]) dep_manager.add_component(protocol) dep_manager.add_component(skill) dep_manager.remove_component(skill.component_id) def test_private_keys(): """Test add/remove private keys.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") builder.add_private_key("fetchai", is_connection=True) assert builder._connection_private_key_paths assert builder._private_key_paths builder.remove_private_key("fetchai") builder.remove_private_key("fetchai", is_connection=True) assert not builder._connection_private_key_paths assert not builder._private_key_paths def test_can_remove_not_exists_component(): """Test fail on remove component not registered.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") protocol = ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) with pytest.raises(ValueError): builder._check_can_remove(protocol.component_id) def test_remove_protocol(): """Test add/remove protocol.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") a_protocol = Protocol( ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0", ), DefaultMessage, ) num_deps = len(builder._package_dependency_manager.all_dependencies) builder.add_component_instance(a_protocol) assert len(builder._package_dependency_manager.all_dependencies) == num_deps + 1 builder.remove_protocol(a_protocol.public_id) assert len(builder._package_dependency_manager.all_dependencies) == num_deps def test_remove_connection(): """Test add/remove connection.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") num_deps = len(builder._package_dependency_manager.all_dependencies) conn = _make_dummy_connection() builder.add_component_instance(conn) assert len(builder._package_dependency_manager.all_dependencies) == num_deps + 1 builder.remove_connection(conn.public_id) assert len(builder._package_dependency_manager.all_dependencies) == num_deps def test_remove_skill(): """Test add/remove skill.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") skill = Skill.from_dir(dummy_skill_path, Mock(agent_name="name")) num_deps = len(builder._package_dependency_manager.all_dependencies) builder.add_component_instance(skill) assert len(builder._package_dependency_manager.all_dependencies) == num_deps + 1 builder.remove_skill(skill.public_id) assert len(builder._package_dependency_manager.all_dependencies) == num_deps def test_remove_contract(): """Test add/remove contract.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") contract = Contract.from_dir(contract_path) num_deps = len(builder._package_dependency_manager.all_dependencies) builder.add_component_instance(contract) assert len(builder._package_dependency_manager.all_dependencies) == num_deps + 1 builder.remove_contract(contract.public_id) assert len(builder._package_dependency_manager.all_dependencies) == num_deps def test_process_connection_ids_not_specified(): """Test process connection fails on no connection specified.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") with pytest.raises( ValueError, match=r"Connection ids .* not declared in the configuration file." ): builder._process_connection_ids( [ConnectionConfig("conn", "author", "0.1.0").public_id] ) def test_process_connection_ids_bad_default_connection(): """Test fail on incorrect default connections.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") connection = _make_dummy_connection() builder.add_component_instance(connection) with pytest.raises( ValueError, match=r"Default connection not a dependency. Please add it and retry.", ): builder._default_connection = ConnectionConfig( "conn", "author", "0.1.0" ).public_id builder._process_connection_ids([connection.public_id]) def test_component_add_bad_dep(): """Test component load failed cause dependency.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") connection = _make_dummy_connection() connection.configuration.pypi_dependencies = { "something": Dependency("something", "==0.1.0") } builder.add_component_instance(connection) a_protocol = Protocol( ProtocolConfig( "a_protocol", "author", "0.1.0", protocol_specification_id="some/author:0.1.0", ), DefaultMessage, ) a_protocol.configuration.pypi_dependencies = { "something": Dependency("something", "==0.2.0") } with pytest.raises( AEAException, match=r"Conflict on package something: specifier set .*" ): builder.add_component_instance(a_protocol) def test_set_from_config_default(): """Test set configuration from config loaded.""" builder = AEABuilder() agent_configuration = Mock() agent_configuration.default_ledger = "fetchai" agent_configuration.required_ledgers = ["fetchai"] agent_configuration.default_connection = "test/test:0.1.0" agent_configuration.default_routing = {} agent_configuration.decision_maker_handler = {} agent_configuration.error_handler = {} agent_configuration.skill_exception_policy = ExceptionPolicyEnum.just_log agent_configuration.connection_exception_policy = ExceptionPolicyEnum.just_log agent_configuration._default_connection = None agent_configuration.connection_private_key_paths_dict = {"fetchai": None} agent_configuration.ledger_apis_dict = {"fetchai": None} agent_configuration.private_key_paths_dict = {"fetchai": None} agent_configuration.protocols = ( agent_configuration.connections ) = agent_configuration.contracts = agent_configuration.skills = [] with patch.object(builder, "set_default_connection"): builder.set_from_configuration(agent_configuration, aea_project_path="/anydir") assert builder._decision_maker_handler_class is None assert builder._decision_maker_handler_dotted_path is None assert builder._decision_maker_handler_file_path is None assert builder._load_decision_maker_handler_class() is None def test_set_from_config_custom(): """Test set configuration from config loaded.""" dm_dotted_path = f"aea.decision_maker.default{DOTTED_PATH_MODULE_ELEMENT_SEPARATOR}DecisionMakerHandler" dm_file_path = ROOT_DIR + "/aea/decision_maker/default.py" builder = AEABuilder() agent_configuration = Mock() agent_configuration.default_ledger = "fetchai" agent_configuration.required_ledgers = ["fetchai"] agent_configuration.default_connection = "test/test:0.1.0" agent_configuration.default_routing = {} agent_configuration.decision_maker_handler = { "dotted_path": dm_dotted_path, "file_path": dm_file_path, "config": {}, } error_handler_dotted_path = ( f"aea.error_handler.default{DOTTED_PATH_MODULE_ELEMENT_SEPARATOR}ErrorHandler" ) error_handler_file_path = ROOT_DIR + "/aea/error_handler/default.py" agent_configuration.error_handler = { "dotted_path": error_handler_dotted_path, "file_path": error_handler_file_path, "config": {}, } agent_configuration.skill_exception_policy = ExceptionPolicyEnum.just_log agent_configuration.connection_exception_policy = ExceptionPolicyEnum.just_log agent_configuration._default_connection = None agent_configuration.connection_private_key_paths_dict = {"fetchai": None} agent_configuration.ledger_apis_dict = {"fetchai": None} agent_configuration.private_key_paths_dict = {"fetchai": None} agent_configuration.protocols = ( agent_configuration.connections ) = agent_configuration.contracts = agent_configuration.skills = [] with patch.object(builder, "set_default_connection"): builder.set_from_configuration(agent_configuration, aea_project_path="/anydir") assert builder._decision_maker_handler_class is None assert builder._decision_maker_handler_dotted_path == dm_dotted_path assert builder._decision_maker_handler_file_path == dm_file_path assert builder._load_decision_maker_handler_class() is not None assert builder._load_error_handler_class() is not None builder.reset(is_full_reset=True) agent_configuration.decision_maker_handler = { "dotted_path": dm_dotted_path, "file_path": None, "config": {}, } agent_configuration.error_handler = { "dotted_path": error_handler_dotted_path, "file_path": None, "config": {}, } builder.set_from_configuration(agent_configuration, aea_project_path="/anydir") assert builder._load_decision_maker_handler_class() is not None assert builder._load_error_handler_class() is not None def test_load_abstract_component(): """Test abstract component loading.""" resources = Resources() builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") builder.add_component(ComponentType.SKILL, dummy_skill_path) with mock.patch("aea.aea_builder.load_aea_package"), mock.patch.object( builder, "_overwrite_custom_configuration", return_value=Mock(is_abstract_component=True), ), mock.patch.object(builder.logger, "debug") as mock_logger: builder._load_and_add_components( ComponentType.SKILL, resources, "aea_1", agent_context=Mock(agent_name="name"), ) mock_logger.assert_called_with( f"Package {DUMMY_SKILL_PUBLIC_ID} of type skill is abstract, " f"therefore only the Python modules have been loaded." ) assert ( len(resources.get_all_skills()) == 0 ), "expected 0 skills because the loaded skill is abstract" def test_find_import_order(): """Test find import order works on cycle dependency.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") _old_load = load_component_configuration def _new_load(*args, **kwargs): skill_config = _old_load(*args, **kwargs) # add loop skill_config.skills = [skill_config.public_id] return skill_config with patch("aea.aea_builder.load_component_configuration", _new_load): with pytest.raises( AEAException, match=r"Cannot load skills, there is a cyclic dependency." ): builder._find_import_order( [ComponentId(ComponentType.SKILL, DUMMY_SKILL_PUBLIC_ID)], Path(os.path.join(CUR_PATH, "data", "dummy_aea")), True, ) def test__build_identity_from_wallet(): """Test AEABuilder._build_identity_from_wallet.""" builder = AEABuilder() builder.set_name("aea_1") builder.add_private_key("fetchai") wallet = Mock() wallet.addresses = {} wallet.public_keys = {} with pytest.raises(AEAWalletNoAddressException): builder._build_identity_from_wallet(wallet) wallet.addresses = {builder.get_default_ledger(): "addr1"} wallet.public_keys = {builder.get_default_ledger(): "pk1"} builder._build_identity_from_wallet(wallet) wallet.addresses = {builder.get_default_ledger(): "addr1", "some_ledger": "addr2"} wallet.public_keys = {builder.get_default_ledger(): "pk11", "some_ledger": "pk2"} builder._build_identity_from_wallet(wallet) class TestFromAEAProject(AEATestCaseEmpty): """Test builder set from project dir.""" def test_from_project(self): """Test builder set from project dir.""" self.generate_private_key() self.add_private_key() builder = AEABuilder.from_aea_project(Path(self._get_cwd())) with cd(self._get_cwd()): aea = builder.build() assert aea.name == self.agent_name class TestFromAEAProjectWithCustomConnectionConfig(AEATestCaseEmpty): """Test builder set from project dir with custom connection config.""" def _add_stub_connection_config(self): """Add custom stub connection config.""" self.generate_private_key() self.add_private_key() cwd = self._get_cwd() aea_config_file = Path(cwd, DEFAULT_AEA_CONFIG_FILE) configuration = aea_config_file.read_text() connection_name = StubConnection.connection_id.name connection_version = StubConnection.connection_id.version configuration += dedent( f""" --- public_id: fetchai/{connection_name}:{connection_version} type: connection config: input_file: "{self.expected_input_file}" output_file: "{self.expected_output_file}" ... """ ) aea_config_file.write_text(configuration) def test_from_project(self): """Test builder set from project dir.""" self.add_item("connection", "fetchai/stub:0.21.3") self.expected_input_file = "custom_input_file" self.expected_output_file = "custom_output_file" self._add_stub_connection_config() builder = AEABuilder.from_aea_project(Path(self._get_cwd())) with cd(self._get_cwd()): aea = builder.build() assert aea.name == self.agent_name stub_connection_id = StubConnection.connection_id stub_connection = aea.resources.get_connection(stub_connection_id) assert stub_connection.configuration.config == dict( input_file=self.expected_input_file, output_file=self.expected_output_file ) class TestFromAEAProjectWithCustomSkillConfig(AEATestCase): """Test builder set from project dir with custom skill config.""" path_to_aea = Path(CUR_PATH) / "data" / "dummy_aea" def _add_dummy_skill_config(self): """Add custom stub connection config.""" cwd = self._get_cwd() aea_config_file = Path(cwd, DEFAULT_AEA_CONFIG_FILE) configuration = aea_config_file.read_text() # here we change all the dummy skill configurations configuration += dedent( f""" --- public_id: dummy_author/dummy:0.1.0 type: skill behaviours: dummy: args: {indent(yaml.dump(self.new_behaviour_args), " ")} handlers: dummy: args: {indent(yaml.dump(self.new_handler_args), " ")} models: dummy: args: {indent(yaml.dump(self.new_model_args), " ")} ... """ ) aea_config_file.write_text(configuration) def test_from_project(self): """Test builder set from project dir.""" self.new_behaviour_args = {"behaviour_arg_1": 42} self.new_handler_args = {"handler_arg_1": 42} self.new_model_args = {"model_arg_1": 42} self._add_dummy_skill_config() self.run_cli_command("issue-certificates", cwd=self._get_cwd()) builder = AEABuilder.from_aea_project(Path(self._get_cwd())) with cd(self._get_cwd()): builder.call_all_build_entrypoints() aea = builder.build() dummy_skill = aea.resources.get_skill(DUMMY_SKILL_PUBLIC_ID) dummy_behaviour = dummy_skill.behaviours["dummy"] assert dummy_behaviour.config == {"behaviour_arg_1": 42, "behaviour_arg_2": "2"} dummy_handler = dummy_skill.handlers["dummy"] assert dummy_handler.config == {"handler_arg_1": 42, "handler_arg_2": "2"} dummy_model = dummy_skill.models["dummy"] assert dummy_model.config == {"model_arg_1": 42, "model_arg_2": "2"} class TestFromAEAProjectMakeSkillAbstract(AEATestCase): """Test builder set from project dir, to make a skill 'abstract'.""" path_to_aea = Path(CUR_PATH) / "data" / "dummy_aea" def _add_dummy_skill_config(self): """Add custom stub connection config.""" cwd = self._get_cwd() aea_config_file = Path(cwd, DEFAULT_AEA_CONFIG_FILE) configuration = aea_config_file.read_text() # here we change all the dummy skill configurations configuration += dedent( """ --- public_id: dummy_author/dummy:0.1.0 type: skill is_abstract: true ... """ ) aea_config_file.write_text(configuration) def test_from_project(self): """Test builder set from project dir.""" self._add_dummy_skill_config() self.run_cli_command("issue-certificates", cwd=self._get_cwd()) builder = AEABuilder.from_aea_project(Path(self._get_cwd())) with cd(self._get_cwd()): builder.call_all_build_entrypoints() aea = builder.build() dummy_skill = aea.resources.get_skill(DUMMY_SKILL_PUBLIC_ID) assert dummy_skill is None, "Shouldn't have found the skill in Resources." class TestFromAEAProjectCustomConfigFailsWhenComponentNotDeclared(AEATestCaseEmpty): """Test builder set from project dir with custom component config fails when the component is not declared in the agent configuration.""" def _add_stub_connection_config(self): """Add custom stub connection config.""" cwd = self._get_cwd() aea_config_file = Path(cwd, DEFAULT_AEA_CONFIG_FILE) configuration = aea_config_file.read_text() configuration += dedent( """ --- public_id: some_author/non_existing_package:0.1.0 type: protocol ... """ ) aea_config_file.write_text(configuration) def test_from_project(self): """Test builder set from project dir.""" self.expected_input_file = "custom_input_file" self.expected_output_file = "custom_output_file" self._add_stub_connection_config() with pytest.raises( AEAEnforceError, match=r"Component \(protocol, some_author/non_existing_package:0.1.0\) not declared in the agent configuration.", ): with cd(self._get_cwd()): AEABuilder.from_aea_project(Path(self._get_cwd())) class TestExtraDeps(AEATestCaseEmpty): """Test builder set from project dir.""" def test_check_dependencies_correct(self): """Test dependencies properly listed.""" self.run_cli_command( "add", "--local", "connection", "fetchai/http_client", cwd=self._get_cwd() ) builder = AEABuilder.from_aea_project(Path(self._get_cwd())) assert "aiohttp" in builder._package_dependency_manager.pypi_dependencies def test_install_dependency(self): """Test dependencies installed.""" package_name = "async_generator" dependency = Dependency(package_name, "==1.10") sys.modules.pop(package_name, None) call_pip(["uninstall", package_name, "-y"]) try: import_module(package_name) raise Exception("should not be raised") except ModuleNotFoundError: pass builder = AEABuilder.from_aea_project(Path(self._get_cwd())) with patch( "aea.aea_builder._DependenciesManager.pypi_dependencies", {"package_name": dependency}, ): builder.install_pypi_dependencies() import_module(package_name) sys.modules.pop(package_name) call_pip(["uninstall", package_name, "-y"]) try: import_module(package_name) raise Exception("should not be raised") except ModuleNotFoundError: pass class TestBuildEntrypoint(AEATestCaseEmpty): """Test build entrypoint.""" def setup(self): """Set up the test.""" self.builder = AEABuilder.from_aea_project(Path(self._get_cwd())) self.component_id = "component_id" # add project-wide build entrypoint self.script_path = Path("script.py") self.builder._build_entrypoint = str(self.script_path) def test_build_positive_aea(self): """Test build project-wide entrypoint, positive.""" with cd(self._get_cwd()): self.script_path.write_text("") with patch.object(self.builder.logger, "info") as info_mock: self.builder.call_all_build_entrypoints() info_mock.assert_any_call("Building AEA package...") info_mock.assert_any_call(RegexComparator("Running command '.*script.py .*'")) def test_build_positive_package(self): """Test build package entrypoint, positive.""" with cd(self._get_cwd()): self.script_path.write_text("") # add mock configuration build entrypoint with patch.object(self.builder, "_package_dependency_manager") as _mock_mgr: mock_config = MagicMock( component_id=self.component_id, build_entrypoint=str(self.script_path), directory=".", build_directory="test", ) mock_values = MagicMock(return_value=[mock_config]) _mock_mgr._dependencies = MagicMock(values=mock_values) with patch.object(self.builder.logger, "info") as info_mock: self.builder.call_all_build_entrypoints() info_mock.assert_any_call(f"Building package {self.component_id}...") info_mock.assert_any_call(RegexComparator("Running command '.*script.py .*'")) def test_build_negative_syntax_error(self): """Test build, negative due to a syntax error in the script.""" match = r"The Python script at 'script.py' has a syntax error: invalid syntax \(, line 1\): syntax\+\.error\n" with cd(self._get_cwd()), pytest.raises(AEAException, match=match): self.script_path.write_text("syntax+.error") self.builder.call_all_build_entrypoints() @mock.patch( "aea.aea_builder.AEABuilder._run_in_subprocess", return_value=("", "some error.", 1), ) def test_build_negative_subprocess(self, *_mocks): """Test build, negative due to script error at runtime.""" match = "An error occurred while running command '.*script.py .+':\nsome error." with cd(self._get_cwd()), pytest.raises(AEAException, match=match): self.script_path.write_text("") self.builder.call_all_build_entrypoints() def test_set_default_connection_and_routing(): """Test checks on default connection and routing set.""" builder = AEABuilder() builder._package_dependency_manager = Mock() good_connection = ComponentId( "connection", PublicId.from_str("good/connection:0.1.0") ) bad_connection = ComponentId( "connection", PublicId.from_str("bad/connection:0.1.0") ) good_protocol = ComponentId("protocol", PublicId.from_str("good/protocol:0.1.0")) bad_protocol = ComponentId("protocol", PublicId.from_str("bad/protocol:0.1.0")) builder._package_dependency_manager.connections = [good_connection] builder._package_dependency_manager.protocols = [good_protocol] builder.set_default_connection(public_id=good_connection.public_id) with pytest.raises( ValueError, match="Connection bad/connection:0.1.0 specified as `default_connection` is not a project dependency!", ): builder.set_default_connection(public_id=bad_connection.public_id) builder.set_default_routing({good_protocol.public_id: good_connection.public_id}) with pytest.raises( ValueError, match="Connection bad/connection:0.1.0 specified in `default_routing` is not a project dependency!", ): builder.set_default_routing({good_protocol.public_id: bad_connection.public_id}) with pytest.raises( ValueError, match="Protocol bad/protocol:0.1.0 specified in `default_routing` is not a project dependency!", ): builder.set_default_routing({bad_protocol.public_id: good_connection.public_id}) def test_builder_pypi_dependencies(): """Test getter for PyPI dependencies.""" dummy_aea_path = Path(CUR_PATH, "data", "dummy_aea") builder = AEABuilder.from_aea_project(dummy_aea_path) dependencies = builder._package_dependency_manager.pypi_dependencies assert set(dependencies.keys()) == { "protobuf", "aea-ledger-fetchai", "aea-ledger-ethereum", "aea-ledger-cosmos", } ================================================ FILE: tests/test_aea/test_agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the agent module.""" import asyncio from threading import Thread from unittest.mock import Mock import pytest from aea.agent import Agent, Identity from aea.runtime import RuntimeStates from packages.fetchai.connections.local.connection import LocalNode from tests.common.utils import wait_for_condition from tests.conftest import _make_local_connection class DummyAgent(Agent): """A dummy agent for testing.""" def __init__(self, *args, **kwargs): """Initialize the agent.""" super().__init__(*args, **kwargs) def setup(self) -> None: """Set up the agent.""" pass def act(self) -> None: """Act.""" pass def react(self) -> None: """React to events.""" pass def update(self) -> None: """Update the state of the agent.""" pass def teardown(self) -> None: """Tear down the agent.""" pass @property def resources(self): """Get resources.""" return Mock() def test_run_agent(): """Test that we can set up and then run the agent.""" with LocalNode() as node: agent_name = "dummyagent" agent_address = "some_address" agent_public_key = "some_public_key" identity = Identity( agent_name, address=agent_address, public_key=agent_public_key ) oef_local_connection = _make_local_connection( agent_address, agent_public_key, node ) oef_local_connection._local_node = node agent = DummyAgent( identity, [oef_local_connection], loop=asyncio.new_event_loop() ) agent_thread = Thread(target=agent.start) assert agent.state == RuntimeStates.stopped agent_thread.start() try: wait_for_condition( lambda: agent.state == RuntimeStates.running, timeout=10, error_msg="Agent state must be 'running'", ) finally: agent.stop() assert agent.state == RuntimeStates.stopped agent_thread.join() def test_runtime_modes(): """Test runtime modes are set.""" agent_name = "dummyagent" agent_address = "some_address" agent_public_key = "some_public_key" identity = Identity(agent_name, address=agent_address, public_key=agent_public_key) agent = DummyAgent( identity, [], ) assert not agent.is_running assert agent.is_stopped agent._runtime_mode = "not exists" with pytest.raises(ValueError): agent._get_runtime_class() with pytest.raises(ValueError): agent.runtime._get_agent_loop_class("not exists") assert agent.runtime.loop_mode == agent.runtime.DEFAULT_RUN_LOOP ================================================ FILE: tests/test_aea/test_agent_loop.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests of the implementation of an agent loop using asyncio.""" import asyncio import datetime import logging from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type from unittest.mock import MagicMock, Mock, patch import pytest from aea.aea import AEA from aea.agent_loop import AgentLoopStates, AsyncAgentLoop, BaseAgentLoop, SyncAgentLoop from aea.exceptions import AEAActException from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.mail.base import Envelope, EnvelopeContext from aea.protocols.base import Message from aea.registries.filter import Filter from aea.registries.resources import Resources from aea.skills.base import Behaviour, Handler, SkillContext from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.default.message import DefaultMessage from tests.common.utils import wait_for_condition, wait_for_condition_async class CountHandler(Handler): """Simple handler to count how many message it gets.""" def setup(self) -> None: """Set up handler.""" self.counter = 0 def handle(self, message: Message) -> None: """Process incoming message.""" self.counter += 1 def teardown(self) -> None: """Clean up handler.""" pass @classmethod def make(cls) -> "CountHandler": """Construct handler.""" return cls(name="test", skill_context=SkillContext()) class CountBehaviour(TickerBehaviour): """Simple behaviour to count how many acts were called.""" def setup(self) -> None: """Set up behaviour.""" self.counter = 0 def act(self) -> None: """Make an action.""" self.counter += 1 @classmethod def make(cls, tick_interval: int = 1) -> "CountBehaviour": """Construct behaviour.""" return cls( name="test", skill_context=SkillContext(), tick_interval=tick_interval ) class FailBehaviour(TickerBehaviour): """Simple behaviour to raise an exception.""" def setup(self) -> None: """Set up behaviour.""" pass def act(self) -> None: """Make an action.""" raise ValueError("expected!") @classmethod def make(cls, tick_interval: int = 1) -> "FailBehaviour": """Construct behaviour.""" return cls(name="test", skill_context=Mock(), tick_interval=tick_interval) class AsyncFakeAgent(AEA): """Fake agent form testing.""" name = "fake_agent" _skills_exception_policy = ExceptionPolicyEnum.just_log def __init__(self, handlers=None, behaviours=None): """Init agent.""" self.handlers = handlers or [] self.behaviours = behaviours or [] self._runtime = MagicMock() self.runtime.decision_maker.message_out_queue = AsyncFriendlyQueue() self._inbox = AsyncFriendlyQueue() self._runtime.agent_loop.skill2skill_queue = asyncio.Queue() self._filter = Filter( Resources(), self.runtime.decision_maker.message_out_queue ) self._logger = logging.getLogger("fake agent") self._period = 0.001 self.filter.handle_internal_message = MagicMock() self.filter.handle_new_handlers_and_behaviours = MagicMock() def _get_behaviours_tasks( self, ) -> Dict[Callable, Tuple[float, Optional[datetime.datetime]]]: tasks = {} for behaviour in self.active_behaviours: tasks[behaviour.act_wrapper] = (behaviour.tick_interval, behaviour.start_at) return tasks @property def active_behaviours(self) -> List[Behaviour]: """Return all behaviours.""" return self.behaviours def _handle(self, envelope: Envelope) -> None: """ Handle an envelope. :param envelope: the envelope to handle. :return: None """ for handler in self.handlers: handler.handle(envelope) def put_inbox(self, msg: Any) -> None: """Add a message to inbox.""" self._inbox.put_nowait(msg) # type: ignore def put_internal_message(self, msg: Any) -> None: """Add a message to internal queue.""" self.runtime.decision_maker.message_out_queue.put_nowait(msg) def _get_msg_and_handlers_for_envelope( self, envelope: Envelope ) -> Tuple[Optional[Message], List[Handler]]: return envelope, self.handlers # type: ignore def _execution_control( self, fn: Callable, args: Optional[Sequence] = None, kwargs: Optional[Dict] = None, ) -> Any: """ Execute skill function in exception handling environment. Logs error, stop agent or propagate excepion depends on policy defined. :param fn: function to call :param component: skill component function belongs to :param args: optional sequence of arguments to pass to function on call :param kwargs: optional dict of keyword arguments to pass to function on call :return: same as function """ return fn(*(args or []), **(kwargs or {})) def _handle_envelope(self, envelope: Envelope) -> None: for handler in self.handlers: handler.handle(envelope) class SyncFakeAgent(AsyncFakeAgent): """Fake agent for sync loop.""" def put_inbox(self, msg: Any) -> None: """Add a message to inbox.""" self._inbox.put_nowait(msg) # type: ignore def put_internal_message(self, msg: Any) -> None: """Add a message to internal queue.""" self.runtime.decision_maker.message_out_queue.put_nowait(msg) class TestAsyncAgentLoop: """Tests for asynchronous loop.""" AGENT_LOOP_CLASS: Type[BaseAgentLoop] = AsyncAgentLoop FAKE_AGENT_CLASS = AsyncFakeAgent def test_loop_start_stop(self): """Test loop start and stopped properly.""" agent = self.FAKE_AGENT_CLASS() agent_loop = self.AGENT_LOOP_CLASS(agent, threaded=True) agent.runtime.agent_loop = agent_loop agent_loop.start() wait_for_condition(lambda: agent_loop.is_running, timeout=10) agent_loop.stop() agent_loop.wait_completed(sync=True) assert not agent_loop.is_running, agent_loop.state def test_set_loop(self): """Test set loop.""" agent_loop = self.AGENT_LOOP_CLASS(self.FAKE_AGENT_CLASS()) loop = asyncio.new_event_loop() agent_loop.set_loop(loop=loop) assert agent_loop._loop == loop @pytest.mark.asyncio async def test_state_property(self): """Test state property.""" agent_loop = self.AGENT_LOOP_CLASS(self.FAKE_AGENT_CLASS()) assert agent_loop.state == AgentLoopStates.initial await asyncio.wait_for( agent_loop.wait_state(AgentLoopStates.initial), timeout=2 ) def test_handle_envelope(self): """Test one envelope handling.""" handler = CountHandler.make() agent = self.FAKE_AGENT_CLASS(handlers=[handler]) agent_loop = self.AGENT_LOOP_CLASS(agent, threaded=True) agent.runtime.agent_loop = agent_loop handler.setup() agent_loop.start() wait_for_condition(lambda: agent_loop.is_running, timeout=10) agent.put_inbox("msg") wait_for_condition(lambda: handler.counter == 1, timeout=2) agent_loop.stop() agent_loop.wait_completed(sync=True) def test_behaviour_act(self): """Test behaviour act called by schedule.""" tick_interval = 0.1 behaviour = CountBehaviour.make(tick_interval=tick_interval) behaviour.setup() agent = self.FAKE_AGENT_CLASS(behaviours=[behaviour]) agent_loop = self.AGENT_LOOP_CLASS(agent, threaded=True) agent.runtime.agent_loop = agent_loop agent_loop.start() wait_for_condition(lambda: agent_loop.is_running, timeout=10) wait_for_condition(lambda: behaviour.counter >= 1, timeout=tick_interval * 2) agent_loop.stop() agent_loop.wait_completed(sync=True) @pytest.mark.asyncio async def test_internal_messages(self): """Test internal meesages are processed.""" agent = self.FAKE_AGENT_CLASS() agent_loop = self.AGENT_LOOP_CLASS(agent) agent.runtime.agent_loop = agent_loop agent_loop.start() await asyncio.wait_for( agent_loop.wait_state(AgentLoopStates.started), timeout=10 ) agent.put_internal_message("msg") await wait_for_condition_async( lambda: agent.filter.handle_internal_message.called is True, timeout=5, ) agent_loop.stop() await agent_loop.wait_completed() def test_new_behaviours(self): """Test new behaviours are added.""" agent = self.FAKE_AGENT_CLASS() agent_loop = self.AGENT_LOOP_CLASS(agent, threaded=True) agent_loop.NEW_BEHAVIOURS_PROCESS_SLEEP = 0.5 agent.runtime.agent_loop = agent_loop agent_loop.start() wait_for_condition(lambda: agent_loop.is_running, timeout=10) wait_for_condition( lambda: agent.filter.handle_new_handlers_and_behaviours.call_count >= 2, timeout=agent_loop.NEW_BEHAVIOURS_PROCESS_SLEEP * 3, ) agent_loop.stop() agent_loop.wait_completed(sync=True) @pytest.mark.asyncio async def test_behaviour_exception(self): """Test behaviour exception reraised properly.""" tick_interval = 0.1 behaviour = FailBehaviour.make(tick_interval) agent = self.FAKE_AGENT_CLASS(behaviours=[behaviour]) agent_loop = self.AGENT_LOOP_CLASS(agent, threaded=True) agent.runtime.agent_loop = agent_loop agent._skills_exception_policy = ExceptionPolicyEnum.propagate with pytest.raises(AEAActException): with pytest.raises(ValueError, match="expected!"): agent_loop.start() await agent_loop.wait_completed() agent_loop.stop() agent_loop.wait_completed(sync=True) @pytest.mark.asyncio async def test_stop(self): """Test loop stoped.""" agent = self.FAKE_AGENT_CLASS() agent_loop = self.AGENT_LOOP_CLASS(agent) agent_loop.start() await asyncio.wait_for( agent_loop._state.wait(AgentLoopStates.started), timeout=10 ) agent_loop.stop() await asyncio.wait_for( agent_loop._state.wait(AgentLoopStates.stopped), timeout=10 ) @pytest.mark.asyncio async def test_send_to_skill(self): """Test loop stoped.""" agent = self.FAKE_AGENT_CLASS() agent_loop = self.AGENT_LOOP_CLASS(agent) agent_loop.start() await asyncio.wait_for( agent_loop._state.wait(AgentLoopStates.started), timeout=10 ) msg = DefaultMessage(performative=DefaultMessage.Performative.BYTES) msg.to = "to" msg.sender = "sender" envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id="some_con"), ) try: with pytest.raises( ValueError, match="Unsupported message or envelope type:" ): agent_loop.send_to_skill("something") with patch.object(agent_loop, "_skill2skill_message_queue"): agent_loop.send_to_skill(msg) agent_loop.send_to_skill(envelope) finally: agent_loop.stop() await asyncio.wait_for( agent_loop._state.wait(AgentLoopStates.stopped), timeout=10 ) class TestSyncAgentLoop: """Tests for synchronous loop.""" AGENT_LOOP_CLASS: Type[BaseAgentLoop] = SyncAgentLoop FAKE_AGENT_CLASS = SyncFakeAgent ================================================ FILE: tests/test_aea/test_cli/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the cli tool commands.""" ================================================ FILE: tests/test_aea/test_cli/constants.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Constants used for CLI testing.""" from aea.configurations.base import DEFAULT_VERSION FORMAT_ITEMS_SAMPLE_OUTPUT = "Correct items" DEFAULT_TESTING_VERSION = DEFAULT_VERSION ================================================ FILE: tests/test_aea/test_cli/test_add/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add` sub-command.""" ================================================ FILE: tests/test_aea/test_cli/test_add/test_connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add connection` sub-command.""" import os import shutil import tempfile import unittest.mock from pathlib import Path from unittest.mock import patch import pytest import yaml from jsonschema import ValidationError import aea.configurations.base from aea.cli import cli from aea.configurations.base import DEFAULT_CONNECTION_CONFIG_FILE, PublicId from aea.test_tools.test_cases import AEATestCaseEmpty, AEATestCaseEmptyFlaky from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_PUBLIC_ID, ) from packages.fetchai.connections.local.connection import ( PUBLIC_ID as LOCAL_CONNECTION_PUBLIC_ID, ) from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner, MAX_FLAKY_RERUNS, double_escape_windows_path_separator, ) class TestAddConnectionFailsWhenConnectionAlreadyExists: """Test that the command 'aea add connection' fails when the connection already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_name = "http_client" cls.connection_author = "fetchai" cls.connection_version = "0.3.0" cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # add connection first time result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) assert result.exit_code == 0 # add connection again cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) @unittest.mock.patch("aea.cli.add.get_package_path", return_value="dest/path") @unittest.mock.patch("aea.cli.add.fetch_package") def test_add_connection_from_registry_positive(self, fetch_package_mock, *mocks): """Test add from registry positive result.""" fetch_package_mock.return_value = Path( "vendor/{}/connections/{}".format( self.connection_author, self.connection_name ) ) public_id = "{}/{}:{}".format( AUTHOR, self.connection_name, self.connection_version ) obj_type = "connection" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add", obj_type, public_id], standalone_mode=False, ) assert result.exit_code == 0 public_id_obj = PublicId.from_str(public_id) fetch_package_mock.assert_called_once_with( obj_type, public_id=public_id_obj, cwd=".", dest="dest/path" ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_connection_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A connection with id '{connection_id}' already exists. Aborting...' """ s = f"A connection with id '{self.connection_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddConnectionFailsWhenConnectionWithSameAuthorAndNameButDifferentVersion: """Test that 'aea add connection' fails when the connection with different version already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_name = "http_client" cls.connection_author = "fetchai" cls.connection_version = "0.3.0" cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # add connection first time result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) assert result.exit_code == 0 # add connection again, but with different version number # first, change version number to package different_version = "0.1.1" different_id = ( cls.connection_author + "/" + cls.connection_name + ":" + different_version ) config_path = Path( cls.t, "packages", cls.connection_author, "connections", cls.connection_name, DEFAULT_CONNECTION_CONFIG_FILE, ) config = yaml.safe_load(config_path.open()) config["version"] = different_version yaml.safe_dump(config, config_path.open(mode="w")) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", different_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_connection_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A connection with id '{connection_id}' already exists. Aborting...' """ s = f"A connection with id '{self.connection_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddConnectionFailsWhenConnectionNotInRegistry: """Test that the command 'aea add connection' fails when the connection is not in the registry.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_id = "author/unknown_connection:0.1.0" cls.connection_name = "unknown_connection" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_connection_already_existing(self): """Test that the log error message is fixed. The expected message is: 'Cannot find connection: '{connection_name}'' """ s = "Cannot find connection: '{}'.".format(self.connection_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddConnectionFailsWhenDifferentPublicId: """Test that the command 'aea add connection' fails when the connection has not the same public id.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_id = "different_author/local:0.1.0" cls.connection_name = "unknown_connection" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_connection_wrong_public_id(self): """Test that the log error message is fixed.""" s = "Cannot find connection: '{}'.".format(self.connection_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddConnectionFailsWhenConfigFileIsNotCompliant: """Test that the command 'aea add connection' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) cls.connection_name = "http_client" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 # change the serialization of the AgentConfig class so to make the parsing to fail. cls.patch = unittest.mock.patch.object( aea.configurations.base.ConnectionConfig, "from_json", side_effect=ValidationError("test error message"), ) cls.patch.start() os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_configuration_file_not_valid(self): """Test that the log error message is fixed. The expected message is: 'Connection configuration file not valid: '{connection_name}'' """ s = "Connection configuration file not valid: test error message" assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddConnectionFailsWhenDirectoryAlreadyExists: """Test that the command 'aea add connection' fails when the destination directory already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) cls.connection_name = "http_client" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) Path( cls.t, cls.agent_name, "vendor", "fetchai", "connections", cls.connection_name, ).mkdir(parents=True, exist_ok=True) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_file_exists_error(self): """Test that the log error message is fixed. The expected message is: 'Cannot find connection: '{connection_name}'' """ missing_path = os.path.join( "vendor", "fetchai", "connections", self.connection_name ) missing_path = double_escape_windows_path_separator(missing_path) assert missing_path in self.result.exception.message @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddConnectionFromRemoteRegistry(AEATestCaseEmptyFlaky): """Test case for add connection from Registry command.""" IS_LOCAL = False IS_EMPTY = True @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_connection_from_remote_registry_positive(self): """Test add connection from Registry positive result.""" self.add_item( "connection", str(LOCAL_CONNECTION_PUBLIC_ID.to_latest()), local=self.IS_LOCAL, ) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "connections") items_folders = os.listdir(items_path) item_name = "local" assert item_name in items_folders class TestAddConnectionWithLatestVersion(AEATestCaseEmpty): """Test case for add connection with latest version.""" def test_add_connection_latest_version(self): """Test add connection with latest version.""" self.add_item( "connection", str(LOCAL_CONNECTION_PUBLIC_ID.to_latest()), local=True ) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "connections") items_folders = os.listdir(items_path) item_name = "local" assert item_name in items_folders class TestAddConnectionMixedWhenNoLocalRegistryExists: """Test that the command 'aea add connection' works in mixed mode when the local registry does not exists (it swaps to remote).""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) cls.connection_name = "http_client" os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) with patch("aea.cli.registry.utils.request_api"), patch( "aea.cli.add.fetch_package" ), patch("aea.cli.add.load_item_config"), patch( "aea.cli.add.is_fingerprint_correct" ), patch( "aea.cli.add.register_item" ): cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "connection", cls.connection_id], standalone_mode=False, catch_exceptions=False, ) def test_exit_code_equal_to_0(self): """Test that the exit code is equal to 0.""" assert self.result.exit_code == 0 def test_standard_output_mentions_swap_to_remote(self): """Test standard output contains information on swap to remote.""" assert "Trying remote registry (`--remote`)." in self.result.stdout @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddConnectionLocalWhenNoLocalRegistryExists: """Test that the command 'aea add connection' fails in local mode when the local registry does not exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) cls.connection_name = "http_client" os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) assert result.exit_code == 0, result.stdout result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0, result.stdout os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1.""" assert self.result.exit_code == 1, self.result.stdout def test_standard_output_mentions_failure(self): """Test standard output contains information on failure.""" assert ( "Registry path not provided and local registry `packages` not found in current (.) and parent directory." in self.result.exception.message ) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_add/test_contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add contract` sub-command.""" import os from unittest import TestCase, mock import pytest from aea.cli import cli from aea.test_tools.test_cases import AEATestCaseEmptyFlaky from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as ERC1155_PUBLIC_ID from tests.conftest import CLI_LOG_OPTION, CliRunner, MAX_FLAKY_RERUNS @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") @mock.patch("aea.cli.utils.decorators._validate_config_consistency") class AddContractCommandTestCase(TestCase): """Test that the command 'aea add contract' works as expected.""" def setUp(self): """Set the test up.""" self.runner = CliRunner() @mock.patch("aea.cli.add.add_item") def test_add_contract_positive(self, *mocks): """Test add contract command positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "contract", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "contract", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) class TestAddContractFromRemoteRegistry(AEATestCaseEmptyFlaky): """Test case for add contract from Registry command.""" IS_LOCAL = False IS_EMPTY = True @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_contract_from_remote_registry_positive(self): """Test add contract from Registry positive result.""" self.add_item( "contract", str(ERC1155_PUBLIC_ID.to_latest()), local=self.IS_LOCAL ) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "contracts") items_folders = os.listdir(items_path) item_name = "erc1155" assert item_name in items_folders class TestAddContractWithLatestVersion(AEATestCaseEmptyFlaky): """Test case for add contract with latest version.""" IS_LOCAL = True @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_contract_latest_version(self): """Test add contract with latest version.""" self.add_item( "contract", str(ERC1155_PUBLIC_ID.to_latest()), local=self.IS_LOCAL ) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "contracts") items_folders = os.listdir(items_path) item_name = "erc1155" assert item_name in items_folders ================================================ FILE: tests/test_aea/test_cli/test_add/test_generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for aea.cli.add generic methods.""" from unittest import TestCase, mock from aea.cli.add import _add_item_deps from tests.test_aea.test_cli.tools_for_testing import ContextMock class AddItemDepsTestCase(TestCase): """Test case for _add_item_deps method.""" @mock.patch("aea.cli.add.add_item") def test__add_item_deps_missing_skills_positive(self, add_item_mock): """Test _add_item_deps for positive result with missing skills.""" ctx = ContextMock(skills=[]) item_config = mock.Mock() item_config.protocols = [] item_config.contracts = [] item_config.connections = [] item_config.skills = ["skill-1", "skill-2"] _add_item_deps(ctx, "skill", item_config) ================================================ FILE: tests/test_aea/test_cli/test_add/test_protocol.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add protocol` sub-command.""" import os import re import shutil import tempfile import unittest.mock from pathlib import Path import pytest import yaml from jsonschema import ValidationError import aea.configurations.base from aea.cli import cli from aea.configurations.base import DEFAULT_PROTOCOL_CONFIG_FILE, PublicId from aea.test_tools.test_cases import AEATestCaseEmpty, AEATestCaseEmptyFlaky from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.gym.message import GymMessage from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner, MAX_FLAKY_RERUNS, double_escape_windows_path_separator, ) class TestAddProtocolFailsWhenProtocolAlreadyExists: """Test that the command 'aea add protocol' fails when the protocol already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.protocol_id = GymMessage.protocol_id cls.protocol_name = cls.protocol_id.name cls.protocol_author = cls.protocol_id.author cls.protocol_version = cls.protocol_id.version # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", str(cls.protocol_id)], standalone_mode=False, ) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", str(cls.protocol_id)], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A protocol with id '{protocol_id}' already exists. Aborting...' """ s = f"A protocol with id '{self.protocol_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddProtocolFailsWhenProtocolWithSameAuthorAndNameButDifferentVersion: """Test that the command 'aea add protocol' fails when the protocol already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.protocol_id = GymMessage.protocol_id cls.protocol_name = cls.protocol_id.name cls.protocol_author = cls.protocol_id.author cls.protocol_version = cls.protocol_id.version # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", str(cls.protocol_id)], standalone_mode=False, ) assert result.exit_code == 0 # add protocol again, but with different version number # first, change version number to package different_version = "0.1.1" different_id = ( cls.protocol_author + "/" + cls.protocol_name + ":" + different_version ) config_path = Path( cls.t, "packages", cls.protocol_author, "protocols", cls.protocol_name, DEFAULT_PROTOCOL_CONFIG_FILE, ) config = yaml.safe_load(config_path.open()) config["version"] = different_version yaml.safe_dump(config, config_path.open(mode="w")) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", different_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A protocol with id '{protocol_id}' already exists. Aborting...' """ s = f"A protocol with id '{self.protocol_id}' already exists. Aborting..." assert self.result.exception.message == s @unittest.mock.patch("aea.cli.add.get_package_path", return_value="dest/path") @unittest.mock.patch("aea.cli.add.fetch_package") def test_add_protocol_from_registry_positive(self, fetch_package_mock, *mocks): """Test add from registry positive result.""" fetch_package_mock.return_value = Path( "vendor/{}/protocols/{}".format(self.protocol_author, self.protocol_name) ) public_id = "{}/{}:{}".format(AUTHOR, self.protocol_name, self.protocol_version) obj_type = "protocol" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add", obj_type, public_id], standalone_mode=False, ) assert result.exit_code == 0 public_id_obj = PublicId.from_str(public_id) fetch_package_mock.assert_called_once_with( obj_type, public_id=public_id_obj, cwd=".", dest="dest/path" ) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddProtocolFailsWhenProtocolNotInRegistry: """Test that the command 'aea add protocol' fails when the protocol is not in the registry.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.protocol_id = "user/unknown_protocol:0.1.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", cls.protocol_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_already_existing(self): """Test that the log error message is fixed. The expected message is: 'Cannot find protocol: '{protocol_name}'' """ s = "Cannot find protocol: '{}'.".format(self.protocol_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddProtocolFailsWhenDifferentPublicId: """Test that the command 'aea add protocol' fails when the protocol has not the same public id.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.protocol_id = "different_author/default:1.0.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", cls.protocol_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_wrong_public_id(self): """Test that the log error message is fixed.""" s = "Cannot find protocol: '{}'.".format(self.protocol_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddProtocolFailsWhenConfigFileIsNotCompliant: """Test that the command 'aea add protocol' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.protocol_id = str(GymMessage.protocol_id) # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 # change the serialization of the ProtocolConfig class so to make the parsing to fail. cls.patch = unittest.mock.patch.object( aea.configurations.base.ProtocolConfig, "from_json", side_effect=ValidationError("test error message"), ) cls.patch.start() os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", cls.protocol_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_configuration_file_not_valid(self): """Test that the log error message is fixed. The expected message is: 'Protocol configuration file not valid: ...' """ assert re.match( "Protocol configuration file '.*' not valid: test error message", self.result.exception.message, ) @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddProtocolFailsWhenDirectoryAlreadyExists: """Test that the command 'aea add protocol' fails when the destination directory already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.protocol_id = str(GymMessage.protocol_id) cls.protocol_name = "gym" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) Path( cls.t, cls.agent_name, "vendor", "fetchai", "protocols", cls.protocol_name ).mkdir(parents=True, exist_ok=True) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", cls.protocol_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_file_exists_error(self): """Test that the log error message is fixed. The expected message is: 'Cannot find protocol: '{protocol_name}'' """ missing_path = os.path.join( "vendor", "fetchai", "protocols", self.protocol_name ) missing_path = double_escape_windows_path_separator(missing_path) assert missing_path in self.result.exception.message @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddProtocolFromRemoteRegistry(AEATestCaseEmptyFlaky): """Test case for add protocol from Registry command.""" IS_LOCAL = False IS_EMPTY = True @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_protocol_from_remote_registry_positive(self): """Test add protocol from Registry positive result.""" self.add_item( "protocol", str(FipaMessage.protocol_id.to_latest()), local=self.IS_LOCAL ) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "protocols") items_folders = os.listdir(items_path) item_name = "fipa" assert item_name in items_folders class TestAddProtocolWithLatestVersion(AEATestCaseEmpty): """Test case for add protocol with latest version.""" def test_add_protocol_latest_version(self): """Test add protocol with latest version.""" self.add_item("protocol", str(FipaMessage.protocol_id.to_latest()), local=True) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "protocols") items_folders = os.listdir(items_path) item_name = "fipa" assert item_name in items_folders ================================================ FILE: tests/test_aea/test_cli/test_add/test_skill.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add skill` sub-command.""" import os import shutil import tempfile from pathlib import Path from unittest import mock import click import pytest import yaml from jsonschema import ValidationError import aea from aea.cli import cli from aea.configurations.base import ( AgentConfig, DEFAULT_AEA_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, PublicId, ) from aea.test_tools.test_cases import AEATestCaseEmpty, AEATestCaseEmptyFlaky from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_PUBLIC_ID from packages.fetchai.skills.erc1155_client import PUBLIC_ID as ERC1155_CLIENT_PUBLIC_ID from packages.fetchai.skills.error import PUBLIC_ID as ERROR_PUBLIC_ID from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner, MAX_FLAKY_RERUNS, ROOT_DIR, double_escape_windows_path_separator, ) class TestAddSkillFailsWhenSkillAlreadyExists: """Test that the command 'aea add skill' fails when the skill already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.skill_id = ERROR_PUBLIC_ID cls.skill_name = cls.skill_id.name cls.skill_author = cls.skill_id.author cls.skill_version = cls.skill_id.version # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) # this also by default adds the stub connection assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", str(cls.skill_id)], standalone_mode=False, ) assert result.exit_code == 0 # add the error skill again cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", str(cls.skill_id)], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_skill_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A skill with id '{skill_id}' already exists. Aborting...' """ s = f"A skill with id '{self.skill_id}' already exists. Aborting..." assert self.result.exception.message == s @mock.patch("aea.cli.add.get_package_path", return_value="dest/path") @mock.patch("aea.cli.add.fetch_package") def test_add_skill_from_registry_positive(self, fetch_package_mock, *mocks): """Test add from registry positive result.""" fetch_package_mock.return_value = Path( "vendor/{}/skills/{}".format(self.skill_author, self.skill_name) ) public_id = "{}/{}:{}".format(AUTHOR, self.skill_name, self.skill_version) obj_type = "skill" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add", obj_type, public_id], standalone_mode=False, ) assert result.exit_code == 0 public_id_obj = PublicId.from_str(public_id) fetch_package_mock.assert_called_once_with( obj_type, public_id=public_id_obj, cwd=".", dest="dest/path" ) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddSkillFailsWhenSkillWithSameAuthorAndNameButDifferentVersion: """Test that the command 'aea add skill' fails when the skill already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.skill_id = ECHO_PUBLIC_ID cls.skill_name = cls.skill_id.name cls.skill_author = cls.skill_id.author cls.skill_version = cls.skill_id.version # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) # this also by default adds the stub connection assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", str(cls.skill_id)], standalone_mode=False, ) assert cls.result.exit_code == 0 # add skill again, but with different version number # first, change version number to package different_version = "0.1.1" different_id = cls.skill_author + "/" + cls.skill_name + ":" + different_version config_path = Path( cls.t, "packages", cls.skill_author, "skills", cls.skill_name, DEFAULT_SKILL_CONFIG_FILE, ) config = yaml.safe_load(config_path.open()) config["version"] = different_version yaml.safe_dump(config, config_path.open(mode="w")) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", different_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_skill_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A skill with id '{skill_id}' already exists. Aborting...' """ s = f"A skill with id '{self.skill_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddSkillFailsWhenSkillNotInRegistry: """Test that the command 'aea add skill' fails when the skill is not in the registry.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.skill_id = "author/unknown_skill:0.1.0" cls.skill_name = "unknown_skill" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", cls.skill_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_skill_already_existing(self): """Test that the log error message is fixed. The expected message is: 'Cannot find skill: '{skill_name}'' """ s = "Cannot find skill: '{}'.".format(self.skill_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddSkillFailsWhenDifferentPublicId: """Test that the command 'aea add skill' fails when the skill has not the same public id.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.skill_id = "different_author/error:0.1.0" cls.skill_name = "unknown_skill" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", cls.skill_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_skill_wrong_public_id(self): """Test that the log error message is fixed.""" s = "Cannot find skill: '{}'.".format(self.skill_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddSkillFailsWhenConfigFileIsNotCompliant: """Test that the command 'aea add skill' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.skill_id = str(ECHO_PUBLIC_ID) cls.skill_name = "echo" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # change default registry path config = AgentConfig.from_json(yaml.safe_load(open(DEFAULT_AEA_CONFIG_FILE))) config.registry_path = os.path.join(ROOT_DIR, "packages") yaml.safe_dump(dict(config.json), open(DEFAULT_AEA_CONFIG_FILE, "w")) # change the serialization of the AgentConfig class so to make the parsing to fail. cls.patch = mock.patch.object( aea.configurations.base.SkillConfig, "from_json", side_effect=ValidationError("test error message"), ) cls.patch.start() cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", cls.skill_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_configuration_file_not_valid(self): """Test that the log error message is fixed. The expected message is: 'Cannot find skill: '{skill_name}'' """ s = "Skill configuration file not valid: test error message" assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddSkillFailsWhenDirectoryAlreadyExists: """Test that the command 'aea add skill' fails when the destination directory already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.skill_id = str(ECHO_PUBLIC_ID) cls.skill_name = "echo" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # change default registry path config = AgentConfig.from_json(yaml.safe_load(open(DEFAULT_AEA_CONFIG_FILE))) config.registry_path = os.path.join(ROOT_DIR, "packages") yaml.safe_dump(dict(config.json), open(DEFAULT_AEA_CONFIG_FILE, "w")) Path( cls.t, cls.agent_name, "vendor", "fetchai", "skills", cls.skill_name ).mkdir(parents=True, exist_ok=True) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", cls.skill_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_file_exists_error(self): """Test that the log error message is fixed. The expected message is: 'Cannot find skill: '{skill_name}'' """ missing_path = os.path.join("vendor", "fetchai", "skills", self.skill_name) missing_path = double_escape_windows_path_separator(missing_path) assert missing_path in self.result.exception.message @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAddSkillWithContractsDeps(AEATestCaseEmpty): """Test add skill with contract dependencies.""" def test_add_skill_with_contracts_positive(self): """Test add skill with contract dependencies positive result.""" self.add_item("skill", str(ERC1155_CLIENT_PUBLIC_ID)) contracts_path = os.path.join(self.agent_name, "vendor", "fetchai", "contracts") contracts_folders = os.listdir(contracts_path) contract_dependency_name = "erc1155" assert contract_dependency_name in contracts_folders class TestAddSkillFromRemoteRegistry(AEATestCaseEmptyFlaky): """Test case for add skill from Registry command.""" IS_LOCAL = False IS_EMPTY = True @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_skill_from_remote_registry_positive(self): """Test add skill from Registry positive result.""" self.add_item("skill", str(ECHO_PUBLIC_ID.to_latest()), local=self.IS_LOCAL) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "skills") items_folders = os.listdir(items_path) item_name = "echo" assert item_name in items_folders class TestAddSkillWithLatestVersion(AEATestCaseEmpty): """Test case for add skill with latest version.""" def test_add_skill_latest_version(self): """Test add skill with latest version.""" self.add_item("skill", str(ECHO_PUBLIC_ID.to_latest()), local=True) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "skills") items_folders = os.listdir(items_path) item_name = "echo" assert item_name in items_folders class TestAddSkillMixedModeFallsBack(AEATestCaseEmpty): """Test add skill in mixed mode that fails with local falls back to remote registry.""" IS_EMPTY = True @mock.patch( "aea.cli.add.find_item_locally_or_distributed", side_effect=click.ClickException(""), ) def test_add_skill_remote_mode_negative_local_positive_remote(self, *_mocks): """Test add skill mixed mode.""" self.run_cli_command( "add", "skill", str(ECHO_PUBLIC_ID.to_latest()), cwd=self._get_cwd() ) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "skills") items_folders = os.listdir(items_path) item_name = "echo" assert item_name in items_folders class TestAddSkillRemoteMode(AEATestCaseEmpty): """Test case for add skill, --remote mode.""" IS_EMPTY = True def test_add_skill_remote_mode(self): """Test add skill mixed mode.""" self.run_cli_command( "add", "--remote", "skill", str(ECHO_PUBLIC_ID.to_latest()), cwd=self._get_cwd(), ) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "skills") items_folders = os.listdir(items_path) item_name = "echo" assert item_name in items_folders ================================================ FILE: tests/test_aea/test_cli/test_add_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add-key` sub-command.""" import os import shutil import tempfile from pathlib import Path from unittest import TestCase, mock import pytest import yaml from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from click.exceptions import BadParameter import aea from aea.cli import cli from aea.cli.add_key import _try_add_key from aea.configurations.base import AgentConfig, DEFAULT_AEA_CONFIG_FILE from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner, ETHEREUM_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE, ROOT_DIR, ) from tests.test_aea.test_cli.tools_for_testing import ContextMock class TestAddFetchKey: """Test that the command 'aea add-key' works as expected for a 'fetchai' key.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.agent_folder = Path(cls.t, cls.agent_name) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name] ) assert result.exit_code == 0 os.chdir(Path(cls.t, cls.agent_name)) shutil.copy( Path(CUR_PATH, "data", FETCHAI_PRIVATE_KEY_FILE), cls.agent_folder / FETCHAI_PRIVATE_KEY_FILE, ) cls.result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE, ], ) def test_return_code(self): """Test return code equal to zero.""" assert self.result.exit_code == 0 def test_key_added(self): """Test that the fetch private key has been added correctly.""" f = open(Path(self.agent_folder, DEFAULT_AEA_CONFIG_FILE)) expected_json = yaml.safe_load(f) config = AgentConfig.from_json(expected_json) private_key_path = config.private_key_paths.read(FetchAICrypto.identifier) assert private_key_path == FETCHAI_PRIVATE_KEY_FILE assert len(config.private_key_paths.read_all()) == 1 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) shutil.rmtree(cls.t) class TestAddEthereumhKey: """Test that the command 'aea add-key' works as expected for an 'ethereum' key.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() cls.agent_folder = Path(cls.t, cls.agent_name) dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name] ) assert result.exit_code == 0 os.chdir(Path(cls.t, cls.agent_name)) shutil.copy( Path(CUR_PATH, "data", ETHEREUM_PRIVATE_KEY_FILE), cls.agent_folder / ETHEREUM_PRIVATE_KEY_FILE, ) cls.result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "add-key", EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE, ], ) def test_return_code(self): """Test return code equal to zero.""" assert self.result.exit_code == 0 def test_key_added(self): """Test that the fetch private key has been added correctly.""" f = open(Path(self.agent_folder, DEFAULT_AEA_CONFIG_FILE)) expected_json = yaml.safe_load(f) config = AgentConfig.from_json(expected_json) private_key_path = config.private_key_paths.read(EthereumCrypto.identifier) assert private_key_path == ETHEREUM_PRIVATE_KEY_FILE assert len(config.private_key_paths.read_all()) == 1 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) shutil.rmtree(cls.t) class TestAddManyKeys: """Test that the command 'aea add-key' works as expected when adding many keys.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.agent_folder = Path(cls.t, cls.agent_name) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name] ) assert result.exit_code == 0 os.chdir(Path(cls.t, cls.agent_name)) shutil.copy( Path(CUR_PATH, "data", FETCHAI_PRIVATE_KEY_FILE), cls.agent_folder / FETCHAI_PRIVATE_KEY_FILE, ) shutil.copy( Path(CUR_PATH, "data", ETHEREUM_PRIVATE_KEY_FILE), cls.agent_folder / ETHEREUM_PRIVATE_KEY_FILE, ) def test_add_many_keys(self, pytestconfig): """Test that the keys are added correctly.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier], ) assert result.exit_code == 0 result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "add-key", EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE, ], ) assert result.exit_code == 0 f = open(Path(self.agent_folder, DEFAULT_AEA_CONFIG_FILE)) expected_json = yaml.safe_load(f) config = AgentConfig.from_json(expected_json) private_key_path_ethereum = config.private_key_paths.read( FetchAICrypto.identifier ) assert private_key_path_ethereum == FETCHAI_PRIVATE_KEY_FILE private_key_path_ethereum = config.private_key_paths.read( EthereumCrypto.identifier ) assert private_key_path_ethereum == ETHEREUM_PRIVATE_KEY_FILE assert len(config.private_key_paths.read_all()) == 2 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except OSError: pass def test_add_key_fails_bad_key(): """Test that 'aea add-key' fails because the key is not valid.""" oldcwd = os.getcwd() runner = CliRunner() agent_name = "myagent" tmpdir = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = tmpdir / dir_path src_dir = oldcwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(tmpdir) try: with mock.patch.object( aea.crypto.helpers._default_logger, "error" ) as mock_logger_error: result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) result = runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", agent_name] ) assert result.exit_code == 0 os.chdir(Path(tmpdir, agent_name)) # create an empty file - surely not a private key pvk_file = "this_is_not_a_key.txt" Path(pvk_file).touch() result = runner.invoke( cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier, pvk_file] ) assert result.exit_code == 1 error_message = "Invalid length of private key, received 0, expected 32" mock_logger_error.assert_called_with( "This is not a valid private key file: '{}'\n Exception: '{}'".format( pvk_file, error_message ), ) # check that no key has been added. f = open(Path(DEFAULT_AEA_CONFIG_FILE)) expected_json = yaml.safe_load(f) config = AgentConfig.from_json(expected_json) assert len(config.private_key_paths.read_all()) == 0 finally: os.chdir(oldcwd) def test_add_key_fails_bad_ledger_id(): """Test that 'aea add-key' fails because the ledger id is not valid.""" oldcwd = os.getcwd() runner = CliRunner() agent_name = "myagent" tmpdir = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = tmpdir / dir_path src_dir = oldcwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(tmpdir) try: result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(tmpdir, agent_name)) # generate a private key file result = runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] ) assert result.exit_code == 0 assert Path(FETCHAI_PRIVATE_KEY_FILE).exists() bad_ledger_id = "this_is_a_bad_ledger_id" result = runner.invoke( cli, [*CLI_LOG_OPTION, "add-key", bad_ledger_id, FETCHAI_PRIVATE_KEY_FILE] ) assert result.exit_code == 2 # check that no key has been added. f = open(Path(DEFAULT_AEA_CONFIG_FILE)) expected_json = yaml.safe_load(f) config = AgentConfig.from_json(expected_json) assert len(config.private_key_paths.read_all()) == 0 finally: os.chdir(oldcwd) @mock.patch("aea.cli.add_key.open_file", mock.mock_open()) class AddKeyTestCase(TestCase): """Test case for _add_key method.""" def test__add_key_positive(self, *mocks): """Test for _add_key method positive result.""" ctx = ContextMock() _try_add_key(ctx, "type", "filepath") @mock.patch("aea.cli.add_key.open_file", mock.mock_open()) class AddKeyConnectionTestCase(TestCase): """Test case for _add_key method.""" def test__add_key_positive(self, *mocks): """Test for _add_key method positive result.""" ctx = ContextMock() _try_add_key(ctx, "type", "filepath", connection=True) @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") @mock.patch("aea.cli.add_key.try_validate_private_key_path") @mock.patch("aea.cli.add_key._try_add_key") class AddKeyCommandTestCase(TestCase): """Test case for CLI add_key command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_run_positive(self, *mocks): """Test for CLI add_key positive result.""" filepath = str( Path(ROOT_DIR, "pyproject.toml") ) # some existing filepath to pass CLI argument check result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "add-key", FetchAICrypto.identifier, filepath, ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") @mock.patch("aea.cli.add_key.try_validate_private_key_path") @mock.patch("aea.cli.add_key._try_add_key") class CheckFileNotExistsTestCase(TestCase): """Test case for CLI add_key command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_file_specified_does_not_exist(self, *mocks): """Test for CLI add_key fails on file not exists.""" with pytest.raises(BadParameter, match=r"File '.*' does not exist."): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "add-key", FetchAICrypto.identifier, "somefile", ], standalone_mode=False, catch_exceptions=False, ) def test_file_not_specified_does_not_exist(self, *mocks): """Test for CLI add_key fails on file not exists.""" with pytest.raises(BadParameter, match=r"File '.*' does not exist."): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "add-key", FetchAICrypto.identifier, ], standalone_mode=False, catch_exceptions=False, ) class TestAddKeyWithPassword(AEATestCaseEmpty): """Test the '--password' option to 'add-key' command.""" FAKE_PASSWORD = "password" # nosec @classmethod def setup_class(cls) -> None: """Set up the class.""" super().setup_class() cls.run_cli_command( "generate-key", FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE, "--password", cls.FAKE_PASSWORD, cwd=cls._get_cwd(), ) def test_add_key_with_password(self): """Test add key with password.""" self.run_cli_command( "add-key", FetchAICrypto.identifier, "--password", self.FAKE_PASSWORD, cwd=self._get_cwd(), ) ================================================ FILE: tests/test_aea/test_cli/test_build.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea build` sub-command.""" import re from pathlib import Path from unittest import mock import pytest from click.exceptions import ClickException from aea.configurations.constants import DEFAULT_VERSION from aea.test_tools.test_cases import AEATestCaseEmpty from tests.common.utils import run_aea_subprocess class TestAEABuildEmpty(AEATestCaseEmpty): """Test the command 'aea build', empty project.""" def test_build(self): """Test build command.""" result = self.run_cli_command("build", cwd=self._get_cwd()) assert result.exit_code == 0 assert "Build completed!" in result.stdout class TestAEABuildMainEntrypoint(AEATestCaseEmpty): """Test the command 'aea build', only main entrypoint.""" @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() cls.entrypoint = "script.py" cls.expected_string = "Hello, world!" (Path(cls._get_cwd()) / cls.entrypoint).write_text( f"print('{cls.expected_string}')" ) cls.nested_set_config("agent.build_entrypoint", cls.entrypoint) def test_build(self): """Test build command.""" result, stdout, stderr = run_aea_subprocess("-s", "build", cwd=self._get_cwd()) assert result.returncode == 0 assert re.search(r"Building AEA package\.\.\.", stdout) assert re.search(r"Running command '.*script\.py .+'", stdout) assert "Build completed!" in stdout assert self.expected_string in stdout class TestAEABuildPackageEntrypoint(AEATestCaseEmpty): """Test the command 'aea build', with a package entrypoint.""" @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() cls.entrypoint = "script.py" cls.expected_string = "Hello, world!" cls.scaffold_package_name = "my_protocol" cls.scaffold_item("protocol", cls.scaffold_package_name) ( Path(cls._get_cwd()) / "protocols" / cls.scaffold_package_name / cls.entrypoint ).write_text(f"print('{cls.expected_string}')") cls.nested_set_config( f"protocols.{cls.scaffold_package_name}.build_entrypoint", cls.entrypoint ) def test_build(self): """Test build command.""" result, stdout, stderr = run_aea_subprocess("-s", "build", cwd=self._get_cwd()) assert result.returncode == 0 assert re.search( rf"Building package \(protocol, {self.author}/{self.scaffold_package_name}:{DEFAULT_VERSION}\)...", stdout, ) assert re.search(r"Running command '.*script\.py .+'", stdout) assert "Build completed!" in stdout class TestAEABuildEntrypointNegative(AEATestCaseEmpty): """Test the command 'aea build', in case there is an exception.""" @mock.patch( "aea.cli.build.AEABuilder.call_all_build_entrypoints", side_effect=Exception("some error."), ) def test_build_exception(self, *_mock): """Test build exception.""" with pytest.raises(ClickException, match="some error."): self.run_cli_command("build", cwd=self._get_cwd()) ================================================ FILE: tests/test_aea/test_cli/test_config.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea config` sub-command.""" import json import os import shutil import tempfile from pathlib import Path import pytest from click.exceptions import ClickException from aea.aea_builder import AEABuilder from aea.cli import cli from aea.cli.config import AgentConfigManager from aea.configurations.base import AgentConfig, DEFAULT_AEA_CONFIG_FILE, PackageType from aea.configurations.loader import ConfigLoader from aea.configurations.manager import ALLOWED_PATH_ROOTS from aea.helpers.yaml_utils import yaml_load from tests.conftest import CLI_LOG_OPTION, CUR_PATH, CliRunner, ROOT_DIR class TestConfigGet: """Test that the command 'aea config get' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(cls.t, "dummy_aea")) os.chdir(Path(cls.t, "dummy_aea")) cls.runner = CliRunner() def test_get_agent_name(self): """Test getting the agent name.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "agent.agent_name"], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 assert result.output == "Agent0\n" def test_get_agent_default_routing(self): """Test getting the agent name.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "agent.default_routing"], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 assert result.output == "{}\n" def test_get_skill_name(self): """Test getting the 'dummy' skill name.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "skills.dummy.name"], standalone_mode=False, ) assert result.exit_code == 0 assert result.output == "dummy\n" def test_get_nested_attribute(self): """Test getting the 'dummy' skill name.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "get", "skills.dummy.behaviours.dummy.class_name", ], standalone_mode=False, ) assert result.exit_code == 0 assert result.output == "DummyBehaviour\n" def test_no_recognized_root(self): """Test that the 'get' fails because the root is not recognized.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "wrong_root.agent_name"], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "The root of the dotted path must be one of: {}".format( ALLOWED_PATH_ROOTS ) ) def test_too_short_path_but_root_correct(self): """Test that the 'get' fails because the path is too short but the root is correct.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "agent"], standalone_mode=False ) assert result.exit_code == 1 assert ( result.exception.message == "The path is too short. Please specify a path up to an attribute name." ) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "skills.dummy"], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "The path is too short. Please specify a path up to an attribute name." ) def test_resource_not_existing(self): """Test that the 'get' fails because the resource does not exist.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "get", "connections.non_existing_connection.name", ], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "Resource connections/non_existing_connection does not exist." ) def test_attribute_not_found(self): """Test that the 'get' fails because the attribute is not found.""" with pytest.raises( ClickException, match=r"Attribute `.* for .* config does not exist" ): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "get", "skills.dummy.non_existing_attribute", ], standalone_mode=False, catch_exceptions=False, ) def test_get_whole_dict(self): """Test that getting the 'dummy' skill behaviours works.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "skills.dummy.behaviours"], standalone_mode=False, ) assert result.exit_code == 0 actual_object = json.loads(result.output) expected_object = { "dummy": { "args": {"behaviour_arg_1": 1, "behaviour_arg_2": "2"}, "class_name": "DummyBehaviour", }, "dummy_behaviour_same_classname": { "args": {"behaviour_arg_1": 1, "behaviour_arg_2": "2"}, "class_name": "DummyBehaviour", "file_path": "dummy_subpackage/foo.py", }, } assert actual_object == expected_object def test_get_list(self): """Test that getting the 'dummy' skill behaviours works.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "get", "vendor.fetchai.connections.p2p_libp2p.config.entry_peers", ], standalone_mode=False, ) assert result.exit_code == 0 assert result.output == "[]\n" def test_get_fails_when_getting_nested_object(self): """Test that getting a nested object in 'dummy' skill fails because path is not valid.""" with pytest.raises( ClickException, match=r"Attribute `.* for .* config does not exist" ): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "get", "skills.dummy.non_existing_attribute.dummy", ], standalone_mode=False, catch_exceptions=False, ) def test_get_fails_when_getting_non_dict_attribute(self): """Test that the get fails because the path point to a non-dict object.""" attribute = "protocols" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", f"skills.dummy.{attribute}.protocol"], standalone_mode=False, ) assert result.exit_code == 1 s = f"Attribute '{attribute}' is not a dictionary." assert result.exception.message == s def test_get_fails_when_getting_non_dict_attribute_in_between(self): """Test that the get fails because an object in between is not a dictionary.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", "agent.skills.some_attribute"], standalone_mode=False, ) assert result.exit_code == 1 s = "Attribute 'skills' is not a dictionary." assert result.exception.message == s def test_get_fails_when_getting_vendor_dependency_with_wrong_component_type(self): """Test that getting a vendor component with wrong component type raises error.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "get", "vendor.fetchai.component_type_not_correct.error.non_existing_attribute", ], standalone_mode=False, ) assert result.exit_code == 1 s = "'component_type_not_correct' is not a valid component type. Please use one of ['protocols', 'connections', 'skills', 'contracts']." assert result.exception.message == s @classmethod def teardown_class(cls): """Teardowm the test.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestConfigSet: """Test that the command 'aea config set' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(cls.t, "dummy_aea")) os.chdir(Path(cls.t, "dummy_aea")) cls.runner = CliRunner() def test_set_agent_logging_options(self): """Test setting the agent name.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.logging_config.disable_existing_loggers", "True", "--type=bool", ], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "get", "agent.logging_config.disable_existing_loggers", ], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 assert result.output == "True\n" def test_set_agent_incorrect_value(self): """Test setting the agent name.""" with pytest.raises( ClickException, match="Attribute `not_agent_name` is not allowed to be updated!", ): self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", "agent.not_agent_name", "new_name"], standalone_mode=False, catch_exceptions=False, ) def test_set_type_bool(self): """Test setting the agent name.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.logging_config.disable_existing_loggers", "true", "--type=bool", ], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 def test_set_type_none(self): """Test setting the agent name.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.logging_config.some_value", "", "--type=none", ], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 def test_set_type_dict(self): """Test setting the default routing.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.default_routing", '{"fetchai/contract_api:any": "fetchai/ledger:any"}', "--type=dict", ], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 def test_set_type_list(self): """Test setting the default routing.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "vendor.fetchai.connections.p2p_libp2p.config.entry_peers", '["peer1", "peer2"]', "--type=list", ], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 def test_set_invalid_value(self): """Test setting the agent name.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.agent_name", "true", "--type=bool", ], standalone_mode=False, ) assert result.exit_code == 1 def test_set_skill_name_should_fail(self): """Test setting the 'dummy' skill name.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", "skills.dummy.name", "new_dummy_name"], standalone_mode=False, ) assert result.exit_code == 1 def test_set_nested_attribute(self): """Test setting a nested attribute.""" path = "skills.dummy.behaviours.dummy.args.behaviour_arg_1" new_value = "10" # cause old value is int result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", path, new_value], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", path], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 assert new_value in result.output def test_set_nested_attribute_not_allowed(self): """Test setting a nested attribute.""" path = "skills.dummy.behaviours.dummy.config.behaviour_arg_1" new_value = "new_dummy_name" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", path, new_value], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "Attribute `behaviours.dummy.config.behaviour_arg_1` is not allowed to be updated!" ) def test_no_recognized_root(self): """Test that the 'get' fails because the root is not recognized.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", "wrong_root.agent_name", "value"], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "The root of the dotted path must be one of: {}".format( ALLOWED_PATH_ROOTS ) ) def test_too_short_path_but_root_correct(self): """Test that the 'get' fails because the path is too short but the root is correct.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", "agent", "data"], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "The path is too short. Please specify a path up to an attribute name." ) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", "skills.dummy", "value"], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "The path is too short. Please specify a path up to an attribute name." ) def test_resource_not_existing(self): """Test that the 'get' fails because the resource does not exist.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "connections.non_existing_connection.name", "value", ], standalone_mode=False, ) assert result.exit_code == 1 assert ( result.exception.message == "Resource connections/non_existing_connection does not exist." ) def test_attribute_not_found(self): """Test that the 'set' fails because the attribute is not found.""" with pytest.raises( ClickException, match="Attribute `non_existing_attribute` is not allowed to be updated!", ): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "skills.dummy.non_existing_attribute", "value", ], standalone_mode=False, catch_exceptions=False, ) def test_set_fails_when_setting_non_primitive_type(self): """Test that setting the 'dummy' skill behaviours fails because not a primitive type.""" with pytest.raises( ClickException, match="Attribute `behaviours` is not allowed to be updated!" ): self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", "skills.dummy.behaviours", "value"], standalone_mode=False, catch_exceptions=False, ) def test_get_fails_when_setting_nested_object(self): """Test that setting a nested object in 'dummy' skill fails because path is not valid.""" with pytest.raises( ClickException, match=r"Attribute `non_existing_attribute.dummy` is not allowed to be updated!", ): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "skills.dummy.non_existing_attribute.dummy", "new_value", ], standalone_mode=False, catch_exceptions=False, ) def test_get_fails_when_setting_non_dict_attribute(self): """Test that the set fails because the path point to a non-dict object.""" behaviour_arg_1 = "behaviour_arg_1" path = f"skills.dummy.behaviours.dummy.args.{behaviour_arg_1}.over_the_string" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", path, "new_value"], standalone_mode=False, ) assert result.exit_code == 1 s = f"Attribute '{behaviour_arg_1}' is not a dictionary." assert result.exception.message == s @classmethod def teardown_class(cls): """Teardowm the test.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestConfigNestedGetSet: """Test that the command 'aea config set' works as expected.""" PATH = "skills.dummy.behaviours.dummy.args.behaviour_arg_1" INCORRECT_PATH = "skills.dummy.behaviours.dummy.args.behaviour_arg_100500" INITIAL_VALUE = 1 NEW_VALUE = 100 def setup(self): """Set the test up.""" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = self.t / dir_path src_dir = self.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(self.t, "dummy_aea")) os.chdir(Path(self.t, "dummy_aea")) self.runner = CliRunner() def teardown(self): """Tear dowm the test.""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass def test_set_get_incorrect_path(self): """Fail on incorrect attribute tryed to be updated.""" with pytest.raises( ClickException, match="Attribute `.*` for .* config does not exist" ): self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", self.INCORRECT_PATH], standalone_mode=False, catch_exceptions=False, ) with pytest.raises( ClickException, match="Attribute `behaviours.dummy.args.behaviour_arg_100500` is not allowed to be updated!", ): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", self.INCORRECT_PATH, str(self.NEW_VALUE), ], standalone_mode=False, catch_exceptions=False, ) def load_agent_config(self) -> AgentConfig: """Load agent config for current dir.""" agent_loader = ConfigLoader.from_configuration_type(PackageType.AGENT) with open(DEFAULT_AEA_CONFIG_FILE, "r") as fp: agent_config = agent_loader.load(fp) return agent_config def get_component_config_value(self) -> dict: """Get component variable value.""" package_type, package_name, *path = self.PATH.split(".") file_path = Path(f"{package_type}") / package_name / f"{package_type[:-1]}.yaml" with open(file_path, "r") as fp: data = yaml_load(fp) value = data for i in path: value = value[i] return value def test_set_get_correct_path(self): """Test component value updated in agent config not in component config.""" agent_config = self.load_agent_config() assert not agent_config.component_configurations config_value = self.get_component_config_value() assert config_value == self.INITIAL_VALUE result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", self.PATH], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 assert str(self.INITIAL_VALUE) in result.output result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", self.PATH, str(self.NEW_VALUE)], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 config_value = self.get_component_config_value() assert config_value == self.INITIAL_VALUE result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "get", self.PATH], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 assert str(self.NEW_VALUE) in result.output agent_config = self.load_agent_config() assert agent_config.component_configurations def test_AgentConfigManager_get_overridables(): """Test agent config manager get_overridables.""" path = Path(CUR_PATH, "data", "dummy_aea") agent_config = AEABuilder.try_to_load_agent_configuration_file(path) config_manager = AgentConfigManager(agent_config, path) agent_overridables, component_overridables = config_manager.get_overridables() assert "description" in agent_overridables assert "is_abstract" in list(component_overridables.values())[0] ================================================ FILE: tests/test_aea/test_cli/test_create.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea create` sub-command.""" import filecmp import json import os import shutil import tempfile from pathlib import Path from typing import Dict from unittest import TestCase from unittest.mock import patch import jsonschema import pytest import yaml from jsonschema import Draft4Validator from packaging.version import Version import aea from aea.cli import cli from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE from aea.configurations.loader import ConfigLoader, make_jsonschema_base_uri from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.protocols.state_update.message import StateUpdateMessage from tests.conftest import ( AGENT_CONFIGURATION_SCHEMA, AUTHOR, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CliRunner, ROOT_DIR, ) class TestCreate: """Test that the command 'aea create ' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema, ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) cls.cli_config_file = f"{cls.t}/cli_config.yaml" cls.cli_config_patch = patch( "aea.cli.utils.config.CLI_CONFIG_PATH", cls.cli_config_file ) cls.cli_config_patch.start() result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0, result.stdout cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) cls.agent_config = cls._load_config_file(cls.agent_name) @classmethod def _load_config_file(cls, agent_name) -> Dict: """Load a config file.""" agent_config_file = Path(agent_name, DEFAULT_AEA_CONFIG_FILE) # type: ignore file_pointer = open(agent_config_file, mode="r", encoding="utf-8") agent_config_instance = yaml.safe_load(file_pointer) return agent_config_instance def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 def test_agent_directory_path_exists(self): """Check that the agent's directory has been created.""" agent_dir = Path(self.agent_name) assert agent_dir.exists() assert agent_dir.is_dir() def test_configuration_file_has_been_created(self): """Check that an agent's configuration file has been created.""" agent_config_file = Path(self.agent_name, DEFAULT_AEA_CONFIG_FILE) assert agent_config_file.exists() assert agent_config_file.is_file() def test_configuration_file_is_compliant_to_schema(self): """Check that the agent's configuration file is compliant with the schema.""" try: self.validator.validate(instance=self.agent_config) except jsonschema.exceptions.ValidationError as e: pytest.fail( "Configuration file is not compliant with the schema. Exception: {}".format( str(e) ) ) def test_aea_version_is_correct(self): """Check that the aea version in the configuration file is correct, i.e. the same of the installed package.""" expected_aea_version = Version(aea.__version__) version_no_micro = Version( f"{expected_aea_version.major}.{expected_aea_version.minor}.0" ) version_no_micro = ( version_no_micro if version_no_micro < expected_aea_version else expected_aea_version ) version_next_minor = Version(f"{expected_aea_version.major + 1}.0.0") version_range = f">={version_no_micro}, <{version_next_minor}" assert self.agent_config["aea_version"] == version_range def test_agent_name_is_correct(self): """Check that the agent name in the configuration file is correct.""" assert self.agent_config["agent_name"] == self.agent_name def test_authors_field_is_empty_string(self): """Check that the 'authors' field in the config file is the empty string.""" assert self.agent_config["author"] == AUTHOR def test_connections_contains_nothing(self): """Check that the 'connections' list contains only the 'stub' connection.""" assert self.agent_config["connections"] == [] def test_default_connection_field_is_empty(self): """Check that the 'default_connection' is not specified.""" assert self.agent_config["default_connection"] is None def test_license_field_is_empty_string(self): """Check that the 'license' is the empty string.""" assert ( self.agent_config["license"] == aea.configurations.constants.DEFAULT_LICENSE ) def test_protocols_field_is_not_empty_list(self): """Check that the 'protocols' field is a list with the 'default' protocol.""" assert self.agent_config["protocols"] == [ str(DefaultMessage.protocol_id), str(SigningMessage.protocol_id), str(StateUpdateMessage.protocol_id), ] def test_skills_field_is_empty_list(self): """Check that the 'skills' field is a list with the 'error' skill.""" assert self.agent_config["skills"] == [] def test_version_field_is_equal_to_0_1_0(self): """Check that the 'version' field is equal to the string '0.1.0'.""" assert self.agent_config["version"] == "0.1.0" def test_vendor_content(self): """Check the content of vendor directory is as expected.""" vendor_dir = Path(self.agent_name, "vendor") assert vendor_dir.exists() assert set(vendor_dir.iterdir()) == { vendor_dir / "fetchai", vendor_dir / "__init__.py", } # assert that every subdirectory of vendor/fetchai is a Python package # (i.e. that contains __init__.py) for package_dir in (vendor_dir / "fetchai").iterdir(): assert (package_dir / "__init__.py").exists() def test_vendor_protocols_contains_default_protocol(self): """Check that the vendor protocols directory contains the default protocol.""" stub_connection_dirpath = Path( self.agent_name, "vendor", "fetchai", "protocols", "default" ) assert stub_connection_dirpath.exists() assert stub_connection_dirpath.is_dir() def test_default_protocol_is_equal_to_library_default_protocol(self): """Check that the stub connection directory is equal to the package's one (packages.fetchai.protocols.default).""" default_protocol_dirpath = Path( self.agent_name, "vendor", "fetchai", "protocols", "default" ) comparison = filecmp.dircmp( str(default_protocol_dirpath), str(Path(ROOT_DIR, "packages", "fetchai", "protocols", "default")), ) assert comparison.diff_files == [] def test_protocols_directory_content(self): """Test the content of the 'protocols' directory.""" dir = Path(self.t, self.agent_name, "protocols") assert dir.exists() assert dir.is_dir() assert set(dir.iterdir()) == {dir / "__init__.py"} def test_connections_directory_content(self): """Test the content of the 'connections' directory.""" dir = Path(self.t, self.agent_name, "connections") assert dir.exists() assert dir.is_dir() assert set(dir.iterdir()) == {dir / "__init__.py"} def test_skills_directory_content(self): """Test the content of the 'skills' directory.""" dir = Path(self.t, self.agent_name, "skills") assert dir.exists() assert dir.is_dir() assert set(dir.iterdir()) == {dir / "__init__.py"} @classmethod def teardown_class(cls): """Tear the test down.""" cls.cli_config_patch.start() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestCreateFailsWhenDirectoryAlreadyExists: """Test that 'aea create' sub-command fails when the directory with the agent name in input already exists.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) # create a directory with the agent name -> make 'aea create fail. os.mkdir(cls.agent_name) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the error code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed. The expected message is: 'Directory already exist. Aborting...' """ s = "Directory already exist. Aborting..." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestCreateFailsWhenConfigFileIsNotCompliant: """Test that 'aea create' sub-command fails when the generated configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.runner = CliRunner() cls.agent_name = "myagent" # change the serialization of the AgentConfig class so to make the parsing to fail. cls.patch = patch.object( aea.configurations.base.AgentConfig, "json", return_value={"hello": "world"} ) cls.patch.start() cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the error code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_agent_folder_is_not_created(self): """Test that the agent folder is removed.""" assert not Path(self.agent_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestCreateFailsWhenExceptionOccurs: """Test that 'aea create' sub-command fails when the generated configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.runner = CliRunner() cls.agent_name = "myagent" # change the serialization of the AgentConfig class so to make the parsing to fail. cls.patch = patch.object(ConfigLoader, "dump", side_effect=Exception) cls.patch.start() cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the error code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_agent_folder_is_not_created(self): """Test that the agent folder is removed.""" assert not Path(self.agent_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestCreateFailsWhenAlreadyInAEAProject: """Test that 'aea create' sub-command fails when it is called within an AEA project.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) cls.runner = CliRunner() cls.agent_name = "myagent" result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert cls.result.exit_code == 0 # calling 'aea create myagent' again within an AEA project - recursively. os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the error code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed. The expected message is: "The current folder is already an AEA project. Please move to the parent folder.". """ s = "The current folder is already an AEA project. Please move to the parent folder." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class CreateCommandTestCase(TestCase): """Test case for CLI create command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_create_no_init(self): """Test for CLI create no init result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "agent_name", "--author=some"], standalone_mode=False, ) self.assertEqual( result.exception.message, "Author is not set up. Please use 'aea init' to initialize.", ) @patch("aea.cli.create.get_or_create_cli_config", return_value={}) def test_create_no_author_local(self, *mocks): """Test for CLI create no author local result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", "agent_name"], standalone_mode=False, ) expected_message = ( "The AEA configurations are not initialized. " "Uses `aea init` before continuing or provide optional argument `--author`." ) self.assertEqual(result.exception.message, expected_message) ================================================ FILE: tests/test_aea/test_cli/test_delete.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea delete` sub-command.""" import os import shutil import tempfile import unittest.mock from pathlib import Path from aea.cli import cli from tests.conftest import AUTHOR, CLI_LOG_OPTION, CliRunner, ROOT_DIR class TestDelete: """Test that the command 'aea create ' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "delete", cls.agent_name], standalone_mode=False ) def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 def test_agent_directory_path_does_not_exists(self): """Check that the agent's directory has been deleted.""" agent_dir = Path(self.agent_name) assert not agent_dir.exists() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestDeleteFailsWhenDirectoryDoesNotExist: """Test that 'aea delete' sub-command fails when the directory with the agent name in input does not exist.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) # agent's directory does not exist -> command will fail. cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "delete", cls.agent_name], standalone_mode=False ) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestDeleteFailsWhenDirectoryCannotBeDeleted: """Test that 'aea delete' sub-command fails when the directory with the agent name cannot be deleted.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 # agent's directory does not exist -> command will fail. with unittest.mock.patch.object(shutil, "rmtree", side_effect=OSError): cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "delete", cls.agent_name], standalone_mode=False ) def test_exit_code_equal_to_1(self): """Test that the error code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed. The expected message is: 'Directory already exist. Aborting...' """ s = "An error occurred while deleting the agent directory. Aborting..." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestDeleteFailsWhenDirectoryIsNotAnAEAProject: """Test that 'aea delete' sub-command fails when the directory with the agent name in input is not an AEA project.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) # directory is not AEA project -> command will fail. Path(cls.t, cls.agent_name).mkdir() cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "delete", cls.agent_name], standalone_mode=False ) def test_exit_code_equal_to_1(self): """Test that the error code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed. The expected message is: 'Directory already exist. Aborting...' """ s = "The name provided is not a path to an AEA project." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_eject.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.eject module.""" import os from pathlib import Path from unittest import mock import click import pytest from aea.cli.utils.config import get_or_create_cli_config from aea.configurations.base import ComponentType, DEFAULT_VERSION, PublicId from aea.configurations.loader import load_component_configuration from aea.test_tools.test_cases import AEATestCaseEmpty, AEATestCaseMany from packages.fetchai.connections.gym.connection import ( PUBLIC_ID as GYM_CONNECTION_PUBLIC_ID, ) from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as ERC1155_PUBLIC_ID from packages.fetchai.protocols.default import DefaultMessage from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.skills.error import PUBLIC_ID as ERROR_PUBLIC_ID from packages.fetchai.skills.gym import PUBLIC_ID as GYM_SKILL_PUBLIC_ID class TestEjectCommands(AEATestCaseMany): """End-to-end test case for CLI eject commands.""" def test_eject_commands_positive(self): """Test eject commands for positive result.""" agent_name = "test_aea" self.create_agents(agent_name) self.set_agent_context(agent_name) cwd = os.path.join(self.t, agent_name) self.add_item("connection", str(GYM_CONNECTION_PUBLIC_ID)) self.add_item("skill", str(GYM_SKILL_PUBLIC_ID)) self.add_item("contract", str(ERC1155_PUBLIC_ID)) # the order must be kept as is, because of recursive ejects self.eject_item("skill", str(GYM_SKILL_PUBLIC_ID)) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "skills")) ) assert "gym" in os.listdir((os.path.join(cwd, "skills"))) self.eject_item("connection", str(GYM_CONNECTION_PUBLIC_ID)) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "connections")) ) assert "gym" in os.listdir((os.path.join(cwd, "connections"))) self.eject_item("protocol", str(GymMessage.protocol_id)) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "protocols")) ) assert "gym" in os.listdir((os.path.join(cwd, "protocols"))) self.eject_item("contract", str(ERC1155_PUBLIC_ID)) assert "erc1155" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "contracts")) ) assert "erc1155" in os.listdir((os.path.join(cwd, "contracts"))) class TestRecursiveEject(AEATestCaseEmpty): """Test that eject is recursive.""" def test_recursive_eject_commands_positive(self): """Test eject commands for positive result.""" agent_name = "test_aea" self.create_agents(agent_name) self.set_agent_context(agent_name) cwd = os.path.join(self.t, agent_name) self.add_item("connection", str(GYM_CONNECTION_PUBLIC_ID)) self.add_item("skill", str(GYM_SKILL_PUBLIC_ID)) self.add_item("contract", str(ERC1155_PUBLIC_ID)) # ejecting the gym protocol will cause the ejection of # all the other packages that depend on it, # that is, gym connection and gym skill. self.eject_item("protocol", str(GymMessage.protocol_id)) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "protocols")) ) assert "gym" in os.listdir((os.path.join(cwd, "protocols"))) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "connections")) ) assert "gym" in os.listdir((os.path.join(cwd, "connections"))) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "skills")) ) assert "gym" in os.listdir((os.path.join(cwd, "skills"))) class TestRecursiveEjectIsAborted(AEATestCaseEmpty): """Test that recursive eject is aborted in non-quiet mode.""" @mock.patch("click.confirm", return_value=False) def test_recursive_eject_commands_non_quiet_negative(self, *_mocks): """Test eject command for negative result in interactive mode.""" agent_name = "test_aea" self.create_agents(agent_name) self.set_agent_context(agent_name) cwd = os.path.join(self.t, agent_name) self.add_item("connection", str(GYM_CONNECTION_PUBLIC_ID)) self.add_item("skill", str(GYM_SKILL_PUBLIC_ID)) self.add_item("contract", str(ERC1155_PUBLIC_ID)) self.run_cli_command( "eject", "protocol", str(GymMessage.protocol_id), cwd=self._get_cwd() ) # assert packages not ejected assert "gym" in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "protocols")) ) assert "gym" not in os.listdir((os.path.join(cwd, "protocols"))) assert "gym" in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "connections")) ) assert "gym" not in os.listdir((os.path.join(cwd, "connections"))) assert "gym" in os.listdir((os.path.join(cwd, "vendor", "fetchai", "skills"))) assert "gym" not in os.listdir((os.path.join(cwd, "skills"))) class BaseTestEjectCommand(AEATestCaseEmpty): """Replace CLI author with a known author.""" EXPECTED_AUTHOR = "" @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() config = get_or_create_cli_config() cls.EXPECTED_AUTHOR = config.get("author", "") class TestEjectCommandCliConfigNotAvailable(AEATestCaseEmpty): """Test that 'aea eject' cannot be run if CLI configuration not provided.""" IS_EMPTY = True @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() cls.add_item("protocol", str(DefaultMessage.protocol_id)) @mock.patch("aea.cli.utils.config.get_or_create_cli_config", return_value={}) def test_error(self, *_mocks): """Test that without CLI configuration, 'aea eject' won't work.""" with pytest.raises( click.ClickException, match="The AEA configurations are not initialized. Use `aea init` before continuing.", ): self.invoke( "eject", "--quiet", "protocol", str(DefaultMessage.protocol_id), ) class TestEjectCommandReplacesReferences(BaseTestEjectCommand): """Test that eject command replaces the right references to the new package.""" IS_EMPTY = True @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() cls.add_item("protocol", str(DefaultMessage.protocol_id)) cls.eject_item("protocol", str(DefaultMessage.protocol_id)) def test_username_is_correct(self): """Test that the author name in the ejected component configuration is updated correctly.""" package_path = Path( self.current_agent_context, "protocols", DefaultMessage.protocol_id.name ) assert ( package_path.exists() ), f"Expected ejected package in '{package_path}', but not found." component_configuration = load_component_configuration( ComponentType.PROTOCOL, package_path ) assert component_configuration.author == self.EXPECTED_AUTHOR assert component_configuration.name == DefaultMessage.protocol_id.name assert component_configuration.version == DEFAULT_VERSION def test_aea_config_references_updated_correctly(self): """Test that the references in the AEA configuration is updated correctly.""" agent_config = self.load_agent_config(self.agent_name) assert agent_config.protocols == { PublicId( self.EXPECTED_AUTHOR, DefaultMessage.protocol_id.name, DEFAULT_VERSION ) } class TestEjectCommandReplacesCustomConfigurationReference(BaseTestEjectCommand): """Test that eject command replaces references in AEA configuration.""" IS_EMPTY = True @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() cls.add_item("skill", str(ERROR_PUBLIC_ID)) # add a custom configuration to the error skill cls.run_cli_command( "--skip-consistency-check", "config", "set", "vendor.fetchai.skills.error.is_abstract", "--type", "bool", "true", cwd=cls._get_cwd(), ) cls.eject_item("skill", str(ERROR_PUBLIC_ID)) def test_username_is_correct(self): """Test that the author name in the ejected component configuration is updated correctly.""" package_path = Path(self.current_agent_context, "skills", ERROR_PUBLIC_ID.name) assert ( package_path.exists() ), f"Expected ejected package in '{package_path}', but not found." component_configuration = load_component_configuration( ComponentType.SKILL, package_path ) assert component_configuration.author == self.EXPECTED_AUTHOR def test_aea_config_references_updated_correctly(self): """Test that the references in the AEA configuration is updated correctly.""" agent_config = self.load_agent_config(self.agent_name) assert agent_config.skills == { PublicId(self.EXPECTED_AUTHOR, ERROR_PUBLIC_ID.name, DEFAULT_VERSION) } class TestEjectWithLatest(AEATestCaseEmpty): """Test the eject command when a public id 'latest' is provided.""" @classmethod def setup_class(cls): """Setup class.""" super(TestEjectWithLatest, cls).setup_class() cls.add_item("skill", str(ERROR_PUBLIC_ID.to_latest())) def test_command(self): """Run the test.""" latest_public_id = ERROR_PUBLIC_ID.to_latest() self.eject_item("skill", str(latest_public_id)) cwd = os.path.join(self.t, self.agent_name) # assert packages ejected assert "error" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "skills")) ) assert "error" in os.listdir((os.path.join(cwd, "skills"))) class TestEjectWithSymlink(AEATestCaseEmpty): """Test the eject command with symlinks flag.""" @classmethod def setup_class(cls): """Setup class.""" super(TestEjectWithSymlink, cls).setup_class() cls.add_item("skill", str(ERROR_PUBLIC_ID.to_latest())) def test_command(self): """Run the test.""" latest_public_id = ERROR_PUBLIC_ID.to_latest() self.run_cli_command( "eject", "--with-symlinks", "skill", str(latest_public_id), cwd=self._get_cwd(), ) cwd = os.path.join(self.t, self.agent_name) # assert packages ejected assert "error" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "skills")) ) assert "error" in os.listdir((os.path.join(cwd, "skills"))) assert "error" in os.listdir( (os.path.join(cwd, "vendor", self.author, "skills")) ) ================================================ FILE: tests/test_aea/test_cli/test_fetch.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI Registry fetch methods.""" import os from abc import ABC from tempfile import TemporaryDirectory from unittest import TestCase, mock import click import pytest from click import ClickException import aea from aea.cli import cli from aea.cli.fetch import _is_version_correct, fetch_agent_locally from aea.cli.utils.context import Context from aea.configurations.base import PublicId from aea.helpers.base import cd from aea.test_tools.test_cases import ( AEATestCaseMany, AEATestCaseManyFlaky, BaseAEATestCase, ) from tests.conftest import ( CLI_LOG_OPTION, CliRunner, MAX_FLAKY_RERUNS, MY_FIRST_AEA_PUBLIC_ID, PACKAGES_DIR, ) from tests.test_aea.test_cli.tools_for_testing import ContextMock, PublicIdMock def _raise_click_exception(*args, **kwargs): raise ClickException("Message") @mock.patch("builtins.open", mock.mock_open()) @mock.patch("aea.cli.utils.decorators._cast_ctx") @mock.patch("aea.cli.fetch.os.path.join", return_value="joined-path") @mock.patch("aea.cli.fetch.try_get_item_source_path", return_value="path") @mock.patch("aea.cli.fetch.try_to_load_agent_config") class FetchAgentLocallyTestCase(TestCase): """Test case for fetch_agent_locally method.""" @mock.patch("aea.cli.fetch._is_version_correct", return_value=True) @mock.patch("aea.cli.fetch.os.path.exists", return_value=False) @mock.patch("aea.cli.fetch.copy_tree") def test_fetch_agent_locally_positive(self, copy_tree, *mocks): """Test for fetch_agent_locally method positive result.""" ctx = ContextMock() ctx.config["is_local"] = True fetch_agent_locally(ctx, PublicIdMock(), alias="some-alias") copy_tree.assert_called_once_with("path", "joined-path", dirs_exist_ok=True) @mock.patch("aea.cli.fetch._is_version_correct", return_value=True) @mock.patch("aea.cli.fetch.os.path.exists", return_value=True) @mock.patch("aea.cli.fetch.copy_tree") def test_fetch_agent_locally_already_exists(self, *mocks): """Test for fetch_agent_locally method agent already exists.""" ctx = ContextMock() ctx.config["is_local"] = True with self.assertRaises(ClickException): fetch_agent_locally(ctx, PublicIdMock()) @mock.patch("aea.cli.fetch._is_version_correct", return_value=False) @mock.patch("aea.cli.fetch.os.path.exists", return_value=True) @mock.patch("aea.cli.fetch.copy_tree") def test_fetch_agent_locally_incorrect_version(self, *mocks): """Test for fetch_agent_locally method incorrect agent version.""" ctx = ContextMock() ctx.config["is_local"] = True with self.assertRaises(ClickException): fetch_agent_locally(ctx, PublicIdMock()) @mock.patch("aea.cli.fetch._is_version_correct", return_value=True) @mock.patch("aea.cli.fetch.add_item") @mock.patch("aea.cli.fetch.os.path.exists", return_value=False) @mock.patch("aea.cli.fetch.copy_tree") def test_fetch_agent_locally_with_deps_positive(self, *mocks): """Test for fetch_agent_locally method with deps positive result.""" public_id = PublicIdMock.from_str("author/name:0.1.0") ctx_mock = ContextMock( connections=[public_id], protocols=[public_id], skills=[public_id], contracts=[public_id], ) ctx_mock.config["is_local"] = True fetch_agent_locally(ctx_mock, PublicIdMock()) @mock.patch("aea.cli.fetch._is_version_correct", return_value=True) @mock.patch("aea.cli.fetch.os.path.exists", return_value=False) @mock.patch("aea.cli.fetch.copy_tree") @mock.patch("aea.cli.fetch.add_item", _raise_click_exception) def test_fetch_agent_locally_with_deps_fail(self, *mocks): """Test for fetch_agent_locally method with deps ClickException catch.""" public_id = PublicIdMock.from_str("author/name:0.1.0") ctx_mock = ContextMock( connections=[public_id], protocols=[public_id], skills=[public_id], contracts=[public_id], ) ctx_mock.config["is_local"] = True with self.assertRaises(ClickException): fetch_agent_locally(ctx_mock, PublicIdMock()) @mock.patch("aea.cli.fetch.fetch_agent") @mock.patch("aea.cli.fetch.fetch_agent_locally") class FetchCommandTestCase(TestCase): """Test case for CLI fetch command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_fetch_positive_mixed(self, *mocks): """Test for CLI push connection positive result.""" self.runner.invoke( cli, [*CLI_LOG_OPTION, "fetch", "author/name:0.1.0"], standalone_mode=False, ) def test_fetch_positive_local(self, *mocks): """Test for CLI push connection positive result.""" self.runner.invoke( cli, [*CLI_LOG_OPTION, "fetch", "--local", "author/name:0.1.0"], standalone_mode=False, ) def test_fetch_positive_remote(self, *mocks): """Test for CLI push connection positive result.""" self.runner.invoke( cli, [*CLI_LOG_OPTION, "fetch", "--remote", "author/name:0.1.0"], standalone_mode=False, ) class IsVersionCorrectTestCase(TestCase): """Test case for _is_version_correct method.""" def test__is_version_correct_positive(self): """Test for _is_version_correct method positive result.""" public_id = PublicId("author", "package", "0.1.0") ctx_mock = ContextMock(version=public_id.version) ctx_mock.agent_config.public_id = public_id result = _is_version_correct(ctx_mock, public_id) self.assertTrue(result) def test__is_version_correct_negative(self): """Test for _is_version_correct method negative result.""" public_id_a = PublicId("author", "package", "0.1.0") public_id_b = PublicId("author", "package", "0.1.1") ctx_mock = ContextMock(version=public_id_b.version) ctx_mock.agent_config.public_id = public_id_b result = _is_version_correct(ctx_mock, public_id_a) self.assertFalse(result) class TestFetchFromRemoteRegistry(AEATestCaseManyFlaky): """Test case for fetch agent command from Registry.""" @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_fetch_agent_from_remote_registry_positive(self): """Test fetch agent from Registry for positive result.""" self.run_cli_command( "fetch", str(MY_FIRST_AEA_PUBLIC_ID.to_latest()), "--remote" ) assert "my_first_aea" in os.listdir(self.t) class TestFetchMixedModeFallsBackCorrectly(AEATestCaseManyFlaky): """Test fetch command when registry fetch fails falls back to local fetch.""" @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @mock.patch("aea.cli.fetch.fetch_agent", side_effect=ClickException("")) @mock.patch("aea.cli.fetch.fetch_agent_locally", wraps=fetch_agent_locally) def test_fetch_agent_from_remote_registry_falls_back_to_local( self, local_fetch, _remote_fetch ): """Test fetch agent from Registry for positive result.""" self.run_cli_command("fetch", str(MY_FIRST_AEA_PUBLIC_ID)) local_fetch.assert_called() assert "my_first_aea" in os.listdir(self.t) class TestFetchLatestVersion(AEATestCaseMany): """Test case for fetch agent, latest version.""" def test_fetch_agent_latest(self): """Test fetch agent, latest version.""" self.run_cli_command( "fetch", "--local", str(MY_FIRST_AEA_PUBLIC_ID.to_latest()) ) assert "my_first_aea" in os.listdir(self.t) class TestFetchAgentMixed(BaseAEATestCase): """Test 'aea fetch' in mixed mode.""" @pytest.mark.integration @mock.patch( "aea.cli.registry.add.fetch_package", wraps=aea.cli.registry.add.fetch_package ) @mock.patch( "aea.cli.add.find_item_locally_or_distributed", side_effect=click.ClickException(""), ) @mock.patch( "aea.cli.fetch.fetch_agent_locally", side_effect=click.ClickException(""), ) def test_fetch_mixed( self, mock_fetch_package, _mock_fetch_locally, _mock_fetch_agent_locally ) -> None: """Test fetch in mixed mode.""" self.run_cli_command( "-v", "DEBUG", "fetch", str(MY_FIRST_AEA_PUBLIC_ID.to_latest()) ) assert "my_first_aea" in os.listdir(self.t) mock_fetch_package.assert_called() class BaseTestFetchAgentError(BaseAEATestCase, ABC): """Test 'aea fetch' in local, remote or mixed mode when it fails.""" ERROR_MESSAGE = "some error." EXPECTED_ERROR_MESSAGE = "" MODE = "" def _mock_raise_click_exception(self, ctx: Context, *args, **kwargs): """Mock 'add_item' so to always fail.""" raise click.ClickException(BaseTestFetchAgentError.ERROR_MESSAGE) @pytest.mark.integration @mock.patch("aea.cli.fetch.add_item", side_effect=_mock_raise_click_exception) @mock.patch("aea.cli.fetch.fetch_agent", side_effect=_mock_raise_click_exception) @mock.patch( "aea.cli.registry.fetch.add_item", side_effect=_mock_raise_click_exception ) def test_fetch_negative(self, *_mocks) -> None: """Test fetch in mixed mode.""" if type(self) == BaseTestFetchAgentError: pytest.skip("Base test class.") with pytest.raises( Exception, match=self.EXPECTED_ERROR_MESSAGE, ): self.run_cli_command( *( ["-v", "DEBUG", "fetch", str(MY_FIRST_AEA_PUBLIC_ID.to_latest())] + ([self.MODE] if self.MODE else []) ) ) class TestFetchAgentNonMixedErrorLocal(BaseTestFetchAgentError): """Test 'aea fetch' in local mode when it fails.""" EXPECTED_ERROR_MESSAGE = f".*{BaseTestFetchAgentError.ERROR_MESSAGE}" MODE = "--local" class TestFetchAgentMixedModeError(BaseTestFetchAgentError): """Test 'aea fetch' in mixed mode when it fails.""" EXPECTED_ERROR_MESSAGE = f".*{BaseTestFetchAgentError.ERROR_MESSAGE}" MODE = "" class TestFetchAgentRemoteModeError(BaseTestFetchAgentError): """Test 'aea fetch' in remote mode when it fails.""" EXPECTED_ERROR_MESSAGE = rf".*{BaseTestFetchAgentError.ERROR_MESSAGE}" MODE = "--remote" def test_fetch_mixed_no_local_registry(): """Test that mixed becomes remote when no local registry.""" with TemporaryDirectory() as tmp_dir: with cd(tmp_dir): name = "my_first_aea" runner = CliRunner() result = runner.invoke( cli, ["fetch", "fetchai/my_first_aea"], catch_exceptions=False, ) assert result.exit_code == 0, result.stdout assert os.path.exists(name) assert "Trying remote registry (`--remote`)." in result.stdout def test_fetch_local_no_local_registry(): """Test that local fetch fails when no local registry.""" with TemporaryDirectory() as tmp_dir: with cd(tmp_dir): runner = CliRunner() result = runner.invoke( cli, ["fetch", "--local", "fetchai/my_first_aea"], catch_exceptions=False, ) assert result.exit_code == 1, result.stdout assert ( "Registry path not provided and local registry `packages` not found in current (.) and parent directory." in result.stdout ) def test_fetch_twice_locally(): """Test fails on fetch if dir exists.""" with TemporaryDirectory() as tmp_dir: with cd(tmp_dir): name = "my_first_aea" runner = CliRunner() result = runner.invoke( cli, [ "--registry-path", PACKAGES_DIR, "fetch", "--local", "fetchai/my_first_aea", ], catch_exceptions=False, ) assert result.exit_code == 0, result.stdout assert os.path.exists(name) with pytest.raises( ClickException, match='Item "my_first_aea" already exists in target folder.', ): result = runner.invoke( cli, [ "--registry-path", PACKAGES_DIR, "fetch", "--local", "fetchai/my_first_aea", ], standalone_mode=False, catch_exceptions=False, ) def test_fetch_twice_remote(): """Test fails on fetch if dir exists.""" with TemporaryDirectory() as tmp_dir: with cd(tmp_dir): name = "my_first_aea" runner = CliRunner() result = runner.invoke( cli, [ "--registry-path", PACKAGES_DIR, "fetch", "--local", "fetchai/my_first_aea", ], catch_exceptions=False, ) assert result.exit_code == 0, result.stdout assert os.path.exists(name) with pytest.raises( ClickException, match='Item "my_first_aea" already exists in target folder.', ): result = runner.invoke( cli, [ "--registry-path", PACKAGES_DIR, "fetch", "--remote", "fetchai/my_first_aea", ], standalone_mode=False, catch_exceptions=False, ) ================================================ FILE: tests/test_aea/test_cli/test_fingerprint.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI fingerprint command.""" from pathlib import Path from unittest import TestCase, mock from unittest.mock import MagicMock import pytest from click import ClickException from aea.cli import cli from aea.cli.fingerprint import fingerprint_item from aea.cli.utils.context import Context from aea.cli.utils.decorators import _check_aea_project from aea.configurations.constants import ( DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, ) from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import CLI_LOG_OPTION, CliRunner from tests.test_aea.test_cli.tools_for_testing import ( ConfigLoaderMock, ContextMock, PublicIdMock, ) @mock.patch("aea.cli.fingerprint.fingerprint_package") class FingerprintCommandTestCase(TestCase): """Test case for CLI fingerprint command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_fingerprint_positive(self, *mocks): """Test for CLI fingerprint positive result.""" public_id = "author/name:0.1.0" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "fingerprint", "connection", public_id], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "fingerprint", "contract", public_id], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "fingerprint", "protocol", public_id], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "fingerprint", "skill", public_id], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) def _run_fingerprint_by_path(self): """Call fingerprint by-path cli command.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "fingerprint", "by-path", "some_dir"], standalone_mode=False, catch_exceptions=False, ) self.assertEqual(result.exit_code, 0, result.exception) def test_by_path_ok(self, fingerprint_mock): """Test fingerprint by_path works ok.""" with mock.patch("os.listdir", return_value=[DEFAULT_CONNECTION_CONFIG_FILE]): self._run_fingerprint_by_path() fingerprint_mock.assert_called() def test_by_path_exceptions(self, *mocks): """Test fingerprint by_path works raises exceptions.""" with pytest.raises( ClickException, match="No package config file found in `.*`. Incorrect directory?", ): with mock.patch("os.listdir", return_value=[]): self._run_fingerprint_by_path() with pytest.raises( ClickException, match="Too many config files in the directory, only one has to present!", ): with mock.patch( "os.listdir", return_value=[ DEFAULT_CONNECTION_CONFIG_FILE, DEFAULT_SKILL_CONFIG_FILE, ], ): self._run_fingerprint_by_path() def _raise_exception(*args, **kwargs): raise Exception() @mock.patch("aea.cli.fingerprint.open_file", mock.mock_open()) class FingerprintItemTestCase(TestCase): """Test case for fingerprint_item method.""" @mock.patch("aea.cli.fingerprint.Path.exists", return_value=False) @mock.patch( "aea.cli.fingerprint.ConfigLoader.from_configuration_type", return_value=ConfigLoaderMock(), ) def test_fingerprint_item_package_not_found(self, *mocks): """Test for fingerprint_item package not found result.""" public_id = PublicIdMock() with self.assertRaises(ClickException) as cm: fingerprint_item(ContextMock(), "skill", public_id) self.assertIn("Package not found at path", cm.exception.message) @mock.patch( "aea.cli.fingerprint.ConfigLoader.from_configuration_type", _raise_exception ) def test_fingerprint_item_exception(self, *mocks): """Test for fingerprint_item exception raised.""" public_id = PublicIdMock() with self.assertRaises(ClickException): fingerprint_item(ContextMock(), "skill", public_id) class TestFingerprintAgent(AEATestCaseEmpty): """Check fingerprint for agent.""" def test_fingerprint(self): """Check fingerprint calculated and checked properly.""" r = self.invoke("fingerprint") assert "calculated" in r.stdout click_context = MagicMock() click_context.obj = Context(self._get_cwd(), "", registry_path=None) click_context.obj.config["skip_consistency_check"] = True _check_aea_project([click_context], check_finger_prints=True) (Path(self._get_cwd()) / "some_file.txt").write_text("sdfds") with pytest.raises( ClickException, match=r"Fingerprints for package .* do not match" ): _check_aea_project([click_context], check_finger_prints=True) self.invoke("fingerprint") _check_aea_project([click_context], check_finger_prints=True) ================================================ FILE: tests/test_aea/test_cli/test_freeze.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea freeze` sub-command.""" import json import os import shutil import tempfile from pathlib import Path import jsonschema import pytest from jsonschema import Draft4Validator from aea.cli import cli from aea.configurations.loader import make_jsonschema_base_uri from tests.conftest import ( AGENT_CONFIGURATION_SCHEMA, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CUR_PATH, CliRunner, MAX_FLAKY_RERUNS, ) class TestFreeze: """Test that the command 'aea freeze' works as expected.""" def setup(self): """Set the test up.""" self.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA)) self.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR)), self.schema ) self.validator = Draft4Validator(self.schema, resolver=self.resolver) self.cwd = os.getcwd() self.t = tempfile.mkdtemp() # copy the 'dummy_aea' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(self.t, "dummy_aea")) self.runner = CliRunner() os.chdir(Path(self.t, "dummy_aea")) self.result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "freeze"], standalone_mode=False ) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_exit_code_equal_to_zero_and_correct_output(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 """Test that the command has printed the correct output.""" assert ( self.result.output == """aea-ledger-cosmos<2.0.0,>=1.0.0\naea-ledger-ethereum<2.0.0,>=1.0.0\naea-ledger-fetchai<2.0.0,>=1.0.0\nprotobuf\n""" ) def teardown(self): """Tear the test down.""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_generate/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea generate` sub-command.""" ================================================ FILE: tests/test_aea/test_cli/test_generate/test_generate.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the aea.cli.generate sub-module.""" from unittest import TestCase, mock import yaml from click import ClickException from aea.cli.generate import _generate_protocol from aea.configurations.base import ( ProtocolSpecification, ProtocolSpecificationParseError, ) from tests.test_aea.test_cli.tools_for_testing import ContextMock def _raise_file_exists(*args, **kwargs): raise FileExistsError() def _which_mock(arg): if arg == "protoc": return True else: return None def _which_mock_isort(arg): if arg == "isort": return None else: return True def _raise_psperror(*args, **kwargs): raise ProtocolSpecificationParseError() def _raise_yamlerror(*args, **kwargs): raise yaml.YAMLError("some yaml error") def _raise_fnfError(*args, **kwargs): raise FileNotFoundError("some fnf error") @mock.patch("aea.protocols.generator.common.open_file", mock.mock_open()) @mock.patch("aea.cli.generate.open_file", mock.mock_open()) @mock.patch( "aea.protocols.generator.common.ConfigLoader.load_protocol_specification", return_value=ProtocolSpecification( name="name", author="author", version="1.0.0", protocol_specification_id="author/name:0.1.0", ), ) @mock.patch("aea.cli.utils.decorators._cast_ctx") class GenerateItemTestCase(TestCase): """Test case for fetch_agent_locally method.""" def test__generate_item_file_exists(self, *_mocks): """Test for fetch_agent_locally method file exists result.""" ctx_mock = ContextMock() with self.assertRaises(ClickException): _generate_protocol(ctx_mock, "path") @mock.patch("aea.protocols.generator.base.shutil.which", _which_mock) def test__generate_item_no_res(self, *_mocks): """Test for fetch_agent_locally method no black.""" ctx_mock = ContextMock() with self.assertRaises(ClickException) as cm: _generate_protocol(ctx_mock, "path") expected_msg = ( "Protocol is NOT generated. The following error happened while generating the protocol:\n" "Cannot find black code formatter! To install, please follow this link: " "https://black.readthedocs.io/en/stable/installation_and_usage.html" ) self.assertEqual(cm.exception.message, expected_msg) @mock.patch("aea.protocols.generator.base.shutil.which", _which_mock_isort) def test__generate_item_no_res_isort_missing(self, *_mocks): """Test for fetch_agent_locally method no isort.""" ctx_mock = ContextMock() with self.assertRaises(ClickException) as cm: _generate_protocol(ctx_mock, "path") expected_msg = ( "Protocol is NOT generated. The following error happened while generating the protocol:\n" "Cannot find isort code formatter! To install, please follow this link: " "https://pycqa.github.io/isort/#installing-isort" ) self.assertEqual(cm.exception.message, expected_msg) @mock.patch("aea.cli.generate.os.path.exists", return_value=False) @mock.patch("aea.protocols.generator.base.shutil.which", return_value="some") @mock.patch("aea.cli.generate.ProtocolGenerator.__init__", _raise_fnfError) def test__generate_item_prerequisite_app_not_installed(self, *mocks): """Test for fetch_agent_locally method parsing specs fail.""" ctx_mock = ContextMock() with self.assertRaises(ClickException) as cm: _generate_protocol(ctx_mock, "path") expected_msg = "Protocol is NOT generated. The following error happened while generating the protocol:\n" self.assertIn(expected_msg, cm.exception.message) @mock.patch("aea.cli.generate.os.path.exists", return_value=False) @mock.patch("aea.protocols.generator.base.shutil.which", return_value="some") @mock.patch("aea.cli.generate.ProtocolGenerator.__init__", _raise_yamlerror) def test__generate_item_parsing_yaml_fail(self, *mocks): """Test for fetch_agent_locally method parsing specs fail.""" ctx_mock = ContextMock() with self.assertRaises(ClickException) as cm: _generate_protocol(ctx_mock, "path") expected_msg = ( "Protocol is NOT generated. The following error happened while generating the protocol:\n" "Yaml error in the protocol specification file:" ) self.assertIn(expected_msg, cm.exception.message) @mock.patch("aea.cli.generate.os.path.exists", return_value=False) @mock.patch("aea.protocols.generator.base.shutil.which", return_value="some") @mock.patch("aea.cli.generate.ProtocolGenerator.__init__", _raise_psperror) def test__generate_item_parsing_specs_fail(self, *mocks): """Test for fetch_agent_locally method parsing specs fail.""" ctx_mock = ContextMock() with self.assertRaises(ClickException) as cm: _generate_protocol(ctx_mock, "path") expected_msg = ( "Protocol is NOT generated. The following error happened while generating the protocol:\n" "Error while parsing the protocol specification: " ) self.assertIn(expected_msg, cm.exception.message) ================================================ FILE: tests/test_aea/test_cli/test_generate/test_protocols.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea generate protocol` sub-command.""" import json import os import shutil import tempfile import unittest.mock from pathlib import Path import jsonschema import yaml from jsonschema import Draft4Validator, ValidationError from aea.cli import cli from aea.configurations.base import DEFAULT_PROTOCOL_CONFIG_FILE from aea.configurations.loader import make_jsonschema_base_uri from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CUR_PATH, CliRunner, PROTOCOL_CONFIGURATION_SCHEMA, ROOT_DIR, ) class TestGenerateProtocolFullMode: """Test that the command 'aea generate protocol' works correctly in correct preconditions.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copyfile( Path(CUR_PATH, "data", "sample_specification.yaml"), Path(cls.t, "sample_specification.yaml"), ) cls.path_to_specification = str(Path("..", "sample_specification.yaml")) cls.schema = json.load(open(PROTOCOL_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema, ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) # create an agent os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.create_result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) os.chdir(cls.agent_name) # generate protocol cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "generate", "protocol", cls.path_to_specification], standalone_mode=False, ) os.chdir(cls.cwd) def test_create_agent_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when creating the agent.""" assert self.create_result.exit_code == 0 def test_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when generating a protocol.""" assert self.result.exit_code == 0, "Failed with stdout='{}'".format( self.result.stdout ) def test_resource_folder_contains_configuration_file(self): """Test that the protocol folder contains a structurally valid configuration file.""" p = Path( self.t, self.agent_name, "protocols", "t_protocol", DEFAULT_PROTOCOL_CONFIG_FILE, ) config_file = yaml.safe_load(open(p)) self.validator.validate(instance=config_file) @classmethod def teardown_class(cls): """Tear the test down.""" try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestGenerateProtocolProtobufOnlyMode: """Test that the command 'aea generate protocol' works correctly in correct preconditions.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copyfile( Path(CUR_PATH, "data", "sample_specification.yaml"), Path(cls.t, "sample_specification.yaml"), ) cls.path_to_specification = str(Path("..", "sample_specification.yaml")) cls.schema = json.load(open(PROTOCOL_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema, ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) # create an agent os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.create_result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) os.chdir(cls.agent_name) # generate protocol cls.result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "generate", "protocol", "--l", "cpp", cls.path_to_specification, ], standalone_mode=False, ) os.chdir(cls.cwd) def test_create_agent_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when creating the agent.""" assert self.create_result.exit_code == 0 def test_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when generating a protocol.""" assert self.result.exit_code == 0, "Failed with stdout='{}'".format( self.result.stdout ) def test_resource_folder_contains_protobuf_schema_file(self): """Test that the protocol folder contains a structurally valid configuration file.""" protobuf_schema_file = Path( self.t, self.agent_name, "protocols", "t_protocol", "t_protocol.proto", ) cpp_header_file = Path( self.t, self.agent_name, "protocols", "t_protocol", "t_protocol.pb.h", ) cpp_implementation_file = Path( self.t, self.agent_name, "protocols", "t_protocol", "t_protocol.pb.cc", ) assert protobuf_schema_file.exists() assert cpp_header_file.exists() assert cpp_implementation_file.exists() @classmethod def teardown_class(cls): """Tear the test down.""" try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestGenerateProtocolFailsWhenDirectoryAlreadyExists: """Test that the command 'aea generate protocol' fails when a directory with the same name as the name of the protocol being generated already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.protocol_name = "t_protocol" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copyfile( Path(CUR_PATH, "data", "sample_specification.yaml"), Path(cls.t, "sample_specification.yaml"), ) cls.path_to_specification = str(Path("..", "sample_specification.yaml")) # create an agent os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.create_result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) os.chdir(cls.agent_name) # create a dummy 'myprotocol' folder Path(cls.t, cls.agent_name, "protocols", cls.protocol_name).mkdir( exist_ok=False, parents=True ) # generate protocol cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "generate", "protocol", cls.path_to_specification], standalone_mode=False, ) os.chdir(cls.cwd) def test_create_agent_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when creating the agent.""" assert self.create_result.exit_code == 0 def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A protocol with name '{protocol_name}' already exists. Aborting...' """ s = ( "Protocol is NOT generated. The following error happened while generating the protocol:\n" + "A directory with name '{}' already exists. Aborting...".format( self.protocol_name ) ) assert self.result.exception.message == s def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "protocols", self.protocol_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestGenerateProtocolFailsWhenProtocolAlreadyExists: """Test that the command 'aea add protocol' fails when the protocol already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copyfile( Path(CUR_PATH, "data", "sample_specification.yaml"), Path(cls.t, "sample_specification.yaml"), ) cls.path_to_specification = str(Path("..", "sample_specification.yaml")) # create an agent os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.create_result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) os.chdir(cls.agent_name) # generate protocol first time cls.generate_result_1 = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "generate", "protocol", cls.path_to_specification], standalone_mode=False, ) # generate protocol second time cls.generate_result_2 = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "generate", "protocol", cls.path_to_specification, ], standalone_mode=False, ) os.chdir(cls.cwd) def test_create_agent_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when creating the agent.""" assert self.create_result.exit_code == 0 def test_generate_protocol_first_time_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 the first time a protocol is generated.""" assert self.generate_result_1.exit_code == 0 def test_generate_protocol_second_time_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 the second time the protocol is generated (i.e. catchall for general errors).""" assert self.generate_result_2.exit_code == 1 def test_error_message_protocol_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A protocol with name '{protocol_name}' already exists. Aborting...' """ s = ( "Protocol is NOT generated. The following error happened while generating the protocol:\n" + "A protocol with name 't_protocol' already exists. Aborting..." ) assert self.generate_result_2.exception.message == s def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "protocols", "t_protocol").exists() @classmethod def teardown_class(cls): """Tear the test down.""" try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestGenerateProtocolFailsWhenConfigFileIsNotCompliant: """Test that the command 'aea generate protocol' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) shutil.copyfile( Path(CUR_PATH, "data", "sample_specification.yaml"), Path(cls.t, "sample_specification.yaml"), ) cls.path_to_specification = str(Path("..", "sample_specification.yaml")) # create an agent os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.create_result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) # change the dumping of yaml module to raise an exception. cls.patch = unittest.mock.patch( "yaml.dump_all", side_effect=ValidationError("test error message") ) cls.patch.start() # generate protocol os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "generate", "protocol", cls.path_to_specification], standalone_mode=False, ) os.chdir(cls.cwd) def test_create_agent_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when creating the agent.""" assert self.create_result.exit_code == 0 def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 when config file is non-compliant (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path(self.t, self.agent_name, "protocols", "t_protocol").exists() def test_configuration_file_not_valid(self): """Test that the log error message is fixed. The expected message is: 'Cannot find protocol: '{protocol_name}' """ s = "Protocol is NOT generated. The following error happened while generating the protocol:\ntest error message" assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestGenerateProtocolFailsWhenExceptionOccurs: """Test that the command 'aea generate protocol' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.path_to_specification = str(Path("..", "sample_specification.yaml")) # create an agent os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 cls.create_result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) # create an exception cls.patch = unittest.mock.patch( "shutil.copytree", side_effect=Exception("unknwon exception") ) cls.patch.start() # generate protocol os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "generate", "protocol", cls.path_to_specification], standalone_mode=False, ) os.chdir(cls.cwd) def test_create_agent_exit_code_equal_to_0(self): """Test that the exit code is equal to 0 when creating the agent.""" assert self.create_result.exit_code == 0 def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 when an exception is thrown (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path(self.t, self.agent_name, "protocols", "t_protocol").exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_generate_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea generate-key` sub-command.""" import json import os import shutil import tempfile from pathlib import Path from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.cli import cli from aea.crypto.registries import make_crypto from aea.helpers.sym_link import cd from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import ( CLI_LOG_OPTION, CliRunner, ETHEREUM_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE, ) class TestGenerateKey: """Test that the command 'aea generate-key' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def test_fetchai(self, password_or_none): """Test that the fetch private key is created correctly.""" args = [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] + ( ["--password", password_or_none] if password_or_none is not None else [] ) result = self.runner.invoke(cli, args) assert result.exit_code == 0 assert Path(FETCHAI_PRIVATE_KEY_FILE).exists() make_crypto( FetchAICrypto.identifier, private_key_path=FETCHAI_PRIVATE_KEY_FILE, password=password_or_none, ) Path(FETCHAI_PRIVATE_KEY_FILE).unlink() def test_ethereum(self, password_or_none): """Test that the fetch private key is created correctly.""" args = [*CLI_LOG_OPTION, "generate-key", EthereumCrypto.identifier] + ( ["--password", password_or_none] if password_or_none is not None else [] ) result = self.runner.invoke(cli, args) assert result.exit_code == 0 assert Path(ETHEREUM_PRIVATE_KEY_FILE).exists() make_crypto( EthereumCrypto.identifier, private_key_path=ETHEREUM_PRIVATE_KEY_FILE, password=password_or_none, ) Path(ETHEREUM_PRIVATE_KEY_FILE).unlink() def test_all(self): """Test that all the private keys are created correctly when running 'aea generate-key all'.""" result = self.runner.invoke(cli, [*CLI_LOG_OPTION, "generate-key", "all"]) assert result.exit_code == 0 assert Path(FETCHAI_PRIVATE_KEY_FILE).exists() assert Path(ETHEREUM_PRIVATE_KEY_FILE).exists() make_crypto(FetchAICrypto.identifier, private_key_path=FETCHAI_PRIVATE_KEY_FILE) make_crypto( EthereumCrypto.identifier, private_key_path=ETHEREUM_PRIVATE_KEY_FILE ) Path(FETCHAI_PRIVATE_KEY_FILE).unlink() Path(ETHEREUM_PRIVATE_KEY_FILE).unlink() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) shutil.rmtree(cls.t) class TestGenerateKeyWhenAlreadyExists: """Test that the command 'aea generate-key' asks for confirmation when a key already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def test_fetchai(self): """Test that the fetchai private key is overwritten or not dependending on the user input.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] ) assert result.exit_code == 0 assert Path(FETCHAI_PRIVATE_KEY_FILE).exists() # This tests if the file has been created and its content is correct. make_crypto(FetchAICrypto.identifier, private_key_path=FETCHAI_PRIVATE_KEY_FILE) content = Path(FETCHAI_PRIVATE_KEY_FILE).read_bytes() # Saying 'no' leave the files as it is. result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier], input="n" ) assert result.exit_code == 0 assert Path(FETCHAI_PRIVATE_KEY_FILE).read_bytes() == content # Saying 'yes' overwrites the file. result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier], input="y" ) assert result.exit_code == 0 assert Path(FETCHAI_PRIVATE_KEY_FILE).read_bytes() != content make_crypto(FetchAICrypto.identifier, private_key_path=FETCHAI_PRIVATE_KEY_FILE) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) shutil.rmtree(cls.t) class TestGenerateKeyWithFile: """Test that the command 'aea generate-key' can accept a file path.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def test_fetchai(self): """Test that the fetchai private key can be deposited in a custom file.""" test_file = "test.txt" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier, test_file] ) assert result.exit_code == 0 assert Path(test_file).exists() # This tests if the file has been created and its content is correct. crypto = make_crypto(FetchAICrypto.identifier, private_key_path=test_file) content = Path(test_file).read_bytes() assert content.decode("utf-8") == crypto.private_key def test_all(self): """Test that the all command does not allow a file to be provided.""" test_file = "test.txt" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", "all", test_file] ) assert result.exit_code == 1 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) shutil.rmtree(cls.t) class TestGenerateKeyWithAddKeyWithoutConnection(AEATestCaseEmpty): """Test that the command 'aea generate-key --add-key' works as expected.""" keys_config_path = "agent.private_key_paths" args = [] # type: ignore def test_fetchai(self): """Test that the fetch private key is created correctly.""" with cd(self._get_cwd()): result = self.run_cli_command( "config", "get", self.keys_config_path, cwd=self._get_cwd() ) assert result.exit_code == 0 assert json.loads(result.stdout_bytes) == {} args = [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] result = self.run_cli_command( *args, "--add-key", *self.args, cwd=self._get_cwd() ) assert result.exit_code == 0 assert Path(FETCHAI_PRIVATE_KEY_FILE).exists() make_crypto( FetchAICrypto.identifier, private_key_path=FETCHAI_PRIVATE_KEY_FILE, password=None, ) Path(FETCHAI_PRIVATE_KEY_FILE).unlink() result = self.run_cli_command( "config", "get", self.keys_config_path, cwd=self._get_cwd() ) assert result.exit_code == 0 agent_keys = json.loads(result.stdout_bytes) assert agent_keys.get(FetchAICrypto.identifier) == FETCHAI_PRIVATE_KEY_FILE class TestGenerateKeyWithAddKeyWithConnection( TestGenerateKeyWithAddKeyWithoutConnection ): """Test that the command 'aea generate-key --add-key' works as expected.""" keys_config_path = "agent.connection_private_key_paths" args = ["--connection"] # type: ignore ================================================ FILE: tests/test_aea/test_cli/test_generate_wealth.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.generate_wealth module.""" from unittest import TestCase, mock from unittest.mock import MagicMock import pytest from aea_ledger_fetchai import FetchAICrypto from aea.cli import cli from aea.cli.generate_wealth import _try_generate_wealth from aea.test_tools.test_cases import AEATestCaseMany, AEATestCaseManyFlaky from tests.conftest import ( CLI_LOG_OPTION, COSMOS_ADDRESS_ONE, CliRunner, MAX_FLAKY_RERUNS_INTEGRATION, ) from tests.test_aea.test_cli.tools_for_testing import ContextMock class GenerateWealthTestCase(TestCase): """Test case for _generate_wealth method.""" @mock.patch("aea.cli.utils.package_utils.Wallet") @mock.patch("aea.cli.generate_wealth.click.echo") @mock.patch("aea.cli.generate_wealth.try_generate_testnet_wealth") @mock.patch( "aea.cli.generate_wealth.get_wallet_from_context", return_value=MagicMock(addresses={"cosmos": COSMOS_ADDRESS_ONE}), ) def test__generate_wealth_positive(self, *mocks): """Test for _generate_wealth method positive result.""" ctx = ContextMock() _try_generate_wealth(ctx, "cosmos", None, True) @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") @mock.patch("aea.cli.utils.package_utils.verify_private_keys_ctx") @mock.patch("aea.cli.generate_wealth._try_generate_wealth") class GenerateWealthCommandTestCase(TestCase): """Test case for CLI generate_wealth command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_run_positive(self, *mocks): """Test for CLI generate_wealth positive result.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "generate-wealth", "--sync", FetchAICrypto.identifier, ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) class TestWealthCommandsPositive(AEATestCaseManyFlaky): """Test case for CLI wealth commands.""" @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_INTEGRATION) def test_wealth_commands(self, password_or_none): """Test wealth commands.""" agent_name = "test_aea" self.create_agents(agent_name) self.set_agent_context(agent_name) self.generate_private_key(password=password_or_none) self.add_private_key(password=password_or_none) self.generate_wealth(password=password_or_none) class TestWealthCommandsNegative(AEATestCaseMany): """Test case for CLI wealth commands, negative case.""" def test_wealth_commands_negative(self): """Test wealth commands.""" agent_name = "test_aea" self.create_agents(agent_name) self.set_agent_context(agent_name) self.generate_private_key() self.add_private_key() settings = {"unsupported_crypto": "path"} self.nested_set_config("agent.private_key_paths", settings) with pytest.raises( Exception, match="Unsupported identifier `unsupported_crypto`" ): self.generate_wealth() ================================================ FILE: tests/test_aea/test_cli/test_get_address.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.get_address module.""" from unittest import TestCase, mock from unittest.mock import MagicMock from aea_ledger_fetchai import FetchAICrypto from aea.cli import cli from aea.cli.get_address import _try_get_address from aea.configurations.constants import DEFAULT_LEDGER from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import CLI_LOG_OPTION, COSMOS_ADDRESS_ONE, CliRunner, method_scope from tests.test_aea.test_cli.tools_for_testing import ContextMock class GetAddressTestCase(TestCase): """Test case for _get_address method.""" @mock.patch( "aea.cli.get_address.get_wallet_from_context", return_value=MagicMock(addresses={"cosmos": COSMOS_ADDRESS_ONE}), ) def test__get_address_positive(self, *mocks): """Test for _get_address method positive result.""" ctx = ContextMock() _try_get_address(ctx, "cosmos") @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") @mock.patch("aea.cli.utils.package_utils.verify_private_keys_ctx") @mock.patch("aea.cli.get_address._try_get_address") @mock.patch("aea.cli.get_address.click.echo") class GetAddressCommandTestCase(TestCase): """Test case for CLI get_address command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_run_positive(self, *mocks): """Test for CLI get_address positive result.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "get-address", FetchAICrypto.identifier, ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) @method_scope class TestGetAddressCommand(AEATestCaseEmpty): """Test 'get-address' command.""" def test_get_address(self, password_or_none): """Run the main test.""" self.generate_private_key(password=password_or_none) self.add_private_key(password=password_or_none) password_option = ["--password", password_or_none] if password_or_none else [] result = self.run_cli_command( "get-address", DEFAULT_LEDGER, *password_option, cwd=self._get_cwd() ) assert result.exit_code == 0 ================================================ FILE: tests/test_aea/test_cli/test_get_multiaddress.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.get_multiaddress module.""" from unittest import mock import base58 import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseEmpty, _get_password_option_args from packages.fetchai.connections.p2p_libp2p.connection import ( PUBLIC_ID as P2P_CONNECTION_PUBLIC_ID, ) from packages.fetchai.connections.stub.connection import ( PUBLIC_ID as STUB_CONNECTION_PUBLIC_ID, ) from tests.conftest import method_scope @method_scope class TestGetMultiAddressCommandPositive(AEATestCaseEmpty): """Test case for CLI get-multiaddress command.""" def test_run(self, password_or_none): """Run the test.""" self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=False, password=password_or_none ) password_options = _get_password_option_args(password_or_none) result = self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, *password_options, cwd=self.current_agent_context, ) assert result.exit_code == 0 # test we can decode the output base58.b58decode(result.stdout) @method_scope class TestGetMultiAddressCommandConnectionPositive(AEATestCaseEmpty): """Test case for CLI get-multiaddress command with --connection flag.""" def test_run(self, password_or_none): """Run the test.""" self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) password_options = _get_password_option_args(password_or_none) result = self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", *password_options, cwd=self.current_agent_context, ) assert result.exit_code == 0 # test we can decode the output @method_scope class TestGetMultiAddressCommandConnectionIdPositive(AEATestCaseEmpty): """Test case for CLI get-multiaddress command with --connection flag.""" def test_run(self, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) self.nested_set_config( "vendor.fetchai.connections.stub.config", {"host": "127.0.0.1", "port": 10000}, ) password_options = _get_password_option_args(password_or_none) result = self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--host-field", "host", "--port-field", "port", *password_options, cwd=self.current_agent_context, ) assert result.exit_code == 0 # multiaddr test expected_multiaddr_prefix = "/dns4/127.0.0.1/tcp/10000/p2p/" assert expected_multiaddr_prefix in result.stdout base58_addr = str(result.stdout).replace(expected_multiaddr_prefix, "") base58.b58decode(base58_addr) @method_scope class TestGetMultiAddressCommandConnectionIdURIPositive(AEATestCaseEmpty): """Test case for CLI get-multiaddress command with --connection flag and --uri.""" def test_run(self, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) port = 10101 host = "127.0.0.1" self.nested_set_config( "vendor.fetchai.connections.stub.config", {"public_uri": f"{host}:{port}"} ) password_options = _get_password_option_args(password_or_none) result = self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--uri-field", "public_uri", *password_options, cwd=self.current_agent_context, ) assert result.exit_code == 0 # multiaddr test expected_multiaddr_prefix = f"/dns4/{host}/tcp/{port}/p2p/" assert expected_multiaddr_prefix in result.stdout base58_addr = str(result.stdout).replace(expected_multiaddr_prefix, "") base58.b58decode(base58_addr) class TestGetMultiAddressCommandConnectionIdURIAgentOverridesPositive(AEATestCaseEmpty): """Test case for CLI get-multiaddress command with --connection flag and --uri for agent overrides.""" def test_run(self, *mocks): """Run the test.""" self.add_item("connection", str(P2P_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier) self.add_private_key(FetchAICrypto.identifier, connection=True) port = 10101 host = "127.0.0.1" self.run_cli_command( "config", "set", "--type", "dict", "vendor.fetchai.connections.p2p_libp2p.config", f'{{"public_uri": "{host}:{port}"}}', cwd=self.current_agent_context, ) result = self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(P2P_CONNECTION_PUBLIC_ID), "--uri-field", "public_uri", cwd=self.current_agent_context, ) assert result.exit_code == 0 # multiaddr test expected_multiaddr_prefix = f"/dns4/{host}/tcp/{port}/p2p/" assert expected_multiaddr_prefix in result.stdout base58_addr = str(result.stdout).replace(expected_multiaddr_prefix, "") base58.b58decode(base58_addr) @method_scope class TestGetMultiAddressCommandConnectionNegative(AEATestCaseEmpty): """Test case for CLI get-multiaddress command with --connection flag.""" def test_run(self, password_or_none): """Run the test.""" self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) password_options = _get_password_option_args(password_or_none) result = self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", *password_options, cwd=self.current_agent_context, ) assert result.exit_code == 0 # test we can decode the output base58.b58decode(result.stdout) @method_scope class TestGetMultiAddressCommandNegativeMissingKey(AEATestCaseEmpty): """Test case for CLI get-multiaddress when the key is missing.""" def test_run(self, password_or_none): """Run the test.""" # this will cause exception because no key is added to the AEA project. with pytest.raises( Exception, match="Cannot find '{}'. Please check private_key_path.".format( FetchAICrypto.identifier ), ): password_options = _get_password_option_args(password_or_none) self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativePeerId(AEATestCaseEmpty): """Test case for CLI get-multiaddress when the peer id computation raises an error.""" @mock.patch( "aea.cli.get_multiaddress.MultiAddr.__init__", side_effect=Exception("test error"), ) def test_run(self, _mock, password_or_none): """Run the test.""" self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=False, password=password_or_none ) # this will cause exception because no key is added to the AEA project. password_options = _get_password_option_args(password_or_none) with pytest.raises(Exception, match="test error"): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativeBadHostField(AEATestCaseEmpty): """Test case for CLI get-multiaddress when the host field is missing.""" def test_run(self, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) # this will cause exception because no host configuration is in stub connection by default. password_options = _get_password_option_args(password_or_none) with pytest.raises( Exception, match="Host field 'some_host' not present in connection configuration fetchai/stub:0.21.3", ): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--host-field", "some_host", "--port-field", "some_port", *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativeBadPortField(AEATestCaseEmpty): """Test case for CLI get-multiaddress when the port field is missing.""" def test_run(self, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) self.nested_set_config( "vendor.fetchai.connections.stub.config", {"host": "127.0.0.1"} ) # this will cause exception because no port configuration is in stub connection by default. password_options = _get_password_option_args(password_or_none) with pytest.raises( Exception, match="Port field 'some_port' not present in connection configuration fetchai/stub:0.21.3", ): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--host-field", "host", "--port-field", "some_port", *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativeBadConnectionId(AEATestCaseEmpty): """Test case for CLI get-multiaddress when the connection id is missing.""" def test_run(self, password_or_none): """Run the test.""" self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) # this will cause exception because a bad public id is provided. password_options = _get_password_option_args(password_or_none) connection_id = "some_author/some_connection:0.1.0" with pytest.raises( Exception, match=f"Cannot find connection with the public id {connection_id}", ): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", connection_id, *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativeFullMultiaddrComputation(AEATestCaseEmpty): """Test case for CLI get-multiaddress when an error occurs in the computation of the full multiaddr.""" @mock.patch( "aea.cli.get_multiaddress.MultiAddr.__init__", side_effect=Exception("test error"), ) def test_run(self, _mock, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) self.nested_set_config( "vendor.fetchai.connections.stub.config", {"host": "127.0.0.1", "port": 10000}, ) # this will cause exception due to the mocking. password_options = _get_password_option_args(password_or_none) with pytest.raises( Exception, match="An error occurred while creating the multiaddress: test error", ): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--host-field", "host", "--port-field", "port", *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativeOnlyHostSpecified(AEATestCaseEmpty): """Test case for CLI get-multiaddress when only the host field is specified.""" def test_run(self, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) # this will cause exception because only the host, and not the port, are specified. password_options = _get_password_option_args(password_or_none) with pytest.raises( Exception, match="-h/--host-field and -p/--port-field must be specified together.", ): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--host-field", "some_host", *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativeUriNotExisting(AEATestCaseEmpty): """Test case for CLI get-multiaddress when the URI field doesn't exists.""" def test_run(self, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) # this will cause exception because only the host, and not the port, are specified. password_options = _get_password_option_args(password_or_none) with pytest.raises( Exception, match="URI field 'some_uri' not present in connection configuration fetchai/stub:0.21.3", ): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--uri-field", "some_uri", *password_options, cwd=self.current_agent_context, ) @method_scope class TestGetMultiAddressCommandNegativeBadUri(AEATestCaseEmpty): """Test case for CLI get-multiaddress when we cannot parse the URI field.""" def test_run(self, password_or_none): """Run the test.""" self.add_item("connection", str(STUB_CONNECTION_PUBLIC_ID)) self.generate_private_key(FetchAICrypto.identifier, password=password_or_none) self.add_private_key( FetchAICrypto.identifier, connection=True, password=password_or_none ) self.nested_set_config( "vendor.fetchai.connections.stub.config", {"some_uri": "some-unparsable_URI"}, ) # this will cause exception because only the host, and not the port, are specified. password_options = _get_password_option_args(password_or_none) with pytest.raises( Exception, match=r"Cannot extract host and port from some_uri: 'some-unparsable_URI'. Reason: URI Doesn't match regex '", ): self.run_cli_command( "get-multiaddress", FetchAICrypto.identifier, "--connection", "--connection-id", str(STUB_CONNECTION_PUBLIC_ID), "--uri-field", "some_uri", *password_options, cwd=self.current_agent_context, ) ================================================ FILE: tests/test_aea/test_cli/test_get_public_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.get_public_key module.""" from unittest import TestCase, mock from unittest.mock import MagicMock from aea_ledger_fetchai import FetchAICrypto from aea.cli import cli from aea.cli.get_public_key import _try_get_public_key from tests.conftest import CLI_LOG_OPTION, COSMOS_ADDRESS_ONE, CliRunner from tests.test_aea.test_cli.tools_for_testing import ContextMock class GetPublicKeyTestCase(TestCase): """Test case for _get_public_key method.""" @mock.patch( "aea.cli.get_public_key.get_wallet_from_context", return_value=MagicMock(public_keys={"cosmos": COSMOS_ADDRESS_ONE}), ) def test__get_address_positive(self, *mocks): """Test for _get_public_key method positive result.""" ctx = ContextMock() _try_get_public_key(ctx, "cosmos") @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") @mock.patch("aea.cli.utils.package_utils.verify_private_keys_ctx") @mock.patch("aea.cli.get_public_key._try_get_public_key") @mock.patch("aea.cli.get_public_key.click.echo") class GetPublicKeyCommandTestCase(TestCase): """Test case for CLI get_public_key command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_run_positive(self, *mocks): """Test for CLI get_public_key positive result.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "get-public-key", FetchAICrypto.identifier, ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) ================================================ FILE: tests/test_aea/test_cli/test_get_wealth.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.generate_wealth module.""" from unittest import TestCase, mock from aea_ledger_fetchai import FetchAICrypto from aea.cli import cli from aea.cli.get_wealth import _try_get_wealth from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import CLI_LOG_OPTION, CliRunner, method_scope from tests.test_aea.test_cli.tools_for_testing import ContextMock class GetWealthTestCase(TestCase): """Test case for _get_wealth method.""" @mock.patch("aea.cli.utils.package_utils.Wallet") @mock.patch("aea.cli.utils.package_utils.verify_private_keys_ctx") @mock.patch("aea.cli.get_wealth.try_get_balance") def test__get_wealth_positive(self, *mocks): """Test for _get_wealth method positive result.""" ctx = ContextMock() _try_get_wealth(ctx, "type") @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") @mock.patch("aea.cli.utils.package_utils.verify_private_keys_ctx") @mock.patch("aea.cli.get_wealth._try_get_wealth") @mock.patch("aea.cli.get_wealth.click.echo") class GetWealthCommandTestCase(TestCase): """Test case for CLI get_wealth command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_run_positive(self, *mocks): """Test for CLI get_wealth positive result.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "get-wealth", FetchAICrypto.identifier, ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) @method_scope class TestGetWealth(AEATestCaseEmpty): """Test 'get-wealth' command.""" @mock.patch("click.echo") def test_get_wealth(self, _echo_mock, password_or_none): """Run the main test.""" self.generate_private_key(password=password_or_none) self.add_private_key(password=password_or_none) self.get_wealth(password=password_or_none) expected_wealth = 0 _echo_mock.assert_called_with(expected_wealth) ================================================ FILE: tests/test_aea/test_cli/test_init.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea init` sub-command.""" import os import shutil import tempfile from pathlib import Path from unittest.mock import patch import yaml from aea.cli import cli from tests.conftest import CLI_LOG_OPTION, CliRunner, random_string class TestDoInit: """Test that the command 'aea init'.""" def setup(self): """Set the test up.""" self.runner = CliRunner() self.agent_name = "myagent" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() self.agent_folder = Path(self.t, self.agent_name) os.chdir(self.t) self.cli_config_file = f"{self.t}/cli_config.yaml" self.cli_config_patch = patch( "aea.cli.utils.config.CLI_CONFIG_PATH", self.cli_config_file ) self.cli_config_patch.start() def test_author_local(self): """Test author set localy.""" author = "test_author" assert not os.path.exists(self.cli_config_file) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", author], ) assert result.exit_code == 0 assert "AEA configurations successfully initialized" in result.output assert self._read_config()["author"] == "test_author" def _read_config(self) -> dict: """Read cli config file. :return: dict """ with open(self.cli_config_file, "r") as f: data = yaml.safe_load(f) return data def test_already_registered(self): """Test author already registered.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", "author"], ) assert result.exit_code == 0 result = self.runner.invoke(cli, [*CLI_LOG_OPTION, "init"]) assert "AEA configurations already initialized" in result.output @patch("aea.cli.register.register_new_account", return_value="TOKEN") def test_non_local(self, mock): """Test registration online.""" email = f"{random_string()}@{random_string()}.com" pwd = random_string() author = "test_author" + random_string() result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--register", "--author", author], input=f"n\n{email}\n{pwd}\n{pwd}\n\n", ) assert result.exit_code == 0, result.output assert "Successfully registered" in result.output config = self._read_config() assert config["author"] == author assert config["auth_token"] == "TOKEN" @patch("aea.cli.init.do_login", return_value=None) def test_registered(self, *mocks): """Test author already registered.""" author = "test_author" + random_string() result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--register", "--author", author], input="y\nsome fake password\n", ) assert result.exit_code == 0 @patch("aea.cli.init.is_auth_token_present", return_value=True) @patch("aea.cli.init.check_is_author_logged_in", return_value=True) def test_already_logged_in(self, *mocks): """Registered and logged in (has token).""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--register", "--author", "test_author"], ) assert result.exit_code == 0 def teardown(self): """Tear the test down.""" self.cli_config_patch.stop() os.chdir(self.cwd) shutil.rmtree(self.t) ================================================ FILE: tests/test_aea/test_cli/test_install.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea install` sub-command.""" from pathlib import Path from typing import Dict import pytest import yaml from click import ClickException from aea.configurations.base import DEFAULT_PROTOCOL_CONFIG_FILE from aea.configurations.constants import DEFAULT_CONNECTION_CONFIG_FILE from aea.test_tools.test_cases import AEATestCase, AEATestCaseEmpty from tests.conftest import CUR_PATH class TestInstall(AEATestCase): """Test that the command 'aea install' works as expected.""" path_to_aea: Path = Path(CUR_PATH, "data", "dummy_aea") @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() cls.result = cls.run_cli_command("install", cwd=cls._get_cwd()) def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 class TestInstallFromRequirementFile(AEATestCase): """Test that the command 'aea install --requirement REQ_FILE' works.""" path_to_aea: Path = Path(CUR_PATH, "data", "dummy_aea") @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() cls.result = cls.run_cli_command( "install", "-r", "requirements.txt", cwd=cls._get_cwd() ) def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 class TestInstallFailsWhenDependencyDoesNotExist(AEATestCaseEmpty): """Test that the command 'aea install' fails when a dependency is not found.""" capture_log = True @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() result = cls.run_cli_command( "scaffold", "protocol", "-y", "my_protocol", cwd=cls._get_cwd() ) assert result.exit_code == 0 config_path = ( Path(cls._get_cwd()) / "protocols" / "my_protocol" / DEFAULT_PROTOCOL_CONFIG_FILE ) with config_path.open() as fp: config = yaml.safe_load(fp) config.setdefault("dependencies", {}).update( { "this_is_a_test_dependency": { "version": "==0.1.0", "index": "https://test.pypi.org/simple", }, } ) with config_path.open(mode="w") as fp: yaml.safe_dump(config, fp) def test_error(self): """Assert an error occurs.""" with pytest.raises( ClickException, match="An error occurred while installing.*this_is_a_test_dependency.*", ): self.run_cli_command("install", cwd=self._get_cwd()) class TestInstallWithRequirementFailsWhenFileIsBad(AEATestCase): """Test that the command 'aea install -r REQ_FILE' fails if the requirement file is not good.""" path_to_aea: Path = Path(CUR_PATH, "data", "dummy_aea") def test_error(self): """Test that an error occurs.""" with pytest.raises( ClickException, match="An error occurred while installing requirement file bad_requirements.txt. Stopping...", ): self.run_cli_command( "install", "-r", "bad_requirements.txt", cwd=self._get_cwd() ) class TestInstallFailsWhenDependencyHasUnsatisfiableSpecifier(AEATestCaseEmpty): """Test that the command 'aea install' fails when a dependency has an unsatisfiable version specifier.""" @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() result = cls.run_cli_command( "scaffold", "connection", "my_connection_1", cwd=cls._get_cwd() ) assert result.exit_code == 0 result = cls.run_cli_command( "scaffold", "connection", "my_connection_2", cwd=cls._get_cwd() ) assert result.exit_code == 0 config_path_1 = ( Path(cls._get_cwd()) / "connections" / "my_connection_1" / DEFAULT_CONNECTION_CONFIG_FILE ) cls._write_dependencies( {"this_is_a_test_dependency": {"version": "==0.1.0"}}, config_path_1 ) config_path_2 = ( Path(cls._get_cwd()) / "connections" / "my_connection_2" / DEFAULT_CONNECTION_CONFIG_FILE ) cls._write_dependencies( {"this_is_a_test_dependency": {"version": "==0.2.0"}}, config_path_2 ) @classmethod def _write_dependencies(cls, dependency_dict: Dict, path_to_config: Path): """Write a dependency to a configuration file.""" with path_to_config.open() as fp: config = yaml.safe_load(fp) config.setdefault("dependencies", {}).update(dependency_dict) with path_to_config.open(mode="w") as fp: yaml.safe_dump(config, fp) def test_error(self): """Assert an error occurs.""" with pytest.raises( ClickException, match="cannot install the following dependencies as the joint version specifier is unsatisfiable:\n - this_is_a_test_dependency: ==0.1.0,==0.2.0", ): self.run_cli_command("install", cwd=self._get_cwd()) ================================================ FILE: tests/test_aea/test_cli/test_interact.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for iteract command.""" from unittest import TestCase, mock import pytest from aea.cli.interact import ( _construct_message, _process_envelopes, _try_construct_envelope, ) from aea.helpers.base import send_control_c from aea.mail.base import Envelope from aea.test_tools.test_cases import AEATestCaseEmptyFlaky, AEATestCaseManyFlaky from packages.fetchai.connections.stub.connection import PUBLIC_ID as STUB_CONNECTION_ID from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_SKILL_PUBLIC_ID from tests.conftest import MAX_FLAKY_RERUNS class TestInteractCommand(AEATestCaseManyFlaky): """Test that interact command work.""" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_interact_command_positive(self): """Run interaction.""" agent_name = "test_iteraction_agent" self.create_agents(agent_name) # prepare agent self.set_agent_context(agent_name) self.generate_private_key() self.add_private_key() self.add_item("connection", str(STUB_CONNECTION_ID)) self.run_install() agent_process = self.run_agent() interaction_process = self.run_interaction() check_strings = ("Starting AEA interaction channel...",) missing_strings = self.missing_from_output( interaction_process, check_strings, is_terminating=False ) assert missing_strings == [], "Strings {} didn't appear in output.".format( missing_strings ) self.terminate_agents(interaction_process) self.terminate_agents(agent_process) assert ( self.is_successfully_terminated() ), "Agent {} wasn't successfully terminated.".format(agent_name) class ConstructMessageTestCase(TestCase): """Test case for _construct_message method.""" def test__construct_message_positive(self, *mocks): """Test _construct_message method for positive result.""" envelope = mock.Mock() envelope.to = "receiver" envelope.sender = "sender" envelope.protocol_specification_id = "protocol-id" envelope.message = "Message" message_class = DefaultMessage with mock.patch.object( message_class.serializer, "decode", return_value="Decoded message" ): result = _construct_message("action", envelope, message_class) expected_result = ( "\nAction envelope:" "\nto: receiver" "\nsender: sender" "\nprotocol_specification_id: protocol-id" "\nmessage: Message\n" ) self.assertEqual(result, expected_result) envelope.message = b"Encoded message" result = _construct_message("action", envelope, message_class) expected_result = ( "\nAction envelope:" "\nto: receiver" "\nsender: sender" "\nprotocol_specification_id: protocol-id" "\nmessage: Decoded message\n" ) self.assertEqual(result, expected_result) def _raise_keyboard_interrupt(): raise KeyboardInterrupt() def _raise_exception(): raise Exception() class TryConstructEnvelopeTestCase(TestCase): """Test case for _try_construct_envelope method.""" @mock.patch("builtins.input", return_value="Inputed value") def test__try_construct_envelope_positive(self, *mocks): """Test _try_construct_envelope for positive result.""" dialogues_mock = mock.Mock() msg_mock = mock.Mock(spec=DefaultMessage) msg_mock.to = "to" msg_mock.sender = "sender" dialogues_mock.create.return_value = msg_mock, None message_class = mock.Mock() envelope = _try_construct_envelope("agent_name", dialogues_mock, message_class) self.assertIsInstance(envelope, Envelope) @mock.patch("builtins.input", return_value="") def test__try_construct_envelope_positive_no_input_message(self, *mocks): """Test _try_construct_envelope for no input message result.""" envelope = _try_construct_envelope("agent_name", "dialogues", "message_class") self.assertEqual(envelope, None) @mock.patch("builtins.input", _raise_keyboard_interrupt) def test__try_construct_envelope_keyboard_interrupt(self, *mocks): """Test _try_construct_envelope for keyboard interrupt result.""" with self.assertRaises(KeyboardInterrupt): _try_construct_envelope("agent_name", "dialogues", mock.Mock()) @mock.patch("builtins.input", _raise_exception) def test__try_construct_envelope_exception_raised(self, *mocks): """Test _try_construct_envelope for exception raised result.""" envelope = _try_construct_envelope("agent_name", "dialogues", "message_class") self.assertEqual(envelope, None) class ProcessEnvelopesTestCase(TestCase): """Test case for _process_envelopes method.""" @mock.patch("aea.cli.interact.click.echo") @mock.patch("aea.cli.interact._construct_message") @mock.patch("aea.cli.interact._try_construct_envelope") def test__process_envelopes_positive( self, try_construct_envelope_mock, construct_message_mock, click_echo_mock ): """Test _process_envelopes method for positive result.""" agent_name = "agent_name" identity_stub = mock.Mock() identity_stub.name = "identity_stub_name" inbox = mock.Mock() inbox.empty = lambda: False inbox.get_nowait = lambda: "Not None" outbox = mock.Mock() dialogues = mock.Mock() message_class = mock.Mock() try_construct_envelope_mock.return_value = None constructed_message = "Constructed message" construct_message_mock.return_value = constructed_message # no envelope and inbox not empty behaviour _process_envelopes(agent_name, inbox, outbox, dialogues, message_class) click_echo_mock.assert_called_once_with(constructed_message) # no envelope and inbox empty behaviour inbox.empty = lambda: True _process_envelopes(agent_name, inbox, outbox, dialogues, message_class) click_echo_mock.assert_called_with("Received no new envelope!") # present envelope behaviour try_construct_envelope_mock.return_value = "Not None envelope" outbox.put = mock.Mock() _process_envelopes(agent_name, inbox, outbox, dialogues, message_class) outbox.put.assert_called_once_with("Not None envelope") click_echo_mock.assert_called_with(constructed_message) @mock.patch("aea.cli.interact._try_construct_envelope", return_value=None) def test__process_envelopes_couldnt_recover(self, *mocks): """Test _process_envelopes for couldn't recover envelope result.""" agent_name = "agent_name" identity_stub = mock.Mock() identity_stub.name = "identity_stub_name" inbox = mock.Mock() inbox.empty = lambda: False inbox.get_nowait = lambda: None outbox = mock.Mock() dialogues = mock.Mock() message_class = mock.Mock() with self.assertRaises(ValueError): _process_envelopes(agent_name, inbox, outbox, dialogues, message_class) class TestInteractEcho(AEATestCaseEmptyFlaky): """Test 'aea interact' with the echo skill.""" @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) # can be flaky on Windows def test_interact(self): """Test the 'aea interact' command with the echo skill.""" self.generate_private_key() self.add_private_key() self.add_item("connection", str(STUB_CONNECTION_ID)) self.add_item("skill", str(ECHO_SKILL_PUBLIC_ID)) self.run_agent() process = self.run_interaction() assert not self.missing_from_output( process, [ "Starting AEA interaction channel...", f"Provide message of protocol '{str(DefaultMessage.protocol_id)}' for performative bytes", ], timeout=10, is_terminating=False, ) # send first message process.stdin.write(b"hello\n") process.stdin.flush() assert not self.missing_from_output( process, [ "Sending envelope:", f"to: {self.agent_name}", f"sender: {self.agent_name}_interact", f"protocol_specification_id: {str(DefaultMessage.protocol_specification_id)}", "message_id=1", "target=0", "performative=bytes", "content=b'hello'", f"Provide message of protocol '{str(DefaultMessage.protocol_id)}' for performative bytes:", ], timeout=10, is_terminating=False, ) # read incoming messages process.stdin.write(b"\n") process.stdin.flush() assert not self.missing_from_output( process, [ "Interrupting input, checking inbox ...", "Received envelope:", f"to: {self.agent_name}_interact", f"sender: {self.agent_name}", f"protocol_specification_id: {str(DefaultMessage.protocol_specification_id)}", "message_id=-1", "target=1", "performative=bytes", "content=b'hello'", f"Provide message of protocol '{str(DefaultMessage.protocol_id)}' for performative bytes:", ], timeout=10, is_terminating=False, ) # read another message - should return nothing process.stdin.write(b"\n") process.stdin.flush() assert not self.missing_from_output( process, [ "Interrupting input, checking inbox ...", "Received no new envelope!", f"Provide message of protocol '{str(DefaultMessage.protocol_id)}' for performative bytes:", ], timeout=10, is_terminating=False, ) send_control_c(process) assert not self.missing_from_output( process, ["Interaction interrupted!"], timeout=10, is_terminating=False ) ================================================ FILE: tests/test_aea/test_cli/test_issue_certificates.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for 'issue-certificates' command.""" import json import os import shutil from pathlib import Path from typing import List import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.cli.utils.config import dump_item_config from aea.helpers.base import CertRequest from aea.test_tools.test_cases import AEATestCaseEmpty, _get_password_option_args from tests.conftest import CUR_PATH, ETHEREUM_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE from tests.data.dummy_connection.connection import DummyConnection class BaseTestIssueCertificates(AEATestCaseEmpty): """Base test class for 'aea issue-certificates' tests.""" @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() # add dummy connection shutil.copytree( os.path.join(CUR_PATH, "data", "dummy_connection"), os.path.join(cls.current_agent_context, "connections", "dummy"), ) agent_config = cls.load_agent_config(cls.agent_name) agent_config.author = FetchAICrypto.identifier agent_config.connections.add(DummyConnection.connection_id) dump_item_config(agent_config, Path(cls.current_agent_context)) @classmethod def add_cert_requests(cls, cert_requests: List[CertRequest], connection_name: str): """Add certificate requests to a target connection.""" cls.nested_set_config( f"connections.{connection_name}.cert_requests", cert_requests ) class TestIssueCertificatesPositive(BaseTestIssueCertificates): """Test 'issue-certificates', positive case.""" @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() cls.expected_path_1 = os.path.abspath("path_1") cls.expected_path_2 = os.path.abspath("path_2") cls.cert_id_1 = "cert_id_1" cls.cert_id_2 = "cert_id_2" cls.cert_request_1 = CertRequest( identifier=cls.cert_id_1, ledger_id=FetchAICrypto.identifier, not_after="2020-01-02", not_before="2020-01-01", public_key=FetchAICrypto.identifier, message_format="{public_key}", save_path=cls.expected_path_1, ) cls.cert_request_2 = CertRequest( identifier=cls.cert_id_2, ledger_id=FetchAICrypto.identifier, not_after="2020-01-02", not_before="2020-01-01", public_key="0xABCDEF123456", message_format="{public_key}", save_path=cls.expected_path_2, ) cls.add_cert_requests( [cls.cert_request_1, cls.cert_request_2], DummyConnection.connection_id.name ) def test_issue_certificate(self, password_or_none): """Test 'aea issue-certificates' in case of success.""" # setup: add private key with password self.generate_private_key(password=password_or_none) self.add_private_key(password=password_or_none) self.add_private_key(connection=True, password=password_or_none) # issue certificates and check password_options = _get_password_option_args(password_or_none) result = self.run_cli_command( "issue-certificates", *password_options, cwd=self._get_cwd() ) self._check_signature(self.cert_id_1, self.expected_path_1, result.stdout) self._check_signature(self.cert_id_2, self.expected_path_2, result.stdout) # teardown: remove private key Path(self._get_cwd(), FETCHAI_PRIVATE_KEY_FILE).unlink() self.remove_private_key() self.remove_private_key(connection=True) def _check_signature(self, cert_id, filename, stdout): """Check signature has been generated correctly.""" path = Path(self.current_agent_context, filename) assert path.exists() signature = path.read_text() def is_ascii(s): """Check isascii method for all Python 3 versions""" return all(ord(c) < 128 for c in s) assert is_ascii(signature) int(signature, 16) # this will fail if not hexadecimal cert_msg_1 = ( f"Issuing certificate '{cert_id}' for connection fetchai/dummy:0.1.0..." ) cert_msg_3 = f"Generated signature: '{signature}'" cert_msg_2 = f"Dumped certificate '{cert_id}' in '{filename}' for connection fetchai/dummy:0.1.0." assert cert_msg_1 in stdout assert cert_msg_2 in stdout assert cert_msg_3 in stdout class TestIssueCertificatesWithOverride(TestIssueCertificatesPositive): """Test 'issue-certificates' with override configurations.""" @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() cls.cert_id_3 = "cert_id_3" cls.cert_id_4 = "cert_id_4" cls.expected_path_3 = os.path.abspath("path_3") cls.expected_path_4 = os.path.abspath("path_4") cls.cert_request_3 = CertRequest( identifier=cls.cert_id_3, ledger_id=EthereumCrypto.identifier, not_after="2020-01-02", not_before="2020-01-01", public_key=EthereumCrypto.identifier, message_format="{public_key}", save_path=cls.expected_path_3, ) cls.cert_request_4 = CertRequest( identifier=cls.cert_id_4, ledger_id=EthereumCrypto.identifier, not_after="2020-01-02", not_before="2020-01-01", public_key="0xABCDEF123456", message_format="{public_key}", save_path=cls.expected_path_4, ) # Add override configurations dotted_path = f"connections.{DummyConnection.connection_id.name}.cert_requests" json_3 = json.dumps(cls.cert_request_3.json).replace("'", '"') json_4 = json.dumps(cls.cert_request_4.json).replace("'", '"') new_cert_requests = f"[{json_3}, {json_4}]" cls.set_config(dotted_path, new_cert_requests, type_="list") def test_issue_certificate(self, password_or_none): """Test 'aea issue-certificates' in case of success.""" # setup: add private key with password ledger_id = EthereumCrypto.identifier self.generate_private_key( ledger_id, ETHEREUM_PRIVATE_KEY_FILE, password=password_or_none ) self.add_private_key( ledger_id, ETHEREUM_PRIVATE_KEY_FILE, password=password_or_none ) self.add_private_key( ledger_id, ETHEREUM_PRIVATE_KEY_FILE, connection=True, password=password_or_none, ) password_options = _get_password_option_args(password_or_none) result = self.run_cli_command( "issue-certificates", *password_options, cwd=self._get_cwd() ) self._check_signature(self.cert_id_3, self.expected_path_3, result.stdout) self._check_signature(self.cert_id_4, self.expected_path_4, result.stdout) # teardown: remove private key Path(self._get_cwd(), ETHEREUM_PRIVATE_KEY_FILE).unlink() self.remove_private_key(ledger_id) self.remove_private_key(ledger_id, connection=True) class TestIssueCertificatesWrongConnectionKey(BaseTestIssueCertificates): """Test 'aea issue-certificates' when a bad connection key id is provided.""" @classmethod def setup_class(cls): """Set up class.""" super().setup_class() cls.cert_id_1 = "cert_id_1" cls.cert_request_1 = CertRequest( identifier=cls.cert_id_1, ledger_id=FetchAICrypto.identifier, not_after="2020-01-02", not_before="2020-01-01", public_key="bad_ledger_id", message_format="{public_key}", save_path="path", ) cls.add_cert_requests([cls.cert_request_1], DummyConnection.connection_id.name) def test_run(self): """Run the test.""" with pytest.raises( Exception, match="Cannot find connection private key with id 'bad_ledger_id'", ): self.run_cli_command("issue-certificates", cwd=self._get_cwd()) class TestIssueCertificatesWrongCryptoKey(BaseTestIssueCertificates): """Test 'aea issue-certificates' when a bad crypto key id is provided.""" @classmethod def setup_class(cls): """Set up class.""" super().setup_class() cls.cert_id_1 = "cert_id_1" cls.cert_request_1 = CertRequest( identifier=cls.cert_id_1, ledger_id="bad_ledger_id", not_after="2020-01-02", not_before="2020-01-01", public_key=FetchAICrypto.identifier, message_format="{public_key}", save_path="path", ) cls.add_cert_requests([cls.cert_request_1], DummyConnection.connection_id.name) # add fetchai key and connection key cls.generate_private_key() cls.add_private_key() cls.add_private_key(connection=True) def test_run(self): """Run the test.""" with pytest.raises( Exception, match="Cannot find private key with id 'bad_ledger_id'", ): self.run_cli_command("issue-certificates", cwd=self._get_cwd()) ================================================ FILE: tests/test_aea/test_cli/test_launch.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea launch` sub-command.""" import logging import os import shutil import sys import tempfile import unittest from contextlib import contextmanager from pathlib import Path from typing import Generator, List, Optional import pytest import yaml from aea_ledger_fetchai import FetchAICrypto from pexpect.exceptions import EOF # type: ignore from aea.cli import cli from aea.configurations.base import DEFAULT_AEA_CONFIG_FILE from tests.common.pexpect_popen import PexpectWrapper from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner, MAX_FLAKY_RERUNS, MAX_FLAKY_RERUNS_ETH, ROOT_DIR, ) logger = logging.getLogger(__name__) DEFAULT_EXPECT_TIMEOUT = 40 class BaseLaunchTestCase: """Base Test case for launch tests.""" PASSWORD: Optional[str] = None # nosec @contextmanager def _cli_launch( self, agents: List[str], options: Optional[List[str]] = None ) -> Generator: """ Run aea.cli wrapped with Pexpect. :param agents: list of agent names to run :param options: list of string options to pass to aea launch. :return: PexpectWrapper """ password_options = self.get_password_args(self.PASSWORD) proc = PexpectWrapper( # nosec [ sys.executable, "-m", "aea.cli", "-v", "DEBUG", "launch", *password_options, *(options or []), *(agents or []), ], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) try: yield proc finally: proc.wait_to_complete(10) @classmethod def setup_class(cls): """Set the test up.""" if cls is BaseLaunchTestCase: raise unittest.SkipTest("Skip BaseTest tests, it's a base class") method_list = [ func for func in dir(cls) if callable(getattr(cls, func)) and not func.startswith("__") and func.startswith("test_") ] if len(method_list) > 1: raise ValueError(f"{cls.__name__} can only contain one test method!") cls.runner = CliRunner() cls.agent_name_1 = "myagent_1" cls.agent_name_2 = "myagent_2" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) password_option = cls.get_password_args(cls.PASSWORD) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name_1] ) assert result.exit_code == 0 os.chdir(cls.agent_name_1) result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier, *password_option, ], ) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier, *password_option], ) assert result.exit_code == 0 os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name_2] ) assert result.exit_code == 0 os.chdir(cls.agent_name_2) result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier, *password_option, ], ) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier, *password_option], ) assert result.exit_code == 0 os.chdir(cls.t) @classmethod def get_password_args(cls, password: Optional[str]) -> List[str]: """Get password arguments.""" return [] if password is None else ["--password", password] @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestLaunch(BaseLaunchTestCase): """Test that the command 'aea launch ' works as expected.""" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_ETH) def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" with self._cli_launch([self.agent_name_1, self.agent_name_2]) as process_launch: process_launch.expect_all( [ f"[{self.agent_name_1}] Start processing messages...", f"[{self.agent_name_2}] Start processing messages...", ], timeout=DEFAULT_EXPECT_TIMEOUT, ) process_launch.control_c() process_launch.expect_all( ["Exit cli. code: 0"], timeout=DEFAULT_EXPECT_TIMEOUT, ) class TestLaunchWithPassword(TestLaunch): """Test that the command 'aea launch --password ' works as expected.""" PASSWORD = "fake-password" # nosec class TestLaunchWithOneFailingAgent(BaseLaunchTestCase): """Test aea launch when there is a failing agent..""" @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() # add the exception skill to agent 2 os.chdir(cls.agent_name_2) shutil.copytree( Path(CUR_PATH, "data", "exception_skill"), Path(cls.t, cls.agent_name_2, "skills", "exception"), ) config_path = Path(cls.t, cls.agent_name_2, DEFAULT_AEA_CONFIG_FILE) config = yaml.safe_load(open(config_path)) config.setdefault("skills", []).append("fetchai/exception:0.1.0") yaml.safe_dump(config, open(config_path, "w")) os.chdir(cls.t) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_exit_code_equal_to_one(self): """Assert that the exit code is equal to one (i.e. generic failure).""" with self._cli_launch([self.agent_name_1, self.agent_name_2]) as process_launch: process_launch.expect_all( [ f"[{self.agent_name_1}] Start processing messages...", "Expected exception!", "Receiving loop terminated", # cause race condition in close/interrupt agent 2, so wait it closed by exception before call ctrl+c ], timeout=DEFAULT_EXPECT_TIMEOUT, ) process_launch.control_c() process_launch.expect_all( [ f"Agent {self.agent_name_1} terminated with exit code 0", f"Agent {self.agent_name_2} terminated with exit code ", ], timeout=DEFAULT_EXPECT_TIMEOUT, ) process_launch.expect( EOF, timeout=DEFAULT_EXPECT_TIMEOUT, ) process_launch.wait_to_complete(10) assert process_launch.returncode == 1 class TestLaunchWithWrongArguments(BaseLaunchTestCase): """Test aea launch when some agent directory does not exist.""" @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() cls.temp_agent = tempfile.mkdtemp() os.chdir(cls.temp_agent) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "launch", "this_agent_does_not_exist"], standalone_mode=True, ) def test_exit_code_equal_to_one(self): """Assert that the exit code is equal to 1.""" assert self.result.exit_code == 1 @classmethod def teardown_class(cls): """Set the test up.""" os.chdir(cls.t) try: shutil.rmtree(cls.temp_agent) except (OSError, IOError): pass super().teardown_class() class TestLaunchMultithreaded(BaseLaunchTestCase): """Test that the command 'aea launch --multithreaded' works as expected.""" def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" with self._cli_launch( [self.agent_name_1, self.agent_name_2], ["--multithreaded"] ) as process_launch: process_launch.expect_all( [ f"[{self.agent_name_1}] Start processing messages", f"[{self.agent_name_2}] Start processing messages", ], timeout=DEFAULT_EXPECT_TIMEOUT, ) process_launch.control_c() process_launch.expect_all( ["Exit cli. code: 0"], timeout=DEFAULT_EXPECT_TIMEOUT, ) class TestLaunchOneAgent(BaseLaunchTestCase): """Test that the command 'aea launch ' works as expected.""" def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" with self._cli_launch([self.agent_name_1]) as process_launch: process_launch.expect_all( [f"[{self.agent_name_1}] Start processing messages..."], timeout=DEFAULT_EXPECT_TIMEOUT, ) process_launch.control_c() process_launch.expect_all( [f"Agent {self.agent_name_1} terminated with exit code 0"], timeout=DEFAULT_EXPECT_TIMEOUT, ) process_launch.wait_to_complete(10) assert process_launch.returncode == 0 ================================================ FILE: tests/test_aea/test_cli/test_launch_end_to_end.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea launch` sub-command.""" import json import os import sqlite3 import sys import uuid import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseMany from tests.common.pexpect_popen import PexpectWrapper from tests.conftest import FETCHAI_PRIVATE_KEY_FILE_CONNECTION class TestLaunchEndToEnd(AEATestCaseMany): """Perform aea launch end to end test.""" key = "seller_service" value = None registration_agent_connection = { "delegate_uri": "127.0.0.1:11011", "entry_peers": [], "ledger_id": "fetchai", "local_uri": "127.0.0.1:9011", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9011", } search_agent_connection = { "delegate_uri": "127.0.0.1:11012", "entry_peers": [], "ledger_id": "fetchai", "local_uri": "127.0.0.1:9012", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9012", } @pytest.mark.integration def test_end_to_end(self): """Perform end to end test with simple register/search agents.""" registration_agent_name = "registration_agent" self.value = uuid.uuid4().hex self.fetch_agent( "fetchai/simple_service_registration", agent_name=registration_agent_name, is_local=True, ) self.run_cli_command( "config", "set", "vendor.fetchai.connections.p2p_libp2p.config", "--type", "dict", json.dumps(self.registration_agent_connection), cwd=registration_agent_name, ) self.run_cli_command( "config", "set", "vendor.fetchai.skills.simple_service_registration.models.strategy.args.service_data", "--type", "dict", json.dumps({"key": self.key, "value": self.value}), cwd=registration_agent_name, ) self.run_cli_command( "config", "set", "vendor.fetchai.connections.soef.config.token_storage_path", os.path.join(self.t, registration_agent_name, "soef_key.txt"), cwd=registration_agent_name, ) storage_file_name = os.path.abspath( os.path.join(registration_agent_name, "test.db") ) self.run_cli_command( "config", "set", "agent.storage_uri", f"sqlite://{storage_file_name}", cwd=registration_agent_name, ) search_agent_name = "search_agent" self.fetch_agent( "fetchai/simple_service_search", agent_name=search_agent_name, is_local=True, ) self.run_cli_command( "config", "set", "vendor.fetchai.connections.p2p_libp2p.config", "--type", "dict", json.dumps(self.search_agent_connection), cwd=search_agent_name, ) self.run_cli_command( "config", "set", "vendor.fetchai.skills.simple_service_search.models.strategy.args.search_query", "--type", "dict", json.dumps( { "constraint_type": "==", "search_key": self.key, "search_value": self.value, } ), cwd=search_agent_name, ) self.run_cli_command( "config", "set", "vendor.fetchai.skills.simple_service_search.behaviours.service_search.args.tick_interval", "--type", "int", "2", cwd=search_agent_name, ) self.run_cli_command( "config", "set", "vendor.fetchai.connections.soef.config.token_storage_path", os.path.join(self.t, search_agent_name, "soef_key.txt"), cwd=search_agent_name, ) self.run_cli_command( "build", cwd=registration_agent_name, ) self.run_cli_command( "build", cwd=search_agent_name, ) self.set_agent_context(registration_agent_name) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.generate_private_key() self.add_private_key() self.unset_agent_context() self.run_cli_command( "issue-certificates", cwd=registration_agent_name, ) self.set_agent_context(search_agent_name) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.generate_private_key() self.add_private_key() self.unset_agent_context() self.run_cli_command( "issue-certificates", cwd=search_agent_name, ) proc = PexpectWrapper( # nosec [ sys.executable, "-m", "aea.cli", "-v", "DEBUG", "launch", registration_agent_name, search_agent_name, ], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) try: proc.expect_all( [f"[{search_agent_name}] found number of agents=1, search_response"], timeout=30, ) finally: proc.control_c() proc.expect("Exit cli. code: 0", timeout=30) assert os.path.exists(storage_file_name) con = sqlite3.connect(storage_file_name) try: cursor = con.cursor() tables = cursor.execute( "SELECT name FROM sqlite_master WHERE type='table';" ).fetchall() assert tables table_name = tables[0][0] num_of_records = cursor.execute( # nosec f"SELECT count(*) FROM {table_name};" ).fetchone()[0] assert num_of_records > 0 finally: con.close ================================================ FILE: tests/test_aea/test_cli/test_list.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea list` sub-command.""" import json import os import shutil import tempfile from pathlib import Path from unittest import TestCase, mock import jsonschema import pytest from jsonschema import Draft4Validator from aea.cli import cli from tests.conftest import ( AGENT_CONFIGURATION_SCHEMA, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CUR_PATH, CliRunner, MAX_FLAKY_RERUNS, ) from tests.test_aea.test_cli.constants import FORMAT_ITEMS_SAMPLE_OUTPUT @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) class TestListProtocols: """Test that the command 'aea list protocols' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( "file://{}/".format(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'dummy_aea' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(cls.t, "dummy_aea")) cls.runner = CliRunner() os.chdir(Path(cls.t, "dummy_aea")) with mock.patch( "aea.cli.list.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT ): cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "list", "protocols"], standalone_mode=False, ) def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 def test_correct_output(self): """Test that the command has printed the correct output.""" compare_text = "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) assert self.result.output == compare_text, self.result.output @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestListConnections: """Test that the command 'aea list connections' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( "file://{}/".format(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'dummy_aea' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(cls.t, "dummy_aea")) cls.runner = CliRunner() os.chdir(Path(cls.t, "dummy_aea")) with mock.patch( "aea.cli.list.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT ): cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "list", "connections"], standalone_mode=False ) def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 def test_correct_output(self): """Test that the command has printed the correct output.""" compare_text = "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) assert self.result.output == compare_text, self.result.output @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestListSkills: """Test that the command 'aea list skills' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( "file://{}/".format(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'dummy_aea' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(cls.t, "dummy_aea")) cls.runner = CliRunner() os.chdir(Path(cls.t, "dummy_aea")) with mock.patch( "aea.cli.list.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT ) as format_items_mock: cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "list", "skills"], standalone_mode=False ) format_items_mock.assert_called() def test_exit_code_equal_to_zero(self): """Assert that the exit code is equal to zero (i.e. success).""" assert self.result.exit_code == 0 def test_correct_output(self): """Test that the command has printed the correct output.""" compare_text = "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) assert self.result.output == compare_text, self.result.output @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class ListContractsCommandTestCase(TestCase): """Test that the command 'aea list contracts' works as expected.""" def setUp(self): """Set the test up.""" self.runner = CliRunner() self.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA)) self.resolver = jsonschema.RefResolver( "file://{}/".format(Path(CONFIGURATION_SCHEMA_DIR).absolute()), self.schema ) self.validator = Draft4Validator(self.schema, resolver=self.resolver) self.cwd = os.getcwd() self.t = tempfile.mkdtemp() # copy the 'dummy_aea' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(self.t, "dummy_aea")) os.chdir(Path(self.t, "dummy_aea")) @mock.patch("aea.cli.list.list_agent_items") @mock.patch("aea.cli.utils.formatting.format_items") def test_list_contracts_positive(self, *mocks): """Test list contracts command positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "list", "contracts"], standalone_mode=False ) self.assertEqual(result.exit_code, 0) def tearDown(self): """Tear the test down.""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass class ListAllCommandTestCase(TestCase): """Test case for aea list all command.""" def setUp(self): """Set the test up.""" self.runner = CliRunner() @mock.patch("aea.cli.list.list_agent_items", return_value=[]) @mock.patch("aea.cli.list.format_items") @mock.patch("aea.cli.utils.decorators._check_aea_project") def test_list_all_no_details_positive(self, *mocks): """Test list all command no details positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "list", "all"], standalone_mode=False ) self.assertEqual(result.exit_code, 0) self.assertEqual(result.output, "") @mock.patch("aea.cli.list.list_agent_items", return_value=[{"name": "some"}]) @mock.patch("aea.cli.list.format_items", return_value="correct") @mock.patch("aea.cli.utils.decorators._check_aea_project") def test_list_all_positive(self, *mocks): """Test list all command positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "list", "all"], standalone_mode=False ) self.assertEqual(result.exit_code, 0) self.assertEqual( result.output, "Connections:\ncorrect\nContracts:\ncorrect\n" "Protocols:\ncorrect\nSkills:\ncorrect\n", ) ================================================ FILE: tests/test_aea/test_cli/test_local_registry_update.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea local-registry-sync""" import os from tempfile import TemporaryDirectory from unittest.mock import patch from aea.cli.core import cli from aea.cli.local_registry_sync import enlist_packages from aea.cli.registry.add import fetch_package from aea.configurations.data_types import PackageId, PackageType, PublicId from aea.helpers.base import cd from aea.test_tools.click_testing import CliRunner def test_local_registry_update(): """Test local-registry-sync cli command.""" PACKAGES = [ PackageId(PackageType.CONNECTION, PublicId("fetchai", "local", "0.17.0")), PackageId(PackageType.AGENT, PublicId("fetchai", "my_first_aea", "0.24.0")), ] with TemporaryDirectory() as tmp_dir: for package_id in PACKAGES: package_dir = os.path.join( tmp_dir, package_id.public_id.author, str(package_id.package_type.to_plural()), package_id.public_id.name, ) os.makedirs(package_dir) fetch_package( str(package_id.package_type), public_id=package_id.public_id, cwd=tmp_dir, dest=package_dir, ) assert set(PACKAGES) == set([i[0] for i in enlist_packages(tmp_dir)]) runner = CliRunner() with cd(tmp_dir): # check intention to upgrade with patch( "aea.cli.local_registry_sync.replace_package" ) as replace_package_mock: result = runner.invoke( cli, ["-s", "local-registry-sync"], catch_exceptions=False ) assert result.exit_code == 0, result.stdout assert replace_package_mock.call_count == 2 # do actual upgrade result = runner.invoke( cli, ["-s", "local-registry-sync"], catch_exceptions=False ) assert result.exit_code == 0, result.stdout # check next update will do nothing with patch( "aea.cli.local_registry_sync.replace_package" ) as replace_package_mock: result = runner.invoke( cli, ["-s", "local-registry-sync"], catch_exceptions=False ) assert result.exit_code == 0, result.stdout assert replace_package_mock.call_count == 0 def sort_(packages): return sorted(packages, key=lambda x: str(x)) new_packages = [i[0] for i in enlist_packages(tmp_dir)] for new_package, old_package in zip(sort_(new_packages), sort_(PACKAGES)): assert new_package.public_id != old_package.public_id ================================================ FILE: tests/test_aea/test_cli/test_loggers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.utils.loggers module.""" from unittest import TestCase, mock from aea.cli.utils.loggers import ColorFormatter class ColorFormatterTestCase(TestCase): """Test case for ColorFormatter class.""" def test_format_positive(self): """Test for format method positive result.""" record = mock.Mock() record.exc_info = None record.levelname = "DEBUG" record.getMessage = mock.Mock(return_value="Message") color_formatter = ColorFormatter() color_formatter.format(record) ================================================ FILE: tests/test_aea/test_cli/test_login.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI login command.""" from unittest import TestCase, mock from aea.cli import cli from tests.conftest import CLI_LOG_OPTION, CliRunner @mock.patch("aea.cli.login.registry_login", return_value="token") @mock.patch("aea.cli.login.update_cli_config") class LoginTestCase(TestCase): """Test case for CLI login command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_login_positive(self, update_cli_config_mock, registry_login_mock): """Test for CLI login positive result.""" username, password = ("Username", "Password") result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "login", username, "--password={}".format(password)], standalone_mode=False, ) expected_output = ( "Signing in as Username...\n" "Successfully signed in: Username.\n" ) self.assertEqual(result.output, expected_output) registry_login_mock.assert_called_once_with(username, password) update_cli_config_mock.assert_called_once_with({"auth_token": "token"}) ================================================ FILE: tests/test_aea/test_cli/test_logout.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI logout command.""" from unittest import TestCase, mock from aea.cli import cli from tests.conftest import CLI_LOG_OPTION, CliRunner @mock.patch("aea.cli.logout.registry_logout") @mock.patch("aea.cli.logout.update_cli_config") class LogoutTestCase(TestCase): """Test case for CLI logout command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_logout_positive(self, update_cli_config_mock, registry_logout_mock): """Test for CLI logout positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "logout"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) registry_logout_mock.assert_called_once() update_cli_config_mock.assert_called_once() ================================================ FILE: tests/test_aea/test_cli/test_misc.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea` sub-commands.""" import aea from aea.cli import cli from tests.conftest import CliRunner def test_no_argument(): """Test that if we run the cli tool without arguments, it exits gracefully.""" runner = CliRunner() result = runner.invoke(cli, []) assert result.exit_code == 0 def test_flag_version(): """Test that the flag '--version' works correctly.""" runner = CliRunner() result = runner.invoke(cli, ["--version"]) assert result.stdout == "aea, version {}\n".format(aea.__version__) def test_flag_help(): """Test that the flag '--help' works correctly.""" runner = CliRunner() result = runner.invoke(cli, ["--help"]) assert ( result.stdout == """Usage: aea [OPTIONS] COMMAND [ARGS]... Command-line tool for setting up an Autonomous Economic Agent (AEA). Options: --version Show the version and exit. -v, --verbosity LVL One of NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL, OFF -s, --skip-consistency-check Skip consistency checks of agent during command execution. --registry-path DIRECTORY Provide a local registry directory full path. --help Show this message and exit. Commands: add Add a package to the agent. add-key Add a private key to the wallet of the agent. build Build the agent and its components. config Read or modify a configuration of the agent. create Create a new agent. delete Delete an agent. eject Eject a vendor package of the agent. fetch Fetch an agent from the registry. fingerprint Fingerprint a non-vendor package of the agent. freeze Get the dependencies of the agent. generate Generate a package for the agent. generate-key Generate a private key and place it in a file. generate-wealth Generate wealth for the agent on a test network. get-address Get the address associated with a private key of the... get-multiaddress Get the multiaddress associated with a private key... get-public-key Get the public key associated with a private key of... get-wealth Get the wealth associated with the private key of... init Initialize your AEA configurations. install Install the dependencies of the agent. interact Interact with the running agent via the stub... ipfs IPFS Commands issue-certificates Issue certificates for connections that require them. launch Launch many agents at the same time. list List the installed packages of the agent. local-registry-sync Upgrade the local package registry. login Login to the registry account. logout Logout from the registry account. publish Publish the agent to the registry. push Push a non-vendor package of the agent to the registry. register Create a new registry account. remove Remove a package from the agent. remove-key Remove a private key from the wallet of the agent. reset_password Reset the password of the registry account. run Run the agent. scaffold Scaffold a package for the agent. search Search for packages in the registry. transfer Transfer wealth associated with a private key of the... upgrade Upgrade the packages of the agent. """ ) ================================================ FILE: tests/test_aea/test_cli/test_plugin.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test the CLI plugin mechanism.""" import inspect from pathlib import Path import click import pytest from pkg_resources import Distribution, EntryPoint, iter_entry_points, working_set # Create a few CLI commands for testing from aea.cli.plugin import with_plugins from aea.test_tools.click_testing import CliRunner from tests.conftest import ROOT_DIR # We need to compute the right dotted path w.r.t. the current module location. # Instead of hard-coding it, we compute it on-the-fly. _PATH_TO_THIS_MODULE = ( Path(inspect.getfile(inspect.currentframe())) # type: ignore .absolute() .relative_to(Path(ROOT_DIR).resolve()) ) _DOTTED_PATH = ".".join(_PATH_TO_THIS_MODULE.with_suffix("").parts) @pytest.fixture(scope="function") def runner(request): """Get a click.CliRunner instance.""" return CliRunner() @click.command() @click.argument("arg") def cmd1(arg): """Test command 1""" click.echo("passed") @click.command() @click.argument("arg") def cmd2(arg): """Test command 2""" click.echo("passed") class DistStub(Distribution): """ Manually register plugins in an entry point and put broken plugins in a different entry point. The `DistStub()` class gets around an exception that is raised when `entry_point.load()` is called. By default `load()` has `requires=True` which calls `dist.requires()` and the `click.group()` decorator doesn't allow us to change this. Because we are manually registering these plugins the `dist` attribute is `None` so we can just create a stub that always returns an empty list since we don't have any requirements. A full `pkg_resources.Distribution()` instance is not needed because there isn't a package installed anywhere. """ def requires(self, *args): """Implement a dummy 'requires' function.""" return [] working_set.by_key["click"]._ep_map = { # type: ignore "_test_click_plugins.test_plugins": { "cmd1": EntryPoint.parse(f"cmd1={_DOTTED_PATH}:cmd1", dist=DistStub()), "cmd2": EntryPoint.parse(f"cmd2={_DOTTED_PATH}:cmd2", dist=DistStub()), }, "_test_click_plugins.broken_plugins": { "before": EntryPoint.parse( "before=tests.broken_plugins:before", dist=DistStub() ), "after": EntryPoint.parse("after=tests.broken_plugins:after", dist=DistStub()), "do_not_exist": EntryPoint.parse( "do_not_exist=tests.broken_plugins:do_not_exist", dist=DistStub() ), }, } # Main CLI groups - one with good plugins attached and the other broken @with_plugins(iter_entry_points("_test_click_plugins.test_plugins")) @click.group() def good_cli(): """Good CLI group.""" pass @with_plugins(iter_entry_points("_test_click_plugins.broken_plugins")) @click.group() def broken_cli(): """Broken CLI group.""" pass def test_registered(): """ Make sure the plugins are properly registered. If this test fails it means that some of the for loops in other tests may not be executing. """ assert len([ep for ep in iter_entry_points("_test_click_plugins.test_plugins")]) > 1 assert ( len([ep for ep in iter_entry_points("_test_click_plugins.broken_plugins")]) > 1 ) def test_register_and_run(runner): """Test that registration and run of the command work correctly.""" result = runner.invoke(good_cli) assert result.exit_code == 0 for ep in iter_entry_points("_test_click_plugins.test_plugins"): cmd_result = runner.invoke(good_cli, [ep.name, "something"]) assert cmd_result.exit_code == 0 assert cmd_result.output.strip() == "passed" def test_broken_register_and_run(runner): """Test that the broken plugin doesn't get registered as expected.""" result = runner.invoke(broken_cli) assert result.exit_code == 0 for ep in iter_entry_points("_test_click_plugins.broken_plugins"): cmd_result = runner.invoke(broken_cli, [ep.name]) assert cmd_result.exit_code != 0 assert "Traceback" in cmd_result.output def test_group_chain(runner): """Test the plugin with nested CLI command group levels.""" # Attach a sub-group to a CLI and get execute it without arguments to make # sure both the sub-group and all the parent group's commands are present @good_cli.group() def sub_cli(): """Sub CLI.""" pass result = runner.invoke(good_cli) assert result.exit_code == 0 assert sub_cli.name in result.output for ep in iter_entry_points("_test_click_plugins.test_plugins"): assert ep.name in result.output # Same as above but the sub-group has plugins @with_plugins(plugins=iter_entry_points("_test_click_plugins.test_plugins")) @good_cli.group(name="sub-cli-plugins") def sub_cli_plugins(): """Sub CLI with plugins.""" pass result = runner.invoke(good_cli, ["sub-cli-plugins"]) assert result.exit_code == 0 for ep in iter_entry_points("_test_click_plugins.test_plugins"): assert ep.name in result.output print(result.output) # Execute one of the sub-group's commands result = runner.invoke(good_cli, ["sub-cli-plugins", "cmd1", "something"]) assert result.exit_code == 0 assert result.output.strip() == "passed" def test_exception(): """Test the 'with_plugins' decorator when it gets used on a non-click.Group object.""" # Decorating something that isn't a click.Group() should fail with pytest.raises(TypeError): @with_plugins([]) @click.command() def cli(): """Whatever""" def test_broken_register_and_run_with_help(runner): """Test the broken registration of the plugin when the command is run with the '--help' flag.""" result = runner.invoke(broken_cli) assert result.exit_code == 0 for ep in iter_entry_points("_test_click_plugins.broken_plugins"): cmd_result = runner.invoke(broken_cli, [ep.name, "--help"]) assert cmd_result.exit_code != 0 assert "Traceback" in cmd_result.output def test_broken_register_and_run_with_args(runner): """Test the broken registration of the plugin when the command is run with the '--help' flag.""" result = runner.invoke(broken_cli) assert result.exit_code == 0 for ep in iter_entry_points("_test_click_plugins.broken_plugins"): cmd_result = runner.invoke(broken_cli, [ep.name, "-a", "b"]) assert cmd_result.exit_code != 0 assert "Traceback" in cmd_result.output ================================================ FILE: tests/test_aea/test_cli/test_publish.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test module for Registry publish methods.""" from pathlib import Path from shutil import rmtree from unittest import TestCase, mock import click import pytest from click import ClickException from aea.cli import cli from aea.cli.publish import ( LocalRegistry, MixedRegistry, RemoteRegistry, _save_agent_locally, _validate_pkp, ) from aea.configurations.base import PublicId from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_SKILL_PUBLIC_ID from tests.conftest import CLI_LOG_OPTION, CliRunner from tests.test_aea.test_cli.tools_for_testing import ( ContextMock, PublicIdMock, raise_click_exception, ) @mock.patch("aea.cli.publish.PublicId", PublicIdMock) @mock.patch("aea.cli.publish.LocalRegistry.check_item_present") @mock.patch("aea.cli.publish.copyfile") @mock.patch("aea.cli.publish.os.makedirs") @mock.patch("aea.cli.publish.os.path.exists", return_value=False) @mock.patch("aea.cli.publish.try_get_item_target_path", return_value="target-dir") @mock.patch("aea.cli.publish.os.path.join", return_value="joined-path") class SaveAgentLocallyTestCase(TestCase): """Test case for _save_agent_locally method.""" def test_save_agent_locally_positive( self, path_join_mock, try_get_item_target_path_mock, path_exists_mock, makedirs_mock, copyfile_mock, _check_is_item_in_local_registry_mock, ): """Test for save_agent_locally positive result.""" _save_agent_locally( ContextMock( connections=["author/default_connection:version", "author/name:version"] ) ) makedirs_mock.assert_called_once_with("target-dir", exist_ok=True) copyfile_mock.assert_called_once_with("joined-path", "joined-path") class CheckIsItemInLocalRegistryTestCase(TestCase): """Test case for _check_is_item_in_local_registry method.""" @mock.patch("aea.cli.publish.try_get_item_source_path") def test__check_is_item_in_local_registry_positive(self, get_path_mock): """Test for _check_is_item_in_local_registry positive result.""" public_id = PublicIdMock.from_str("author/name:version") item_type_plural = "items" ctx = mock.Mock() ctx.registry_path = "some-registry-path" LocalRegistry(ctx).check_item_present(item_type_plural, public_id) get_path_mock.assert_called_once_with( ctx.registry_path, public_id.author, item_type_plural, public_id.name ) @mock.patch("aea.cli.publish.try_get_item_source_path", raise_click_exception) def test__check_is_item_in_local_registry_negative(self): """Test for _check_is_item_in_local_registry negative result.""" public_id = PublicIdMock.from_str("author/name:version") item_type_plural = "items" with self.assertRaises(ClickException): ctx = mock.Mock() ctx.registry_path = "some-registry-path" LocalRegistry(ctx).check_item_present(item_type_plural, public_id) @mock.patch("aea.cli.utils.decorators._check_aea_project") @mock.patch("aea.cli.publish._save_agent_locally") @mock.patch("aea.cli.publish.publish_agent") @mock.patch("aea.cli.publish._validate_pkp") @mock.patch("aea.cli.publish._validate_config") @mock.patch("aea.cli.publish.cast", return_value=ContextMock()) class PublishCommandTestCase(TestCase): """Test case for CLI publish command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_publish_positive(self, *mocks): """Test for CLI publish positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "publish", "--local"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "publish", "--remote"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "publish"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) class ValidatePkpTestCase(TestCase): """Test case for _validate_pkp method.""" def test__validate_pkp_positive(self): """Test _validate_pkp for positive result.""" private_key_paths = mock.Mock() private_key_paths.read_all = mock.Mock(return_value=[]) _validate_pkp(private_key_paths) private_key_paths.read_all.assert_called_once() def test__validate_pkp_negative(self): """Test _validate_pkp for negative result.""" private_key_paths = mock.Mock() private_key_paths.read_all = mock.Mock(return_value=[1, 2]) with self.assertRaises(ClickException): _validate_pkp(private_key_paths) private_key_paths.read_all.assert_called_once() @mock.patch("aea.cli.publish.MixedRegistry.check_item_present") class TestPublishMixedMode(AEATestCaseEmpty): """Test the execution branch with in mixed mode.""" def test_publish_positive(self, *mocks): """Test for CLI publish positive result.""" self.set_config("agent.description", "some-description") self.run_cli_command("publish", cwd=self._get_cwd()) def test_negative_check_is_item_in_remote_registry(): """Test the utility function (negative) to check if an item is in the remote registry""" with pytest.raises(click.ClickException, match="Not found in Registry."): RemoteRegistry(mock.Mock()).check_item_present( "protocols", PublicId("nonexisting_package_author", "nonexisting_package_name", "0.0.0"), ) def test_negative_check_is_item_in_registry_mixed(): """Check if item in registry, mixed mode.""" ctx = mock.Mock() ctx.registry_path = "some-registry-path" with pytest.raises( click.ClickException, match="Can not find dependency locally or remotely: .*. Try to add flag `--push-missing` to push dependency package to the registry.", ): MixedRegistry(ctx).check_item_present( "protocols", PublicId.from_str("bad_author/bad_package_name:0.8.0") ) def test_positive_check_is_item_in_registry_mixed_not_locally_but_remotely(): """Check if item in registry, mixed mode, when not in local registry but only in remote.""" ctx = mock.Mock() ctx.registry_path = "some-registry-path" MixedRegistry(ctx).check_item_present( "protocols", PublicId.from_str("fetchai/default:0.8.0") ) class TestPublishLocallyWithDeps(AEATestCaseEmpty): """Test case for cli publish --local --push-missing.""" ITEM_PUBLIC_ID = ECHO_SKILL_PUBLIC_ID ITEM_TYPE = "skill" NEW_ITEM_TYPE = "skill" NEW_ITEM_NAME = "my_test_skill" @classmethod def setup_class(cls): """Set up test case.""" super(TestPublishLocallyWithDeps, cls).setup_class() cls.add_item(cls.ITEM_TYPE, str(cls.ITEM_PUBLIC_ID), local=True) cls.scaffold_item(cls.NEW_ITEM_TYPE, cls.NEW_ITEM_NAME) def test_publish_ok_with_missing_push( self, ): """Test ok for missing push.""" with pytest.raises(ClickException, match=r"Dependency is missing") as e: self.invoke("publish", "--local") assert "use --push-missing" in str(e) self.invoke("publish", "--local", "--push-missing") # remove agents published and publish again packages_dir = self.t / self.packages_dir_path # type: ignore rmtree(Path(packages_dir) / self.author / "agents") self.invoke("publish", "--local") @mock.patch("aea.cli.publish._push_item_remote") @mock.patch("aea.cli.publish.publish_agent") class TestPublishRemotellyWithDeps(AEATestCaseEmpty): """Test case for cli publish --push-missing.""" ITEM_PUBLIC_ID = ECHO_SKILL_PUBLIC_ID ITEM_TYPE = "skill" NEW_ITEM_TYPE = "skill" NEW_ITEM_NAME = "my_test_skill" @classmethod def setup_class(cls): """Set up test case.""" super(TestPublishRemotellyWithDeps, cls).setup_class() cls.add_item(cls.ITEM_TYPE, str(cls.ITEM_PUBLIC_ID), local=True) cls.scaffold_item(cls.NEW_ITEM_TYPE, cls.NEW_ITEM_NAME) def test_publish_ok_with_missing_push( self, publish_agent_mock, push_item_remote_mock ): """Test ok for missing push.""" with pytest.raises( ClickException, match=r"Package not found in remote registry" ) as e, mock.patch( "aea.cli.publish.get_package_meta", side_effect=ClickException("expected"), ): self.invoke("publish", "--remote") assert "--push-missing" in str(e) publish_agent_mock.assert_not_called() with mock.patch( "aea.cli.publish.get_package_meta", side_effect=[ClickException("expected")] + [mock.DEFAULT] * 100, ): self.invoke("publish", "--remote", "--push-missing") push_item_remote_mock.assert_called() publish_agent_mock.assert_called() class CheckAndPublishCommandTestCase(TestCase): """Test case for Registry.check_item_present_and_push method.""" def test_publish_not_present_positive(self, *mocks): """Test for publish positive result.""" ctx = mock.Mock() registry = LocalRegistry(ctx) with mock.patch( "aea.cli.publish.LocalRegistry.check_item_present", side_effect=[ClickException("expected"), None], ), mock.patch("aea.cli.publish.LocalRegistry.push_item") as push_item_mock: registry.check_item_present_and_push("connections", mock.Mock()) push_item_mock.assert_called_once() def test_publish_not_present_failed_to_push(self, *mocks): """Test for publish failed.""" ctx = mock.Mock() registry = LocalRegistry(ctx) with mock.patch( "aea.cli.publish.LocalRegistry.check_item_present", side_effect=[ClickException("expected"), None], ), mock.patch( "aea.cli.publish.LocalRegistry.push_item", side_effect=Exception("expected") ): with pytest.raises( ClickException, match="Failed to push missing item.*expected" ): registry.check_item_present_and_push("connections", mock.Mock()) def test_publish_not_present_failed_to_check(self, *mocks): """Test for publish failed lookup after push.""" ctx = mock.Mock() registry = LocalRegistry(ctx) with mock.patch( "aea.cli.publish.LocalRegistry.check_item_present", side_effect=ClickException("expected"), ), mock.patch("aea.cli.publish.LocalRegistry.push_item") as push_item_mock: with pytest.raises( ClickException, match="Failed to find item after push:.*expected" ): registry.check_item_present_and_push("connections", mock.Mock()) push_item_mock.assert_called_once() ================================================ FILE: tests/test_aea/test_cli/test_push.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test module for Registry push methods.""" import filecmp import os from unittest import TestCase, mock import pytest from click import ClickException from aea.cli import cli from aea.cli.push import _save_item_locally, check_package_public_id from aea.cli.utils.constants import ITEM_TYPES from aea.configurations.base import PublicId from aea.test_tools.constants import DEFAULT_AUTHOR from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.skills.echo import PUBLIC_ID from tests.conftest import AUTHOR, CLI_LOG_OPTION, CliRunner from tests.test_aea.test_cli.tools_for_testing import ContextMock, PublicIdMock @mock.patch("aea.cli.push.copytree") class SaveItemLocallyTestCase(TestCase): """Test case for save_item_locally method.""" @mock.patch("aea.cli.push.try_get_item_target_path", return_value="target") @mock.patch("aea.cli.push.try_get_item_source_path", return_value="source") @mock.patch("aea.cli.push.check_package_public_id", return_value=None) def test_save_item_locally_positive( self, _check_package_public_id_mock, try_get_item_source_path_mock, try_get_item_target_path_mock, copy_tree_mock, ): """Test for save_item_locally positive result.""" item_type = "skill" item_id = PublicIdMock() ctx_mock = ContextMock() _save_item_locally(ctx_mock, item_type, item_id) try_get_item_source_path_mock.assert_called_once_with( "cwd", None, "skills", item_id.name ) try_get_item_target_path_mock.assert_called_once_with( ctx_mock.registry_path, item_id.author, item_type + "s", item_id.name, ) _check_package_public_id_mock.assert_called_once_with( "source", item_type, item_id ) copy_tree_mock.assert_called_once_with("source", "target") @mock.patch("aea.cli.push.copytree") class TestPushLocally(AEATestCaseEmpty): """Test case for cli push --local.""" ITEM_PUBLIC_ID = PUBLIC_ID ITEM_TYPE = "skill" @classmethod def setup_class(cls): """Set up test case.""" super(TestPushLocally, cls).setup_class() cls.add_item(cls.ITEM_TYPE, str(cls.ITEM_PUBLIC_ID), local=True) def test_vendor_ok( self, copy_tree_mock, ): """Test ok for vendor's item.""" with mock.patch( "aea.cli.utils.package_utils.is_path_exist", side_effect=[False, True, False], ): self.invoke("push", "--local", "skill", "fetchai/echo") copy_tree_mock.assert_called_once() src_path, dst_path = copy_tree_mock.mock_calls[0][1] # check for correct author, type, name assert ( os.path.normpath(src_path).split(os.sep)[-3:] == os.path.normpath(dst_path).split(os.sep)[-3:] ) def test_user_ok( self, copy_tree_mock, ): """Test ok for users's item.""" with mock.patch( "aea.cli.push.try_get_item_source_path", return_value=f"{self.author}/skills/echo", ), mock.patch("aea.cli.push.check_package_public_id"): self.invoke("push", "--local", "skill", f"{self.author}/echo") copy_tree_mock.assert_called_once() src_path, dst_path = copy_tree_mock.mock_calls[0][1] # check for correct author, type, name assert ( os.path.normpath(src_path).split(os.sep)[-3:] == os.path.normpath(dst_path).split(os.sep)[-3:] ) def test_fail_no_item( self, *mocks, ): """Test fail, item_not_exists .""" expected_path_pattern = ".*" + ".*".join( ["vendor", "fetchai", "skills", "not_exists"] ) with pytest.raises( ClickException, match=rf'Item "fetchai/not_exists" not found in source folder "{expected_path_pattern}"\.', ): self.invoke("push", "--local", "skill", "fetchai/not_exists") @mock.patch( "aea.cli.registry.push.load_yaml", return_value={"author": AUTHOR, "name": "name", "version": "0.1.0"}, ) class CheckPackagePublicIdTestCase(TestCase): """Test case for _check_package_public_id method.""" def test__check_package_public_id_positive(self, *mocks): """Test for _check_package_public_id positive result.""" check_package_public_id( "source-path", "item-type", PublicId.from_str(f"{AUTHOR}/name:0.1.0"), ) def test__check_package_public_id_negative(self, *mocks): """Test for _check_package_public_id negative result.""" with self.assertRaises(ClickException): check_package_public_id( "source-path", "item-type", PublicId.from_str(f"{AUTHOR}/name:0.1.1"), ) class TestPushLocalFailsArgumentNotPublicId: """Test the case when we try a local push with a non public id.""" @classmethod def setup_class(cls): """Set the tests up.""" cls.runner = CliRunner() cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "--local", "connection", "oef"], standalone_mode=False, ) def test_exit_code_1(self): """Test the exit code is 1 (SystemExit).""" assert self.result.exit_code == 1 @classmethod def teardown_class(cls): """Tear the tests down.""" @mock.patch("aea.cli.utils.config.try_to_load_agent_config") @mock.patch("aea.cli.push._save_item_locally") @mock.patch("aea.cli.push.push_item") @mock.patch("aea.cli.utils.decorators._check_aea_project") class PushCommandTestCase(TestCase): """Test case for CLI push command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_push_connection_positive(self, *mocks): """Test for CLI push connection positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "connection", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "--local", "connection", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) def test_push_protocol_positive(self, *mocks): """Test for CLI push protocol positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "protocol", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "--local", "protocol", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) def test_push_skill_positive(self, *mocks): """Test for CLI push skill positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "skill", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "--local", "skill", "author/name:0.1.0"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") class PushContractCommandTestCase(TestCase): """Test that the command 'aea push contract' works as expected.""" def setUp(self): """Set the test up.""" self.runner = CliRunner() @mock.patch("aea.cli.push._save_item_locally") def test_push_contract_positive(self, *mocks): """Test push contract command positive result.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "push", "--local", "contract", "author/name:0.1.0", ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) @mock.patch("aea.cli.push.push_item") def test_push_contract_registry_positive(self, *mocks): """Test push contract to registry command positive result.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "push", "contract", "author/name:0.1.0", ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) class TestPushLocallyWithLatest(AEATestCaseEmpty): """Test push locally with 'latest' as version.""" @pytest.mark.parametrize("component_type", ITEM_TYPES) def test_command(self, component_type): """Run the test.""" item_name = f"my_{component_type}" version = ":latest" self.scaffold_item(component_type, item_name) self.run_cli_command( "push", "--local", component_type, f"{self.author}/{item_name}{version}", cwd=self._get_cwd(), ) component_type_plural = component_type + "s" path_to_pushed_package = ( self.packages_dir_path / DEFAULT_AUTHOR / component_type_plural / item_name ) path_to_current_package = ( self.t / self.agent_name / component_type_plural / item_name ) assert path_to_pushed_package.exists() comparison = filecmp.dircmp(path_to_pushed_package, path_to_current_package) assert comparison.diff_files == [] @mock.patch("aea.cli.utils.config.try_to_load_agent_config") @mock.patch("aea.cli.utils.decorators._check_aea_project") @mock.patch("os.path.exists") class TestPushVersionsMismatch(TestCase): """Test that the command 'aea push contract' works as expected.""" def setUp(self): """Set the test up.""" self.runner = CliRunner() def test_push_local_version_check_failed(self, *mocks): """Test push contract command positive result.""" with mock.patch( "aea.cli.registry.push.load_component_public_id", return_value=PublicId("author", "name", "1000.0.0"), ): with pytest.raises( ClickException, match="Version, name or author does not match." ): self.runner.invoke( cli, [ *CLI_LOG_OPTION, "push", "--local", "contract", "author/name:0.1.0", ], standalone_mode=False, catch_exceptions=False, ) def test_push_remote_version_check_failed(self, *mocks): """Test push contract command positive result.""" with mock.patch( "aea.cli.registry.push.load_component_public_id", return_value=PublicId("author", "name", "1000.0.0"), ): with pytest.raises( ClickException, match="Version, name or author does not match." ): self.runner.invoke( cli, [*CLI_LOG_OPTION, "push", "contract", "author/name:0.1.0"], standalone_mode=False, catch_exceptions=False, ) ================================================ FILE: tests/test_aea/test_cli/test_register.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI register command.""" from unittest import TestCase, mock from aea.cli import cli from aea.cli.register import do_register from aea.cli.registry.settings import AUTH_TOKEN_KEY from tests.conftest import CLI_LOG_OPTION, CliRunner @mock.patch("aea.cli.register.do_register") class RegisterTestCase(TestCase): """Test case for CLI register command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_register_positive(self, do_register_mock): """Test for CLI register positive result.""" username = "username" email = "email@example.com" fake_pwd = "fake_pwd" # nosec result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "register", "--username={}".format(username), "--email={}".format(email), "--password={}".format(fake_pwd), "--confirm_password={}".format(fake_pwd), "--no-subscribe", ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) do_register_mock.assert_called_once_with( username, email, fake_pwd, fake_pwd, True ) @mock.patch("aea.cli.register.validate_author_name", lambda x: x) @mock.patch("aea.cli.register.register_new_account", return_value="token") @mock.patch("aea.cli.register.click.echo") @mock.patch("aea.cli.register.click.confirm", return_value=True) @mock.patch("aea.cli.register.update_cli_config") class DoRegisterTestCase(TestCase): """Test case for do_register method.""" def test_do_register_positive( self, update_cli_config_mock, confirm_mock, echo_mock, *mocks ): """Test for do_register method positive result.""" username = "username" email = "email@example.com" fake_pwd = "fake_pwd" # nosec no_subscribe = False do_register(username, email, fake_pwd, fake_pwd, no_subscribe) update_cli_config_mock.assert_called_once_with({AUTH_TOKEN_KEY: "token"}) confirm_mock.assert_called_once() def test_do_register_no_subscribe_true_positive( self, update_cli_config_mock, confirm_mock, echo_mock, *mocks ): """Test for do_register method no_subscribe flag = True positive result.""" username = "username" email = "email@example.com" fake_pwd = "fake_pwd" # nosec no_subscribe = True do_register(username, email, fake_pwd, fake_pwd, no_subscribe) update_cli_config_mock.assert_called_once_with({AUTH_TOKEN_KEY: "token"}) confirm_mock.assert_not_called() ================================================ FILE: tests/test_aea/test_cli/test_registry/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test package contains the tests for Registry operating tools.""" ================================================ FILE: tests/test_aea/test_cli/test_registry/test_add.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for CLI Registry add methods.""" import os from unittest import TestCase, mock from aea.cli.registry.add import fetch_package from aea.configurations.base import PublicId @mock.patch("aea.cli.registry.utils.request_api", return_value={"file": "url"}) @mock.patch("aea.cli.registry.add.download_file", return_value="filepath") @mock.patch("aea.cli.registry.add.extract") class FetchPackageTestCase(TestCase): """Test case for fetch_package method.""" def test_fetch_package_positive( self, extract_mock, download_file_mock, request_api_mock ): """Test for fetch_package method positive result.""" obj_type = "connection" public_id = PublicId.from_str("author/name:0.1.0") cwd = "cwd" dest_path = os.path.join("dest", "path", "package_folder_name") fetch_package(obj_type, public_id, cwd, dest_path) request_api_mock.assert_called_with( "GET", "/connections/author/name/0.1.0", params=None ) download_file_mock.assert_called_once_with("url", "cwd") extract_mock.assert_called_once_with("filepath", os.path.join("dest", "path")) ================================================ FILE: tests/test_aea/test_cli/test_registry/test_fetch.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI Registry fetch methods.""" import os import shutil import tempfile from unittest import TestCase, mock from click import ClickException from aea.cli.registry.fetch import fetch_agent from tests.test_aea.test_cli.tools_for_testing import ContextMock, PublicIdMock def _raise_exception(): raise Exception() @mock.patch("aea.cli.registry.fetch.open_file", mock.mock_open()) @mock.patch("aea.cli.utils.decorators._cast_ctx") @mock.patch("aea.cli.registry.fetch.PublicId", PublicIdMock) @mock.patch("aea.cli.registry.fetch.os.rename") @mock.patch("aea.cli.registry.fetch.os.makedirs") @mock.patch("aea.cli.registry.fetch.try_to_load_agent_config") @mock.patch("aea.cli.registry.fetch.download_file", return_value="filepath") @mock.patch("aea.cli.registry.fetch.extract") class TestFetchAgent(TestCase): """Test case for fetch_package method.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) @mock.patch( "aea.cli.registry.fetch.request_api", return_value={ "file": "url", "connections": [], "contracts": [], "protocols": [], "skills": [], }, ) def test_fetch_agent_positive( self, request_api_mock, extract_mock, download_file_mock, *mocks ): """Test for fetch_agent method positive result.""" public_id_mock = PublicIdMock() fetch_agent(ContextMock(), public_id_mock, alias="alias") request_api_mock.assert_called_with( "GET", "/agents/{}/{}/{}".format( public_id_mock.author, public_id_mock.name, public_id_mock.version ), ) download_file_mock.assert_called_once_with("url", "cwd") extract_mock.assert_called_once_with("filepath", "cwd") @mock.patch("aea.cli.registry.fetch.add_item") @mock.patch( "aea.cli.registry.fetch.request_api", return_value={ "file": "url", "connections": ["public/id:{}".format(PublicIdMock.DEFAULT_VERSION)], "contracts": ["public/id:{}".format(PublicIdMock.DEFAULT_VERSION)], "protocols": ["public/id:{}".format(PublicIdMock.DEFAULT_VERSION)], "skills": ["public/id:{}".format(PublicIdMock.DEFAULT_VERSION)], }, ) def test_fetch_agent_with_dependencies_positive( self, request_api_mock, add_item_mock, extract_mock, download_file_mock, *mocks ): """Test for fetch_agent method with dependencies positive result.""" public_id_mock = PublicIdMock() ctx_mock = ContextMock( connections=["public/id:{}".format(PublicIdMock.DEFAULT_VERSION)] ) fetch_agent(ctx_mock, public_id_mock) request_api_mock.assert_called_with( "GET", "/agents/{}/{}/{}".format( public_id_mock.author, public_id_mock.name, public_id_mock.version ), ) download_file_mock.assert_called_once_with("url", "cwd") extract_mock.assert_called_once_with("filepath", "cwd") add_item_mock.assert_called() @mock.patch("aea.cli.registry.fetch.add_item", _raise_exception) @mock.patch( "aea.cli.registry.fetch.request_api", return_value={ "file": "url", "connections": ["public/id:{}".format(PublicIdMock.DEFAULT_VERSION)], "contracts": [], "protocols": [], "skills": [], }, ) def test_fetch_agent_with_dependencies_unable_to_fetch(self, *mocks): """Test for fetch_agent method unable to fetch.""" ctx_mock = ContextMock( connections=["public/id:{}".format(PublicIdMock.DEFAULT_VERSION)] ) with self.assertRaises(ClickException): fetch_agent(ctx_mock, PublicIdMock()) @classmethod def teardown_class(cls): """Teardowm the test.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_registry/test_login.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for CLI Registry login methods.""" from unittest import TestCase, mock from aea.cli.registry.login import registry_login, registry_reset_password @mock.patch("aea.cli.registry.login.request_api", return_value={"key": "key"}) class RegistryLoginTestCase(TestCase): """Test case for registry_login method.""" def test_registry_login_positive(self, request_api_mock): """Test for registry_login method positive result.""" result = registry_login("username", "password") expected_result = "key" self.assertEqual(result, expected_result) request_api_mock.assert_called_once() @mock.patch( "aea.cli.registry.login.request_api", return_value={"message": "Email was sent."} ) class RegistryResetPasswordTestCase(TestCase): """Test case for registry_reset_password method.""" def test_registry_reset_password_positive(self, request_api_mock): """Test for registry_reset_password method positive result.""" registry_reset_password("email@example.com") request_api_mock.assert_called_once() ================================================ FILE: tests/test_aea/test_cli/test_registry/test_logout.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for CLI Registry logout methods.""" from unittest import TestCase, mock from aea.cli.registry.logout import registry_logout @mock.patch("aea.cli.registry.logout.request_api") class RegistryLogoutTestCase(TestCase): """Test case for registry_logout method.""" def test_registry_logout_positive(self, request_api_mock): """Test for registry_logout method positive result.""" registry_logout() request_api_mock.assert_called_once() ================================================ FILE: tests/test_aea/test_cli/test_registry/test_publish.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test module for Registry publish methods.""" from pathlib import Path from unittest import TestCase, mock from unittest.mock import mock_open from aea.cli.registry.publish import _compress, publish_agent from aea.test_tools.test_cases import AEATestCase from tests.conftest import CUR_PATH from tests.test_aea.test_cli.tools_for_testing import ContextMock @mock.patch("builtins.open", mock_open(read_data="test")) @mock.patch("aea.cli.registry.publish.shutil.copy") @mock.patch("aea.cli.registry.publish.try_to_load_agent_config") @mock.patch("aea.cli.registry.publish.check_is_author_logged_in") @mock.patch("aea.cli.registry.utils._rm_tarfiles") @mock.patch("aea.cli.registry.publish.os.getcwd", return_value="cwd") @mock.patch("aea.cli.registry.publish._compress") @mock.patch( "aea.cli.registry.publish.request_api", return_value={"public_id": "public-id"} ) class PublishAgentTestCase(TestCase): """Test case for publish_agent method.""" @mock.patch("aea.cli.registry.publish.is_readme_present", return_value=True) def test_publish_agent_positive( self, is_readme_present_mock, request_api_mock, *mocks ): """Test for publish_agent positive result.""" description = "Some description." version = "0.1.0" context_mock = ContextMock(description=description, version=version) publish_agent(context_mock) request_api_mock.assert_called_once_with( "POST", "/agents/create", data={ "name": "agent_name", "description": description, "version": version, "connections": [], "contracts": [], "protocols": [], "skills": [], }, is_auth=True, files={"file": mock.ANY, "readme": mock.ANY}, ) @mock.patch("aea.cli.registry.publish.is_readme_present", return_value=False) def test_publish_agent_without_readme_positive( self, is_readme_present_mock, request_api_mock, *mocks ): """Test for publish_agent without readme positive result.""" description = "Some description." version = "0.1.0" context_mock = ContextMock(description=description, version=version) publish_agent(context_mock) request_api_mock.assert_called_once_with( "POST", "/agents/create", data={ "name": "agent_name", "description": description, "version": version, "connections": [], "contracts": [], "protocols": [], "skills": [], }, is_auth=True, files={"file": mock.ANY}, ) @mock.patch("aea.cli.registry.publish.tarfile") class CompressTestCase(TestCase): """Test case for _compress method.""" def test__compress_positive(self, tarfile_mock): """Test for _compress positive result.""" tar_obj_mock = mock.MagicMock() open_mock = mock.MagicMock(return_value=tar_obj_mock) tarfile_mock.open = open_mock _compress("output_filename", "file1", "file2") open_mock.assert_called_once_with("output_filename", "w:gz") @mock.patch("aea.cli.registry.publish.request_api", side_effect=ValueError("expected")) class PublishAgentCleanupOnFailTestCase(AEATestCase): """Test case for publish_agent method.""" path_to_aea = Path(CUR_PATH) / "data" / "dummy_aea" def test_publish_agent_fails(self, *mocks): """Test for publish_agent positive result.""" description = "Some description." version = "0.1.0" context_mock = ContextMock(description=description, version=version) context_mock.cwd = "." publish_agent(context_mock) ================================================ FILE: tests/test_aea/test_cli/test_registry/test_push.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test module for Registry push methods.""" import os from unittest import TestCase, mock from unittest.mock import mock_open, patch import pytest from click import ClickException from aea.cli.registry.push import ( _compress_dir, _remove_pycache, check_package_public_id, push_item, ) from aea.configurations.base import PublicId from tests.test_aea.test_cli.tools_for_testing import ContextMock, PublicIdMock @mock.patch("builtins.open", mock_open(read_data="opened_file")) @mock.patch("aea.cli.registry.push.check_is_author_logged_in") @mock.patch("aea.cli.registry.push.list_missing_packages", return_value=[]) @mock.patch("aea.cli.registry.utils._rm_tarfiles") @mock.patch("aea.cli.registry.push.os.getcwd", return_value="cwd") @mock.patch("aea.cli.registry.push._compress_dir") @mock.patch( "aea.cli.registry.push.load_yaml", return_value={ "description": "some-description", "version": PublicIdMock.DEFAULT_VERSION, "author": "some_author", "name": "some_name", "protocols": ["some/protocol:0.1.2"], }, ) @mock.patch( "aea.cli.registry.push.request_api", return_value={"public_id": "public-id"} ) class PushItemTestCase(TestCase): """Test case for push_item method.""" @mock.patch("aea.cli.registry.push.os.path.exists", return_value=True) @mock.patch("aea.cli.registry.push.is_readme_present", return_value=True) def test_push_item_positive( self, is_readme_present_mock, path_exists_mock, request_api_mock, load_yaml_mock, compress_mock, getcwd_mock, rm_tarfiles_mock, check_is_author_logged_in_mock, *_, ): """Test for push_item positive result.""" public_id = PublicIdMock( name="some_name", author="some_author", version="{}".format(PublicIdMock.DEFAULT_VERSION), ) push_item(ContextMock(), "some-type", public_id) request_api_mock.assert_called_once_with( "POST", "/some-types/create", data={ "name": "some_name", "description": "some-description", "version": PublicIdMock.DEFAULT_VERSION, "protocols": ["some/protocol:0.1.2"], }, is_auth=True, files={"file": open("file.1"), "readme": open("file.2")}, ) @mock.patch("aea.cli.registry.push.os.path.exists", return_value=True) @mock.patch("aea.cli.registry.push.is_readme_present", return_value=True) def test_push_dependency_fail( self, is_readme_present_mock, path_exists_mock, request_api_mock, load_yaml_mock, compress_mock, getcwd_mock, rm_tarfiles_mock, check_is_author_logged_in_mock, *_, ): """Test for push_item fails cause dependencies check.""" public_id = PublicIdMock( name="some_name", author="some_author", version="{}".format(PublicIdMock.DEFAULT_VERSION), ) with patch( "aea.cli.registry.push.list_missing_packages", return_value=[("some", PublicId.from_str("some/pack:0.1.0"))], ): with pytest.raises( ClickException, match="Found missing dependencies! Push canceled!" ): push_item(ContextMock(), "some-type", public_id) @mock.patch("aea.cli.registry.push.os.path.exists", return_value=True) @mock.patch("aea.cli.registry.push.is_readme_present", return_value=False) def test_push_item_positive_without_readme( self, is_readme_present_mock, path_exists_mock, request_api_mock, *mocks ): """Test for push_item without readme positive result.""" public_id = PublicIdMock( name="some_name", author="some_author", version="{}".format(PublicIdMock.DEFAULT_VERSION), ) push_item(ContextMock(), "some-type", public_id) request_api_mock.assert_called_once_with( "POST", "/some-types/create", data={ "name": "some_name", "description": "some-description", "version": PublicIdMock.DEFAULT_VERSION, "protocols": ["some/protocol:0.1.2"], }, is_auth=True, files={"file": open("opened_file", "r")}, ) @mock.patch("aea.cli.registry.push.os.path.exists", return_value=False) def test_push_item_item_not_found( self, path_exists_mock, request_api_mock, load_yaml_mock, compress_mock, getcwd_mock, rm_tarfiles_mock, check_is_author_logged_in_mock, *_, ): """Test for push_item - item not found.""" with self.assertRaises(ClickException): push_item(ContextMock(), "some-type", PublicIdMock()) request_api_mock.assert_not_called() @mock.patch("aea.cli.registry.push.shutil.rmtree") class RemovePycacheTestCase(TestCase): """Test case for _remove_pycache method.""" @mock.patch("aea.cli.registry.push.os.path.exists", return_value=True) def test_remove_pycache_positive(self, path_exists_mock, rmtree_mock): """Test for _remove_pycache positive result.""" source_dir = "somedir" pycache_path = os.path.join(source_dir, "__pycache__") _remove_pycache(source_dir) rmtree_mock.assert_called_once_with(pycache_path) @mock.patch("aea.cli.registry.push.os.path.exists", return_value=False) def test_remove_pycache_no_pycache(self, path_exists_mock, rmtree_mock): """Test for _remove_pycache if there's no pycache.""" source_dir = "somedir" _remove_pycache(source_dir) rmtree_mock.assert_not_called() @mock.patch("aea.cli.registry.push.tarfile") @mock.patch("aea.cli.registry.push._remove_pycache") class CompressDirTestCase(TestCase): """Test case for _compress_dir method.""" def test__compress_dir_positive(self, _remove_pycache_mock, tarfile_mock): """Test for _compress_dir positive result.""" tar_obj_mock = mock.MagicMock() open_mock = mock.MagicMock(return_value=tar_obj_mock) tarfile_mock.open = open_mock _compress_dir("output_filename", "source_dir") _remove_pycache_mock.assert_called_once_with("source_dir") open_mock.assert_called_once_with("output_filename", "w:gz") def test_check_package_public_id(): """Test check_package_public_id.""" public_id = PublicId("test", "test", "10.0.1") with mock.patch( "aea.cli.registry.push.load_component_public_id", return_value=public_id ): check_package_public_id(mock.Mock(), mock.Mock(), public_id) with mock.patch( "aea.cli.registry.push.load_component_public_id", return_value=public_id ): with pytest.raises( ClickException, match="Version, name or author does not match" ): check_package_public_id( mock.Mock(), mock.Mock(), PublicId("test", "test", "10.0.2") ) ================================================ FILE: tests/test_aea/test_cli/test_registry/test_registration.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test module for Registry registration methods.""" from unittest import TestCase, mock from click import ClickException from aea.cli.registry.registration import register class RegistrationTestCase(TestCase): """Test case for Registry registration methods.""" @mock.patch( "aea.cli.registry.registration.request_api", return_value=({"key": "token"}, 201), ) def test_register_positive(self, *mocks): """Test register method positive result.""" username, email, password = ("username", "email", "password") result = register(username, email, password, password) expected_result = "token" self.assertEqual(result, expected_result) @mock.patch( "aea.cli.registry.registration.request_api", return_value=({"username": "Already exists"}, 400), ) def test_register_negative(self, *mocks): """Test register method negative result.""" username, email, password = ("bad-username", "email", "password") with self.assertRaises(ClickException): register(username, email, password, password) ================================================ FILE: tests/test_aea/test_cli/test_registry/test_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for CLI Registry utils.""" import os import tempfile from json.decoder import JSONDecodeError from pathlib import Path from unittest import TestCase, mock from unittest.mock import MagicMock, patch import pytest from click import ClickException from requests.exceptions import ConnectionError from aea.cli.registry.settings import AUTH_TOKEN_KEY, REGISTRY_API_URL from aea.cli.registry.utils import ( FILE_DOWNLOAD_TIMEOUT, _rm_tarfiles, check_is_author_logged_in, clean_tarfiles, download_file, extract, get_latest_public_id_mixed, get_latest_version_available_in_registry, get_package_meta, is_auth_token_present, list_missing_packages, request_api, ) from aea.cli.utils.exceptions import AEAConfigException from aea.configurations.base import PublicId from aea.helpers.base import cd from packages.fetchai.protocols.default import DefaultMessage def _raise_connection_error(*args, **kwargs): raise ConnectionError() def _raise_config_exception(*args): raise AEAConfigException() def _raise_json_decode_error(*args): raise JSONDecodeError(None, "None", 1) # args requied for JSONDecodeError raising @mock.patch("aea.cli.registry.utils.requests.request") class RequestAPITestCase(TestCase): """Test case for request_api method.""" def test_request_api_positive(self, request_mock): """Test for request_api method positive result.""" expected_result = {"correct": "json"} resp_mock = mock.Mock() resp_mock.json = lambda: expected_result resp_mock.status_code = 200 request_mock.return_value = resp_mock result = request_api("GET", "/path") request_mock.assert_called_once_with( method="GET", params=None, data=None, files=None, headers={}, url=REGISTRY_API_URL + "/path", ) self.assertEqual(result, expected_result) result = request_api("GET", "/path", return_code=True) self.assertEqual(result, (expected_result, 200)) def test_request_api_404(self, request_mock): """Test for request_api method 404 server response.""" resp_mock = mock.Mock() resp_mock.status_code = 404 request_mock.return_value = resp_mock with self.assertRaises(ClickException): request_api("GET", "/path") def test_request_api_500(self, request_mock): """Test for request_api method 500 server response.""" resp_mock = mock.Mock() resp_mock.status_code = 500 resp_mock.json.return_value = {"detail": "test"} request_mock.return_value = resp_mock with self.assertRaises(ClickException): request_api("GET", "/path") def test_request_api_201(self, request_mock): """Test for request_api method 201 server response.""" expected_result = {"correct": "json"} resp_mock = mock.Mock() resp_mock.json = lambda: expected_result resp_mock.status_code = 201 request_mock.return_value = resp_mock result = request_api("GET", "/path") self.assertEqual(result, expected_result) def test_request_api_403(self, request_mock): """Test for request_api method notauthorized server response.""" resp_mock = mock.Mock() resp_mock.status_code = 403 request_mock.return_value = resp_mock with self.assertRaises(ClickException): request_api("GET", "/path") def test_request_api_400(self, request_mock): """Test for request_api method 400 code server response.""" resp_mock = mock.Mock() resp_mock.status_code = 400 request_mock.return_value = resp_mock with self.assertRaises(ClickException): request_api("GET", "/path") def test_request_api_409(self, request_mock): """Test for request_api method conflict server response.""" resp_mock = mock.Mock() resp_mock.status_code = 409 resp_mock.json = lambda: {"detail": "some"} request_mock.return_value = resp_mock with self.assertRaises(ClickException): request_api("GET", "/path") def test_request_api_unexpected_response(self, request_mock): """Test for request_api method unexpected server response.""" resp_mock = mock.Mock() status_code = 501 resp_mock.status_code = status_code # not implemented status resp_mock.json = _raise_json_decode_error request_mock.return_value = resp_mock with self.assertRaises(ClickException): request_api("GET", "/path") error_msg = "Error occured." resp_mock.json = mock.Mock(return_value={"detail": error_msg}) with self.assertRaises(ClickException) as execinfo: request_api("GET", "/path") expected_exception = f"Wrong server response. Status code: {status_code}: Error detail: {error_msg}" self.assertEqual(str(execinfo.exception), expected_exception) @mock.patch("aea.cli.registry.utils.get_or_create_cli_config", return_value={}) def test_request_api_no_auth_data( self, get_or_create_cli_config_mock, request_mock ): """Test for request_api method no auth data.""" with self.assertRaises(ClickException): request_api("GET", "/path", is_auth=True) @mock.patch( "aea.cli.registry.utils.get_or_create_cli_config", return_value={AUTH_TOKEN_KEY: "key"}, ) def test_request_api_with_auth_positive( self, get_or_create_cli_config_mock, request_mock ): """Test for request_api method with auth positive result.""" expected_result = {"correct": "json"} resp_mock = mock.Mock() resp_mock.json = lambda: expected_result resp_mock.status_code = 200 request_mock.return_value = resp_mock result = request_api("GET", "/path", is_auth=True) self.assertEqual(result, expected_result) @mock.patch("builtins.open", mock.mock_open()) def test_request_api_with_files_positive(self, request_mock): """Test for request_api method with file positive result.""" expected_result = {"correct": "json"} resp_mock = mock.Mock() resp_mock.json = lambda: expected_result resp_mock.status_code = 200 request_mock.return_value = resp_mock test_files = { "file": open("file.tar.gz", "rb"), "readme": open("file.md", "rb"), } result = request_api("GET", "/path", files=test_files) self.assertEqual(result, expected_result) @mock.patch("aea.cli.registry.utils.requests.request", _raise_connection_error) class RequestAPINoResponseTestCase(TestCase): """Test case for request_api method no server response.""" def test_request_api_server_not_responding(self): """Test for request_api method no server response.""" with self.assertRaises(ClickException): request_api("GET", "/path") @mock.patch("aea.cli.registry.utils.requests.get") class DownloadFileTestCase(TestCase): """Test case for download_file method.""" @mock.patch("builtins.open", mock.mock_open()) def test_download_file_positive(self, get_mock): """Test for download_file method positive result.""" filename = "filename.tar.gz" url = "url/{}".format(filename) cwd = "cwd" filepath = os.path.join(cwd, filename) resp_mock = mock.Mock() raw_mock = mock.Mock() raw_mock.read = lambda: "file content" resp_mock.raw = raw_mock resp_mock.status_code = 200 get_mock.return_value = resp_mock result = download_file(url, cwd) expected_result = filepath self.assertEqual(result, expected_result) get_mock.assert_called_once_with( url, stream=True, timeout=FILE_DOWNLOAD_TIMEOUT ) def test_download_file_wrong_response(self, get_mock): """Test for download_file method wrong response from file server.""" resp_mock = mock.Mock() resp_mock.status_code = 404 get_mock.return_value = resp_mock with self.assertRaises(ClickException): download_file("url", "cwd") class ExtractTestCase(TestCase): """Test case for extract method.""" @mock.patch("aea.cli.registry.utils.os.remove") @mock.patch("aea.cli.registry.utils.tarfile.open") def test_extract_positive(self, tarfile_open_mock, os_remove_mock): """Test for extract method positive result.""" source = "file.tar.gz" target = "target-folder" tar_mock = mock.Mock() tar_mock.extractall = lambda path: None tar_mock.close = lambda: None tarfile_open_mock.return_value = tar_mock extract(source, target) tarfile_open_mock.assert_called_once_with(source, "r:gz") os_remove_mock.assert_called_once_with(source) def test_extract_wrong_file_type(self): """Test for extract method wrong file type.""" source = "file.wrong" target = "target-folder" with self.assertRaises(ValueError): extract(source, target) @mock.patch( "aea.cli.registry.utils.request_api", return_value={"username": "current-user"} ) class CheckIsAuthorLoggedInTestCase(TestCase): """Test case for check_is_author_logged_in method.""" def test_check_is_author_logged_in_positive(self, request_api_mock): """Test for check_is_author_logged_in method positive result.""" check_is_author_logged_in("current-user") def test_check_is_author_logged_in_negative(self, request_api_mock): """Test for check_is_author_logged_in method negative result.""" with self.assertRaises(ClickException): check_is_author_logged_in("not-current-user") @mock.patch("aea.cli.registry.utils.os.remove") @mock.patch("aea.cli.registry.utils.os.listdir", return_value=["file1.tar.gz", "file2"]) @mock.patch("aea.cli.registry.utils.os.getcwd", return_value="cwd") class RmTarfilesTestCase(TestCase): """Test case for _rm_tarfiles method.""" def test__rm_tarfiles_positive(self, getcwd_mock, listdir_mock, remove_mock): """Test for _rm_tarfiles method positive result.""" _rm_tarfiles() listdir_mock.assert_called_once_with("cwd") remove_mock.assert_called_once() @mock.patch("aea.cli.registry.utils.get_auth_token", return_value="token") class IsAuthTokenPresentTestCase(TestCase): """Test case for is_auth_token_present method.""" def test_is_auth_token_present_positive(self, get_auth_token_mock): """Test for is_auth_token_present method positive result.""" result = is_auth_token_present() self.assertTrue(result) @mock.patch( "aea.cli.registry.utils.find_item_locally", side_effect=ClickException("some error") ) @mock.patch( "aea.cli.registry.utils.get_package_meta", return_value=dict(public_id="author/name:0.1.0"), ) def test_get_latest_public_id_mixed_negative(*_mocks): """Test 'get_latest_public_id_mixed', when local fetch fails.""" get_latest_public_id_mixed( MagicMock(), "protocol", PublicId.from_str("author/name:0.1.0") ) def test_clean_tarfiles(): """Test clean tarfiles wrapper.""" expected_result = "result" def func() -> str: """The function being wrapped.""" return expected_result wrapped = clean_tarfiles(func) with tempfile.TemporaryDirectory() as tempdir: with cd(tempdir): tarfile_path = Path(tempdir, "tarfile.tar.gz") tarfile_path.touch() result = wrapped() assert not tarfile_path.exists() assert result == expected_result def test_clean_tarfiles_error(): """Test clean tarfiles wrapper in case of error.""" expected_message = "some exception" def func() -> str: """The function being wrapped.""" raise Exception(expected_message) wrapped = clean_tarfiles(func) with tempfile.TemporaryDirectory() as tempdir: with cd(tempdir): tarfile_path = Path(tempdir, "tarfile.tar.gz") tarfile_path.touch() with pytest.raises(Exception, match=expected_message): wrapped() assert not tarfile_path.exists() @pytest.mark.integration def test_get_package_meta(): """Test get package meta.""" package_meta = get_package_meta("protocol", DefaultMessage.protocol_id.to_latest()) assert isinstance(package_meta, dict) assert package_meta["name"] == DefaultMessage.protocol_id.name @mock.patch( "aea.cli.registry.utils.find_item_locally", return_value=(None, MagicMock(public_id=DefaultMessage.protocol_id)), ) def test_get_latest_public_id_mixed(*_mock): """Test 'get_latest_public_id_mixed', in case of success.""" result = get_latest_public_id_mixed( MagicMock(), "protocol", DefaultMessage.protocol_id ) assert result == DefaultMessage.protocol_id @mock.patch( "aea.cli.registry.utils.get_package_meta", return_value=dict(public_id=str(DefaultMessage.protocol_id)), ) def test_get_latest_version_available_in_registry_remote_mode(*_mocks): """Test 'get_latest_version_available_in_registry', remote mode.""" context_mock = MagicMock(config=dict(is_local=False, is_mixed=False)) result = get_latest_version_available_in_registry( context_mock, "protocol", DefaultMessage.protocol_id ) assert result == DefaultMessage.protocol_id def test_list_missing_packages(): """Test 'list_missing_packages'.""" packages = [("connection", PublicId.from_str("test/test:0.1.2"))] resp_ok = MagicMock() resp_ok.status_code = 200 with patch( "aea.cli.registry.utils._perform_registry_request", return_value=resp_ok ): assert list_missing_packages(packages) == [] resp_404 = MagicMock() resp_404.status_code = 404 with patch( "aea.cli.registry.utils._perform_registry_request", return_value=resp_404 ): assert list_missing_packages(packages) == packages ================================================ FILE: tests/test_aea/test_cli/test_remove/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea remove` sub-command.""" ================================================ FILE: tests/test_aea/test_cli/test_remove/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test module for aea.cli.remove.remove_item method.""" import os import shutil import tempfile from pathlib import Path from unittest import TestCase, mock from unittest.mock import patch import pytest from click import ClickException from aea.cli.core import cli from aea.cli.remove import remove_item from aea.configurations.base import ( AgentConfig, DEFAULT_AEA_CONFIG_FILE, PackageId, PackageType, PublicId, ) from aea.configurations.constants import DEFAULT_PROTOCOL from aea.configurations.loader import ConfigLoader from aea.helpers.base import cd from aea.test_tools.click_testing import CliRunner from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_PUBLIC_ID, ) from packages.fetchai.connections.soef.connection import PUBLIC_ID as SOEF_PUBLIC_ID from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH from tests.test_aea.test_cli.tools_for_testing import ContextMock, PublicIdMock @mock.patch("aea.cli.remove.shutil.rmtree") @mock.patch("aea.cli.remove.Path.exists", return_value=False) @mock.patch("aea.cli.remove.try_to_load_agent_config") class RemoveItemTestCase(TestCase): """Test case for remove_item method.""" def test_remove_item_item_folder_not_exists( self, *mocks ): # pylint: disable=unused-argument """Test for save_agent_locally item folder not exists.""" public_id = PublicIdMock.from_str("author/name:0.1.0") with pytest.raises(ClickException, match="Can not find folder for the package"): remove_item(ContextMock(protocols=[public_id]), "protocol", public_id) @mock.patch("aea.cli.remove.shutil.rmtree") @mock.patch("aea.cli.remove.Path.exists", return_value=True) @mock.patch("aea.cli.remove.ItemRemoveHelper.get_component_directory") @mock.patch("aea.cli.remove.load_item_config") @mock.patch("aea.cli.remove.try_to_load_agent_config") class RemoveItemBadConfigurationTestCase(TestCase): """Test case for remove_item method.""" def test_remove_item_item_folder_not_exists( self, *mocks ): # pylint: disable=unused-argument """Test for component bad configuration load.""" public_id = PublicIdMock.from_str("author/name:0.1.0") with pytest.raises( ClickException, match="Error loading .* configuration, author/name do not match: .*", ): remove_item(ContextMock(protocols=[public_id]), "protocol", public_id) class TestRemovePackageWithLatestVersion(AEATestCaseEmpty): """Test case for remove package with latest version.""" @pytest.mark.parametrize( ["type_", "public_id"], [ ("protocol", PublicId.from_str(DEFAULT_PROTOCOL)), ("connection", PublicId("fetchai", "stub").to_latest()), ("contract", PublicId("fetchai", "erc1155").to_latest()), ], ) def test_remove_pacakge_latest_version(self, type_, public_id): """Test remove protocol with latest version.""" assert public_id.package_version.is_latest # we need this because there isn't a default contract/connection if type_ == "connection": self.add_item("connection", str(public_id)) if type_ == "contract": self.add_item("contract", str(public_id)) # first, check the package is present items_path = os.path.join(self.agent_name, "vendor", "fetchai", type_ + "s") items_folders = os.listdir(items_path) item_name = public_id.name assert item_name in items_folders # remove the package with patch("aea.cli.remove.RemoveItem.is_required_by", False): self.run_cli_command( *["remove", type_, str(public_id)], cwd=self._get_cwd() ) # check that the 'aea remove' took effect. items_folders = os.listdir(items_path) assert item_name not in items_folders class TestRemoveConfig( AEATestCaseEmpty ): # pylint: disable=attribute-defined-outside-init """Test component configuration also removed from agent config.""" ITEM_TYPE = "connection" ITEM_PUBLIC_ID = SOEF_PUBLIC_ID @staticmethod def loader() -> ConfigLoader: """Return Agent config loader.""" return ConfigLoader.from_configuration_type(PackageType.AGENT) def load_config(self) -> AgentConfig: """Load AgentConfig from current directory.""" with cd(self._get_cwd()): agent_loader = self.loader() path = Path(DEFAULT_AEA_CONFIG_FILE) with path.open(mode="r", encoding="utf-8") as fp: agent_config = agent_loader.load(fp) return agent_config def test_component_configuration_removed_from_agent_config(self): """Test component configuration removed from agent config.""" with cd(self._get_cwd()): self.run_cli_command( "add", "--local", self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID) ) self.run_cli_command("add", "--local", "connection", "fetchai/http_server") self.runner.invoke( cli, [ "config", "set", "vendor.fetchai.connections.soef.config.api_key", "some_api_key", ], standalone_mode=False, catch_exceptions=False, ) self.runner.invoke( cli, [ "config", "set", "vendor.fetchai.connections.http_server.config.port", "9000", ], standalone_mode=False, catch_exceptions=False, ) config = self.load_config() assert config.component_configurations assert ( PackageId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) in config.component_configurations ) self.run_cli_command("remove", self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID)) config = self.load_config() assert ( PackageId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) not in config.component_configurations ) assert config.component_configurations class TestRemoveWithIncompatibleAEAVersion: """Test remove command when agent/package has incompatible aea version.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) cls.connection_name = "http_client" os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) assert result.exit_code == 0 def test_exit_code_equal_to_zero(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" with patch("aea.configurations.base.__aea_version__", "2.0.0"): result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "connection", self.connection_id], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_remove/test_connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea remove connection` sub-command.""" import os import shutil import tempfile import unittest.mock from pathlib import Path import yaml import aea import aea.configurations.base from aea.cli import cli from aea.configurations.base import DEFAULT_AEA_CONFIG_FILE from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_CLIENT_PUBLIC_ID, ) from packages.fetchai.connections.local.connection import ( PUBLIC_ID as LOCAL_CONNECTION_PUBLIC_ID, ) from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner class TestRemoveConnectionWithPublicId: """Test that the command 'aea remove connection' works correctly when using the public id.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) cls.connection_name = "http_client" os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_zero(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 0 def test_directory_does_not_exist(self): """Test that the directory of the removed connection does not exist.""" assert not Path("connections", self.connection_name).exists() def test_connection_not_present_in_agent_config(self): """Test that the name of the removed connection is not present in the agent configuration file.""" agent_config = aea.configurations.base.AgentConfig.from_json( yaml.safe_load(open(DEFAULT_AEA_CONFIG_FILE)) ) assert self.connection_id not in agent_config.connections @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRemoveConnectionFailsWhenConnectionDoesNotExist: """Test that the command 'aea remove connection' fails when the connection does not exist.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) cls.connection_id = str(LOCAL_CONNECTION_PUBLIC_ID) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_connection_not_existing(self): """Test that the log error message is fixed. The expected message is: 'Connection '{connection_name}' not found.' """ s = "The connection '{}' is not supported.".format(self.connection_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRemoveConnectionFailsWhenExceptionOccurs: """Test that the command 'aea remove connection' fails when an exception occurs while removing the directory.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) cls.connection_id = str(HTTP_CLIENT_PUBLIC_ID) cls.connection_name = "http_client" os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", cls.connection_id], standalone_mode=False, ) assert result.exit_code == 0 cls.patch = unittest.mock.patch( "shutil.rmtree", side_effect=BaseException("an exception") ) cls.patch.start() cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "connection", cls.connection_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_remove/test_contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea remove contract` sub-command.""" from unittest import TestCase, mock from aea.cli import cli from tests.conftest import CLI_LOG_OPTION, CliRunner @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") class RemoveContractCommandTestCase(TestCase): """Test that the command 'aea remove contract' works as expected.""" def setUp(self): """Set the test up.""" self.runner = CliRunner() @mock.patch("aea.cli.remove.remove_item") def test_remove_contract_positive(self, *mocks): """Test remove contract command positive result.""" result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "--skip-consistency-check", "remove", "contract", "author/name:0.1.0", ], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) ================================================ FILE: tests/test_aea/test_cli/test_remove/test_dependencies.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add connection` sub-command.""" import os import shutil import tempfile from pathlib import Path from unittest.mock import MagicMock import pytest from click.exceptions import ClickException from aea.cli import cli from aea.cli.upgrade import ItemRemoveHelper from aea.configurations.base import ( AgentConfig, DEFAULT_AEA_CONFIG_FILE, PackageId, PackageType, ) from aea.configurations.loader import ConfigLoader from packages.fetchai.connections import oef from packages.fetchai.connections.soef.connection import PUBLIC_ID as SOEF_PUBLIC_ID from packages.fetchai.protocols.oef_search.message import OefSearchMessage from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner class TestRemoveAndDependencies: # pylint: disable=attribute-defined-outside-init """Test dependency remove helper and upgrade with dependency removed.""" ITEM_TYPE = "connection" ITEM_PUBLIC_ID = SOEF_PUBLIC_ID DEPENDENCY_TYPE = "protocol" DEPENDENCY_PUBLIC_ID = OefSearchMessage.protocol_id @staticmethod def loader() -> ConfigLoader: """Return Agent config loader.""" return ConfigLoader.from_configuration_type(PackageType.AGENT) def load_config(self) -> AgentConfig: """Load AgentConfig from current directory.""" agent_loader = self.loader() path = Path(DEFAULT_AEA_CONFIG_FILE) with path.open(mode="r", encoding="utf-8") as fp: agent_config = agent_loader.load(fp) return agent_config def setup(self): """Set the test up.""" self.runner = CliRunner() self.agent_name = "myagent" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(self.t, "packages")) os.chdir(self.t) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) assert result.exit_code == 0 result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", self.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(self.agent_name) # add connection first time self.DEPENDENCY_PACKAGE_ID = PackageId( self.DEPENDENCY_TYPE, self.DEPENDENCY_PUBLIC_ID ) result = self.runner.invoke( cli, ["-v", "DEBUG", "add", "--local", self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID)], standalone_mode=False, catch_exceptions=False, ) def teardown(self): """Tear the test down.""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass def check_remove(self, item_type, public_id): """Check remove can be performed with remove helper.""" context_mock = MagicMock(agent_config=self.load_config()) return ItemRemoveHelper(context_mock).check_remove(item_type, public_id) def test_package_can_be_removed_with_its_dependency(self): """Test package (soef) can be removed with its dependency (oef_search).""" required_by, can_be_removed, can_not_be_removed = self.check_remove( self.ITEM_TYPE, self.ITEM_PUBLIC_ID ) assert not required_by, required_by assert self.DEPENDENCY_PACKAGE_ID in can_be_removed assert self.DEPENDENCY_PACKAGE_ID not in can_not_be_removed def _install_oef(self): self.runner.invoke( cli, [ "-v", "DEBUG", "add", "--local", "connection", str(oef.connection.PUBLIC_ID), ], standalone_mode=False, catch_exceptions=False, ) def test_package_can_be_removed_but_not_dependency(self): """Test package (soef) can be removed but not its shared dependency (oef_search) with other package (oef).""" self._install_oef() required_by, can_be_removed, can_not_be_removed = self.check_remove( self.ITEM_TYPE, self.ITEM_PUBLIC_ID ) assert not required_by, required_by assert self.DEPENDENCY_PACKAGE_ID not in can_be_removed assert self.DEPENDENCY_PACKAGE_ID in can_not_be_removed def test_package_can_not_be_removed_cause_required_by_another_package(self): """Test package (oef_search) can not be removed cause required by another package (soef).""" required_by, can_be_removed, can_not_be_removed = self.check_remove( self.DEPENDENCY_TYPE, self.DEPENDENCY_PUBLIC_ID ) assert PackageId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) in required_by assert not can_be_removed assert not can_not_be_removed def test_removed_with_dependencies(self): """ Test dependency removed after upgrade. Done with mocking _add_item_deps to avoid dependencies installation. """ assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols self.runner.invoke( cli, [ "-v", "DEBUG", "remove", "--with-dependencies", self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}", ], standalone_mode=False, catch_exceptions=False, ) assert self.DEPENDENCY_PUBLIC_ID not in self.load_config().protocols def test_removed_and_dependency_not_removed_caused_required_by_another_item(self): """Test dependency is not removed after upgrade cause required by another item.""" assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols # do not add dependencies for the package self._install_oef() self.runner.invoke( cli, [ "-v", "DEBUG", "remove", "--with-dependencies", self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}", ], standalone_mode=False, catch_exceptions=False, ) assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols def test_not_removed_cause_required(self): """Test dependency is not removed after upgrade cause required by another item.""" assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols # do not add dependencies for the package with pytest.raises( ClickException, match="Package .* can not be removed because it is required by .*", ): self.runner.invoke( cli, [ "-v", "DEBUG", "remove", "--with-dependencies", self.DEPENDENCY_TYPE, f"{self.DEPENDENCY_PUBLIC_ID.author}/{self.DEPENDENCY_PUBLIC_ID.name}", ], standalone_mode=False, catch_exceptions=False, ) assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols ================================================ FILE: tests/test_aea/test_cli/test_remove/test_protocol.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea remove protocol` sub-command.""" import os import shutil import tempfile import unittest.mock from pathlib import Path import yaml import aea import aea.configurations.base from aea.cli import cli from aea.configurations.base import DEFAULT_AEA_CONFIG_FILE from packages.fetchai.protocols.gym.message import GymMessage from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner class TestRemoveProtocolWithPublicId: """Test that the command 'aea remove protocol' works correctly when using the public id.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) cls.protocol_id = str(GymMessage.protocol_id) cls.protocol_name = "gym" os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", cls.protocol_id], standalone_mode=False, ) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "protocol", cls.protocol_id], standalone_mode=False, ) def test_exit_code_equal_to_zero(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 0 def test_directory_does_not_exist(self): """Test that the directory of the removed protocol does not exist.""" assert not Path("protocols", self.protocol_name).exists() def test_protocol_not_present_in_agent_config(self): """Test that the name of the removed protocol is not present in the agent configuration file.""" agent_config = aea.configurations.base.AgentConfig.from_json( yaml.safe_load(open(DEFAULT_AEA_CONFIG_FILE)) ) assert self.protocol_id not in agent_config.protocols @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRemoveProtocolFailsWhenProtocolDoesNotExist: """Test that the command 'aea remove protocol' fails when the protocol does not exist.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) cls.protocol_id = str(GymMessage.protocol_id) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "protocol", cls.protocol_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_not_existing(self): """Test that the log error message is fixed. The expected message is: 'Protocol '{protocol_name}' not found.' """ s = "The protocol '{}' is not supported.".format(self.protocol_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRemoveProtocolFailsWhenExceptionOccurs: """Test that the command 'aea remove protocol' fails when an exception occurs while removing the directory.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) cls.protocol_id = str(GymMessage.protocol_id) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "protocol", cls.protocol_id], standalone_mode=False, ) assert result.exit_code == 0 cls.patch = unittest.mock.patch( "shutil.rmtree", side_effect=BaseException("an exception") ) cls.patch.start() cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "protocol", cls.protocol_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_remove/test_skill.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea remove skill` sub-command.""" import os import shutil import tempfile import unittest.mock from pathlib import Path import yaml import aea import aea.configurations.base from aea.cli import cli from aea.configurations.base import AgentConfig, DEFAULT_AEA_CONFIG_FILE from packages.fetchai.skills.gym import PUBLIC_ID as GYM_SKILL_PUBLIC_ID from tests.conftest import AUTHOR, CLI_LOG_OPTION, CliRunner, ROOT_DIR class TestRemoveSkillWithPublicId: """Test that the command 'aea remove skill' works correctly when using the public id.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.skill_id = str(GYM_SKILL_PUBLIC_ID) cls.skill_name = "gym" os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # change default registry path config = AgentConfig.from_json(yaml.safe_load(open(DEFAULT_AEA_CONFIG_FILE))) config.registry_path = os.path.join(ROOT_DIR, "packages") yaml.safe_dump(dict(config.json), open(DEFAULT_AEA_CONFIG_FILE, "w")) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", cls.skill_id], standalone_mode=False, ) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "skill", cls.skill_id], standalone_mode=False, ) def test_exit_code_equal_to_zero(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 0 def test_directory_does_not_exist(self): """Test that the directory of the removed skill does not exist.""" assert not Path("skills", self.skill_name).exists() def test_skill_not_present_in_agent_config(self): """Test that the name of the removed skill is not present in the agent configuration file.""" agent_config = aea.configurations.base.AgentConfig.from_json( yaml.safe_load(open(DEFAULT_AEA_CONFIG_FILE)) ) assert self.skill_id not in agent_config.skills @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRemoveSkillFailsWhenSkillIsNotSupported: """Test that the command 'aea remove skill' fails when the skill is not supported.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.skill_id = str(GYM_SKILL_PUBLIC_ID) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "skill", cls.skill_id], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_skill_not_existing(self): """Test that the log error message is fixed. The expected message is: 'The skill '{skill_name}' is not supported.' """ s = "The skill '{}' is not supported.".format(self.skill_id) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRemoveSkillFailsWhenExceptionOccurs: """Test that the command 'aea remove skill' fails when an exception occurs while removing the directory.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.skill_id = str(GYM_SKILL_PUBLIC_ID) cls.skill_name = "gym" os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # change default registry path config = AgentConfig.from_json(yaml.safe_load(open(DEFAULT_AEA_CONFIG_FILE))) config.registry_path = os.path.join(ROOT_DIR, "packages") yaml.safe_dump(dict(config.json), open(DEFAULT_AEA_CONFIG_FILE, "w")) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "skill", cls.skill_id], standalone_mode=False, ) assert result.exit_code == 0 cls.patch = unittest.mock.patch( "shutil.rmtree", side_effect=BaseException("an exception") ) cls.patch.start() cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "remove", "skill", cls.skill_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_remove_key.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea remove-key` sub-command.""" import pytest from click.exceptions import ClickException from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import FETCHAI_PRIVATE_KEY_PATH class BaseTestRemovePrivateKey(AEATestCaseEmpty): """Base test class on removing private keys.""" WITH_CONNECTION: bool @classmethod def setup_class(cls): """Set up class.""" super().setup_class() cls.add_private_key( private_key_filepath=FETCHAI_PRIVATE_KEY_PATH, connection=cls.WITH_CONNECTION, ) def test_remove(self): """Test remove.""" result = self.remove_private_key(connection=self.WITH_CONNECTION) assert result.exit_code == 0 class TestRemoveCryptoPrivateKey(BaseTestRemovePrivateKey): """Test removing a crypto private key.""" WITH_CONNECTION = False class TestRemoveConnectionPrivateKey(BaseTestRemovePrivateKey): """Test removing a connection private key.""" WITH_CONNECTION = True class BaseTestRemovePrivateKeyNegative(AEATestCaseEmpty): """Base test class on removing private keys, when key is not present""" WITH_CONNECTION: bool EXPECTED_ERROR_MSG: str def test_remove(self): """Test remove.""" with pytest.raises(ClickException, match=self.EXPECTED_ERROR_MSG): self.remove_private_key(connection=self.WITH_CONNECTION) class TestRemoveCryptoPrivateKeyNegative(BaseTestRemovePrivateKeyNegative): """Test removing a crypto private key.""" WITH_CONNECTION = False EXPECTED_ERROR_MSG = "There is no key registered with id fetchai." class TestRemoveConnectionPrivateKeyNegative(BaseTestRemovePrivateKeyNegative): """Test removing a connection private key.""" WITH_CONNECTION = True EXPECTED_ERROR_MSG = "There is no connection key registered with id fetchai." ================================================ FILE: tests/test_aea/test_cli/test_reset_password.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI reset_password command.""" from unittest import TestCase, mock from aea.cli import cli from aea.cli.reset_password import _do_password_reset from tests.conftest import CLI_LOG_OPTION, CliRunner @mock.patch("aea.cli.reset_password._do_password_reset") class ResetPasswordTestCase(TestCase): """Test case for CLI reset_password command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_reset_password_positive(self, registry_reset_password_mock): """Test for CLI reset_password positive result.""" email = "email@example.com" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "reset_password", email], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) registry_reset_password_mock.assert_called_once_with(email) class DoPasswordResetTestCase(TestCase): """Test case for _do_password_reset method.""" @mock.patch("aea.cli.reset_password.registry_reset_password") @mock.patch("aea.cli.reset_password.click.echo") def test__do_password_reset_positive(self, echo_mock, registry_reset_password_mock): """Test _do_password_reset for positive result.""" email = "email@example.com" _do_password_reset(email) registry_reset_password_mock.assert_called_once_with(email) echo_mock.assert_called_once_with( "An email with a password reset link was sent to {}".format(email) ) ================================================ FILE: tests/test_aea/test_cli/test_run.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea run` sub-command.""" import os import re import shutil import sys import tempfile import time from pathlib import Path from unittest import TestCase, mock from unittest.mock import patch import pytest import yaml from aea_ledger_fetchai import FetchAICrypto from click import ClickException from pexpect.exceptions import EOF # type: ignore from aea.cli import cli from aea.cli.run import _build_aea, run_aea from aea.configurations.base import ( DEFAULT_AEA_CONFIG_FILE, DEFAULT_CONNECTION_CONFIG_FILE, ) from aea.exceptions import AEAPackageLoadingError from aea.test_tools.test_cases import AEATestCaseEmpty, _get_password_option_args from packages.fetchai.connections.http_client.connection import ( PUBLIC_ID as HTTP_ClIENT_PUBLIC_ID, ) from packages.fetchai.connections.stub.connection import ( PUBLIC_ID as STUB_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.fipa.message import FipaMessage from tests.common.pexpect_popen import PexpectWrapper from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CliRunner, FETCHAI_PRIVATE_KEY_FILE, MAX_FLAKY_RERUNS, ROOT_DIR, ) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_run(password_or_none): """Test that the command 'aea run' works as expected.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) password_options = _get_password_option_args(password_or_none) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier, *password_options], ) assert result.exit_code == 0 result = runner.invoke( cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier, *password_options] ) assert result.exit_code == 0 result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 result = runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.default_connection", str(HTTP_ClIENT_PUBLIC_ID), ], ) assert result.exit_code == 0 try: process = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "run", *password_options], env=os.environ.copy(), maxread=10000, encoding="utf-8", logfile=sys.stdout, ) process.expect("Start processing messages", timeout=10) process.control_c() process.wait_to_complete(10) assert process.returncode == 0 finally: process.terminate() process.wait_to_complete(10) os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) # flaky on Windows @pytest.mark.skipif( sys.version_info < (3, 7), reason="cannot run on 3.6 as AttributeError: 'functools._lru_list_elem' object has no attribute '__class__'", ) def test_run_with_profiling(): """Test profiling data showed.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] ) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier]) assert result.exit_code == 0 result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 result = runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.default_connection", str(HTTP_ClIENT_PUBLIC_ID), ], ) assert result.exit_code == 0 try: process = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "run", "--profiling", "1"], env=os.environ.copy(), maxread=10000, encoding="utf-8", logfile=sys.stdout, ) process.expect("Start processing messages", timeout=10) process.expect("Profiling details", timeout=10) process.control_c() process.wait_to_complete(10) assert process.returncode == 0 finally: process.terminate() process.wait_to_complete(10) os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_run_with_default_connection(): """Test that the command 'aea run' works as expected.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] ) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier]) assert result.exit_code == 0 try: process = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "run"], env=os.environ.copy(), maxread=10000, encoding="utf-8", logfile=sys.stdout, ) process.expect("Start processing messages", timeout=10) process.control_c() process.wait_to_complete(10) assert process.returncode == 0 finally: process.terminate() process.wait_to_complete(10) os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass @pytest.mark.parametrize( argnames=["connection_ids"], argvalues=[ [f"{str(HTTP_ClIENT_PUBLIC_ID)},{str(STUB_CONNECTION_PUBLIC_ID)}"], [f"'{str(HTTP_ClIENT_PUBLIC_ID)}, {str(STUB_CONNECTION_PUBLIC_ID)}'"], [f"{str(HTTP_ClIENT_PUBLIC_ID)},,{str(STUB_CONNECTION_PUBLIC_ID)},"], ], ) def test_run_multiple_connections(connection_ids): """Test that the command 'aea run' works as expected when specifying multiple connections.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] ) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier]) assert result.exit_code == 0 result = runner.invoke( cli, [ *CLI_LOG_OPTION, "add", "--local", "connection", str(STUB_CONNECTION_PUBLIC_ID), ], ) assert result.exit_code == 0 result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 # stub is the default connection, so it should fail result = runner.invoke( cli, [ *CLI_LOG_OPTION, "add", "--local", "connection", str(STUB_CONNECTION_PUBLIC_ID), ], ) assert result.exit_code == 1 process = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "run", "--connections", connection_ids], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) try: process.expect_all(["Start processing messages"], timeout=40) process.control_c() process.expect( EOF, timeout=40, ) process.wait_to_complete(15) assert process.returncode == 0 finally: process.wait_to_complete(15) os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass def test_run_unknown_private_key(): """Test that the command 'aea run' works as expected.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 result = runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.default_connection", str(HTTP_ClIENT_PUBLIC_ID), ], ) assert result.exit_code == 0 # Load the agent yaml file and manually insert the things we need file = open("aea-config.yaml", mode="r") # read all lines at once whole_file = file.read() find_text = "private_key_paths: {}" replace_text = """private_key_paths: fetchai_not: fetchai_private_key.txt""" whole_file = whole_file.replace(find_text, replace_text) # close the file file.close() with open("aea-config.yaml", "w") as f: f.write(whole_file) # Private key needs to exist otherwise doesn't get to code path we are interested in testing with open(FETCHAI_PRIVATE_KEY_FILE, "w") as f: f.write("3801d3703a1fcef18f6bf393fba89245f36b175f4989d8d6e026300dad21e05d") result = runner.invoke( cli, [*CLI_LOG_OPTION, "run", "--connections", str(HTTP_ClIENT_PUBLIC_ID)], standalone_mode=False, ) s = "Unsupported identifier `fetchai_not` in private key paths. Supported identifiers: ['cosmos', 'ethereum', 'fetchai']." assert result.exception.message == s os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass def test_run_fet_private_key_config(): """Test that the command 'aea run' works as expected.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 # Load the agent yaml file and manually insert the things we need file = open("aea-config.yaml", mode="r") # read all lines at once whole_file = file.read() find_text = "private_key_paths: {}" replace_text = """private_key_paths: fetchai: default_private_key_not.txt""" whole_file = whole_file.replace(find_text, replace_text) # close the file file.close() with open("aea-config.yaml", "w") as f: f.write(whole_file) error_msg = "" try: cli.main([*CLI_LOG_OPTION, "run", "--connections", str(HTTP_ClIENT_PUBLIC_ID)]) except SystemExit as e: error_msg = str(e) assert error_msg == "1" os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass def test_run_ethereum_private_key_config(): """Test that the command 'aea run' works as expected.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 # Load the agent yaml file and manually insert the things we need file = open("aea-config.yaml", mode="r") # read all lines at once whole_file = file.read() find_text = "private_key_paths: {}" replace_text = """private_key_paths: ethereum: default_private_key_not.txt""" whole_file = whole_file.replace(find_text, replace_text) # close the file file.close() with open("aea-config.yaml", "w") as f: f.write(whole_file) error_msg = "" try: cli.main([*CLI_LOG_OPTION, "run", "--connections", str(HTTP_ClIENT_PUBLIC_ID)]) except SystemExit as e: error_msg = str(e) assert error_msg == "1" os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) # install depends on network def test_run_with_install_deps(): """Test that the command 'aea run --install-deps' does not crash.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. packages_src = os.path.join(ROOT_DIR, "packages") packages_dst = os.path.join(t, "packages") shutil.copytree(packages_src, packages_dst) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] ) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier]) assert result.exit_code == 0 result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 result = runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.default_connection", str(HTTP_ClIENT_PUBLIC_ID), ], ) assert result.exit_code == 0 try: process = PexpectWrapper( [ sys.executable, "-m", "aea.cli", "-v", "DEBUG", "run", "--install-deps", "--connections", str(HTTP_ClIENT_PUBLIC_ID), ], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) process.expect_all(["Start processing messages..."], timeout=30) time.sleep(1.0) process.control_c() process.wait_to_complete(10) assert process.returncode == 0 finally: process.terminate() process.wait_to_complete(10) os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) # install depends on network def test_run_with_install_deps_and_requirement_file(): """Test that the command 'aea run --install-deps' with requirement file does not crash.""" runner = CliRunner() agent_name = "myagent" cwd = os.getcwd() t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(t, "packages")) os.chdir(t) result = runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "create", "--local", agent_name]) assert result.exit_code == 0 os.chdir(Path(t, agent_name)) result = runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", FetchAICrypto.identifier] ) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "add-key", FetchAICrypto.identifier]) assert result.exit_code == 0 result = runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID)], ) assert result.exit_code == 0 result = runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.default_connection", str(HTTP_ClIENT_PUBLIC_ID), ], ) assert result.exit_code == 0 result = runner.invoke(cli, [*CLI_LOG_OPTION, "freeze"]) assert result.exit_code == 0 Path(t, agent_name, "requirements.txt").write_text(result.output) try: process = PexpectWrapper( [ sys.executable, "-m", "aea.cli", "-v", "DEBUG", "run", "--install-deps", "--connections", str(HTTP_ClIENT_PUBLIC_ID), ], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) process.expect_all(["Start processing messages..."], timeout=30) time.sleep(1.0) process.control_c() process.wait_to_complete(10) assert process.returncode == 0 finally: process.terminate() process.wait_to_complete(10) os.chdir(cwd) try: shutil.rmtree(t) except (OSError, IOError): pass class TestRunFailsWhenExceptionOccursInSkill: """Test that the command 'aea run' fails when an exception occurs in any skill.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(Path(cls.t, cls.agent_name)) result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "add", "--local", "connection", str(HTTP_ClIENT_PUBLIC_ID), ], standalone_mode=False, ) assert result.exit_code == 0 shutil.copytree( Path(ROOT_DIR, "tests", "data", "exception_skill"), Path(cls.t, cls.agent_name, "vendor", "fetchai", "skills", "exception"), ) config_path = Path(cls.t, cls.agent_name, DEFAULT_AEA_CONFIG_FILE) config = yaml.safe_load(open(config_path)) config.setdefault("skills", []).append("fetchai/exception:0.1.0") yaml.safe_dump(config, open(config_path, "w")) try: cli.main( [*CLI_LOG_OPTION, "run", "--connections", str(HTTP_ClIENT_PUBLIC_ID)] ) except SystemExit as e: cls.exit_code = e.code def test_exit_code_equal_to_1(self): """Assert that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.exit_code == 1 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRunFailsWhenConfigurationFileNotFound: """Test that the command 'aea run' fails when the agent configuration file is not found in the current directory.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 Path(cls.t, cls.agent_name, DEFAULT_AEA_CONFIG_FILE).unlink() os.chdir(Path(cls.t, cls.agent_name)) cls.result = cls.runner.invoke( cli, ["--skip-consistency-check", *CLI_LOG_OPTION, "run"], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Assert that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed.""" s = "Agent configuration file '{}' not found in the current directory.".format( DEFAULT_AEA_CONFIG_FILE ) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRunFailsWhenConfigurationFileIsEmpty: """Test that the command 'aea run' fails when the agent configuration file is empty.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 Path(cls.t, cls.agent_name, DEFAULT_AEA_CONFIG_FILE).write_text("") os.chdir(Path(cls.t, cls.agent_name)) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "run"], standalone_mode=False ) def test_exit_code_equal_to_1(self): """Assert that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed.""" s = "Agent configuration file was empty." assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRunFailsWhenConfigurationFileInvalid: """Test that the command 'aea run' fails when the agent configuration file is invalid.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 Path(cls.t, cls.agent_name, DEFAULT_AEA_CONFIG_FILE).write_text( "invalid_attribute: 'foo'\n" ) os.chdir(Path(cls.t, cls.agent_name)) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "run"], standalone_mode=False ) def test_exit_code_equal_to_1(self): """Assert that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed.""" s = "Agent configuration file '{}' is invalid: `ExtraPropertiesError: properties not expected: invalid_attribute`. Please check the documentation.".format( DEFAULT_AEA_CONFIG_FILE ) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRunFailsWhenConnectionNotDeclared(AEATestCaseEmpty): """Test that the command 'aea run --connections' fails when the connection is not declared.""" @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() cls.connection_id = "author/unknown_connection:0.1.0" cls.connection_name = "unknown_connection" cls.generate_private_key(FetchAICrypto.identifier) cls.add_private_key(FetchAICrypto.identifier) def test_run(self): """Run the test.""" expected_message = f"Connection ids ['{self.connection_id}'] not declared in the configuration file." with pytest.raises(ClickException, match=re.escape(expected_message)): self.run_cli_command( "run", "--connections", str(self.connection_id), cwd=self._get_cwd() ) class TestRunFailsWhenConnectionConfigFileNotFound: """Test that the command 'aea run --connections' fails when the connection config file is not found.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.connection_id = HTTP_ClIENT_PUBLIC_ID cls.connection_name = cls.connection_id.name cls.connection_author = cls.connection_id.author cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(Path(cls.t, cls.agent_name)) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(cls.connection_id)], standalone_mode=False, ) assert result.exit_code == 0 result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.default_connection", str(HTTP_ClIENT_PUBLIC_ID), ], ) assert result.exit_code == 0 cls.connection_configuration_path = Path( cls.t, cls.agent_name, "vendor", cls.connection_author, "connections", cls.connection_name, DEFAULT_CONNECTION_CONFIG_FILE, ) cls.connection_configuration_path.unlink() cls.relative_connection_configuration_path = ( cls.connection_configuration_path.relative_to(Path(cls.t, cls.agent_name)) ) cls.result = cls.runner.invoke( cli, [ "--skip-consistency-check", *CLI_LOG_OPTION, "run", "--connections", str(cls.connection_id), ], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Assert that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed.""" s = "Connection configuration not found: {}".format( self.relative_connection_configuration_path ) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRunFailsWhenConnectionNotComplete(AEATestCaseEmpty): """Test that the command 'aea run --connections' fails when the connection.py module is missing.""" @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() cls.connection_id = HTTP_ClIENT_PUBLIC_ID cls.connection_author = cls.connection_id.author cls.connection_name = cls.connection_id.name cls.generate_private_key(FetchAICrypto.identifier) cls.add_private_key(FetchAICrypto.identifier) cls.add_item("connection", str(cls.connection_id)) cls.set_config("agent.default_connection", str(HTTP_ClIENT_PUBLIC_ID)) connection_module_path = Path( cls.t, cls.agent_name, "vendor", "fetchai", "connections", cls.connection_name, "connection.py", ) connection_module_path.unlink() cls.relative_connection_module_path = connection_module_path.relative_to( Path(cls.t, cls.agent_name) ) def test_run(self): """Run the test.""" expected_message = "Package loading error: An error occurred while loading connection {}: Connection module '{}' not found.".format( self.connection_id, self.relative_connection_module_path ) with pytest.raises(ClickException, match=re.escape(expected_message)): self.run_cli_command( "--skip-consistency-check", "run", "--connections", str(self.connection_id), cwd=self._get_cwd(), ) class TestRunFailsWhenConnectionClassNotPresent(AEATestCaseEmpty): """Test that the command 'aea run --connections' fails when the connection is not declared.""" @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() cls.connection_id = str(HTTP_ClIENT_PUBLIC_ID) cls.connection_name = "http_client" cls.generate_private_key(FetchAICrypto.identifier) cls.add_private_key(FetchAICrypto.identifier) cls.add_item("connection", cls.connection_id) cls.set_config("agent.default_connection", cls.connection_id) Path( cls.t, cls.agent_name, "vendor", "fetchai", "connections", cls.connection_name, "connection.py", ).write_text("") def test_run(self): """Run the test.""" expected_message = "Package loading error: An error occurred while loading connection {}: Connection class '{}' not found.".format( self.connection_id, "HTTPClientConnection" ) with pytest.raises(ClickException, match=expected_message): self.run_cli_command( "--skip-consistency-check", "run", "--connections", self.connection_id, cwd=self._get_cwd(), ) class TestRunFailsWhenProtocolConfigFileNotFound: """Test that the command 'aea run' fails when a protocol configuration file is not found.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.connection_id = str(STUB_CONNECTION_PUBLIC_ID) cls.connection_name = "stub" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(Path(cls.t, cls.agent_name)) configuration_file_path = Path( cls.t, cls.agent_name, "vendor", "fetchai", "protocols", "default", "protocol.yaml", ) configuration_file_path.unlink() cls.relative_configuration_file_path = configuration_file_path.relative_to( Path(cls.t, cls.agent_name) ) cls.result = cls.runner.invoke( cli, [ "--skip-consistency-check", *CLI_LOG_OPTION, "run", "--connections", cls.connection_id, ], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Assert that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed.""" s = "Protocol configuration not found: {}".format( self.relative_configuration_file_path ) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRunFailsWhenProtocolNotComplete: """Test that the command 'aea run' fails when a protocol directory is not complete.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(os.path.join(cls.t, cls.agent_name)) result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "add", "--local", "protocol", str(FipaMessage.protocol_id), ], standalone_mode=False, ) assert result.exit_code == 0 # remove protocol configuration configuration_path = Path( cls.t, cls.agent_name, "vendor", "fetchai", "protocols", "fipa", "protocol.yaml", ) configuration_path.unlink() cls.relative_configuration_file_path = configuration_path.relative_to( Path(cls.t, cls.agent_name) ) cls.result = cls.runner.invoke( cli, ["--skip-consistency-check", *CLI_LOG_OPTION, "run"], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Assert that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_log_error_message(self): """Test that the log error message is fixed.""" s = "Protocol configuration not found: {}".format( self.relative_configuration_file_path ) assert self.result.exception.message == s @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass def _raise_click_exception(*args, **kwargs): raise ClickException("message") class RunAEATestCase(TestCase): """Test case for run_aea method.""" def test_run_aea_positive_mock(self): """Test run_aea method for positive result (mocked).""" ctx = mock.Mock() aea = mock.Mock() ctx.config = {"skip_consistency_check": True} with mock.patch("aea.cli.run._build_aea", return_value=aea): run_aea(ctx, ["author/name:0.1.0"], "env_file", False) def test_run_aea_positive_install_deps_mock(self): """Test run_aea method for positive result (mocked), install deps true.""" ctx = mock.Mock() aea = mock.Mock() ctx.config = {"skip_consistency_check": True} with mock.patch("aea.cli.run.do_install"): with mock.patch("aea.cli.run._build_aea", return_value=aea): run_aea(ctx, ["author/name:0.1.0"], "env_file", True) @mock.patch("aea.cli.run._prepare_environment", _raise_click_exception) def test_run_aea_negative(self, *mocks): """Test run_aea method for negative result.""" ctx = mock.Mock() ctx.config = {"skip_consistency_check": True} with self.assertRaises(ClickException): run_aea(ctx, ["author/name:0.1.0"], "env_file", False) def _raise_aea_package_loading_error(*args, **kwargs): raise AEAPackageLoadingError() @mock.patch("aea.cli.run.AEABuilder.from_aea_project", _raise_aea_package_loading_error) class BuildAEATestCase(TestCase): """Test case for run_aea method.""" def test__build_aea_negative(self, *mocks): """Test _build_aea method for negative result.""" with self.assertRaises(ClickException): _build_aea(connection_ids=[], skip_consistency_check=True) class TestExcludeConnection(AEATestCaseEmpty): """Test that the command 'aea run --connections' fails when the connection is not declared.""" @classmethod def setup_class(cls): """Set the test up.""" super().setup_class() cls.connection_id = str(HTTP_ClIENT_PUBLIC_ID) cls.connection2_id = str(STUB_CONNECTION_PUBLIC_ID) cls.generate_private_key(FetchAICrypto.identifier) cls.add_private_key(FetchAICrypto.identifier) cls.add_item("connection", cls.connection_id) cls.add_item("connection", cls.connection2_id) def test_connection_excluded(self): """Test connection excluded.""" def raise_err(*args): raise Exception(args[1]) with pytest.raises(Exception, match="^None$"): with patch("aea.cli.run.run_aea", raise_err): self.run_cli_command( "run", cwd=self._get_cwd(), ) with pytest.raises(Exception, match=f"^..{self.connection2_id}..$"): with patch("aea.cli.run.run_aea", raise_err): self.run_cli_command( "run", "--exclude-connections", self.connection_id, cwd=self._get_cwd(), ) def test_fail_to_exclude_non_existing_connection(self): """Test fail to exclude not defined connection.""" with pytest.raises( Exception, match="Connections to exclude: fake/connection:0.1.0 are not defined in agent configuration", ): self.run_cli_command( "--skip-consistency-check", "run", "--exclude-connections", "fake/connection:0.1.0", cwd=self._get_cwd(), ) def test_fail_to_specify_connections_and_exclude_the_same_time(self): """Test connections specification and exclusion not permited.""" with pytest.raises( Exception, match="Please use only one of --connections or --exclude-connections, not both!", ): self.run_cli_command( "run", "--exclude-connections", self.connection_id, "--connections", self.connection_id, cwd=self._get_cwd(), ) ================================================ FILE: tests/test_aea/test_cli/test_scaffold/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea scaffold` sub-command.""" ================================================ FILE: tests/test_aea/test_cli/test_scaffold/test_connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea scaffold connection` sub-command.""" import json import os import shutil import tempfile import unittest.mock from pathlib import Path import jsonschema import yaml from jsonschema import Draft4Validator, ValidationError from aea.cli import cli from aea.configurations.base import DEFAULT_CONNECTION_CONFIG_FILE from aea.configurations.loader import make_jsonschema_base_uri from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CONNECTION_CONFIGURATION_SCHEMA, CliRunner, ROOT_DIR, ) class TestScaffoldConnection: """Test that the command 'aea scaffold connection' works correctly in correct preconditions.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.schema = json.load(open(CONNECTION_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema, ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # scaffold connection cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "connection", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_0(self): """Test that the exit code is equal to 0.""" assert self.result.exit_code == 0 def test_resource_folder_contains_module_connection(self): """Test that the resource folder contains scaffold connection.py module.""" p = Path( self.t, self.agent_name, "connections", self.resource_name, "connection.py" ) assert p.exists() def test_resource_folder_contains_configuration_file(self): """Test that the resource folder contains a good configuration file.""" p = Path( self.t, self.agent_name, "connections", self.resource_name, DEFAULT_CONNECTION_CONFIG_FILE, ) config_file = yaml.safe_load(open(p)) self.validator.validate(instance=config_file) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldConnectionWithSymlinks: """Test that the command 'aea scaffold connection' works correctly in correct preconditions with the `--with-symlinks` flag.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.schema = json.load(open(CONNECTION_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema, ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # scaffold connection cls.result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "scaffold", "--with-symlinks", "connection", cls.resource_name, ], standalone_mode=False, ) def test_exit_code_equal_to_0(self): """Test that the exit code is equal to 0.""" assert self.result.exit_code == 0 def test_resource_folder_contains_module_connection(self): """Test that the resource folder contains scaffold connection.py module.""" p = Path( self.t, self.agent_name, "connections", self.resource_name, "connection.py" ) assert p.exists() def test_resource_folder_contains_configuration_file(self): """Test that the resource folder contains a good configuration file.""" p = Path( self.t, self.agent_name, "connections", self.resource_name, DEFAULT_CONNECTION_CONFIG_FILE, ) config_file = yaml.safe_load(open(p)) self.validator.validate(instance=config_file) def test_symlinks_exist(self): """Test that the symlinks where created.""" packages = "packages" assert os.path.islink(packages) assert os.readlink(packages) == "vendor" vendor_package = Path("vendor", AUTHOR, "connections", self.resource_name) non_vendor_package = Path("connections", self.resource_name) assert os.path.islink(vendor_package) assert os.readlink(str(vendor_package)) == os.path.join( "..", "..", "..", non_vendor_package ) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldConnectionFailsWhenDirectoryAlreadyExists: """Test that the command 'aea scaffold connection' fails when a folder with 'scaffold' name already.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # create a dummy 'myresource' folder Path(cls.t, cls.agent_name, "connections", cls.resource_name).mkdir( exist_ok=False, parents=True ) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "connection", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_connection_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A connection with name '{connection_name}' already exists. Aborting...' """ s = "A connection with this name already exists. Please choose a different name and try again." assert self.result.exception.message == s def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "connections", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldConnectionFailsWhenConnectionAlreadyExists: """Test that the command 'aea add connection' fails when the connection already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # add connection first time result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "connection", cls.resource_name], standalone_mode=False, ) assert result.exit_code == 0 # scaffold connection with the same connection name cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "connection", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_connection_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A connection with name '{connection_name}' already exists. Aborting...' """ s = "A connection with name '{}' already exists. Aborting...".format( self.resource_name ) assert self.result.exception.message == s def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "connections", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldConnectionFailsWhenConfigFileIsNotCompliant: """Test that the command 'aea scaffold connection' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 # change the dumping of yaml module to raise an exception. cls.patch = unittest.mock.patch( "yaml.dump", side_effect=ValidationError("test error message") ) cls.patch.start() os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "connection", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_configuration_file_not_valid(self): """Test that the log error message is fixed. The expected message is: 'Cannot find connection: '{connection_name}'' """ s = "Error when validating the connection configuration file." assert self.result.exception.message == s def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path( self.t, self.agent_name, "connections", self.resource_name ).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldConnectionFailsWhenExceptionOccurs: """Test that the command 'aea scaffold connection' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 cls.patch = unittest.mock.patch( "shutil.copytree", side_effect=Exception("unknwon exception") ) cls.patch.start() os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "connection", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path( self.t, self.agent_name, "connections", self.resource_name ).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_scaffold/test_decision_maker_handler.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI scaffold generic methods and commands.""" from unittest import TestCase, mock from click import ClickException from aea.cli import cli from aea.cli.scaffold import _scaffold_dm_handler from tests.conftest import CLI_LOG_OPTION, CliRunner from tests.test_aea.test_cli.tools_for_testing import ContextMock @mock.patch("aea.cli.scaffold._scaffold_dm_handler") @mock.patch("aea.cli.utils.decorators._check_aea_project") class ScaffoldDecisionMakerHandlerTestCase(TestCase): """Test case for CLI scaffold decision maker handler command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_scaffold_decision_maker_handler_command_positive(self, *mocks): """Test for CLI scaffold decision maker handler command for positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "decision-maker-handler"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) def _raise_exception(*args): raise Exception() class ScaffoldDmHandlerTestCase(TestCase): """Test case for _scaffold_dm_handler method.""" def test__scaffold_dm_handler_already_exists(self): """Test _scaffold_dm_handler method dm handler already exists result.""" dm_handler = {"dm": "handler"} ctx = ContextMock() ctx.agent_config.decision_maker_handler = dm_handler with self.assertRaises(ClickException) as cm: _scaffold_dm_handler(ctx) self.assertEqual( "A decision maker handler specification already exists. Aborting...", str(cm.exception), ) @mock.patch("aea.cli.scaffold.shutil.copyfile", _raise_exception) @mock.patch("aea.cli.scaffold.os.remove") def test__scaffold_dm_handler_exception(self, os_remove_mock, *mocks): """Test _scaffold_dm_handler method exception raised result.""" dm_handler = {} ctx = ContextMock() ctx.agent_config.decision_maker_handler = dm_handler with self.assertRaises(ClickException): _scaffold_dm_handler(ctx) os_remove_mock.assert_called_once() @mock.patch("aea.cli.scaffold.shutil.copyfile") @mock.patch("aea.cli.scaffold.os.remove") @mock.patch("aea.cli.scaffold.open_file", mock.mock_open()) @mock.patch("aea.cli.scaffold.Path", return_value="Path") def test__scaffold_dm_handler_positive(self, *mocks): """Test _scaffold_dm_handler method for positive result.""" dm_handler = {} ctx = ContextMock() ctx.agent_config.decision_maker_handler = dm_handler ctx.agent_loader.dump = mock.Mock() _scaffold_dm_handler(ctx) ctx.agent_loader.dump.assert_called_once() ================================================ FILE: tests/test_aea/test_cli/test_scaffold/test_error_handler.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI scaffold generic methods and commands.""" from unittest import TestCase, mock from click import ClickException from aea.cli import cli from aea.cli.scaffold import _scaffold_error_handler from tests.conftest import CLI_LOG_OPTION, CliRunner from tests.test_aea.test_cli.tools_for_testing import ContextMock @mock.patch("aea.cli.scaffold._scaffold_error_handler") @mock.patch("aea.cli.utils.decorators._check_aea_project") class ScaffoldErrorHandlerTestCase(TestCase): """Test case for CLI scaffold error handler command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_scaffold_error_handler_command_positive(self, *mocks): """Test for CLI scaffold error handler command for positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "error-handler"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) def _raise_exception(*args): raise Exception() class ScaffoldErrHandlerTestCase(TestCase): """Test case for _scaffold_error_handler method.""" def test__scaffold_error_handler_already_exists(self): """Test _scaffold_error_handler method dm handler already exists result.""" err_handler = {"err": "handler"} ctx = ContextMock() ctx.agent_config.error_handler = err_handler with self.assertRaises(ClickException) as cm: _scaffold_error_handler(ctx) self.assertEqual( "A error handler specification already exists. Aborting...", str(cm.exception), ) @mock.patch("aea.cli.scaffold.shutil.copyfile", _raise_exception) @mock.patch("aea.cli.scaffold.os.remove") def test__scaffold_error_handler_exception(self, os_remove_mock, *mocks): """Test _scaffold_error_handler method exception raised result.""" err_handler = {} ctx = ContextMock() ctx.agent_config.error_handler = err_handler with self.assertRaises(ClickException): _scaffold_error_handler(ctx) os_remove_mock.assert_called_once() @mock.patch("aea.cli.scaffold.shutil.copyfile") @mock.patch("aea.cli.scaffold.os.remove") @mock.patch("aea.cli.scaffold.open_file", mock.mock_open()) @mock.patch("aea.cli.scaffold.Path", return_value="Path") def test__scaffold_error_handler_positive(self, *mocks): """Test _scaffold_error_handler method for positive result.""" err_handler = {} ctx = ContextMock() ctx.agent_config.error_handler = err_handler ctx.agent_loader.dump = mock.Mock() _scaffold_error_handler(ctx) ctx.agent_loader.dump.assert_called_once() ================================================ FILE: tests/test_aea/test_cli/test_scaffold/test_generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for CLI scaffold generic methods and commands.""" from unittest import TestCase, mock from aea.cli import cli from tests.conftest import CLI_LOG_OPTION, CliRunner @mock.patch("aea.cli.scaffold.scaffold_item") @mock.patch("aea.cli.utils.decorators._check_aea_project") class ScaffoldContractCommandTestCase(TestCase): """Test case for CLI scaffold contract command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_scaffold_contract_command_positive(self, *mocks): """Test for CLI scaffold contract command for positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "contract", "contract_name"], standalone_mode=False, ) self.assertEqual(result.exit_code, 0) ================================================ FILE: tests/test_aea/test_cli/test_scaffold/test_protocols.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea scaffold protocol` sub-command.""" import filecmp import json import os import shutil import tempfile import unittest.mock from pathlib import Path from unittest.mock import patch import jsonschema import yaml from jsonschema import Draft4Validator, ValidationError from aea import AEA_DIR from aea.cli import cli from aea.configurations.base import DEFAULT_PROTOCOL_CONFIG_FILE from aea.configurations.loader import make_jsonschema_base_uri from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CliRunner, PROTOCOL_CONFIGURATION_SCHEMA, ROOT_DIR, ) class TestScaffoldProtocol: """Test that the command 'aea scaffold protocol' works correctly in correct preconditions.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.schema = json.load(open(PROTOCOL_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema, ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # scaffold protocol with patch("click.confirm", return_value=True) as confirm_mock: cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "protocol", cls.resource_name], standalone_mode=False, ) confirm_mock.assert_called_once_with( "We highly recommend auto-generating protocols with the aea generate command. Do you really want to continue scaffolding?" ) def test_exit_code_equal_to_0(self): """Test that the exit code is equal to 0.""" assert self.result.exit_code == 0 def test_resource_folder_contains_module_message(self): """Test that the resource folder contains scaffold message.py module.""" p = Path(self.t, self.agent_name, "protocols", self.resource_name, "message.py") original = Path(AEA_DIR, "protocols", "scaffold", "message.py") assert filecmp.cmp(p, original) def test_resource_folder_contains_module_protocol(self): """Test that the resource folder contains scaffold protocol.py module.""" p = Path( self.t, self.agent_name, "protocols", self.resource_name, "serialization.py" ) original = Path(AEA_DIR, "protocols", "scaffold", "serialization.py") assert filecmp.cmp(p, original) def test_resource_folder_contains_configuration_file(self): """Test that the resource folder contains a good configuration file.""" p = Path( self.t, self.agent_name, "protocols", self.resource_name, DEFAULT_PROTOCOL_CONFIG_FILE, ) config_file = yaml.safe_load(open(p)) self.validator.validate(instance=config_file) @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldProtocolFailsWhenDirectoryAlreadyExists: """Test that the command 'aea scaffold protocol' fails when a folder with 'scaffold' name already.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # create a dummy 'myresource' folder Path(cls.t, cls.agent_name, "protocols", cls.resource_name).mkdir( exist_ok=False, parents=True ) with patch("click.confirm", return_value=True): cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "protocol", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A protocol with name '{protocol_name}' already exists. Aborting...' """ s = "A protocol with this name already exists. Please choose a different name and try again." assert self.result.exception.message == s def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "protocols", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldProtocolFailsWhenProtocolAlreadyExists: """Test that the command 'aea add protocol' fails when the protocol already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # add protocol first time with patch("click.confirm", return_value=True): result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "protocol", cls.resource_name], standalone_mode=False, ) assert result.exit_code == 0 # scaffold protocol with the same protocol name cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "protocol", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_protocol_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A protocol with name '{protocol_name}' already exists. Aborting...' """ s = "A protocol with name '{}' already exists. Aborting...".format( self.resource_name ) assert s in self.result.exception.message def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "protocols", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldProtocolFailsWhenConfigFileIsNotCompliant: """Test that the command 'aea scaffold protocol' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 # change the dumping of yaml module to raise an exception. cls.patch = unittest.mock.patch( "yaml.dump", side_effect=ValidationError("test error message") ) cls.patch.start() os.chdir(cls.agent_name) with patch("click.confirm", return_value=True): cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "protocol", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_configuration_file_not_valid(self): """Test that the log error message is fixed. The expected message is: 'Cannot find protocol: '{protocol_name}' """ s = "Error when validating the protocol configuration file." assert self.result.exception.message == s def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path( self.t, self.agent_name, "protocols", self.resource_name ).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldProtocolFailsWhenExceptionOccurs: """Test that the command 'aea scaffold protocol' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 cls.patch = unittest.mock.patch( "shutil.copytree", side_effect=Exception("unknwon exception") ) cls.patch.start() os.chdir(cls.agent_name) with patch("click.confirm", return_value=True): cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "protocol", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path( self.t, self.agent_name, "protocols", self.resource_name ).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_scaffold/test_skills.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea scaffold skill` sub-command.""" import filecmp import json import os import re import shutil import tempfile import unittest.mock from pathlib import Path import jsonschema import yaml from jsonschema import Draft4Validator, ValidationError from aea import AEA_DIR from aea.cli import cli from aea.configurations.base import DEFAULT_SKILL_CONFIG_FILE, DEFAULT_VERSION from aea.configurations.loader import make_jsonschema_base_uri from tests.conftest import ( AUTHOR, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CliRunner, ROOT_DIR, SKILL_CONFIGURATION_SCHEMA, ) class TestScaffoldSkill: """Test that the command 'aea scaffold skill' works correctly in correct preconditions.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) cls.schema = json.load(open(SKILL_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema, ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # scaffold skill cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "skill", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_0(self): """Test that the exit code is equal to 0.""" assert self.result.exit_code == 0 def test_resource_folder_contains_module_handlers(self): """Test that the resource folder contains scaffold handlers.py module.""" p = Path(self.t, self.agent_name, "skills", self.resource_name, "handlers.py") original = Path(AEA_DIR, "skills", "scaffold", "handlers.py") assert filecmp.cmp(p, original) def test_resource_folder_contains_module_behaviours(self): """Test that the resource folder contains scaffold behaviours.py module.""" p = Path(self.t, self.agent_name, "skills", self.resource_name, "behaviours.py") original = Path(AEA_DIR, "skills", "scaffold", "behaviours.py") assert filecmp.cmp(p, original) def test_resource_folder_contains_module_model(self): """Test that the resource folder contains scaffold my_model.py module.""" p = Path(self.t, self.agent_name, "skills", self.resource_name, "my_model.py") original = Path(AEA_DIR, "skills", "scaffold", "my_model.py") assert filecmp.cmp(p, original) def test_resource_folder_contains_configuration_file(self): """Test that the resource folder contains a good configuration file.""" p = Path( self.t, self.agent_name, "skills", self.resource_name, DEFAULT_SKILL_CONFIG_FILE, ) config_file = yaml.safe_load(open(p)) self.validator.validate(instance=config_file) def test_init_module_contains_new_public_id(self): """Test that the PUBLIC ID variable in the init module is replaced correctly.""" p = Path(self.t, self.agent_name, "skills", self.resource_name, "__init__.py") init_module_content = p.read_text() expected_public_id = f"{AUTHOR}/{self.resource_name}:{DEFAULT_VERSION}" matches = re.findall( rf'^PUBLIC_ID = PublicId\.from_str\("{expected_public_id}"\)$', init_module_content, re.MULTILINE, ) assert len(matches) == 1 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldSkillFailsWhenDirectoryAlreadyExists: """Test that the command 'aea scaffold skill' fails when a folder with 'scaffold' name already.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # create a dummy 'myresource' folder Path(cls.t, cls.agent_name, "skills", cls.resource_name).mkdir( exist_ok=False, parents=True ) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "skill", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_skill_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A skill with name '{skill_name}' already exists. Aborting...' """ s = "A skill with this name already exists. Please choose a different name and try again." assert self.result.exception.message == s def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "skills", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldSkillFailsWhenSkillAlreadyExists: """Test that the command 'aea add skill' fails when the skill already exists.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # add skill first time result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "skill", cls.resource_name], standalone_mode=False, ) assert result.exit_code == 0 # scaffold skill with the same skill name cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "skill", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_error_message_skill_already_existing(self): """Test that the log error message is fixed. The expected message is: 'A skill with name '{skill_name}' already exists. Aborting...' """ s = "A skill with name '{}' already exists. Aborting...".format( self.resource_name ) assert self.result.exception.message == s def test_resource_directory_exists(self): """Test that the resource directory still exists. This means that after every failure, we make sure we restore the previous state. """ assert Path(self.t, self.agent_name, "skills", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldSkillFailsWhenConfigFileIsNotCompliant: """Test that the command 'aea scaffold skill' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 # change the dumping of yaml module to raise an exception. cls.patch = unittest.mock.patch( "yaml.dump", side_effect=ValidationError("test error message"), ) cls.patch.start() os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "skill", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_configuration_file_not_valid(self): """Test that the log error message is fixed. The expected message is: 'Cannot find skill: '{skill_name}' """ s = "Error when validating the skill configuration file." assert self.result.exception.message == s def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path(self.t, self.agent_name, "skills", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestScaffoldSkillFailsWhenExceptionOccurs: """Test that the command 'aea scaffold skill' fails when the configuration file is not compliant with the schema.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.resource_name = "myresource" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 cls.patch = unittest.mock.patch( "shutil.copytree", side_effect=Exception("unknwon exception") ) cls.patch.start() os.chdir(cls.agent_name) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "scaffold", "skill", cls.resource_name], standalone_mode=False, ) def test_exit_code_equal_to_1(self): """Test that the exit code is equal to 1 (i.e. catchall for general errors).""" assert self.result.exit_code == 1 def test_resource_directory_does_not_exists(self): """Test that the resource directory does not exist. This means that after every failure, we make sure we restore the previous state. """ assert not Path(self.t, self.agent_name, "skills", self.resource_name).exists() @classmethod def teardown_class(cls): """Tear the test down.""" cls.patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_search.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea search` sub-command.""" import json import os import shutil import tempfile from pathlib import Path from unittest import TestCase, mock import jsonschema import pytest from jsonschema import Draft4Validator from aea.cli import cli from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_SKILL_PUBLIC_ID from packages.fetchai.skills.error import PUBLIC_ID as ERROR_SKILL_PUBLIC_ID from tests.conftest import ( AGENT_CONFIGURATION_SCHEMA, AUTHOR, CLI_LOG_OPTION, CONFIGURATION_SCHEMA_DIR, CliRunner, MAX_FLAKY_RERUNS, ROOT_DIR, ) from tests.test_aea.test_cli.constants import FORMAT_ITEMS_SAMPLE_OUTPUT class TestSearchProtocolsLocal: """Test that the command 'aea search protocols' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT) def test_correct_output_default_registry(self, _): """Test that the command has printed the correct output when using the default registry.""" self.result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "--local", "protocols"], standalone_mode=False, ) assert self.result.output == ( 'Searching for ""...\n' "Protocols found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) class TestSearchContractsLocal(TestCase): """Test that the command 'aea search contracts' works as expected.""" def setUp(self): """Set the test up.""" self.runner = CliRunner() @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT) @mock.patch("aea.cli.search._search_items_locally", return_value=["item1"]) def test_search_contracts_positive(self, *mocks): """Test search contracts command positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "--local", "contracts"], standalone_mode=False, ) assert result.output == ( 'Searching for ""...\n' "Contracts found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT) @mock.patch( "aea.cli.search.request_api", return_value={"results": ["item1"], "count": 1} ) def test_search_contracts_registry_positive(self, *mocks): """Test search contracts in registry command positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "contracts"], standalone_mode=False, ) assert result.output == ( 'Searching for ""...\n' "Contracts found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) class TestSearchConnectionsLocal: """Test that the command 'aea search connections' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT) def test_correct_output_default_registry(self, _): """Test that the command has printed the correct output when using the default registry.""" self.result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "--local", "connections"], standalone_mode=False, ) assert self.result.output == ( 'Searching for ""...\n' "Connections found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) class TestSearchSkillsLocal: """Test that the command 'aea search skills' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT) def test_correct_output_default_registry(self, _): """Test that the command has printed the correct output when using the default registry.""" self.result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "--local", "skills"], standalone_mode=False ) assert self.result.output == ( 'Searching for ""...\n' "Skills found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) class TestSearchAgentsLocal: """Test that the command 'aea search agents' works as expected.""" @classmethod def setup_class(cls): """Set the test up.""" cls.schema = json.load(open(AGENT_CONFIGURATION_SCHEMA)) cls.resolver = jsonschema.RefResolver( "file://{}/".format(Path(CONFIGURATION_SCHEMA_DIR).absolute()), cls.schema ) cls.validator = Draft4Validator(cls.schema, resolver=cls.resolver) cls.cwd = os.getcwd() cls.runner = CliRunner() cls.t = tempfile.mkdtemp() dir_path = Path("packages") tmp_dir = cls.t / dir_path src_dir = cls.cwd / Path(ROOT_DIR, dir_path) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(cls.t) cls.cli_config_file = f"{cls.t}/cli_config.yaml" cls.cli_config_patch = mock.patch( "aea.cli.utils.config.CLI_CONFIG_PATH", cls.cli_config_file ) cls.cli_config_patch.start() result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", "myagent"], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(Path(cls.t, "myagent")) result = cls.runner.invoke( cli, [ *CLI_LOG_OPTION, "config", "set", "agent.description", "Some description.", ], standalone_mode=False, catch_exceptions=False, ) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "publish", "--local"], standalone_mode=False ) assert result.exit_code == 0 cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "--local", "agents"], standalone_mode=False ) def test_correct_output_default_registry(self): """Test that the command has printed the correct output when using the default registry.""" expected = [ ('Searching for ""...\n' "Agents found:\n\n"), ( "------------------------------\n" "Public ID: default_author/myagent:0.1.0\n" "Name: myagent\n" "Description: Some description.\n" "Author: default_author\n" "Version: 0.1.0\n" "------------------------------\n\n" ), ] assert [strings in self.result.output for strings in expected] @classmethod def teardown_class(cls): """Tear the test down.""" cls.cli_config_patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @mock.patch( "aea.cli.search.request_api", return_value={"results": ["correct", "results"], "count": 2}, ) @mock.patch("aea.cli.search.format_items", return_value=FORMAT_ITEMS_SAMPLE_OUTPUT) class RegistrySearchTestCase(TestCase): """Test case for search --registry CLI command.""" def setUp(self): """Set it up.""" self.runner = CliRunner() def test_search_connections_positive(self, format_items_mock, request_api_mock): """Test for CLI search --registry connections positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "connections", "--query=some"], standalone_mode=False, ) expected_output = ( 'Searching for "some"...\n' "Connections found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) self.assertEqual(result.output, expected_output) request_api_mock.assert_called_once_with( "GET", "/connections", params={"search": "some", "page": 1} ) format_items_mock.assert_called_once_with(["correct", "results"]) def test_search_agents_positive(self, format_items_mock, request_api_mock): """Test for CLI search --registry agents positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "agents", "--query=some"], standalone_mode=False, ) expected_output = ( 'Searching for "some"...\n' "Agents found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) self.assertEqual(result.output, expected_output) request_api_mock.assert_called_once_with( "GET", "/agents", params={"search": "some", "page": 1} ) format_items_mock.assert_called_once_with(["correct", "results"]) def test_search_protocols_positive(self, format_items_mock, request_api_mock): """Test for CLI search --registry protocols positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "protocols", "--query=some"], standalone_mode=False, ) expected_output = ( 'Searching for "some"...\n' "Protocols found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) self.assertEqual(result.output, expected_output) request_api_mock.assert_called_once_with( "GET", "/protocols", params={"search": "some", "page": 1} ) format_items_mock.assert_called_once_with(["correct", "results"]) def test_search_skills_positive(self, format_items_mock, request_api_mock): """Test for CLI search --registry skills positive result.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "skills", "--query=some"], standalone_mode=False, ) expected_output = ( 'Searching for "some"...\n' "Skills found:\n\n" "{}\n".format(FORMAT_ITEMS_SAMPLE_OUTPUT) ) self.assertEqual(result.output, expected_output) request_api_mock.assert_called_once_with( "GET", "/skills", params={"search": "some", "page": 1} ) format_items_mock.assert_called_once_with(["correct", "results"]) class TestSearchWithRegistryInSubfolderLocal: """Test the search when the registry directory is a subfolder of the current path.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.runner = CliRunner() cls.t = tempfile.mkdtemp() os.chdir(cls.t) # copy the packages directory in the temporary test directory. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) # remove all the skills except the echo and error skill (to make testing easier). for p in Path(cls.t, "packages", "fetchai", "skills").iterdir(): if p.name not in ["echo", "error"] and p.is_dir(): shutil.rmtree(p) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "--local", "skills"], standalone_mode=False ) def test_exit_code_equal_to_zero(self): """Test that the exit code is equal to 0 (i.e. success).""" assert self.result.exit_code == 0 def test_correct_output( self, ): """Test that the command has printed the correct output..""" public_id_echo = ECHO_SKILL_PUBLIC_ID public_id_error = ERROR_SKILL_PUBLIC_ID expected = [ ('Searching for ""...\n' "Skills found:\n\n"), ( "------------------------------\n" "Public ID: {}\n" "Name: echo\n" "Description: The echo skill implements simple echo functionality.\n" "Author: fetchai\n" "Version: {}\n" "------------------------------\n" ).format(str(public_id_echo), str(public_id_echo.version)), ( "------------------------------\n" "Public ID: {}\n" "Name: error\n" "Description: The error skill implements basic error handling required by all AEAs.\n" "Author: fetchai\n" "Version: {}\n" "------------------------------\n\n" ).format( str(public_id_error), str(public_id_error.version), ), ] assert [strings in self.result.output for strings in expected] @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestSearchInAgentDirectoryLocal: """Test the search when we are in the agent directory.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.runner = CliRunner() cls.t = tempfile.mkdtemp() os.chdir(cls.t) # copy the packages directory in the temporary test directory. shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) # remove all the skills except the echo and error skill (to make testing easier). for p in Path(cls.t, "packages", "fetchai", "skills").iterdir(): if p.name not in ["echo", "error"] and p.is_dir(): shutil.rmtree(p) result = cls.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 # create an AEA proejct and enter into it. result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", "myagent"], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(Path(cls.t, "myagent")) cls.result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "search", "--local", "skills"], standalone_mode=False ) def test_exit_code_equal_to_zero(self): """Test that the exit code is equal to 0 (i.e. success).""" assert self.result.exit_code == 0 def test_correct_output( self, ): """Test that the command has printed the correct output..""" public_id_echo = ECHO_SKILL_PUBLIC_ID public_id_error = ERROR_SKILL_PUBLIC_ID expected = [ ('Searching for ""...\n' "Skills found:\n\n"), ( "------------------------------\n" "Public ID: {}\n" "Name: echo\n" "Description: The echo skill implements simple echo functionality.\n" "Author: fetchai\n" "Version: {}\n" "------------------------------\n" ).format(str(public_id_echo), str(public_id_echo.version)), ( "------------------------------\n" "Public ID: {}\n" "Name: error\n" "Description: The error skill implements basic error handling required by all AEAs.\n" "Author: fetchai\n" "Version: {}\n" "------------------------------\n\n" ).format( str(public_id_error), str(public_id_error.version), ), ] assert [strings in self.result.output for strings in expected] @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_cli/test_transfer.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for commands in aea.cli.transfer module.""" import random import string from pathlib import Path from typing import List, Optional from unittest.mock import patch import pytest from aea_ledger_cosmos import CosmosCrypto from aea_ledger_fetchai import FetchAICrypto from click.exceptions import ClickException from aea.cli.transfer import wait_tx_settled from aea.cli.utils.package_utils import try_get_balance from aea.configurations.manager import AgentConfigManager from aea.crypto.helpers import get_wallet_from_agent_config, private_key_verify from aea.helpers.base import cd from aea.test_tools.test_cases import AEATestCaseEmpty from tests.common.utils import wait_for_condition from tests.conftest import MAX_FLAKY_RERUNS class TestCliTransferFetchAINetwork(AEATestCaseEmpty): """Test cli transfer command.""" LEDGER_ID = FetchAICrypto.identifier ANOTHER_LEDGER_ID = CosmosCrypto.identifier PASSWORD: Optional[str] = None @classmethod def setup_class(cls): """Set up the test class.""" super(TestCliTransferFetchAINetwork, cls).setup_class() cls.agent_name2 = "agent_" + "".join( random.choices(string.ascii_lowercase, k=5) # nosec ) cls.create_agents(cls.agent_name2) cls.gen_key(cls.agent_name) cls.gen_key(cls.agent_name2) @classmethod def gen_key(cls, agent_name: str) -> None: """Generate crypto key.""" cls.set_agent_context(agent_name) key_file = f"{cls.LEDGER_ID}.key" password_options = cls.get_password_args(cls.PASSWORD) assert cls.run_cli_command( "generate-key", cls.LEDGER_ID, key_file, *password_options, cwd=cls._get_cwd(), ) assert cls.run_cli_command( "add-key", cls.LEDGER_ID, key_file, *password_options, cwd=cls._get_cwd() ) @classmethod def get_password_args(cls, password: Optional[str]) -> List[str]: """Get password arguments.""" return [] if password is None else ["--password", password] def get_balance(self) -> int: """Get balance for current agent.""" with cd(self._get_cwd()): agent_config = AgentConfigManager.verify_private_keys( Path("."), substitude_env_vars=False, private_key_helper=private_key_verify, password=self.PASSWORD, ).agent_config wallet = get_wallet_from_agent_config(agent_config, password=self.PASSWORD) return int(try_get_balance(agent_config, wallet, self.LEDGER_ID)) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_integration(self): """Perform integration tests of cli transfer command with real transfer.""" self.set_agent_context(self.agent_name2) password_option = self.get_password_args(self.PASSWORD) agent2_original_balance = self.get_balance() agent2_address = self.get_address(self.LEDGER_ID, self.PASSWORD) self.set_agent_context(self.agent_name) agent1_original_balance = self.get_balance() self.generate_wealth(password=self.PASSWORD) wait_for_condition( lambda: self.get_balance() > agent1_original_balance, timeout=15, period=0.1 ) agent1_balance = self.get_balance() assert agent1_balance > agent1_original_balance amount = round(agent1_balance / 10) fee = round(agent1_balance / 20) self.invoke( "transfer", self.LEDGER_ID, agent2_address, str(amount), str(fee), "-y", *password_option, ) wait_for_condition( lambda: self.get_balance() == (agent1_balance - amount - fee), timeout=15, period=0.1, ) self.set_agent_context(self.agent_name2) wait_for_condition( lambda: self.get_balance() == (agent2_original_balance + amount), timeout=15, period=0.1, ) @patch("aea.cli.transfer.do_transfer", return_value="some_digest") @patch("aea.cli.transfer.click.confirm", return_value=None) @patch("aea.cli.transfer.wait_tx_settled", return_value=None) def test_yes_option_enabled( self, wait_tx_settled_mock, confirm_mock, do_transfer_mock ): """Test yes option is enabled.""" password_option = self.get_password_args(self.PASSWORD) self.invoke( "transfer", self.LEDGER_ID, self.get_address(self.LEDGER_ID, self.PASSWORD), "100000", "100", "-y", *password_option, ) confirm_mock.assert_not_called() @patch("aea.cli.transfer.do_transfer", return_value="some_digest") @patch("aea.cli.transfer.click.confirm", return_value=None) @patch("aea.cli.transfer.wait_tx_settled", return_value=None) def test_yes_option_disabled( self, wait_tx_settled_mock, confirm_mock, do_transfer_mock ): """Test yes option is disabled.""" password_option = self.get_password_args(self.PASSWORD) self.invoke( "transfer", self.LEDGER_ID, self.get_address(self.LEDGER_ID, self.PASSWORD), "100000", "100", *password_option, ) confirm_mock.assert_called_once() @patch("aea.cli.transfer.do_transfer", return_value="some_digest") @patch("aea.cli.transfer.click.confirm", return_value=None) @patch("aea.cli.transfer.wait_tx_settled", return_value=None) def test_sync_option_enabled( self, wait_tx_settled_mock, confirm_mock, do_transfer_mock ): """Test sync option is enabled.""" password_option = self.get_password_args(self.PASSWORD) self.invoke( "transfer", self.LEDGER_ID, self.get_address(self.LEDGER_ID, self.PASSWORD), "100000", "100", "-y", *password_option, ) wait_tx_settled_mock.assert_not_called() @patch("aea.cli.transfer.do_transfer", return_value="some_digest") @patch("aea.cli.transfer.click.confirm", return_value=None) @patch("aea.cli.transfer.wait_tx_settled", return_value=None) def test_sync_option_disabled( self, wait_tx_settled_mock, confirm_mock, do_transfer_mock ): """Test sync option is disabled.""" password_option = self.get_password_args(self.PASSWORD) self.invoke( "transfer", self.LEDGER_ID, self.get_address(self.LEDGER_ID, self.PASSWORD), "100000", "100", "-y", "--sync", *password_option, ) wait_tx_settled_mock.assert_called_once() @patch("aea.cli.transfer.do_transfer", return_value=None) @patch("aea.cli.transfer.click.confirm", return_value=None) @patch("aea.cli.transfer.wait_tx_settled", return_value=None) def test_failed_on_send(self, wait_tx_settled_mock, confirm_mock, do_transfer_mock): """Test fail to send a transaction.""" with pytest.raises(ClickException, match=r"Failed to send a transaction!"): password_option = self.get_password_args(self.PASSWORD) self.invoke( "transfer", self.LEDGER_ID, self.get_address(self.LEDGER_ID, self.PASSWORD), "100000", "100", *password_option, ) @patch("aea.cli.transfer.click.confirm", return_value=None) @patch("aea.cli.transfer.wait_tx_settled", return_value=None) def test_no_wallet_registered(self, wait_tx_settled_mock, confirm_mock): """Test no wallet for crypto id registered.""" password_option = self.get_password_args(self.PASSWORD) with pytest.raises( ClickException, match=r"No private key registered for `.*` in wallet!" ): self.invoke( "transfer", self.ANOTHER_LEDGER_ID, self.get_address(self.LEDGER_ID, self.PASSWORD), "100000", "100", *password_option, ) @patch("aea.cli.transfer.try_get_balance", return_value=10) @patch("aea.cli.transfer.click.confirm", return_value=None) @patch("aea.cli.transfer.wait_tx_settled", return_value=None) def test_balance_too_low( self, wait_tx_settled_mock, confirm_mock, do_transfer_mock ): """Test balance too low exception.""" password_option = self.get_password_args(self.PASSWORD) with pytest.raises( ClickException, match=r"Balance is not enough! Available=[0-9]+, required=[0-9]+!", ): self.invoke( "transfer", self.LEDGER_ID, self.get_address(self.LEDGER_ID, self.PASSWORD), "100000", "100", *password_option, ) @patch( "aea.cli.transfer.LedgerApis.is_transaction_settled", side_effects=[False, True] ) def test_wait_tx_settled_ok(self, is_transaction_settled_mock): """Test wait tx settle is ok.""" wait_tx_settled("some", "some", timeout=4) @patch("aea.cli.transfer.LedgerApis.is_transaction_settled", return_value=False) def test_wait_tx_settled_timeout(self, is_transaction_settled_mock): """Test wait tx settle fails with timeout error.""" with pytest.raises(TimeoutError): wait_tx_settled("some", "some", timeout=0.5) class TestCliTransferFetchAINetworkWithPassword(TestCliTransferFetchAINetwork): """Test cli transfer command, with '--password' option.""" PASSWORD = "fake-password" # nosec ================================================ FILE: tests/test_aea/test_cli/test_upgrade.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the `aea add connection` sub-command.""" import filecmp import os import shutil import tempfile from contextlib import contextmanager from pathlib import Path from typing import List, Set, cast from unittest import mock from unittest.mock import MagicMock, patch import pytest from click.exceptions import ClickException from click.testing import Result from packaging.version import Version import aea from aea import get_current_aea_version from aea.cli import cli from aea.cli.registry.utils import get_latest_version_available_in_registry from aea.cli.upgrade import ItemRemoveHelper from aea.cli.utils.config import load_item_config from aea.configurations.base import ( AgentConfig, ComponentId, ComponentType, DEFAULT_AEA_CONFIG_FILE, PackageId, PackageType, PublicId, ) from aea.configurations.constants import DEFAULT_README_FILE, DEFAULT_VERSION from aea.configurations.loader import ConfigLoader, load_component_configuration from aea.helpers.base import cd, compute_specifier_from_version from aea.test_tools.test_cases import AEATestCaseEmpty, BaseAEATestCase from packages.fetchai.connections import oef from packages.fetchai.connections.oef.connection import PUBLIC_ID as OEF_PUBLIC_ID from packages.fetchai.connections.soef.connection import PUBLIC_ID as SOEF_PUBLIC_ID from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as ERC1155_PUBLIC_ID from packages.fetchai.protocols.default import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_SKILL_PUBLIC_ID from packages.fetchai.skills.error import PUBLIC_ID as ERROR_SKILL_PUBLIC_ID from tests.common.mocks import RegexComparator from tests.common.utils import are_dirs_equal, dircmp_recursive from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner class BaseTestCase: """Base test case class with setup and teardown and some utils.""" ITEM_TYPE = "connection" ITEM_PUBLIC_ID = SOEF_PUBLIC_ID LOCAL: List[str] = ["--local"] DEPENDENCY_TYPE = "protocol" DEPENDENCY_PUBLIC_ID = OefSearchMessage.protocol_id @staticmethod def loader() -> ConfigLoader: """Return Agent config loader.""" return ConfigLoader.from_configuration_type(PackageType.AGENT) def load_config(self) -> AgentConfig: """Load AgentConfig from current directory.""" agent_loader = self.loader() path = Path(DEFAULT_AEA_CONFIG_FILE) with path.open(mode="r", encoding="utf-8") as fp: agent_config = agent_loader.load(fp) return agent_config def load_mock_context(self) -> MagicMock: """Load mock context.""" context_mock = MagicMock(agent_config=self.load_config()) return context_mock def dump_config(self, agent_config: AgentConfig) -> None: """Dump AgentConfig to current directory.""" agent_loader = self.loader() path = Path(DEFAULT_AEA_CONFIG_FILE) with path.open(mode="w", encoding="utf-8") as fp: agent_loader.dump(agent_config, fp) @classmethod def setup(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) os.chdir(cls.t) result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR], standalone_mode=False, ) assert result.exit_code == 0 result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], standalone_mode=False, ) assert result.exit_code == 0 os.chdir(cls.agent_name) # add connection first time @contextmanager def with_oef_installed(self): """Add and remove oef connection.""" result = self.runner.invoke( cli, [ "-v", "DEBUG", "add", "--local", "connection", str(oef.connection.PUBLIC_ID), ], standalone_mode=False, ) assert result.exit_code == 0 try: yield finally: result = self.runner.invoke( cli, ["-v", "DEBUG", "remove", "connection", str(oef.connection.PUBLIC_ID)], standalone_mode=False, ) assert result.exit_code == 0 @contextmanager def with_config_update(self): """Context manager to update item version to 0.0.1.""" original_config = self.load_config() config_data = original_config.json if str(self.ITEM_PUBLIC_ID) in config_data[f"{self.ITEM_TYPE}s"]: config_data[f"{self.ITEM_TYPE}s"].remove(str(self.ITEM_PUBLIC_ID)) config_data[f"{self.ITEM_TYPE}s"].append( f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:0.0.1" ) self.dump_config(AgentConfig.from_json(config_data)) try: yield finally: self.dump_config(original_config) @classmethod def teardown(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestRemoveAndDependencies(BaseTestCase): """Test dependency remove helper and upgrade with dependency removed.""" ITEM_TYPE = "connection" ITEM_PUBLIC_ID = SOEF_PUBLIC_ID LOCAL: List[str] = ["--local"] DEPENDENCY_TYPE = "protocol" DEPENDENCY_PUBLIC_ID = OefSearchMessage.protocol_id @classmethod def setup(cls): """Set the test up.""" super(TestRemoveAndDependencies, cls).setup() cls.DEPENDENCY_PACKAGE_ID = PackageId( cls.DEPENDENCY_TYPE, cls.DEPENDENCY_PUBLIC_ID ) result = cls.runner.invoke( cli, ["-v", "DEBUG", "add", "--local", cls.ITEM_TYPE, str(cls.ITEM_PUBLIC_ID)], standalone_mode=False, ) assert result.exit_code == 0 def test_upgrade_and_dependency_removed(self): """ Test dependency removed after upgrade. Done with mocking _add_item_deps to avoid dependencies installation. Also checks dependency configuration removed with component """ assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols # add empty component config to aea-config.py agent_config = self.load_config() component_id = ComponentId(self.DEPENDENCY_TYPE, self.DEPENDENCY_PUBLIC_ID) agent_config.component_configurations[component_id] = {} # just empty agent_config.component_configurations[ ComponentId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) ] = {} # just empty self.dump_config(agent_config) agent_config = self.load_config() assert component_id in agent_config.component_configurations with patch( "aea.cli.upgrade.ItemUpgrader.check_upgrade_is_required", return_value=self.ITEM_PUBLIC_ID.version, ), patch("aea.cli.add._add_item_deps"): result = self.runner.invoke( cli, [ "-v", "DEBUG", "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", ], catch_exceptions=True, ) try: assert result.exit_code == 0 assert self.DEPENDENCY_PUBLIC_ID not in self.load_config().protocols agent_config = self.load_config() # check configuration was removed too assert component_id not in agent_config.component_configurations assert ( ComponentId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) in agent_config.component_configurations ) finally: # restore component removed result = self.runner.invoke( cli, [ "-v", "DEBUG", "add", *self.LOCAL, self.DEPENDENCY_TYPE, f"{self.DEPENDENCY_PUBLIC_ID.author}/{self.DEPENDENCY_PUBLIC_ID.name}:latest", ], catch_exceptions=True, ) assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols def test_upgrade_and_dependency_not_removed_caused_required_by_another_item(self): """Test dependency is not removed after upgrade cause required by another item.""" assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols # do not add dependencies for the package with self.with_oef_installed(), self.with_config_update(), patch( "aea.cli.add._add_item_deps" ): result = self.runner.invoke( cli, [ "-v", "DEBUG", "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", ], catch_exceptions=True, ) assert result.exit_code == 0 assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols class TestUpgradeSharedDependencies(AEATestCaseEmpty): """ Test removal of shared dependency. The shared dependency in this test case is 'fetchai/oef_search:0.9.0'. """ IS_EMPTY = True OLD_SOEF_ID = PublicId.from_str("fetchai/soef:0.16.0") OLD_OEF_SEARCH_ID = PublicId.from_str("fetchai/oef_search:0.13.0") OLD_OEF_ID = PublicId.from_str("fetchai/oef:0.16.0") @classmethod def setup_class(cls): """ Set the test up. Skip consistency checks to avoid aea version compatibility checks. """ super().setup_class() result = cls.run_cli_command( "-s", "add", "connection", str(cls.OLD_SOEF_ID), cwd=cls._get_cwd() ) assert result.exit_code == 0 result = cls.run_cli_command( "-s", "add", "connection", str(cls.OLD_OEF_ID), cwd=cls._get_cwd() ) assert result.exit_code == 0 def test_upgrade_shared_dependencies(self): """Test upgrade shared dependencies.""" result = self.run_cli_command("-s", "upgrade", cwd=self._get_cwd()) assert result.exit_code == 0 agent_config: AgentConfig = cast( AgentConfig, load_item_config(PackageType.AGENT.value, Path(self.current_agent_context)), ) assert OefSearchMessage.protocol_id in agent_config.protocols assert SOEF_PUBLIC_ID in agent_config.connections assert OEF_PUBLIC_ID in agent_config.connections class TestUpgradeProject(BaseAEATestCase, BaseTestCase): """Test that the command 'aea upgrade' works.""" capture_log = True @classmethod def setup(cls): """Set up test case.""" super(TestUpgradeProject, cls).setup() cls.change_directory(Path("..")) cls.agent_name = "generic_buyer" cls.latest_agent_name = "generic_buyer_latest" cls.run_cli_command( "--skip-consistency-check", "fetch", "fetchai/generic_buyer:0.30.5", "--alias", cls.agent_name, ) cls.run_cli_command( "--skip-consistency-check", "fetch", "fetchai/generic_buyer:latest", "--alias", cls.latest_agent_name, ) cls.agents.add(cls.agent_name) cls.set_agent_context(cls.agent_name) def test_upgrade(self): """Test upgrade project old version to latest one and compare with latest project fetched.""" with cd(self.latest_agent_name): latest_agent_items = set( ItemRemoveHelper(self.load_mock_context()) .get_agent_dependencies_with_reverse_dependencies() .keys() ) with cd(self.agent_name): self.runner.invoke( # pylint: disable=no-member cli, ["--skip-consistency-check", "upgrade", "--local"], standalone_mode=False, catch_exceptions=False, ) agent_items = set( ItemRemoveHelper(self.load_mock_context()) .get_agent_dependencies_with_reverse_dependencies() .keys() ) assert latest_agent_items == agent_items # upgrade again to check it workd with upgraded version with cd(self.agent_name): self.runner.invoke( # pylint: disable=no-member cli, ["--skip-consistency-check", "upgrade", "--local"], standalone_mode=False, catch_exceptions=False, ) agent_items = set( ItemRemoveHelper(self.load_mock_context()) .get_agent_dependencies_with_reverse_dependencies() .keys() ) assert latest_agent_items == agent_items # compare both configuration files, except the agent name and the author upgraded_agent_dir = Path(self.agent_name) latest_agent_dir = Path(self.latest_agent_name) lines_upgraded_agent_config = ( (upgraded_agent_dir / DEFAULT_AEA_CONFIG_FILE).read_text().splitlines() ) lines_latest_agent_config = ( (latest_agent_dir / DEFAULT_AEA_CONFIG_FILE).read_text().splitlines() ) # the slice is because we don't compare the agent name and the author name assert lines_upgraded_agent_config[2:] == lines_latest_agent_config[2:] # compare vendor folders. assert are_dirs_equal( upgraded_agent_dir / "vendor", latest_agent_dir / "vendor" ) class TestNonVendorProject(BaseAEATestCase, BaseTestCase): """Test that the command 'aea upgrade' works.""" capture_log = True @classmethod def setup(cls): """Set up test case.""" super(TestNonVendorProject, cls).setup() cls.change_directory(Path("..")) cls.agent_name = "generic_buyer" cls.run_cli_command( "fetch", "fetchai/generic_buyer:0.30.5", "--alias", cls.agent_name ) cls.agents.add(cls.agent_name) cls.set_agent_context(cls.agent_name) @patch("aea.cli.upgrade.ItemUpgrader.is_non_vendor", True) @patch( "aea.cli.upgrade.ItemUpgrader.check_upgrade_is_required", return_value="0.99.0" ) @patch("aea.cli.upgrade.ItemUpgrader.remove_item") @patch("aea.cli.upgrade.ItemUpgrader.add_item") def test_non_vendor_nothing_to_upgrade( self, *mocks ): # pylint: disable=unused-argument """Test upgrade project dependencies not removed cause non vendor.""" with cd(self.agent_name): base_agent_items = set( ItemRemoveHelper(self.load_mock_context()) .get_agent_dependencies_with_reverse_dependencies() .keys() ) self.runner.invoke( # pylint: disable=no-member cli, ["--skip-consistency-check", "upgrade"], standalone_mode=False, catch_exceptions=False, ) agent_items = set( ItemRemoveHelper(self.load_mock_context()) .get_agent_dependencies_with_reverse_dependencies() .keys() ) assert base_agent_items == agent_items class TestUpgradeConnectionLocally(BaseTestCase): """Test that the command 'aea upgrade connection' works.""" ITEM_TYPE = "connection" ITEM_PUBLIC_ID = SOEF_PUBLIC_ID LOCAL: List[str] = ["--local"] @classmethod def setup(cls): """Set the test up.""" super(TestUpgradeConnectionLocally, cls).setup() result = cls.runner.invoke( cli, ["-v", "DEBUG", "add", "--local", cls.ITEM_TYPE, str(cls.ITEM_PUBLIC_ID)], standalone_mode=False, ) assert result.exit_code == 0 def test_upgrade_to_same_version(self): """Test do not upgrade to version already installed.""" with pytest.raises( ClickException, match=r"The .* with id '.*' already has version .*. Nothing to upgrade.", ): self.runner.invoke( cli, ["upgrade", *self.LOCAL, self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID)], standalone_mode=False, catch_exceptions=False, ) @patch("aea.cli.upgrade.ItemUpgrader.is_non_vendor", True) def test_upgrade_non_vendor(self): """Test do not upgrade non vendor package.""" with pytest.raises( ClickException, match=r"The .* with id '.*' already has version .*. Nothing to upgrade.", ): self.runner.invoke( cli, [ "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:100.0.0", ], standalone_mode=False, catch_exceptions=False, ) def test_upgrade_to_latest_but_same_version(self): """Test no update to latest if already latest component.""" with pytest.raises( ClickException, match=r"The .* with id '.*' already has version .*. Nothing to upgrade.", ): self.runner.invoke( cli, [ "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", ], standalone_mode=False, catch_exceptions=False, ) def test_upgrade_to_non_registered(self): """Test can not upgrade not registered component.""" with pytest.raises( ClickException, match=r".* with id .* is not registered. Please use the `add` command. Aborting...", ): self.runner.invoke( cli, [ "-v", "DEBUG", "upgrade", *self.LOCAL, self.ITEM_TYPE, "nonexits/dummy:0.0.0", ], standalone_mode=False, catch_exceptions=False, ) def test_upgrade_required_mock(self): """Test upgrade with mocking upgrade required.""" with patch( "aea.cli.upgrade.ItemUpgrader.check_upgrade_is_required", return_value="100.0.0", ): result = self.runner.invoke( cli, [ "-v", "DEBUG", "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", ], catch_exceptions=False, ) assert result.exit_code == 0 def test_do_upgrade(self): """Test real full upgrade.""" with self.with_config_update(): result = self.runner.invoke( cli, [ "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", ], standalone_mode=False, ) assert result.exit_code == 0 def test_package_can_not_be_found_in_registry(self): """Test no package in registry.""" with self.with_config_update(): with patch( "aea.cli.registry.utils.get_package_meta", side_effects=Exception("expected!"), ), patch( "aea.cli.registry.utils.find_item_locally", side_effects=Exception("expected!"), ), pytest.raises( ClickException, match=r"Package .* details can not be fetched from the registry!", ): self.runner.invoke( cli, [ "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", ], standalone_mode=False, catch_exceptions=False, ) def test_package_can_not_upgraded_cause_required(self): """Test no package in registry.""" with self.with_config_update(): with patch( "aea.cli.upgrade.ItemRemoveHelper.check_remove", return_value=( set([PackageId("connection", PublicId("test", "test", "0.0.1"))]), set(), dict(), ), ), pytest.raises( ClickException, match=r"Can not upgrade .* because it is required by '.*'", ): self.runner.invoke( cli, [ "upgrade", *self.LOCAL, self.ITEM_TYPE, f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", ], standalone_mode=False, catch_exceptions=False, ) @classmethod def teardown(cls): """Tear the test down.""" super(TestUpgradeConnectionLocally, cls).teardown() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestUpgradeConnectionRemoteRegistry(TestUpgradeConnectionLocally): """Test that the command 'aea upgrade connection' works.""" LOCAL: List[str] = [] def test_upgrade_to_latest_but_same_version(self): """Skip.""" pass class TestUpgradeProtocolLocally(TestUpgradeConnectionLocally): """Test that the command 'aea upgrade protocol --local' works.""" ITEM_TYPE = "protocol" ITEM_PUBLIC_ID = HttpMessage.protocol_id class TestUpgradeProtocolRemoteRegistry(TestUpgradeProtocolLocally): """Test that the command 'aea upgrade protocol' works.""" LOCAL: List[str] = [] def test_upgrade_to_latest_but_same_version(self): """Skip.""" pass class TestUpgradeSkillLocally(TestUpgradeConnectionLocally): """Test that the command 'aea upgrade skill --local' works.""" ITEM_TYPE = "skill" ITEM_PUBLIC_ID = ECHO_SKILL_PUBLIC_ID class TestUpgradeSkillRemoteRegistry(TestUpgradeSkillLocally): """Test that the command 'aea upgrade skill' works.""" LOCAL: List[str] = [] def test_upgrade_to_latest_but_same_version(self): """Skip.""" pass def test_upgrade_required_mock(self): """Skip.""" pass def test_do_upgrade(self): """Skip.""" pass class TestUpgradeContractLocally(TestUpgradeConnectionLocally): """Test that the command 'aea upgrade contract' works.""" ITEM_TYPE = "contract" ITEM_PUBLIC_ID = ERC1155_PUBLIC_ID class TestUpgradeContractRemoteRegistry(TestUpgradeContractLocally): """Test that the command 'aea upgrade contract --local' works.""" LOCAL: List[str] = [] def test_upgrade_to_latest_but_same_version(self): """Skip.""" pass @pytest.mark.integration class TestUpgradeNonVendorDependencies(AEATestCaseEmpty): """ Test that the command 'aea upgrade' correctly updates non-vendor package data. In particular, check that 'aea upgrade' updates: - the public ids of the package dependencies and the 'aea_version' field. - the 'aea_version' field in case it is not compatible with the current version. The test works as follows: - scaffold a package, one for each possible package type; - add the protocol "fetchai/default:0.12.0" as dependency to each of them. - add the skill "fetchai/error:0.12.0"; this will also add the default protocol. add it also as dependency of non-vendor skill. - run 'aea upgrade' - check that the reference to "fetchai/default" in each scaffolded package has the new version. """ capture_log = True IS_EMPTY = True old_default_protocol_id = PublicId( DefaultMessage.protocol_id.author, DefaultMessage.protocol_id.name, "0.12.0" ) old_error_skill_id = PublicId( ERROR_SKILL_PUBLIC_ID.author, ERROR_SKILL_PUBLIC_ID.name, "0.12.0" ) old_aea_version_range = compute_specifier_from_version(Version("0.1.0")) @classmethod def scaffold_item( cls, item_type: str, name: str, skip_consistency_check: bool = False ) -> Result: """Override default behaviour by adding a custom dependency to the scaffolded item.""" result = super(TestUpgradeNonVendorDependencies, cls).scaffold_item( item_type, name, skip_consistency_check ) # add custom dependency (a protocol) to each package # that supports dependencies (only connections and skills) if item_type in {ComponentType.CONNECTION.value, ComponentType.SKILL.value}: cls.nested_set_config( f"{ComponentType(item_type).to_plural()}.{name}.protocols", [str(cls.old_default_protocol_id)], ) # add the vendor skill as dependency of the non-vendor skill if item_type == ComponentType.SKILL.value: cls.nested_set_config( f"{ComponentType(item_type).to_plural()}.{name}.skills", [str(cls.old_error_skill_id)], ) # update 'aea_version' to an old version range. cls.nested_set_config( f"{ComponentType(item_type).to_plural()}.{name}.aea_version", str(cls.old_aea_version_range), ) return result @classmethod def setup_class(cls): """Set up test case.""" super(TestUpgradeNonVendorDependencies, cls).setup_class() cls.scaffold_item("protocol", "my_protocol", skip_consistency_check=True) cls.scaffold_item("connection", "my_connection", skip_consistency_check=True) cls.scaffold_item("contract", "my_contract", skip_consistency_check=True) cls.scaffold_item("skill", "my_skill", skip_consistency_check=True) cls.run_cli_command( "--skip-consistency-check", "add", "skill", str(cls.old_error_skill_id), cwd=cls._get_cwd(), ) cls.run_cli_command( "--skip-consistency-check", "upgrade", "--local", cwd=cls._get_cwd() ) def test_agent_config_updated(self): """Test the agent configuration is updated.""" loader = ConfigLoader.from_configuration_type(PackageType.AGENT) with Path(self._get_cwd(), DEFAULT_AEA_CONFIG_FILE).open() as fp: agent_config = loader.load(fp) assert DefaultMessage.protocol_id in agent_config.protocols assert ERROR_SKILL_PUBLIC_ID in agent_config.skills def test_non_vendor_update_references_to_upgraded_packages( self, ): # pylint: disable=unused-argument """Test that dependencies in non-vendor packages are updated correctly after upgrade.""" self.assert_dependency_updated( ComponentType.CONNECTION, "my_connection", "protocols", {DefaultMessage.protocol_id}, ) self.assert_dependency_updated( ComponentType.SKILL, "my_skill", "protocols", {DefaultMessage.protocol_id} ) self.assert_dependency_updated( ComponentType.SKILL, "my_skill", "skills", {ERROR_SKILL_PUBLIC_ID} ) def assert_dependency_updated( self, item_type: ComponentType, package_name: str, package_type: str, expected: Set[PublicId], ): """Assert dependency is updated.""" package_path = Path(self._get_cwd(), item_type.to_plural(), package_name) component_config = load_component_configuration(item_type, package_path) assert hasattr(component_config, package_type), "Test is not well-written." assert getattr(component_config, package_type) == expected # type: ignore expected_version_range = compute_specifier_from_version( get_current_aea_version() ) assert component_config.aea_version == expected_version_range class TestUpdateReferences(AEATestCaseEmpty): """ Test that references are updated correctly after 'aea upgrade'. In particular, 'default_routing', 'default_connection' and custom component configurations in AEA configuration. How the test works: - add fetchai/error:0.12.0, that requires fetchai/default:0.12.0 - add fetchai/stub:0.16.0 - add 'fetchai/default:0.12.0: fetchai/stub:0.16.0' to default routing - add custom configuration to stub connection. - run 'aea upgrade'. This will upgrade `stub` connection and `error` skill, and in turn `default` protocol. """ IS_EMPTY = True OLD_DEFAULT_PROTOCOL_PUBLIC_ID = PublicId.from_str("fetchai/default:0.12.0") OLD_ERROR_SKILL_PUBLIC_ID = PublicId.from_str("fetchai/error:0.12.0") OLD_STUB_CONNECTION_PUBLIC_ID = PublicId.from_str("fetchai/stub:0.16.0") @classmethod def setup_class(cls): """Set up the test class.""" super().setup_class() cls.run_cli_command( "--skip-consistency-check", "add", "skill", str(cls.OLD_ERROR_SKILL_PUBLIC_ID), cwd=cls._get_cwd(), ) cls.run_cli_command( "--skip-consistency-check", "add", "connection", str(cls.OLD_STUB_CONNECTION_PUBLIC_ID), cwd=cls._get_cwd(), ) cls.nested_set_config( "agent.default_routing", {cls.OLD_DEFAULT_PROTOCOL_PUBLIC_ID: cls.OLD_STUB_CONNECTION_PUBLIC_ID}, ) cls.nested_set_config( "agent.default_connection", cls.OLD_STUB_CONNECTION_PUBLIC_ID, ) cls.run_cli_command( "--skip-consistency-check", "config", "set", "vendor.fetchai.skills.error.is_abstract", "--type", "bool", "true", cwd=cls._get_cwd(), ) cls.run_cli_command( "--skip-consistency-check", "upgrade", "--local", cwd=cls._get_cwd() ) def test_default_routing_updated_correctly(self): """Test default routing has been updated correctly.""" result = self.run_cli_command( "--skip-consistency-check", "config", "get", "agent.default_routing", cwd=self._get_cwd(), ) assert ( result.stdout == f'{{"{DefaultMessage.protocol_id}": "{StubConnection.connection_id}"}}\n' ) def test_default_connection_updated_correctly(self): """Test default routing has been updated correctly.""" result = self.run_cli_command( "--skip-consistency-check", "config", "get", "agent.default_connection", cwd=self._get_cwd(), ) assert result.stdout == "fetchai/stub:0.21.3\n" def test_custom_configuration_updated_correctly(self): """Test default routing has been updated correctly.""" result = self.run_cli_command( "--skip-consistency-check", "config", "get", "vendor.fetchai.skills.error.is_abstract", cwd=self._get_cwd(), ) assert result.stdout == "True\n" @mock.patch("click.echo") class TestNothingToUpgrade(AEATestCaseEmpty): """Test the upgrade command when there's nothing t upgrade.""" def test_nothing_to_upgrade(self, mock_click_echo): """Test nothing to upgrade.""" agent_config = self.load_agent_config(self.agent_name) result = self.run_cli_command("upgrade", cwd=self._get_cwd()) assert result.exit_code == 0 mock_click_echo.assert_any_call("Starting project upgrade...") mock_click_echo.assert_any_call( f"Checking if there is a newer remote version of agent package '{agent_config.public_id}'..." ) mock_click_echo.assert_any_call( "Package not found, continuing with normal upgrade." ) mock_click_echo.assert_any_call("Everything is already up to date!") @pytest.mark.integration @mock.patch("click.echo") class TestWrongAEAVersion(AEATestCaseEmpty): """ Test consistency check ignores AEA version fields. Use an old version of a package to simulate an upgrade. """ AEA_VERSION_SPECIFIER: str = "==0.1.0" IS_EMPTY = True @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() # this is an old version of the package, just to trigger an upgrade. cls.add_item("protocol", "fetchai/default:0.12.0", local=False) # change aea version of the AEA project agent_config = cls.load_agent_config(cls.current_agent_context) cls._update_aea_version(agent_config) cls.nested_set_config("agent.aea_version", cls.AEA_VERSION_SPECIFIER) cls.nested_set_config("agent.author", "wrong_author") @classmethod def _update_aea_version(cls, agent_config: AgentConfig): """Update aea version to all items and fingerprint them.""" for item in agent_config.package_dependencies: type_ = item.component_type.to_plural() dotted_path = f"vendor.{item.author}.{type_}.{item.name}.aea_version" path = os.path.join("vendor", item.author, type_, item.name) cls.nested_set_config(dotted_path, cls.AEA_VERSION_SPECIFIER) cls.run_cli_command("fingerprint", "by-path", path, cwd=cls._get_cwd()) def test_nothing_to_upgrade(self, mock_click_echo): """Test nothing to upgrade, and additionally, that 'aea_version' is correct.""" result = self.run_cli_command("upgrade", cwd=self._get_cwd()) assert result.exit_code == 0 mock_click_echo.assert_any_call("Starting project upgrade...") mock_click_echo.assert_any_call( f"Updating AEA version specifier from ==0.1.0 to {compute_specifier_from_version(get_current_aea_version())}." ) # test 'aea_version' of agent configuration is upgraded expected_aea_version_specifier = compute_specifier_from_version( get_current_aea_version() ) agent_config = self.load_agent_config(self.current_agent_context) assert agent_config.aea_version == expected_aea_version_specifier assert agent_config.author == self.author assert agent_config.version == DEFAULT_VERSION @mock.patch("click.echo") @mock.patch("click.confirm") @mock.patch("aea.cli.upgrade.get_latest_version_available_in_registry") class BaseTestUpgradeWithEject(AEATestCaseEmpty): """ Base test class to test 'aea upgrade' with request for ejection. We use an old version of 'generic seller' skill to simulate a request from the CLI tool. The utility 'get_latest_version_available_in_registry' is mocked so to hide the new version of that package, hence triggering an ejection.; """ IS_EMPTY = True GENERIC_SELLER = ComponentId( ComponentType.SKILL, PublicId.from_str("fetchai/generic_seller:0.28.6") ) unmocked = get_latest_version_available_in_registry EXPECTED_CLICK_ECHO_CALLS: List[str] = [] EXPECTED_CLICK_CONFIRM_CALLS: List[str] = [] CONFIRM_OUTPUT = [False, False] @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() cls.add_item("skill", str(cls.GENERIC_SELLER.public_id), local=False) @classmethod def mock_get_latest_version_available_in_registry(cls, *args, **kwargs): """Mock 'get_latest_version_available_in_registry' when called with generic_seller public key.""" if ( args[1] == str(cls.GENERIC_SELLER.package_type) and args[2] == cls.GENERIC_SELLER.public_id.to_latest() ): # return current version return cls.GENERIC_SELLER return cls.unmocked(*args, **kwargs) def _get_mock(self): """Get the mock of 'get_latest_version_available_in_registry'.""" return mock.patch( "aea.cli.upgrade.get_latest_version_available_in_registry", side_effect=self.mock_get_latest_version_available_in_registry, ) def test_run(self, mock_get_latest_version, mock_click_confirm, mock_click_echo): """Run the test.""" mock_get_latest_version.side_effect = ( self.mock_get_latest_version_available_in_registry ) mock_click_confirm.side_effect = self.CONFIRM_OUTPUT result = self.run_cli_command("upgrade", cwd=self._get_cwd()) assert result.exit_code == 0 self.mock_click_echo = mock_click_echo self.mock_click_confirm = mock_click_confirm self._assert_calls(self.EXPECTED_CLICK_ECHO_CALLS, mock_click_echo) self._assert_calls(self.EXPECTED_CLICK_CONFIRM_CALLS, mock_click_confirm) def _assert_calls(self, args: List, stdout_mock: MagicMock): """Assert lines are present in stdout.""" for expected_line in args: stdout_mock.assert_any_call(expected_line) @pytest.mark.integration class TestUpgradeWithEjectAbort(BaseTestUpgradeWithEject): """Test 'aea upgrade' command with request for ejection, refused.""" GENERIC_SELLER = ComponentId( ComponentType.SKILL, PublicId.from_str("fetchai/generic_seller:0.24.0") ) EXPECTED_CLICK_ECHO_CALLS = ["Abort."] EXPECTED_CLICK_CONFIRM_CALLS = [ RegexComparator( r"Skill fetchai/generic_seller:0.24.0 prevents the upgrade of the following vendor packages:.*as there isn't a compatible version available on the AEA registry\. Would you like to eject it\?" ) ] @pytest.mark.integration class TestUpgradeWithEjectAccept(BaseTestUpgradeWithEject): """Test 'aea upgrade' command with request for ejection, accepted by the user.""" CONFIRM_OUTPUT = [True, True] GENERIC_SELLER = ComponentId( ComponentType.SKILL, PublicId.from_str("fetchai/generic_seller:0.24.0") ) EXPECTED_CLICK_ECHO_CALLS = [ "Ejecting (skill, fetchai/generic_seller:0.24.0)...", "Ejecting item skill fetchai/generic_seller:0.24.0", "Fingerprinting skill components of 'default_author/generic_seller:0.1.0' ...", "Successfully ejected skill fetchai/generic_seller:0.24.0 to ./skills/generic_seller as default_author/generic_seller:0.1.0.", ] EXPECTED_CLICK_CONFIRM_CALLS = [ RegexComparator( "Skill fetchai/generic_seller:0.24.0 prevents the upgrade of the following vendor packages:" ), RegexComparator( "as there isn't a compatible version available on the AEA registry. Would you like to eject it?" ), ] def test_run(self, *mocks): """Run the test.""" super().test_run(*mocks) ejected_package_path = Path( self.t, self.current_agent_context, "skills", "generic_seller" ) assert ejected_package_path.exists() assert ejected_package_path.is_dir() @pytest.mark.integration class BaseTestUpgradeProject(AEATestCaseEmpty): """Base test class for testing project upgrader.""" OLD_AGENT_PUBLIC_ID = PublicId.from_str("fetchai/weather_station:0.32.0") EXPECTED_NEW_AGENT_PUBLIC_ID = OLD_AGENT_PUBLIC_ID.to_latest() EXPECTED = "expected_agent" @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() cls.run_cli_command( "--skip-consistency-check", "fetch", "--remote", str(cls.EXPECTED_NEW_AGENT_PUBLIC_ID), "--alias", cls.EXPECTED, ) def setup(self): """Set up the class.""" self.run_cli_command("fetch", str(self.OLD_AGENT_PUBLIC_ID)) self.set_agent_context(self.OLD_AGENT_PUBLIC_ID.name) def teardown(self): """Tear down class.""" shutil.rmtree(self.current_agent_context) @mock.patch("click.confirm") class TestUpgradeProjectWithNewerVersion(BaseTestUpgradeProject): """Test upgrade project with newer version available.""" @pytest.mark.parametrize("confirm", [True, False]) def test_upgrade(self, mock_confirm, confirm): """Test upgrade.""" mock_confirm.return_value = confirm result = self.run_cli_command("upgrade", "--remote", cwd=self._get_cwd()) assert result.exit_code == 0 mock_confirm.assert_any_call( RegexComparator( r"Found a newer version of this project:.*Would you like to replace this project with it\?.*Warning: the content in the current directory.*will be removed" ) ) # compare with latest fetched agent. ignore = [ DEFAULT_AEA_CONFIG_FILE, DEFAULT_README_FILE, ] + filecmp.DEFAULT_IGNORES dircmp = filecmp.dircmp( self.current_agent_context, self.EXPECTED, ignore=ignore ) _left_only, _right_only, diff = dircmp_recursive(dircmp) assert _right_only == diff == _left_only == set() @mock.patch("aea.cli.upgrade.get_latest_version_available_in_registry") @mock.patch("click.echo") class TestUpgradeProjectWithoutNewerVersion(BaseTestUpgradeProject): """Test upgrade project without newer version available (but available on registry).""" def test_run(self, mock_click_echo, mock_get_latest_version): """Run the test.""" fake_old_public_id = self.OLD_AGENT_PUBLIC_ID mock_get_latest_version.return_value = fake_old_public_id result = self.run_cli_command("upgrade", "--remote", cwd=self._get_cwd()) assert result.exit_code == 0 version_str = str(self.OLD_AGENT_PUBLIC_ID.version) mock_click_echo.assert_any_call( f"Latest version found is '{version_str}' which is smaller or equal than current version '{version_str}'. Continuing..." ) # compare with latest fetched agent. ignore = [ DEFAULT_AEA_CONFIG_FILE, DEFAULT_README_FILE, ] + filecmp.DEFAULT_IGNORES dircmp = filecmp.dircmp( self.current_agent_context, self.EXPECTED, ignore=ignore ) _left_only, _right_only, diff = dircmp_recursive(dircmp) assert diff == set() # temp: assert diff == _left_only == _right_only == set() @mock.patch.object(aea, "__version__", "0.11.0") class TestUpgradeAEACompatibility(BaseTestUpgradeProject): """ Test 'aea upgrade' takes into account the current aea version. The test works as follows: """ OLD_AGENT_PUBLIC_ID = PublicId.from_str("fetchai/weather_station:0.32.5") EXPECTED_NEW_AGENT_PUBLIC_ID = PublicId.from_str("fetchai/weather_station:latest") def test_upgrade(self): """Test upgrade.""" result = self.run_cli_command("upgrade", "--remote", "-y", cwd=self._get_cwd()) assert result.exit_code == 0 # compare with latest fetched agent. ignore = [DEFAULT_AEA_CONFIG_FILE] + filecmp.DEFAULT_IGNORES dircmp = filecmp.dircmp( self.current_agent_context, self.EXPECTED, ignore=ignore ) _left_only, _right_only, diff = dircmp_recursive(dircmp) assert diff == set() # temp # compare agent configuration files (except the name) expected_content = ( Path(self.EXPECTED, DEFAULT_AEA_CONFIG_FILE).read_text().splitlines()[1:] ) actual_content = ( Path(self.current_agent_context, DEFAULT_AEA_CONFIG_FILE) .read_text() .splitlines()[1:] ) assert expected_content != actual_content # temp ================================================ FILE: tests/test_aea/test_cli/test_utils/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the cli utils.""" ================================================ FILE: tests/test_aea/test_cli/test_utils/test_config.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for aea.cli.utils.config module.""" from unittest import TestCase, mock from aea.cli.utils.config import validate_item_config from aea.cli.utils.exceptions import AEAConfigException from tests.test_aea.test_cli.tools_for_testing import ( AgentConfigMock, ConfigLoaderMock, FaultyAgentConfigMock, ) class ValidateItemConfigTestCase(TestCase): """Test case for validate_item_config method.""" @mock.patch( "aea.cli.utils.config.load_item_config", return_value=AgentConfigMock(description="Description"), ) @mock.patch( "aea.cli.utils.config.ConfigLoaders.from_package_type", return_value=ConfigLoaderMock(required_fields=["description"]), ) def test_validate_item_config_positive(self, *mocks): """Test validate_item_config for positive result.""" validate_item_config(item_type="agent", package_path="file/path") @mock.patch( "aea.cli.utils.config.load_item_config", return_value=FaultyAgentConfigMock(), ) @mock.patch( "aea.cli.utils.config.ConfigLoaders.from_package_type", return_value=ConfigLoaderMock(required_fields=["description"]), ) def test_validate_item_config_negative(self, *mocks): """Test validate_item_config for negative result.""" with self.assertRaises(AEAConfigException): validate_item_config(item_type="agent", package_path="file/path") ================================================ FILE: tests/test_aea/test_cli/test_utils/test_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for aea.cli.utils module.""" from builtins import FileNotFoundError from copy import deepcopy from tempfile import TemporaryDirectory from typing import cast from unittest import TestCase, mock from unittest.mock import MagicMock, patch from uuid import uuid4 import click import pytest from aea_ledger_fetchai import FetchAICrypto from click import BadParameter, ClickException, UsageError from click.testing import CliRunner from jsonschema import ValidationError from yaml import YAMLError from aea.cli.utils.click_utils import ( MutuallyExclusiveOption, PublicIdParameter, password_option, ) from aea.cli.utils.config import ( _init_cli_config, get_or_create_cli_config, set_cli_author, update_cli_config, ) from aea.cli.utils.context import Context from aea.cli.utils.decorators import _validate_config_consistency, clean_after from aea.cli.utils.formatting import format_items from aea.cli.utils.generic import is_readme_present from aea.cli.utils.package_utils import ( _override_ledger_configurations, find_item_in_distribution, find_item_locally, get_dotted_package_path_unified, get_package_path_unified, get_wallet_from_context, is_distributed_item, is_fingerprint_correct, is_item_present_unified, try_get_balance, try_get_item_source_path, try_get_item_target_path, validate_author_name, validate_package_name, ) from aea.configurations.base import ComponentId, ComponentType, PublicId from aea.configurations.constants import ( DEFAULT_LEDGER, DEFAULT_PROTOCOL, LEDGER_CONNECTION, ) from aea.crypto.ledger_apis import FETCHAI_DEFAULT_CHAIN_ID, LedgerApis from aea.crypto.wallet import Wallet from aea.helpers.base import cd from aea.test_tools.test_cases import AEATestCaseEmpty from tests.test_aea.test_cli.tools_for_testing import ( ConfigLoaderMock, ContextMock, PublicIdMock, StopTest, raise_stoptest, ) AUTHOR = "author" class FormatItemsTestCase(TestCase): """Test case for format_items method.""" def testformat_items_positive(self): """Test format_items positive result.""" items = [ { "public_id": "author/name:version", "name": "obj_name", "description": "Some description", "author": "author", "version": "1.0", } ] result = format_items(items) expected_result = ( "------------------------------\n" "Public ID: author/name:version\n" "Name: obj_name\n" "Description: Some description\n" "Author: author\n" "Version: 1.0\n" "------------------------------\n" ) self.assertEqual(result, expected_result) @mock.patch("aea.cli.utils.package_utils.os.path.join", return_value="some-path") class TryGetItemSourcePathTestCase(TestCase): """Test case for try_get_item_source_path method.""" @mock.patch("aea.cli.utils.package_utils.os.path.exists", return_value=True) def test_get_item_source_path_positive(self, exists_mock, join_mock): """Test for get_item_source_path positive result.""" result = try_get_item_source_path("cwd", AUTHOR, "skills", "skill_name") expected_result = "some-path" self.assertEqual(result, expected_result) join_mock.assert_called_once_with("cwd", AUTHOR, "skills", "skill_name") exists_mock.assert_called_once_with("some-path") result = try_get_item_source_path("cwd", None, "skills", "skill_name") self.assertEqual(result, expected_result) @mock.patch("aea.cli.utils.package_utils.os.path.exists", return_value=False) def test_get_item_source_path_not_exists(self, exists_mock, join_mock): """Test for get_item_source_path item already exists.""" item_name = "skill_name" with pytest.raises( ClickException, match=f'Item "{AUTHOR}/{item_name}" not found in source folder "some-path"', ): try_get_item_source_path("cwd", AUTHOR, "skills", item_name) @mock.patch("aea.cli.utils.package_utils.os.path.join", return_value="some-path") class TryGetItemTargetPathTestCase(TestCase): """Test case for try_get_item_target_path method.""" @mock.patch("aea.cli.utils.package_utils.os.path.exists", return_value=False) def test_get_item_target_path_positive(self, exists_mock, join_mock): """Test for get_item_source_path positive result.""" result = try_get_item_target_path("packages", AUTHOR, "skills", "skill_name") expected_result = "some-path" self.assertEqual(result, expected_result) join_mock.assert_called_once_with("packages", AUTHOR, "skills", "skill_name") exists_mock.assert_called_once_with("some-path") @mock.patch("aea.cli.utils.package_utils.os.path.exists", return_value=True) def test_get_item_target_path_already_exists(self, exists_mock, join_mock): """Test for get_item_target_path item already exists.""" with self.assertRaises(ClickException): try_get_item_target_path("skills", AUTHOR, "skill_name", "packages_path") class PublicIdParameterTestCase(TestCase): """Test case for PublicIdParameter class.""" def test_get_metavar_positive(self): """Test for get_metavar positive result.""" result = PublicIdParameter.get_metavar("obj", "param") expected_result = "PUBLIC_ID" self.assertEqual(result, expected_result) @mock.patch("aea.cli.utils.config.os.path.dirname", return_value="dir-name") @mock.patch("aea.cli.utils.config.os.path.exists", return_value=False) @mock.patch("aea.cli.utils.config.os.makedirs") @mock.patch("aea.cli.utils.click_utils.open_file") class InitConfigFolderTestCase(TestCase): """Test case for _init_cli_config method.""" def test_init_cli_config_positive( self, open_mock, makedirs_mock, exists_mock, dirname_mock ): """Test for _init_cli_config method positive result.""" _init_cli_config() dirname_mock.assert_called_once() exists_mock.assert_called_once_with("dir-name") makedirs_mock.assert_called_once_with("dir-name") @mock.patch("aea.cli.utils.config.get_or_create_cli_config") @mock.patch("aea.cli.utils.generic.yaml.dump") @mock.patch("aea.cli.utils.click_utils.open_file", mock.mock_open()) class UpdateCLIConfigTestCase(TestCase): """Test case for update_cli_config method.""" def testupdate_cli_config_positive(self, dump_mock, icf_mock): """Test for update_cli_config method positive result.""" update_cli_config({"some": "config"}) icf_mock.assert_called_once() dump_mock.assert_called_once() def _raise_yamlerror(*args): raise YAMLError() def _raise_file_not_found_error(*args): raise FileNotFoundError() @mock.patch("aea.cli.utils.click_utils.open_file", mock.mock_open()) class GetOrCreateCLIConfigTestCase(TestCase): """Test case for read_cli_config method.""" @mock.patch( "aea.cli.utils.generic.yaml.safe_load", return_value={"correct": "output"} ) def testget_or_create_cli_config_positive(self, safe_load_mock): """Test for get_or_create_cli_config method positive result.""" result = get_or_create_cli_config() expected_result = {"correct": "output"} self.assertEqual(result, expected_result) safe_load_mock.assert_called_once() @mock.patch("aea.cli.utils.generic.yaml.safe_load", _raise_yamlerror) def testget_or_create_cli_config_bad_yaml(self): """Test for rget_or_create_cli_config method bad yaml behavior.""" with self.assertRaises(ClickException): get_or_create_cli_config() class CleanAfterTestCase(TestCase): """Test case for clean_after decorator method.""" @mock.patch("aea.cli.utils.decorators.os.path.exists", return_value=True) @mock.patch("aea.cli.utils.decorators._cast_ctx", lambda x: x) @mock.patch("aea.cli.utils.decorators.shutil.rmtree") def test_clean_after_positive(self, rmtree_mock, *mocks): """Test clean_after decorator method for positive result.""" @clean_after def func(click_context): ctx = cast(Context, click_context.obj) ctx.clean_paths.append("clean/path") raise ClickException("Message") with self.assertRaises(ClickException): func(ContextMock()) rmtree_mock.assert_called_once_with("clean/path") @mock.patch("aea.cli.utils.package_utils.click.echo", raise_stoptest) class ValidateAuthorNameTestCase(TestCase): """Test case for validate_author_name method.""" @mock.patch( "aea.cli.utils.package_utils.click.prompt", return_value="correct_author" ) def test_validate_author_name_positive(self, prompt_mock): """Test validate_author_name for positive result.""" author = "valid_author" result = validate_author_name(author=author) self.assertEqual(result, author) result = validate_author_name() self.assertEqual(result, "correct_author") prompt_mock.assert_called_once() @mock.patch( "aea.cli.utils.package_utils.click.prompt", return_value="inv@l1d_@uth&r" ) def test_validate_author_name_negative(self, prompt_mock): """Test validate_author_name for negative result.""" with self.assertRaises(StopTest): validate_author_name() prompt_mock.return_value = "skills" with self.assertRaises(StopTest): validate_author_name() class ValidatePackageNameTestCase(TestCase): """Test case for validate_package_name method.""" def test_validate_package_name_positive(self): """Test validate_package_name for positive result.""" validate_package_name("correct_name") def test_validate_package_name_negative(self): """Test validate_package_name for negative result.""" with self.assertRaises(BadParameter): validate_package_name("incorrect-name") def _raise_validation_error(*args, **kwargs): raise ValidationError("Message.") class FindItemLocallyTestCase(TestCase): """Test case for find_item_locally method.""" @mock.patch("aea.cli.utils.package_utils.Path.exists", return_value=True) @mock.patch( "aea.cli.utils.package_utils.ConfigLoader.from_configuration_type", _raise_validation_error, ) def test_find_item_locally_bad_config(self, *mocks): """Test find_item_locally for bad config result.""" public_id = PublicIdMock.from_str("fetchai/echo:0.20.6") with self.assertRaises(ClickException) as cm: find_item_locally(ContextMock(), "skill", public_id) self.assertIn("configuration file not valid", cm.exception.message) @mock.patch("aea.cli.utils.package_utils.Path.exists", return_value=True) @mock.patch("aea.cli.utils.package_utils.open_file", mock.mock_open()) @mock.patch( "aea.cli.utils.package_utils.ConfigLoader.from_configuration_type", return_value=ConfigLoaderMock(), ) def test_find_item_locally_cant_find(self, from_conftype_mock, *mocks): """Test find_item_locally for can't find result.""" public_id = PublicIdMock.from_str("fetchai/echo:0.20.6") with self.assertRaises(ClickException) as cm: find_item_locally(ContextMock(), "skill", public_id) self.assertEqual( cm.exception.message, "Cannot find skill with author and version specified." ) class FindItemInDistributionTestCase(TestCase): """Test case for find_item_in_distribution method.""" @mock.patch("aea.cli.utils.package_utils.Path.exists", return_value=True) @mock.patch( "aea.cli.utils.package_utils.ConfigLoader.from_configuration_type", _raise_validation_error, ) def testfind_item_in_distribution_bad_config(self, *mocks): """Test find_item_in_distribution for bad config result.""" public_id = PublicIdMock.from_str("fetchai/echo:0.20.6") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) self.assertIn("configuration file not valid", cm.exception.message) @mock.patch("aea.cli.utils.package_utils.Path.exists", return_value=False) def testfind_item_in_distribution_not_found(self, *mocks): """Test find_item_in_distribution for not found result.""" public_id = PublicIdMock.from_str("fetchai/echo:0.20.6") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) self.assertIn("Cannot find skill", cm.exception.message) @mock.patch("aea.cli.utils.package_utils.Path.exists", return_value=True) @mock.patch("aea.cli.utils.package_utils.open_file", mock.mock_open()) @mock.patch( "aea.cli.utils.package_utils.ConfigLoader.from_configuration_type", return_value=ConfigLoaderMock(), ) def testfind_item_in_distribution_cant_find(self, from_conftype_mock, *mocks): """Test find_item_locally for can't find result.""" public_id = PublicIdMock.from_str("fetchai/echo:0.20.6") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) self.assertEqual( cm.exception.message, "Cannot find skill with author and version specified." ) class ValidateConfigConsistencyTestCase(TestCase): """Test case for _validate_config_consistency method.""" @mock.patch("aea.cli.utils.config.Path.exists", _raise_validation_error) def test__validate_config_consistency_cant_find(self, *mocks): """Test _validate_config_consistency can't find result""" with self.assertRaises(ValueError) as cm: _validate_config_consistency(ContextMock(protocols=["some"])) self.assertIn("Cannot find", str(cm.exception)) @mock.patch( "aea.cli.utils.package_utils._compute_fingerprint", return_value={"correct": "fingerprint"}, ) class IsFingerprintCorrectTestCase(TestCase): """Test case for adding skill with invalid fingerprint.""" def test_is_fingerprint_correct_positive(self, *mocks): """Test is_fingerprint_correct method for positive result.""" item_config = mock.Mock() item_config.fingerprint = {"correct": "fingerprint"} item_config.fingerprint_ignore_patterns = [] result = is_fingerprint_correct("package_path", item_config) self.assertTrue(result) def test_is_fingerprint_correct_negative(self, *mocks): """Test is_fingerprint_correct method for negative result.""" item_config = mock.Mock() item_config.fingerprint = {"incorrect": "fingerprint"} item_config.fingerprint_ignore_patterns = [] package_path = "package_dir" result = is_fingerprint_correct(package_path, item_config) self.assertFalse(result) @mock.patch("aea.cli.utils.package_utils.LedgerApis", mock.MagicMock()) class TryGetBalanceTestCase(TestCase): """Test case for try_get_balance method.""" def test_try_get_balance_positive(self): """Test for try_get_balance method positive result.""" agent_config = mock.Mock() agent_config.default_ledger_config = FetchAICrypto.identifier wallet_mock = mock.Mock() wallet_mock.addresses = {FetchAICrypto.identifier: "some-adress"} try_get_balance(agent_config, wallet_mock, FetchAICrypto.identifier) @mock.patch("aea.cli.utils.generic.os.path.exists", return_value=True) class IsReadmePresentTestCase(TestCase): """Test case for is_readme_present method.""" def test_is_readme_present_positive(self, *mocks): """Test is_readme_present for positive result.""" self.assertTrue(is_readme_present("readme/path")) @mock.patch("aea.cli.utils.package_utils.get_package_path", return_value="some_path") @mock.patch("aea.cli.utils.package_utils.is_item_present") @pytest.mark.parametrize("vendor", [True, False]) def test_get_package_path_unified(mock_present, mock_path, vendor): """Test 'get_package_path_unified'.""" contex_mock = mock.MagicMock() contex_mock.agent_config.author = "some_author" if vendor else "another_author" mock_present.return_value = vendor public_id_mock = mock.MagicMock(author="some_author") result = get_package_path_unified( ".", contex_mock.agent_config, "some_component_type", public_id_mock ) assert result == "some_path" @mock.patch("aea.cli.utils.package_utils.get_package_path", return_value="some_path") @mock.patch("aea.cli.utils.package_utils.is_item_present") @pytest.mark.parametrize("vendor", [True, False]) def test_get_dotted_package_path_unified(mock_present, mock_path, vendor): """Test 'get_package_path_unified'.""" contex_mock = mock.MagicMock() contex_mock.cwd = "." contex_mock.agent_config.author = "some_author" if vendor else "another_author" mock_present.return_value = vendor public_id_mock = mock.MagicMock(author="some_author") result = get_dotted_package_path_unified( ".", contex_mock.agent_config, "some_component_type", public_id_mock ) assert result == "some_path" @mock.patch("aea.cli.utils.package_utils.is_item_present", return_value=False) @pytest.mark.parametrize("vendor", [True, False]) def test_is_item_present_unified(mock_, vendor): """Test 'is_item_present_unified'.""" contex_mock = mock.MagicMock() contex_mock.agent_config.author = "some_author" if vendor else "another_author" public_id_mock = mock.MagicMock(author="some_author") result = is_item_present_unified(contex_mock, "some_component_type", public_id_mock) assert not result @pytest.mark.parametrize( ["public_id", "expected_outcome"], [ (PublicId.from_str("author/package:0.1.0"), False), (PublicId.from_str("author/package:latest"), False), (PublicId.from_str("fetchai/oef:0.1.0"), False), (PublicId.from_str("fetchai/oef:latest"), False), (PublicId.from_str("fetchai/stub:latest"), False), (PublicId.from_str(DEFAULT_PROTOCOL), False), ], ) def test_is_distributed_item(public_id, expected_outcome): """Test the 'is_distributed_item' CLI utility function.""" assert is_distributed_item(public_id) is expected_outcome class TestGetWalletFromtx(AEATestCaseEmpty): """Test get_wallet_from_context.""" def test_get_wallet_from_ctx(self): """Test get_wallet_from_context.""" ctx = mock.Mock() with cd(self._get_cwd()): assert isinstance(get_wallet_from_context(ctx), Wallet) def test_override_ledger_configurations_negative(): """Test override ledger configurations function util when nothing to override.""" agent_config = MagicMock() agent_config.component_configurations = {} expected_configurations = deepcopy(LedgerApis.ledger_api_configs) _override_ledger_configurations(agent_config) actual_configurations = LedgerApis.ledger_api_configs assert expected_configurations == actual_configurations def test_override_ledger_configurations_positive(): """Test override ledger configurations function util with fields to override.""" new_chain_id = "some_chain" agent_config = MagicMock() agent_config.component_configurations = { ComponentId(ComponentType.CONNECTION, PublicId.from_str(LEDGER_CONNECTION)): { "config": {"ledger_apis": {DEFAULT_LEDGER: {"chain_id": new_chain_id}}} } } old_configurations = deepcopy(LedgerApis.ledger_api_configs) expected_configurations = deepcopy(old_configurations[DEFAULT_LEDGER]) expected_configurations["chain_id"] = new_chain_id try: _override_ledger_configurations(agent_config) actual_configurations = LedgerApis.ledger_api_configs.get("fetchai") assert expected_configurations == actual_configurations finally: # this is important - _ovveride_ledger_configurations does # side-effect to LedgerApis.ledger_api_configs LedgerApis.ledger_api_configs = old_configurations assert ( LedgerApis.ledger_api_configs[DEFAULT_LEDGER]["chain_id"] == FETCHAI_DEFAULT_CHAIN_ID ) def test_mutually_exclusive_usage_error(): """Test MutuallyExclusiveOption.handle_parse_result.""" opt = MutuallyExclusiveOption(["--arg1"], mutually_exclusive=["arg2"]) with pytest.raises( UsageError, match=f"Illegal usage: `arg1` is mutually exclusive with arguments `{', '.join(['arg2'])}`.", ): opt.handle_parse_result(MagicMock(), {"arg1": None, "arg2": None}, []) @mock.patch("aea.cli.utils.config.get_or_create_cli_config", return_value={}) def test_set_cli_author_negative(*_mocks): """Test set_cli_author, negative case.""" with pytest.raises( ClickException, match="The AEA configurations are not initialized. Use `aea init` before continuing.", ): set_cli_author(MagicMock()) @mock.patch( "aea.cli.utils.config.get_or_create_cli_config", return_value=dict(author="some_author"), ) def test_set_cli_author_positive(*_mocks): """Test set_cli_author, positive case.""" context_mock = MagicMock() set_cli_author(context_mock) context_mock.obj.set_config.assert_called_with("cli_author", "some_author") def test_password_option(): """Test password option.""" @click.command() @password_option() def cmd(password): raise ValueError(password) # no password specified with pytest.raises(ValueError, match="None"): CliRunner().invoke(cmd, [], catch_exceptions=False, standalone_mode=False) # --password specified password = uuid4().hex with pytest.raises(ValueError, match=password): CliRunner().invoke( cmd, ["--password", password], catch_exceptions=False, standalone_mode=False ) # -p to ask with click.prompt with pytest.raises(ValueError, match=password): with patch("click.prompt", return_value=password): CliRunner().invoke( cmd, ["-p"], catch_exceptions=False, standalone_mode=False ) # -p and --password togehter, -p in priority with pytest.raises(ValueError, match=password): with patch("click.prompt", return_value="prompted_password"): CliRunner().invoke( cmd, ["-p", "--password", password], catch_exceptions=False, standalone_mode=False, ) def test_context_registry_path_does_not_exist(): """Test context registry path specified but not found.""" with pytest.raises( ValueError, match="Registry path directory provided .* can not be found." ): Context( cwd=".", verbosity="", registry_path="some_path_does_not_exist" ).registry_path with TemporaryDirectory() as tmp_dir: with cd(tmp_dir): with pytest.raises( ValueError, match="Registry path not provided and local registry `packages` not found", ): Context(cwd=".", verbosity="", registry_path=None).registry_path ================================================ FILE: tests/test_aea/test_cli/tools_for_testing.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tools used for CLI registry testing.""" from typing import List from unittest.mock import Mock from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from click import ClickException from packaging.specifiers import SpecifierSet import aea from aea.configurations.base import PackageVersion from aea.configurations.constants import DEFAULT_LEDGER from tests.conftest import AUTHOR from tests.test_aea.test_cli.constants import DEFAULT_TESTING_VERSION def raise_click_exception(*args, **kwargs): """Raise ClickException.""" raise ClickException("Message") class AgentConfigMock: """A class to mock Agent config.""" def __init__(self, *args, **kwargs): """Init the AgentConfigMock object.""" self.aea_version_specifiers: SpecifierSet = kwargs.get( "aea_version_specifier", SpecifierSet(f"=={aea.__version__}") ) self.connections: List[str] = kwargs.get("connections", []) self.contracts: List[str] = kwargs.get("contracts", []) self.description: str = kwargs.get("description", "") self.version: str = kwargs.get("version", "") self.protocols: List[str] = kwargs.get("protocols", []) self.skills: List[str] = kwargs.get("skills", []) self.agent_name: str = kwargs.get("agent_name", "agent_name") self.author: str = AUTHOR private_key_paths = kwargs.get("private_key_paths", []) self.private_key_paths = Mock() self.private_key_paths.read_all = Mock(return_value=private_key_paths) self.private_key_paths.read = Mock( return_value=private_key_paths[0][1] if private_key_paths else None ) connection_private_key_paths = kwargs.get("connection_private_key_paths", []) self.connection_private_key_paths = Mock() self.connection_private_key_paths.read_all = Mock( return_value=connection_private_key_paths ) self.get = lambda x, default=None: getattr(self, x, default) self.component_configurations = {} self.package_dependencies = set() self.config: dict = {} self.default_ledger = DEFAULT_LEDGER name = "name" class FaultyAgentConfigMock: """A Class to mock Agent config with missing attributes.""" def __init__(self, *args, **kwargs): """Init faulty agent config.""" class ContextMock: """A class to mock Context.""" cwd = "cwd" def __init__(self, *args, **kwargs): """Init the ContextMock object.""" self.invoke = Mock() self.agent_config = AgentConfigMock(*args, **kwargs) self.config: dict = {} self.connection_loader = ConfigLoaderMock() self.agent_loader = ConfigLoaderMock() self.clean_paths: List = [] self.obj = self self.registry_path = "packages" self.cwd = "cwd" def set_config(self, key, value): """Set config.""" setattr(self.config, key, value) class PublicIdMock: """A class to mock PublicId.""" DEFAULT_VERSION = DEFAULT_TESTING_VERSION def __init__(self, author=AUTHOR, name="name", version=DEFAULT_TESTING_VERSION): """Init the Public ID mock object.""" self.name = name self.author = author self.version = version @classmethod def from_str(cls, public_id): """Create object from str public_id without validation.""" author, name, version = public_id.replace(":", "/").split("/") return cls(author, name, version) @property def package_version(self) -> PackageVersion: """Get package version.""" return PackageVersion(self.version) class AEAConfMock: """A class to mock AgentConfig.""" def __init__(self, *args, **kwargs): """Init the AEAConf mock object.""" self.author = AUTHOR self.version = DEFAULT_TESTING_VERSION self.ledger_apis = Mock() ledger_apis = ( (CosmosCrypto.identifier, "value"), (EthereumCrypto.identifier, "value"), ) self.ledger_apis.read_all = Mock(return_value=ledger_apis) ledger_api_config = {"host": "host", "port": "port", "address": "address"} self.ledger_apis.read = Mock(return_value=ledger_api_config) class ConfigLoaderMock: """A class to mock ConfigLoader.""" def __init__(self, *args, **kwargs): """Init the ConfigLoader mock object.""" self.required_fields = kwargs.get("required_fields", []) def load(self, *args, **kwargs): """Mock the load method.""" return AEAConfMock() def dump(self, *args, **kwargs): """Mock the dump method.""" pass class StopTest(Exception): """An exception to stop test.""" pass def raise_stoptest(*args, **kwargs): """Raise StopTest exception.""" raise StopTest() ================================================ FILE: tests/test_aea/test_components/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea/components/*""" ================================================ FILE: tests/test_aea/test_components/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea/components/base.py""" import sys from pathlib import Path import pytest from aea.components.base import Component, load_aea_package from aea.configurations.base import ConnectionConfig, ProtocolConfig from tests.conftest import ROOT_DIR class TestComponentProperties: """Test accessibility of component properties.""" def setup_class(self): """Setup test.""" self.configuration = ProtocolConfig( "name", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) self.configuration.build_directory = "test" self.component = Component(configuration=self.configuration) self.directory = Path() self.component._directory = self.directory def test_component_type(self): """Test component type attribute.""" assert self.component.component_type == self.configuration.component_type def test_is_vendor(self): """Test component type attribute.""" assert self.component.is_vendor is False def test_prefix_import_path(self): """Test component type attribute.""" assert self.component.prefix_import_path == "packages.author.protocols.name" def test_component_id(self): """Test component id.""" assert self.component.component_id == self.configuration.component_id def test_public_id(self): """Test public id.""" assert self.component.public_id == self.configuration.public_id def test_directory(self): """Test directory.""" assert self.component.directory == self.directory def test_build_directory(self): """Test directory.""" assert self.component.build_directory def test_directory_setter(): """Test directory.""" configuration = ProtocolConfig( "author", "name", "0.1.0", protocol_specification_id="some/author:0.1.0" ) component = Component(configuration=configuration) with pytest.raises(ValueError): component.directory new_path = Path("new_path") component.directory = new_path assert component.directory == new_path def test_load_aea_package(): """Test aea package load.""" config = ConnectionConfig("http_client", "fetchai", "0.5.0") config.directory = ( Path(ROOT_DIR) / "packages" / "fetchai" / "connections" / "http_client" ) load_aea_package(config) def test_load_aea_package_twice(): """Test aea package load twice and ensure python objects stay the same.""" config = ConnectionConfig("http_client", "fetchai", "0.5.0") config.directory = ( Path(ROOT_DIR) / "packages" / "fetchai" / "connections" / "http_client" ) sys.modules.pop("packages.fetchai.connections.http_client.connection", None) load_aea_package(config) assert "packages.fetchai.connections.http_client.connection" not in sys.modules from packages.fetchai.connections.http_client.connection import HTTPClientConnection assert "packages.fetchai.connections.http_client.connection" in sys.modules BaseHTTPCLientConnection = HTTPClientConnection load_aea_package(config) from packages.fetchai.connections.http_client.connection import HTTPClientConnection assert BaseHTTPCLientConnection is HTTPClientConnection ================================================ FILE: tests/test_aea/test_components/test_loader.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea/components/loader.py""" from pathlib import Path from unittest import mock import pytest from aea.components.loader import load_component_from_config from aea.configurations.base import ProtocolConfig from aea.exceptions import ( AEAComponentLoadException, AEAInstantiationException, AEAPackageLoadingError, ) from aea.helpers.base import cd from aea.protocols.base import Protocol from aea.test_tools.test_cases import AEATestCaseEmpty from tests.common.pexpect_popen import PexpectWrapper @pytest.fixture(scope="module") def component_configuration(): """Return a component configuration.""" return ProtocolConfig( "a_protocol", "an_author", "0.1.0", protocol_specification_id="some/author:0.1.0", ) def test_component_loading_generic_exception(component_configuration): """Test 'load_component_from_config' method when a generic "Exception" occurs.""" with mock.patch.object( Protocol, "from_config", side_effect=Exception("Generic exception") ): with pytest.raises( Exception, match="Package loading error: An error occurred while loading" ): load_component_from_config(component_configuration) def test_component_loading_generic_module_not_found_error(component_configuration): """Test 'load_component_from_config' method when a generic "ModuleNotFoundError" occurs.""" with mock.patch.object( Protocol, "from_config", side_effect=ModuleNotFoundError( "Package loading error: An error occurred while loading .*: Generic error" ), ): with pytest.raises(ModuleNotFoundError, match="Generic error"): load_component_from_config(component_configuration) def test_component_loading_module_not_found_error_non_framework_package( component_configuration, ): """Test 'load_component_from_config' method when a "ModuleNotFoundError" occurs for a generic import path (non framework related.""" with mock.patch.object( Protocol, "from_config", side_effect=ModuleNotFoundError("No module named 'generic.package'"), ): with pytest.raises(ModuleNotFoundError): load_component_from_config(component_configuration) def test_component_loading_module_not_found_error_framework_package( component_configuration, ): """Test 'load_component_from_config' method when a "ModuleNotFoundError" occurs for a framework-related import (starts with 'packages') but for some reason it doesn't contain the author name.""" with mock.patch.object( Protocol, "from_config", side_effect=ModuleNotFoundError("No module named 'packages'"), ): with pytest.raises(ModuleNotFoundError, match="No module named 'packages'"): load_component_from_config(component_configuration) def test_component_loading_module_not_found_error_framework_package_with_wrong_author( component_configuration, ): """Test 'load_component_from_config' method when a "ModuleNotFoundError" occurs for a framework-related import (starts with 'packages') with wrong author.""" with mock.patch.object( Protocol, "from_config", side_effect=ModuleNotFoundError("No module named 'packages.some_author'"), ): with pytest.raises( AEAPackageLoadingError, match="An error occurred while loading protocol an_author/a_protocol:0.1.0:", ) as e: load_component_from_config(component_configuration) assert "some_type" in str(e) def test_component_loading_module_not_found_error_framework_package_with_wrong_type( component_configuration, ): """Test 'load_component_from_config' method when a "ModuleNotFoundError" occurs for a framework-related import (starts with 'packages') with correct author but wrong type.""" with mock.patch.object( Protocol, "from_config", side_effect=ModuleNotFoundError( "No module named 'packages.some_author.some_type'" ), ): with pytest.raises( AEAPackageLoadingError, match="An error occurred while loading protocol an_author/a_protocol:0.1.0:", ) as e: load_component_from_config(component_configuration) assert ( "package 'packages/some_author' of type 'protocols' exists, but cannot find module 'some_type'" in e.value.args[0] ) def test_component_loading_module_not_found_error_framework_package_with_wrong_name( component_configuration, ): """Test 'load_component_from_config' method when a "ModuleNotFoundError" occurs for a framework-related import (starts with 'packages') with correct author and type but wrong name.""" with mock.patch.object( Protocol, "from_config", side_effect=ModuleNotFoundError( "No module named 'packages.some_author.protocols.some_name'" ), ): with pytest.raises( AEAPackageLoadingError, match="An error occurred while loading protocol an_author/a_protocol:0.1.0:", ) as e: load_component_from_config(component_configuration) assert ( " No AEA package found with author name 'some_author', type 'protocols', name 'some_name'" in e.value.args[0] ) def test_component_loading_module_not_found_error_framework_package_with_wrong_suffix( component_configuration, ): """Test 'load_component_from_config' method when a "ModuleNotFoundError" occurs for a framework-related import (starts with 'packages') with correct author and type but wrong suffix.""" with mock.patch.object( Protocol, "from_config", side_effect=ModuleNotFoundError( "No module named 'packages.some_author.protocols.some_name.some_subpackage'" ), ): with pytest.raises( AEAPackageLoadingError, match="An error occurred while loading protocol an_author/a_protocol:0.1.0:", ) as e: load_component_from_config(component_configuration) assert ( "package 'packages/some_author' of type 'protocols' exists, but cannot find module 'some_subpackage'" in e.value.args[0] ) def test_component_loading_instantiation_exception(component_configuration): """Test 'load_component_from_config' method when a generic "Exception" occurs.""" with mock.patch.object( Protocol, "from_config", side_effect=AEAInstantiationException("Generic exception"), ): with pytest.raises(AEAInstantiationException): load_component_from_config(component_configuration) def test_component_loading_component_exception(component_configuration): """Test 'load_component_from_config' method when a generic "Exception" occurs.""" with mock.patch.object( Protocol, "from_config", side_effect=AEAComponentLoadException("Generic exception"), ): with pytest.raises( AEAPackageLoadingError, match="Package loading error: An error occurred while loading protocol an_author/a_protocol:0.1.0: Generic exception", ): load_component_from_config(component_configuration) class TestLoadFailedCauseImportedPackageNotFound(AEATestCaseEmpty): """Test package not found in import.""" def test_load_component_failed_cause_package_not_found(self): """Test package not found in import.""" self.generate_private_key() self.add_private_key() self.add_item("skill", "fetchai/echo:latest", local=True) with cd(self._get_cwd()): echo_dir = "./vendor/fetchai/skills/echo" handlers_file = Path(echo_dir) / "handlers.py" assert handlers_file.exists() file_data = handlers_file.read_text() file_data = file_data.replace( "from packages.fetchai.protocols.default", "from packages.fetchai.protocols.not_exist_protocol", ) handlers_file.write_text(file_data) with cd("./vendor/fetchai"): self.run_cli_command("fingerprint", "skill", "fetchai/echo:0.20.6") proc = PexpectWrapper.aea_cli(["run"], cwd=self._get_cwd()) proc.expect_all( ["No AEA package found with author name", "not_exist_protocol"] ) ================================================ FILE: tests/test_aea/test_components/test_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea/components/utils.py""" from itertools import chain from unittest.mock import patch from aea.components.utils import _enlist_component_packages, _populate_packages from tests.test_aea.test_aea import test_act def test_modules_enlisted_and_loaded(): """Test modules enlisted and loaded back.""" # ensure some packages loaded test_act() packages = _enlist_component_packages() num_of_packages = len(list(chain(*packages.values()))) assert num_of_packages > 0, "No packages present" with patch("aea.components.utils.perform_load_aea_package") as mock_package_load: _populate_packages(packages) assert mock_package_load.call_count == num_of_packages, packages ================================================ FILE: tests/test_aea/test_configurations/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the aea.configurations module.""" ================================================ FILE: tests/test_aea/test_configurations/test_aea_config.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the aea configurations.""" import io from enum import Enum from pathlib import Path from textwrap import dedent from typing import Any, List, Sequence from unittest import TestCase import pytest import yaml from jsonschema.exceptions import ValidationError # type: ignore from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.configurations.base import ( AgentConfig, ComponentId, ComponentType, PackageType, PublicId, ) from aea.configurations.loader import ConfigLoader, ConfigLoaders from aea.exceptions import AEAValidationError from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.helpers.yaml_utils import yaml_load_all from tests.conftest import CUR_PATH, ROOT_DIR class NotSet(type): """Definition to use when variable is not set.""" base_config = dedent( """ agent_name: my_seller_aea author: solarw version: 0.1.0 license: Apache-2.0 fingerprint: {} fingerprint_ignore_patterns: [] aea_version: '>=1.0.0, <2.0.0' description: '' connections: [] contracts: [] protocols: [] skills: [] default_connection: null default_ledger: cosmos required_ledgers: [cosmos] private_key_paths: cosmos: tests/data/cosmos_private_key.txt connection_private_key_paths: cosmos: tests/data/cosmos_private_key.txt dependencies: {} """ ) class BaseConfigTestVariable(TestCase): """Base class to test aea config variables.""" OPTION_NAME: str = "" CONFIG_ATTR_NAME: str = "" GOOD_VALUES: Sequence[Any] = [] INCORRECT_VALUES: List[Any] = [] BASE_CONFIG: str = base_config REQUIRED: bool = False AEA_ATTR_NAME: str = "" AEA_DEFAULT_VALUE: Any = None @classmethod def setUpClass(cls) -> None: """Skip tests for base class.""" if cls is BaseConfigTestVariable: pytest.skip("base class") super(BaseConfigTestVariable, cls).setUpClass() @property def loader(self) -> ConfigLoader: """ Create ConfigLoader for Agent config. :return: ConfigLoader for AgentConfig """ return ConfigLoader.from_configuration_type(PackageType.AGENT) def _make_configuration_yaml(self, value: Any = NotSet) -> str: """Create yaml text configuration file for aea with value for tested parameter. :param value: value to set for test config parameter :return: string yaml """ if value is NotSet: return self.BASE_CONFIG value = self._un_enum_value(value) return f"{self.BASE_CONFIG}\n" + yaml.dump({self.OPTION_NAME: value}) def _make_configuration(self, value: Any = NotSet) -> AgentConfig: """Create AgentConfig file using generated yaml file with value set. :param value: value to set for test config parameter :return: AgentConfig """ config_data = self._make_configuration_yaml(value) f = io.StringIO(config_data) return self.loader.load(f) @staticmethod def _un_enum_value(value: Any) -> Any: """Return enum.value if value is enum, otherwise just value.""" if isinstance(value, Enum): value = value.value return value def test_no_variable_passed(self) -> None: """Test option not specified in cofig.""" if self.REQUIRED: with self.assertRaises(ValidationError): self._make_configuration(NotSet) return configuration = self._make_configuration(NotSet) assert getattr(configuration, self.CONFIG_ATTR_NAME) is None def test_good_value_passed(self) -> None: """Test correct values parsed and set.""" for good_value in self.GOOD_VALUES: good_value = self._un_enum_value(good_value) configuration = self._make_configuration(good_value) assert getattr(configuration, self.CONFIG_ATTR_NAME) == good_value def test_incorrect_value_passed(self) -> None: """Test validation error on incorrect values.""" for incorrect_value in self.INCORRECT_VALUES: with pytest.raises( AEAValidationError, match="The following errors occurred during validation:", ): self._make_configuration(incorrect_value) def _get_aea_value(self, aea: AEA) -> Any: """Get AEA attribute value. :param aea: AEA instance to get attribute value from. :return: value of attribute. """ return getattr(aea, self.AEA_ATTR_NAME) def test_builder_applies_default_value_to_aea(self) -> None: """Test AEABuilder applies default value to AEA instance when option is not specified in config.""" configuration = self._make_configuration(NotSet) builder = AEABuilder() builder.set_from_configuration(configuration, aea_project_path=Path(".")) aea = builder.build() assert self._get_aea_value(aea) == self.AEA_DEFAULT_VALUE def test_builder_applies_config_value_to_aea(self) -> None: """Test AEABuilder applies value to AEA instance when option is specified in config.""" for good_value in self.GOOD_VALUES: configuration = self._make_configuration(good_value) builder = AEABuilder() builder.set_from_configuration( configuration, aea_project_path=Path(ROOT_DIR) ) aea = builder.build() assert self._get_aea_value(aea) == good_value class TestPeriodConfigVariable(BaseConfigTestVariable): """Test `period` aea config option.""" OPTION_NAME = "period" CONFIG_ATTR_NAME = "period" GOOD_VALUES = [0.1, 1.1] INCORRECT_VALUES = [0, "sTrING?", -1] REQUIRED = False AEA_ATTR_NAME = "_period" AEA_DEFAULT_VALUE = AEABuilder.DEFAULT_AGENT_ACT_PERIOD class TestExecutionTimeoutConfigVariable(BaseConfigTestVariable): """Test `execution_timeout` aea config option.""" OPTION_NAME = "execution_timeout" CONFIG_ATTR_NAME = "execution_timeout" GOOD_VALUES = [0, 1.1] INCORRECT_VALUES = ["sTrING?", -1] REQUIRED = False AEA_ATTR_NAME = "_execution_timeout" AEA_DEFAULT_VALUE = AEABuilder.DEFAULT_EXECUTION_TIMEOUT class TestMaxReactionsConfigVariable(BaseConfigTestVariable): """Test `max_reactions` aea config option.""" OPTION_NAME = "max_reactions" CONFIG_ATTR_NAME = "max_reactions" GOOD_VALUES = [1, 10] INCORRECT_VALUES = ["sTrING?", -1, 0, 1.1] REQUIRED = False AEA_ATTR_NAME = "max_reactions" AEA_DEFAULT_VALUE = AEABuilder.DEFAULT_MAX_REACTIONS class TestLoopModeConfigVariable(BaseConfigTestVariable): """Test `loop_mode` aea config option.""" OPTION_NAME = "loop_mode" CONFIG_ATTR_NAME = "loop_mode" GOOD_VALUES = ["async", "sync"] INCORRECT_VALUES = [None, "sTrING?", -1] REQUIRED = False AEA_ATTR_NAME = "_loop_mode" AEA_DEFAULT_VALUE = AEABuilder.DEFAULT_LOOP_MODE def _get_aea_value(self, aea: AEA) -> Any: """Get AEA attribute value. :param aea: AEA isntance to get atribute value from. :return: value of attribute. """ return aea.runtime.loop_mode class TestSkillExceptionPolicyConfigVariable(BaseConfigTestVariable): """Test `skill_exception_policy` aea config option.""" OPTION_NAME = "skill_exception_policy" CONFIG_ATTR_NAME = "skill_exception_policy" GOOD_VALUES = ExceptionPolicyEnum # type: ignore INCORRECT_VALUES = [None, "sTrING?", -1] REQUIRED = False AEA_ATTR_NAME = "_skills_exception_policy" AEA_DEFAULT_VALUE = ExceptionPolicyEnum.propagate class TestStorageUriConfigVariable(BaseConfigTestVariable): """Test `storage_uri` aea config option.""" OPTION_NAME = "storage_uri" CONFIG_ATTR_NAME = "storage_uri" GOOD_VALUES = ["sqlite://test"] # type: ignore INCORRECT_VALUES = [None, -1] REQUIRED = False AEA_ATTR_NAME = "_storage_uri" AEA_DEFAULT_VALUE = None class TestConnectionExceptionPolicyConfigVariable(BaseConfigTestVariable): """Test `skill_exception_policy` aea config option.""" OPTION_NAME = "connection_exception_policy" CONFIG_ATTR_NAME = "connection_exception_policy" GOOD_VALUES = ExceptionPolicyEnum # type: ignore INCORRECT_VALUES = [None, "sTrING?", -1] REQUIRED = False AEA_ATTR_NAME = "_connection_exception_policy" AEA_DEFAULT_VALUE = ExceptionPolicyEnum.propagate class TestRuntimeModeConfigVariable(BaseConfigTestVariable): """Test `runtime_mode` aea config option.""" OPTION_NAME = "runtime_mode" CONFIG_ATTR_NAME = "runtime_mode" GOOD_VALUES = ["threaded", "async"] INCORRECT_VALUES = [None, "sTrING?", -1] REQUIRED = False AEA_ATTR_NAME = "_runtime_mode" AEA_DEFAULT_VALUE = AEABuilder.DEFAULT_RUNTIME_MODE def test_agent_configuration_loading_multipage(): """Test agent configuration loading, multi-page case.""" loader = ConfigLoaders.from_package_type(PackageType.AGENT) agent_config = loader.load( Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() ) # test main agent configuration loaded correctly assert agent_config.agent_name == "myagent" assert agent_config.author == "fetchai" # test component configurations loaded correctly assert len(agent_config.component_configurations) == 1 keys = list(agent_config.component_configurations) dummy_skill_public_id = PublicId.from_str("dummy_author/dummy:0.1.0") expected_component_id = ComponentId("skill", dummy_skill_public_id) assert keys[0] == expected_component_id def test_agent_configuration_loading_multipage_when_empty_file(): """Test agent configuration loading, multi-page case, in case of empty file.""" with pytest.raises(ValueError, match="Agent configuration file was empty."): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(io.StringIO()) def test_agent_configuration_loading_multipage_when_type_not_found(): """Test agent configuration loading, multi-page case, when type not found in some component.""" # remove type field manually file = Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() jsons = list(yaml.safe_load_all(file)) jsons[1].pop("type") modified_file = io.StringIO() yaml.safe_dump_all(jsons, modified_file) modified_file.seek(0) with pytest.raises( ValueError, match="There are missing fields in component id 1: {'type'}." ): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(modified_file) def test_agent_configuration_loading_multipage_when_same_id(): """Test agent configuration loading, multi-page case, when there are two components with the same id.""" file = Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() jsons = list(yaml.safe_load_all(file)) # add twice the last component jsons.append(jsons[-1]) modified_file = io.StringIO() yaml.safe_dump_all(jsons, modified_file) modified_file.seek(0) with pytest.raises( ValueError, match=r"Configuration of component \(skill, dummy_author/dummy:0.1.0\) occurs more than once.", ): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(modified_file) def test_agent_configuration_loading_multipage_validation_error(): """Test agent configuration loading, multi-page case, when the configuration is invalid.""" file = Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() jsons = list(yaml.safe_load_all(file)) # make invalid the last component configuration jsons[-1]["invalid_attribute"] = "foo" modified_file = io.StringIO() yaml.safe_dump_all(jsons, modified_file) modified_file.seek(0) with pytest.raises( ValueError, match=r"Configuration of component \(skill, dummy_author/dummy:0.1.0\) is not valid.", ): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(modified_file) @pytest.mark.parametrize( "component_type", [ ComponentType.PROTOCOL, ComponentType.CONNECTION, ComponentType.CONTRACT, ComponentType.SKILL, ], ) def test_agent_configuration_loading_multipage_positive_case(component_type): """Test agent configuration loading, multi-page case, positive case.""" public_id = PublicId("dummy_author", "dummy", "0.1.0") file = Path(CUR_PATH, "data", "aea-config.example.yaml").open() json_data = yaml.safe_load(file) json_data[component_type.to_plural()].append(str(public_id)) modified_file = io.StringIO() yaml.safe_dump(json_data, modified_file) modified_file.flush() modified_file.write("---\n") modified_file.write(f"public_id: {public_id}\n") modified_file.write(f"type: {component_type.value}\n") modified_file.seek(0) expected_component_id = ComponentId( component_type, PublicId("dummy_author", "dummy", "0.1.0") ) loader = ConfigLoaders.from_package_type(PackageType.AGENT) agent_config = loader.load(modified_file) assert isinstance(agent_config.component_configurations, dict) assert len(agent_config.component_configurations) assert set(agent_config.component_configurations.keys()) == {expected_component_id} def test_agent_configuration_dump_multipage(): """Test agent configuration dump with component configuration.""" loader = ConfigLoaders.from_package_type(PackageType.AGENT) agent_config = loader.load( Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() ) # test main agent configuration loaded correctly assert agent_config.agent_name == "myagent" assert agent_config.author == "fetchai" # test component configurations loaded correctly assert len(agent_config.component_configurations) == 1 fp = io.StringIO() loader.dump(agent_config, fp) fp.seek(0) agent_config = yaml_load_all(fp) assert agent_config[0]["agent_name"] == "myagent" assert agent_config[1]["public_id"] == "dummy_author/dummy:0.1.0" assert agent_config[1]["type"] == "skill" def test_agent_configuration_dump_multipage_fails_bad_component_configuration(): """Test agent configuration dump with INCORRECT component configuration.""" loader = ConfigLoaders.from_package_type(PackageType.AGENT) agent_config = loader.load( Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() ) # test main agent configuration loaded correctly assert agent_config.agent_name == "myagent" assert agent_config.author == "fetchai" # test component configurations loaded correctly assert len(agent_config.component_configurations) == 1 list(agent_config.component_configurations.values())[0][ "BAD FIELD" ] = "not in specs!" fp = io.StringIO() with pytest.raises( ValueError, match="Configuration of component .* is not valid. ExtraPropertiesError: properties not expected: BAD FIELD", ): loader.dump(agent_config, fp) class TestTaskManagerModeConfigVariable(BaseConfigTestVariable): """Test `task_manager_mode` aea config option.""" OPTION_NAME = "task_manager_mode" CONFIG_ATTR_NAME = "task_manager_mode" GOOD_VALUES = ["threaded", "multiprocess"] INCORRECT_VALUES = [None, "sTrING?", -1] REQUIRED = False AEA_ATTR_NAME = "_task_manager_mode" AEA_DEFAULT_VALUE = AEABuilder.DEFAULT_TASKMANAGER_MODE ================================================ FILE: tests/test_aea/test_configurations/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the aea.configurations.base module.""" import re from copy import copy from pathlib import Path from unittest import TestCase, mock from unittest.mock import Mock import pytest import semver import yaml from packaging.specifiers import SpecifierSet from aea.configurations.base import ( AgentConfig, CRUDCollection, ComponentId, ComponentType, ConnectionConfig, ContractConfig, Dependency, PackageId, PackageType, PackageVersion, ProtocolConfig, ProtocolSpecification, PublicId, SkillConfig, SpeechActContentConfig, _check_aea_version, _compare_fingerprints, _get_default_configuration_file_name_from_type, dependencies_from_json, dependencies_to_json, ) from aea.configurations.constants import ( DEFAULT_AEA_CONFIG_FILE, DEFAULT_GIT_REF, DEFAULT_LEDGER, DEFAULT_PYPI_INDEX_URL, DEFAULT_SKILL_CONFIG_FILE, ) from aea.configurations.loader import ConfigLoaders, load_component_configuration from tests.conftest import ( AUTHOR, CUR_PATH, DUMMY_SKILL_PATH, ROOT_DIR, agent_config_files, connection_config_files, contract_config_files, protocol_config_files, random_string, skill_config_files, ) from tests.data.dummy_skill import PUBLIC_ID as DUMMY_SKILL_PUBLIC_ID class TestCRUDCollection: """Test the CRUDCollection data structure.""" def test_create_with_existing_key(self): """Test that creating and item with an existing key raises an exception.""" collection = CRUDCollection() collection.create("one", 1) with pytest.raises(ValueError, match="Item with name .* already present"): collection.create("one", 1) def test_read_not_empty(self): """Test that reading a previously created item gives a non-empty result.""" collection = CRUDCollection() collection.create("one", 1) item = collection.read("one") assert item == 1 def test_read_empty(self): """Test that reading with a non-existing key returns None.""" collection = CRUDCollection() item = collection.read("one") assert item is None def test_update(self): """Test that the update method works correctly.""" collection = CRUDCollection() collection.create("one", 1) assert collection.read("one") == 1 collection.update("one", 2) assert collection.read("one") == 2 def test_delete(self): """Test that the delete method works correctly.""" collection = CRUDCollection() collection.create("one", 1) assert collection.read("one") == 1 collection.delete("one") assert collection.read("one") is None def test_read_all(self): """Test that the read_all method works correctly.""" collection = CRUDCollection() collection.create("one", 1) collection.create("two", 2) keyvalue_pairs = collection.read_all() assert {("one", 1), ("two", 2)} == set(keyvalue_pairs) def test_keys(self): """Test the keys method.""" collection = CRUDCollection() collection.create("one", 1) collection.create("two", 2) keyvalue_pairs = collection.keys() assert {"one", "two"} == set(keyvalue_pairs) class TestContractConfig: """Test the contract configuration class.""" @pytest.mark.parametrize("contract_path", contract_config_files) def test_from_json_and_to_json(self, contract_path): """Test the 'from_json' method and 'to_json' work correctly.""" f = open(contract_path) original_json = yaml.safe_load(f) original_json["build_directory"] = "some" expected_config = ContractConfig.from_json(original_json) assert isinstance(expected_config, ContractConfig) expected_json = expected_config.json actual_config = ContractConfig.from_json(expected_json) actual_json = actual_config.json assert expected_json == actual_json class TestConnectionConfig: """Test the connection configuration class.""" @pytest.mark.parametrize("connection_path", connection_config_files) def test_from_json_and_to_json(self, connection_path): """Test the 'from_json' method and 'to_json' work correctly.""" f = open(connection_path) original_json = yaml.safe_load(f) original_json["build_directory"] = "some" expected_config = ConnectionConfig.from_json(original_json) assert isinstance(expected_config, ConnectionConfig) assert isinstance(expected_config.package_dependencies, set) assert not expected_config.is_abstract_component expected_json = expected_config.json actual_config = ConnectionConfig.from_json(expected_json) actual_json = actual_config.json assert expected_json == actual_json class TestProtocolConfig: """Test the protocol configuration class.""" @pytest.mark.parametrize("protocol_path", protocol_config_files) def test_from_json_and_to_json(self, protocol_path): """Test the 'from_json' method and 'to_json' work correctly.""" f = open(protocol_path) original_json = yaml.safe_load(f) original_json["build_directory"] = "some" expected_config = ProtocolConfig.from_json(original_json) assert isinstance(expected_config, ProtocolConfig) expected_json = expected_config.json actual_config = ProtocolConfig.from_json(expected_json) actual_json = actual_config.json assert expected_json == actual_json class TestSkillConfig: """ Test the skill configuration class. This suite tests also the handlers/tasks/behaviours/models configuration classes. """ @pytest.mark.parametrize("skill_path", skill_config_files) def test_from_json_and_to_json(self, skill_path): """Test the 'from_json' method and 'to_json' work correctly.""" f = open(skill_path) original_json = yaml.safe_load(f) original_json["build_directory"] = "some" expected_config = SkillConfig.from_json(original_json) assert isinstance(expected_config, SkillConfig) expected_json = expected_config.json actual_config = SkillConfig.from_json(expected_json) actual_json = actual_config.json assert expected_json == actual_json def test_update_method(self): """Test the update method.""" skill_config_path = Path(DUMMY_SKILL_PATH) loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) dummy_behaviour = skill_config.behaviours.read("dummy") expected_dummy_behaviour_args = copy(dummy_behaviour.args) expected_dummy_behaviour_args["behaviour_arg_1"] = 42 dummy_handler = skill_config.handlers.read("dummy") expected_dummy_handler_args = copy(dummy_handler.args) expected_dummy_handler_args["handler_arg_1"] = 42 dummy_model = skill_config.models.read("dummy") expected_dummy_model_args = copy(dummy_model.args) expected_dummy_model_args["model_arg_1"] = 42 new_configurations = { "behaviours": {"dummy": {"args": dict(behaviour_arg_1=42)}}, "handlers": {"dummy": {"args": dict(handler_arg_1=42)}}, "models": {"dummy": {"args": dict(model_arg_1=42)}}, } directory = "test_directory" skill_config.directory = directory skill_config.update(new_configurations) assert skill_config.directory == directory assert ( expected_dummy_behaviour_args == skill_config.behaviours.read("dummy").args ) assert expected_dummy_handler_args == skill_config.handlers.read("dummy").args assert expected_dummy_model_args == skill_config.models.read("dummy").args assert len(skill_config.package_dependencies) def test_update_method_raises_error_if_skill_component_not_allowed(self): """Test that we raise error if the custom configuration contain unexpected skill components.""" skill_config_path = Path( ROOT_DIR, "packages", "fetchai", "skills", "error", DEFAULT_SKILL_CONFIG_FILE, ) loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) new_configurations = { "behaviours": {"new_behaviour": {"args": {}}}, "handlers": {"new_handler": {"args": {}}}, "models": {"new_model": {"args": {}}}, } with pytest.raises( ValueError, match="Attribute `behaviours.new_behaviour.args` is not allowed to be updated!", ): skill_config.update(new_configurations) def test_update_method_raises_error_if_we_try_to_change_classname_of_skill_component( self, ): """Test that we raise error if we try to change the 'class_name' field of a skill component configuration.""" skill_config_path = Path( ROOT_DIR, "packages", "fetchai", "skills", "error", DEFAULT_SKILL_CONFIG_FILE, ) loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) new_configurations = { "handlers": {"error_handler": {"class_name": "SomeClass", "args": {}}}, } with pytest.raises( ValueError, match="Attribute `handlers.error_handler.class_name` is not allowed to be updated!", ): skill_config.update(new_configurations) class TestAgentConfig: """Test the agent configuration class.""" @pytest.mark.parametrize("agent_path", agent_config_files) def test_from_json_and_to_json(self, agent_path): """Test the 'from_json' method and 'to_json' work correctly.""" f = open(agent_path) original_jsons = list(yaml.safe_load_all(f)) components = original_jsons[1:] original_json = original_jsons[0] original_json["component_configurations"] = components original_json["build_entrypoint"] = "some" expected_config = AgentConfig.from_json(original_json) assert isinstance(expected_config, AgentConfig) expected_json = expected_config.json actual_config = AgentConfig.from_json(expected_json) actual_json = actual_config.json assert expected_json == actual_json class TestAgentConfigUpdate: """Test methods that change the agent configuration.""" def setup(self): """Set up the tests.""" self.aea_config_path = Path( CUR_PATH, "data", "dummy_aea", DEFAULT_AEA_CONFIG_FILE ) self.loader = ConfigLoaders.from_package_type(PackageType.AGENT) self.aea_config: AgentConfig = self.loader.load(self.aea_config_path.open()) self.dummy_skill_component_id = ComponentId( ComponentType.SKILL, DUMMY_SKILL_PUBLIC_ID ) self.new_dummy_skill_config = { "behaviours": {"dummy": {"args": dict(behaviour_arg_1=42)}}, "handlers": {"dummy": {"args": dict(handler_arg_1=42)}}, "models": {"dummy": {"args": dict(model_arg_1=42)}}, } def test_all_components_id(self): """Test all components id listing.""" assert self.dummy_skill_component_id in self.aea_config.all_components_id def test_component_configurations_setter(self): """Test component configuration setter.""" assert self.aea_config.component_configurations == {} new_component_configurations = { self.dummy_skill_component_id: self.new_dummy_skill_config } self.aea_config.component_configurations = new_component_configurations def test_component_configurations_setter_negative(self): """Test component configuration setter with wrong configurations.""" assert self.aea_config.component_configurations == {} new_component_configurations = { self.dummy_skill_component_id: { "handlers": {"dummy": {"class_name": "SomeClass"}} } } with pytest.raises( ValueError, match=r"Configuration of component .* is not valid.*" ): self.aea_config.component_configurations = new_component_configurations def test_aea_version_setter(self): """Test 'aea_version' setter.""" new_version_specifier = "==0.1.0" self.aea_config.aea_version = new_version_specifier assert self.aea_config.aea_version == new_version_specifier assert self.aea_config.aea_version_specifiers == SpecifierSet( new_version_specifier ) def test_update(self): """Test the update method.""" new_private_key_paths = dict(ethereum="foo") expected_private_key_paths = dict( ethereum="foo", cosmos="cosmos_private_key.txt", fetchai="fetchai_private_key.txt", ) self.aea_config.update( dict( component_configurations={ self.dummy_skill_component_id: self.new_dummy_skill_config }, private_key_paths=new_private_key_paths, connection_private_key_paths=new_private_key_paths, ) ) assert ( self.aea_config.component_configurations[self.dummy_skill_component_id] == self.new_dummy_skill_config ) assert ( dict(self.aea_config.private_key_paths.read_all()) == expected_private_key_paths ) assert ( dict(self.aea_config.connection_private_key_paths.read_all()) == expected_private_key_paths ) # test idempotence self.aea_config.update( dict( component_configurations={ self.dummy_skill_component_id: self.new_dummy_skill_config } ) ) assert ( self.aea_config.component_configurations[self.dummy_skill_component_id] == self.new_dummy_skill_config ) # to json self.aea_config.json class GetDefaultConfigurationFileNameFromStrTestCase(TestCase): """Test case for _get_default_configuration_file_name_from_type method.""" def test__get_default_configuration_file_name_from_type_positive(self): """Test for _get_default_configuration_file_name_from_type method positive result.""" _get_default_configuration_file_name_from_type("agent") _get_default_configuration_file_name_from_type("connection") _get_default_configuration_file_name_from_type("protocol") _get_default_configuration_file_name_from_type("skill") _get_default_configuration_file_name_from_type("contract") class PublicIdTestCase(TestCase): """Test case for PublicId class.""" @mock.patch("aea.configurations.data_types.re.match", return_value=None) def test_public_id_from_str_not_matching(self, *mocks): """Test case for from_str method regex not matching.""" with self.assertRaises(ValueError): PublicId.from_str("public_id_str") def test_public_id_from_json_positive(self): """Test case for from_json method positive result.""" obj = {"author": AUTHOR, "name": "name", "version": "0.1.0"} PublicId.from_json(obj) def test_public_id_json_positive(self): """Test case for json property positive result.""" obj = PublicId(AUTHOR, "name", "0.1.0") obj.json def test_public_id_eq_positive(self): """Test case for json __eq__ method positive result.""" obj1 = PublicId(AUTHOR, "name", "0.1.0") obj2 = PublicId(AUTHOR, "name", "0.1.0") self.assertTrue(obj1 == obj2) def test_public_id_lt_positive(self): """Test case for json __lt__ method positive result.""" obj1 = PublicId(AUTHOR, "name", "1.0.0") obj2 = PublicId(AUTHOR, "name", "2.0.0") self.assertTrue(obj1 < obj2) def test_is_valid_str(self): """Test is_valid_str method.""" assert PublicId.is_valid_str("author/name:0.1.0") assert not PublicId.is_valid_str("author!name:0.1.0") def test_try_from_str(self): """Test is_valid_str method.""" assert PublicId.try_from_str("author/name:0.1.0") assert not PublicId.try_from_str("author!name:0.1.0") class AgentConfigTestCase(TestCase): """Test case for AgentConfig class.""" def test_init_logging_config_positive(self): """Test case for from_json method positive result.""" AgentConfig(agent_name="my_agent", author="fetchai", logging_config={}) def test_default_connection(self): """Test case for default_connection setter positive result.""" agent_config = AgentConfig(agent_name="my_agent", author="fetchai") agent_config.default_connection = None agent_config.default_connection = 1 agent_config.public_id def test_name_and_author(self): """Test case for default_connection setter positive result.""" agent_config = AgentConfig(agent_name="my_agent", author="fetchai") agent_config.name = "new_name" agent_config.author = "new_author" class SpeechActContentConfigTestCase(TestCase): """Test case for SpeechActContentConfig class.""" def test_speech_act_content_config_init_positive(self): """Test case for __init__ method positive result.""" SpeechActContentConfig() def test_json_positive(self): """Test case for json property positive result.""" config = SpeechActContentConfig() config.json def test_from_json_positive(self): """Test case for from_json method positive result.""" SpeechActContentConfig.from_json({}) class ProtocolSpecificationTestCase(TestCase): """Test case for ProtocolSpecification class.""" def test_init_positive(self): """Test case for __init__ method positive result.""" ProtocolSpecification( name="my_protocol", author="fetchai", protocol_specification_id="some/author:0.1.0", ) def test_json_positive(self): """Test case for json property positive result.""" obj = ProtocolSpecification( name="my_protocol", author="fetchai", protocol_specification_id="some/author:0.1.0", ) obj.json @mock.patch("aea.configurations.base.SpeechActContentConfig.from_json") def test_from_json_positive(self, *mocks): """Test case for from_json method positive result.""" json_disc = { "name": "name", "author": AUTHOR, "version": "0.1.0", "license": "license", "description": "description", "speech_acts": {"arg1": "arg1", "arg2": "arg2"}, "protocol_specification_id": "some/author:0.1.0", } ProtocolSpecification.from_json(json_disc) def test_package_type_plural(): """Test PackageType.to_plural""" assert PackageType.AGENT.to_plural() == "agents" assert PackageType.PROTOCOL.to_plural() == "protocols" assert PackageType.CONNECTION.to_plural() == "connections" assert PackageType.CONTRACT.to_plural() == "contracts" assert PackageType.SKILL.to_plural() == "skills" def test_package_type_str(): """Test PackageType.__str__""" assert str(PackageType.AGENT) == "agent" assert str(PackageType.PROTOCOL) == "protocol" assert str(PackageType.CONNECTION) == "connection" assert str(PackageType.CONTRACT) == "contract" assert str(PackageType.SKILL) == "skill" def test_component_type_str(): """Test ComponentType.__str__""" assert str(ComponentType.PROTOCOL) == "protocol" assert str(ComponentType.CONNECTION) == "connection" assert str(ComponentType.CONTRACT) == "contract" assert str(ComponentType.SKILL) == "skill" def test_configuration_ordered_json(): """Test configuration ordered json.""" configuration = ProtocolConfig( "name", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) configuration._key_order = ["aea_version"] configuration.ordered_json def test_public_id_versions(): """Test that a public id version can be initialized with different objects.""" PublicId("author", "name", "0.1.0") PublicId("author", "name", semver.VersionInfo(major=0, minor=1, patch=0)) def test_public_id_invalid_version(): """Test the case when the version id is of an invalid type.""" with pytest.raises(ValueError, match="Version type not valid."): PublicId("author", "name", object()) def test_public_id_from_string(): """Test parsing the public id from string.""" public_id = PublicId.from_str("author/package:0.1.0") assert public_id.author == "author" assert public_id.name == "package" assert public_id.version == "0.1.0" def test_public_id_from_string_without_version_string(): """Test parsing the public id without version string.""" public_id = PublicId.from_str("author/package") assert public_id.author == "author" assert public_id.name == "package" assert public_id.version == "latest" def test_public_id_from_string_with_version_string_latest(): """Test parsing the public id with version string 'latest'.""" public_id = PublicId.from_str("author/package:latest") assert public_id.author == "author" assert public_id.name == "package" assert public_id.version == "latest" def test_public_id_from_uri_path(): """Test PublicId.from_uri_path""" result = PublicId.from_uri_path("author/package_name/0.1.0") assert result.name == "package_name" assert result.author == "author" assert result.version == "0.1.0" def test_public_id_from_uri_path_wrong_input(): """Test that when a bad formatted path is passed in input of PublicId.from_uri_path an exception is raised.""" with pytest.raises( ValueError, match="Input 'bad/formatted:input' is not well formatted." ): PublicId.from_uri_path("bad/formatted:input") def test_public_id_to_uri_path(): """Test PublicId.to_uri_path""" public_id = PublicId("author", "name", "0.1.0") assert public_id.to_uri_path == "author/name/0.1.0" def test_pubic_id_repr(): """Test PublicId.__repr__""" public_id = PublicId("author", "name", "0.1.0") assert repr(public_id) == "" def test_pubic_id_to_latest(): """Test PublicId.to_latest""" public_id = PublicId("author", "name", "0.1.0") expected_public_id = PublicId("author", "name", "latest") actual_public_id = public_id.to_latest() assert expected_public_id == actual_public_id def test_pubic_id_to_any(): """Test PublicId.to_any""" public_id = PublicId("author", "name", "0.1.0") expected_public_id = PublicId("author", "name", "any") actual_public_id = public_id.to_any() assert expected_public_id == actual_public_id def test_pubic_id_same_prefix(): """Test PublicId.same_prefix""" same_1 = PublicId("author", "name", "0.1.0") same_2 = PublicId("author", "name", "0.1.1") different = PublicId("author", "different_name", "0.1.0") assert same_1.same_prefix(same_2) assert same_2.same_prefix(same_1) assert not different.same_prefix(same_1) assert not same_1.same_prefix(different) assert not different.same_prefix(same_2) assert not same_2.same_prefix(different) def test_public_id_comparator_when_author_is_different(): """Test PublicId.__lt__ when author is different.""" pid1 = PublicId("author_1", "name", "0.1.0") pid2 = PublicId("author_2", "name", "0.1.0") with pytest.raises( ValueError, match="The public IDs .* and .* cannot be compared. Their author or name attributes are different.", ): _ = pid1 < pid2 def test_public_id_comparator_when_name_is_different(): """Test PublicId.__lt__ when author is different.""" pid1 = PublicId("author", "name_1", "0.1.0") pid2 = PublicId("author", "name_2", "0.1.0") with pytest.raises( ValueError, match="The public IDs .* and .* cannot be compared. Their author or name attributes are different.", ): _ = pid1 < pid2 def test_package_id_version(): """Test PackageId.version""" package_id = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) assert package_id.version == "0.1.0" def test_package_id_str(): """Test PackageId.__str__""" package_id = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) assert str(package_id) == "(protocol, author/name:0.1.0)" def test_package_id_repr(): """Test PackageId.__repr__""" package_id = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) assert repr(package_id) == "PackageId(protocol, author/name:0.1.0)" def test_package_id_lt(): """Test PackageId.__lt__""" package_id_1 = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) package_id_2 = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.2.0")) assert package_id_1 < package_id_2 def test_package_id_from_uri_path(): """Test PackageId.from_uri_path""" result = PackageId.from_uri_path("skill/author/package_name/0.1.0") assert str(result.package_type) == "skill" assert result.public_id.name == "package_name" assert result.public_id.author == "author" assert result.public_id.version == "0.1.0" def test_package_id_to_uri_path(): """Test PackageId.to_uri_path""" package_id = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) assert package_id.to_uri_path == "protocol/author/name/0.1.0" def test_package_id_from_uri_path_negative(): """Test PackageId.from_uri_path with invalid type""" with pytest.raises( ValueError, match="Input 'not_a_valid_type/author/package_name/0.1.0' is not well formatted.", ): PackageId.from_uri_path("not_a_valid_type/author/package_name/0.1.0") def test_component_id_prefix_import_path(): """Test ComponentId.prefix_import_path""" component_id = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0") ) assert component_id.prefix_import_path == "packages.author.protocols.name" assert component_id.json def test_component_id_same_prefix(): """Test ComponentId.same_prefix""" component_id_1 = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0") ) component_id_2 = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.2.0") ) assert component_id_1.same_prefix(component_id_2) def test_component_configuration_load_file_not_found(): """Test Component.load when a file is not found.""" with mock.patch( "aea.configurations.loader.open_file", side_effect=FileNotFoundError ): with pytest.raises(FileNotFoundError): load_component_configuration( ComponentType.PROTOCOL, mock.MagicMock(spec=Path) ) def test_component_configuration_check_fingerprint_bad_directory(): """Test ComponentConfiguration.check_fingerprint when a bad directory is provided.""" config = ProtocolConfig( "name", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) with pytest.raises(ValueError, match="Directory .* is not valid."): config.check_fingerprint(Path("non_existing_directory")) def test_component_configuration_check_fingerprint_different_fingerprints_vendor(): """Test ComponentConfiguration.check_fingerprint when the fingerprints differ for a vendor package.""" config = ProtocolConfig( "name", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) package_dir = Path("path", "to", "dir") error_regex = ( f"Fingerprints for package {re.escape(str(package_dir))} do not match:\nExpected: {dict()}\nActual: {dict(foo='bar')}\n" + "Vendorized projects should not be tampered with, please revert any changes to protocol author/name:0.1.0" ) with pytest.raises(ValueError, match=error_regex): with mock.patch( "aea.configurations.base._compute_fingerprint", return_value={"foo": "bar"} ): _compare_fingerprints(config, package_dir, True, PackageType.PROTOCOL) def test_component_configuration_check_fingerprint_different_fingerprints_no_vendor(): """Test ComponentConfiguration.check_fingerprint when the fingerprints differ for a non-vendor package.""" config = ProtocolConfig( "name", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) package_dir = Path("path", "to", "dir") error_regex = ( f"Fingerprints for package {re.escape(str(package_dir))} do not match:\nExpected: {dict()}\nActual: {dict(foo='bar')}\n" + "Please fingerprint the package before continuing: 'aea fingerprint protocol author/name:0.1.0" ) with pytest.raises(ValueError, match=error_regex): with mock.patch( "aea.configurations.base._compute_fingerprint", return_value={"foo": "bar"} ): _compare_fingerprints(config, package_dir, False, PackageType.PROTOCOL) def test_agent_fingerprint_different_fingerprints(): """Test ComponentConfiguration.check_fingerprint for agent.""" config = Mock() config.fingerprint = {} package_dir = Path("path", "to", "dir") error_regex = ( f"Fingerprints for package {re.escape(str(package_dir))} do not match:\nExpected: {dict()}\nActual: {dict(foo='bar')}\n" + "Please fingerprint the package before continuing: 'aea fingerprint" ) with pytest.raises(ValueError, match=error_regex): with mock.patch( "aea.configurations.base._compute_fingerprint", return_value={"foo": "bar"} ): _compare_fingerprints(config, package_dir, False, PackageType.AGENT) def test_check_aea_version_when_it_fails(): """Test the check for the AEA version when it fails.""" config = ProtocolConfig( "name", "author", "0.1.0", aea_version=">0.1.0", protocol_specification_id="some/author:0.1.0", ) with mock.patch("aea.configurations.base.__aea_version__", "0.1.0"): with pytest.raises( ValueError, match="The CLI version is 0.1.0, but package author/name:0.1.0 requires version >0.1.0", ): _check_aea_version(config) def test_connection_config_with_connection_id(): """Test construction of ConnectionConfig with connection id.""" ConnectionConfig(connection_id=PublicId("name", "author", "0.1.0")) def test_agent_config_package_dependencies(): """Test agent config package dependencies.""" agent_config = AgentConfig("name", "author") assert agent_config.package_dependencies == set() pid = PublicId("author", "name", "0.1.0") agent_config.protocols.add(pid) agent_config.connections.add(pid) agent_config.contracts.add(pid) agent_config.skills.add(pid) assert agent_config.package_dependencies == { PackageId(PackageType.PROTOCOL, pid), PackageId(PackageType.CONNECTION, pid), PackageId(PackageType.CONTRACT, pid), PackageId(PackageType.SKILL, pid), } def test_agent_config_to_json_with_optional_configurations(): """Test agent config to json with optional configurations.""" agent_config = AgentConfig( "name", "author", period=0.1, execution_timeout=1.0, max_reactions=100, decision_maker_handler=dict(dotted_path="", file_path=""), error_handler=dict(dotted_path="", file_path=""), skill_exception_policy="propagate", connection_exception_policy="propagate", default_routing={"author/name:0.1.0": "author/name:0.1.0"}, currency_denominations={"fetchai": "fet"}, loop_mode="sync", runtime_mode="async", storage_uri="some_uri_to_storage", task_manager_mode="threaded", ) agent_config.default_connection = "author/name:0.1.0" agent_config.default_ledger = DEFAULT_LEDGER agent_config.json assert agent_config.package_id == PackageId.from_uri_path("agent/author/name/0.1.0") def test_protocol_specification_attributes(): """Test protocol specification attributes.""" protocol_specification = ProtocolSpecification( "name", "author", "0.1.0", protocol_specification_id="some/author:0.1.0" ) # test getter and setter for 'protobuf_snippets' assert protocol_specification.protobuf_snippets == {} protocol_specification.protobuf_snippets = {"a": 1} assert protocol_specification.protobuf_snippets == {"a": 1} # test getter and setter for 'dialogue_config' assert protocol_specification.dialogue_config == {} protocol_specification.dialogue_config = {"a": 1} assert protocol_specification.dialogue_config == {"a": 1} def test_contract_config_component_type(): """Test ContractConfig.component_type""" config = ContractConfig("name", "author", "0.1.0") assert config.component_type == ComponentType.CONTRACT def test_package_version_eq_negative(): """Test package version __eq__.""" v1 = PackageVersion("0.1.0") v2 = PackageVersion("0.2.0") assert v1 != v2 def test_package_version_lt(): """Test package version __lt__.""" v1 = PackageVersion("0.1.0") v2 = PackageVersion("0.2.0") v3 = PackageVersion("latest") assert v1 < v2 < v3 class TestDependencyGetPipInstallArgs: """Test 'get_pip_install_args' of 'Dependency' class.""" @classmethod def setup_class(cls): """Set up the class.""" cls.package_name = "package_name" cls.version = "<0.2.0,>=0.1.0" cls.custom_index = "https://test.pypi.org" cls.git_url = "https://github.com/some-author/some-repository.git" cls.ref = "develop" def test_only_name_and_version(self): """Test only with name and version.""" # no index and no git dep = Dependency(self.package_name, self.version) assert dep.get_pip_install_args() == [ f"{self.package_name}{self.version}", ] def test_name_version_index(self): """Test the method with name, version and index.""" dep = Dependency(self.package_name, self.version, self.custom_index) assert dep.get_pip_install_args() == [ "-i", self.custom_index, f"{self.package_name}{self.version}", ] def test_name_version_index_git(self): """Test the method when name, version, index and git fields are provided.""" dep = Dependency( self.package_name, self.version, self.custom_index, self.git_url ) git_url = f"git+{self.git_url}@{DEFAULT_GIT_REF}#egg={self.package_name}" assert dep.get_pip_install_args() == ["-i", self.custom_index, git_url] def test_name_version_index_git_ref(self): """Test the method when name, version, index, git and ref fields are provided.""" dep = Dependency( self.package_name, self.version, self.custom_index, self.git_url, self.ref ) git_url = f"git+{self.git_url}@{self.ref}#egg={self.package_name}" assert dep.get_pip_install_args() == ["-i", self.custom_index, git_url] def test_dependencies_from_to_json(): """Test serialization and deserialization of Dependencies object.""" version_str = "==0.1.0" git_url = "https://some-git-repo.git" branch = "some-branch" dep1 = Dependency("package_1", version_str, DEFAULT_PYPI_INDEX_URL, git_url, branch) dep2 = Dependency("package_2", version_str) expected_obj = {"package_1": dep1, "package_2": dep2} expected_obj_json = dependencies_to_json(expected_obj) assert expected_obj_json == { "package_1": { "version": "==0.1.0", "index": DEFAULT_PYPI_INDEX_URL, "git": git_url, "ref": branch, }, "package_2": {"version": version_str}, } actual_obj = dependencies_from_json(expected_obj_json) assert expected_obj == actual_obj def test_dependency_from_json_fail_more_than_one_key(): """Test failure of Dependency.from_json due to more than one key at the top level.""" bad_obj = {"field_1": {}, "field_2": {}} keys = set(bad_obj.keys()) with pytest.raises(ValueError, match=f"Only one key allowed, found {keys}"): Dependency.from_json(bad_obj) def test_dependency_from_json_fail_not_allowed_keys(): """Test failure of Dependency.from_json due to unallowed keys""" bad_obj = {"field_1": {"not-allowed-key": "value"}} with pytest.raises(ValueError, match="Not allowed keys: {'not-allowed-key'}"): Dependency.from_json(bad_obj) def test_dependency_to_string(): """Test dependency.__str__ method.""" dependency = Dependency( "package_1", "==0.1.0", "https://index.com", "https://some-repo.git", "branch" ) assert ( str(dependency) == "Dependency(name='package_1', version='==0.1.0', index='https://index.com', git='https://some-repo.git', ref='branch')" ) def test_check_public_id_consistency_negative(): """Test ComponentId.check_public_id_consistency raises error when directory does not exists.""" random_dir_name = random_string() with pytest.raises(ValueError, match=f"Directory {random_dir_name} is not valid."): component_configuration = ProtocolConfig( "name", "author", protocol_specification_id="some/author:0.1.0" ) component_configuration.check_public_id_consistency(Path(random_dir_name)) def test_check_public_id_consistency_positive(): """Test ComponentId.check_public_id_consistency works.""" skill_config_path = Path(DUMMY_SKILL_PATH) loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) skill_config.check_public_id_consistency(Path(skill_config_path).parent) def test_component_id_from_json(): """Test ComponentId.from_json.""" json_data = { "type": "connection", "author": "author", "name": "name", "version": "1.0.0", } assert ComponentId.from_json(json_data).json == json_data ================================================ FILE: tests/test_aea/test_configurations/test_loader.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the aea.configurations.loader module.""" import os from io import StringIO from pathlib import Path from unittest import mock from unittest.mock import MagicMock import pytest import yaml import aea from aea.configurations.base import PackageType, ProtocolSpecification from aea.configurations.loader import ConfigLoader from aea.configurations.validation import make_jsonschema_base_uri from aea.exceptions import AEAEnforceError from aea.protocols.generator.common import load_protocol_specification from tests.conftest import protocol_specification_files def test_windows_uri_path(): """Test uri path on running platform.""" path = Path("aea", "configurations").absolute() output = make_jsonschema_base_uri(path) if os.name == "nt": assert output == f"file:///{'/'.join(path.parts)}/" else: assert output == f"file:/{'/'.join(path.parts)}/" def test_config_loader_get_required_fields(): """Test required fields of ConfigLoader.""" config_loader = ConfigLoader.from_configuration_type(PackageType.PROTOCOL) config_loader.required_fields @mock.patch.object(aea.configurations.loader, "yaml_dump") @mock.patch.object(ConfigLoader, "validate") @mock.patch("builtins.open") def test_config_loader_dump_component(*_mocks): """Test ConfigLoader.dump""" config_loader = ConfigLoader.from_configuration_type(PackageType.PROTOCOL) configuration = MagicMock() config_loader.dump(configuration, open("foo")) @mock.patch.object(aea.configurations.loader, "yaml_dump_all") @mock.patch.object(ConfigLoader, "validate") @mock.patch("builtins.open") def test_config_loader_dump_agent_config(*_mocks): """Test ConfigLoader.dump""" config_loader = ConfigLoader.from_configuration_type(PackageType.AGENT) configuration = MagicMock(ordered_json={"component_configurations": []}) config_loader.dump(configuration, open("foo")) @pytest.mark.parametrize("spec_file_path", protocol_specification_files) def test_load_protocol_specification(spec_file_path): """Test for the utility function 'load_protocol_specification'""" result = load_protocol_specification(spec_file_path) assert type(result) == ProtocolSpecification @mock.patch("aea.protocols.generator.common.open_file") @mock.patch.object(ConfigLoader, "validate") def test_load_protocol_specification_only_first_part(*_mocks): """Test 'load_protocol_specification' with only the first part.""" valid_protocol_specification = dict( name="name", author="author", version="0.1.0", license="", aea_version="0.1.0", speech_acts={"example": {}}, protocol_specification_id="test/test:0.1.0", description="some", ) with mock.patch.object( yaml, "safe_load_all", return_value=[valid_protocol_specification] ): load_protocol_specification("foo") @mock.patch("aea.protocols.generator.common.open_file") @mock.patch.object(ConfigLoader, "validate") def test_load_protocol_specification_two_parts(*_mocks): """Test 'load_protocol_specification' with two parts.""" valid_protocol_specification = dict( name="name", author="author", version="0.1.0", license="", aea_version="0.1.0", speech_acts={"example": {}}, protocol_specification_id="test/test:0.1.0", description="some", ) with mock.patch.object( yaml, "safe_load_all", return_value=[valid_protocol_specification, valid_protocol_specification], ): load_protocol_specification("foo") def test_load_protocol_specification_too_many_parts(): """Test 'load_protocol_specification' with more than three parts.""" with pytest.raises( ValueError, match="Incorrect number of Yaml documents in the protocol specification.", ): with mock.patch.object( yaml, "safe_load_all", return_value=[{}] * 4 ), mock.patch("aea.protocols.generator.common.open_file"): load_protocol_specification("foo") @mock.patch.object(aea, "__version__", "0.1.0") def test_load_package_configuration_with_incompatible_aea_version(*_mocks): """Test that loading a package configuration with incompatible AEA version raises an error.""" config_loader = ConfigLoader.from_configuration_type( PackageType.PROTOCOL, skip_aea_validation=False ) specifier_set = "<2.0.0,>=1.0.0" file = StringIO(f"name: some_protocol\naea_version: '{specifier_set}'") with pytest.raises( AEAEnforceError, match=f"AEA version in use '0.1.0' is not compatible with the specifier set '{specifier_set}'.", ): config_loader.load(file) ================================================ FILE: tests/test_aea/test_configurations/test_manager.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the configurations manager module.""" import os from copy import deepcopy from pathlib import Path from unittest.mock import mock_open, patch import pytest import yaml from aea.configurations.constants import CONNECTION from aea.configurations.data_types import ComponentId, PublicId from aea.configurations.manager import ( AgentConfigManager, find_component_directory_from_component_id, handle_dotted_path, ) from aea.configurations.validation import SAME_MARK from aea.exceptions import AEAException from tests.conftest import ROOT_DIR DUMMY_AEA = Path(ROOT_DIR) / "tests" / "data" / "dummy_aea" agent_config_data = yaml.safe_load( """ agent_name: Agent0 author: dummy_author version: 1.0.0 description: dummy_aea agent description license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/local:0.21.6 contracts: [] protocols: - fetchai/default:1.1.7 skills: - dummy_author/dummy:0.1.0 - fetchai/error:0.18.6 default_connection: fetchai/local:0.21.6 default_ledger: cosmos logging_config: disable_existing_loggers: ${DISABLE_LOGS:bool} version: 1 private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt connection_private_key_paths: cosmos: cosmos_private_key.txt ethereum: ethereum_private_key.txt default_routing: {} """ ) def test_envvars_applied(): """Test env vars replaced with values.""" dct = deepcopy(agent_config_data) with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): os.environ["DISABLE_LOGS"] = "true" agent_config_manager = AgentConfigManager.load(".", substitude_env_vars=True) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] is True ) with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): os.environ["DISABLE_LOGS"] = "false" agent_config_manager = AgentConfigManager.load(".", substitude_env_vars=True) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] is False ) # no env! no default value os.environ.pop("DISABLE_LOGS") with pytest.raises( ValueError, match="`DISABLE_LOGS` not found in env variables and no default value set!", ): with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): agent_config_manager = AgentConfigManager.load( ".", substitude_env_vars=True ) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] is False ) # check default value specified dct["logging_config"]["disable_existing_loggers"] = "${DISABLE_LOGS:bool:true}" with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): agent_config_manager = AgentConfigManager.load(".", substitude_env_vars=True) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] is True ) # check incorrect data type dct["logging_config"]["disable_existing_loggers"] = "${DISABLE_LOGS:int:true}" with pytest.raises(ValueError, match="Cannot convert string `true` to type `int`"): with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): agent_config_manager = AgentConfigManager.load( ".", substitude_env_vars=True ) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] is True ) # not applied dct = deepcopy(agent_config_data) with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): os.environ["DISABLE_LOGS"] = "true" agent_config_manager = AgentConfigManager.load(".", substitude_env_vars=False) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] == dct["logging_config"]["disable_existing_loggers"] ) @patch.object(AgentConfigManager, "get_overridables", return_value=[{}, {}]) def test_envvars_preserved(*mocks): """Test env vars not modified on config update.""" dct = deepcopy(agent_config_data) new_cosmos_key_value = "cosmons_key_updated" with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): os.environ["DISABLE_LOGS"] = "true" agent_config_manager = AgentConfigManager.load(".", substitude_env_vars=False) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] == dct["logging_config"]["disable_existing_loggers"] ) with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): os.environ["DISABLE_LOGS"] = "true" agent_config_manager = AgentConfigManager.load(".", substitude_env_vars=False) assert ( agent_config_manager.json["private_key_paths"]["cosmos"] != new_cosmos_key_value ) agent_config_manager.update_config( {"private_key_paths": {"cosmos": new_cosmos_key_value}} ) assert ( agent_config_manager.json["logging_config"]["disable_existing_loggers"] == dct["logging_config"]["disable_existing_loggers"] ) assert ( agent_config_manager.json["private_key_paths"]["cosmos"] == new_cosmos_key_value ) def test_agent_attribute_get_set(): """Test agent config manager get set variables.""" dct = deepcopy(agent_config_data) with patch.object(AgentConfigManager, "_load_config_data", return_value=[dct]): os.environ["DISABLE_LOGS"] = "true" agent_config_manager = AgentConfigManager.load( DUMMY_AEA, substitude_env_vars=False ) assert ( agent_config_manager.get_variable("agent.default_ledger") == dct["default_ledger"] ) assert ( agent_config_manager.get_variable("vendor.fetchai.skills.error.name") == "error" ) assert ( agent_config_manager.get_variable( "vendor.fetchai.connections.local.is_abstract" ) is False ) agent_config_manager.set_variable( "vendor.fetchai.connections.local.is_abstract", True ) assert ( agent_config_manager.get_variable( "vendor.fetchai.connections.local.is_abstract" ) is True ) agent_config_manager.set_variable("agent.default_ledger", "fetchai") assert agent_config_manager.get_variable("agent.default_ledger") == "fetchai" assert ( agent_config_manager.json["component_configurations"][0]["is_abstract"] is True ) agent_config_manager = AgentConfigManager.load(DUMMY_AEA, substitude_env_vars=False) agent_config_manager.set_variable( "vendor.fetchai.connections.p2p_libp2p.config.delegate_uri", "some_url" ) assert ( agent_config_manager.get_variable( "vendor.fetchai.connections.p2p_libp2p.config.delegate_uri" ) == "some_url" ) with pytest.raises( ValueError, match="Attribute `does_not_exist` for AgentConfig does not exist" ): agent_config_manager.get_variable("agent.does_not_exist") agent_config_manager.validate_current_config() agent_config_manager.verify_private_keys(DUMMY_AEA, lambda x, y, z: None) def test_agent_attribute_get_overridables(): """Test AgentConfigManager.get_overridables.""" agent_config_manager = AgentConfigManager.load(DUMMY_AEA, substitude_env_vars=False) agent_overrides, component_overrides = agent_config_manager.get_overridables() assert "default_ledger" in agent_overrides assert "is_abstract" in list(component_overrides.values())[0] def test_dump_config(): """Test AgentConfigManager.dump_config.""" agent_config_manager = AgentConfigManager.load(DUMMY_AEA, substitude_env_vars=False) with patch("aea.configurations.manager.open_file", mock_open()), patch( "aea.configurations.loader.ConfigLoader.dump" ) as dump_mock: agent_config_manager.dump_config() dump_mock.assert_called_once() def test_handle_dotted_path(): """Test handle_dotted_path.""" with pytest.raises( AEAException, match=r"The root of the dotted path must be one of:" ): handle_dotted_path("something", author="fetchai") with pytest.raises( AEAException, match=r"The path is too short. Please specify a path up to an attribute name.", ): handle_dotted_path("skills", author="fetchai") with pytest.raises( AEAException, match=r"is not a valid component type. Please use one of" ): handle_dotted_path("vendor.fetchai.notskills.dummy.name", author="fetchai") with pytest.raises(AEAException, match=r"Resource .* does not exist."): handle_dotted_path("skills.notdummy.name", author="fetchai") def test_find_component_directory_from_component_id(): """Test find_component_directory_from_component_id.""" with pytest.raises(ValueError, match=r"Package .* not found."): find_component_directory_from_component_id( Path("."), ComponentId( component_type=CONNECTION, public_id=PublicId("test", "test", "1.0.1") ), ) def test_agent_attribute_get_and_apply_overridables(): """Test AgentConfigManager.get_overridables and apply it.""" agent_config_manager = AgentConfigManager.load(DUMMY_AEA, substitude_env_vars=False) initial_agent_config_json = agent_config_manager.json agent_overrides, component_overrides = agent_config_manager.get_overridables() agent_config_manager.update_config(agent_overrides) assert initial_agent_config_json == agent_config_manager.json agent_overrides["component_configurations"] = component_overrides agent_config_manager.update_config(agent_overrides) assert agent_config_manager._filter_overrides(agent_overrides) == SAME_MARK agent_overrides["execution_timeout"] = 12 assert agent_config_manager._filter_overrides(agent_overrides) == { "execution_timeout": 12 } ================================================ FILE: tests/test_aea/test_configurations/test_pypi.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for the aea.helpers.pypi module.""" import pytest from packaging.specifiers import SpecifierSet from aea.configurations.base import Dependency from aea.configurations.pypi import ( is_satisfiable, is_simple_dep, merge_dependencies, merge_dependencies_list, to_set_specifier, ) def test_is_satisfiable_common_cases(): """Test the 'is_satisfiable' function with common cases.""" assert is_satisfiable(SpecifierSet("<=1.0,>1.1, >0.9")) is False assert is_satisfiable(SpecifierSet("==1.0,!=1.0")) is False assert is_satisfiable(SpecifierSet("!=0.9,!=1.0")) is True assert is_satisfiable(SpecifierSet("<=1.0,>=1.0")) is True assert is_satisfiable(SpecifierSet("<=1.0,>1.0")) is False assert is_satisfiable(SpecifierSet("<1.0,<=1.0,>1.0")) is False assert is_satisfiable(SpecifierSet(">1.0,>=1.0,<1.0")) is False def test_is_satisfiable_with_compatibility_constraints(): """Test the 'is_satisfiable' function with ~= constraints.""" assert is_satisfiable(SpecifierSet("~=1.1,==2.0")) is False assert is_satisfiable(SpecifierSet("~=1.1,==1.0")) is False assert is_satisfiable(SpecifierSet("~=1.1,<=1.1")) is True assert is_satisfiable(SpecifierSet("~=1.1,<1.1")) is False assert is_satisfiable(SpecifierSet("~=1.0,<1.0")) is False assert is_satisfiable(SpecifierSet("~=1.1,==1.2")) is True assert is_satisfiable(SpecifierSet("~=1.1,>1.2")) is True assert is_satisfiable(SpecifierSet("==1.1,==1.2")) is False assert is_satisfiable(SpecifierSet("~= 1.4.5.0")) is True assert is_satisfiable(SpecifierSet("~= 1.4.5.0,>1.4.6")) is False assert is_satisfiable(SpecifierSet("~=2.2.post3,==2.*")) is True assert is_satisfiable(SpecifierSet("~=2.2.post3,>2.3")) is True assert is_satisfiable(SpecifierSet("~=2.2.post3,>3")) is False assert is_satisfiable(SpecifierSet("~=1.4.5a4,>1.4.6")) is True assert is_satisfiable(SpecifierSet("~=1.4.5a4,>1.5")) is False def test_is_satisfiable_with_legacy_version(): """Test the 'is_satisfiable' function with legacy versions.""" assert is_satisfiable(SpecifierSet("==1.0,==1.*")) is True def test_merge_dependencies(): """Test the 'merge_dependencies' function.""" dependencies_a = { "package_1": Dependency("package_1", "==0.1.0"), "package_2": Dependency("package_2", "==0.3.0"), "package_3": Dependency("package_3", "==0.2.0", "https://pypi.org"), } dependencies_b = { "package_1": Dependency("package_1", "==0.1.0"), "package_2": Dependency("package_2", "==0.2.0"), "package_4": Dependency("package_4", "==0.1.0", "https://pypi.org"), } expected_merged_dependencies = { "package_1": Dependency("package_1", "==0.1.0"), "package_2": Dependency("package_2", "==0.2.0,==0.3.0"), "package_3": Dependency("package_3", "==0.2.0", "https://pypi.org"), "package_4": Dependency("package_4", "==0.1.0", "https://pypi.org"), } assert expected_merged_dependencies == merge_dependencies( dependencies_a, dependencies_b ) assert expected_merged_dependencies == merge_dependencies_list( dependencies_a, dependencies_b ) def test_merge_dependencies_fails_not_simple(): """Test we can't merge dependencies if at least one of the overlapping dependency is not 'simple'.""" dependencies_a = { "package_1": Dependency("package_1", "==0.1.0", index="https://pypi.org"), } dependencies_b = { "package_1": Dependency("package_1", "==0.1.0", index="https://test.pypi.org"), } with pytest.raises( ValueError, match="cannot trivially merge these two PyPI dependencies:.*" ): merge_dependencies(dependencies_a, dependencies_b) def test_merge_dependencies_succeeds_not_simple_but_the_same(): """Test we can't merge dependencies if conflicting deps are not 'simple' but equal.""" dependencies_a = { "package_1": Dependency("package_1", "==0.1.0", index="https://pypi.org"), } dependencies_b = { "package_1": Dependency("package_1", "==0.1.0", index="https://pypi.org"), } expected_merged_dependencies = merge_dependencies(dependencies_a, dependencies_b) assert dependencies_a == dependencies_b == expected_merged_dependencies def test_is_simple_dep(): """Test the `is_simple_dep` function.""" dependency_a = Dependency("name", "==0.1.0") assert is_simple_dep(dependency_a), "Should be a simple dependency." dependency_b = Dependency("name") assert is_simple_dep(dependency_b), "Should be a simple dependency." dependency_c = Dependency("name", "==0.1.0", "pypi") assert not is_simple_dep(dependency_c), "Should not be a simple dependency." def test_to_set_specifier(): """Test the 'to_set_specifier' function.""" dependency_a = Dependency("name", "==0.1.0") assert to_set_specifier(dependency_a) == SpecifierSet("==0.1.0") ================================================ FILE: tests/test_aea/test_configurations/test_schema.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the JSON schemas of the configuration files.""" import itertools import json import os from itertools import zip_longest from pathlib import Path import jsonschema import pytest import yaml from jsonschema import Draft4Validator # type: ignore from aea.configurations.validation import make_jsonschema_base_uri from tests.conftest import ( AGENT_CONFIGURATION_SCHEMA, CONFIGURATION_SCHEMA_DIR, CONNECTION_CONFIGURATION_SCHEMA, CONTRACT_CONFIGURATION_SCHEMA, PROTOCOL_CONFIGURATION_SCHEMA, PROTOCOL_SPEC_CONFIGURATION_SCHEMA, ROOT_DIR, SKILL_CONFIGURATION_SCHEMA, agent_config_files, connection_config_files, contract_config_files, protocol_config_files, protocol_specification_files, skill_config_files, ) def test_agent_configuration_schema_is_valid_wrt_draft_04(): """Test that the JSON schema for the agent configuration file is compliant with the specification Draft 04.""" agent_config_schema = json.load( open( os.path.join( ROOT_DIR, "aea", "configurations", "schemas", "aea-config_schema.json" ) ) ) Draft4Validator.check_schema(agent_config_schema) def test_skill_configuration_schema_is_valid_wrt_draft_04(): """Test that the JSON schema for the skill configuration file is compliant with the specification Draft 04.""" skill_config_schema = json.load( open( os.path.join( ROOT_DIR, "aea", "configurations", "schemas", "skill-config_schema.json" ) ) ) Draft4Validator.check_schema(skill_config_schema) def test_connection_configuration_schema_is_valid_wrt_draft_04(): """Test that the JSON schema for the connection configuration file is compliant with the specification Draft 04.""" connection_config_schema = json.load( open( os.path.join( ROOT_DIR, "aea", "configurations", "schemas", "connection-config_schema.json", ) ) ) Draft4Validator.check_schema(connection_config_schema) def test_protocol_configuration_schema_is_valid_wrt_draft_04(): """Test that the JSON schema for the protocol configuration file is compliant with the specification Draft 04.""" protocol_config_schema = json.load( open( os.path.join( ROOT_DIR, "aea", "configurations", "schemas", "protocol-config_schema.json", ) ) ) Draft4Validator.check_schema(protocol_config_schema) def test_definitions_schema_is_valid_wrt_draft_04(): """Test that the JSON schema for the definitions is compliant with the specification Draft 04.""" definitions_config_schema = json.load( open( os.path.join( ROOT_DIR, "aea", "configurations", "schemas", "definitions.json" ) ) ) Draft4Validator.check_schema(definitions_config_schema) @pytest.mark.parametrize( "schema_file_path, config_file_path", itertools.chain.from_iterable( [ zip_longest([], files, fillvalue=schema) for files, schema in [ (agent_config_files, AGENT_CONFIGURATION_SCHEMA), (protocol_config_files, PROTOCOL_CONFIGURATION_SCHEMA), (contract_config_files, CONTRACT_CONFIGURATION_SCHEMA), (connection_config_files, CONNECTION_CONFIGURATION_SCHEMA), (skill_config_files, SKILL_CONFIGURATION_SCHEMA), (protocol_specification_files, PROTOCOL_SPEC_CONFIGURATION_SCHEMA), ] ] ), ) def test_config_validation(schema_file_path, config_file_path): """Test configuration validation.""" # TODO a bit inefficient to load each schema everytime; consider making the validators as fixtures. schema = json.load(open(schema_file_path)) resolver = jsonschema.RefResolver( make_jsonschema_base_uri(Path(CONFIGURATION_SCHEMA_DIR).absolute()), schema, ) validator = Draft4Validator(schema, resolver=resolver) config_data = list(yaml.safe_load_all(open(config_file_path))) validator.validate(instance=config_data[0]) ================================================ FILE: tests/test_aea/test_configurations/test_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests configuration utils.""" from unittest.mock import MagicMock from aea.configurations.base import ( AgentConfig, ComponentId, ComponentType, ConnectionConfig, PublicId, SkillConfig, ) from aea.configurations.utils import ( get_latest_component_id_from_prefix, replace_component_ids, ) def test_get_latest_component_id_from_prefix(): """Test the utility to get the latest concrete version id.""" agent_config = MagicMock() expected_component_id = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0") ) agent_config.package_dependencies = {expected_component_id} result = get_latest_component_id_from_prefix( agent_config, expected_component_id.component_prefix ) assert result == expected_component_id def test_get_latest_component_id_from_prefix_negative(): """Test the utility to get the latest concrete version id, negative case.""" agent_config = MagicMock() agent_config.package_dependencies = {} result = get_latest_component_id_from_prefix( agent_config, (ComponentType.PROTOCOL, "author", "name") ) assert result is None class BaseTestReplaceComponentIds: """Base test class for 'replace_component_ids' utility function.""" old_protocol_id = PublicId("author", "old_protocol", "0.1.0") old_contract_id = PublicId("author", "old_contract", "0.1.0") old_connection_id = PublicId("author", "old_connection", "0.1.0") old_skill_id = PublicId("author", "old_skill", "0.1.0") new_protocol_id = PublicId("author", "new_protocol", "0.1.0") new_contract_id = PublicId("author", "new_contract", "0.1.0") new_connection_id = PublicId("author", "new_connection", "0.1.0") new_skill_id = PublicId("author", "new_skill", "0.1.0") new_public_ids = { new_protocol_id, new_contract_id, new_connection_id, new_skill_id, } replacements = { ComponentType.PROTOCOL: {old_protocol_id: new_protocol_id}, ComponentType.CONTRACT: {old_contract_id: new_contract_id}, ComponentType.CONNECTION: {old_connection_id: new_connection_id}, ComponentType.SKILL: {old_skill_id: new_skill_id}, } class TestReplaceComponentIdsInAgentConfig(BaseTestReplaceComponentIds): """Test replace component ids in agent configuration.""" @classmethod def setup_class(cls): """Set up the test.""" cls.expected_custom_component_configuration = dict(foo="bar") cls.agent_config = AgentConfig( agent_name="agent_name", author="author", version="0.1.0", default_routing={str(cls.old_protocol_id): str(cls.old_connection_id)}, default_connection=str(cls.old_connection_id), ) cls.agent_config.protocols = {cls.old_protocol_id} cls.agent_config.contracts = {cls.old_contract_id} cls.agent_config.connections = {cls.old_connection_id} cls.agent_config.skills = {cls.old_skill_id} cls.agent_config.component_configurations[ ComponentId(ComponentType.PROTOCOL, cls.old_protocol_id) ] = cls.expected_custom_component_configuration cls.agent_config.component_configurations[ ComponentId(ComponentType.CONTRACT, cls.old_contract_id) ] = cls.expected_custom_component_configuration cls.agent_config.component_configurations[ ComponentId(ComponentType.CONNECTION, cls.old_connection_id) ] = cls.expected_custom_component_configuration cls.agent_config.component_configurations[ ComponentId(ComponentType.SKILL, cls.old_skill_id) ] = cls.expected_custom_component_configuration replace_component_ids(cls.agent_config, cls.replacements) def test_protocols_updated(self): """Test set of protocol ids updated.""" assert self.agent_config.protocols == {self.new_protocol_id} def test_contracts_updated(self): """Test set of contract ids updated.""" assert self.agent_config.contracts == {self.new_contract_id} def test_connections_updated(self): """Test set of connection ids updated.""" assert self.agent_config.connections == {self.new_connection_id} def test_skills_updated(self): """Test set of skill ids updated.""" assert self.agent_config.skills == {self.new_skill_id} def test_default_connection_updated(self): """Test default connection updated.""" assert self.agent_config.default_connection == self.new_connection_id def test_default_routing_updated(self): """Test default routing updated.""" assert self.agent_config.default_routing == { self.new_protocol_id: self.new_connection_id } def test_custom_configuration_updated(self): """Test default routing updated.""" component_protocol_id = ComponentId( ComponentType.PROTOCOL, self.new_protocol_id ) component_contract_id = ComponentId( ComponentType.CONTRACT, self.new_contract_id ) component_connection_id = ComponentId( ComponentType.CONNECTION, self.new_connection_id ) component_skill_id = ComponentId(ComponentType.SKILL, self.new_skill_id) assert ( self.agent_config.component_configurations[component_protocol_id] == self.expected_custom_component_configuration ) assert ( self.agent_config.component_configurations[component_contract_id] == self.expected_custom_component_configuration ) assert ( self.agent_config.component_configurations[component_connection_id] == self.expected_custom_component_configuration ) assert ( self.agent_config.component_configurations[component_skill_id] == self.expected_custom_component_configuration ) class TestReplaceComponentIdsInConnectionConfig(BaseTestReplaceComponentIds): """Test replace component ids utility with connection configuration.""" @classmethod def setup_class(cls): """Set up the test.""" cls.connection_config = ConnectionConfig( name="connection_name", author="author", version="0.1.0", connections={cls.old_connection_id}, protocols={cls.old_protocol_id}, restricted_to_protocols={cls.old_protocol_id}, excluded_protocols={cls.old_protocol_id}, ) replace_component_ids(cls.connection_config, cls.replacements) def test_protocols_updated(self): """Test set of protocol ids updated.""" assert self.connection_config.protocols == {self.new_protocol_id} def test_connections_updated(self): """Test set of connection ids updated.""" assert self.connection_config.connections == {self.new_connection_id} def test_restricted_to_protocols_updated(self): """Test restricted to protocols updated.""" assert self.connection_config.restricted_to_protocols == {self.new_protocol_id} def test_excluded_protocols_updated(self): """Test excluded protocols updated.""" assert self.connection_config.excluded_protocols == {self.new_protocol_id} class TestReplaceComponentIdsInSkillConfig(BaseTestReplaceComponentIds): """Test replace component ids in skill configuration.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.expected_custom_component_configuration = dict(foo="bar") cls.skill_config = SkillConfig( name="skill_name", author="author", version="0.1.0", ) cls.skill_config.protocols = {cls.old_protocol_id} cls.skill_config.contracts = {cls.old_contract_id} cls.skill_config.connections = {cls.old_connection_id} cls.skill_config.skills = {cls.old_skill_id} replace_component_ids(cls.skill_config, cls.replacements) def test_protocols_updated(self): """Test set of protocol ids updated.""" assert self.skill_config.protocols == {self.new_protocol_id} def test_contracts_updated(self): """Test set of contract ids updated.""" assert self.skill_config.contracts == {self.new_contract_id} def test_connections_updated(self): """Test set of connection ids updated.""" assert self.skill_config.connections == {self.new_connection_id} def test_skills_updated(self): """Test set of skill ids updated.""" assert self.skill_config.skills == {self.new_skill_id} ================================================ FILE: tests/test_aea/test_configurations/test_validation.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the aea.configurations.validation module.""" from aea.configurations.validation import ( SAME_MARK, filter_data, validate_data_with_pattern, ) def test_compare_data_pattern(): """Test validate_data_with_pattern.""" errors = validate_data_with_pattern({"a": 12}, {"a": 13}) assert not errors errors = validate_data_with_pattern({"a": 12}, {"a": "string"}) assert errors assert ( errors[0] == "For attribute `a` `str` data type is expected, but `int` was provided!" ) errors = validate_data_with_pattern({"a": None}, {"a": int}) assert not errors assert not validate_data_with_pattern( {"a": 12}, {"a": "${var}"}, skip_env_vars=True ) assert not validate_data_with_pattern( {"a": "${var}"}, {"a": "string"}, skip_env_vars=True ) errors = validate_data_with_pattern({"a": 12}, {"b": 12}) assert errors assert errors[0] == "Attribute `a` is not allowed to be updated!" errors = validate_data_with_pattern({"a": {}}, {"a": {"b": 12}}) assert errors assert errors[0] == "Attribute `a` is not allowed to be updated!" def test_filter_data(): """Test filter_data function.""" assert filter_data(1, 1) == SAME_MARK assert filter_data(1, 2) == 2 assert filter_data(2, 1) == 1 assert filter_data({}, {}) == SAME_MARK assert filter_data({1: 2}, {1: 2}) == SAME_MARK assert filter_data({1: 2}, {1: 3}) == {1: 3} assert filter_data([1, 2, 3], [1, 2, 3]) == SAME_MARK assert filter_data([1, 2, 3], [1, 2]) == [1, 2] assert filter_data({1: {2: 3}, 3: {2: 1}}, {1: {2: 3}, 3: {2: 3}, 0: 0}) == { 3: {2: 3}, 0: 0, } ================================================ FILE: tests/test_aea/test_connections/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the channels.""" ================================================ FILE: tests/test_aea/test_connections/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the base module.""" import asyncio import os import unittest from pathlib import Path from typing import cast from unittest.mock import MagicMock import pytest import aea from aea.configurations.base import ( ComponentId, ComponentType, ConnectionConfig, PublicId, ) from aea.configurations.loader import load_component_configuration from aea.connections.base import Connection, ConnectionStates from aea.exceptions import AEAComponentLoadException, AEAEnforceError from aea.mail.base import Envelope from tests.conftest import CUR_PATH class TConnection(Connection): """Test class for Connection.""" connection_id = PublicId.from_str("fetchai/some_connection:0.1.0") def connect(self, *args, **kwargs): """Connect.""" pass def disconnect(self, *args, **kwargs): """Disconnect.""" pass def from_config(self, *args, **kwargs): """From config.""" pass def receive(self, *args, **kwargs): """Receive.""" pass def send(self, *args, **kwargs): """Send.""" pass class TestConnectionTestCase: """Test case for Connection abstract class.""" TConnection = TConnection @pytest.mark.asyncio async def test_loop_only_in_running_loop(self): """Test loop property positive result.""" obj = self.TConnection( ConnectionConfig("some_connection", "fetchai", "0.1.0"), MagicMock() ) obj.loop def test_loop_fails_on_non_running_loop(self): """Test loop property positive result.""" obj = self.TConnection( ConnectionConfig("some_connection", "fetchai", "0.1.0"), MagicMock() ) with pytest.raises(AEAEnforceError): obj.loop def test_excluded_protocols_positive(self): """Test excluded_protocols property positive result.""" obj = self.TConnection( ConnectionConfig("some_connection", "fetchai", "0.1.0"), MagicMock() ) obj._excluded_protocols = "excluded_protocols" obj.excluded_protocols def test_loop_property(): """Test connection's loop property.""" connection = TConnection( MagicMock(public_id=TConnection.connection_id), MagicMock() ) with unittest.mock.patch.object(aea.connections.base, "enforce"): loop = connection.loop assert isinstance(loop, asyncio.AbstractEventLoop) def test_ensure_valid_envelope_for_external_comms_negative_cases(): """Test the staticmethod '_ensure_valid_envelope_for_external_comms', negative cases.""" protocol_specification_id = PublicId("author", "name", "0.1.0") wrong_sender = wrong_to = "author/name:0.1.0" envelope_wrong_sender = Envelope( to=wrong_to, sender=wrong_sender, protocol_specification_id=protocol_specification_id, message=b"", ) with pytest.raises( AEAEnforceError, match=f"Sender and to field of envelope is public id, needs to be address. Found: sender={wrong_sender}, to={wrong_to}", ): Connection._ensure_valid_envelope_for_external_comms(envelope_wrong_sender) def test_state(): """Test connect context of a connection.""" connection = TConnection( MagicMock(public_id=TConnection.connection_id), MagicMock() ) assert connection.state == ConnectionStates.disconnected with connection._connect_context(): assert connection.state == ConnectionStates.connecting assert connection.state == ConnectionStates.connected def test_ensure_connected(): """Test ensure_connected method.""" connection = TConnection( MagicMock(public_id=TConnection.connection_id), MagicMock() ) assert not connection.is_connected with pytest.raises( ConnectionError, match="Connection is not connected! Connect first!" ): connection._ensure_connected() connection._state.set(ConnectionStates.connected) connection._ensure_connected() def test_from_dir(): """Test Connection.from_dir""" dummy_connection_dir = os.path.join(CUR_PATH, "data", "dummy_connection") identity = MagicMock() identity.name = "agent_name" crypto_store = MagicMock() data_dir = MagicMock() connection = Connection.from_dir( dummy_connection_dir, identity, crypto_store, data_dir ) assert isinstance(connection, Connection) assert connection.component_id == ComponentId( ComponentType.CONNECTION, PublicId("fetchai", "dummy", "0.1.0") ) def test_from_config_exception_path(): """Test Connection.from_config with exception""" dummy_connection_dir = os.path.join(CUR_PATH, "data", "dummy_connection") configuration = cast( ConnectionConfig, load_component_configuration( ComponentType.CONNECTION, Path(dummy_connection_dir) ), ) wrong_dir = os.path.join(CUR_PATH, "data", "wrong_connection") configuration.directory = Path(wrong_dir) identity = MagicMock() identity.name = "agent_name" crypto_store = MagicMock() data_dir = MagicMock() with pytest.raises(AEAComponentLoadException, match="Connection module"): Connection.from_config(configuration, identity, crypto_store, data_dir) def test_from_config_exception_class(): """Test Connection.from_config with exception""" dummy_connection_dir = os.path.join(CUR_PATH, "data", "dummy_connection") configuration = cast( ConnectionConfig, load_component_configuration( ComponentType.CONNECTION, Path(dummy_connection_dir) ), ) configuration.directory = Path(dummy_connection_dir) configuration.class_name = "WrongName" identity = MagicMock() identity.name = "agent_name" crypto_store = MagicMock() data_dir = MagicMock() with pytest.raises(AEAComponentLoadException, match="Connection class"): Connection.from_config(configuration, identity, crypto_store, data_dir) def test_set_base_state(): """Check error raised on bad state set.""" con = TConnection( configuration=ConnectionConfig("some_connection", "fetchai", "0.1.0"), data_dir=MagicMock(), ) with pytest.raises(ValueError, match="Incorrect state.*"): con.state = "some bad state" ================================================ FILE: tests/test_aea/test_connections/test_scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the scaffold connection.""" import os import sys from unittest.mock import MagicMock import pytest from aea.configurations.base import ConnectionConfig from aea.connections.scaffold.connection import MyScaffoldAsyncConnection from aea.helpers.base import cd from aea.test_tools.test_cases import AEATestCaseEmpty from tests.common.pexpect_popen import PexpectWrapper from tests.conftest import ROOT_DIR class TestScaffoldConnectionReception: """Test that the stub connection is implemented correctly.""" def setup(self): """Set case up.""" configuration = ConnectionConfig( connection_id=MyScaffoldAsyncConnection.connection_id, ) self.connection = MyScaffoldAsyncConnection( configuration=configuration, data_dir=MagicMock() ) @pytest.mark.asyncio async def test_methods_not_implemented(self): """Test methods not implemented.""" with pytest.raises(NotImplementedError): await self.connection.connect() with pytest.raises(NotImplementedError): await self.connection.disconnect() with pytest.raises(NotImplementedError): await self.connection.send(None) with pytest.raises(NotImplementedError): await self.connection.receive() class TestScaffoldConnectionAndRun(AEATestCaseEmpty): """Test that the scaffold connection created.""" def setup(self): """Set case up.""" result = self.invoke("scaffold", "connection", "my_con") assert result.exit_code == 0 def test_run_and_not_implemented_error(self): """Test aea run crashes with connect not implemented for scaffolded connection.""" self.generate_private_key() self.add_private_key() with cd(self._get_cwd()): proc = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "-v", "DEBUG", "run"], env={ **os.environ, "PYTHONPATH": ROOT_DIR + ":" + os.environ.get("PYTHONPATH", ""), }, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) try: proc.expect_all( [ "Error while connecting : NotImplementedError()" ], timeout=50, ) finally: proc.terminate() proc.wait_to_complete(timeout=50) ================================================ FILE: tests/test_aea/test_connections/test_sync_connection.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the sync connection module.""" import asyncio import time from unittest.mock import MagicMock, Mock, patch import pytest from aea.configurations.data_types import PublicId from aea.connections.base import BaseSyncConnection from aea.mail.base import Envelope class SampleConnection(BaseSyncConnection): """Sample connection for testing.""" MAX_WORKER_THREADS = 3 connection_id = PublicId("test", "test", "0.1.0") PAUSE = 0.5 def __init__(self, *args, **kwargs): """Init connection.""" super().__init__(*args, **kwargs) self.main_called = False self.send_counter = 0 self.on_connect_called = False self.on_disconnect_called = False def main(self): """Run main.""" self.main_called = True envelope = Mock() envelope.message = "main" self.put_envelope(envelope) def on_send(self, envelope: Envelope) -> None: """Run on send.""" time.sleep(self.PAUSE) resp_envelope = Mock() resp_envelope.message = f"resp for {str(envelope.message)}" self.put_envelope(resp_envelope) self.send_counter += 1 def on_connect(self): """Run on connect.""" self.on_connect_called = True def on_disconnect(self): """Run on disconnect.""" self.on_disconnect_called = True @pytest.mark.asyncio async def test_sync_connection(): """Test sync connection example.""" conf = Mock() conf.public_id = SampleConnection.connection_id conf.config = {} con = SampleConnection(conf, MagicMock()) await asyncio.wait_for(con.connect(), timeout=10) assert con.is_connected envelope = Mock() for i in range(10): envelope.message = str(i) await asyncio.wait_for(con.send(envelope), timeout=10) await asyncio.sleep(con.PAUSE * 1.5) assert con.send_counter == con.MAX_WORKER_THREADS await asyncio.wait_for(con.disconnect(), timeout=10) assert con.is_disconnected assert con.on_connect_called assert con.on_disconnect_called assert con.main_called with patch.object(con, "_ensure_connected"): envelope = await con.receive() assert envelope.message == "main" ================================================ FILE: tests/test_aea/test_context/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.context.""" ================================================ FILE: tests/test_aea/test_context/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.context.""" import os from unittest.mock import Mock from aea_ledger_fetchai import FetchAICrypto from aea.context.base import AgentContext from aea.identity.base import Identity def test_agent_context(): """Test the agent context.""" agent_name = "name" address = "address" addresses = {FetchAICrypto.identifier: address} public_key = "public_key" public_keys = {FetchAICrypto.identifier: public_key} identity = Identity(agent_name, addresses=addresses, public_keys=public_keys) connection_status = "connection_status_stub" outbox = "outbox_stub" decision_maker_message_queue = "decision_maker_message_queue_stub" decision_maker_handler_context = "decision_maker_handler_context_stub" task_manager = "task_manager_stub" default_connection = "default_connection_stub" default_routing = "default_routing_stub" search_service_address = "search_service_address_stub" decision_maker_address = "decision_maker_address_stub" value = "some_value" kwargs = {"some_key": value} default_ledger_id = "fetchai" currency_denominations = {} data_dir = os.getcwd() send_to_skill = Mock() def storage_callable_(): pass ac = AgentContext( identity=identity, connection_status=connection_status, outbox=outbox, decision_maker_message_queue=decision_maker_message_queue, decision_maker_handler_context=decision_maker_handler_context, task_manager=task_manager, default_ledger_id=default_ledger_id, currency_denominations=currency_denominations, default_connection=default_connection, default_routing=default_routing, search_service_address=search_service_address, decision_maker_address=decision_maker_address, storage_callable=storage_callable_, data_dir=data_dir, send_to_skill=send_to_skill, **kwargs, ) assert ac.data_dir == data_dir assert ac.shared_state == {} assert ac.identity == identity assert ac.agent_name == identity.name assert ac.address == identity.address assert ac.addresses == identity.addresses assert ac.public_key == identity.public_key assert ac.public_keys == identity.public_keys assert ac.connection_status == connection_status assert ac.outbox == outbox assert ac.decision_maker_message_queue == decision_maker_message_queue assert ac.decision_maker_handler_context == decision_maker_handler_context assert ac.task_manager == task_manager assert ac.default_ledger_id == default_ledger_id assert ac.currency_denominations == currency_denominations assert ac.default_connection == default_connection assert ac.default_routing == default_routing assert ac.search_service_address == search_service_address assert ac.namespace.some_key == value assert ac.decision_maker_address == decision_maker_address assert ac.storage == storage_callable_() ac.send_to_skill("test") send_to_skill.assert_called() ================================================ FILE: tests/test_aea/test_contracts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea.contracts.""" ================================================ FILE: tests/test_aea/test_contracts/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea.contracts.base.""" import logging import os from pathlib import Path from typing import cast from unittest.mock import patch import pytest import web3 from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.configurations.base import ComponentType, ContractConfig from aea.configurations.loader import load_component_configuration from aea.contracts import contract_registry from aea.contracts.base import Contract from aea.contracts.scaffold.contract import MyScaffoldContract from aea.crypto.ledger_apis import ETHEREUM_DEFAULT_ADDRESS, FETCHAI_DEFAULT_ADDRESS from aea.crypto.registries import crypto_registry, ledger_apis_registry from aea.exceptions import AEAComponentLoadException from tests.conftest import ROOT_DIR, make_uri logger = logging.getLogger(__name__) def test_from_dir(): """Tests the from dir and from config methods.""" contract = Contract.from_dir( os.path.join(ROOT_DIR, "tests", "data", "dummy_contract") ) assert contract is not None assert contract.contract_interface is not None assert isinstance(contract.contract_interface, dict) def test_from_config_and_registration(): """Tests the from config method and contract registry registration.""" directory = Path(ROOT_DIR, "tests", "data", "dummy_contract") configuration = load_component_configuration(ComponentType.CONTRACT, directory) configuration._directory = directory configuration = cast(ContractConfig, configuration) if str(configuration.public_id) in contract_registry.specs: contract_registry.specs.pop(str(configuration.public_id)) contract = Contract.from_config(configuration) assert contract is not None assert contract.contract_interface is not None assert isinstance(contract.contract_interface, dict) assert contract.configuration == configuration assert contract.id == configuration.public_id # the contract is registered as side-effect assert str(contract.public_id) in contract_registry.specs try: contract_registry.specs.pop(str(configuration.public_id)) except Exception as e: logger.exception(e) def test_from_config_negative(): """Tests the from config method raises.""" directory = Path(ROOT_DIR, "tests", "data", "dummy_contract") configuration = load_component_configuration(ComponentType.CONTRACT, directory) configuration._directory = directory configuration = cast(ContractConfig, configuration) if str(configuration.public_id) in contract_registry.specs: contract_registry.specs.pop(str(configuration.public_id)) configuration.class_name = "WrongName" with pytest.raises(AEAComponentLoadException): _ = Contract.from_config(configuration) try: contract_registry.specs.pop(str(configuration.public_id)) except Exception as e: logger.exception(e) def test_non_implemented_class_methods(): """Tests the non implemented class methods.""" with pytest.raises(NotImplementedError): Contract.get_raw_transaction("ledger_api", "contract_address") with pytest.raises(NotImplementedError): Contract.get_raw_message("ledger_api", "contract_address") with pytest.raises(NotImplementedError): Contract.get_state("ledger_api", "contract_address") @pytest.fixture() def dummy_contract(request): """Dummy contract fixture.""" directory = Path(ROOT_DIR, "tests", "data", "dummy_contract") configuration = load_component_configuration(ComponentType.CONTRACT, directory) configuration._directory = directory configuration = cast(ContractConfig, configuration) if str(configuration.public_id) in contract_registry.specs: contract_registry.specs.pop(str(configuration.public_id)) # load into sys modules and register into contract registry contract = Contract.from_config(configuration) yield contract contract_registry.specs.pop(str(configuration.public_id)) def test_get_instance_no_address_ethereum(dummy_contract): """Tests get instance method with no address for ethereum.""" ledger_api = ledger_apis_registry.make( EthereumCrypto.identifier, address=ETHEREUM_DEFAULT_ADDRESS, ) instance = dummy_contract.get_instance(ledger_api) assert type(instance) == web3._utils.datatypes.PropertyCheckingFactory @pytest.mark.integration @pytest.mark.ledger def test_get_deploy_transaction_ethereum( dummy_contract, ganache_addr, ganache_port, ganache ): """Tests the deploy transaction classmethod for ethereum.""" aea_ledger_ethereum = crypto_registry.make(EthereumCrypto.identifier) ledger_api = ledger_apis_registry.make( EthereumCrypto.identifier, address=make_uri(ganache_addr, ganache_port) ) with patch( "web3.contract.ContractConstructor.buildTransaction", return_value={"data": "0xstub"}, ): deploy_tx = dummy_contract.get_deploy_transaction( ledger_api, aea_ledger_ethereum.address ) assert deploy_tx is not None and len(deploy_tx) == 6 assert all( key in ["from", "value", "gas", "gasPrice", "nonce", "data"] for key in deploy_tx.keys() ) def test_get_instance_no_address_cosmwasm(dummy_contract): """Tests get instance method with no address for fetchai.""" ledger_api = ledger_apis_registry.make( FetchAICrypto.identifier, address=FETCHAI_DEFAULT_ADDRESS, ) instance = dummy_contract.get_instance(ledger_api) assert instance is None def test_get_deploy_transaction_cosmwasm(dummy_contract): """Tests the deploy transaction classmethod for fetchai.""" aea_ledger_fetchai = crypto_registry.make(FetchAICrypto.identifier) ledger_api = ledger_apis_registry.make( FetchAICrypto.identifier, address=FETCHAI_DEFAULT_ADDRESS, ) deploy_tx = dummy_contract.get_deploy_transaction( ledger_api, aea_ledger_fetchai.address, account_number=1, sequence=0 ) assert deploy_tx is not None and len(deploy_tx) == 2 assert all(key in ["tx", "sign_data"] for key in deploy_tx.keys()) def test_scaffold(): """Test the scaffold contract can be loaded/instantiated.""" scaffold = MyScaffoldContract("config") kwargs = {"key": "value"} with pytest.raises(NotImplementedError): scaffold.get_raw_transaction("ledger_api", "contract_address", **kwargs) with pytest.raises(NotImplementedError): scaffold.get_raw_message("ledger_api", "contract_address", **kwargs) with pytest.raises(NotImplementedError): scaffold.get_state("ledger_api", "contract_address", **kwargs) ================================================ FILE: tests/test_aea/test_crypto/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.crypto.""" ================================================ FILE: tests/test_aea/test_crypto/test_helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the crypto/helpers module.""" import logging import os from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import mock_open, patch import pytest from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.crypto.helpers import ( create_private_key, get_wallet_from_agent_config, make_certificate, private_key_verify, try_generate_testnet_wealth, try_validate_private_key_path, ) from aea.crypto.wallet import Wallet from tests.conftest import ( COSMOS_PRIVATE_KEY_FILE, CUR_PATH, ETHEREUM_PRIVATE_KEY_FILE, ETHEREUM_PRIVATE_KEY_PATH, FETCHAI_PRIVATE_KEY_PATH, ) from tests.test_aea.test_cli.tools_for_testing import AgentConfigMock logger = logging.getLogger(__name__) class ResponseMock: """Mock for response class.""" text = "some text" def __init__(self, status_code=200): """Initialise response mock.""" self.status_code = status_code class TestHelperFile: """Test helper module in aea/crypto.""" def tests_private_keys(self): """Test the private keys.""" try_validate_private_key_path( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_PATH ) with pytest.raises(Exception): private_key_path = os.path.join( CUR_PATH, "data", "fet_private_key_wrong.txt" ) try_validate_private_key_path(FetchAICrypto.identifier, private_key_path) try_validate_private_key_path( EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_PATH ) with pytest.raises(Exception): private_key_path = os.path.join( CUR_PATH, "data", "fet_private_key_wrong.txt" ) try_validate_private_key_path(EthereumCrypto.identifier, private_key_path) def tests_generate_wealth_ethereum_fail_no_url(self, caplog): """Test generate wealth for ethereum.""" address = "my_address" with caplog.at_level( logging.DEBUG, logger="aea_ledger_ethereum._default_logger" ): try_generate_testnet_wealth( identifier=EthereumCrypto.identifier, address=address ) assert ( "Url is none, no default url provided. Please provide a faucet url." in caplog.text ) def tests_generate_wealth_ethereum_fail_invalid_url(self, caplog): """Test generate wealth for ethereum.""" address = "my_address" result = ResponseMock(status_code=500) with patch("aea_ledger_ethereum.requests.get", return_value=result): with caplog.at_level( logging.DEBUG, logger="aea_ledger_ethereum._default_logger" ): try_generate_testnet_wealth( identifier=EthereumCrypto.identifier, address=address, url="wrong_url", ) assert "Response: 500" in caplog.text def tests_generate_wealth_ethereum_fail_valid_url(self, caplog): """Test generate wealth for ethereum.""" address = "my_address" result = ResponseMock(status_code=200) with patch("aea_ledger_ethereum.requests.get", return_value=result): with caplog.at_level( logging.DEBUG, logger="aea_ledger_ethereum._default_logger" ): try_generate_testnet_wealth( identifier=EthereumCrypto.identifier, address=address, url="correct_url", ) @patch("aea_ledger_ethereum.requests.post", return_value=ResponseMock()) @patch("aea_ledger_ethereum.json.loads", return_value={"error_message": ""}) def test_try_generate_testnet_wealth_error_resp_ethereum(self, *mocks): """Test try_generate_testnet_wealth error_resp.""" try_generate_testnet_wealth(EthereumCrypto.identifier, "address") def test_try_validate_private_key_path_positive(self): """Test _validate_private_key_path positive result.""" try_validate_private_key_path( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_PATH ) try_validate_private_key_path( EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_PATH ) @patch("builtins.open", mock_open()) def test__create_ethereum_private_key_positive(self, *mocks): """Test _create_ethereum_private_key positive result.""" create_private_key(EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE) @patch("builtins.open", mock_open()) def test__create_cosmos_private_key_positive(self, *mocks): """Test _create_cosmos_private_key positive result.""" create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE) def test_private_key_verify(): """Test private_key_verify.""" agent_conf = AgentConfigMock(private_key_paths=[("fetchai", "test")]) with patch("aea.crypto.helpers.try_validate_private_key_path") as mock_validate: private_key_verify(agent_conf, Path(".")) mock_validate.assert_called() agent_conf = AgentConfigMock(private_key_paths=[("fetchai", "${var}")]) with patch("aea.crypto.helpers.try_validate_private_key_path") as mock_validate: with patch("aea.crypto.helpers.create_private_key") as mock_create: private_key_verify(agent_conf, Path(".")) mock_validate.assert_not_called() mock_create.assert_not_called() def test_make_certificate(): """Test make_certificate.""" with TemporaryDirectory() as tmp_dir: make_certificate( "fetchai", os.path.join(CUR_PATH, "data", "fetchai_private_key.txt"), b"message", os.path.join(tmp_dir, "test.txt"), ) def test_get_wallet_from_agent_config(): """Test get_wallet_from_agent_config.""" agent_conf = AgentConfigMock() wallet = get_wallet_from_agent_config(agent_conf) assert isinstance(wallet, Wallet) ================================================ FILE: tests/test_aea/test_crypto/test_ledger_apis.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the crypto/helpers module.""" import logging from unittest import mock import pytest from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import AEAEnforceError from tests.conftest import COSMOS_ADDRESS_ONE, ETHEREUM_ADDRESS_ONE, FETCHAI_ADDRESS_ONE logger = logging.getLogger(__name__) def _raise_exception(*args, **kwargs): raise Exception("Message") def test_initialisation(): """Test the initialisation of the ledger APIs.""" ledger_apis = LedgerApis assert ledger_apis.has_ledger(FetchAICrypto.identifier) assert type(LedgerApis.get_api(FetchAICrypto.identifier)).__name__ == "FetchAIApi" assert LedgerApis.has_ledger(EthereumCrypto.identifier) assert type(LedgerApis.get_api(EthereumCrypto.identifier)).__name__ == "EthereumApi" assert LedgerApis.has_ledger(CosmosCrypto.identifier) assert type(LedgerApis.get_api(CosmosCrypto.identifier)).__name__ == "CosmosApi" with pytest.raises(AEAEnforceError): ledger_apis.get_api("UNKNOWN") class TestLedgerApis: """Test the ledger_apis module.""" @classmethod def setup_class(cls): """Setup the test case.""" cls.ledger_apis = LedgerApis def test_get_balance(self): """Test the get_balance.""" with mock.patch("aea_ledger_ethereum.EthereumApi.get_balance", return_value=10): balance = self.ledger_apis.get_balance( EthereumCrypto.identifier, ETHEREUM_ADDRESS_ONE ) assert balance == 10 def test_get_transfer_transaction(self): """Test the get_transfer_transaction.""" with mock.patch( "aea_ledger_cosmos.CosmosApi.get_transfer_transaction", return_value="mock_transaction", ): tx = self.ledger_apis.get_transfer_transaction( identifier=CosmosCrypto.identifier, sender_address="sender_address", destination_address=COSMOS_ADDRESS_ONE, amount=10, tx_fee=10, tx_nonce="transaction nonce", ) assert tx == "mock_transaction" def test_send_signed_transaction(self): """Test the send_signed_transaction.""" with mock.patch( "aea_ledger_cosmos.CosmosApi.send_signed_transaction", return_value="mock_transaction_digest", ): tx_digest = self.ledger_apis.send_signed_transaction( identifier=CosmosCrypto.identifier, tx_signed="signed_transaction", ) assert tx_digest == "mock_transaction_digest" def test_get_transaction_receipt(self): """Test the get_transaction_receipt.""" with mock.patch( "aea_ledger_cosmos.CosmosApi.get_transaction_receipt", return_value="mock_transaction_receipt", ): tx_receipt = self.ledger_apis.get_transaction_receipt( identifier=CosmosCrypto.identifier, tx_digest="tx_digest", ) assert tx_receipt == "mock_transaction_receipt" def test_get_transaction(self): """Test the get_transaction.""" with mock.patch( "aea_ledger_cosmos.CosmosApi.get_transaction", return_value="mock_transaction", ): tx = self.ledger_apis.get_transaction( identifier=CosmosCrypto.identifier, tx_digest="tx_digest", ) assert tx == "mock_transaction" def test_is_transaction_settled(self): """Test the is_transaction_settled.""" with mock.patch( "aea_ledger_cosmos.CosmosApi.is_transaction_settled", return_value=True, ): is_settled = self.ledger_apis.is_transaction_settled( identifier=CosmosCrypto.identifier, tx_receipt="tx_receipt", ) assert is_settled def test_is_transaction_valid(self): """Test the is_transaction_valid.""" with mock.patch( "aea_ledger_cosmos.CosmosApi.is_transaction_valid", return_value=True, ): is_valid = self.ledger_apis.is_transaction_valid( identifier=CosmosCrypto.identifier, tx="tx", seller="seller", client="client", tx_nonce="tx_nonce", amount=10, ) assert is_valid def test_recover_message(self): """Test the is_transaction_valid.""" expected_addresses = ("address_1", "address_2") with mock.patch( "aea_ledger_cosmos.CosmosApi.recover_message", return_value=expected_addresses, ): addresses = self.ledger_apis.recover_message( identifier=CosmosCrypto.identifier, message="message", signature="signature", ) assert addresses == expected_addresses def test_get_hash(self): """Test the get_hash.""" expected_hash = "hash" with mock.patch( "aea_ledger_cosmos.CosmosApi.get_hash", return_value=expected_hash, ): hash_ = self.ledger_apis.get_hash( identifier=CosmosCrypto.identifier, message=b"message", ) assert hash_ == expected_hash def test_get_contract_address(self): """Test the get_contract_address.""" expected_address = "address" with mock.patch( "aea_ledger_cosmos.CosmosApi.get_contract_address", return_value=expected_address, ): address_ = self.ledger_apis.get_contract_address( identifier=CosmosCrypto.identifier, tx_receipt={}, ) assert address_ == expected_address def test_generate_tx_nonce_positive(self): """Test generate_tx_nonce positive result.""" result = LedgerApis.generate_tx_nonce( CosmosCrypto.identifier, "seller", "client" ) assert int(result, 16) def test_is_valid_address(): """Test LedgerApis.is_valid_address.""" assert LedgerApis.is_valid_address(DEFAULT_LEDGER, FETCHAI_ADDRESS_ONE) assert LedgerApis.is_valid_address(EthereumCrypto.identifier, ETHEREUM_ADDRESS_ONE) assert LedgerApis.is_valid_address(CosmosCrypto.identifier, COSMOS_ADDRESS_ONE) ================================================ FILE: tests/test_aea/test_crypto/test_password_end2end.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the private key password support.""" from pathlib import Path import pytest from click.exceptions import ClickException from aea.helpers.base import cd from aea.test_tools.test_cases import AEATestCaseEmpty class TestKeyEncryption(AEATestCaseEmpty): """Test that the command 'aea generate-key' works as expected.""" @pytest.mark.parametrize("ledger_name", ["fetchai", "ethereum", "cosmos"]) def test_crypto_plugin(self, ledger_name): """Test that the fetch private key is created correctly.""" with cd(self._get_cwd()): plain_file_name = Path(f"{ledger_name}_key") encrypted_file_name = Path(f"{ledger_name}_key_encrypted") password = "somePwd" # nosec self.invoke("generate-key", ledger_name, str(plain_file_name)) assert plain_file_name.exists() self.invoke( "generate-key", ledger_name, str(encrypted_file_name), "--password", password, ) assert encrypted_file_name.exists() assert len(encrypted_file_name.read_bytes()) != len( plain_file_name.read_bytes() ) with pytest.raises(ClickException, match="Error on key.*load.*password"): self.invoke("add-key", ledger_name, str(encrypted_file_name)) with pytest.raises(ClickException, match="Decrypt error! Bad password?"): self.invoke( "add-key", ledger_name, str(encrypted_file_name), "--password", "incorrectpassword", ) r = self.invoke( "add-key", ledger_name, str(encrypted_file_name), "--password", password, ) assert r.exit_code == 0 r = self.invoke( "get-address", ledger_name, "--password", password, ) assert r.exit_code == 0 ================================================ FILE: tests/test_aea/test_crypto/test_registries.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea.crypto.registries""" from typing import Optional from aea_ledger_cosmos import CosmosApi, CosmosCrypto from aea.crypto.base import Crypto from aea.crypto.registries import make_crypto, make_ledger_api from aea.crypto.registries.base import ItemId, Registry COSMOS = CosmosCrypto.identifier def test_make_crypto_cosmos_positive(): """Test make_crypto for fetchai.""" crypto = make_crypto(COSMOS) assert isinstance(crypto, CosmosCrypto) def test_make_ledger_api_cosmos_positive(): """Test make_crypto for fetchai.""" ledger_api = make_ledger_api(COSMOS, **{"network": "testnet"}) assert isinstance(ledger_api, CosmosApi) class Something: """Some class.""" class_key = None # type: Optional[str] def __init__(self, **kwargs): """Initialize something.""" self.kwargs = kwargs def test_register_make_with_class_kwargs(): """Test registry make with class kwargs.""" reg = Registry() id_ = "id" kwargs = {"key": "value"} class_kwargs = {"class_key": "class_value"} reg.register( id_=id_, entry_point="tests.test_aea.test_crypto.test_registries:Something", class_kwargs=class_kwargs, **kwargs, ) assert Something.class_key is None item = reg.make(id_) assert item is not None assert type(item) == Something assert item.kwargs == kwargs assert item.class_key == "class_value" def test_itemid(): """Test the idemid object.""" item_id = ItemId(COSMOS) assert item_id.name == COSMOS def test_registry(): """Test the registry object.""" registry = Registry[Crypto]() item_id = ItemId(COSMOS) assert not registry.has_spec(item_id), "Registry should be empty" ================================================ FILE: tests/test_aea/test_crypto/test_registry/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the crypto and ledger registries.""" ================================================ FILE: tests/test_aea/test_crypto/test_registry/test_crypto_registry.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the crypto registry.""" import logging import string from unittest import mock import pytest from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto import aea.crypto from aea.crypto.registries.base import EntryPoint from aea.exceptions import AEAException from tests.data.custom_crypto import CustomCrypto logger = logging.getLogger(__name__) forbidden_special_characters = "".join( filter(lambda c: c not in "_:/.", string.punctuation) ) def test_make_fetchai(): """Test the 'make' method for 'fetchai' crypto.""" aea_ledger_fetchai = aea.crypto.registries.make_crypto(FetchAICrypto.identifier) # calling 'make' again will give a different object. aea_ledger_fetchai_1 = aea.crypto.registries.make_crypto(FetchAICrypto.identifier) assert type(aea_ledger_fetchai) == type(aea_ledger_fetchai_1) assert aea_ledger_fetchai.address != aea_ledger_fetchai_1 def test_make_ethereum(): """Test the 'make' method for 'ethereum' crypto.""" aea_ledger_ethereum = aea.crypto.registries.make_crypto(EthereumCrypto.identifier) # calling 'make' again will give a different object. aea_ledger_ethereum_1 = aea.crypto.registries.make_crypto(EthereumCrypto.identifier) assert type(aea_ledger_ethereum) == type(aea_ledger_ethereum_1) assert aea_ledger_ethereum.address != aea_ledger_ethereum_1.address def test_make_cosmos(): """Test the 'make' method for 'cosmos' crypto.""" aea_ledger_cosmos = aea.crypto.registries.make_crypto(CosmosCrypto.identifier) # calling 'make' again will give a different object. aea_ledger_cosmos_1 = aea.crypto.registries.make_crypto(CosmosCrypto.identifier) assert type(aea_ledger_cosmos) == type(aea_ledger_cosmos_1) assert aea_ledger_cosmos.address != aea_ledger_cosmos_1.address def test_register_custom_crypto(): """Test the 'register' method with a custom crypto object.""" aea.crypto.registries.register_crypto( "my_custom_crypto", entry_point="tests.data.custom_crypto:CustomCrypto" ) assert ( aea.crypto.registries.crypto_registry.specs.get("my_custom_crypto") is not None ) actual_spec = aea.crypto.registries.crypto_registry.specs["my_custom_crypto"] expected_id = "my_custom_crypto" expected_entry_point = EntryPoint("tests.data.custom_crypto:CustomCrypto") assert actual_spec.id == expected_id assert actual_spec.entry_point == expected_entry_point assert actual_spec.entry_point.import_path == expected_entry_point.import_path assert actual_spec.entry_point.class_name == expected_entry_point.class_name my_crypto = aea.crypto.registries.make_crypto("my_custom_crypto") assert type(my_crypto) == CustomCrypto # calling 'make' again will give a different object. my_crypto_1 = aea.crypto.registries.make_crypto("my_custom_crypto") assert type(my_crypto) == type(my_crypto_1) assert my_crypto != my_crypto_1 aea.crypto.registries.crypto_registry.specs.pop("my_custom_crypto") def test_cannot_register_crypto_twice(): """Test we cannot register a crypto twice.""" aea.crypto.registries.register_crypto( "my_custom_crypto", entry_point="tests.data.custom_crypto:CustomCrypto" ) with pytest.raises(AEAException, match="Cannot re-register id: 'my_custom_crypto'"): aea.crypto.registries.register_crypto( "my_custom_crypto", entry_point="tests.data.custom_crypto:CustomCrypto" ) aea.crypto.registries.crypto_registry.specs.pop("my_custom_crypto") @mock.patch("importlib.import_module", side_effect=ImportError) def test_import_error(*mocks): """Test import errors.""" aea.crypto.registries.register_crypto( "some_crypto", entry_point="path.to.module:SomeCrypto" ) with pytest.raises( AEAException, match="A module (.*) was specified for the item but was not found", ): aea.crypto.registries.make_crypto("some_crypto", module="some.module") aea.crypto.registries.crypto_registry.specs.pop("some_crypto") class TestRegisterWithMalformedId: """Test the error message when we try to register a crypto whose identifier is malformed.""" MESSAGE_REGEX = "Malformed .*: '.*'. It must be of the form '.*'." def test_wrong_spaces(self): """Spaces not allowed in a Crypto ID.""" # beginning space with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( " malformed_id", "path.to.module:CryptoClass" ) # trailing space with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( "malformed_id ", "path.to.module:CryptoClass" ) # in between with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( "malformed id", "path.to.module:CryptoClass" ) @pytest.mark.parametrize("special_character", forbidden_special_characters) def test_special_characters(self, special_character): """Special characters are not allowed (only underscore).""" with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( "malformed_id" + special_character, "path.to.module:CryptoClass" ) class TestRegisterWithMalformedEntryPoint: """Test the error message when we try to register a crypto with a wrong entry point.""" MESSAGE_REGEX = "Malformed .*: '.*'. It must be of the form '.*'." def test_wrong_spaces(self): """Spaces not allowed in a Crypto ID.""" # beginning space with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( "crypto_id", " path.to.module:CryptoClass" ) # trailing space with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( "crypto_id", "path.to.module :CryptoClass" ) # in between with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( "crypto_id", "path.to .module:CryptoClass" ) @pytest.mark.parametrize("special_character", forbidden_special_characters) def test_special_characters(self, special_character): """Special characters are not allowed (only underscore).""" with pytest.raises(AEAException, match=self.MESSAGE_REGEX): aea.crypto.registries.register_crypto( "crypto_id", "path" + special_character + ".to.module:CryptoClass" ) ================================================ FILE: tests/test_aea/test_crypto/test_registry/test_ledger_api_registry.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the ledger api registry.""" import logging import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto import aea.crypto from tests.conftest import ( ETHEREUM_ADDRESS_ONE, ETHEREUM_TESTNET_CONFIG, FETCHAI_ADDRESS_ONE, FETCHAI_TESTNET_CONFIG, ) logger = logging.getLogger(__name__) @pytest.mark.parametrize( "identifier,address,config", [ (FetchAICrypto.identifier, FETCHAI_ADDRESS_ONE, FETCHAI_TESTNET_CONFIG), (EthereumCrypto.identifier, ETHEREUM_ADDRESS_ONE, ETHEREUM_TESTNET_CONFIG), ], ) def test_make_ledger_apis(identifier, address, config): """Test the 'make' method for ledger api.""" api = aea.crypto.registries.make_ledger_api(identifier, **config) # minimal functional test - comprehensive tests on ledger APIs are located in another module balance_1 = api.get_balance(address) balance_2 = api.get_balance(address) assert balance_1 == balance_2 ================================================ FILE: tests/test_aea/test_crypto/test_registry/test_misc.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains misc tests for the registry (crypto/ledger_api/contract).""" import logging import pytest from aea.crypto.registries.base import Registry from aea.exceptions import AEAException logger = logging.getLogger(__name__) @pytest.mark.parametrize( "current_id,is_valid", [ ("a", True), ("_", True), ("0", False), ("_0", True), ("-", False), ("ABCDE", True), ("author/package:0.1.0", True), ("author/package:0.1.", False), ("0author/package:0.1.0", False), ], ) def test_validation_item_id(current_id, is_valid): """Test validation of item id id.""" registry = Registry() entrypoint = "some_entrypoint:SomeEntrypoint" if is_valid: registry.register(current_id, entry_point=entrypoint) else: with pytest.raises( AEAException, match=rf"Malformed ItemId: '{current_id}'\. It must be of the form .*\.", ): registry.register(current_id, entry_point=entrypoint) ================================================ FILE: tests/test_aea/test_crypto/test_wallet.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the wallet module.""" from unittest import TestCase import pytest from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.crypto.wallet import Wallet from aea.exceptions import AEAException from tests.conftest import ( COSMOS_PRIVATE_KEY_PATH, ETHEREUM_PRIVATE_KEY_PATH, FETCHAI_PRIVATE_KEY_PATH, ) def test_wallet_initialisation_error(): """Test the value error when we initialise the wallet.""" with pytest.raises(AEAException): Wallet({"Test": "test"}) class WalletTestCase(TestCase): """Test case for Wallet class.""" def test_wallet_init_positive(self): """Test Wallet init positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_PATH, } Wallet(private_key_paths) def test_wallet_init_bad_id(self): """Test Wallet init unsupported private key paths identifier.""" private_key_paths = {"unknown_id": "path1"} with self.assertRaises(AEAException): Wallet(private_key_paths) def test_wallet_init_bad_paths(self): """Test Wallet init with bad paths to private keys""" private_key_paths = {FetchAICrypto.identifier: "this_path_does_not_exists"} with self.assertRaises(FileNotFoundError): Wallet(private_key_paths) def test_wallet_crypto_objects_positive(self): """Test Wallet.crypto_objects init positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) crypto_objects = wallet.crypto_objects self.assertTupleEqual( tuple(crypto_objects), (EthereumCrypto.identifier, FetchAICrypto.identifier) ) def test_wallet_public_keys_positive(self): """Test Wallet.public_keys init positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) public_keys = wallet.public_keys self.assertTupleEqual( tuple(public_keys), (EthereumCrypto.identifier, FetchAICrypto.identifier) ) def test_wallet_addresses_positive(self): """Test Wallet.addresses init positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) addresses = wallet.addresses self.assertTupleEqual( tuple(addresses), (EthereumCrypto.identifier, FetchAICrypto.identifier) ) def test_wallet_private_keys_positive(self): """Test Wallet.private_keys init positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) private_keys = wallet.private_keys self.assertTupleEqual( tuple(private_keys), (EthereumCrypto.identifier, FetchAICrypto.identifier) ) def test_wallet_cryptos_positive(self): """Test Wallet.main_cryptos and connection cryptos init positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } connection_private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths, connection_private_key_paths) assert len(wallet.main_cryptos.crypto_objects) == len( wallet.connection_cryptos.crypto_objects ), "Incorrect amount of cryptos" def test_wallet_sign_message_positive(self): """Test Wallet.sign_message positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) signature = wallet.sign_message( EthereumCrypto.identifier, message=b"some message" ) assert type(signature) == str and int( signature, 16 ), "No signature present or not hexadecimal" def test_wallet_sign_message_negative(self): """Test Wallet.sign_message negative result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) signature = wallet.sign_message("unknown id", message=b"some message") assert signature is None, "Signature should be none" def test_wallet_sign_transaction_positive(self): """Test Wallet.sign_transaction positive result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) signed_transaction = wallet.sign_transaction( EthereumCrypto.identifier, transaction={"gasPrice": 50, "nonce": 10, "gas": 10}, ) assert type(signed_transaction) == dict, "No signed transaction returned" def test_wallet_sign_transaction_negative(self): """Test Wallet.sign_transaction negative result.""" private_key_paths = { EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } wallet = Wallet(private_key_paths) signed_transaction = wallet.sign_transaction( "unknown id", transaction={"this is my tx": "here"} ) assert signed_transaction is None, "Signed transaction should be none" ================================================ FILE: tests/test_aea/test_decision_maker/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for decision_maker.""" ================================================ FILE: tests/test_aea/test_decision_maker/test_default.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for decision_maker.""" import pytest from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.configurations.base import PublicId from aea.crypto.registries import make_crypto, make_ledger_api from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMaker from aea.decision_maker.default import DecisionMakerHandler from aea.helpers.transaction.base import ( RawMessage, RawTransaction, SignedMessage, Terms, ) from aea.identity.base import Identity from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.signing.dialogues import SigningDialogue from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage from tests.conftest import ( COSMOS_PRIVATE_KEY_PATH, ETHEREUM_PRIVATE_KEY_PATH, FETCHAI_PRIVATE_KEY_PATH, FETCHAI_TESTNET_CONFIG, MAX_FLAKY_RERUNS, get_wealth_if_needed, ) class SigningDialogues(BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return SigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) class BaseTestDecisionMaker: """Test the decision maker.""" decision_maker_handler_cls = DecisionMakerHandler decision_maker_cls = DecisionMaker @classmethod def setup(cls): """Initialise the decision maker.""" cls.wallet = Wallet( { CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_PATH, EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } ) cls.agent_name = "test" cls.identity = Identity( cls.agent_name, addresses=cls.wallet.addresses, public_keys=cls.wallet.public_keys, default_address_key=FetchAICrypto.identifier, ) cls.config = {} cls.decision_maker_handler = cls.decision_maker_handler_cls( identity=cls.identity, wallet=cls.wallet, config=cls.config ) cls.decision_maker = cls.decision_maker_cls(cls.decision_maker_handler) cls.tx_sender_addr = "agent_1" cls.tx_counterparty_addr = "pk" cls.info = {"some_info_key": "some_info_value"} cls.ledger_id = FetchAICrypto.identifier cls.decision_maker.start() def test_decision_maker_config(self): """Test config property.""" assert self.decision_maker_handler.config == self.config def test_decision_maker_execute_w_wrong_input(self): """Test the execute method with wrong input.""" with pytest.raises(ValueError): self.decision_maker.message_in_queue.put_nowait("wrong input") with pytest.raises(ValueError): self.decision_maker.message_in_queue.put("wrong input") def test_decision_maker_queue_access_not_permitted(self): """Test the in queue of the decision maker can not be accessed.""" with pytest.raises(ValueError): self.decision_maker.message_in_queue.get() with pytest.raises(ValueError): self.decision_maker.message_in_queue.get_nowait() with pytest.raises(ValueError): self.decision_maker.message_in_queue.protected_get( access_code="some_invalid_code" ) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_handle_tx_signing_fetchai(self): """Test tx signing for fetchai.""" fetchai_api = make_ledger_api( FetchAICrypto.identifier, **FETCHAI_TESTNET_CONFIG ) sender_address = self.wallet.addresses["fetchai"] fc2 = make_crypto(FetchAICrypto.identifier) get_wealth_if_needed(sender_address, fetchai_api) amount = 10000 transfer_transaction = fetchai_api.get_transfer_transaction( sender_address=sender_address, destination_address=fc2.address, amount=amount, tx_fee=1000, tx_nonce="something", ) signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=FetchAICrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_transaction=RawTransaction( FetchAICrypto.identifier, transfer_transaction ), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) recovered_dialogue = signing_dialogues.update(signing_msg_response) assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_TRANSACTION ) assert type(signing_msg_response.signed_transaction.body) == dict def test_handle_tx_signing_ethereum(self): """Test tx signing for ethereum.""" tx = {"gasPrice": 30, "nonce": 1, "gas": 20000} signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_transaction=RawTransaction(EthereumCrypto.identifier, tx), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) recovered_dialogue = signing_dialogues.update(signing_msg_response) assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_TRANSACTION ) assert type(signing_msg_response.signed_transaction.body) == dict def test_handle_tx_signing_unknown(self): """Test tx signing for unknown.""" tx = {} signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id="unknown", sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_transaction=RawTransaction("unknown", tx), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) recovered_dialogue = signing_dialogues.update(signing_msg_response) assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert signing_msg_response.performative == SigningMessage.Performative.ERROR assert ( signing_msg_response.error_code == SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING ) def test_handle_message_signing_fetchai(self): """Test message signing for fetchai.""" message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=FetchAICrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_message=RawMessage(FetchAICrypto.identifier, message), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) recovered_dialogue = signing_dialogues.update(signing_msg_response) assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_MESSAGE ) assert type(signing_msg_response.signed_message) == SignedMessage def test_handle_message_signing_ethereum(self): """Test message signing for ethereum.""" message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_message=RawMessage(EthereumCrypto.identifier, message), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) recovered_dialogue = signing_dialogues.update(signing_msg_response) assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_MESSAGE ) assert type(signing_msg_response.signed_message) == SignedMessage def test_handle_message_signing_ethereum_deprecated(self): """Test message signing for ethereum deprecated.""" message = b"0x11f3f9487724404e3a1fb7252a3226" signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_message=RawMessage( EthereumCrypto.identifier, message, is_deprecated_mode=True ), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) recovered_dialogue = signing_dialogues.update(signing_msg_response) assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_MESSAGE ) assert type(signing_msg_response.signed_message) == SignedMessage assert signing_msg_response.signed_message.is_deprecated_mode def test_handle_message_signing_unknown_and_two_dialogues(self): """Test message signing for unknown.""" message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id="unknown", sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_message=RawMessage("unknown", message), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) recovered_dialogue = signing_dialogues.update(signing_msg_response) assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert signing_msg_response.performative == SigningMessage.Performative.ERROR assert ( signing_msg_response.error_code == SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING ) def test_handle_messages_from_two_dialogues_same_agent(self): """Test message signing for unknown.""" message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) dialogue_reference = signing_dialogues.new_self_initiated_dialogue_reference() signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=dialogue_reference, terms=Terms( ledger_id="unknown", sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_message=RawMessage("unknown", message), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) assert signing_msg_response is not None signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=dialogue_reference, terms=Terms( ledger_id="unknown", sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_message=RawMessage("unknown", message), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None with pytest.raises(Exception): # Exception occurs because the same counterparty sends two identical dialogue references self.decision_maker.message_out_queue.get(timeout=1) # test twice; should work again even from same agent signing_dialogues = SigningDialogues( str(PublicId("author", "a_skill", "0.1.0")) ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id="unknown", sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_message=RawMessage("unknown", message), ) signing_dialogue = signing_dialogues.create_with_message( "decision_maker", signing_msg ) assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) assert signing_msg_response is not None @classmethod def teardown(cls): """Tear the tests down.""" cls.decision_maker.stop() class TestDecisionMaker(BaseTestDecisionMaker): """Run test for default decision maker.""" ================================================ FILE: tests/test_aea/test_decision_maker/test_gop.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for decision_maker.""" from queue import Queue from typing import Optional, cast from unittest import mock from aea_ledger_cosmos import CosmosCrypto from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto import aea import aea.decision_maker.gop from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMaker from aea.decision_maker.gop import DecisionMakerHandler from aea.identity.base import Identity from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.signing.dialogues import SigningDialogue from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.state_update.dialogues import StateUpdateDialogue from packages.fetchai.protocols.state_update.dialogues import ( StateUpdateDialogues as BaseStateUpdateDialogues, ) from packages.fetchai.protocols.state_update.message import StateUpdateMessage from tests.conftest import ( COSMOS_PRIVATE_KEY_PATH, ETHEREUM_PRIVATE_KEY_PATH, FETCHAI_PRIVATE_KEY_PATH, ) from tests.test_aea.test_decision_maker.test_default import ( BaseTestDecisionMaker as BaseTestDecisionMakerDefault, ) class SigningDialogues(BaseSigningDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return SigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) class StateUpdateDialogues(BaseStateUpdateDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return StateUpdateDialogue.Role.DECISION_MAKER BaseStateUpdateDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class TestDecisionMaker: """Test the decision maker.""" @classmethod def _patch_logger(cls): cls.patch_logger_warning = mock.patch.object( aea.decision_maker.gop._default_logger, "warning" ) cls.mocked_logger_warning = cls.patch_logger_warning.__enter__() @classmethod def _unpatch_logger(cls): cls.mocked_logger_warning.__exit__() @classmethod def setup(cls): """Initialise the decision maker.""" cls._patch_logger() cls.wallet = Wallet( { CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_PATH, EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, } ) cls.agent_name = "test" cls.identity = Identity( cls.agent_name, addresses=cls.wallet.addresses, public_keys=cls.wallet.public_keys, default_address_key=FetchAICrypto.identifier, ) cls.decision_maker_handler = DecisionMakerHandler( identity=cls.identity, wallet=cls.wallet, config={} ) cls.decision_maker = DecisionMaker(cls.decision_maker_handler) cls.tx_sender_addr = "agent_1" cls.tx_counterparty_addr = "pk" cls.info = {"some_info_key": "some_info_value"} cls.ledger_id = FetchAICrypto.identifier cls.decision_maker.start() def test_properties(self): """Test the properties of the decision maker.""" assert isinstance(self.decision_maker.message_in_queue, Queue) assert isinstance(self.decision_maker.message_out_queue, Queue) def test_decision_maker_handle_state_update_initialize_and_apply(self): """Test the handle method for a stateUpdate message with Initialize and Apply performative.""" good_holdings = {"good_id": 2} currency_holdings = {"FET": 100} utility_params = {"good_id": 20.0} exchange_params = {"FET": 10.0} currency_deltas = {"FET": -10} good_deltas = {"good_id": 1} state_update_dialogues = StateUpdateDialogues("agent") state_update_message_1 = StateUpdateMessage( performative=StateUpdateMessage.Performative.INITIALIZE, dialogue_reference=state_update_dialogues.new_self_initiated_dialogue_reference(), amount_by_currency_id=currency_holdings, quantities_by_good_id=good_holdings, exchange_params_by_currency_id=exchange_params, utility_params_by_good_id=utility_params, ) state_update_dialogue = cast( Optional[StateUpdateDialogue], state_update_dialogues.create_with_message( "decision_maker", state_update_message_1 ), ) assert state_update_dialogue is not None, "StateUpdateDialogue not created" self.decision_maker.handle(state_update_message_1) assert ( self.decision_maker_handler.context.ownership_state.amount_by_currency_id is not None ) assert ( self.decision_maker_handler.context.ownership_state.quantities_by_good_id is not None ) assert ( self.decision_maker_handler.context.preferences.exchange_params_by_currency_id is not None ) assert ( self.decision_maker_handler.context.preferences.utility_params_by_good_id is not None ) state_update_message_2 = state_update_dialogue.reply( performative=StateUpdateMessage.Performative.APPLY, amount_by_currency_id=currency_deltas, quantities_by_good_id=good_deltas, ) self.decision_maker.handle(state_update_message_2) expected_amount_by_currency_id = { key: currency_holdings.get(key, 0) + currency_deltas.get(key, 0) for key in set(currency_holdings) | set(currency_deltas) } expected_quantities_by_good_id = { key: good_holdings.get(key, 0) + good_deltas.get(key, 0) for key in set(good_holdings) | set(good_deltas) } assert ( self.decision_maker_handler.context.ownership_state.amount_by_currency_id == expected_amount_by_currency_id ), "The amount_by_currency_id must be equal with the expected amount." assert ( self.decision_maker_handler.context.ownership_state.quantities_by_good_id == expected_quantities_by_good_id ) @classmethod def teardown(cls): """Tear the tests down.""" cls._unpatch_logger() cls.decision_maker.stop() class TestDecisionMaker2(BaseTestDecisionMakerDefault): """Test the decision maker.""" decision_maker_handler_cls = DecisionMakerHandler # type: ignore decision_maker_cls = DecisionMaker # type: ignore @classmethod def _patch_logger(cls): cls.patch_logger_warning = mock.patch.object( aea.decision_maker.gop._default_logger, "warning" ) cls.mocked_logger_warning = cls.patch_logger_warning.__enter__() @classmethod def _unpatch_logger(cls): cls.mocked_logger_warning.__exit__() @classmethod def setup(cls): """Initialise the decision maker.""" super().setup() cls._patch_logger() @classmethod def teardown(cls): """Tear the tests down.""" cls._unpatch_logger() super().teardown() ================================================ FILE: tests/test_aea/test_decision_maker/test_ownership_state.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for decision_maker.""" import pytest from aea_ledger_ethereum import EthereumCrypto from aea.decision_maker.gop import OwnershipState from aea.helpers.transaction.base import Terms def test_non_initialized_ownership_state_raises_exception(): """Test that non-initialized ownership state raises exception.""" ownership_state = OwnershipState() with pytest.raises(ValueError): ownership_state.amount_by_currency_id with pytest.raises(ValueError): ownership_state.quantities_by_good_id def test_initialisation(): """Test the initialisation of the ownership_state.""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 2} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert ownership_state.amount_by_currency_id is not None assert ownership_state.quantities_by_good_id is not None assert ownership_state.is_initialized def test_is_affordable_for_uninitialized(): """Test the initialisation of the ownership_state.""" ownership_state = OwnershipState() buyer_terms = Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ) assert ownership_state.is_affordable( terms=buyer_terms ), "Any transaction should be classed as affordable." class TestOwnershipState: """Test the OwnershipState module.""" @classmethod def setup_class(cls): """Setup class for test case.""" cls.buyer_terms = Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ) cls.neutral_terms = Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": 0}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 0}, nonce="transaction nonce", ) cls.malformed_terms = Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -10}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ) cls.malformed_terms._amount_by_currency_id = {"FET": 10} cls.seller_terms = Terms( ledger_id=EthereumCrypto.identifier, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": 1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": -10}, nonce="transaction nonce", ) def test_transaction_is_affordable_agent_is_buyer(self): """Check if the agent has the money to cover the sender_amount (the agent=sender is the buyer).""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 20} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert ownership_state.is_affordable( terms=self.buyer_terms ), "We should have the money for the transaction!" def test_transaction_is_affordable_there_is_no_wealth(self): """Reject the transaction when there is no wealth exchange.""" currency_endowment = {"FET": 0} good_endowment = {"good_id": 0} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert not ownership_state.is_affordable_transaction( terms=self.buyer_terms ), "We must reject the transaction." def test_transaction_is_affordable_neutral(self): """Reject the transaction when there is no wealth exchange.""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 20} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert not ownership_state.is_affordable_transaction( terms=self.neutral_terms ), "We must reject the transaction." def test_transaction_is_affordable_malformed(self): """Reject the transaction when there is no wealth exchange.""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 20} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert not ownership_state.is_affordable_transaction( terms=self.malformed_terms ), "We must reject the transaction." def test_transaction_is_affordable_agent_is_seller(self): """Check if the agent has the goods (the agent=sender is the seller).""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 20} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert ownership_state.is_affordable_transaction( terms=self.seller_terms ), "We must reject the transaction." def test_apply(self): """Test the apply function.""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 2} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) list_of_terms = [self.buyer_terms] state = ownership_state new_state = ownership_state.apply_transactions(list_of_terms=list_of_terms) assert ( state != new_state ), "after applying a list_of_terms must have a different state!" def test_transaction_update(self): """Test the transaction update when sending tokens.""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 20} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert ownership_state.amount_by_currency_id == currency_endowment assert ownership_state.quantities_by_good_id == good_endowment ownership_state.update(terms=self.buyer_terms) expected_amount_by_currency_id = {"FET": 99} expected_quantities_by_good_id = {"good_id": 30} assert ownership_state.amount_by_currency_id == expected_amount_by_currency_id assert ownership_state.quantities_by_good_id == expected_quantities_by_good_id def test_transaction_update_receive(self): """Test the transaction update when receiving tokens.""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 20} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert ownership_state.amount_by_currency_id == currency_endowment assert ownership_state.quantities_by_good_id == good_endowment ownership_state.update(terms=self.seller_terms) expected_amount_by_currency_id = {"FET": 101} expected_quantities_by_good_id = {"good_id": 10} assert ownership_state.amount_by_currency_id == expected_amount_by_currency_id assert ownership_state.quantities_by_good_id == expected_quantities_by_good_id ================================================ FILE: tests/test_aea/test_decision_maker/test_preferences.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for decision_maker.""" import copy import pytest from aea_ledger_ethereum import EthereumCrypto from aea.decision_maker.gop import OwnershipState, Preferences from aea.helpers.transaction.base import Terms def test_preferences_properties(): """Test the properties of the preferences class.""" preferences = Preferences() with pytest.raises(ValueError): preferences.exchange_params_by_currency_id with pytest.raises(ValueError): preferences.utility_params_by_good_id def test_preferences_init(): """Test the preferences init().""" utility_params = {"good_id": 20.0} exchange_params = {"FET": 10.0} preferences = Preferences() preferences.set( exchange_params_by_currency_id=exchange_params, utility_params_by_good_id=utility_params, ) assert preferences.utility_params_by_good_id is not None assert preferences.exchange_params_by_currency_id is not None assert preferences.is_initialized copied_preferences = copy.copy(preferences) assert ( preferences.exchange_params_by_currency_id == copied_preferences.exchange_params_by_currency_id ) assert ( preferences.utility_params_by_good_id == copied_preferences.utility_params_by_good_id ) def test_logarithmic_utility(): """Calculate the logarithmic utility and checks that it is not none..""" utility_params = {"good_id": 20.0} exchange_params = {"FET": 10.0} good_holdings = {"good_id": 2} preferences = Preferences() preferences.set( utility_params_by_good_id=utility_params, exchange_params_by_currency_id=exchange_params, ) log_utility = preferences.logarithmic_utility(quantities_by_good_id=good_holdings) assert log_utility is not None, "Log_utility must not be none." def test_linear_utility(): """Calculate the linear_utility and checks that it is not none.""" currency_holdings = {"FET": 100} utility_params = {"good_id": 20.0} exchange_params = {"FET": 10.0} preferences = Preferences() preferences.set( utility_params_by_good_id=utility_params, exchange_params_by_currency_id=exchange_params, ) linear_utility = preferences.linear_utility(amount_by_currency_id=currency_holdings) assert linear_utility is not None, "Linear utility must not be none." def test_utility(): """Calculate the score.""" utility_params = {"good_id": 20.0} exchange_params = {"FET": 10.0} currency_holdings = {"FET": 100} good_holdings = {"good_id": 2} preferences = Preferences() preferences.set( utility_params_by_good_id=utility_params, exchange_params_by_currency_id=exchange_params, ) score = preferences.utility( quantities_by_good_id=good_holdings, amount_by_currency_id=currency_holdings, ) linear_utility = preferences.linear_utility(amount_by_currency_id=currency_holdings) log_utility = preferences.logarithmic_utility(quantities_by_good_id=good_holdings) assert ( score == log_utility + linear_utility ), "The score must be equal to the sum of log_utility and linear_utility." def test_marginal_utility(): """Test the marginal utility.""" currency_holdings = {"FET": 100} utility_params = {"good_id": 20.0} exchange_params = {"FET": 10.0} good_holdings = {"good_id": 2} preferences = Preferences() preferences.set( utility_params_by_good_id=utility_params, exchange_params_by_currency_id=exchange_params, ) delta_good_holdings = {"good_id": 1} delta_currency_holdings = {"FET": -5} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_holdings, quantities_by_good_id=good_holdings, ) marginal_utility = preferences.marginal_utility( ownership_state=ownership_state, delta_quantities_by_good_id=delta_good_holdings, delta_amount_by_currency_id=delta_currency_holdings, ) assert marginal_utility is not None, "Marginal utility must not be none." def test_score_diff_from_transaction(): """Test the difference between the scores.""" good_holdings = {"good_id": 2} currency_holdings = {"FET": 100} utility_params = {"good_id": 20.0} exchange_params = {"FET": 10.0} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_holdings, quantities_by_good_id=good_holdings ) preferences = Preferences() preferences.set( utility_params_by_good_id=utility_params, exchange_params_by_currency_id=exchange_params, ) terms = Terms( ledger_id=EthereumCrypto.identifier, sender_address="agent_1", counterparty_address="pk", amount_by_currency_id={"FET": -20}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ) cur_score = preferences.utility( quantities_by_good_id=good_holdings, amount_by_currency_id=currency_holdings ) new_state = ownership_state.apply_transactions([terms]) new_score = preferences.utility( quantities_by_good_id=new_state.quantities_by_good_id, amount_by_currency_id=new_state.amount_by_currency_id, ) diff_scores = new_score - cur_score score_difference = preferences.utility_diff_from_transaction( ownership_state=ownership_state, terms=terms ) assert ( score_difference == diff_scores ), "The calculated difference must be equal to the return difference from the function." assert not preferences.is_utility_enhancing( ownership_state=ownership_state, terms=terms ), "Should not enhance utility." def test_is_utility_enhancing_uninitialized(): """Test is_utility_enhancing when the states are uninitialized.""" ownership_state = OwnershipState() preferences = Preferences() terms = Terms( ledger_id=EthereumCrypto.identifier, sender_address="agent_1", counterparty_address="pk", amount_by_currency_id={"FET": -20}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ) assert preferences.is_utility_enhancing( ownership_state=ownership_state, terms=terms ), "Should enhance utility." ================================================ FILE: tests/test_aea/test_decision_maker/test_scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for decision_maker.""" import pytest from aea.decision_maker.scaffold import DecisionMakerHandler from aea.identity.base import Identity def test_init_and_not_implemented(): """Initialise the decision maker handler.""" decision_maker_handler = DecisionMakerHandler( identity=Identity("name", "address", "public_key"), wallet="wallet", config={} ) with pytest.raises(NotImplementedError): decision_maker_handler.handle("message") ================================================ FILE: tests/test_aea/test_error_handler/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test modules for the error handler.""" ================================================ FILE: tests/test_aea/test_error_handler/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the sym link module.""" import logging from unittest.mock import Mock, patch from aea.error_handler.default import ErrorHandler _default_logger = logging.getLogger(__name__) def test_config(): """Test the config property.""" config = {"some": "config"} handler = ErrorHandler(**config) assert handler.config == config def test_send_unsupported_protocol(): """Test the send_unsupported_protocol method.""" handler = ErrorHandler() envelope_mock = Mock() envelope_mock.protocol_specification_id = "1" envelope_mock.sender = "2" envelope_mock.to = "3" count = handler.unsupported_protocol_count with patch.object(_default_logger, "warning") as mock_logger: handler.send_unsupported_protocol(envelope_mock, _default_logger) mock_logger.assert_any_call( f"Unsupported protocol: protocol_specification_id={envelope_mock.protocol_specification_id}. You might want to add a handler for a protocol implementing this specification. Sender={envelope_mock.sender}, to={envelope_mock.sender}." ) assert count + 1 == handler.unsupported_protocol_count def test_send_decoding_error(): """Test the send_decoding_error method.""" handler = ErrorHandler() envelope_mock = Mock() envelope_mock.protocol_specification_id = "1" envelope_mock.sender = "2" envelope_mock.to = "3" count = handler.decoding_error_count e = Exception("some") with patch.object(_default_logger, "warning") as mock_logger: handler.send_decoding_error(envelope_mock, e, _default_logger) mock_logger.assert_any_call( f"Decoding error for envelope: {envelope_mock}. Protocol_specification_id='{envelope_mock.protocol_specification_id}' and message are inconsistent. Sender={envelope_mock.sender}, to={envelope_mock.sender}. Exception={e}." ) assert count + 1 == handler.decoding_error_count def test_send_no_active_handler_1(): """Test the send_no_active_handler method.""" handler = ErrorHandler() envelope_mock = Mock() envelope_mock.protocol_specification_id = "1" envelope_mock.sender = "2" envelope_mock.to = "3" envelope_mock.skill_id = None count = handler.no_active_handler_count reason = "reason" with patch.object(_default_logger, "warning") as mock_logger: handler.send_no_active_handler(envelope_mock, reason, _default_logger) mock_logger.assert_any_call( f"Cannot handle envelope: {reason}. Sender={envelope_mock.sender}, to={envelope_mock.sender}." ) assert count + 1 == handler.no_active_handler_count def test_send_no_active_handler_2(): """Test the send_no_active_handler method.""" handler = ErrorHandler() envelope_mock = Mock() envelope_mock.protocol_id = "1" envelope_mock.sender = "2" envelope_mock.to = "3" envelope_mock.skill_id = "4" count = handler.no_active_handler_count reason = "reason" with patch.object(_default_logger, "warning") as mock_logger: handler.send_no_active_handler(envelope_mock, reason, _default_logger) mock_logger.assert_any_call( f"Cannot handle envelope: {reason}. Sender={envelope_mock.sender}, to={envelope_mock.sender}." ) assert count + 1 == handler.no_active_handler_count ================================================ FILE: tests/test_aea/test_error_handler/test_scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for decision_maker.""" import pytest from aea.error_handler.scaffold import ErrorHandler def test_scaffold_send_unsupported_protocol_raises_not_implemented_error(): """Test 'send_unsupported_protocol' raises not implemented error.""" with pytest.raises(NotImplementedError): ErrorHandler().send_unsupported_protocol(None, None) def test_scaffold_send_decoding_error_raises_not_implemented_error(): """Test 'send_decoding_error' raises not implemented error.""" with pytest.raises(NotImplementedError): ErrorHandler().send_decoding_error(None, None, None) def test_scaffold_send_no_active_handler_raises_not_implemented_error(): """Test 'send_no_active_handler' raises not implemented error.""" with pytest.raises(NotImplementedError): ErrorHandler().send_no_active_handler(None, None, None) ================================================ FILE: tests/test_aea/test_exceptions.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea exceptions.""" import pytest from aea.exceptions import AEAEnforceError, _StopRuntime, enforce, parse_exception def test_enforce_no_exception(): """Test enforce does not throw exception if condition is True.""" enforce(True, "Error message") def test_enforce_exception(): """Test enforce does throw exception if condition is False.""" error_msg = "Error message" with pytest.raises(AEAEnforceError, match=error_msg): enforce(False, error_msg) def test_stop_runtime(): """Test thes stop runtime exception.""" test = "test string" e = _StopRuntime(test) assert e.reraise == test def test_parse_exception_i(): """Test parse exception.""" def exception_raise(): """A function that raises an exception.""" raise ValueError("expected") try: exception_raise() except Exception as e: out = parse_exception(e) expected = [ "Traceback (most recent call last):\n\n", 'test_exceptions.py", line ', "in exception_raise\n", 'raise ValueError("expected")\n\nValueError: expected\n', ] assert all([string in out for string in expected]) def test_parse_exception_ii(): """Test parse exception.""" def exception_raise(): """A function that raises an exception.""" raise AEAEnforceError("expected") try: exception_raise() except Exception as e: out = parse_exception(e) expected = [ "Traceback (most recent call last):\n\n", 'test_exceptions.py", line ', "in test_parse_exception_ii\n", "exception_raise()\n\n", ", line", "in exception_raise\n", 'raise AEAEnforceError("expected")\n\naea.exceptions.AEAEnforceError: expected\n', ] assert all([string in out for string in expected]) ================================================ FILE: tests/test_aea/test_helpers/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helper module.""" ================================================ FILE: tests/test_aea/test_helpers/test_acn/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helper.acn module.""" ================================================ FILE: tests/test_aea/test_helpers/test_acn/test_agent_record.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for acn helper module.""" import pytest from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.registries import make_crypto from aea.helpers.acn.agent_record import AgentRecord from aea.helpers.base import CertRequest from tests.conftest import _process_cert def test_agent_record(change_directory): """Test signature and public key proper retrieval from a CertRequest""" agent_key_1 = make_crypto(DEFAULT_LEDGER) agent_key_2 = make_crypto(DEFAULT_LEDGER) peer_public_key_1 = make_crypto(DEFAULT_LEDGER).public_key peer_public_key_2 = make_crypto(DEFAULT_LEDGER).public_key cert_path = "test_acn_cert.txt" cert = CertRequest( peer_public_key_1, "test_service", DEFAULT_LEDGER, "2021-01-01", "2022-01-01", "{public_key}", cert_path, ) _process_cert(agent_key_1, cert, change_directory) # success agent_record = AgentRecord.from_cert_request( cert, agent_key_1.address, peer_public_key_1 ) assert ( agent_record.address == agent_key_1.address and agent_record.public_key == agent_key_1.public_key and agent_record.representative_public_key == peer_public_key_1 and agent_record.signature == cert.get_signature() and agent_record.message == cert.get_message(peer_public_key_1) ) # success agent_record = AgentRecord( agent_key_1.address, peer_public_key_1, cert.identifier, cert.ledger_id, cert.not_before, cert.not_after, cert.message_format, cert.get_signature(), ) assert ( agent_record.address == agent_key_1.address and agent_record.public_key == agent_key_1.public_key and agent_record.representative_public_key == peer_public_key_1 and agent_record.signature == cert.get_signature() and agent_record.message == cert.get_message(peer_public_key_1) ) # error: wrong signer with pytest.raises( ValueError, match="Invalid signature for provided representative_public_key and agent address!", ): AgentRecord.from_cert_request(cert, agent_key_2.address, peer_public_key_1) # error: wrong signer with pytest.raises( ValueError, match="Invalid signature for provided representative_public_key and agent address!", ): AgentRecord.from_cert_request(cert, agent_key_1.address, peer_public_key_2) ================================================ FILE: tests/test_aea/test_helpers/test_acn/test_uri.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for acn helper module.""" from aea.helpers.acn.uri import Uri def test_uri(): """Test URI class""" Uri(uri="localhost:9000") uri = Uri(host="localhost", port=9000) assert str(uri) == "localhost:9000" assert uri.host == "localhost" assert uri.port == 9000 Uri() ================================================ FILE: tests/test_aea/test_helpers/test_async_friendly_queue.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for AsyncFriendlyQueue.""" import asyncio import time from queue import Empty from threading import Thread import pytest from aea.helpers.async_friendly_queue import AsyncFriendlyQueue def test_same_thread() -> None: """Test AsyncFriendlyQueue in one thread environment.""" sq = AsyncFriendlyQueue() with pytest.raises(Empty): sq.get_nowait() item = "item" sq.put_nowait(item) assert sq.get_nowait() == item @pytest.mark.asyncio async def test_asyncio_loop() -> None: """Test AsyncFriendlyQueue inside one event loop.""" sq = AsyncFriendlyQueue() item = "item" sq.put(item) assert await sq.async_get() == item def test_many_threads_with_asyncio() -> None: """Test AsyncFriendlyQueue wuth multiple asyncio event loop consumers in different threads.""" sq = AsyncFriendlyQueue() num_threads = 10 threads = [] results = [] # type: ignore def test(sq, timeout, results): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) async def wait_msg(sq, timeout, results): await asyncio.wait_for(sq.async_get(), timeout) results.append("done") loop.run_until_complete(wait_msg(sq, timeout, results)) loop.close() for _ in range(num_threads): t = Thread(target=test, args=(sq, 5, results)) t.daemon = True t.start() threads.append(t) time.sleep(0.03) for _ in range(num_threads): sq.put("item") for t in threads: t.join() assert len(results) == num_threads ================================================ FILE: tests/test_aea/test_helpers/test_async_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for AsyncFriendlyQueue.""" import asyncio import time from concurrent.futures._base import CancelledError from contextlib import suppress from threading import Thread import pytest from aea.helpers.async_utils import ( AsyncState, PeriodicCaller, Runnable, ThreadedAsyncRunner, ensure_list, ) from tests.common.utils import wait_for_condition, wait_for_condition_async def test_enusre_list() -> None: """Test AsyncFriendlyQueue in one thread environment.""" list1 = [1, 2, 3] assert ensure_list(list1) is list1 assert ensure_list(1) == [1] assert ensure_list(map(lambda x: x, list1)) == list1 @pytest.mark.asyncio async def test_async_state(): """Test various cases for AsyncState.""" loop = asyncio.get_event_loop() state = AsyncState() # check set/get value = 1 state.set(value) assert state.get() == value # check set/get with state property value = 3 state.state = 3 assert state.state == value # check wait/set loop.call_soon(state.set, 2) await state.wait(2) # state is already set await state.wait(2) @pytest.mark.asyncio async def test_async_state_transit(): """Test async state transit contextmanager.""" state = AsyncState() state.set(None) with state.transit(initial=1, success=2, fail=3): assert state.get() == 1 assert state.get() == 2 state.set(None) with suppress(ValueError): with state.transit(initial=1, success=2, fail=3): assert state.get() == 1 raise ValueError() assert state.get() == 3 @pytest.mark.asyncio async def test_asyncstate_with_list_of_valid_states(): """Test various cases for AsyncState.""" states = [1, 2, 3] state = AsyncState(1, states) state.set(2) assert state.get() == 2 with pytest.raises(ValueError): state.set("anything") assert state.get() == 2 @pytest.mark.asyncio async def test_asyncstate_callback(): """Test various cases for AsyncState.callback.""" state = AsyncState() called = False def callback_err(state): raise Exception("expected") def callback(state): nonlocal called called = True state.add_callback(callback_err) state.add_callback(callback) state.set(2) assert state.get() == 2 assert called @pytest.mark.asyncio async def test_periodic_caller_start_stop(): """Test start stop calls for PeriodicCaller.""" called = 0 def callback(): nonlocal called called += 1 periodic_caller = PeriodicCaller(callback, period=0.1) periodic_caller.start() await asyncio.sleep(0.15) assert called >= 1 periodic_caller.stop() old_called = called await asyncio.sleep(0.15) assert old_called == called @pytest.mark.asyncio async def test_periodic_caller_exception(): """Test exception raises for PeriodicCaller.""" exception_called = False def exception_callback(*args, **kwargs): nonlocal exception_called exception_called = True def callback(): raise Exception("expected") periodic_caller = PeriodicCaller( callback, period=0.1, exception_callback=exception_callback ) periodic_caller.start() await asyncio.sleep(0.15) assert exception_called periodic_caller.stop() @pytest.mark.asyncio async def test_threaded_async_run(): """Test threaded async runner.""" runner = ThreadedAsyncRunner() runner.start() async def fn(): return "ok" assert runner.call(fn()).result() == "ok" runner.stop() @pytest.mark.asyncio async def test_threaded_async_run_cancel_task(): """Test threaded async runner tasks cancelled.""" runner = ThreadedAsyncRunner() runner.start() async def fn(): await asyncio.sleep(1) task = runner.call(fn()) await asyncio.sleep(0.1) task.cancel() await asyncio.sleep(0.1) with pytest.raises(CancelledError): task.result() assert task.done() # cancel before start task = runner.call(fn()) task.cancel() with pytest.raises(CancelledError): task.result() assert task.done() class RunAndExit(Runnable): """Test class.""" async def run(self): """Test method.""" await asyncio.sleep(0.2) class TestRunnable: """Tests for Runnable object.""" def test_no_loop_and_threded(self): """Test runnable fails on threaded mode and loop provided..""" with pytest.raises( ValueError, ): RunAndExit(loop=asyncio.get_event_loop(), threaded=True) def test_task_cancel_not_set(self): """Test task cancel.""" class TestRun(Runnable): async def run(self): while True: await asyncio.sleep(1) run = TestRun() run._task_cancel() @pytest.mark.asyncio async def test_runnable_async(self): """Test runnable async methods.""" # for pydocstyle class TestRun(Runnable): async def run(self): while True: await asyncio.sleep(1) run = TestRun() run.start() run.stop() await run.wait_completed() run = TestRun(threaded=True) run.start() run.stop() run.wait_completed(sync=True) run = RunAndExit() await run.start_and_wait_completed() def test_runnable_sync(self): """Test runnable sync methods.""" run = RunAndExit() run.start_and_wait_completed(sync=True) @pytest.mark.asyncio async def test_double_start(self): """Test runnable async methods.""" # for pydocstyle class TestRun(Runnable): async def run(self): while True: await asyncio.sleep(1) run = TestRun() await run.wait_completed() assert run.start() assert not run.start() run.stop() await run.wait_completed() await run.wait_completed() @pytest.mark.asyncio async def test_run_in_thread(self): """Test runnable in thread mode.""" # for pydocstyle class TestRun(Runnable): async def run(self): while True: await asyncio.sleep(1) run = TestRun() t = Thread(target=run.start_and_wait_completed, kwargs=dict(sync=True)) t.start() while not run.is_running: pass run.stop() t.join() @pytest.mark.asyncio async def test_timeout(self): """Test runnable async methods.""" # for pydocstyle class TestRun(Runnable): def __init__( self, loop: asyncio.AbstractEventLoop = None, threaded: bool = False ) -> None: Runnable.__init__(self, loop=loop, threaded=threaded) self.started = False async def run(self): while True: await asyncio.sleep(0.1) self.started = True run = TestRun(threaded=True) run.start() wait_for_condition(lambda: run.started, timeout=5) with pytest.raises(asyncio.TimeoutError): run.wait_completed(sync=True, timeout=1) run.stop() run.wait_completed(sync=True) run = TestRun() run.start() await wait_for_condition_async(lambda: run.started, timeout=5) with pytest.raises(asyncio.TimeoutError): await run.wait_completed(timeout=1) run.stop() await run.wait_completed() @pytest.mark.asyncio async def test_exception(self): """Test runnable async methods.""" # for pydocstyle import time class TestRun(Runnable): async def run(self): raise Exception("awaited") run = TestRun(threaded=True) run.start() time.sleep(0.1) with pytest.raises(Exception, match="awaited"): run.wait_completed(sync=True, timeout=1) run.stop() run.wait_completed(sync=True) run = TestRun() run.start() with pytest.raises(Exception, match="awaited"): await run.wait_completed(timeout=1) run.stop() await run.wait_completed() @pytest.mark.asyncio async def test_wait_async_threaded(self): """Test runnable async methods.""" # for pydocstyle class TestRun(Runnable): async def run(self): raise Exception("awaited") run = TestRun(threaded=True) run.start() await asyncio.sleep(0.4) with pytest.raises(Exception, match="awaited"): await run.wait_completed(timeout=1) run.stop() await run.wait_completed() @pytest.mark.asyncio async def test_wait_async_threaded_no_exception(self): """Test runnable threaded wait completed.""" # for pydocstyle class TestRun(Runnable): async def run(self): await asyncio.sleep(0.1) run = TestRun(threaded=True) run.start() await run.wait_completed() @pytest.mark.asyncio async def test_double_stop(self): """Test runnable double stop.""" # for pydocstyle class TestRun(Runnable): async def run(self): await asyncio.sleep(0.1) run = TestRun() run.start() run.stop() run.stop() await run.wait_completed() def test_stop_before_run(self): """Test stop before run.""" # for pydocstyle class TestRun(Runnable): async def run(self): await asyncio.sleep(0.1) run = TestRun() run.stop() run.start() time.sleep(1) assert not run.is_running ================================================ FILE: tests/test_aea/test_helpers/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helper module.""" import datetime import os import platform import re import shutil import signal import tempfile import time from copy import copy from functools import wraps from pathlib import Path from subprocess import Popen # nosec from tempfile import TemporaryDirectory from typing import Dict, Optional, Set from unittest.mock import patch import pytest from packaging.version import Version from aea.exceptions import AEAEnforceError from aea.helpers.base import ( CertRequest, MaxRetriesError, RegexConstrainedString, compute_specifier_from_version, decorator_with_optional_params, delete_directory_contents, dict_to_path_value, ensure_dir, exception_log_and_reraise, find_topological_order, load_env_file, load_module, locate, prepend_if_not_absolute, reachable_nodes, recursive_update, retry_decorator, send_control_c, try_decorator, win_popen_kwargs, ) from packages.fetchai.connections.oef.connection import OEFConnection from tests.conftest import CUR_PATH, ROOT_DIR, skip_test_windows class TestHelpersBase: """Test the helper functions.""" def test_locate(self): """Test the locate function to locate modules.""" cwd = os.getcwd() os.chdir(os.path.join(CUR_PATH, "..")) gym_package = locate( "packages.fetchai.connections.gym.connection.GymConnection" ) non_existing_package = locate( "packages.fetchai.connections.non_existing_connection" ) os.chdir(cwd) assert gym_package is not None assert non_existing_package is None def test_locate_class(self): """Test the locate function to locate classes.""" cwd = os.getcwd() os.chdir(os.path.join(CUR_PATH, "..")) expected_class = OEFConnection actual_class = locate( "packages.fetchai.connections.oef.connection.OEFConnection" ) os.chdir(cwd) # although they are the same class, they are different instances in memory # and the build-in default "__eq__" method does not compare the attributes. # so compare the names assert actual_class is not None assert expected_class.__name__ == actual_class.__name__ def test_locate_with_builtins(self): """Test that locate function returns the built-in.""" result = locate("int.bit_length") assert int.bit_length == result def test_locate_when_path_does_not_exist(self): """Test that locate function returns None when the dotted path does not exist.""" result = locate("aea.not.existing.path") assert result is None result = locate("ThisClassDoesNotExist") assert result is None def test_regex_constrained_string_initialization(): """Test we can initialize a regex constrained with the default regex.""" RegexConstrainedString("") RegexConstrainedString("abcde") RegexConstrainedString(b"") RegexConstrainedString(b"abcde") RegexConstrainedString(RegexConstrainedString("")) RegexConstrainedString(RegexConstrainedString("abcde")) def test_load_module(): """Test load module from filepath and dotted notation.""" load_module( "packages.fetchai.connections.gym.connection", Path(ROOT_DIR) / "packages" / "fetchai" / "connections" / "gym" / "connection.py", ) def test_load_env_file(): """Test load env file updates process environment variables.""" load_env_file(Path(ROOT_DIR) / "tests" / "data" / "dot_env_file") assert os.getenv("TEST") == "yes" def test_reg_exp_not_match(): """Test regexp checks.""" # for pydocstyle class MyReString(RegexConstrainedString): REGEX = re.compile(r"[0-9]+") with pytest.raises(ValueError): MyReString("anystring") def test_try_decorator(): """Test try and log decorator.""" # for pydocstyle @try_decorator("oops", default_return="failed") def fn(): raise Exception("expected") assert fn() == "failed" def test_retry_decorator(): """Test auto retry decorator.""" num_calls = 0 retries = 3 @retry_decorator(retries, "oops. expected") def fn(): nonlocal num_calls num_calls += 1 raise Exception("expected") with pytest.raises(MaxRetriesError): fn() assert num_calls == retries def test_log_and_reraise(): """Test log and reraise context manager.""" log_msg = None def fn(msg): nonlocal log_msg log_msg = msg with pytest.raises(ValueError): with exception_log_and_reraise(fn, "oops"): raise ValueError() assert log_msg == "oops" @skip_test_windows def test_send_control_c_group(): """Test send control c to process group.""" # Can't test process group id kill directly, # because o/w pytest would be stopped. process = Popen(["sleep", "1"]) # nosec pgid = os.getpgid(process.pid) time.sleep(0.1) with patch("os.killpg") as mock_killpg: send_control_c(process, kill_group=True) process.communicate(timeout=3) mock_killpg.assert_called_with(pgid, signal.SIGINT) def test_send_control_c(): """Test send control c to process.""" # Can't test process group id kill directly, # because o/w pytest would be stopped. process = Popen( # nosec ["timeout" if platform.system() == "Windows" else "sleep", "5"], **win_popen_kwargs(), ) time.sleep(0.001) send_control_c(process) process.communicate(timeout=3) assert process.returncode != 0 @skip_test_windows def test_send_control_c_windows(): """Test send control c on Windows.""" process = Popen( # nosec ["timeout" if platform.system() == "Windows" else "sleep", "5"] ) time.sleep(0.001) pid = process.pid with patch("aea.helpers.base.signal") as mock_signal: mock_signal.CTRL_C_EVENT = "mock" with patch("platform.system", return_value="Windows"): with patch("os.kill") as mock_kill: send_control_c(process) mock_kill.assert_called_with(pid, mock_signal.CTRL_C_EVENT) def test_recursive_update_no_recursion(): """Test the 'recursive update' utility, in the case there's no recursion.""" to_update = dict(not_updated=0, an_integer=1, a_list=[1, 2, 3], a_tuple=(1, 2, 3)) new_integer, new_list, new_tuple = 2, [3], (3,) new_values = dict(an_integer=new_integer, a_list=new_list, a_tuple=new_tuple) recursive_update(to_update, new_values) assert to_update == dict( not_updated=0, an_integer=new_integer, a_list=new_list, a_tuple=new_tuple ) def test_recursive_update_with_recursion(): """Test the 'recursive update' utility with recursion.""" # here we try to update an integer and add a new value to_update = dict(subdict=dict(to_update=1)) new_values = dict(subdict=dict(to_update=2)) recursive_update(to_update, new_values) assert to_update == dict(subdict=dict(to_update=2)) def test_recursive_update_negative_different_type(): """Test the 'recursive update' utility, when the types are different.""" # here we try to update an integer with a boolean - it raises error. to_update = dict(subdict=dict(to_update=1)) new_values = dict(subdict=dict(to_update=False)) with pytest.raises( ValueError, match="Trying to replace value '1' with value 'False' which is of different type.", ): recursive_update(to_update, new_values) def test_recursive_update_new_fields(): """Test the 'recursive update' utility, with new fields.""" # here we try to update an integer with a boolean - it raises error. to_update = dict(subdict=dict(to_update=1)) new_values = dict(subdict=dict(to_update2=False)) with pytest.raises( ValueError, match="Key 'to_update2' is not contained in the dictionary to update.", ): recursive_update(to_update, new_values) assert "to_update2" not in to_update["subdict"] recursive_update(to_update, new_values, allow_new_values=True) assert "to_update2" in to_update["subdict"] def test_recursive_update_negative_unknown_field(): """Test the 'recursive update' utility, when there are unknown fields.""" # here we try to update an integer with a boolean - it raises error. to_update = dict(subdict=dict(field=1)) new_values = dict(subdict=dict(new_field=False)) with pytest.raises( ValueError, match="Key 'new_field' is not contained in the dictionary to update.", ): recursive_update(to_update, new_values) class TestTopologicalOrder: """Test the computation of topological order.""" def test_empty_graph(self): """Test the function with empty input.""" order = find_topological_order({}) assert order == [] def test_one_node(self): """Test the function with only one node.""" order = find_topological_order({0: set()}) assert order == [0] def test_one_node_with_cycle(self): """Test the function with only one node and a loop.""" with pytest.raises(ValueError, match="Graph has at least one cycle."): find_topological_order({0: {0}}) def test_two_nodes_no_edges(self): """Test the function with two nodes, but no edges.""" order = find_topological_order({0: set(), 1: set()}) assert order == [0, 1] def test_two_nodes_no_cycle(self): """Test the function with two nodes, but no cycles.""" order = find_topological_order({0: {1}}) assert order == [0, 1] def test_two_nodes_with_cycle(self): """Test the function with two nodes and a cycle between them.""" with pytest.raises(ValueError, match="Graph has at least one cycle."): find_topological_order({0: {1}, 1: {0}}) def test_two_nodes_clique(self): """Test the function with a clique of two nodes.""" with pytest.raises(ValueError, match="Graph has at least one cycle."): find_topological_order({0: {1, 0}, 1: {0, 1}}) @pytest.mark.parametrize("chain_length", [3, 5, 10, 100]) def test_chain(self, chain_length): """Test the function with a chain.""" adj_list: Dict[int, Set[int]] = {} for i in range(chain_length - 1): adj_list[i] = {i + 1} adj_list[chain_length - 1] = set() order = find_topological_order(adj_list) assert order == list(range(chain_length)) class TestReachableNodes: """Test reachable_nodes utility.""" def test_empty_graph(self): """Test empty graph.""" result = reachable_nodes({}, set()) assert result == {} def test_starting_node_not_in_the_graph(self): """Test error when starting node not in the graph.""" with pytest.raises( AEAEnforceError, match="These starting nodes are not in the set of nodes: {1}", ): reachable_nodes({}, {1}) def test_one_node(self): """Test one node.""" result = reachable_nodes({1: set()}, {1}) assert result == {1: set()} def test_one_node_loop(self): """Test one node, loop.""" g = {1: {1}} result = reachable_nodes(g, {1}) assert result == g def test_two_nodes(self): """Test two nodes.""" g = {1: {2}} result = reachable_nodes(g, {1}) assert result == g result = reachable_nodes(g, {2}) assert result == {2: set()} def test_two_nodes_cycle(self): """Test two nodes in a cycle""" g = {1: {2}, 2: {1}} result = reachable_nodes(g, {1}) assert result == g result = reachable_nodes(g, {2}) assert result == g def test_chain(self): """Test chain""" g = {1: {2}, 2: {3}, 3: set()} result = reachable_nodes(g, {1}) assert result == g result = reachable_nodes(g, {2}) expected = copy(g) expected.pop(1) assert result == expected result = reachable_nodes(g, {3}) assert result == {3: set()} def test_ensure_dir(): """Test ensure_dir.""" dir_name = "test" with TemporaryDirectory() as tmpdirname: full_path = os.path.join(tmpdirname, dir_name) assert not os.path.exists(full_path) ensure_dir(full_path) assert os.path.exists(full_path) file_path = os.path.join(full_path, "file_name") with open(file_path, "w"): pass with pytest.raises(AEAEnforceError): ensure_dir(file_path) class BaseTestCertRequestError: """Test errors when instantiating a CertRequest object.""" PUBLIC_KEY = "a_public_key" IDENTIFIER = "an_identifier" LEDGER_ID = "a_ledger_id" NOT_BEFORE = "2020-01-01" NOT_AFTER = "2020-01-02" MESSAGE_FORMAT = "{public_key}" PATH = "some/path" ERROR_MESSAGE_PATTERN = "" def test_error(self): """Test error during instantiation..""" with pytest.raises(ValueError, match=self.ERROR_MESSAGE_PATTERN): CertRequest( self.PUBLIC_KEY, self.IDENTIFIER, self.LEDGER_ID, self.NOT_BEFORE, self.NOT_AFTER, self.MESSAGE_FORMAT, self.PATH, ) class TestCertRequestBadPublicKey(BaseTestCertRequestError): """Test instantiation of CertRequest class with bad public key.""" PUBLIC_KEY = "0a_bad_identifier" ERROR_MESSAGE_PATTERN = "Public key field '0a_bad_identifier' is neither a valid identifier nor an address." class TestCertRequestBadIdentifier(BaseTestCertRequestError): """Test instantiation of CertRequest class with bad identifier.""" IDENTIFIER = "0bad_identifier" ERROR_MESSAGE_PATTERN = ( "Value 0bad_identifier does not match the regular expression.*" ) class TestCertRequestBadLedgerId(BaseTestCertRequestError): """Test instantiation of CertRequest class with bad ledger id.""" LEDGER_ID = "0bad_identifier" ERROR_MESSAGE_PATTERN = ( "Value 0bad_identifier does not match the regular expression.*" ) class TestCertRequestBadNotBefore(BaseTestCertRequestError): """Test instantiation of CertRequest class with bad not_before date.""" NOT_BEFORE = "bad-formatted-date" ERROR_MESSAGE_PATTERN = ( "time data 'bad-formatted-date' does not match format '%Y-%m-%d'" ) class TestCertRequestBadNotAfter(BaseTestCertRequestError): """Test instantiation of CertRequest class with bad not_after date.""" NOT_AFTER = "bad-formatted-date" ERROR_MESSAGE_PATTERN = ( "time data 'bad-formatted-date' does not match format '%Y-%m-%d'" ) class TestCertRequestInconsistentDates(BaseTestCertRequestError): """Test instantiation of CertRequest class when not_before >= not_after""" NOT_BEFORE = "1954-06-07" NOT_AFTER = "1900-01-01" ERROR_MESSAGE_PATTERN = r"Inconsistent certificate validity period: 'not_before' field '1954-06-07' is not before than 'not_after' field '1900-01-01'" class BaseTestCertRequestInstantiation: """Test (successful) instantiation of CertRequest class.""" PUBLIC_KEY: Optional[str] = "" EXPECTED_PUBLIC_KEY: Optional[str] = "" EXPECTED_KEY_IDENTIFIER: Optional[str] = "" @classmethod def setup_class(cls): """Set up class.""" cls.expected_public_key = cls.PUBLIC_KEY cls.expected_identifier = "identifier" cls.expected_ledger_id = "ledger_id" cls.not_before = "2020-01-01" cls.not_after = "2020-01-02" cls.message_format = "{public_key}" cls.expected_path = os.path.abspath("some/path") cls.cert_request = CertRequest( cls.expected_public_key, cls.expected_identifier, cls.expected_ledger_id, cls.not_before, cls.not_after, cls.message_format, cls.expected_path, ) def test_instantiation(self): """Test instantiation.""" assert self.cert_request.public_key == self.EXPECTED_PUBLIC_KEY assert self.cert_request.key_identifier == self.EXPECTED_KEY_IDENTIFIER assert self.cert_request.identifier == self.expected_identifier assert self.cert_request.ledger_id == self.expected_ledger_id expected_not_before = datetime.datetime( 2020, 1, 1, 0, 0, 0, 0, datetime.timezone.utc ) assert self.cert_request.not_before == expected_not_before expected_not_after = datetime.datetime( 2020, 1, 2, 0, 0, 0, 0, datetime.timezone.utc ) assert self.cert_request.not_after == expected_not_after assert self.cert_request.not_before_string == expected_not_before.strftime( "%Y-%m-%d" ) assert self.cert_request.not_after_string == expected_not_after.strftime( "%Y-%m-%d" ) some_key = "some_key" assert self.cert_request.get_message(some_key) == "some_key".encode("ascii") assert self.cert_request.save_path == Path(self.expected_path) def test_from_to_json(self): """Test from-to json methods.""" assert self.cert_request == self.cert_request.from_json(self.cert_request.json) class TestCertRequestInstantiationWithKeyIdentifier(BaseTestCertRequestInstantiation): """Test (successful) instantiation of CertRequest class.""" PUBLIC_KEY = "public_key" EXPECTED_PUBLIC_KEY = None EXPECTED_KEY_IDENTIFIER = PUBLIC_KEY class TestCertRequestInstantiationWithKeyHex(BaseTestCertRequestInstantiation): """Test (successful) instantiation of CertRequest class.""" PUBLIC_KEY = "0xABCDEF12345" EXPECTED_PUBLIC_KEY = "0xABCDEF12345" EXPECTED_KEY_IDENTIFIER = None def test_compute_specifier_from_version(): """Test function 'compute_specifier_from_version'.""" version = "0.1.5" expected_range = ">=0.1.0, <0.2.0" assert expected_range == compute_specifier_from_version(Version(version)) version = "1.0.0rc1" expected_range = ">=1.0.0rc1, <2.0.0" assert expected_range == compute_specifier_from_version(Version(version)) version = "1.0.0.post1" expected_range = ">=1.0.0, <2.0.0" assert expected_range == compute_specifier_from_version(Version(version)) version = "1.1.0rc1" expected_range = ">=1.1.0rc1, <2.0.0" assert expected_range == compute_specifier_from_version(Version(version)) def test_dict_to_path_value(): """Test dict_to_path_value.""" path_values = { tuple(path): value for path, value in dict_to_path_value({"a": 12, "b": {"c": 1}}) } assert path_values.get(("a",)) == 12 assert path_values.get(("b", "c")) == 1 def test_decorator_with_optional_params(): """Test the utility 'decorator_with_optional_params'.""" def hello(name: str): """Say hello to 'name'.""" return f"Hello, {name}!" @decorator_with_optional_params def add_preamble(f, computer_name: str = ""): """ This function adds a preamble to the 'hello' function. It prepends: 'Computer says: ' if the computer name is not specified, else 'Computer {computer_name} says: '. """ @wraps(f) def wrapper(*args, **kwargs): computer_name_or_space = f" {computer_name} " if computer_name else " " preamble = f"Computer{computer_name_or_space}says: " result = f(*args, **kwargs) return preamble + result return wrapper # we can use the 'add_preamble' wrapper either as: # @add_preamble # or: # @add_preamble() @add_preamble def hello_with_preamble(*args, **kwargs): return hello(*args, **kwargs) @add_preamble(computer_name="Commodore 64") def hello_from_commodore(*args, **kwargs): return hello(*args, **kwargs) # we test that the name is updated correctly with pytest.raises( TypeError, match=re.escape("hello() missing 1 required positional argument: 'name'"), ): hello_with_preamble() assert hello_with_preamble("User") == "Computer says: Hello, User!" assert hello_from_commodore("User") == "Computer Commodore 64 says: Hello, User!" class TestDeleteDirectoryContents: """Test utility 'delete_directory_contents'.""" def setup(self): """Set up the test.""" self.root = Path(tempfile.mkdtemp()) # create a file file_1 = self.root / "file_1" file_1.touch() # create a symlink (self.root / "symlink_1").symlink_to(file_1) # create a subdirectory subdir = self.root / "directory" subdir.mkdir() # create a file in the directory (subdir / "file").touch() def test_main(self): """Run the test.""" delete_directory_contents(self.root) assert self.root.exists() assert len(list(self.root.iterdir())) == 0 def teardown(self): """Tear down the test.""" shutil.rmtree(str(self.root), ignore_errors=True) def test_prepend_if_not_absolute(): """Test the prepend_if_not_absolute method.""" path = "path" prefix = "prefix" res = prepend_if_not_absolute(path, prefix) assert res == Path(prefix, path) abs_path = os.path.abspath(path) res = prepend_if_not_absolute(abs_path, prefix) assert res == abs_path ================================================ FILE: tests/test_aea/test_helpers/test_env_vars.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helper module.""" import pytest from aea.helpers.env_vars import ( apply_env_variables, convert_value_str_to_type, is_env_variable, replace_with_env_var, ) def test_is_env_variable(): """Test is_env_variable.""" assert is_env_variable("${test}") assert is_env_variable("${test:int}") assert is_env_variable("${test:int:12}") assert not is_env_variable("sdfsdf") def test_apply_env_variables(): """Test apply_env_variables""" assert apply_env_variables("${var}", {"var": "test"}) == "test" assert apply_env_variables("var", {"var": "test"}) == "var" assert apply_env_variables(["${var}"], {"var": "test"}) == ["test"] assert apply_env_variables({"${var}": "${var}"}, {"var": "test"}) == { "test": "test" } def test_replace_with_env_var(): """Test replace_with_env_var.""" assert replace_with_env_var("${var:int:12}", {"var": "10"}) == 10 assert replace_with_env_var("${var:int:12}", {}) == 12 assert replace_with_env_var("var", {}) == "var" assert replace_with_env_var("${var}", {}, default_value=100) == 100 with pytest.raises( ValueError, match=r"`var` not found in env variables and no default value set!", ): replace_with_env_var("${var}", {}) def test_convert_value_str_to_type(): """Test convert_value_str_to_type.""" assert convert_value_str_to_type("false", "bool") is False assert convert_value_str_to_type("True", "bool") is True assert convert_value_str_to_type("12", "int") == 12 assert convert_value_str_to_type("1.1", "float") == 1.1 assert convert_value_str_to_type("1sdfsdf2", "none") is None assert convert_value_str_to_type('{"a": 12}', "dict") == {"a": 12} ================================================ FILE: tests/test_aea/test_helpers/test_exec_timeout.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers.exec_timout.""" import os import time import unittest from functools import partial from threading import Thread from unittest.case import TestCase import pytest from aea.helpers.exec_timeout import ( BaseExecTimeout, ExecTimeoutSigAlarm, ExecTimeoutThreadGuard, TimeoutException, ) from tests.common.utils import timeit_context from tests.conftest import MAX_FLAKY_RERUNS if os.name == "nt": pytest.skip("signal.settimer non available on Windows.", allow_module_level=True) class BaseTestExecTimeout(TestCase): """Base test case for code execution timeout.""" EXEC_TIMEOUT_CLASS = BaseExecTimeout @classmethod def setUpClass(cls): """Set up.""" if cls is BaseTestExecTimeout: raise unittest.SkipTest("Skip BaseTest tests, it's a base class") def test_cancel_by_timeout(self): """Test function interrupted by timeout.""" slow_function_time = 0.4 timeout = 0.1 assert timeout < slow_function_time with timeit_context() as timeit_result: with pytest.raises(TimeoutException): with self.EXEC_TIMEOUT_CLASS(timeout) as exec_timeout: self.slow_function(slow_function_time) assert exec_timeout.is_cancelled_by_timeout() assert ( timeit_result.time_passed >= timeout and timeit_result.time_passed < slow_function_time ) def test_limit_is_0_do_not_limit_execution(self): """Test function will not be interrupted cause timeout is 0 or None.""" slow_function_time = 0.1 timeout = 0 assert timeout < slow_function_time with timeit_context() as timeit_result: with self.EXEC_TIMEOUT_CLASS(timeout) as exec_timeout: self.slow_function(slow_function_time) assert not exec_timeout.is_cancelled_by_timeout() assert timeit_result.time_passed >= slow_function_time def test_timeout_bigger_than_execution_time(self): """Test function interrupted by timeout.""" slow_function_time = 0.1 timeout = 1 assert timeout > slow_function_time with timeit_context() as timeit_result: with self.EXEC_TIMEOUT_CLASS(timeout) as exec_timeout: self.slow_function(slow_function_time) assert not exec_timeout.is_cancelled_by_timeout() assert ( timeit_result.time_passed <= timeout and timeit_result.time_passed >= slow_function_time ) @classmethod def slow_function(cls, sleep): """Sleep some time to test timeout applied.""" time.sleep(sleep) class TestSigAlarm(BaseTestExecTimeout): """Test code execution timeout using unix signals.""" EXEC_TIMEOUT_CLASS = ExecTimeoutSigAlarm class TestThreadGuard(BaseTestExecTimeout): """Test code execution timeout using. thread set execption.""" EXEC_TIMEOUT_CLASS = ExecTimeoutThreadGuard def setUp(self): """Set up.""" self.EXEC_TIMEOUT_CLASS.start() def tearDown(self): """Tear down.""" self.EXEC_TIMEOUT_CLASS.stop(force=True) @classmethod def slow_function(cls, sleep): """Sleep in cycle to be perfect interrupted.""" fractions = 10 for _ in range(fractions): time.sleep(sleep / fractions) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_execution_limit_in_threads(self): """Test two threads with different timeouts same time.""" # pydocstyle: ignore # conflict with black # noqa: E800 def make_test_function(slow_function_time, timeout): assert timeout < slow_function_time with timeit_context() as timeit_result: with pytest.raises(TimeoutException): with self.EXEC_TIMEOUT_CLASS(timeout) as exec_limit: self.slow_function(slow_function_time) assert exec_limit.is_cancelled_by_timeout() assert ( timeit_result.time_passed >= timeout and timeit_result.time_passed < slow_function_time ) t1_sleep, t1_timeout = 1, 0.6 t2_sleep, t2_timeout = 0.45, 0.1 t1 = Thread(target=partial(make_test_function, t1_sleep, t1_timeout)) t2 = Thread(target=partial(make_test_function, t2_sleep, t2_timeout)) with timeit_context() as time_t1: t1.start() with timeit_context() as time_t2: t2.start() t2.join() t1.join() assert t2_timeout <= time_t2.time_passed <= t2_sleep assert t1_timeout <= time_t1.time_passed < t1_sleep def test_supervisor_not_started(): """Test that TestThreadGuard supervisor thread not started.""" timeout = 0.1 sleep_time = 0.5 exec_limiter = ExecTimeoutThreadGuard(timeout) with exec_limiter as exec_limit: assert not exec_limiter._future_guard_task TestThreadGuard.slow_function(sleep_time) assert not exec_limit.is_cancelled_by_timeout() ================================================ FILE: tests/test_aea/test_helpers/test_file_io.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the 'helpers/file_io' module.""" import os import tempfile from pathlib import Path from unittest.mock import patch import pytest import aea from aea.configurations.base import PublicId from aea.helpers.file_io import _decode, envelope_from_bytes, lock_file, write_envelope from aea.mail.base import Envelope class TestFileLock: """Test for filelocks.""" def test_lock_file_ok(self): """Work ok ok for random file.""" with tempfile.TemporaryFile() as fp: with lock_file(fp): pass def test_lock_file_error(self): """Fail on closed file.""" with tempfile.TemporaryFile() as fp: fp.close() with pytest.raises(ValueError): with lock_file(fp): pass def test_envelope_serialization(): """Test envelope serialization/deserialization with files.""" envelope = Envelope( to="to", sender="sender", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=b"", ) with tempfile.TemporaryDirectory() as temp_dir: output_file = Path(os.path.join(temp_dir, "output_file")) with output_file.open(mode="wb") as fout: write_envelope(envelope, fout) actual_envelope = envelope_from_bytes(output_file.read_bytes()) assert envelope == actual_envelope def test_decode_fails(): """Test decode fails.""" with pytest.raises( ValueError, match="Expected at least 5 values separated by commas and last value being empty or new line, got 1", ): _decode(b"") def test_envelope_from_bytes_bad_format(): """Test envelope_from_bytes, when the input has a bad format we raise ValueError.""" test_error_message = "Something bad happened." _bytes = b"" with patch( "aea.helpers.file_io._decode", side_effect=ValueError(test_error_message) ): with patch.object(aea.helpers.file_io._default_logger, "error") as mock_error: envelope_from_bytes(_bytes) mock_error.assert_called_with( f"Bad formatted input: {_bytes}. {test_error_message}" ) ================================================ FILE: tests/test_aea/test_helpers/test_install_dependency.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test helper to install python dependecies.""" from unittest import mock from unittest.case import TestCase import pytest from aea.configurations.base import Dependency from aea.exceptions import AEAException from aea.helpers.install_dependency import install_dependencies, install_dependency class InstallDependencyTestCase(TestCase): """Test case for _install_dependency method.""" def test__install_dependency_fails(self, *mocks): """Test for install_dependency method fails.""" result = mock.Mock() result.returncode = 1 with mock.patch("subprocess.run", return_value=result): with self.assertRaises(AEAException): install_dependency("test", Dependency("test", "==10.0.0"), mock.Mock()) def test__install_dependency_ok(self, *mocks): """Test for install_dependency method ok.""" result = mock.Mock() result.returncode = 0 with mock.patch("subprocess.run", return_value=result): install_dependency("test", Dependency("test", "==10.0.0"), mock.Mock()) def test__install_dependency_fails_real_pip_call(self): """Test for install_dependency method fails.""" with pytest.raises(AEAException, match=r"No matching distribution found"): install_dependency( "testnotexists", Dependency("testnotexists", "==10.0.0"), mock.Mock() ) class InstallDependenciesTestCase(TestCase): """Test case for _install_dependencies method.""" def test_fails(self, *mocks): """Test for install_dependency method fails.""" result = mock.Mock() result.returncode = 1 with mock.patch("subprocess.run", return_value=result): with self.assertRaises(AEAException): install_dependencies([Dependency("test", "==10.0.0")], mock.Mock()) def test_ok(self, *mocks): """Test for install_dependency method ok.""" result = mock.Mock() result.returncode = 0 with mock.patch("subprocess.run", return_value=result): install_dependencies([Dependency("test", "==10.0.0")], mock.Mock()) def test_fails_real_pip_call(self): """Test for install_dependency method fails.""" with pytest.raises(AEAException, match=r"No matching distribution found"): install_dependencies([Dependency("testnotexists", "==10.0.0")], mock.Mock()) """Test for install_dependency method fails.""" with pytest.raises(AEAException, match=r"No matching distribution found"): install_dependency( "testnotexists", Dependency("testnotexists", "==10.0.0"), mock.Mock() ) ================================================ FILE: tests/test_aea/test_helpers/test_io.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the 'aea.helpers.io' module.""" import os from pathlib import Path from unittest.mock import MagicMock import pytest from aea.helpers.io import open_file @pytest.mark.parametrize(argnames="path_builder", argvalues=[os.path.join, Path]) def test_open_file(change_directory, path_builder): """Test 'open_file' for the built-in open.""" expected_string = "hello\nworld" path = path_builder(change_directory, "temporary-file") with open_file(path, "w") as file_out: file_out.write(expected_string) with open_file(path, "r") as file_in: assert file_in.read() == expected_string with open(path, "rb") as bytes_in: assert bytes_in.read() == bytes(expected_string, encoding="utf-8") def test_raise_if_binary_mode(): """Raise if mode is binary mode.""" with pytest.raises(ValueError, match="This function can only work in text mode."): open_file(MagicMock(), mode="rb") ================================================ FILE: tests/test_aea/test_helpers/test_ipfs/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the ipfs helper module.""" ================================================ FILE: tests/test_aea/test_helpers/test_ipfs/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the ipfs helper module.""" import os from unittest.mock import patch from aea.helpers.ipfs.base import IPFSHashOnly, _is_text from tests.conftest import CUR_PATH FILE_PATH = "__init__.py" def test_get_hash(): """Test get hash IPFSHashOnly.""" ipfs_hash = IPFSHashOnly().get(file_path=os.path.join(CUR_PATH, FILE_PATH)) assert ipfs_hash == "QmVyhvd64oCVNqs4Vg2zn8WE6UsK1tk1jTW4GEopcUVyuH" def test_is_text_negative(): """Test the helper method 'is_text' negative case.""" # https://gehrcke.de/2015/12/how-to-raise-unicodedecodeerror-in-python-3/ with patch( "aea.helpers.ipfs.base.open_file", side_effect=UnicodeDecodeError("foo", b"bytes", 1, 2, "Fake reason"), ): assert not _is_text("path") def test_hash_for_big_file(): """Check hash is ok for big amount of data with chunks support.""" VALID_HASH = "QmWt5fanMr2JbiaUAUpyLUL8FegGn95t5tHA6kgobXgWX3" # from ipfs daemon data = b"1" * int(IPFSHashOnly.DEFAULT_CHUNK_SIZE * 1.5) my_hash = IPFSHashOnly._generate_hash(data) assert my_hash == VALID_HASH ================================================ FILE: tests/test_aea/test_helpers/test_logging.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers/logging module.""" import logging from unittest.mock import patch from aea.helpers.logging import AgentLoggerAdapter, WithLogger, get_logger def test_get_logger(): """Test the get_logger function.""" module_path = "some.dotted.module.path" agent_name = "agent_name" expected_name = "some.agent_name.dotted.module.path" logger = get_logger(module_path, agent_name) assert logger.name == expected_name def test_agent_logger_adapter(): """Test the agent logger adapter.""" logger = logging.getLogger("some.logger") logger = AgentLoggerAdapter(logger, agent_name="some_agent") logger.setLevel("DEBUG") with patch.object(logger.logger, "log") as mock_logger: logger.debug("Some log message.") mock_logger.assert_any_call(logging.DEBUG, "[some_agent] Some log message.") def test_with_logger_default_logger_name(): """Test the WithLogger interface, default logger name.""" class SomeClass(WithLogger): pass x = SomeClass() assert isinstance(x.logger, logging.Logger) with patch.object(x.logger, "debug") as mock_logger: x.logger.debug("Some log message.") mock_logger.assert_any_call("Some log message.") def test_with_logger_custom_logger_name(): """Test the WithLogger interface, custom logger name.""" class SomeClass(WithLogger): pass x = SomeClass(default_logger_name="some.logger") assert isinstance(x.logger, logging.Logger) with patch.object(x.logger, "debug") as mock_logger: x.logger.debug("Some log message.") mock_logger.assert_any_call("Some log message.") def test_with_logger_custom_logger(): """Test the WithLogger interface, custom logger.""" class SomeClass(WithLogger): pass logger = logging.getLogger("some.logger") x = SomeClass(logger=logger) assert isinstance(x.logger, logging.Logger) with patch.object(x.logger, "debug") as mock_logger: x.logger.debug("Some log message.") mock_logger.assert_any_call("Some log message.") def test_with_logger_setter(): """Test the WithLogger interface, logger setter.""" class SomeClass(WithLogger): pass logger_1 = logging.getLogger("some.logger") x = SomeClass(logger=logger_1) assert isinstance(x.logger, logging.Logger) assert x.logger.name == "some.logger" logger_2 = logging.getLogger("another.logger") x.logger = logger_2 assert x.logger.name == "another.logger" ================================================ FILE: tests/test_aea/test_helpers/test_multiaddr.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for MultiAddr helper class.""" import tempfile from shutil import rmtree import pytest from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.registries import make_crypto from aea.helpers.multiaddr.base import MultiAddr HOST = "127.0.0.1" PORT = 13000 PRIV_KEY = "b6dbe68a5b9bc135a3736a9b59892b6c806bf7594092de441f43e6d8609ea5fd" PEER_ID = "16Uiu2HAkw1VyY3RkiuMy38XKjb6w9EhbtXfwHkRpbQzNvXYVkG1T" def test_multiaddr_consistency(): """Test multiaddress consistency.""" key = make_crypto(DEFAULT_LEDGER) maddr1 = MultiAddr(HOST, PORT, key.public_key) tmpdir = tempfile.mkdtemp() key_file = tmpdir + "/key" key.dump(key_file) key2 = make_crypto(DEFAULT_LEDGER, private_key_path=key_file) maddr2 = MultiAddr(HOST, PORT, key2.public_key) rmtree(tmpdir) assert str(maddr1) == str(maddr2) assert maddr1.public_key == maddr2.public_key assert maddr1.peer_id == maddr2.peer_id def test_multiaddr_correctness(): """Test multiaddress correctness.""" tmpdir = tempfile.mkdtemp() key_file = tmpdir + "/key" with open(key_file, "w+") as k: k.write(PRIV_KEY) key = make_crypto(DEFAULT_LEDGER, private_key_path=key_file) maddr = MultiAddr(HOST, PORT, key.public_key) rmtree(tmpdir) assert maddr._peerid == PEER_ID def test_multiaddr_from_string(): """Test multiaddress from string""" maddr_str = "/dns4/" + HOST + "/tcp/" + str(PORT) + "/p2p/" maddr = MultiAddr.from_string(maddr_str + PEER_ID) assert maddr.host == HOST and maddr.port == PORT and maddr.peer_id == PEER_ID with pytest.raises(ValueError): MultiAddr.from_string("") with pytest.raises(ValueError): MultiAddr.from_string(maddr_str + "wrong-peer-id") ================================================ FILE: tests/test_aea/test_helpers/test_multiple_executor.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers.multiple_executor.""" import asyncio from asyncio.events import AbstractEventLoop from aea.helpers.multiple_executor import AbstractExecutorTask, TaskAwaitable class Task(AbstractExecutorTask): """Simple Executor Task for testing.""" def start(self): """Implement start task function here.""" pass def stop(self) -> None: """Implement stop task function here.""" pass def create_async_task(self, loop: AbstractEventLoop) -> TaskAwaitable: """ Create asyncio task for task run in asyncio loop. :param loop: the event loop :return: task to run in asyncio loop. """ pass def test_task_failed(): """Test task failed.""" task = Task() assert not task.failed task.future = asyncio.Future() assert not task.failed task.future.set_result(None) assert not task.failed task.future = asyncio.Future() task.future.set_exception(KeyboardInterrupt()) assert not task.failed task.future = asyncio.Future() task.future.set_exception(ValueError()) assert task.failed ================================================ FILE: tests/test_aea/test_helpers/test_pipe/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for portable IPC pipe module.""" ================================================ FILE: tests/test_aea/test_helpers/test_pipe/test_pipe.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for the pipe module.""" import asyncio from threading import Thread import pytest from aea.helpers.pipe import ( IPCChannelClient, PosixNamedPipeChannel, PosixNamedPipeChannelClient, TCPSocketChannel, TCPSocketChannelClient, make_ipc_channel, make_ipc_channel_client, ) from tests.conftest import skip_test_windows def _run_echo_service(client: IPCChannelClient): async def echo_service(client: IPCChannelClient): try: await client.connect() while True: data = await client.read() if not data: break await client.write(data) except (asyncio.IncompleteReadError, asyncio.CancelledError, OSError): pass finally: await client.close() loop = asyncio.new_event_loop() loop.run_until_complete(echo_service(client)) @pytest.mark.asyncio class TestAEAHelperMakePipe: """Test that make_ipc_channel utility and abstract class IPCChannel work properly""" @pytest.mark.asyncio async def test_connection_communication(self): """Test connection communication.""" pipe = make_ipc_channel() assert ( pipe.in_path is not None and pipe.out_path is not None ), "Pipe not properly setup" connected = asyncio.ensure_future(pipe.connect()) client_pipe = make_ipc_channel_client(pipe.out_path, pipe.in_path) client = Thread(target=_run_echo_service, args=[client_pipe]) client.start() try: assert await connected, "Failed to connect pipe" message = b"hello" await pipe.write(message) received = await pipe.read() assert received == message, "Echoed message differs" except Exception: raise finally: await pipe.close() client.join() @pytest.mark.asyncio class TestAEAHelperTCPSocketChannel: """Test that TCPSocketChannel work properly""" @pytest.mark.asyncio async def test_connection_communication(self): """Test connection communication.""" pipe = TCPSocketChannel() assert ( pipe.in_path is not None and pipe.out_path is not None ), "TCPSocketChannel not properly setup" connected = asyncio.ensure_future(pipe.connect()) client_pipe = TCPSocketChannelClient(pipe.out_path, pipe.in_path) client = Thread(target=_run_echo_service, args=[client_pipe]) client.start() try: assert await connected, "Failed to connect pipe" message = b"hello" await pipe.write(message) received = await pipe.read() assert received == message, "Echoed message differs" except Exception: raise finally: await pipe.close() client.join() @pytest.mark.asyncio async def test_connection_refused(self): """Test connection refused.""" pipe = TCPSocketChannel() assert ( pipe.in_path is not None and pipe.out_path is not None ), "TCPSocketChannel not properly setup" client_pipe = TCPSocketChannelClient(pipe.out_path, pipe.in_path) connected = await client_pipe.connect() assert connected is False def make_future(result) -> asyncio.Future: """Make future for value.""" f = asyncio.Future() # type: ignore f.set_result(result) return f @skip_test_windows @pytest.mark.asyncio class TestAEAHelperPosixNamedPipeChannel: """Test that TCPSocketChannel work properly""" @pytest.mark.asyncio async def test_connection_communication(self): """Test connection communication.""" pipe = PosixNamedPipeChannel() assert ( pipe.in_path is not None and pipe.out_path is not None ), "PosixNamedPipeChannel not properly setup" connected = asyncio.ensure_future(pipe.connect()) client_pipe = PosixNamedPipeChannelClient(pipe.out_path, pipe.in_path) client = Thread(target=_run_echo_service, args=[client_pipe]) client.start() try: assert await connected, "Failed to connect pipe" message = b"hello" await pipe.write(message) received = await pipe.read() assert received == message, "Echoed message differs" except Exception: raise finally: await pipe.close() client.join() ================================================ FILE: tests/test_aea/test_helpers/test_preference_representations/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the preference representations helper module.""" ================================================ FILE: tests/test_aea/test_helpers/test_preference_representations/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the preference representations helper module.""" from aea.helpers.preference_representations.base import ( linear_utility, logarithmic_utility, ) def test_logarithmic_utility(): """Test logarithmic utlity.""" assert ( logarithmic_utility( utility_params_by_good_id={"good_1": 0.2, "good_2": 0.8}, quantities_by_good_id={"good_1": 2, "good_2": 1}, ) > 0 ), "Utility should be positive." def test_linear_utility(): """Test logarithmic utlity.""" assert ( linear_utility( exchange_params_by_currency_id={"cur_1": 0.2, "cur_2": 0.8}, balance_by_currency_id={"cur_1": 20, "cur_2": 100}, ) > 0 ), "Utility should be positive." ================================================ FILE: tests/test_aea/test_helpers/test_profiling.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers/profiling module.""" from aea.helpers.profiling import Profiling from aea.protocols.base import Message from tests.common.utils import wait_for_condition def test_profiling(): """Test profiling tool.""" result = "" def output_function(report): nonlocal result result = report p = Profiling(1, [Message], [Message], output_function=output_function) p.start() wait_for_condition(lambda: p.is_running, timeout=20) m = Message() try: wait_for_condition(lambda: result, timeout=20) assert "Profiling details" in result finally: p.stop() p.wait_completed(sync=True, timeout=20) del m ================================================ FILE: tests/test_aea/test_helpers/test_search/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the search helper module.""" ================================================ FILE: tests/test_aea/test_helpers/test_search/base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the search helper module.""" from aea.helpers.search.models import Location def test_location_init(): """Test the initialization of the location model""" latitude = 51.507351 longitude = -0.127758 loc = Location(latitude, longitude) latitude_2 = 48.856613 longitude_2 = 2.352222 loc2 = Location(latitude_2, longitude_2) assert loc != loc2, "Locations should not be the same." assert loc.distance(loc2) > 0.0, "Locations should be positive." ================================================ FILE: tests/test_aea/test_helpers/test_search/test_generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers.search.generic.""" import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.generic import GenericDataModel def test_generic_data_model(): """Test generic data model creation.""" # ok GenericDataModel( "test", {"attr1": {"name": "attr1", "type": "str", "is_required": True}} ) # bad type with pytest.raises(AEAEnforceError): GenericDataModel( "test", {"attr1": {"name": "attr1", "type": "bad type", "is_required": True}}, ) # bad name with pytest.raises(AEAEnforceError): GenericDataModel( "test", {"attr1": {"name": 1231, "type": "str", "is_required": True}} ) # bad is required with pytest.raises(AEAEnforceError): GenericDataModel( "test", {"attr1": {"name": "attr1", "type": "str", "is_required": "True"}} ) ================================================ FILE: tests/test_aea/test_helpers/test_search/test_models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers.search.models.""" import re from unittest.mock import MagicMock import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.models import ( And, Attribute, AttributeInconsistencyException, Constraint, ConstraintExpr, ConstraintType, ConstraintTypes, DataModel, Description, Location, Not, Or, Query, generate_data_model, ) def test_location(): """Test Location type.""" location = Location(latitude=1.1, longitude=2.2) assert location is not None assert location.tuple == (location.latitude, location.longitude) assert location.distance(Location(1.1, 2.2)) == 0 assert location == Location(1.1, 2.2) assert str(location) == "Location(latitude=1.1,longitude=2.2)" location_pb = location.encode() actual_location = Location.decode(location_pb) assert actual_location == location assert location.latitude == actual_location.latitude assert location.longitude == actual_location.longitude def test_attribute(): """Test data model Attribute.""" params = dict(name="test", type_=str, is_required=True) attribute = Attribute(**params) assert attribute is not None assert attribute == Attribute(**params) assert attribute != Attribute(name="another", type_=int, is_required=True) assert str(attribute) == "Attribute(name=test,type=,is_required=True)" attribute_pb = attribute.encode() actual_attribute = Attribute.decode(attribute_pb) assert actual_attribute == attribute def test_data_model(): """Test data model definitions.""" params = dict(name="test", type_=str, is_required=True) data_model = DataModel("test", [Attribute(**params)]) data_model._check_validity() with pytest.raises( ValueError, match="Invalid input value for type 'DataModel': duplicated attribute name.", ): data_model = DataModel("test", [Attribute(**params), Attribute(**params)]) data_model._check_validity() assert data_model == DataModel("test", [Attribute(**params)]) assert data_model != DataModel("not test", [Attribute(**params)]) assert ( str(data_model) == "DataModel(name=test,attributes={'test': \"Attribute(name=test,type=,is_required=True)\"},description=)" ) data_model_pb = data_model.encode() actual_data_model = DataModel.decode(data_model_pb) assert actual_data_model == data_model def test_generate_data_model(): """Test model generated from description.""" params = dict(name="test", type_=str, is_required=True) data_model = DataModel("test", [Attribute(**params)]) assert generate_data_model("test", {"test": "str"}) == data_model def test_description(): """Test model description.""" values = { "test": "test_value", "bool": True, "float": 1.1, "int": int(1), "loc": Location(1, 1), } description = Description( values=values, data_model=generate_data_model("test", values) ) assert description.values == values assert description == Description( values=values, data_model=generate_data_model("test", values) ) assert list(description.values.values()) == list(values.values()) assert list(description) == list(values) with pytest.raises( AttributeInconsistencyException, match=r"Missing required attribute." ): Description( values=values, data_model=generate_data_model("test", {"extra_key": "key"}) ) with pytest.raises( AttributeInconsistencyException, match=r"Have extra attribute not in data model.", ): Description(values=values, data_model=generate_data_model("test", {})) with pytest.raises( AttributeInconsistencyException, match=r".* has incorrect type:.*" ): Description( values=values, data_model=generate_data_model("test", {**values, "test": 12}), ) with pytest.raises( AttributeInconsistencyException, match=r".* has unallowed type:.*" ): Description( values={"test": object()}, data_model=generate_data_model("test", {"test": object()}), ) assert re.match(r"Description\(values=.*data_model=.*", str(description)) description_pb = description._encode() actual_description = Description._decode(description_pb) assert actual_description == description mock = MagicMock() mock.description_bytes = None Description.encode(mock, description) assert mock.description_bytes is not None description = Description.decode(mock) assert "test" in description.values def test_constraint_type(): """Test ConstraintType.""" constraint_type_values = { "int": 12, "bool": True, "float": 10.4, "str": "some_string", "location": Location(1.1, 2.2), } constraint_type_types = { "int": int, "bool": bool, "float": float, "str": str, "location": Location, } to_check = { "int": 13, "bool": False, "float": 9.3, "str": "some_other_string", "location": Location(1.2, 2.3), } # = and != for constraint_types_type in [ConstraintTypes.EQUAL, ConstraintTypes.NOT_EQUAL]: for allowed_type in ["int", "bool", "float", "str"]: constraint_type = ConstraintType( constraint_types_type, constraint_type_values[allowed_type] ) constraint_type.is_valid( Attribute("test", constraint_type_types[allowed_type], True) ) constraint_type.check(to_check[allowed_type]) assert constraint_type == ConstraintType( constraint_types_type, constraint_type_values[allowed_type] ) assert ( str(constraint_type) == f"ConstraintType(value={constraint_type_values[allowed_type]},type={constraint_types_type})" ) constraint_type_pb = constraint_type.encode() actual_constraint_type = ConstraintType.decode( constraint_type_pb, "relation" ) assert actual_constraint_type == constraint_type # < and <= and > and >= for constraint_types_type in [ ConstraintTypes.LESS_THAN, ConstraintTypes.LESS_THAN_EQ, ConstraintTypes.GREATER_THAN, ConstraintTypes.GREATER_THAN_EQ, ]: for allowed_type in ["int", "float", "str"]: constraint_type = ConstraintType( constraint_types_type, constraint_type_values[allowed_type] ) constraint_type.is_valid( Attribute("test", constraint_type_types[allowed_type], True) ) constraint_type.check(to_check[allowed_type]) assert constraint_type == ConstraintType( constraint_types_type, constraint_type_values[allowed_type] ) assert ( str(constraint_type) == f"ConstraintType(value={constraint_type_values[allowed_type]},type={constraint_types_type})" ) constraint_type_pb = constraint_type.encode() actual_constraint_type = ConstraintType.decode( constraint_type_pb, "relation" ) assert actual_constraint_type == constraint_type # within constraint_type_values = { "int": (1, 2), "float": (2.4, 5.4), "str": ("str_1", "str_2"), "location": (Location(1.1, 2.2), Location(1.2, 5.2)), } constraint_type_types = { "int": int, "float": float, "str": str, "location": Location, } to_check = { "int": 13, "float": 9.3, "str": "some_other_string", "location": Location(1.2, 2.3), } for range_constraint_type in ["int", "float", "str"]: # location is not working constraint_type = ConstraintType( ConstraintTypes.WITHIN, constraint_type_values[range_constraint_type] ) constraint_type.is_valid( Attribute("test", constraint_type_types[range_constraint_type], True) ) constraint_type.check(to_check[range_constraint_type]) assert constraint_type == ConstraintType( ConstraintTypes.WITHIN, constraint_type_values[range_constraint_type] ) assert ( str(constraint_type) == f"ConstraintType(value={constraint_type_values[range_constraint_type]},type=within)" ) constraint_type_pb = constraint_type.encode() actual_constraint_type = ConstraintType.decode(constraint_type_pb, "range") assert actual_constraint_type == constraint_type # in and not_in constraint_type_values = { "int": (1, 2), "bool": (True, False), "float": (2.4, 5.4), "str": ("str_1", "str_2"), "location": (Location(1.1, 2.2), Location(1.2, 5.2)), } constraint_type_types = { "int": int, "bool": bool, "float": float, "str": str, "location": Location, } to_check = { "int": 13, "bool": False, "float": 9.3, "str": "some_other_string", "location": Location(1.2, 2.3), } for constraint_types_type in [ConstraintTypes.IN, ConstraintTypes.NOT_IN]: for constraint_set in ["int", "bool", "float", "str", "location"]: constraint_type = ConstraintType( constraint_types_type, constraint_type_values[constraint_set] ) constraint_type.is_valid( Attribute("test", constraint_type_types[constraint_set], True) ) constraint_type.check(to_check[constraint_set]) assert constraint_type == ConstraintType( constraint_types_type, constraint_type_values[constraint_set] ) assert ( str(constraint_type) == f"ConstraintType(value={constraint_type_values[constraint_set]},type={constraint_types_type})" ) constraint_type_pb = constraint_type.encode() actual_constraint_type = ConstraintType.decode(constraint_type_pb, "set") assert actual_constraint_type == constraint_type # distance constraint_location = (Location(1.1, 2.2), 2.2) constraint_type_distance = ConstraintType( ConstraintTypes.DISTANCE, constraint_location ) constraint_type_distance.is_valid(Attribute("test", int, True)) constraint_type_distance.check(Location(1.1, 2.2)) assert constraint_type_distance == ConstraintType( ConstraintTypes.DISTANCE, constraint_location ) constraint_type_distance_pb = constraint_type_distance.encode() actual_constraint_type_distance = ConstraintType.decode( constraint_type_distance_pb, "distance" ) assert actual_constraint_type_distance == constraint_type_distance # failures with pytest.raises(ValueError): ConstraintType("something", [Location(1.1, 2.2), 2.2]).is_valid( Attribute("test", int, True) ) with pytest.raises(AEAEnforceError, match=""): ConstraintType(ConstraintTypes.GREATER_THAN, str) list_value = [1, 2] set_value = {1, 2} list_location = [Location(1.1, 2.2), 2.2] with pytest.raises( AEAEnforceError, match=f"Expected tuple, got {type(list_value)}" ): ConstraintType(ConstraintTypes.WITHIN, list_value) with pytest.raises( AEAEnforceError, match=f"Expected tuple, got {type(list_value)}" ): ConstraintType(ConstraintTypes.IN, list_value) with pytest.raises(AEAEnforceError, match=f"Expected tuple, got {type(set_value)}"): ConstraintType(ConstraintTypes.IN, set_value) with pytest.raises( AEAEnforceError, match=f"Expected tuple, got {type(list_value)}" ): ConstraintType(ConstraintTypes.NOT_IN, list_value) with pytest.raises(AEAEnforceError, match=f"Expected tuple, got {type(set_value)}"): ConstraintType(ConstraintTypes.NOT_IN, set_value) with pytest.raises( AEAEnforceError, match=f"Expected tuple, got {type(list_location)}" ): ConstraintType(ConstraintTypes.DISTANCE, list_location) incorrect_category = "some_incorrect_category" with pytest.raises( ValueError, match=r"Incorrect category. Expected either of .* Found some_incorrect_category.", ): constraint_type_distance_pb = constraint_type_distance.encode() ConstraintType.decode(constraint_type_distance_pb, incorrect_category) def test_constraints_expression(): """Test constraint expressions: And, Or, Not, Constraint.""" and_expression = And( [ Constraint("number", ConstraintType(ConstraintTypes.LESS_THAN, 15)), Constraint("number", ConstraintType(ConstraintTypes.GREATER_THAN, 10)), ] ) and_expression.check_validity() assert and_expression.check(Description({"number": 12})) assert and_expression.is_valid( DataModel("some_name", [Attribute("number", int, True)]) ) and_expression_pb = ConstraintExpr._encode(and_expression) actual_and_expression = ConstraintExpr._decode(and_expression_pb) assert actual_and_expression == and_expression or_expression = Or( [ Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 12)), Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 13)), ] ) or_expression.check_validity() assert or_expression.check(Description({"number": 12})) assert or_expression.is_valid( DataModel("some_name", [Attribute("number", int, True)]) ) or_expression_pb = ConstraintExpr._encode(or_expression) actual_or_expression = ConstraintExpr._decode(or_expression_pb) assert actual_or_expression == or_expression not_expression = Not( And( [ Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 12)), Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 12)), ] ) ) not_expression.check_validity() assert not_expression.check(Description({"number": 13})) assert not_expression.is_valid( DataModel("some_name", [Attribute("number", int, True)]) ) not_expression_pb = ConstraintExpr._encode(not_expression) actual_not_expression = ConstraintExpr._decode(not_expression_pb) assert actual_not_expression == not_expression # constraint constraint_expression = Constraint("author", ConstraintType("==", "Stephen King")) constraint_expression.check_validity() assert constraint_expression.check(Description({"author": "Stephen King"})) assert constraint_expression.is_valid( DataModel("some_name", [Attribute("author", str, True)]) ) constraint_expression_pb = ConstraintExpr._encode(constraint_expression) actual_constraint_expression = ConstraintExpr._decode(constraint_expression_pb) assert actual_constraint_expression == constraint_expression incorrect_expression = Location(1.1, 2.2) with pytest.raises( ValueError, match=f"Invalid expression type. Expected either of 'And', 'Or', 'Not', 'Constraint'. Found {type(incorrect_expression)}.", ): ConstraintExpr._encode(incorrect_expression) def test_constraints_and(): """Test And.""" and_expression = And( [ Constraint("number", ConstraintType(ConstraintTypes.LESS_THAN, 15)), Constraint("number", ConstraintType(ConstraintTypes.GREATER_THAN, 10)), ] ) and_expression.check_validity() assert and_expression.check(Description({"number": 12})) assert and_expression.is_valid( DataModel("some_name", [Attribute("number", int, True)]) ) and_expression_pb = and_expression.encode() actual_and_expression = And.decode(and_expression_pb) assert actual_and_expression == and_expression def test_constraints_or(): """Test Or.""" or_expression = Or( [ Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 12)), Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 13)), ] ) or_expression.check_validity() assert or_expression.check(Description({"number": 12})) assert or_expression.is_valid( DataModel("some_name", [Attribute("number", int, True)]) ) or_expression_pb = or_expression.encode() actual_or_expression = Or.decode(or_expression_pb) assert actual_or_expression == or_expression def test_constraints_not(): """Test Not.""" not_expression = Not( And( [ Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 12)), Constraint("number", ConstraintType(ConstraintTypes.EQUAL, 12)), ] ) ) not_expression.check_validity() assert not_expression.check(Description({"number": 13})) assert not_expression.is_valid( DataModel("some_name", [Attribute("number", int, True)]) ) not_expression_pb = not_expression.encode() actual_not_expression = Not.decode(not_expression_pb) assert actual_not_expression == not_expression def test_constraint(): """Test Constraint.""" c1 = Constraint("author", ConstraintType("==", "Stephen King")) c2 = Constraint("year", ConstraintType("within", (2000, 2010))) c3 = Constraint("author", ConstraintType("in", ("Stephen King", "J. K. Rowling"))) c4 = Constraint( "author", ConstraintType("not_in", ("Stephen King", "J. K. Rowling")) ) c5 = Constraint("address", ConstraintType("distance", (Location(1.1, 2.2), 2.2))) book_1 = Description( {"author": "Stephen King", "year": 2005, "address": Location(1.1, 2.2)} ) book_2 = Description( {"author": "George Orwell", "year": 1948, "address": Location(1.1, 2.2)} ) assert c1.check(book_1) assert not c1.check(book_2) # empty description assert not c1.check(Description({})) # bad type assert not c1.check(Description({"author": 12})) # bad type assert not c2.check(Description({"author": 12})) assert c1.is_valid(generate_data_model("test", {"author": "some author"})) assert not c1.is_valid(generate_data_model("test", {"not_author": "some author"})) assert c1 == c1 assert c1 != c2 assert ( str(c1) == f"Constraint(attribute_name=author,constraint_type={c1.constraint_type})" ) assert ( str(c2) == f"Constraint(attribute_name=year,constraint_type={c2.constraint_type})" ) c1_pb = c1.encode() actual_c1 = Constraint.decode(c1_pb) assert actual_c1 == c1 c2_pb = c2.encode() actual_c2 = Constraint.decode(c2_pb) assert actual_c2 == c2 c3_pb = c3.encode() actual_c3 = Constraint.decode(c3_pb) assert actual_c3 == c3 c4_pb = c4.encode() actual_c4 = Constraint.decode(c4_pb) assert actual_c4 == c4 c5_pb = c5.encode() actual_c5 = Constraint.decode(c5_pb) assert actual_c5 == c5 def test_query(): """Test Query.""" c1 = Constraint("author", ConstraintType("==", "Stephen King")) model = generate_data_model("book_author", {"author": "author of the book"}) query = Query([c1], model=model) assert query.check( Description({"author": "Stephen King", "year": 1991, "genre": "horror"}) ) assert query.is_valid(generate_data_model("test", {"author": "some author"})) query.check_validity() with pytest.raises(ValueError, match=r"Constraints must be a list .*"): query = Query(c1) Query([]).check_validity() with pytest.raises( ValueError, match=r"Invalid input value for type 'Query': the query is not valid for the given data model.", ): Query( [c1], generate_data_model("test", {"notauthor": "not some author"}) ).check_validity() assert query == Query( [c1], model=generate_data_model("book_author", {"author": "author of the book"}) ) assert ( str(query) == f"Query(constraints=['Constraint(attribute_name=author,constraint_type=ConstraintType(value=Stephen King,type===))'],model={model})" ) query_pb = query._encode() actual_query = Query._decode(query_pb) assert actual_query == query query_pb = MagicMock() query_pb.query_bytes = None Query.encode(query_pb, query) assert query_pb.query_bytes is not None query = Query.decode(query_pb) assert "author" in query.model.attributes_by_name ================================================ FILE: tests/test_aea/test_helpers/test_serializers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers/serializers module.""" import pytest from aea.helpers.serializers import DictProtobufStructSerializer def test_encode_decode_i(): """Test encode decode logic.""" case = { "bool_true": True, "bool_False": False, "none": None, "float": 0.12, "int": 100, "str": "some string", "bytes": b"some bytes string", "empty dict": {}, "list_of_bytes": [b"1234", b"234234"], "list_of_ints": [1, 2, 3], "list_of_str": ["1234", "234234"], "list_of_floats": [1.1, 2.2, 3.0], "nested_dict": { "bool_true": True, "bool_False": False, "none": None, "float": 0.12, "int": 100, "str": "some string", "bytes": b"some bytes string", "empty dict": {}, "list_of_bytes": [b"1234", b"234234"], "list_of_ints": [1, 2, 3], "list_of_str": ["1234", "234234"], "list_of_floats": [1.1, 2.2, 3.0], "list": [{"a": 1, "b": {"c": 12}}, {"b": b"234", "x": [1, 2, 3]}], }, "list_of_dict": { "list": [{"a": 1, "b": {"c": 12}}, {"b": b"234", "x": [1, 2, 3]}] }, } encoded = DictProtobufStructSerializer.encode(case) assert isinstance(encoded, bytes) decoded = DictProtobufStructSerializer.decode(encoded) assert case == decoded def test_error_type_not_supported(): """Test type not supported.""" case = { "obj": object(), } with pytest.raises(Exception, match=r".*doesn't support dict value type.*"): DictProtobufStructSerializer.encode(case) def test_list_mixed_type_not_supported(): """Test list mixed type not supported.""" case = {"list": [1, 1.0, "test", {"a": False}]} with pytest.raises(Exception, match=r"Mixed data types in list are not allowed"): DictProtobufStructSerializer.encode(case) def test_encode_dict_is_deterministic(): """Check DictProtobufStructSerializer.encode result is the same for the same input data.""" data = dict(c=3, b=2, a=1) assert DictProtobufStructSerializer.encode( data ) == DictProtobufStructSerializer.encode(data) ================================================ FILE: tests/test_aea/test_helpers/test_storage.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for aea helpers storage code.""" import os import time import pytest from aea.helpers.storage.generic_storage import Storage class TestAsyncCollection: """Test async storage collection.""" @pytest.mark.asyncio async def test_collection(self): """Test collecton methods.""" s = Storage("sqlite://:memory:") s.start() await s.wait_connected() col = await s.get_collection("test_col") col2 = await s.get_collection("another_collection") obj_id = "1" obj_body = {"a": 12} await col.put(obj_id, {"x": 13}) await col.put(obj_id, obj_body) assert await col.find("a", 12) == [(obj_id, obj_body)] assert await col.get(obj_id) == obj_body assert await col2.get(obj_id) is None assert await col.get("not exists") is None assert await col.list() == [(obj_id, obj_body)] await col.remove(obj_id) assert await col.get(obj_id) is None s.stop() await s.wait_completed() class TestSyncCollection: """Test sync storage collection.""" def test_collection(self): """Test collecton methods.""" s = Storage("sqlite://:memory:", threaded=True) s.start() while not s.is_connected: time.sleep(0.01) obj_id = "1" obj_body = {"a": 12} col = s.get_sync_collection("test_col") col2 = s.get_sync_collection("another_collection") col.put(obj_id, {"x": 13}) col.put(obj_id, obj_body) assert col.find("a", 12) == [(obj_id, obj_body)] assert col.get(obj_id) == obj_body assert col2.get(obj_id) is None assert col.get("not exists") is None assert col.list() == [(obj_id, obj_body)] col.remove(obj_id) assert col.get(obj_id) is None s.stop() s.wait_completed(sync=True, timeout=5) class TestMisc: """Various tests.""" def test_invalid_col_name(self): """Test bad collection name raises exception.""" s = Storage("sqlite://:memory:", threaded=True) s.start() try: with pytest.raises(ValueError, match="Invalid collection name:"): s.get_sync_collection("invalid%2345346?^$$$ /// : collection name") finally: s.stop() s.wait_completed(sync=True, timeout=10) def test_unsupoported_backend(self): """Test unsupported backed raises exception.""" with pytest.raises( ValueError, match="Backend .* is not supported. Supported are" ): Storage._get_backend_instance("bad_back://test") if __name__ == "__main__": pytest.main([os.path.basename(__file__)]) ================================================ FILE: tests/test_aea/test_helpers/test_sym_link.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the sym link module.""" import os import shutil import tempfile from pathlib import Path from aea.helpers.sym_link import create_symlink def test_create_symlink(): """Test create_symlink method.""" t = Path(tempfile.mkdtemp()) cwd = os.getcwd() os.chdir(t) try: link = Path(os.path.join(t, "here")) target = Path(os.path.join(t, "test", "nested")) os.makedirs(target) create_symlink(link, target, t) assert os.path.islink(link) assert os.readlink("here") == os.path.join("test", "nested") finally: os.chdir(cwd) shutil.rmtree(t) ================================================ FILE: tests/test_aea/test_helpers/test_transaction/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the transaction helper module.""" ================================================ FILE: tests/test_aea/test_helpers/test_transaction/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the base module.""" import pytest from aea.configurations.constants import DEFAULT_LEDGER from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import ( RawMessage, RawTransaction, SignedMessage, SignedTransaction, State, Terms, TransactionDigest, TransactionReceipt, ) def test_init_terms(): """Test the terms object initialization.""" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": -10} quantities_by_good_id = {"good_1": 20} is_sender_payable_tx_fee = True nonce = "somestring" kwargs = {"key": "value"} fee_by_currency_id = {} terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, **kwargs, ) sender_hash = "9af02c24bdb18b73aad129291dc9eee008f9bcf62f5a6e91b5cb7427f146ca3b" counterparty_hash = ( "174c1321c0eb4a49bf99d783b56f4fc30d0ee558106454c56d1c0fad295ccc79" ) assert terms.ledger_id == ledger_id assert terms.sender_address == sender_addr assert terms.counterparty_address == counterparty_addr assert terms.amount_by_currency_id == amount_by_currency_id assert terms.quantities_by_good_id == quantities_by_good_id assert terms.is_sender_payable_tx_fee == is_sender_payable_tx_fee assert terms.nonce == nonce assert terms.kwargs == kwargs assert terms.fee_by_currency_id == fee_by_currency_id assert terms.id == sender_hash assert terms.sender_hash == sender_hash assert terms.counterparty_hash == counterparty_hash assert terms.currency_id == next(iter(amount_by_currency_id.keys())) assert str( terms ) == "Terms: ledger_id={}, sender_address={}, counterparty_address={}, amount_by_currency_id={}, quantities_by_good_id={}, is_sender_payable_tx_fee={}, nonce={}, fee_by_currency_id={}, kwargs={}".format( ledger_id, sender_addr, counterparty_addr, amount_by_currency_id, quantities_by_good_id, is_sender_payable_tx_fee, nonce, fee_by_currency_id, kwargs, ) assert terms == terms with pytest.raises(AEAEnforceError): terms.fee def test_init_terms_w_fee(): """Test the terms object initialization with fee.""" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": -10} quantities_by_good_id = {"good_1": 20} is_sender_payable_tx_fee = True nonce = "somestring" fee_by_currency_id = {"FET": 1} terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, fee_by_currency_id=fee_by_currency_id, ) new_counterparty_address = "CounterpartyAddressNew" terms.counterparty_address = new_counterparty_address assert terms.counterparty_address == new_counterparty_address assert terms.fee == next(iter(fee_by_currency_id.values())) assert terms.fee_by_currency_id == fee_by_currency_id assert terms.counterparty_payable_amount == 0 assert terms.sender_payable_amount == -next(iter(amount_by_currency_id.values())) assert terms.sender_payable_amount_incl_fee == -next( iter(amount_by_currency_id.values()) ) + next(iter(fee_by_currency_id.values())) assert terms.sender_fee == next(iter(fee_by_currency_id.values())) assert terms.counterparty_fee == 0 def test_init_terms_w_fee_counterparty(): """Test the terms object initialization with fee.""" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": 10} quantities_by_good_id = {"good_1": -20} is_sender_payable_tx_fee = False nonce = "somestring" fee_by_currency_id = {"FET": 1} terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, fee_by_currency_id=fee_by_currency_id, ) new_counterparty_address = "CounterpartyAddressNew" terms.counterparty_address = new_counterparty_address assert terms.counterparty_address == new_counterparty_address assert terms.fee == next(iter(fee_by_currency_id.values())) assert terms.fee_by_currency_id == fee_by_currency_id assert terms.counterparty_payable_amount == next( iter(amount_by_currency_id.values()) ) assert terms.counterparty_payable_amount_incl_fee == next( iter(amount_by_currency_id.values()) ) + next(iter(fee_by_currency_id.values())) assert terms.sender_payable_amount == 0 assert terms.sender_fee == 0 assert terms.counterparty_fee == next(iter(fee_by_currency_id.values())) def test_init_terms_strict_positive(): """Test the terms object initialization in strict mode.""" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": -10} quantities_by_good_id = {"good_1": 20} is_sender_payable_tx_fee = True nonce = "somestring" assert Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, is_strict=True, ) def test_init_terms_strict_negative(): """Test the terms object initialization in strict mode.""" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": 10} quantities_by_good_id = {"good_1": 20} is_sender_payable_tx_fee = True nonce = "somestring" with pytest.raises(AEAEnforceError): Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, is_strict=True, ) def test_init_terms_multiple_goods(): """Test the terms object initialization with multiple goods.""" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": -10} quantities_by_good_id = {"good_1": 20, "good_2": -10} is_sender_payable_tx_fee = True nonce = "somestring" terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, ) assert ( terms.id == "f81812773f5242d0cb52cfa82bc08bdba8d17b1e56e2cf02b3056749184e198c" ) def test_init_terms_no_amount_and_quantity(): """Test the terms object initialization with no amount.""" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {} quantities_by_good_id = {} nonce = "somestring" terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, nonce=nonce, ) new_counterparty_address = "CounterpartyAddressNew" terms.counterparty_address = new_counterparty_address assert terms.counterparty_address == new_counterparty_address assert not terms.has_fee assert terms.counterparty_payable_amount == 0 assert terms.counterparty_payable_amount_incl_fee == 0 assert terms.sender_payable_amount == 0 assert terms.sender_payable_amount_incl_fee == 0 def test_terms_encode_decode(): """Test encoding and decoding of terms.""" class TermsProtobufObject: terms_bytes = b"" ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": -10} quantities_by_good_id = {"good_1": 20} is_sender_payable_tx_fee = True nonce = "somestring" terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, counterparty_address=counterparty_addr, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, is_strict=True, ) Terms.encode(TermsProtobufObject, terms) recovered_terms = Terms.decode(TermsProtobufObject) assert terms == recovered_terms def test_init_raw_transaction(): """Test the raw_transaction object initialization.""" ledger_id = "some_ledger" body = {"body": "value"} rt = RawTransaction(ledger_id, body) assert rt.ledger_id == ledger_id assert rt.body == body assert str(rt) == "RawTransaction: ledger_id=some_ledger, body={'body': 'value'}" assert rt == rt def test_raw_transaction_encode_decode(): """Test encoding and decoding of terms.""" class RawTransactionProtobufObject: raw_transaction_bytes = b"" ledger_id = "some_ledger" body = {"body": "value"} rt = RawTransaction(ledger_id, body) RawTransaction.encode(RawTransactionProtobufObject, rt) recovered_rt = RawTransaction.decode(RawTransactionProtobufObject) assert rt == recovered_rt def test_init_raw_message(): """Test the raw_message object initialization.""" ledger_id = "some_ledger" body = b"body" rm = RawMessage(ledger_id, body) assert rm.ledger_id == ledger_id assert rm.body == body assert not rm.is_deprecated_mode assert ( str(rm) == f"RawMessage: ledger_id=some_ledger, body={body}, is_deprecated_mode=False" ) assert rm == rm def test_raw_message_encode_decode(): """Test encoding and decoding of raw_message.""" class RawMessageProtobufObject: raw_message_bytes = b"" ledger_id = "some_ledger" body = b"body" rm = RawMessage(ledger_id, body) RawMessage.encode(RawMessageProtobufObject, rm) recovered_rm = RawMessage.decode(RawMessageProtobufObject) assert rm == recovered_rm def test_init_signed_transaction(): """Test the signed_transaction object initialization.""" ledger_id = "some_ledger" body = {"key": "value"} st = SignedTransaction(ledger_id, body) assert st.ledger_id == ledger_id assert st.body == body assert str(st) == "SignedTransaction: ledger_id=some_ledger, body={'key': 'value'}" assert st == st def test_signed_transaction_encode_decode(): """Test encoding and decoding of signed_transaction.""" class SignedTransactionProtobufObject: signed_transaction_bytes = b"" ledger_id = "some_ledger" body = {"key": "value"} st = SignedTransaction(ledger_id, body) SignedTransaction.encode(SignedTransactionProtobufObject, st) recovered_st = SignedTransaction.decode(SignedTransactionProtobufObject) assert st == recovered_st def test_init_signed_message(): """Test the signed_message object initialization.""" ledger_id = "some_ledger" body = "body" sm = SignedMessage(ledger_id, body) assert sm.ledger_id == ledger_id assert sm.body == body assert not sm.is_deprecated_mode assert ( str(sm) == "SignedMessage: ledger_id=some_ledger, body=body, is_deprecated_mode=False" ) assert sm == sm def test_signed_message_encode_decode(): """Test encoding and decoding of signed_message.""" class SignedMessageProtobufObject: signed_message_bytes = b"" ledger_id = "some_ledger" body = "body" sm = SignedMessage(ledger_id, body) SignedMessage.encode(SignedMessageProtobufObject, sm) recovered_sm = SignedMessage.decode(SignedMessageProtobufObject) assert sm == recovered_sm def test_init_transaction_receipt(): """Test the transaction_receipt object initialization.""" ledger_id = "some_ledger" receipt = {"receipt": "v"} transaction = {"transaction": "v"} tr = TransactionReceipt(ledger_id, receipt, transaction) assert tr.ledger_id == ledger_id assert tr.receipt == receipt assert tr.transaction == transaction assert ( str(tr) == f"TransactionReceipt: ledger_id={ledger_id}, receipt={receipt}, transaction={transaction}" ) assert tr == tr def test_transaction_receipt_encode_decode(): """Test encoding and decoding of transaction_receipt.""" class TransactionReceiptProtobufObject: transaction_receipt_bytes = b"" ledger_id = "some_ledger" receipt = {"receipt": "v"} transaction = {"transaction": "v"} tr = TransactionReceipt(ledger_id, receipt, transaction) TransactionReceipt.encode(TransactionReceiptProtobufObject, tr) recovered_tr = TransactionReceipt.decode(TransactionReceiptProtobufObject) assert tr == recovered_tr def test_init_state(): """Test the state object initialization.""" ledger_id = "some_ledger" body = {"state": "v"} state = State(ledger_id, body) assert state.ledger_id == ledger_id assert state.body == body assert str(state) == f"State: ledger_id={ledger_id}, body={body}" assert state == state def test_state_encode_decode(): """Test encoding and decoding of state.""" class StateProtobufObject: state_bytes = b"" ledger_id = "some_ledger" body = {"state": "v"} state = State(ledger_id, body) State.encode(StateProtobufObject, state) recovered_state = State.decode(StateProtobufObject) assert state == recovered_state def test_init_transaction_digest(): """Test the transaction_digest object initialization.""" ledger_id = "some_ledger" body = "digest" td = TransactionDigest(ledger_id, body) assert td.ledger_id == ledger_id assert td.body == body assert str(td) == "TransactionDigest: ledger_id={}, body={}".format(ledger_id, body) assert td == td def test_transaction_digest_encode_decode(): """Test encoding and decoding of transaction_digest.""" class TransactionDigestProtobufObject: transaction_digest_bytes = b"" ledger_id = "some_ledger" body = "digest" td = TransactionDigest(ledger_id, body) TransactionDigest.encode(TransactionDigestProtobufObject, td) recovered_td = TransactionDigest.decode(TransactionDigestProtobufObject) assert td == recovered_td ================================================ FILE: tests/test_aea/test_helpers/test_yaml_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the yaml utils module.""" import io import random import string from collections import OrderedDict from aea.helpers.yaml_utils import ( _AEAYamlLoader, yaml_dump, yaml_dump_all, yaml_load, yaml_load_all, ) def test_yaml_dump_load(): """Test yaml dump/load works.""" data = OrderedDict({"a": 12, "b": None}) stream = io.StringIO() yaml_dump(data, stream) stream.seek(0) loaded_data = yaml_load(stream) assert loaded_data == data def test_yaml_dump_all_load_all(): """Test yaml_dump_all and yaml_load_all.""" f = io.StringIO() data = [{"a": "12"}, {"b": "13"}] yaml_dump_all(data, f) f.seek(0) assert yaml_load_all(f) == data def test_instantiate_loader_twice(): """Test that instantiating the AEA YAML loader twice doesn't add twice implicit resolvers.""" loader = _AEAYamlLoader(io.StringIO()) old_length = len(loader.yaml_implicit_resolvers) loader = _AEAYamlLoader(io.StringIO()) assert len(loader.yaml_implicit_resolvers) == old_length def _generate_random_string(n: int = 100): return "".join( random.choice(string.ascii_uppercase + string.digits) for _ in range(n) # nosec ) ================================================ FILE: tests/test_aea/test_identity/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the identity module.""" ================================================ FILE: tests/test_aea/test_identity/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the identity module.""" import pytest from aea_ledger_fetchai import FetchAICrypto from aea.configurations.constants import DEFAULT_LEDGER from aea.exceptions import AEAEnforceError from aea.identity.base import Identity def test_init_identity_positive(): """Test initialization of the identity object.""" assert Identity("some_name", address="some_address", public_key="some_public_key") assert Identity( "some_name", address="some_address", public_key="some_public_key", default_address_key=DEFAULT_LEDGER, ) assert Identity( "some_name", addresses={ DEFAULT_LEDGER: "some_address", FetchAICrypto.identifier: "some_address", }, public_keys={ DEFAULT_LEDGER: "some_public_key", FetchAICrypto.identifier: "some_public_key", }, ) assert Identity( "some_name", addresses={ DEFAULT_LEDGER: "some_address", FetchAICrypto.identifier: "some_address", }, public_keys={ DEFAULT_LEDGER: "some_public_key", FetchAICrypto.identifier: "some_public_key", }, default_address_key=DEFAULT_LEDGER, ) def test_init_identity_negative(): """Test initialization of the identity object.""" name = "some_name" address_1 = "some_address" addresses_1 = {"some_ledger_id": "some_address"} addresses_2 = {} public_key_1 = "some_public_key" public_keys_1 = {"some_ledger_id": "some_public_key"} public_keys_2 = {} with pytest.raises(ValueError, match="Provide a key for the default address."): Identity(name, default_address_key=None) with pytest.raises( ValueError, match="Either provide a single address or a dictionary of addresses, and not both.", ): Identity(name) with pytest.raises( ValueError, match="Either provide a single address or a dictionary of addresses, and not both.", ): Identity(name, address=address_1, addresses=addresses_1) with pytest.raises(ValueError, match="Provide at least one pair of addresses."): Identity(name, addresses=addresses_2) with pytest.raises( ValueError, match="If you provide a dictionary of addresses, you must provide its corresponding dictionary of public keys.", ): Identity(name, addresses=addresses_1) with pytest.raises( ValueError, match="If you provide a dictionary of addresses, you must not provide a single public key.", ): Identity(name, addresses=addresses_1, public_key=public_key_1) with pytest.raises( AEAEnforceError, match="Keys in public keys and addresses dictionaries do not match. They must be identical.", ): Identity(name, addresses=addresses_1, public_keys=public_keys_2) with pytest.raises( AEAEnforceError, match="The default address key must exist in both addresses and public keys dictionaries.", ): Identity( name, addresses=addresses_1, public_keys=public_keys_1, default_address_key="some_other_ledger", ) with pytest.raises( ValueError, match="If you provide a single address, you must not provide a dictionary of public keys.", ): Identity(name, address=address_1, public_keys=public_keys_1) with pytest.raises( ValueError, match="If you provide a single address, you must provide its corresponding public key.", ): Identity(name, address=address_1) def test_accessors(): """Test the properties of the identity object.""" name = "some_name" address = "some_address" public_key = "some_public_key" identity = Identity(name, address=address, public_key=public_key) assert identity.name == name assert identity.address == address assert identity.addresses == {DEFAULT_LEDGER: address} assert identity.public_key == public_key assert identity.public_keys == {DEFAULT_LEDGER: public_key} assert identity.default_address_key == DEFAULT_LEDGER ================================================ FILE: tests/test_aea/test_launcher.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea launcher.""" import shutil import time from multiprocessing import Event from pathlib import Path from threading import Thread from unittest.mock import patch import pytest import yaml from aea.configurations.base import DEFAULT_AEA_CONFIG_FILE from aea.launcher import AEADirMultiprocessTask, AEALauncher, _run_agent from aea.test_tools.test_cases import AEATestCaseMany from tests.common.utils import wait_for_condition from tests.conftest import CUR_PATH class TestThreadLauncherMode(AEATestCaseMany): """Test launcher in threaded mode.""" RUNNER_MODE = "threaded" agent_name_1 = "myagent_1" agent_name_2 = "myagent_2" failing_agent = "failing_agent" @classmethod def setup_class(cls): """Set the test up.""" super(AEATestCaseMany, cls).setup_class() cls.create_agents(cls.agent_name_1, cls.agent_name_2, cls.failing_agent) cls.set_agent_context(cls.failing_agent) shutil.copytree( Path(CUR_PATH, "data", "exception_skill"), Path(cls.t, cls.failing_agent, "skills", "exception"), ) config_path = Path(cls.t, cls.failing_agent, DEFAULT_AEA_CONFIG_FILE) with open(config_path) as fp: config = yaml.safe_load(fp) config.setdefault("skills", []).append("fetchai/exception:0.1.0") yaml.safe_dump(config, open(config_path, "w")) cls.unset_agent_context() for agent_name in (cls.agent_name_1, cls.agent_name_2, cls.failing_agent): cls.set_agent_context(agent_name) cls.generate_private_key() cls.add_private_key() cls.set_runtime_mode_to_async(agent_name) cls.unset_agent_context() @classmethod def set_runtime_mode_to_async(cls, agent_name: str) -> None: """Set runtime mode of the agent to async.""" config_path = Path(cls.t, agent_name, DEFAULT_AEA_CONFIG_FILE) with open(config_path) as fp: config = yaml.safe_load(fp) config.setdefault("runtime_mode", "async") with open(config_path, "w") as fp: yaml.safe_dump(config, fp) def test_start_stop(self, capfd, caplog) -> None: """Test agents started stopped.""" try: runner = AEALauncher( [self.agent_name_1, self.agent_name_2], self.RUNNER_MODE, log_level="DEBUG", ) runner.start(True) wait_for_condition(lambda: runner.is_running, timeout=10) capfd_out = "" def _check(): nonlocal capfd_out # to accumulate logs from capfd to chgck all the logs captured. capfd_out += capfd.readouterr().out log_text = capfd_out + "\n".join(caplog.messages) return ( f"[{self.agent_name_1}]: Runtime state changed to RuntimeStates.running." in log_text and f"[{self.agent_name_2}]: Runtime state changed to RuntimeStates.running." in log_text ) wait_for_condition(_check, timeout=10, period=0.1) assert runner.num_failed == 0 finally: runner.stop() assert not runner.is_running assert runner.num_failed == 0 def test_one_fails(self) -> None: """Test agents started, one agent failed, exception is raised.""" try: runner = AEALauncher( [self.agent_name_1, self.agent_name_2, self.failing_agent], self.RUNNER_MODE, ) with pytest.raises(Exception, match="Expected exception!"): runner.start() finally: runner.stop() def test_run_agent_in_thread(self): """Test agent started and stopped in thread.""" stop_event = Event() t = Thread(target=_run_agent, args=(self.agent_name_1, stop_event)) t.start() time.sleep(1) stop_event.set() t.join() class TestAsyncLauncherMode(TestThreadLauncherMode): """Test launcher in async mode.""" RUNNER_MODE = "async" class TestProcessLauncherMode(TestThreadLauncherMode): """Test launcher in process mode.""" RUNNER_MODE = "multiprocess" def test_task_stop(): """Test AEADirMultiprocessTask.stop when not started.""" task = AEADirMultiprocessTask("some") assert not task.failed with patch.object(task._stop_event, "set") as set_mock: task.stop() set_mock.assert_not_called() ================================================ FILE: tests/test_aea/test_mail/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.mail.""" ================================================ FILE: tests/test_aea/test_mail/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for Envelope of mail.base.py.""" import unittest.mock import pytest import aea from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError from aea.mail import base_pb2 from aea.mail.base import Envelope, EnvelopeContext, ProtobufEnvelopeSerializer, URI from aea.multiplexer import InBox, Multiplexer, OutBox from aea.protocols.base import Message from packages.fetchai.connections.local.connection import LocalNode from packages.fetchai.protocols.default.message import DefaultMessage from tests.common.utils import wait_for_condition from tests.conftest import _make_dummy_connection, _make_local_connection def test_uri(): """Testing the uri initialisation.""" uri_raw = "http://user:pwd@NetLoc:80/path;param?query=arg#frag" uri = URI(uri_raw=uri_raw) assert uri_raw == str(uri) assert uri.scheme == "http" assert uri.netloc == "user:pwd@NetLoc:80" assert uri.path == "/path" assert uri.params == "param" assert uri.query == "query=arg" assert uri.fragment == "frag" assert uri.host == "netloc" assert uri.port == 80 assert uri.username == "user" assert uri.password == "pwd" # nosec def test_uri_eq(): """Testing the uri __eq__ function.""" uri_raw = "http://user:pwd@NetLoc:80/path;param?query=arg#frag" uri = URI(uri_raw=uri_raw) assert uri == uri def test_envelope_initialisation(): """Testing the envelope initialisation.""" agent_address = "Agent0" receiver_address = "Agent1" msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="hello" ) msg.to = receiver_address envelope = Envelope( to=receiver_address, sender=agent_address, message=msg, ) assert envelope, "Cannot generate a new envelope" envelope.to = "ChangedAgent" envelope.sender = "ChangedSender" envelope.message = b"HelloWorld" assert envelope.to == "ChangedAgent", "Cannot set to value on Envelope" assert envelope.sender == "ChangedSender", "Cannot set sender value on Envelope" assert envelope.message == b"HelloWorld", "Cannot set message on Envelope" assert envelope.context is None assert not envelope.is_sender_public_id assert not envelope.is_to_public_id def test_inbox_empty(): """Tests if the inbox is empty.""" multiplexer = Multiplexer([_make_dummy_connection()]) _inbox = InBox(multiplexer) assert _inbox.empty(), "Inbox is not empty" def test_inbox_nowait(): """Tests the inbox without waiting.""" agent_address = "Agent0" receiver_address = "Agent1" msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="hello" ) msg.to = receiver_address multiplexer = Multiplexer([_make_dummy_connection()]) envelope = Envelope( to=receiver_address, sender=agent_address, message=msg, ) multiplexer.in_queue.put(envelope) inbox = InBox(multiplexer) assert ( inbox.get_nowait() == envelope ), "Check for a message on the in queue and wait for no time." def test_inbox_get(): """Tests for a envelope on the in queue.""" agent_address = "Agent0" receiver_address = "Agent1" msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="hello" ) msg.to = receiver_address multiplexer = Multiplexer([_make_dummy_connection()]) envelope = Envelope( to=receiver_address, sender=agent_address, message=msg, ) multiplexer.in_queue.put(envelope) inbox = InBox(multiplexer) assert ( inbox.get() == envelope ), "Checks if the returned envelope is the same with the queued envelope." def test_inbox_get_raises_exception_when_empty(): """Test that getting an envelope from an empty inbox raises an exception.""" multiplexer = Multiplexer([_make_dummy_connection()]) inbox = InBox(multiplexer) with pytest.raises(aea.mail.base.Empty): with unittest.mock.patch.object(multiplexer, "get", return_value=None): inbox.get() def test_inbox_get_nowait_returns_none(): """Test that getting an envelope from an empty inbox returns None.""" # TODO get_nowait in this case should raise an exception, like it's done in queue.Queue multiplexer = Multiplexer([_make_dummy_connection()]) inbox = InBox(multiplexer) assert inbox.get_nowait() is None def test_outbox_put(): """Tests that an envelope is putted into the queue.""" agent_address = "Agent0" receiver_address = "Agent1" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = receiver_address msg.sender = agent_address dummy_connection = _make_dummy_connection() multiplexer = Multiplexer([dummy_connection]) outbox = OutBox(multiplexer) inbox = InBox(multiplexer) multiplexer.connect() wait_for_condition( lambda: dummy_connection.is_connected, 15, "Connection is not connected" ) envelope = Envelope( to=receiver_address, sender=agent_address, message=msg, ) outbox.put(envelope) wait_for_condition( lambda: inbox.empty(), 15, "Inbox must not be empty after putting an envelope" ) multiplexer.disconnect() def test_outbox_put_message(): """Tests that an envelope is created from the message is in the queue.""" agent_address = "Agent0" receiver_address = "Agent1" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = receiver_address msg.sender = agent_address dummy_connection = _make_dummy_connection() multiplexer = Multiplexer([dummy_connection]) outbox = OutBox(multiplexer) inbox = InBox(multiplexer) multiplexer.connect() wait_for_condition( lambda: multiplexer.is_connected, 15, "Multiplexer is not connected" ) outbox.put_message(msg) wait_for_condition( lambda: inbox.empty(), 15, "Inbox must not be empty after putting a message" ) multiplexer.disconnect() def test_outbox_empty(): """Test thet the outbox queue is empty.""" dummy_connection = _make_dummy_connection() multiplexer = Multiplexer([dummy_connection]) multiplexer.connect() outbox = OutBox(multiplexer) assert outbox.empty(), "The outbox is not empty" multiplexer.disconnect() def test_multiplexer(): """Tests if the multiplexer is connected.""" with LocalNode() as node: address_1 = "address_1" public_key_1 = "public_key_1" oef_local_connection = _make_local_connection(address_1, public_key_1, node) multiplexer = Multiplexer([oef_local_connection]) multiplexer.connect() assert ( multiplexer.is_connected ), "Mailbox cannot connect to the specific Connection(OEFLocalConnection)" multiplexer.disconnect() def test_envelope_fails_on_message_empty_protocol_specification_id(): """Check message.protocol_specification_id.""" class BadMessage(Message): protocol_id = "some/some:0.1.0" message = BadMessage() with pytest.raises(ValueError): Envelope(message=message, to="1", sender="1") def test_protobuf_envelope_serializer(): """Test Protobuf envelope serializer.""" serializer = ProtobufEnvelopeSerializer() # connection id is None because it is not included in the encoded envelope envelope_context = EnvelopeContext(connection_id=None, uri=URI("/uri")) expected_envelope = Envelope( to="to", sender="sender", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=b"message", context=envelope_context, ) encoded_envelope = serializer.encode(expected_envelope) actual_envelope = serializer.decode(encoded_envelope) assert actual_envelope == expected_envelope def test_envelope_serialization(): """Test Envelope.encode and Envelope.decode methods.""" expected_envelope = Envelope( to="to", sender="sender", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=b"message", ) encoded_envelope = expected_envelope.encode() actual_envelope = Envelope.decode(encoded_envelope) assert actual_envelope == expected_envelope def test_envelope_message_bytes(): """Test the property Envelope.message_bytes.""" message = DefaultMessage(DefaultMessage.Performative.BYTES, content=b"message") envelope = Envelope( to="to", sender="sender", message=message, ) expected_message_bytes = message.encode() actual_message_bytes = envelope.message_bytes assert expected_message_bytes == actual_message_bytes def test_envelope_context_connection_id(): """Test the property EnvelopeContext.connection_id.""" connection_id = PublicId("author", "skill_name", "0.1.0") envelope_context = EnvelopeContext() envelope_context.connection_id = connection_id assert envelope_context.connection_id == connection_id def test_envelope_context_is_None_for_c2c(): """Test the Envelope context is None for c2c.""" envelope_context = EnvelopeContext( connection_id=PublicId.from_str("author/connection_name:0.1.0") ) message = DefaultMessage(DefaultMessage.Performative.BYTES, content=b"message") with pytest.raises( AEAEnforceError, match="EnvelopeContext must be None for component to component messages.", ): Envelope( to="some_author/some_name:0.1.0", sender="some_author/some_name:0.1.0", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=message, context=envelope_context, ) def test_envelope_to_as_public_id(): """Test the Envelope to_as_public_id method.""" env = Envelope( to="some_author/some_name:0.1.0", sender="some_author/some_name:0.1.0", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=b"message", ) assert env.to_as_public_id is not None def test_envelope_constructor(): """Test Envelope constructor checks.""" Envelope( to="to", sender="sender", message=DefaultMessage(performative=DefaultMessage.Performative.BYTES), ) Envelope( to="to", sender="sender", message=b"", protocol_specification_id=DefaultMessage.protocol_specification_id, ) with pytest.raises( AEAEnforceError, match="message should be a type of Message or bytes!" ): Envelope(to="to", sender="sender", message=123) with pytest.raises( Exception, match=r"Message is bytes object, protocol_specification_id must be provided!", ): Envelope(to="asd", sender="asdasd", message=b"sdfdf") def test_envelope_context_repr(): """Check repr for EnvelopeContext.""" assert str(EnvelopeContext(1, 2)) == "EnvelopeContext(connection_id=1, uri=2)" def test_envelope_specification_id_translated(): """Test protocol id to protocol specification id translation and back.""" protocol_specification_id = PublicId("author", "specification", "0.1.0") envelope = Envelope( to="to", sender="sender", protocol_specification_id=protocol_specification_id, message=b"", ) assert envelope.protocol_specification_id == protocol_specification_id envelope_bytes = envelope.encode() envelope_pb = base_pb2.Envelope() envelope_pb.ParseFromString(envelope_bytes) new_envelope = Envelope.decode(envelope_bytes) assert new_envelope.protocol_specification_id == envelope.protocol_specification_id ================================================ FILE: tests/test_aea/test_multiplexer.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the Multiplexer.""" import asyncio import logging import os import shutil import sys import tempfile import time import unittest.mock from pathlib import Path from threading import Thread from unittest import mock from unittest.mock import MagicMock, Mock, call, patch import pytest from pexpect.exceptions import EOF # type: ignore import aea from aea.cli.core import cli from aea.configurations.constants import DEFAULT_LEDGER from aea.connections.base import ConnectionStates from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.identity.base import Identity from aea.mail.base import AEAConnectionError, Envelope, EnvelopeContext from aea.multiplexer import AsyncMultiplexer, InBox, Multiplexer, OutBox from aea.test_tools.click_testing import CliRunner from packages.fetchai.connections.local.connection import LocalNode from packages.fetchai.connections.p2p_libp2p.connection import ( PUBLIC_ID as P2P_PUBLIC_ID, ) from packages.fetchai.connections.stub.connection import PUBLIC_ID as STUB_CONNECTION_ID from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from ..conftest import ( AUTHOR, CLI_LOG_OPTION, ROOT_DIR, UNKNOWN_CONNECTION_PUBLIC_ID, UNKNOWN_PROTOCOL_PUBLIC_ID, _make_dummy_connection, _make_local_connection, _make_stub_connection, logger, ) from tests.common.pexpect_popen import PexpectWrapper from tests.common.utils import wait_for_condition UnknownProtocolMock = Mock() UnknownProtocolMock.protocol_id = UNKNOWN_PROTOCOL_PUBLIC_ID UnknownProtocolMock.protocol_specification_id = UNKNOWN_PROTOCOL_PUBLIC_ID @pytest.mark.asyncio async def test_receiving_loop_terminated(): """Test that connecting twice the multiplexer behaves correctly.""" multiplexer = Multiplexer([_make_dummy_connection()]) multiplexer.connect() with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: multiplexer.connection_status.set(ConnectionStates.disconnected) await multiplexer._receiving_loop() mock_logger_debug.assert_called_with("Receiving loop terminated.") multiplexer.connection_status.set(ConnectionStates.connected) multiplexer.disconnect() def test_connect_twice(): """Test that connecting twice the multiplexer behaves correctly.""" multiplexer = Multiplexer([_make_dummy_connection()]) assert not multiplexer.connection_status.is_connected multiplexer.connect() assert multiplexer.connection_status.is_connected multiplexer.connect() assert multiplexer.connection_status.is_connected multiplexer.disconnect() def test_disconnect_twice(): """Test that connecting twice the multiplexer behaves correctly.""" multiplexer = Multiplexer([_make_dummy_connection()]) assert not multiplexer.connection_status.is_connected multiplexer.connect() assert multiplexer.connection_status.is_connected multiplexer.disconnect() multiplexer.disconnect() def test_connect_twice_with_loop(): """Test that connecting twice the multiplexer behaves correctly.""" running_loop = asyncio.new_event_loop() thread_loop = Thread(target=running_loop.run_forever) thread_loop.start() try: multiplexer = Multiplexer([_make_dummy_connection()], loop=running_loop) with unittest.mock.patch.object( multiplexer.logger, "debug" ) as mock_logger_debug: assert not multiplexer.connection_status.is_connected multiplexer.connect() assert multiplexer.connection_status.is_connected multiplexer.connect() assert multiplexer.connection_status.is_connected mock_logger_debug.assert_called_with("Multiplexer already connected.") multiplexer.disconnect() running_loop.call_soon_threadsafe(running_loop.stop) finally: thread_loop.join() @pytest.mark.asyncio async def test_connect_twice_a_single_connection(): """Test that connecting twice a single connection behaves correctly.""" connection = _make_dummy_connection() multiplexer = Multiplexer([connection]) assert not multiplexer.connection_status.is_connected await multiplexer._connect_one(connection.connection_id) with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: await multiplexer._connect_one(connection.connection_id) mock_logger_debug.assert_called_with( "Connection fetchai/dummy:0.1.0 already established." ) await multiplexer._disconnect_one(connection.connection_id) @pytest.mark.asyncio async def test_run_bad_conneect(): """Test that connecting twice a single connection behaves correctly.""" connection = _make_dummy_connection() multiplexer = AsyncMultiplexer([connection]) f = asyncio.Future() f.set_result(None) with unittest.mock.patch.object(multiplexer, "connect", return_value=f): with pytest.raises(ValueError, match="Multiplexer is not connected properly."): await multiplexer.run() def test_multiplexer_connect_all_raises_error(): """Test the case when the multiplexer raises an exception while connecting.""" multiplexer = Multiplexer([_make_dummy_connection()]) with unittest.mock.patch.object(multiplexer, "_connect_all", side_effect=Exception): with pytest.raises( AEAConnectionError, match="Failed to connect the multiplexer." ): multiplexer.connect() multiplexer.disconnect() def test_multiplexer_connect_one_raises_error_many_connections(): """Test the case when the multiplexer raises an exception while attempting the connection of one connection.""" node = LocalNode() tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "input_file.csv" connection_1 = _make_local_connection("my_addr", "my_public_key", node) connection_2 = _make_stub_connection(input_file_path, output_file_path) connection_3 = _make_dummy_connection() multiplexer = Multiplexer([connection_1, connection_2, connection_3]) assert not connection_1.is_connected assert not connection_2.is_connected assert not connection_3.is_connected with unittest.mock.patch.object(connection_3, "connect", side_effect=Exception): with pytest.raises( AEAConnectionError, match="Failed to connect the multiplexer." ): multiplexer.connect() assert not connection_1.is_connected assert not connection_2.is_connected assert not connection_3.is_connected multiplexer.disconnect() try: shutil.rmtree(tmpdir) except OSError as e: logger.warning("Couldn't delete {}".format(tmpdir)) logger.exception(e) @pytest.mark.asyncio async def test_disconnect_twice_a_single_connection(): """Test that connecting twice a single connection behaves correctly.""" connection = _make_dummy_connection() multiplexer = Multiplexer([_make_dummy_connection()]) assert not multiplexer.connection_status.is_connected with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: await multiplexer._disconnect_one(connection.connection_id) mock_logger_debug.assert_called_with( "Connection fetchai/dummy:0.1.0 already disconnected." ) def test_multiplexer_disconnect_all_raises_error(): """Test the case when the multiplexer raises an exception while disconnecting.""" multiplexer = Multiplexer([_make_dummy_connection()]) multiplexer.connect() assert multiplexer.connection_status.is_connected with unittest.mock.patch.object( multiplexer, "_disconnect_all", side_effect=Exception ): with pytest.raises( AEAConnectionError, match="Failed to disconnect the multiplexer." ): multiplexer.disconnect() # # do the true disconnection - for clean the test up assert multiplexer.connection_status.is_disconnecting multiplexer.disconnect() assert multiplexer.connection_status.is_disconnected @pytest.mark.asyncio async def test_multiplexer_disconnect_one_raises_error_many_connections(): """Test the case when the multiplexer raises an exception while attempting the disconnection of one connection.""" with LocalNode() as node: tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "input_file.csv" connection_1 = _make_local_connection("my_addr", "my_public_key", node) connection_2 = _make_stub_connection(input_file_path, output_file_path) connection_3 = _make_dummy_connection() multiplexer = Multiplexer([connection_1, connection_2, connection_3]) assert not connection_1.is_connected assert not connection_2.is_connected assert not connection_3.is_connected multiplexer.connect() assert connection_1.is_connected assert connection_2.is_connected assert connection_3.is_connected with unittest.mock.patch.object( connection_3, "disconnect", side_effect=Exception ): with pytest.raises( AEAConnectionError, match="Failed to disconnect the multiplexer." ): multiplexer.disconnect() assert not connection_1.is_connected assert not connection_2.is_connected assert connection_3.is_connected # clean the test up. await connection_3.disconnect() multiplexer.disconnect() try: shutil.rmtree(tmpdir) except OSError as e: logger.warning("Couldn't delete {}".format(tmpdir)) logger.exception(e) @pytest.mark.asyncio async def test_sending_loop_does_not_start_if_multiplexer_not_connected(): """Test that the sending loop is stopped does not start if the multiplexer is not connected.""" multiplexer = Multiplexer([_make_dummy_connection()]) with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: await multiplexer._send_loop() mock_logger_debug.assert_called_with( "Sending loop not started. The multiplexer is not connected." ) @pytest.mark.asyncio async def test_sending_loop_cancelled(): """Test the case when the sending loop is cancelled.""" multiplexer = Multiplexer([_make_dummy_connection()]) multiplexer.connect() await asyncio.sleep(0.1) with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: multiplexer.disconnect() mock_logger_debug.assert_any_call("Sending loop cancelled.") @pytest.mark.asyncio async def test_receiving_loop_raises_exception(): """Test the case when an error occurs when a receive is started.""" connection = _make_dummy_connection() multiplexer = Multiplexer([connection]) with unittest.mock.patch("asyncio.wait", side_effect=Exception("a weird error.")): with unittest.mock.patch.object( multiplexer.logger, "error" ) as mock_logger_error: multiplexer.connect() time.sleep(0.1) mock_logger_error.assert_called_with( "Error in the receiving loop: a weird error.", exc_info=True ) multiplexer.disconnect() @pytest.mark.asyncio async def test_send_envelope_with_non_registered_connection(): """Test that sending an envelope with an unregistered connection raises an exception.""" connection = _make_dummy_connection() multiplexer = Multiplexer([connection], protocols=[DefaultProtocolMock]) multiplexer.connect() envelope = Envelope( to="", sender="", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", context=EnvelopeContext(connection_id=UNKNOWN_CONNECTION_PUBLIC_ID), ) with unittest.mock.patch.object( multiplexer.logger, "warning" ) as mock_logger_warning: await multiplexer._send(envelope) mock_logger_warning.assert_called_with( f"Dropping envelope, no connection available for sending: {envelope}" ) multiplexer.disconnect() @pytest.mark.asyncio async def test_send_envelope_when_no_connection(): """Test that sending an envelope with no connection logs a warning.""" multiplexer = Multiplexer([], protocols=[DefaultProtocolMock]) multiplexer.connect() envelope = Envelope( to="", sender="", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", ) with unittest.mock.patch.object( multiplexer.logger, "warning" ) as mock_logger_warning: await multiplexer._send(envelope) mock_logger_warning.assert_called_with( f"Dropping envelope, no connection available for sending: {envelope}" ) multiplexer.disconnect() def test_send_envelope_error_is_logged_by_send_loop(): """Test that the AEAConnectionError in the '_send' method is logged by the '_send_loop'.""" connection = _make_dummy_connection() multiplexer = Multiplexer([connection], protocols=[DefaultProtocolMock]) multiplexer.connect() fake_connection_id = UNKNOWN_CONNECTION_PUBLIC_ID envelope = Envelope( to="", sender="", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", context=EnvelopeContext(connection_id=fake_connection_id), ) with unittest.mock.patch.object(multiplexer.logger, "error") as mock_logger_error: multiplexer.put(envelope) time.sleep(0.1) mock_logger_error.assert_called_with( "No connection registered with id: {}".format(fake_connection_id) ) multiplexer.disconnect() def test_get_from_multiplexer_when_empty(): """Test that getting an envelope from the multiplexer when the input queue is empty raises an exception.""" connection = _make_dummy_connection() multiplexer = Multiplexer([connection]) with pytest.raises(aea.mail.base.Empty): multiplexer.get() def test_send_message_no_supported_protocol(): """Test the case when we send an envelope with a specific connection that does not support the protocol.""" with LocalNode() as node: identity_1 = Identity( "identity", address="address_1", public_key="public_key_1" ) connection_1 = _make_local_connection( identity_1.address, identity_1.public_key, node, restricted_to_protocols={DefaultMessage.protocol_id}, excluded_protocols={FipaMessage.protocol_id}, ) multiplexer = Multiplexer( [connection_1], protocols=[DefaultMessage, FipaMessage, UnknownProtocolMock] ) multiplexer.connect() with mock.patch.object(multiplexer.logger, "warning") as mock_logger_warning: envelope = Envelope( to=identity_1.address, sender=identity_1.address, protocol_specification_id=FipaMessage.protocol_specification_id, message=b"some bytes", ) multiplexer.put(envelope) time.sleep(0.5) mock_logger_warning.assert_called_with( "Connection {} does not support protocol {}. It is explicitly excluded.".format( connection_1.connection_id, FipaMessage.protocol_id ) ) with mock.patch.object(multiplexer.logger, "warning") as mock_logger_warning: envelope = Envelope( to=identity_1.address, sender=identity_1.address, protocol_specification_id=UnknownProtocolMock.protocol_specification_id, message=b"some bytes", ) multiplexer.put(envelope) time.sleep(0.5) mock_logger_warning.assert_called_with( "Connection {} does not support protocol {}. The connection is restricted to protocols in {}.".format( connection_1.connection_id, UnknownProtocolMock.protocol_id, connection_1.restricted_to_protocols, ) ) multiplexer.disconnect() def test_protocol_not_resolved(): """Test multiplexer raises ValueError on protocol not resolved.""" multiplexer = Multiplexer([Mock()]) envelope = Envelope( to="1", sender="2", protocol_specification_id=FipaMessage.protocol_specification_id, message=b"some bytes", ) with pytest.raises(ValueError): multiplexer._get_protocol_id_for_envelope(envelope) def test_autoset_default_connection(): """Set default connection automatically.""" connection_1 = _make_dummy_connection() connection_2 = _make_dummy_connection() connections = [connection_1, connection_2] multiplexer = Multiplexer(connections) multiplexer._default_connection = None multiplexer._set_default_connection_if_none() assert multiplexer._default_connection == connections[0] def test__get_connection(): """Test the method _get_connection.""" connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = Multiplexer(connections) conn_ = multiplexer._get_connection(connection_1.connection_id.to_any()) assert conn_ == connection_1 @pytest.mark.asyncio async def test_disconnect_when_not_connected(): """Test disconnect when not connected.""" connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections) with patch.object(multiplexer, "_disconnect_all") as disconnect_all_mocked: await multiplexer.disconnect() disconnect_all_mocked.assert_not_called() @pytest.mark.asyncio async def test_exit_on_none_envelope(): """Test sending task exit on None envelope.""" connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections, loop=asyncio.get_event_loop()) try: await multiplexer.connect() assert multiplexer.is_connected multiplexer.put(None) await asyncio.sleep(0.5) assert multiplexer._send_loop_task.done() finally: await multiplexer.disconnect() @pytest.mark.asyncio async def test_inbox_outbox(): """Test InBox OutBox objects.""" connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections, loop=asyncio.get_event_loop()) msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content=b"", ) msg.to = "to" msg.sender = "sender" envelope = Envelope( to="to", sender="sender", message=msg, ) try: await multiplexer.connect() inbox = InBox(multiplexer) outbox = OutBox(multiplexer) assert inbox.empty() assert outbox.empty() outbox.put(envelope) received = await inbox.async_get() assert received == envelope assert inbox.empty() assert outbox.empty() outbox.put_message(msg) await inbox.async_wait() received = inbox.get_nowait() assert received == envelope finally: await multiplexer.disconnect() @pytest.mark.asyncio async def test_threaded_mode(): """Test InBox OutBox objects in threaded mode.""" connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections, threaded=True) msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content=b"", ) msg.to = "to" msg.sender = "sender" envelope = Envelope(to="to", sender="sender", message=msg) try: await multiplexer.connect() await asyncio.sleep(0.5) inbox = InBox(multiplexer) outbox = OutBox(multiplexer) assert inbox.empty() assert outbox.empty() outbox.put(envelope) received = await inbox.async_get() assert received == envelope assert inbox.empty() assert outbox.empty() outbox.put_message(msg) await inbox.async_wait() received = inbox.get_nowait() assert received == envelope finally: await multiplexer.disconnect() @pytest.mark.asyncio async def test_outbox_negative(): """Test InBox OutBox objects.""" connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections, loop=asyncio.get_event_loop()) msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content=b"", ) context = EnvelopeContext(connection_id=connection_1.connection_id) envelope = Envelope( to="to", sender="sender", protocol_specification_id=msg.protocol_specification_id, message=b"", context=context, ) try: await multiplexer.connect() outbox = OutBox(multiplexer) assert outbox.empty() with pytest.raises(ValueError) as execinfo: outbox.put(envelope) assert ( str(execinfo.value) == "Only Message type allowed in envelope message field when putting into outbox." ) assert outbox.empty() with pytest.raises(ValueError) as execinfo: outbox.put_message("") assert str(execinfo.value) == "Provided message not of type Message." assert outbox.empty() with pytest.raises(ValueError) as execinfo: outbox.put_message(msg) assert str(execinfo.value) == "Provided message has message.to not set." assert outbox.empty() msg.to = "to" with pytest.raises(ValueError) as execinfo: outbox.put_message(msg) assert str(execinfo.value) == "Provided message has message.sender not set." finally: await multiplexer.disconnect() DefaultProtocolMock = Mock() DefaultProtocolMock.protocol_id = DefaultMessage.protocol_id DefaultProtocolMock.protocol_specification_id = DefaultMessage.protocol_specification_id @pytest.mark.asyncio async def test_default_route_applied(caplog): """Test default route is selected automatically.""" logger = logging.getLogger("aea.multiplexer") with caplog.at_level(logging.DEBUG, logger="aea.multiplexer"): connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer( connections, loop=asyncio.get_event_loop(), protocols=[DefaultProtocolMock] ) multiplexer.logger = logger envelope = Envelope( to="", sender="", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", context=EnvelopeContext(), ) multiplexer.default_routing = { DefaultMessage.protocol_id: connection_1.connection_id } try: await multiplexer.connect() inbox = InBox(multiplexer) outbox = InBox(multiplexer) assert inbox.empty() assert outbox.empty() multiplexer.put(envelope) await outbox.async_get() finally: await multiplexer.disconnect() assert "Using default routing:" in caplog.text @pytest.mark.asyncio async def test_connection_id_in_to_field_detected(caplog): """Test to field is parsed correctly and used for routing.""" logger = logging.getLogger("aea.multiplexer") with caplog.at_level(logging.DEBUG, logger="aea.multiplexer"): connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer( connections, loop=asyncio.get_event_loop(), protocols=[DefaultProtocolMock] ) multiplexer.logger = logger envelope = Envelope( to=str(connection_1.connection_id), sender="some_author/some_skill:0.1.0", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", ) try: await multiplexer.connect() inbox = InBox(multiplexer) outbox = InBox(multiplexer) assert inbox.empty() assert outbox.empty() multiplexer.put(envelope) await outbox.async_get() finally: await multiplexer.disconnect() assert "Using envelope `to` field as connection_id:" in caplog.text @pytest.mark.asyncio async def test_routing_helper_applied(caplog): """Test the routing helper is used for routing.""" logger = logging.getLogger("aea.multiplexer") with caplog.at_level(logging.DEBUG, logger="aea.multiplexer"): connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer( connections, loop=asyncio.get_event_loop(), protocols=[DefaultProtocolMock] ) multiplexer.logger = logger envelope = Envelope( to="test", sender="", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", ) multiplexer._routing_helper[envelope.to] = connection_1.connection_id try: await multiplexer.connect() inbox = InBox(multiplexer) outbox = InBox(multiplexer) assert inbox.empty() assert outbox.empty() multiplexer.put(envelope) await outbox.async_get() finally: await multiplexer.disconnect() assert ( f"Using routing helper with connection_id: {connection_1.connection_id}" in caplog.text ) def test_multiplexer_setup(): """Test multiplexer setup to set connections.""" node = LocalNode() tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "input_file.csv" connection_1 = _make_local_connection("my_addr", "my_public_key", node) connection_2 = _make_stub_connection(input_file_path, output_file_path) connection_3 = _make_dummy_connection() connections = [connection_1, connection_2, connection_3] multiplexer = Multiplexer([]) with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: multiplexer._connection_consistency_checks() mock_logger_debug.assert_called_with("List of connections is empty.") multiplexer._setup(connections, default_routing=None) multiplexer._connection_consistency_checks() class TestExceptionHandlingOnConnectionSend: """Test exception handling policy on connection.send.""" def setup(self): """Set up test case.""" self.connection = _make_dummy_connection() self.multiplexer = Multiplexer( [self.connection], protocols=[DefaultProtocolMock] ) self.multiplexer.connect() self.envelope = Envelope( to="", sender="", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", context=EnvelopeContext(connection_id=self.connection.connection_id), ) self.exception = ValueError("expected") def teardown(self): """Tear down test case.""" self.multiplexer.disconnect() def test_log_policy(self): """Test just log exception.""" with patch.object(self.connection, "send", side_effect=self.exception): self.multiplexer._exception_policy = ExceptionPolicyEnum.just_log self.multiplexer.put(self.envelope) time.sleep(1) assert not self.multiplexer._send_loop_task.done() def test_propagate_policy(self): """Test propagate exception.""" assert self.multiplexer._exception_policy == ExceptionPolicyEnum.propagate with patch.object(self.connection, "send", side_effect=self.exception): self.multiplexer.put(self.envelope) time.sleep(1) wait_for_condition( lambda: self.multiplexer._send_loop_task.done(), timeout=5 ) assert self.multiplexer._send_loop_task.exception() == self.exception def test_stop_policy(self): """Test stop multiplexer on exception.""" with patch.object(self.connection, "send", side_effect=self.exception): self.multiplexer._exception_policy = ExceptionPolicyEnum.stop_and_exit self.multiplexer.put(self.envelope) time.sleep(1) wait_for_condition( lambda: self.multiplexer.connection_status.is_disconnected, timeout=5 ) def test_disconnect_order(self): """Test disconnect order: tasks first, disconnect_all next.""" parent = MagicMock() async def fn(): return with patch.object( self.multiplexer, "_stop_receive_send_loops", return_value=fn() ) as stop_loops, patch.object( self.multiplexer, "_disconnect_all", return_value=fn() ) as disconnect_all, patch.object( self.multiplexer, "_check_and_set_disconnected_state" ) as check_and_set_disconnected_state: parent.attach_mock(stop_loops, "stop_loops") parent.attach_mock(disconnect_all, "disconnect_all") parent.attach_mock( check_and_set_disconnected_state, "check_and_set_disconnected_state" ) self.multiplexer.disconnect() assert parent.mock_calls == [ call.stop_loops(), call.disconnect_all(), call.check_and_set_disconnected_state(), ] class TestMultiplexerDisconnectsOnTermination: # pylint: disable=attribute-defined-outside-init """Test multiplexer disconnects on agent process keyboard interrupted.""" def setup(self): """Set the test up.""" self.proc = None self.runner = CliRunner() self.agent_name = "myagent" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() shutil.copytree(Path(ROOT_DIR, "packages"), Path(self.t, "packages")) os.chdir(self.t) self.key_path = os.path.join(self.t, "fetchai_private_key.txt") self.conn_key_path = os.path.join(self.t, "conn_private_key.txt") result = self.runner.invoke(cli, [*CLI_LOG_OPTION, "init", "--author", AUTHOR]) assert result.exit_code == 0 result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "create", "--local", self.agent_name] ) assert result.exit_code == 0 os.chdir(Path(self.t, self.agent_name)) result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", DEFAULT_LEDGER, self.key_path] ) assert result.exit_code == 0, result.stdout_bytes result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add-key", DEFAULT_LEDGER, self.key_path] ) assert result.exit_code == 0, result.stdout_bytes def test_multiplexer_disconnected_on_early_interruption(self): """Test multiplexer disconnected properly on termination before connected.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(P2P_PUBLIC_ID)] ) assert result.exit_code == 0, result.stdout_bytes result = self.runner.invoke(cli, [*CLI_LOG_OPTION, "build"]) assert result.exit_code == 0, result.stdout_bytes result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "generate-key", DEFAULT_LEDGER, self.conn_key_path] ) assert result.exit_code == 0, result.stdout_bytes result = self.runner.invoke( cli, [ *CLI_LOG_OPTION, "add-key", DEFAULT_LEDGER, self.conn_key_path, "--connection", ], ) assert result.exit_code == 0, result.stdout_bytes result = self.runner.invoke(cli, [*CLI_LOG_OPTION, "issue-certificates"]) assert result.exit_code == 0, result.stdout_bytes self.proc = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "-v", "DEBUG", "run"], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) self.proc.expect_all( ["Starting libp2p node..."], timeout=50, ) self.proc.control_c() self.proc.expect_all( ["Multiplexer .*disconnected."], timeout=20, strict=False, ) self.proc.expect_all( [EOF], timeout=20, ) def test_multiplexer_disconnected_on_termination_after_connected_no_connection( self, ): """Test multiplexer disconnected properly on termination after connected.""" self.proc = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "-v", "DEBUG", "run"], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) self.proc.expect_all( ["Start processing messages..."], timeout=20, ) self.proc.control_c() self.proc.expect_all( ["Multiplexer disconnecting...", "Multiplexer disconnected.", EOF], timeout=20, ) def test_multiplexer_disconnected_on_termination_after_connected_one_connection( self, ): """Test multiplexer disconnected properly on termination after connected.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(STUB_CONNECTION_ID)], ) assert result.exit_code == 0, result.stdout_bytes self.proc = PexpectWrapper( # nosec [sys.executable, "-m", "aea.cli", "-v", "DEBUG", "run"], env=os.environ, maxread=10000, encoding="utf-8", logfile=sys.stdout, ) self.proc.expect_all( ["Start processing messages..."], timeout=20, ) self.proc.control_c() self.proc.expect_all( ["Multiplexer disconnecting...", "Multiplexer disconnected.", EOF], timeout=20, ) def teardown(self): """Tear the test down.""" if self.proc: self.proc.wait_to_complete(10) os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass def test_multiplexer_setup_replaces_connections(): """Test proper connections reset on setup call.""" m = AsyncMultiplexer([MagicMock(), MagicMock(), MagicMock()]) assert len(m._id_to_connection) == 3 assert len(m._connections) == 3 m._setup([MagicMock()], MagicMock()) assert len(m._id_to_connection) == 1 assert len(m._connections) == 1 def test_connect_after_disconnect_sync(): """Test connect-disconnect-connect again for threaded multiplexer.""" multiplexer = Multiplexer([_make_dummy_connection()]) assert not multiplexer.connection_status.is_connected multiplexer.connect() assert multiplexer.connection_status.is_connected multiplexer.disconnect() assert not multiplexer.connection_status.is_connected multiplexer.connect() assert multiplexer.connection_status.is_connected multiplexer.disconnect() assert not multiplexer.connection_status.is_connected @pytest.mark.asyncio async def test_connect_after_disconnect_async(): """Test connect-disconnect-connect again for async multiplexer.""" multiplexer = AsyncMultiplexer([_make_dummy_connection()]) assert not multiplexer.connection_status.is_connected await multiplexer.connect() assert multiplexer.connection_status.is_connected await multiplexer.disconnect() assert not multiplexer.connection_status.is_connected await multiplexer.connect() assert multiplexer.connection_status.is_connected await multiplexer.disconnect() assert not multiplexer.connection_status.is_connected @pytest.mark.asyncio async def test_connection_timeouts(): """Test connect,send, disconnect timeouts for connections.""" async def slow_fn(*asrgs, **kwargs): await asyncio.sleep(100) connection = _make_dummy_connection() envelope = Envelope( to="", sender="", message=DefaultMessage(performative=DefaultMessage.Performative.BYTES), context=EnvelopeContext(connection_id=connection.connection_id), ) connection = _make_dummy_connection() connection.connect = slow_fn multiplexer = AsyncMultiplexer([connection]) multiplexer.CONNECT_TIMEOUT = 0.1 with pytest.raises(AEAConnectionError, match=r"TimeoutError"): await multiplexer.connect() connection = _make_dummy_connection() connection.send = slow_fn multiplexer = AsyncMultiplexer([connection]) multiplexer.SEND_TIMEOUT = 0.1 await multiplexer.connect() with pytest.raises(asyncio.TimeoutError): await multiplexer._send(envelope) await multiplexer.disconnect() connection = _make_dummy_connection() connection.disconnect = slow_fn multiplexer = AsyncMultiplexer([connection]) multiplexer.DISCONNECT_TIMEOUT = 0.1 await multiplexer.connect() with pytest.raises( AEAConnectionError, match=f"Failed to disconnect multiplexer, some connections are not disconnected.*{str(connection.connection_id)}", ): await multiplexer.disconnect() @pytest.mark.asyncio async def test_stops_on_connectionerror_during_connect(): """Test multiplexer stopped and reraise exception on connect fails on conection.connect with AEAConnectionError.""" connection = _make_dummy_connection() multiplexer = AsyncMultiplexer([connection]) with patch.object( connection, "connect", side_effect=AEAConnectionError("expected") ): with pytest.raises(AEAConnectionError, match=r"expected"): await multiplexer.connect() assert multiplexer.connection_status.is_disconnected ================================================ FILE: tests/test_aea/test_package_loading.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for AEA package loading.""" import os import sys from unittest.mock import Mock import pytest from aea.skills.base import Skill from tests.conftest import CUR_PATH def test_loading(): """Test that we correctly load AEA package modules.""" agent_context_mock = Mock(agent_name="name") skill_directory = os.path.join(CUR_PATH, "data", "dummy_skill") prefixes = [ "packages", "packages.dummy_author", "packages.dummy_author.skills", "packages.dummy_author.skills.dummy", "packages.dummy_author.skills.dummy.dummy_subpackage", ] Skill.from_dir(skill_directory, agent_context_mock) assert all( prefix in sys.modules for prefix in prefixes ), "Not all the subpackages are importable." # try to import a function from a skill submodule. from packages.dummy_author.skills.dummy.dummy_subpackage.foo import ( # type: ignore bar, ) assert bar() == 42 import packages # type: ignore import packages.dummy_author # type: ignore import packages.dummy_author.skills # type: ignore import packages.dummy_author.skills.dummy # type: ignore with pytest.raises( ModuleNotFoundError, match="No module named 'packages.dummy_author.connections'" ): import packages.dummy_author.connections # type: ignore with pytest.raises( ModuleNotFoundError, match="No module named 'packages.dummy_author.skills.not_exists_skill'", ): import packages.dummy_author.skills.not_exists_skill # type: ignore # noqa # flake8: noqa ================================================ FILE: tests/test_aea/test_protocols/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The test protocols module contains the tests of the AEA protocols.""" ================================================ FILE: tests/test_aea/test_protocols/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the protocols base module.""" import os import shutil import tempfile from copy import copy from enum import Enum from pathlib import Path from typing import Callable, List, Tuple, Type from unittest.mock import Mock import pytest from google.protobuf.struct_pb2 import Struct from aea.exceptions import AEAEnforceError from aea.mail.base import Envelope from aea.mail.base_pb2 import DialogueMessage as Pb2DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.protocols.base import Message, Protocol, Serializer from aea.protocols.dialogue.base import Dialogue, DialogueLabel from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue, DefaultDialogues, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue, SigningDialogues, ) from packages.fetchai.protocols.state_update.dialogues import ( StateUpdateDialogue, StateUpdateDialogues, ) from tests.conftest import ROOT_DIR, UNKNOWN_PROTOCOL_PUBLIC_ID def role_from_first_message_dd( message: Message, receiver_address: str ) -> Dialogue.Role: """Role from first message.""" return DefaultDialogue.Role.AGENT def role_from_first_message_sd( message: Message, receiver_address: str ) -> Dialogue.Role: """Role from first message.""" return SigningDialogue.Role.SKILL def role_from_first_message_sud( message: Message, receiver_address: str ) -> Dialogue.Role: """Role from first message.""" return StateUpdateDialogue.Role.SKILL DIALOGUE_CLASSES: List[Tuple[Type, Type, Enum, Callable]] = [ ( DefaultDialogue, DefaultDialogues, DefaultDialogue.Role.AGENT, role_from_first_message_dd, ), ( SigningDialogue, SigningDialogues, SigningDialogue.Role.SKILL, role_from_first_message_sd, ), ( StateUpdateDialogue, StateUpdateDialogues, StateUpdateDialogue.Role.SKILL, role_from_first_message_sud, ), ] class TMessage(Message): """Message class for tests.""" class _SlotsCls: __slots__ = ( "body_1", "body_2", "kwarg", "content", "performative", "dialogue_reference", "message_id", "target", ) class TestMessageProperties: """Test that the base serializations work.""" @classmethod def setup_class(cls): """Setup test.""" cls.body = {"body_1": "1", "body_2": "2"} cls.kwarg = 1 cls.message = TMessage(cls.body, kwarg=cls.kwarg) def test_message_properties(self): """Test message properties.""" for key, value in self.body.items(): assert self.message.get(key) == value assert self.message.get("kwarg") == self.kwarg assert not self.message.has_sender assert not self.message.has_to to = "to" sender = "sender" self.message.to = to self.message.sender = sender assert self.message.sender == sender assert self.message.to == to assert ( str(self.message) == "Message(sender=sender,to=to,body_1=1,body_2=2,kwarg=1)" ) assert ( repr(self.message) == "Message(sender=sender,to=to,body_1=1,body_2=2,kwarg=1)" ) assert self.message.valid_performatives == set() class ExampleProtobufSerializer(Serializer): """ Example Protobuf serializer. It assumes that the Message contains a JSON-serializable body. """ @staticmethod def encode(msg: Message) -> bytes: """ Encode a message into bytes using Protobuf. - if one of message_id, target and dialogue_reference are not defined, serialize only the message body/ - otherwise, extract those fields from the body and instantiate a Message struct. """ message_pb = ProtobufMessage() if msg.has_dialogue_info: dialogue_message_pb = Pb2DialogueMessage() dialogue_message_pb.message_id = msg.message_id dialogue_message_pb.dialogue_starter_reference = msg.dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = msg.dialogue_reference[1] dialogue_message_pb.target = msg.target new_body = copy(msg._body) # pylint: disable=protected-access new_body.pop("message_id") new_body.pop("dialogue_reference") new_body.pop("target") body_json = Struct() body_json.update(new_body) # pylint: disable=no-member dialogue_message_pb.content = ( # pylint: disable=no-member body_json.SerializeToString() ) message_pb.dialogue_message.CopyFrom( # pylint: disable=no-member dialogue_message_pb ) else: body_json = Struct() body_json.update(msg._body) # pylint: disable=no-member,protected-access message_pb.body.CopyFrom(body_json) # pylint: disable=no-member return message_pb.SerializeToString() @staticmethod def decode(obj: bytes) -> Message: """ Decode bytes into a message using Protobuf. First, try to parse the input as a Protobuf 'Message'; if it fails, parse the bytes as struct. """ message_pb = ProtobufMessage() message_pb.ParseFromString(obj) message_type = message_pb.WhichOneof("message") if message_type == "body": body = dict(message_pb.body) # pylint: disable=no-member msg = TMessage(_body=body) return msg if message_type == "dialogue_message": dialogue_message_pb = ( message_pb.dialogue_message # pylint: disable=no-member ) message_id = dialogue_message_pb.message_id target = dialogue_message_pb.target dialogue_starter_reference = dialogue_message_pb.dialogue_starter_reference dialogue_responder_reference = ( dialogue_message_pb.dialogue_responder_reference ) body_json = Struct() body_json.ParseFromString(dialogue_message_pb.content) body = dict(body_json) body["message_id"] = message_id body["target"] = target body["dialogue_reference"] = ( dialogue_starter_reference, dialogue_responder_reference, ) return TMessage(_body=body) raise ValueError("Message type not recognized.") # pragma: nocover class TestBaseSerializations: """Test that the base serializations work.""" @classmethod def setup_class(cls): """Set up the use case.""" cls.message = TMessage(content="hello") cls.message2 = TMessage(_body={"content": "hello"}) cls.message3 = TMessage( message_id=1, target=0, dialogue_reference=("", ""), _body={"content": "hello"}, ) def test_default_protobuf_serialization(self): """Test that the default Protobuf serialization works.""" message_bytes = ExampleProtobufSerializer().encode(self.message) envelope = Envelope( to="receiver", sender="sender", protocol_specification_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=message_bytes, ) envelope_bytes = envelope.encode() expected_envelope = Envelope.decode(envelope_bytes) actual_envelope = envelope assert expected_envelope == actual_envelope expected_msg = ExampleProtobufSerializer().decode(expected_envelope.message) actual_msg = self.message assert expected_msg == actual_msg def test_default_protobuf_serialization_with_dialogue_info(self): """Test that the default Protobuf serialization with dialogue info works.""" message_bytes = ExampleProtobufSerializer().encode(self.message3) envelope = Envelope( to="receiver", sender="sender", protocol_specification_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=message_bytes, ) envelope_bytes = envelope.encode() expected_envelope = Envelope.decode(envelope_bytes) actual_envelope = envelope assert expected_envelope == actual_envelope expected_msg = ExampleProtobufSerializer().decode(expected_envelope.message) actual_msg = self.message3 assert expected_msg == actual_msg def test_set(self): """Test that the set method works.""" self.message._body = {} # clean values key, value = "content", "temporary_value" assert self.message.get(key) is None self.message.set(key, value) assert self.message.get(key) == value def test_body_setter(self): """Test the body setter.""" m_dict = {"content": "data"} self.message2._body = m_dict assert self.message2._body == m_dict class TestMessageEncode: """Test the 'Protocol.from_dir' method.""" def test_encode(self): """Test encode on message.""" class TTMessage(TMessage): """Test class extended.""" serializer = ExampleProtobufSerializer msg = TTMessage({"body_1": "1", "body_2": "2"}) msg.encode() class TestProtocolFromDir: """Test the 'Protocol.from_dir' method.""" @classmethod def setup_class(cls): """Set the tests up.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) def test_protocol_load_positive(self): """Test protocol loaded correctly.""" default_protocol = Protocol.from_dir( Path("packages", "fetchai", "protocols", "default") ) assert str(default_protocol.public_id) == str( DefaultMessage.protocol_id ), "Protocol not loaded correctly." assert str(default_protocol.protocol_specification_id) == str( DefaultMessage.protocol_specification_id ), "Protocol not loaded correctly." assert default_protocol.serializer is not None @classmethod def teardown_class(cls): """Tear the tests down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestMessageAttributes: """Test some message attributes.""" def test_performative(self): """Test message performative.""" class SomePerformative(Message.Performative): value = "value" message = Message(performative=SomePerformative.value) assert message.performative == SomePerformative.value assert str(message.performative) == "value" def test_to(self): """Test the 'to' attribute getter and setter.""" message = Message() with pytest.raises(ValueError, match="Message's 'To' field must be set."): message.to message.to = "to" assert message.to == "to" with pytest.raises(AEAEnforceError, match="To already set."): message.to = "to" def test_dialogue_reference(self): """Test the 'dialogue_reference' attribute.""" message = Message(dialogue_reference=("x", "y")) assert message.dialogue_reference == ("x", "y") def test_message_id(self): """Test the 'message_id' attribute.""" message = Message(message_id=1) assert message.message_id == 1 def test_target(self): """Test the 'target' attribute.""" message = Message(target=1) assert message.target == 1 @pytest.mark.parametrize("dialogue_classes", DIALOGUE_CLASSES) def test_dialogue(dialogue_classes): """Test dialogue initialization.""" dialogue_class, _, role, _ = dialogue_classes dialogue_class( DialogueLabel(("x", "y"), "opponent_addr", "starer_addr"), "agent_address", role ) @pytest.mark.parametrize("dialogues_classes", DIALOGUE_CLASSES) def test_dialogues(dialogues_classes): """Test dialogues initialization.""" dialogue_class, dialogues_class, _, role_from_first_message = dialogues_classes dialogues_class("agent_address", role_from_first_message, dialogue_class) def test_protocol_repr(): """Test protocol repr.""" config_mock = Mock() config_mock.public_id = UNKNOWN_PROTOCOL_PUBLIC_ID protocol = Protocol(config_mock, message_class=Message) assert repr(protocol) == f"Protocol({UNKNOWN_PROTOCOL_PUBLIC_ID})" ================================================ FILE: tests/test_aea/test_protocols/test_dialogue/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the dialogue helper module.""" ================================================ FILE: tests/test_aea/test_protocols/test_dialogue/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the dialogue/base.py module.""" import re import sys from typing import FrozenSet, Tuple, Type, cast from unittest import mock from unittest.mock import Mock, patch import pytest import aea from aea.common import Address from aea.configurations.base import PublicId from aea.exceptions import AEAEnforceError from aea.helpers.storage.generic_storage import Storage from aea.protocols.base import Message from aea.protocols.dialogue.base import BasicDialoguesStorage from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel, DialogueMessage, DialogueStats from aea.protocols.dialogue.base import Dialogues as BaseDialogues from aea.protocols.dialogue.base import ( InvalidDialogueMessage, PersistDialoguesStorage, PersistDialoguesStorageWithOffloading, find_caller_object, ) from aea.skills.base import SkillComponent from packages.fetchai.protocols.default.custom_types import ErrorCode from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.state_update.message import StateUpdateMessage from tests.common.utils import wait_for_condition class Dialogue(BaseDialogue): """This concrete class defines a dialogue.""" INITIAL_PERFORMATIVES = frozenset({DefaultMessage.Performative.BYTES}) TERMINAL_PERFORMATIVES = frozenset({DefaultMessage.Performative.ERROR}) VALID_REPLIES = { DefaultMessage.Performative.BYTES: frozenset( {DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR} ), DefaultMessage.Performative.ERROR: frozenset(), } class Role(BaseDialogue.Role): """This class defines the agent's role in this dialogue.""" ROLE1 = "role1" ROLE2 = "role2" class EndState(BaseDialogue.EndState): """This class defines the end states of this dialogue.""" SUCCESSFUL = 0 FAILED = 1 def __init__( self, dialogue_label: DialogueLabel, message_class: Type[Message] = DefaultMessage, self_address: Address = "agent 1", role: BaseDialogue.Role = Role.ROLE1, ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ BaseDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class Dialogues(BaseDialogues): """This class gives a concrete definition of dialogues.""" END_STATES = frozenset( {Dialogue.EndState.SUCCESSFUL, Dialogue.EndState.FAILED} ) # type: FrozenSet[BaseDialogue.EndState] def __init__( self, self_address: Address, message_class=DefaultMessage, dialogue_class=Dialogue, keep_terminal_state_dialogues=None, ) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return Dialogue.Role.ROLE1 BaseDialogues.__init__( self, self_address=self_address, end_states=cast(FrozenSet[BaseDialogue.EndState], self.END_STATES), message_class=message_class, dialogue_class=dialogue_class, role_from_first_message=role_from_first_message, keep_terminal_state_dialogues=keep_terminal_state_dialogues, ) @pytest.mark.skipif( sys.version_info < (3, 7), reason="This part of code is only defined for python version >= 3.7", ) def test_dialogue_message_python_3_7(): """Test DiallogueMessage if python is 3.7""" dialogue_message = DialogueMessage(DefaultMessage.Performative.BYTES) assert isinstance(dialogue_message.performative, Message.Performative) assert dialogue_message.performative == DefaultMessage.Performative.BYTES assert dialogue_message.contents == {} assert dialogue_message.is_incoming is None assert dialogue_message.target is None @pytest.mark.skipif( sys.version_info >= (3, 7), reason="This part of code is only defined for python version < 3.7", ) def test_dialogue_message_python_3_6(): """Test DiallogueMessage if python is 3.6""" with mock.patch.object( aea.protocols.dialogue.base.sys, "version_info", return_value=(3, 6) ): dialogue_message = DialogueMessage(DefaultMessage.Performative.BYTES) assert isinstance(dialogue_message.performative, Message.Performative) assert dialogue_message.performative == DefaultMessage.Performative.BYTES assert dialogue_message.contents == {} assert dialogue_message.is_incoming is None assert dialogue_message.target is None class TestDialogueLabel: """Test for DialogueLabel.""" @classmethod def setup(cls): """Initialise the environment to test DialogueLabel.""" cls.agent_address = "agent 1" cls.opponent_address = "agent 2" cls.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), dialogue_opponent_addr=cls.opponent_address, dialogue_starter_addr=cls.agent_address, ) def test_all_methods(self): """Test the DialogueLabel.""" assert self.dialogue_label.dialogue_reference == (str(1), "") assert self.dialogue_label.dialogue_starter_reference == str(1) assert self.dialogue_label.dialogue_responder_reference == "" assert self.dialogue_label.dialogue_opponent_addr == self.opponent_address assert self.dialogue_label.dialogue_starter_addr == self.agent_address assert str(self.dialogue_label) == "{}_{}_{}_{}".format( self.dialogue_label.dialogue_starter_reference, self.dialogue_label.dialogue_responder_reference, self.dialogue_label.dialogue_opponent_addr, self.dialogue_label.dialogue_starter_addr, ) dialogue_label_eq = DialogueLabel( dialogue_reference=(str(1), ""), dialogue_opponent_addr=self.opponent_address, dialogue_starter_addr=self.agent_address, ) assert dialogue_label_eq == self.dialogue_label dialogue_label_not_eq = "This is a test" assert not dialogue_label_not_eq == self.dialogue_label assert hash(dialogue_label_eq) == hash(self.dialogue_label) assert self.dialogue_label.json == dict( dialogue_starter_reference=str(1), dialogue_responder_reference="", dialogue_opponent_addr=self.opponent_address, dialogue_starter_addr=self.agent_address, ) assert DialogueLabel.from_json(self.dialogue_label.json) == self.dialogue_label assert DialogueLabel.from_str(str(self.dialogue_label)) == self.dialogue_label class TestDialogueBase: """Test for Dialogue.""" @classmethod def setup(cls): """Initialise the environment to test Dialogue.""" cls.incomplete_reference = (str(1), "") cls.complete_reference = (str(1), str(1)) cls.opponent_address = "agent 2" cls.agent_address = "agent 1" cls.dialogue_label = DialogueLabel( dialogue_reference=cls.incomplete_reference, dialogue_opponent_addr=cls.opponent_address, dialogue_starter_addr=cls.agent_address, ) cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label) cls.dialogue_label_opponent_started = DialogueLabel( dialogue_reference=cls.complete_reference, dialogue_opponent_addr=cls.opponent_address, dialogue_starter_addr=cls.opponent_address, ) cls.dialogue_opponent_started = Dialogue( dialogue_label=cls.dialogue_label_opponent_started ) # convenient messages to reuse across tests cls.valid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) cls.valid_message_1_by_self.sender = cls.agent_address cls.valid_message_1_by_self.to = cls.opponent_address cls.valid_message_2_by_other = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=-1, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) cls.valid_message_2_by_other.sender = cls.opponent_address cls.valid_message_2_by_other.to = cls.agent_address cls.valid_message_3_by_self = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=2, target=-1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) cls.valid_message_3_by_self.sender = cls.agent_address cls.valid_message_3_by_self.to = cls.opponent_address def test_inner_classes(self): """Test the inner classes: Role and EndStates.""" assert str(Dialogue.Role.ROLE1) == "role1" assert str(Dialogue.Role.ROLE2) == "role2" assert str(Dialogue.EndState.SUCCESSFUL) == "0" assert str(Dialogue.EndState.FAILED) == "1" def test_dialogue_properties(self): """Test dialogue properties.""" assert self.dialogue.dialogue_label == self.dialogue_label assert self.dialogue.incomplete_dialogue_label == self.dialogue_label assert self.dialogue.dialogue_labels == {self.dialogue_label} assert self.dialogue.self_address == self.agent_address assert self.dialogue.role == Dialogue.Role.ROLE1 assert str(self.dialogue.role) == "role1" assert self.dialogue.rules.initial_performatives == frozenset( {DefaultMessage.Performative.BYTES} ) assert self.dialogue.rules.terminal_performatives == frozenset( {DefaultMessage.Performative.ERROR} ) assert self.dialogue.rules.valid_replies == { DefaultMessage.Performative.BYTES: frozenset( {DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR} ), DefaultMessage.Performative.ERROR: frozenset(), } assert self.dialogue.rules.get_valid_replies( DefaultMessage.Performative.BYTES ) == frozenset( {DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR} ) assert self.dialogue.rules.get_valid_replies( DefaultMessage.Performative.ERROR ) == frozenset({}) assert self.dialogue.message_class == DefaultMessage assert self.dialogue.is_self_initiated assert self.dialogue.last_incoming_message is None assert self.dialogue.last_outgoing_message is None assert self.dialogue.last_message is None assert self.dialogue.is_empty def test_counterparty_from_message(self): """Test the 'counterparty_from_message' method.""" assert ( self.dialogue._counterparty_from_message(self.valid_message_1_by_self) == self.opponent_address ) assert ( self.dialogue._counterparty_from_message(self.valid_message_2_by_other) == self.opponent_address ) def test_is_message_by_self(self): """Test the 'is_message_by_self' method.""" assert self.dialogue._is_message_by_self(self.valid_message_1_by_self) assert not self.dialogue._is_message_by_self(self.valid_message_2_by_other) def test_is_message_by_other(self): """Test the 'is_message_by_other' method.""" assert not self.dialogue._is_message_by_other(self.valid_message_1_by_self) assert self.dialogue._is_message_by_other(self.valid_message_2_by_other) def test_try_get_message(self): """Test the 'try_get_message' method.""" assert ( self.dialogue.get_message_by_id(self.valid_message_1_by_self.message_id) is None ) self.dialogue._update(self.valid_message_1_by_self) assert ( self.dialogue.get_message_by_id(self.valid_message_1_by_self.message_id) == self.valid_message_1_by_self ) assert ( self.dialogue.get_message_by_id(self.valid_message_2_by_other.message_id) is None ) self.dialogue._update(self.valid_message_2_by_other) assert ( self.dialogue.get_message_by_id(self.valid_message_2_by_other.message_id) == self.valid_message_2_by_other ) def test_has_message_id(self): """Test the 'has_message_id' method.""" assert self.dialogue._has_message_id(1) is False self.dialogue._update(self.valid_message_1_by_self) assert self.dialogue._has_message_id(1) is True assert self.dialogue._has_message_id(2) is False def test_update_positive(self): """Positive test for the 'update' method.""" self.dialogue._update(self.valid_message_1_by_self) assert self.dialogue.last_outgoing_message == self.valid_message_1_by_self def test_update_positive_multiple_messages_by_self(self): """Positive test for the 'update' method: multiple messages by self is sent to the dialogue.""" self.dialogue._update(self.valid_message_1_by_self) valid_message_2_by_self = DefaultMessage( dialogue_reference=(str(1), ""), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_self.sender = self.agent_address valid_message_2_by_self.to = self.opponent_address self.dialogue._update(valid_message_2_by_self) assert self.dialogue.last_message.message_id == 2 def test_terminal_state_callback(self): """Test dialogue terminal state callback works.""" called = False def callback(dialogue): nonlocal called called = True self.dialogue.add_terminal_state_callback(callback) self.dialogue._update(self.valid_message_1_by_self) self.dialogue.reply( target_message=self.valid_message_1_by_self, performative=DefaultMessage.Performative.ERROR, error_code=ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="oops", error_data={}, ) assert called def test_update_negative_is_valid_next_message_fails(self): """Negative test for the 'update' method: input message is invalid with respect to the dialogue.""" invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), message_id=200, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address with pytest.raises( InvalidDialogueMessage, match=r"Message .* is invalid with respect to this dialogue. Error: Invalid message_id. Expected .*. Found 200.", ): self.dialogue._update(invalid_message_1_by_self) assert self.dialogue.last_outgoing_message is None def test_update_dialogue_negative_message_does_not_belong_to_dialogue(self): """Negative test for the 'update' method in dialogue with wrong message not belonging to dialogue.""" invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(2), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address with pytest.raises(InvalidDialogueMessage) as cm: self.dialogue._update(invalid_message_1_by_self) assert str(cm.value) == ( "The message 1 does not belong to this dialogue." "The dialogue reference of the message is {}, while the dialogue reference of the dialogue is {}".format( invalid_message_1_by_self.dialogue_reference, self.dialogue.dialogue_label.dialogue_reference, ) ) assert self.dialogue.is_empty def test_is_belonging_to_dialogue(self): """Test for the '_is_belonging_to_dialogue' method""" valid_message_2_by_self = DefaultMessage( dialogue_reference=(str(2), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) valid_message_2_by_self.sender = self.agent_address valid_message_2_by_self.to = self.opponent_address assert self.dialogue._is_belonging_to_dialogue(self.valid_message_1_by_self) assert not self.dialogue._is_belonging_to_dialogue(valid_message_2_by_self) def test_reply_positive(self): """Positive test for the 'reply' method.""" self.dialogue._update(self.valid_message_1_by_self) self.dialogue.reply( target_message=self.valid_message_1_by_self, performative=DefaultMessage.Performative.BYTES, content=b"Hello Back", ) assert self.dialogue.last_message.message_id == 2 def test_reply_negative_empty_dialogue(self): """Negative test for the 'reply' method: target message is not in the dialogue.""" with pytest.raises(ValueError) as cm: self.dialogue.reply( target_message=self.valid_message_1_by_self, performative=DefaultMessage.Performative.BYTES, content=b"Hello Back", ) assert str(cm.value) == "Cannot reply in an empty dialogue!" assert self.dialogue.is_empty def test_reply_negative_target_does_not_exist(self): """Negative test for the 'reply' method: target is not in the dialogue.""" self.dialogue._update(self.valid_message_1_by_self) with pytest.raises(ValueError) as cm: self.dialogue.reply( target=10, performative=DefaultMessage.Performative.BYTES, content=b"Hello Back", ) assert str(cm.value) == "No target message found!" def test_reply_negative_target_message_target_mismatch(self): """Negative test for the 'reply' method: target message and target provided but do not match.""" self.dialogue._update(self.valid_message_1_by_self) assert self.dialogue.last_message.message_id == 1 with pytest.raises(AEAEnforceError) as cm: self.dialogue.reply( target_message=self.valid_message_1_by_self, target=2, performative=DefaultMessage.Performative.BYTES, content=b"Hello Back", ) assert str(cm.value) == "The provided target and target_message do not match." assert self.dialogue.last_message.message_id == 1 def test_reply_negative_invalid_target(self): """Negative test for the 'reply' method: target message is not in the dialogue.""" self.dialogue._update(self.valid_message_1_by_self) assert self.dialogue.last_message.message_id == 1 invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello There", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address with pytest.raises(AEAEnforceError) as cm: self.dialogue.reply( target_message=invalid_message_1_by_self, performative=DefaultMessage.Performative.BYTES, content=b"Hello Back", ) assert str(cm.value) == "The target message does not exist in this dialogue." assert self.dialogue.last_message.message_id == 1 def test_is_valid_next_message_positive(self): """Positive test for the 'validate_next_message' method""" self.dialogue._update(self.valid_message_1_by_self) self.dialogue._update(self.valid_message_2_by_other) result, msg = self.dialogue._validate_next_message(self.valid_message_3_by_self) assert result is True assert msg == "Message is valid with respect to this dialogue." def test_is_valid_next_message_negative_basic_validation_fails(self): """Negative test for the 'validate_next_message' method: basic_validation method fails""" invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), message_id=2, target=0, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address result, msg = self.dialogue._validate_next_message(invalid_message_1_by_self) assert result is False assert msg == "Invalid message_id. Expected 1. Found 2." def test_is_valid_next_message_negative_additional_validation_fails(self): """Negative test for the 'validate_next_message' method: additional_validation method fails""" self.dialogue._update(self.valid_message_1_by_self) self.dialogue._update(self.valid_message_2_by_other) invalid_message_3_by_self = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=2, target=3, performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) invalid_message_3_by_self.sender = self.agent_address invalid_message_3_by_self.to = self.opponent_address result, msg = self.dialogue._validate_next_message(invalid_message_3_by_self) assert result is False assert "Invalid target" in msg def test_is_valid_next_message_negative_is_valid_fails(self): """Negative test for the 'validate_next_message' method: is_valid method fails""" def failing_custom_validation(self, message: Message) -> Tuple[bool, str]: return False, "some reason" with patch.object( self.dialogue.__class__, "_custom_validation", failing_custom_validation ): result, msg = self.dialogue._validate_next_message( self.valid_message_1_by_self ) assert result is False assert msg == "some reason" def test_basic_validation_positive(self): """Positive test for the '_basic_validation' method.""" result, msg = self.dialogue._basic_validation(self.valid_message_1_by_self) assert result is True assert msg == "The initial message passes basic validation." self.dialogue._update(self.valid_message_1_by_self) result, msg = self.dialogue._basic_validation(self.valid_message_2_by_other) assert result is True assert msg == "The non-initial message passes basic validation." def test_basic_validation_negative_initial_message_invalid(self): """Negative test for the '_basic_validation' method: initial message is invalid.""" invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address assert self.dialogue.is_empty result, msg = self.dialogue._basic_validation(invalid_message_1_by_self) assert result is False assert msg == "Invalid message_id. Expected 1. Found 2." @patch.object(BaseDialogue, "_validate_message_id", return_value=None) def test_basic_validation_negative_non_initial_message_invalid(self, *mocks): """Negative test for the '_basic_validation' method: non-initial message is invalid.""" self.dialogue._update(self.valid_message_1_by_self) invalid_message_2_by_other = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=-1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) invalid_message_2_by_other.sender = self.opponent_address invalid_message_2_by_other.to = self.agent_address result, msg = self.dialogue._basic_validation(invalid_message_2_by_other) assert result is False assert msg == "Invalid target. Expected a non-zero integer. Found 0." def test_basic_validation_initial_message_positive(self): """Positive test for the '_basic_validation_initial_message' method.""" result, msg = self.dialogue._basic_validation_initial_message( self.valid_message_1_by_self ) assert result is True assert msg == "The initial message passes basic validation." def test_basic_validation_initial_message_negative_invalid_dialogue_reference(self): """Negative test for the '_basic_validation' method: input message has invalid dialogue reference.""" invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(2), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address result, msg = self.dialogue._basic_validation_initial_message( invalid_message_1_by_self ) assert result is False assert msg == "Invalid dialogue_reference[0]. Expected 1. Found 2." def test_basic_validation_initial_message_negative_invalid_message_id(self): """Negative test for the '_basic_validation' method: input message has invalid message id.""" invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), message_id=200, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address result, msg = self.dialogue._basic_validation_initial_message( invalid_message_1_by_self ) assert result is False assert re.match("Invalid message_id. Expected .*. Found 200.", msg) def test_basic_validation_initial_message_negative_invalid_target(self): """Negative test for the '_basic_validation_initial_message' method: input message has invalid target.""" invalid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), message_id=1, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_self.sender = self.agent_address invalid_message_1_by_self.to = self.opponent_address result, msg = self.dialogue._basic_validation_initial_message( invalid_message_1_by_self ) assert result is False assert msg == "Invalid target. Expected 0. Found 1." def test_basic_validation_initial_message_negative_invalid_performative(self): """Negative test for the '_basic_validation_initial_message' method: input message has invalid performative.""" invalid_initial_msg = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_MESSAGE, error_msg="some_error_message", error_data={"some_data": b"some_bytes"}, ) invalid_initial_msg.sender = self.agent_address invalid_initial_msg.to = self.opponent_address result, msg = self.dialogue._basic_validation_initial_message( invalid_initial_msg ) assert result is False assert ( msg == "Invalid initial performative. Expected one of {}. Found error.".format( self.dialogue.rules.initial_performatives ) ) def test_basic_validation_non_initial_message_positive(self): """Positive test for the '_basic_validation_non_initial_message' method.""" self.dialogue._update(self.valid_message_1_by_self) result, msg = self.dialogue._basic_validation_non_initial_message( self.valid_message_2_by_other ) assert result is True assert msg == "The non-initial message passes basic validation." def test_basic_validation_non_initial_message_negative_invalid_dialogue_reference( self, ): """Negative test for the '_basic_validation_non_initial_message' method: input message has invalid dialogue reference.""" self.dialogue._update(self.valid_message_1_by_self) invalid_message_2_by_other = DefaultMessage( dialogue_reference=(str(2), str(1)), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) invalid_message_2_by_other.sender = self.opponent_address invalid_message_2_by_other.to = self.agent_address result, msg = self.dialogue._basic_validation_non_initial_message( invalid_message_2_by_other ) assert result is False assert msg == "Invalid dialogue_reference[0]. Expected 1. Found 2." def test_basic_validation_non_initial_message_negative_invalid_message_id(self): """Negative test for the '_basic_validation_non_initial_message' method: input message has invalid message id.""" self.dialogue._update(self.valid_message_1_by_self) invalid_message_2_by_other = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=1000500000, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) invalid_message_2_by_other.sender = self.opponent_address invalid_message_2_by_other.to = self.agent_address result, msg = self.dialogue._basic_validation_non_initial_message( invalid_message_2_by_other ) assert result is False assert re.match("Invalid message_id. Expected .*. Found 1000500000", msg) @patch.object(BaseDialogue, "_validate_message_id", return_value=None) def test_basic_validation_non_initial_message_negative_invalid_target_1( self, *mocks ): """Negative test for the '_basic_validation_non_initial_message' method: input message has target less than 1.""" self.dialogue._update(self.valid_message_1_by_self) invalid_message_2_by_other = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=2, target=0, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) invalid_message_2_by_other.sender = self.opponent_address invalid_message_2_by_other.to = self.agent_address result, msg = self.dialogue._basic_validation_non_initial_message( invalid_message_2_by_other ) assert result is False assert msg == "Invalid target. Expected a non-zero integer. Found 0." def test_basic_validation_non_initial_message_negative_invalid_target_2(self): """Negative test for the '_basic_validation_non_initial_message' method: input message has target greater than the id of the last existing message.""" self.dialogue._update(self.valid_message_1_by_self) invalid_message_2_by_other = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=-1, target=2, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) invalid_message_2_by_other.sender = self.opponent_address invalid_message_2_by_other.to = self.agent_address result, msg = self.dialogue._basic_validation_non_initial_message( invalid_message_2_by_other ) assert result is False assert "Invalid target. Expected a value less than " in msg @patch.object(BaseDialogue, "_validate_message_id", return_value=None) def test_basic_validation_non_initial_message_negative_invalid_performative( self, *mocks ): """Negative test for the '_basic_validation_non_initial_message' method: input message has invalid performative.""" self.dialogue._update(self.valid_message_1_by_self) invalid_message_2_by_other = StateUpdateMessage( dialogue_reference=(str(1), str(1)), message_id=Mock(), target=1, performative=StateUpdateMessage.Performative.APPLY, amount_by_currency_id={}, quantities_by_good_id={}, ) invalid_message_2_by_other.sender = self.opponent_address invalid_message_2_by_other.to = self.agent_address result, msg = self.dialogue._basic_validation_non_initial_message( invalid_message_2_by_other ) assert result is False assert msg == "Invalid performative. Expected one of {}. Found {}.".format( self.dialogue.rules.get_valid_replies( self.valid_message_1_by_self.performative ), invalid_message_2_by_other.performative, ) def test_update_dialogue_label_positive(self): """Positive test for the 'update_dialogue_label' method.""" self.dialogue._update(self.valid_message_1_by_self) new_label = DialogueLabel( (str(1), str(1)), self.valid_message_1_by_self.to, self.agent_address ) self.dialogue._update_dialogue_label(new_label) assert self.dialogue.dialogue_label == new_label def test_update_dialogue_label_negative_invalid_existing_label(self): """Negative test for the 'update_dialogue_label' method: existing dialogue reference is invalid.""" self.dialogue._update(self.valid_message_1_by_self) self.dialogue._update(self.valid_message_2_by_other) new_label = DialogueLabel( (str(1), str(1)), self.valid_message_1_by_self.to, self.agent_address ) self.dialogue._update_dialogue_label(new_label) assert self.dialogue.dialogue_label == new_label new_label = DialogueLabel( (str(1), str(2)), self.valid_message_1_by_self.to, self.agent_address ) with pytest.raises(AEAEnforceError) as cm: self.dialogue._update_dialogue_label(new_label) assert str(cm.value) == "Dialogue label cannot be updated." assert self.dialogue.dialogue_label != new_label def test_update_dialogue_label_negative_invalid_input_label(self): """Negative test for the 'update_dialogue_label' method: input dialogue label's dialogue reference is invalid.""" self.dialogue._update(self.valid_message_1_by_self) new_label = DialogueLabel( (str(2), ""), self.valid_message_1_by_self.to, self.agent_address ) with pytest.raises(AEAEnforceError) as cm: self.dialogue._update_dialogue_label(new_label) assert str(cm.value) == "Dialogue label cannot be updated." assert self.dialogue.dialogue_label != new_label def test___str__1(self): """Test the '__str__' method: dialogue is self initiated""" self.dialogue._update(self.valid_message_1_by_self) self.dialogue._update(self.valid_message_2_by_other) self.dialogue._update(self.valid_message_3_by_self) dialogue_str = "Dialogue Label:\n1__agent 2_agent 1\nMessages:\nmessage_id=1, target=0, performative=bytes\nmessage_id=-1, target=1, performative=bytes\nmessage_id=2, target=-1, performative=bytes\n" assert str(self.dialogue) == dialogue_str def test___str__2(self): """Test the '__str__' method: dialogue is other initiated""" valid_message_1_by_other = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) valid_message_1_by_other.sender = self.opponent_address valid_message_1_by_other.to = self.agent_address self.dialogue_opponent_started._update(valid_message_1_by_other) valid_message_2_by_self = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=-1, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_self.sender = self.agent_address valid_message_2_by_self.to = self.opponent_address self.dialogue_opponent_started._update(valid_message_2_by_self) valid_message_3_by_other = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=2, target=-1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) valid_message_3_by_other.sender = self.opponent_address valid_message_3_by_other.to = self.agent_address self.dialogue_opponent_started._update(valid_message_3_by_other) dialogue_str = "Dialogue Label:\n1_1_agent 2_agent 2\nMessages:\nmessage_id=1, target=0, performative=bytes\nmessage_id=-1, target=1, performative=bytes\nmessage_id=2, target=-1, performative=bytes\n" assert str(self.dialogue_opponent_started) == dialogue_str class TestDialogueStats: """Test for DialogueStats.""" @classmethod def setup(cls): """Initialise the environment to test DialogueStats.""" cls.agent_address = "agent 1" cls.opponent_address = "agent 2" cls.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), dialogue_opponent_addr=cls.opponent_address, dialogue_starter_addr=cls.agent_address, ) cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label) end_states = frozenset( {Dialogue.EndState.SUCCESSFUL, Dialogue.EndState.FAILED} ) # type: FrozenSet[BaseDialogue.EndState] cls.dialogue_stats = DialogueStats(end_states) def test_properties(self): """Test dialogue properties.""" assert isinstance(self.dialogue_stats.self_initiated, dict) assert self.dialogue_stats.self_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } assert self.dialogue_stats.other_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } def test_add_dialogue_endstate(self): """Test for the 'add_dialogue_endstate' method.""" assert self.dialogue_stats.self_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } assert self.dialogue_stats.other_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } self.dialogue_stats.add_dialogue_endstate(Dialogue.EndState.SUCCESSFUL, True) assert self.dialogue_stats.self_initiated == { Dialogue.EndState.SUCCESSFUL: 1, Dialogue.EndState.FAILED: 0, } assert self.dialogue_stats.other_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } self.dialogue_stats.add_dialogue_endstate(Dialogue.EndState.FAILED, False) assert self.dialogue_stats.self_initiated == { Dialogue.EndState.SUCCESSFUL: 1, Dialogue.EndState.FAILED: 0, } assert self.dialogue_stats.other_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 1, } class TestDialoguesBase: """Test for Dialogues.""" def setup(self): """Initialise the environment to test Dialogue.""" self.agent_address = "agent 1" self.opponent_address = "agent 2" self.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), dialogue_opponent_addr=self.opponent_address, dialogue_starter_addr=self.agent_address, ) self.dialogue = Dialogue(dialogue_label=self.dialogue_label) self.own_dialogues = Dialogues(self.agent_address) self.opponent_dialogues = Dialogues(self.opponent_address) # convenient messages to reuse across tests self.valid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) self.valid_message_1_by_self.sender = self.agent_address self.valid_message_1_by_self.to = self.opponent_address self.valid_message_2_by_other = DefaultMessage( dialogue_reference=(str(1), str(1)), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) self.valid_message_2_by_other.sender = self.opponent_address self.valid_message_2_by_other.to = self.agent_address def test_dialogues_properties(self): """Test dialogue properties.""" assert self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label == {} assert self.own_dialogues.self_address == self.agent_address assert self.own_dialogues.dialogue_stats.other_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } assert self.own_dialogues.dialogue_stats.self_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } assert self.own_dialogues.message_class == DefaultMessage assert self.own_dialogues.dialogue_class == Dialogue def test_counterparty_from_message(self): """Test the 'counterparty_from_message' method.""" assert ( self.own_dialogues._counterparty_from_message(self.valid_message_1_by_self) == self.opponent_address ) assert ( self.own_dialogues._counterparty_from_message(self.valid_message_2_by_other) == self.opponent_address ) def test_is_message_by_self(self): """Test the 'is_message_by_self' method.""" assert self.own_dialogues._is_message_by_self(self.valid_message_1_by_self) assert not self.own_dialogues._is_message_by_self(self.valid_message_2_by_other) def test_is_message_by_other(self): """Test the 'is_message_by_other' method.""" assert not self.own_dialogues._is_message_by_other(self.valid_message_1_by_self) assert self.own_dialogues._is_message_by_other(self.valid_message_2_by_other) def test_new_self_initiated_dialogue_reference(self): """Test the 'new_self_initiated_dialogue_reference' method.""" self_initiated_ref = self.own_dialogues.new_self_initiated_dialogue_reference() assert ( isinstance(self_initiated_ref[0], str) and self_initiated_ref[0] != "" and len(self_initiated_ref[0]) == DialogueLabel.NONCE_BYTES_NB * 2 ) assert self_initiated_ref[1] == "" self_initiated_ref_2 = ( self.own_dialogues.new_self_initiated_dialogue_reference() ) assert self_initiated_ref_2 != self_initiated_ref def test_create_positive(self): """Positive test for the 'create' method.""" assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 1 ) def test_create_negative_incorrect_performative_content_combination(self): """Negative test for the 'create' method: invalid performative and content combination (i.e. invalid message).""" assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) with pytest.raises( ValueError, match="Invalid initial performative. Expected one of" ): self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.ERROR, content=b"Hello", ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) def test_update_positive_new_dialogue_by_other(self): """Positive test for the 'update' method: the input message is for a new dialogue dialogue by other.""" valid_message_1_by_other = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) valid_message_1_by_other.sender = self.opponent_address valid_message_1_by_other.to = self.agent_address assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) dialogue = self.own_dialogues.update(valid_message_1_by_other) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 1 ) assert dialogue is not None assert dialogue.last_message.dialogue_reference == (str(1), "") assert dialogue.last_message.message_id == 1 assert dialogue.last_message.target == 0 assert dialogue.last_message.performative == DefaultMessage.Performative.BYTES assert dialogue.last_message.content == b"Hello" def test_update_positive_existing_dialogue(self): """Positive test for the 'update' method: the input message is for an existing dialogue.""" msg, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) dialogue_reference = ( msg.dialogue_reference[0], self.opponent_dialogues._generate_dialogue_nonce(), ) valid_message_2_by_other = DefaultMessage( dialogue_reference=dialogue_reference, message_id=dialogue.get_incoming_next_message_id(), target=msg.message_id, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_other.sender = self.opponent_address valid_message_2_by_other.to = self.agent_address assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 1 ) dialogue = self.own_dialogues.update(valid_message_2_by_other) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 1 ) assert dialogue is not None assert dialogue.last_message.dialogue_reference == dialogue_reference assert dialogue.last_message.message_id == valid_message_2_by_other.message_id assert dialogue.last_message.target == valid_message_2_by_other.target assert dialogue.last_message.performative == DefaultMessage.Performative.BYTES assert dialogue.last_message.content == b"Hello back" def test_update_positive_existing_dialogue_2(self): """Positive test for the 'update' method: the input message is for an existing dialogue from the original sender.""" msg_1, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) opponent_dialogue_1 = self.opponent_dialogues.update(msg_1) msg_2 = dialogue.reply( performative=DefaultMessage.Performative.BYTES, content=b"Hello again", ) opponent_dialogue_2 = self.opponent_dialogues.update(msg_2) assert opponent_dialogue_1 == opponent_dialogue_2 def test_update_negative_invalid_label(self): """Negative test for the 'update' method: dialogue is not extendable with the input message.""" invalid_message_1_by_other = DefaultMessage( dialogue_reference=("", ""), message_id=0, target=0, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_other.sender = self.opponent_address invalid_message_1_by_other.to = self.agent_address assert not self.own_dialogues.update(invalid_message_1_by_other) def test_update_negative_new_dialogue_by_self(self): """Negative test for the 'update' method: the message is not by the counterparty.""" assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) with pytest.raises(AEAEnforceError) as cm: self.own_dialogues.update(self.valid_message_1_by_self) assert ( str(cm.value) == "Invalid 'update' usage. Update must only be used with a message by another agent." ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) def test_update_negative_no_to(self): """Negative test for the 'update' method: the 'to' field of the input message is not set.""" invalid_message_1_by_other = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_other.sender = self.opponent_address assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) with pytest.raises(AEAEnforceError) as cm: self.own_dialogues.update(invalid_message_1_by_other) assert str(cm.value) == "The message's 'to' field is not set {}".format( invalid_message_1_by_other ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) def test_update_negative_no_sender(self): """Negative test for the 'update' method: the 'sender' field of the input message is not set.""" invalid_message_1_by_other = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_other.to = self.agent_address assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) with pytest.raises(AEAEnforceError) as cm: self.own_dialogues.update(invalid_message_1_by_other) assert ( str(cm.value) == "Invalid 'update' usage. Update must only be used with a message by another agent." ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) def test_update_negative_no_matching_to(self): """Negative test for the 'update' method: the 'to' field of the input message does not match self address.""" invalid_message_1_by_other = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) invalid_message_1_by_other.to = self.agent_address + "wrong_stuff" invalid_message_1_by_other.sender = self.opponent_address assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) with pytest.raises(AEAEnforceError) as cm: self.own_dialogues.update(invalid_message_1_by_other) assert ( str(cm.value) == "Message to and dialogue self address do not match. Got 'to=agent 1wrong_stuff' expected 'to=agent 1'." ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) def test_update_negative_invalid_message(self): """Negative test for the 'update' method: the message is invalid.""" invalid_message_1_by_other = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_MESSAGE, error_msg="some_error_message", error_data={"some_data": b"some_bytes"}, ) invalid_message_1_by_other.sender = self.opponent_address invalid_message_1_by_other.to = self.agent_address assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) dialogue = self.own_dialogues.update(invalid_message_1_by_other) assert dialogue is None assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) def test_update_negative_existing_dialogue_non_nonexistent(self): """Negative test for the 'update' method: the dialogue referred by the input message does not exist.""" _, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) invalid_message_2_by_other = DefaultMessage( dialogue_reference=(str(2), str(1)), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) invalid_message_2_by_other.sender = self.opponent_address invalid_message_2_by_other.to = self.agent_address updated_dialogue = self.own_dialogues.update(invalid_message_2_by_other) assert updated_dialogue is None last_message = self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).last_message assert ( last_message.dialogue_reference[0] != "" and last_message.dialogue_reference[1] == "" ) assert ( self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).last_message.message_id == 1 ) assert ( self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).last_message.target == 0 ) assert ( self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).last_message.performative == DefaultMessage.Performative.BYTES ) assert ( self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).last_message.content == b"Hello" ) def test_complete_dialogue_reference_positive( self, ): """Positive test for the '_complete_dialogue_reference' method.""" msg, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) valid_message_2_by_other = DefaultMessage( dialogue_reference=( msg.dialogue_reference[0], self.opponent_dialogues._generate_dialogue_nonce(), ), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_other.sender = self.opponent_address valid_message_2_by_other.to = self.agent_address self.own_dialogues._complete_dialogue_reference(valid_message_2_by_other) assert ( self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).dialogue_label.dialogue_reference == valid_message_2_by_other.dialogue_reference ) def test_complete_dialogue_reference_negative_incorrect_reference( self, ): """Negative test for the '_complete_dialogue_reference' method: the input message has invalid dialogue reference.""" msg, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) wrong_own_ref = ( "wrong reference" # if correct, would be msg.dialogue_reference[0] ) valid_message_2_by_other = DefaultMessage( dialogue_reference=( wrong_own_ref, self.opponent_dialogues._generate_dialogue_nonce(), ), message_id=msg.message_id + 1, target=msg.message_id, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_other.sender = self.opponent_address valid_message_2_by_other.to = self.agent_address self.own_dialogues._complete_dialogue_reference(valid_message_2_by_other) assert ( self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).dialogue_label.dialogue_reference == msg.dialogue_reference ) def test_get_dialogue_positive_1(self): """Positive test for the 'get_dialogue' method: the dialogue is self initiated and the second message is by the other agent.""" msg, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) valid_message_2_by_other = DefaultMessage( dialogue_reference=( msg.dialogue_reference[0], self.opponent_dialogues._generate_dialogue_nonce(), ), message_id=msg.message_id + 1, target=msg.message_id, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_other.sender = self.opponent_address valid_message_2_by_other.to = self.agent_address self.own_dialogues._complete_dialogue_reference(valid_message_2_by_other) assert ( self.own_dialogues._dialogues_storage.get( dialogue.dialogue_label ).dialogue_label.dialogue_reference == valid_message_2_by_other.dialogue_reference ) retrieved_dialogue = self.own_dialogues.get_dialogue(valid_message_2_by_other) assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label def test_get_dialogue_positive_2(self): """Positive test for the 'get_dialogue' method: the dialogue is other initiated and the second message is by this agent.""" valid_message_1_by_other = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) valid_message_1_by_other.sender = self.opponent_address valid_message_1_by_other.to = self.agent_address dialogue = self.own_dialogues.update(valid_message_1_by_other) valid_message_2_by_other = DefaultMessage( dialogue_reference=dialogue.dialogue_label.dialogue_reference, message_id=valid_message_1_by_other.message_id + 1, target=valid_message_1_by_other.message_id, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_other.sender = self.agent_address valid_message_2_by_other.to = self.opponent_address retrieved_dialogue = self.own_dialogues.get_dialogue(valid_message_2_by_other) assert retrieved_dialogue is not None assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label @patch.object(BaseDialogue, "_validate_message_id", return_value=None) def test_get_dialogue_negative_invalid_reference(self, *mocks): """Negative test for the 'get_dialogue' method: the input message has invalid dialogue reference.""" msg, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) valid_message_2_by_other = DefaultMessage( dialogue_reference=( msg.dialogue_reference[0], self.opponent_dialogues._generate_dialogue_nonce(), ), message_id=msg.message_id + 1, target=msg.message_id, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) valid_message_2_by_other.sender = self.opponent_address valid_message_2_by_other.to = self.agent_address dialogue = self.own_dialogues.update(valid_message_2_by_other) assert dialogue is not None invalid_message_3_by_self = DefaultMessage( dialogue_reference=(str(2), str(1)), message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) invalid_message_3_by_self.sender = self.agent_address invalid_message_3_by_self.to = self.opponent_address retrieved_dialogue = self.own_dialogues.get_dialogue(invalid_message_3_by_self) assert retrieved_dialogue is None def test_get_latest_label(self): """Positive test for the 'get_latest_label' method.""" pass def test_get_dialogue_from_label_positive(self): """Positive test for the 'get_dialogue_from_label' method.""" _, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) retrieved_dialogue = self.own_dialogues.get_dialogue_from_label( dialogue.dialogue_label ) assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label def test_get_dialogue_from_label_negative_incorrect_input_label(self): """Negative test for the 'get_dialogue_from_label' method: the input dialogue label does not exist.""" _, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) incorrect_label = DialogueLabel( (str(1), "error"), self.opponent_address, self.agent_address ) retrieved_dialogue = self.own_dialogues.get_dialogue_from_label(incorrect_label) assert retrieved_dialogue is None def test_create_self_initiated_positive(self): """Positive test for the '_create_self_initiated' method.""" assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) self.own_dialogues._create_self_initiated( self.opponent_address, (str(1), ""), Dialogue.Role.ROLE1 ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 1 ) def test_create_self_initiated_negative_invalid_dialogue_reference(self): """Negative test for the '_create_self_initiated' method: invalid dialogue reference""" pass def test_create_opponent_initiated_positive(self): """Positive test for the '_create_opponent_initiated' method.""" assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) self.own_dialogues._create_opponent_initiated( self.opponent_address, (str(1), ""), Dialogue.Role.ROLE2 ) assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 1 ) def test_create_opponent_initiated_negative_invalid_input_dialogue_reference(self): """Negative test for the '_create_opponent_initiated' method: input dialogue label has invalid dialogue reference.""" assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) try: self.own_dialogues._create_opponent_initiated( self.opponent_address, ("", str(1)), Dialogue.Role.ROLE2 ) result = True except AEAEnforceError: result = False assert not result assert ( len(self.own_dialogues._dialogues_storage._dialogues_by_dialogue_label) == 0 ) def test_create_with_message(self): """Positive test for create with message.""" msg = DefaultMessage( dialogue_reference=self.own_dialogues.new_self_initiated_dialogue_reference(), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) self.own_dialogues.create_with_message("opponent", msg) def test__create_positive(self): """Positive test for the '_create' method.""" pass def test__create_negative_incomplete_dialogue_label_present(self): """Negative test for the '_create' method: incomplete dialogue label already present.""" pass def test__create_negative_dialogue_label_present(self): """Negative test for the '_create' method: dialogue label already present.""" pass def test_generate_dialogue_nonce(self): """Test the '_generate_dialogue_nonce' method.""" nonce = self.own_dialogues._generate_dialogue_nonce() assert ( isinstance(nonce, str) and nonce != "" and len(nonce) == DialogueLabel.NONCE_BYTES_NB * 2 ) second_nonce = self.own_dialogues._generate_dialogue_nonce() assert nonce != second_nonce def test_get_dialogues_with_counterparty(self): """Test get dialogues with counterparty.""" assert ( self.own_dialogues.get_dialogues_with_counterparty(self.opponent_address) == [] ) _, dialogue = self.own_dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) assert self.own_dialogues.get_dialogues_with_counterparty( self.opponent_address ) == [dialogue] def test_setup(self): """Test dialogues.setup().""" self.own_dialogues.setup() def test_teardown(self): """Test dialogues.teardown().""" self.own_dialogues.teardown() class TestPersistDialoguesStorage: """Test PersistDialoguesStorage.""" def setup(self): """Initialise the environment to test PersistDialogueStorage.""" self.agent_address = "agent 1" self.opponent_address = "agent 2" self.dialogues = Dialogues( self.agent_address, keep_terminal_state_dialogues=True ) self.dialogues._dialogues_storage = PersistDialoguesStorage(self.dialogues) self.skill_component = Mock() self.skill_component.name = "test_component" self.skill_component.skill_id = PublicId("test", "test", "0.1.0") self.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), dialogue_opponent_addr=self.opponent_address, dialogue_starter_addr=self.agent_address, ) self.generic_storage = Storage("sqlite://:memory:", threaded=True) self.generic_storage.start() wait_for_condition(lambda: self.generic_storage.is_connected, timeout=10) self.skill_component.context.storage = self.generic_storage def teardown(self): """Tear down the environment to test PersistDialogueStorage.""" self.generic_storage.stop() self.generic_storage.wait_completed(sync=True, timeout=10) def test_dialogue_serialize_deserialize(self): """Test dialogue dumped and restored.""" msg = DefaultMessage( dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) dialogue = self.dialogues.create_with_message("opponent", msg) data = dialogue.json() dialogue_restored = dialogue.__class__.from_json(dialogue.message_class, data) assert dialogue == dialogue_restored def test_dump_restore(self): """Test dump and load methods of the persists storage.""" dialogues_storage = PersistDialoguesStorage(self.dialogues) dialogues_storage._skill_component = self.skill_component self.dialogues._dialogues_storage = dialogues_storage dialogues_storage._incomplete_to_complete_dialogue_labels[ self.dialogue_label ] = self.dialogue_label self.dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) msg, dialogue = self.dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello2" ) dialogue.reply( target_message=msg, performative=DefaultMessage.Performative.ERROR, error_code=ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="oops", error_data={}, ) assert dialogues_storage.dialogues_in_terminal_state assert dialogues_storage.dialogues_in_active_state assert dialogues_storage._dialogue_by_address assert dialogues_storage._incomplete_to_complete_dialogue_labels dialogues_storage.teardown() dialogues_storage_restored = PersistDialoguesStorage(self.dialogues) dialogues_storage_restored._skill_component = self.skill_component dialogues_storage_restored.setup() assert len(dialogues_storage._dialogue_by_address) == len( dialogues_storage_restored._dialogue_by_address ) assert len(dialogues_storage._dialogue_by_address) == len( dialogues_storage_restored._dialogue_by_address ) assert ( dialogues_storage._incomplete_to_complete_dialogue_labels == dialogues_storage_restored._incomplete_to_complete_dialogue_labels ) assert set( [str(i.dialogue_label) for i in dialogues_storage.dialogues_in_active_state] ) == set( [ str(i.dialogue_label) for i in dialogues_storage_restored.dialogues_in_active_state ] ) assert set( [ str(i.dialogue_label) for i in dialogues_storage.dialogues_in_terminal_state ] ) == set( [ str(i.dialogue_label) for i in dialogues_storage_restored.dialogues_in_terminal_state ] ) # test remove from storage on storeage.remove assert dialogues_storage_restored._terminal_dialogues_collection dialogue_label = dialogues_storage.dialogues_in_terminal_state[0].dialogue_label assert dialogues_storage_restored._terminal_dialogues_collection.get( str(dialogue_label) ) dialogues_storage_restored.remove(dialogue_label) assert ( dialogues_storage_restored._terminal_dialogues_collection.get( str(dialogue_label) ) is None ) class TestPersistDialoguesStorageOffloading: """Test PersistDialoguesStorage.""" def setup(self): """Initialise the environment to test PersistDialogueStorage.""" self.agent_address = "agent 1" self.opponent_address = "agent 2" self.dialogues = Dialogues( self.agent_address, keep_terminal_state_dialogues=True ) self.skill_component = Mock() self.skill_component.name = "test_component" self.skill_component.skill_id = PublicId("test", "test", "0.1.0") self.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), dialogue_opponent_addr=self.opponent_address, dialogue_starter_addr=self.agent_address, ) self.generic_storage = Storage("sqlite://:memory:", threaded=True) self.generic_storage.start() wait_for_condition(lambda: self.generic_storage.is_connected, timeout=10) self.skill_component.context.storage = self.generic_storage def teardown(self): """Tear down the environment to test PersistDialogueStorage.""" self.generic_storage.stop() self.generic_storage.wait_completed(sync=True, timeout=10) def test_dump_restore(self): """Test dump and load methods of the persists storage.""" dialogues_storage = PersistDialoguesStorageWithOffloading(self.dialogues) dialogues_storage._skill_component = self.skill_component self.dialogues._dialogues_storage = dialogues_storage dialogues_storage._incomplete_to_complete_dialogue_labels[ self.dialogue_label ] = self.dialogue_label self.dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) msg, dialogue = self.dialogues.create( self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello2" ) dialogue.reply( target_message=msg, performative=DefaultMessage.Performative.ERROR, error_code=ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="oops", error_data={}, ) assert dialogues_storage.dialogues_in_terminal_state assert dialogues_storage.dialogues_in_active_state assert dialogues_storage._dialogue_by_address assert dialogues_storage._incomplete_to_complete_dialogue_labels dialogues_by_addr = dialogues_storage.get_dialogues_with_counterparty( dialogue.dialogue_label.dialogue_opponent_addr ) dialogues_storage.teardown() dialogues_storage_restored = PersistDialoguesStorageWithOffloading( self.dialogues ) dialogues_storage_restored._skill_component = self.skill_component dialogues_storage_restored.setup() assert len(dialogues_storage._dialogue_by_address) == len( dialogues_storage_restored._dialogue_by_address ) assert ( dialogues_storage._incomplete_to_complete_dialogue_labels == dialogues_storage_restored._incomplete_to_complete_dialogue_labels ) assert set( [str(i.dialogue_label) for i in dialogues_storage.dialogues_in_active_state] ) == set( [ str(i.dialogue_label) for i in dialogues_storage_restored.dialogues_in_active_state ] ) assert set( [ str(i.dialogue_label) for i in dialogues_storage.dialogues_in_terminal_state ] ) == set( [ str(i.dialogue_label) for i in dialogues_storage_restored.dialogues_in_terminal_state ] ) dialogue_label = dialogues_storage.dialogues_in_terminal_state[0].dialogue_label assert len(dialogues_by_addr) == len( dialogues_storage_restored.get_dialogues_with_counterparty( dialogue.dialogue_label.dialogue_opponent_addr ) ) # check get and cache assert not dialogues_storage_restored._terminal_state_dialogues_labels assert dialogues_storage_restored.get(dialogue_label) is not None assert dialogues_storage_restored._terminal_state_dialogues_labels # test remove from storage on storeage.remove assert dialogues_storage_restored._terminal_dialogues_collection assert ( dialogues_storage_restored._terminal_dialogues_collection.get( str(dialogue_label) ) is not None ) dialogues_storage_restored.remove(dialogue_label) assert ( dialogues_storage_restored._terminal_dialogues_collection.get( str(dialogue_label) ) is None ) assert dialogues_storage_restored.get(dialogue_label) is None class TestBaseDialoguesStorage: """Test PersistDialoguesStorage.""" @classmethod def setup(cls): """Initialise the environment to test Dialogue.""" cls.incomplete_reference = (str(1), "") cls.complete_reference = (str(1), str(1)) cls.opponent_address = "agent 2" cls.agent_address = "agent 1" cls.dialogue_label = DialogueLabel( dialogue_reference=cls.incomplete_reference, dialogue_opponent_addr=cls.opponent_address, dialogue_starter_addr=cls.agent_address, ) cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label) cls.dialogue_label_opponent_started = DialogueLabel( dialogue_reference=cls.complete_reference, dialogue_opponent_addr=cls.opponent_address, dialogue_starter_addr=cls.opponent_address, ) cls.dialogue_opponent_started = Dialogue( dialogue_label=cls.dialogue_label_opponent_started ) # convenient messages to reuse across tests cls.valid_message_1_by_self = DefaultMessage( dialogue_reference=(str(1), ""), performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) cls.valid_message_1_by_self.sender = cls.agent_address cls.valid_message_1_by_self.to = cls.opponent_address cls.storage = BasicDialoguesStorage(Mock()) def test_dialogues_in_terminal_state_kept(self): """Test dialogues in terminal state handled properly.""" self.storage.add(self.dialogue) assert self.storage.dialogues_in_active_state assert not self.storage.dialogues_in_terminal_state self.dialogue._update(self.valid_message_1_by_self) self.dialogue.reply( target_message=self.valid_message_1_by_self, performative=DefaultMessage.Performative.ERROR, error_code=ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="oops", error_data={}, ) assert not self.storage.dialogues_in_active_state assert self.storage.dialogues_in_terminal_state self.storage.remove(self.dialogue.dialogue_label) assert not self.storage.dialogues_in_active_state assert not self.storage.dialogues_in_terminal_state def test_dialogues_in_terminal_state_removed(self): """Test dialogues in terminal state handled properly.""" self.storage._dialogues.is_keep_dialogues_in_terminal_state = False self.storage.add(self.dialogue) assert self.storage.dialogues_in_active_state assert not self.storage.dialogues_in_terminal_state self.dialogue._update(self.valid_message_1_by_self) self.dialogue.reply( target_message=self.valid_message_1_by_self, performative=DefaultMessage.Performative.ERROR, error_code=ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="oops", error_data={}, ) assert not self.storage.dialogues_in_active_state assert not self.storage.dialogues_in_terminal_state def teardown(self): """Tear down the environment to test BaseDialogueStorage.""" def test_find_caller_object(): """Test find_caller_object.""" class CustomSkillComponent(SkillComponent): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.storage = PersistDialoguesStorage(self) def setup(self): pass def teardown(self): pass @classmethod def parse_module(cls, *args, **kwargs): pass skill_component = CustomSkillComponent(Mock(), Mock(), Mock()) assert skill_component.storage._skill_component == skill_component class CustomObject: def __init__(self, *args, **kwargs): self.component = find_caller_object(SkillComponent) custom_object = CustomObject() assert custom_object.component is None def test_dialogues_keep_terminal_state_dialogues(): """Test Dialogues keep_terminal_state_dialogues option.""" initial = Dialogues._keep_terminal_state_dialogues dialogues = Dialogues(Mock(), keep_terminal_state_dialogues=True) assert dialogues.is_keep_dialogues_in_terminal_state is True assert Dialogues._keep_terminal_state_dialogues == initial dialogues = Dialogues(Mock(), keep_terminal_state_dialogues=False) assert dialogues.is_keep_dialogues_in_terminal_state is False assert Dialogues._keep_terminal_state_dialogues == initial ================================================ FILE: tests/test_aea/test_protocols/test_dialogue/test_msg_resolve.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contains the tests for the dialogue messages resolution. issue: https://github.com/fetchai/agents-aea/issues/2128 """ from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.protocols.tac.dialogues import TacDialogue, TacDialogues from packages.fetchai.protocols.tac.message import TacMessage def role_participant( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return TacDialogue.Role.PARTICIPANT def role_controller( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return TacDialogue.Role.PARTICIPANT def test_dialogues_message_resolved_properly(): """ Test for the issue in parallel messages sends in the dialogues system. issue: https://github.com/fetchai/agents-aea/issues/2128 """ addr1 = "addr1" addr2 = "addr2" part1 = TacDialogues(addr1, role_from_first_message=role_participant) part2 = TacDialogues(addr2, role_from_first_message=role_participant) msg, _ = part1.create( addr2, performative=TacMessage.Performative.REGISTER, agent_name=addr1 ) dialogue = part2.update(msg) assert dialogue game_data_msg = dialogue.reply(performative=TacMessage.Performative.GAME_DATA) dialogue = part1.update(game_data_msg) assert dialogue transaction1_msg = dialogue.reply(performative=TacMessage.Performative.TRANSACTION) transaction2_msg = dialogue.reply(performative=TacMessage.Performative.TRANSACTION) dialogue = part2.update(transaction1_msg) assert dialogue comfirmation1_msg = dialogue.reply( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION ) dialogue = part1.update(comfirmation1_msg) assert dialogue transaction3_msg = dialogue.reply(performative=TacMessage.Performative.TRANSACTION) dialogue = part2.update(transaction2_msg) assert dialogue comfirmation2_msg = dialogue.reply( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION ) assert comfirmation2_msg.target == transaction2_msg.message_id dialogue = part1.update(comfirmation2_msg) assert dialogue dialogue = part2.update(transaction3_msg) assert dialogue msg1 = dialogue.reply(performative=TacMessage.Performative.TRANSACTION_CONFIRMATION) # self reply msg2 = dialogue.reply(performative=TacMessage.Performative.TRANSACTION_CONFIRMATION) assert abs(msg2.message_id) - abs(msg1.message_id) == 1 assert msg2.target == msg1.message_id ================================================ FILE: tests/test_aea/test_protocols/test_generator/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The test generator module contains the tests of the AEA protocol generator.""" ================================================ FILE: tests/test_aea/test_protocols/test_generator/common.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains utility code for the test_generator modules.""" import os from tests.conftest import ROOT_DIR T_PROTOCOL_NAME = "t_protocol" T_PROTOCOL_NO_CT_NAME = "t_protocol_no_ct" PATH_TO_T_PROTOCOL_SPECIFICATION = os.path.join( ROOT_DIR, "tests", "data", "sample_specification.yaml" ) PATH_TO_T_PROTOCOL_NO_CT_SPECIFICATION = os.path.join( ROOT_DIR, "tests", "data", "sample_specification_no_custom_types.yaml" ) PATH_TO_T_PROTOCOL = os.path.join( ROOT_DIR, "tests", "data", "generator", T_PROTOCOL_NAME ) PATH_TO_T_PROTOCOL_NO_CT = os.path.join( ROOT_DIR, "tests", "data", "generator", T_PROTOCOL_NO_CT_NAME ) def black_is_not_installed(*args, **kwargs): """Check black is not installed.""" return not args[0] == "black" ================================================ FILE: tests/test_aea/test_protocols/test_generator/test_common.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for generator/common.py module.""" import logging import os import shutil import tempfile from pathlib import Path from subprocess import CalledProcessError # nosec from unittest import TestCase, mock from aea.protocols.generator.common import ( _camel_case_to_snake_case, _create_protocol_file, _get_sub_types_of_compositional_types, _has_matched_brackets, _includes_custom_type, _match_brackets, _python_pt_or_ct_type_to_proto_type, _to_camel_case, _union_sub_type_to_protobuf_variable_name, apply_protolint, base_protolint_command, check_prerequisites, check_protobuf_using_protoc, compile_protobuf_using_protoc, is_installed, load_protocol_specification, try_run_black_formatting, try_run_isort_formatting, try_run_protoc, ) from tests.test_aea.test_protocols.test_generator.common import ( PATH_TO_T_PROTOCOL_SPECIFICATION, T_PROTOCOL_NAME, ) logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) def isort_is_not_installed_side_effect(*args, **kwargs): """Isort not installed.""" return not args[0] == "isort" def protolint_is_not_installed_side_effect(*args, **kwargs): """Protolint not installed.""" return not args[0] == "protolint" def black_is_not_installed_side_effect(*args, **kwargs): """Black not installed.""" return not args[0] == "black" def protoc_is_not_installed_side_effect(*args, **kwargs): """Protoco not installed.""" return not args[0] == "protoc" class TestCommon(TestCase): """Test for generator/common.py.""" @classmethod def setup_class(cls): """Setup test.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def test_to_camel_case(self): """Test the '_to_camel_case' method.""" input_text_1 = "this_is_a_snake_case_text" expected_1 = "ThisIsASnakeCaseText" output_1 = _to_camel_case(input_text_1) assert output_1 == expected_1 input_text_2 = "This_is_a_Snake_Case_text" expected_2 = "ThisIsASnakeCaseText" output_2 = _to_camel_case(input_text_2) assert output_2 == expected_2 def test_camel_case_to_snake_case(self): """Test the '_camel_case_to_snake_case' method.""" input_text_1 = "ThisIsASnakeCaseText" expected_1 = "this_is_a_snake_case_text" output_1 = _camel_case_to_snake_case(input_text_1) assert output_1 == expected_1 def test_match_brackets( self, ): """Positive test the '_match_brackets' method.""" text_1 = "[so[met[hi]]ng]" assert _match_brackets(text_1, 0) == 14 assert _match_brackets(text_1, 3) == 11 assert _match_brackets(text_1, 7) == 10 text_2 = "[]]som[]et[hi[ng][sf]" index_2 = 4 with self.assertRaises(SyntaxError) as cm: _match_brackets(text_2, index_2) self.assertEqual( str(cm.exception), "Index {} in 'text' is not an open bracket '['. It is {}".format( index_2, text_2[index_2], ), ) index_3 = 2 with self.assertRaises(SyntaxError) as cm: _match_brackets(text_2, index_3) self.assertEqual( str(cm.exception), "Index {} in 'text' is not an open bracket '['. It is {}".format( index_3, text_2[index_3], ), ) index_4 = 10 with self.assertRaises(SyntaxError) as cm: _match_brackets(text_2, index_4) self.assertEqual( str(cm.exception), "No matching closing bracket ']' for the opening bracket '[' at {} " + str(index_4), ) def test_has_matched_brackets( self, ): """Positive test the '_has_matched_brackets' method.""" valid_text_1 = "[so[met[hi]]ng]" assert _has_matched_brackets(valid_text_1) is True valid_text_2 = "[[][[]]]" assert _has_matched_brackets(valid_text_2) is True valid_text_3 = "[[[[[[[]]]]]]]" assert _has_matched_brackets(valid_text_3) is True invalid_text_1 = "[]]som[]et[hi[ng][sf]" assert _has_matched_brackets(invalid_text_1) is False invalid_text_2 = "[]][][[][]" assert _has_matched_brackets(invalid_text_2) is False invalid_text_3 = "[]]" assert _has_matched_brackets(invalid_text_3) is False invalid_text_4 = "[[]" assert _has_matched_brackets(invalid_text_4) is False def test_get_sub_types_of_compositional_types_positive( self, ): """Positive test the '_get_sub_types_of_compositional_types' method.""" composition_type_1 = "pt:set[pt:int, integer, bool]" expected_1 = ("pt:int", "integer", "bool") assert _get_sub_types_of_compositional_types(composition_type_1) == expected_1 composition_type_2 = "FrozenSet[something, anotherthing]" expected_2 = ("something", "anotherthing") assert _get_sub_types_of_compositional_types(composition_type_2) == expected_2 composition_type_3 = "pt:list[pt:str]" expected_3 = ("pt:str",) assert _get_sub_types_of_compositional_types(composition_type_3) == expected_3 composition_type_4 = "Tuple[bytes, ...]" expected_4 = ("bytes",) assert _get_sub_types_of_compositional_types(composition_type_4) == expected_4 composition_type_5 = "pt:dict[pt:int, pt:int]" expected_5 = ("pt:int", "pt:int") assert _get_sub_types_of_compositional_types(composition_type_5) == expected_5 composition_type_6 = "Dict[bool, float]" expected_6 = ("bool", "float") assert _get_sub_types_of_compositional_types(composition_type_6) == expected_6 composition_type_7 = "pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]" expected_7 = ( "ct:DataModel", "pt:bytes", "pt:int", "pt:bool", "pt:float", "pt:str", "pt:set[pt:int]", "pt:list[pt:bool]", "pt:dict[pt:str,pt:str]", ) assert _get_sub_types_of_compositional_types(composition_type_7) == expected_7 composition_type_8 = "Union[int, Tuple[bool, ...]]" expected_8 = ("int", "Tuple[bool, ...]") assert _get_sub_types_of_compositional_types(composition_type_8) == expected_8 composition_type_9 = ( "Union[DataModel, FrozenSet[int], Tuple[bool, ...], bytes, Dict[bool,float], int, " "FrozenSet[bool], Dict[int, str], Tuple[str, ...], bool, float, str, Dict[str, str]]" ) expected_9 = ( "DataModel", "FrozenSet[int]", "Tuple[bool, ...]", "bytes", "Dict[bool,float]", "int", "FrozenSet[bool]", "Dict[int, str]", "Tuple[str, ...]", "bool", "float", "str", "Dict[str, str]", ) assert _get_sub_types_of_compositional_types(composition_type_9) == expected_9 composition_type_10 = "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]]" expected_10 = ( "pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]", ) assert _get_sub_types_of_compositional_types(composition_type_10) == expected_10 composition_type_11 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str,str]]]" expected_11 = ( "Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str,str]]", ) assert _get_sub_types_of_compositional_types(composition_type_11) == expected_11 def test_get_sub_types_of_compositional_types_negative( self, ): """Negative test the '_get_sub_types_of_compositional_types' method""" composition_type_1 = "pt:int" with self.assertRaises(SyntaxError) as cm: _get_sub_types_of_compositional_types(composition_type_1) self.assertEqual( str(cm.exception), "{} is not a valid compositional type.".format(composition_type_1), ) composition_type_2 = "pt:int[pt:DataModel]" with self.assertRaises(SyntaxError) as cm: _get_sub_types_of_compositional_types(composition_type_2) self.assertEqual( str(cm.exception), "{} is not a valid compositional type.".format(composition_type_2), ) composition_type_3 = "pt:dict[pt:set[int, pt:list[pt:bool]]" with self.assertRaises(SyntaxError) as cm: _get_sub_types_of_compositional_types(composition_type_3) self.assertEqual( str(cm.exception), "Bad formatting. No matching close bracket ']' for the open bracket at pt:set[", ) def test_union_sub_type_to_protobuf_variable_name( self, ): """Test the '_union_sub_type_to_protobuf_variable_name' method""" content_name = "proposal" content_type_1 = "FrozenSet[int]" assert ( _union_sub_type_to_protobuf_variable_name(content_name, content_type_1) == "proposal_type_set_of_int" ) content_type_2 = "Tuple[str, ...]" assert ( _union_sub_type_to_protobuf_variable_name(content_name, content_type_2) == "proposal_type_list_of_str" ) content_type_3 = "Dict[bool, float]" assert ( _union_sub_type_to_protobuf_variable_name(content_name, content_type_3) == "proposal_type_dict_of_bool_float" ) content_type_4 = "int" assert ( _union_sub_type_to_protobuf_variable_name(content_name, content_type_4) == "proposal_type_int" ) content_type_5 = "DataModel" assert ( _union_sub_type_to_protobuf_variable_name(content_name, content_type_5) == "proposal_type_DataModel" ) def test_python_pt_or_ct_type_to_proto_type( self, ): """Test the '_python_pt_or_ct_type_to_proto_type' method""" content_type_bytes = "bytes" assert _python_pt_or_ct_type_to_proto_type(content_type_bytes) == "bytes" content_type_int = "int" assert _python_pt_or_ct_type_to_proto_type(content_type_int) == "int64" content_type_float = "float" assert _python_pt_or_ct_type_to_proto_type(content_type_float) == "float" content_type_bool = "bool" assert _python_pt_or_ct_type_to_proto_type(content_type_bool) == "bool" content_type_str = "str" assert _python_pt_or_ct_type_to_proto_type(content_type_str) == "string" content_type_ct = "Query" assert _python_pt_or_ct_type_to_proto_type(content_type_ct) == "Query" def test_includes_custom_type( self, ): """Test the '_includes_custom_type' method""" content_type_includes_1 = "Optional[DataModel]" assert _includes_custom_type(content_type_includes_1) is True content_type_includes_2 = "Union[int, DataModel]" assert _includes_custom_type(content_type_includes_2) is True content_type_includes_3 = "Optional[Union[int, float, DataModel, Query, float]]" assert _includes_custom_type(content_type_includes_3) is True content_type_not_includes_1 = "Optional[int]" assert _includes_custom_type(content_type_not_includes_1) is False content_type_not_includes_2 = "Union[int, float, str]" assert _includes_custom_type(content_type_not_includes_2) is False content_type_not_includes_3 = ( "Optional[Union[int, float, FrozenSet[int], Tuple[bool, ...], float]]" ) assert _includes_custom_type(content_type_not_includes_3) is False @mock.patch("shutil.which", return_value="some string") def test_is_installed_positive(self, mocked_shutil_which): """Positive test for the 'is_installed' method""" assert is_installed("some_programme") is True @mock.patch("shutil.which", return_value=None) def test_is_installed_negative(self, mocked_shutil_which): """Negative test for the 'is_installed' method: programme is not installed""" assert is_installed("some_programme") is False def test_base_protolint_command(self): """Tests the 'base_protolint_command' method""" assert ( base_protolint_command() == "protolint" or "PATH=${PATH}:${GOPATH}/bin/:~/go/bin protolint" ) @mock.patch("aea.protocols.generator.common.is_installed", return_value=True) def test_check_prerequisites_positive(self, mocked_is_installed): """Positive test for the 'check_prerequisites' method""" try: check_prerequisites() except FileNotFoundError: self.assertTrue(False) @mock.patch( "aea.protocols.generator.common.is_installed", side_effect=black_is_not_installed_side_effect, ) def test_check_prerequisites_negative_black_is_not_installed( self, mocked_is_installed ): """Negative test for the 'check_prerequisites' method: black isn't installed""" with self.assertRaises(FileNotFoundError): check_prerequisites() @mock.patch( "aea.protocols.generator.common.is_installed", side_effect=isort_is_not_installed_side_effect, ) def test_check_prerequisites_negative_isort_is_not_installed( self, mocked_is_installed ): """Negative test for the 'check_prerequisites' method: isort isn't installed""" with self.assertRaises(FileNotFoundError): check_prerequisites() @mock.patch( "aea.protocols.generator.common.subprocess.call", return_value=1, ) def test_check_prerequisites_negative_protolint_is_not_installed( self, mocked_is_installed ): """Negative test for the 'check_prerequisites' method: protolint isn't installed""" with self.assertRaises(FileNotFoundError): check_prerequisites() @mock.patch( "aea.protocols.generator.common.is_installed", side_effect=protoc_is_not_installed_side_effect, ) def test_check_prerequisites_negative_protoc_is_not_installed( self, mocked_is_installed ): """Negative test for the 'check_prerequisites' method: protoc isn't installed""" with self.assertRaises(FileNotFoundError): check_prerequisites() def test_load_protocol_specification( self, ): """Test the 'load_protocol_specification' method""" spec = load_protocol_specification(PATH_TO_T_PROTOCOL_SPECIFICATION) assert spec.name == T_PROTOCOL_NAME assert spec.version == "0.1.0" assert spec.author == "fetchai" assert spec.license == "Apache-2.0" assert spec.aea_version == ">=1.0.0, <2.0.0" assert spec.description == "A protocol for testing purposes." assert spec.speech_acts is not None assert spec.protobuf_snippets is not None and spec.protobuf_snippets != "" def test_create_protocol_file( self, ): """Test the '_create_protocol_file' method""" file_name = "temp_file" file_content = "this is a temporary file" _create_protocol_file(self.t, file_name, file_content) path_to_the_file = os.path.join(self.t, file_name) assert Path(path_to_the_file).exists() assert Path(path_to_the_file).read_text() == file_content @mock.patch("subprocess.run") def test_try_run_black_formatting(self, mocked_subprocess): """Test the 'try_run_black_formatting' method""" try_run_black_formatting("some_path") mocked_subprocess.assert_called_once() @mock.patch("subprocess.run") def test_try_run_isort_formatting(self, mocked_subprocess): """Test the 'try_run_isort_formatting' method""" try_run_isort_formatting("some_path") mocked_subprocess.assert_called_once() @mock.patch("subprocess.run") def test_try_run_protoc(self, mocked_subprocess): """Test the 'try_run_protoc' method""" try_run_protoc("some_path", "some_name") mocked_subprocess.assert_called_once() @mock.patch("subprocess.run") def test_try_run_protolint(self, mocked_subprocess): """Test the 'try_run_protolint' method""" try_run_protoc("some_path", "some_name") mocked_subprocess.assert_called_once() @mock.patch("aea.protocols.generator.common.try_run_protoc") def test_check_protobuf_using_protoc_positive(self, mocked_try_run_protoc): """Positive test for the 'check_protobuf_using_protoc' method""" protocol_name = "protocol_name" file_name = protocol_name + "_pb2.py" new_file = open(os.path.join(self.t, file_name), "w+") new_file.close() result, msg = check_protobuf_using_protoc(self.t, protocol_name) assert not Path(self.t, file_name).exists() assert result is True assert msg == "protobuf file is valid" @mock.patch( "subprocess.run", side_effect=CalledProcessError( 1, "some_command", stderr="name.proto:12:45: some_protoc_error\n" ), ) def test_check_protobuf_using_protoc_nagative(self, mocked_subprocess): """Negative test for the 'check_protobuf_using_protoc' method: protoc has some errors""" result, msg = check_protobuf_using_protoc("some_path", "name") assert result is False assert msg == "some_protoc_error" @mock.patch("aea.protocols.generator.common.try_run_protoc") def test_compile_protobuf_using_protoc_positive(self, mocked_try_run_protoc): """Positive test for the 'compile_protobuf_using_protoc' method""" protocol_name = "protocol_name" result, msg = compile_protobuf_using_protoc(self.t, protocol_name, "python") mocked_try_run_protoc.assert_called_once() assert result is True assert msg == "protobuf schema successfully compiled" @mock.patch( "subprocess.run", side_effect=CalledProcessError( 1, "some_command", stderr="protocol_name.proto:12:45: some_protoc_error\n" ), ) def test_compile_protobuf_using_protoc_nagative(self, mocked_subprocess): """Negative test for the 'check_protobuf_using_protoc' method: protoc has some errors""" protocol_name = "protocol_name" result, msg = compile_protobuf_using_protoc(self.t, protocol_name, "python") assert result is False assert msg == "some_protoc_error" @mock.patch("aea.protocols.generator.common.try_run_protolint") def test_apply_protolint_positive(self, mocked_try_run_protoc): """Positive test for the 'apply_protolint' method""" protocol_name = "protocol_name" result, msg = apply_protolint(self.t, protocol_name) mocked_try_run_protoc.assert_called_once() assert result is True assert msg == "protolint has no output" @mock.patch( "subprocess.run", side_effect=CalledProcessError( 1, "some_command", stderr="protocol_name.proto:12:45: some_protoc_error\nprotocol_name.proto:12:45: incorrect indentation style ...", ), ) def test_apply_protolint_nagative(self, mocked_subprocess): """Negative test for the 'apply_protolint' method: protoc has some errors""" protocol_name = "protocol_name" result, msg = apply_protolint(self.t, protocol_name) assert result is False assert msg == "protocol_name.proto:12:45: some_protoc_error" @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_protocols/test_generator/test_end_to_end.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains end to end tests for the protocol generator.""" import logging import os import shutil import tempfile import time from pathlib import Path from threading import Thread from typing import Optional, cast from aea.aea_builder import AEABuilder from aea.configurations.base import ComponentType, PublicId, SkillConfig from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE from aea.crypto.helpers import create_private_key from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.skills.base import Handler, Skill, SkillContext from packages.fetchai.connections.oef.connection import ( PUBLIC_ID as OEF_CONNECTION_PUBLIC_ID, ) from tests.common.utils import UseOef from tests.conftest import ROOT_DIR from tests.data.generator.t_protocol.dialogues import ( TProtocolDialogue, TProtocolDialogues, ) from tests.data.generator.t_protocol.message import TProtocolMessage # type: ignore from tests.test_aea.test_protocols.test_generator.common import PATH_TO_T_PROTOCOL logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) class TestEndToEndGenerator(UseOef): """ Test that the generating a protocol works correctly in correct preconditions. Note: Types involving Floats seem to lose some precision when serialised then deserialised using protobuf. So tests for these types are commented out throughout for now. """ @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() shutil.copytree(Path(ROOT_DIR, "packages"), Path(cls.t, "packages")) os.chdir(cls.t) cls.private_key_path_1 = os.path.join(cls.t, DEFAULT_PRIVATE_KEY_FILE + "_1") cls.private_key_path_2 = os.path.join(cls.t, DEFAULT_PRIVATE_KEY_FILE + "_2") create_private_key(DEFAULT_LEDGER, cls.private_key_path_1) create_private_key(DEFAULT_LEDGER, cls.private_key_path_2) def test_generated_protocol_end_to_end(self): """Test that a generated protocol could be used in exchanging messages between two agents.""" agent_name_1 = "my_aea_1" agent_name_2 = "my_aea_2" builder_1 = AEABuilder() builder_1.set_name(agent_name_1) builder_1.add_private_key(DEFAULT_LEDGER, self.private_key_path_1) builder_1.set_default_ledger(DEFAULT_LEDGER) builder_1.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa") ) builder_1.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") ) builder_1.add_component( ComponentType.PROTOCOL, Path(PATH_TO_T_PROTOCOL), skip_consistency_check=True, ) builder_1.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "oef") ) builder_1.set_default_connection(OEF_CONNECTION_PUBLIC_ID) builder_2 = AEABuilder() builder_2.set_name(agent_name_2) builder_2.add_private_key(DEFAULT_LEDGER, self.private_key_path_2) builder_2.set_default_ledger(DEFAULT_LEDGER) builder_2.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa") ) builder_2.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") ) builder_2.add_component( ComponentType.PROTOCOL, Path(PATH_TO_T_PROTOCOL), skip_consistency_check=True, ) builder_2.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "oef") ) builder_2.set_default_connection(OEF_CONNECTION_PUBLIC_ID) # create AEAs aea_1 = builder_1.build(connection_ids=[OEF_CONNECTION_PUBLIC_ID]) aea_2 = builder_2.build(connection_ids=[OEF_CONNECTION_PUBLIC_ID]) # dialogues def role_from_first_message_1( message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return TProtocolDialogue.Role.ROLE_1 agent_1_dialogues = TProtocolDialogues( self_address=aea_1.identity.address, role_from_first_message=role_from_first_message_1, ) def role_from_first_message_1( message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return TProtocolDialogue.Role.ROLE_2 agent_2_dialogues = TProtocolDialogues( self_address=aea_2.identity.address, role_from_first_message=role_from_first_message_1, ) # messages message_1, aea_1_dialogue = agent_1_dialogues.create( counterparty=aea_2.identity.address, performative=TProtocolMessage.Performative.PERFORMATIVE_PT, content_bytes=b"some bytes", content_int=42, content_float=42.7, content_bool=True, content_str="some string", ) message_1 = cast(TProtocolMessage, message_1) message_2, aea_2_dialogue = agent_2_dialogues.create( counterparty=aea_1.identity.address, performative=TProtocolMessage.Performative.PERFORMATIVE_PT, content_bytes=b"some other bytes", content_int=43, content_float=43.7, content_bool=False, content_str="some other string", ) message_2 = cast(TProtocolMessage, message_2) # add handlers to AEA resources skill_context_1 = SkillContext(aea_1.context) skill_1 = Skill(SkillConfig("fake_skill", "fetchai", "0.1.0"), skill_context_1) skill_context_1._skill = skill_1 agent_1_handler = Agent1Handler( skill_context=skill_context_1, name="fake_handler_1", dialogues=agent_1_dialogues, ) aea_1.resources._handler_registry.register( ( PublicId.from_str("fetchai/fake_skill:0.1.0"), TProtocolMessage.protocol_id, ), agent_1_handler, ) skill_context_2 = SkillContext(aea_2.context) skill_2 = Skill(SkillConfig("fake_skill", "fetchai", "0.1.0"), skill_context_2) skill_context_2._skill = skill_2 agent_2_handler = Agent2Handler( message=message_2, dialogues=agent_2_dialogues, skill_context=skill_context_2, name="fake_handler_2", ) aea_2.resources._handler_registry.register( ( PublicId.from_str("fetchai/fake_skill:0.1.0"), TProtocolMessage.protocol_id, ), agent_2_handler, ) # Start threads t_1 = Thread(target=aea_1.start) t_2 = Thread(target=aea_2.start) try: t_1.start() t_2.start() time.sleep(1.0) aea_1.outbox.put_message(message_1) time.sleep(5.0) assert ( agent_2_handler.handled_message.message_id == message_1.message_id ), "Message from Agent 1 to 2: message ids do not match" assert ( agent_2_handler.handled_message.dialogue_reference == message_1.dialogue_reference ), "Message from Agent 1 to 2: dialogue references do not match" assert ( agent_2_handler.handled_message.dialogue_reference[0] == message_1.dialogue_reference[0] ), "Message from Agent 1 to 2: dialogue reference[0]s do not match" assert ( agent_2_handler.handled_message.dialogue_reference[1] == message_1.dialogue_reference[1] ), "Message from Agent 1 to 2: dialogue reference[1]s do not match" assert ( agent_2_handler.handled_message.target == message_1.target ), "Message from Agent 1 to 2: targets do not match" assert ( agent_2_handler.handled_message.performative == message_1.performative ), "Message from Agent 1 to 2: performatives do not match" assert ( agent_2_handler.handled_message.content_bytes == message_1.content_bytes ), "Message from Agent 1 to 2: content_bytes do not match" assert ( agent_2_handler.handled_message.content_int == message_1.content_int ), "Message from Agent 1 to 2: content_int do not match" # assert ( # agent_2_handler.handled_message.content_float == message_1.content_float # noqa: E800 # ), "Message from Agent 1 to 2: content_float do not match" assert ( agent_2_handler.handled_message.content_bool == message_1.content_bool ), "Message from Agent 1 to 2: content_bool do not match" assert ( agent_2_handler.handled_message.content_str == message_1.content_str ), "Message from Agent 1 to 2: content_str do not match" assert ( agent_1_handler.handled_message.message_id == message_2.message_id ), "Message from Agent 1 to 2: dialogue references do not match" assert ( agent_1_handler.handled_message.dialogue_reference == message_2.dialogue_reference ), "Message from Agent 2 to 1: dialogue references do not match" assert ( agent_1_handler.handled_message.dialogue_reference[0] == message_2.dialogue_reference[0] ), "Message from Agent 2 to 1: dialogue reference[0]s do not match" assert ( agent_1_handler.handled_message.dialogue_reference[1] == message_2.dialogue_reference[1] ), "Message from Agent 2 to 1: dialogue reference[1]s do not match" assert ( agent_1_handler.handled_message.target == message_2.target ), "Message from Agent 2 to 1: targets do not match" assert ( agent_1_handler.handled_message.performative == message_2.performative ), "Message from Agent 2 to 1: performatives do not match" assert ( agent_1_handler.handled_message.content_bytes == message_2.content_bytes ), "Message from Agent 2 to 1: content_bytes do not match" assert ( agent_1_handler.handled_message.content_int == message_2.content_int ), "Message from Agent 2 to 1: content_int do not match" # assert ( # agent_1_handler.handled_message.content_float == message_2.content_float # noqa: E800 # ), "Message from Agent 2 to 1: content_float do not match" assert ( agent_1_handler.handled_message.content_bool == message_2.content_bool ), "Message from Agent 2 to 1: content_bool do not match" assert ( agent_1_handler.handled_message.content_str == message_2.content_str ), "Message from Agent 2 to 1: content_str do not match" time.sleep(2.0) finally: aea_1.stop() aea_2.stop() t_1.join() t_2.join() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class Agent1Handler(Handler): """The handler for agent 1.""" SUPPORTED_PROTOCOL = TProtocolMessage.protocol_id # type: Optional[PublicId] def __init__(self, dialogues: TProtocolDialogues, **kwargs): """Initialize the handler.""" super().__init__(**kwargs) self.kwargs = kwargs self.handled_message = None # type: Optional[TProtocolMessage] self.dialogues = dialogues def setup(self) -> None: """Implement the setup for the handler.""" pass def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ message = cast(TProtocolMessage, message) self.dialogues.update(message) self.handled_message = message def teardown(self) -> None: """ Implement the handler teardown. :return: None """ class Agent2Handler(Handler): """The handler for agent 2.""" SUPPORTED_PROTOCOL = TProtocolMessage.protocol_id # type: Optional[PublicId] def __init__( self, message: TProtocolMessage, dialogues: TProtocolDialogues, **kwargs ): """Initialize the handler.""" print("inside handler's initialisation method for agent 2") super().__init__(**kwargs) self.kwargs = kwargs self.handled_message = None # type: Optional[TProtocolMessage] self.message_2 = message self.dialogues = dialogues def setup(self) -> None: """Implement the setup for the handler.""" pass def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ message = cast(TProtocolMessage, message) dialogue = self.dialogues.update(message) self.handled_message = message assert ( dialogue is not None ), "Agent 2 didn't update dialogue with incoming message {}".format( str(message) ) dialogue.reply( target_message=message, performative=self.message_2.performative, content_bytes=self.message_2.content_bytes, content_int=self.message_2.content_int, content_float=self.message_2.content_float, content_bool=self.message_2.content_bool, content_str=self.message_2.content_str, ) self.context.outbox.put_message(self.message_2) def teardown(self) -> None: """ Implement the handler teardown. :return: None """ ================================================ FILE: tests/test_aea/test_protocols/test_generator/test_extract_specification.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for generator/extract_specification.py module.""" import logging import os import shutil import tempfile from unittest import TestCase from aea.configurations.base import ProtocolSpecificationParseError from aea.protocols.generator.common import load_protocol_specification from aea.protocols.generator.extract_specification import ( PythonicProtocolSpecification, _ct_specification_type_to_python_type, _mt_specification_type_to_python_type, _optional_specification_type_to_python_type, _pct_specification_type_to_python_type, _pmt_specification_type_to_python_type, _pt_specification_type_to_python_type, _specification_type_to_python_type, extract, ) from tests.test_aea.test_protocols.test_generator.common import ( PATH_TO_T_PROTOCOL_SPECIFICATION, ) logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) class TestExtractSpecification(TestCase): """Test for generator/extract_specification.py.""" @classmethod def setup_class(cls): """Setup class.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def test_ct_specification_type_to_python_type(self): """Test the '_ct_specification_type_to_python_type' method.""" specification_type_1 = "ct:DataModel" expected_1 = "DataModel" assert _ct_specification_type_to_python_type(specification_type_1) == expected_1 specification_type_2 = "ct:Query" expected_2 = "Query" assert _ct_specification_type_to_python_type(specification_type_2) == expected_2 def test_pt_specification_type_to_python_type(self): """Test the '_pt_specification_type_to_python_type' method.""" specification_type_1 = "pt:bytes" expected_1 = "bytes" assert _pt_specification_type_to_python_type(specification_type_1) == expected_1 specification_type_2 = "pt:int" expected_2 = "int" assert _pt_specification_type_to_python_type(specification_type_2) == expected_2 specification_type_3 = "pt:float" expected_3 = "float" assert _pt_specification_type_to_python_type(specification_type_3) == expected_3 specification_type_4 = "pt:bool" expected_4 = "bool" assert _pt_specification_type_to_python_type(specification_type_4) == expected_4 specification_type_5 = "pt:str" expected_5 = "str" assert _pt_specification_type_to_python_type(specification_type_5) == expected_5 def test_pct_specification_type_to_python_type(self): """Test the '_pct_specification_type_to_python_type' method.""" specification_type_1 = "pt:set[pt:bytes]" expected_1 = "FrozenSet[bytes]" assert ( _pct_specification_type_to_python_type(specification_type_1) == expected_1 ) specification_type_2 = "pt:set[pt:int]" expected_2 = "FrozenSet[int]" assert ( _pct_specification_type_to_python_type(specification_type_2) == expected_2 ) specification_type_3 = "pt:set[pt:float]" expected_3 = "FrozenSet[float]" assert ( _pct_specification_type_to_python_type(specification_type_3) == expected_3 ) specification_type_4 = "pt:set[pt:bool]" expected_4 = "FrozenSet[bool]" assert ( _pct_specification_type_to_python_type(specification_type_4) == expected_4 ) specification_type_5 = "pt:set[pt:str]" expected_5 = "FrozenSet[str]" assert ( _pct_specification_type_to_python_type(specification_type_5) == expected_5 ) specification_type_6 = "pt:list[pt:bytes]" expected_6 = "Tuple[bytes, ...]" assert ( _pct_specification_type_to_python_type(specification_type_6) == expected_6 ) specification_type_7 = "pt:list[pt:int]" expected_7 = "Tuple[int, ...]" assert ( _pct_specification_type_to_python_type(specification_type_7) == expected_7 ) specification_type_8 = "pt:list[pt:float]" expected_8 = "Tuple[float, ...]" assert ( _pct_specification_type_to_python_type(specification_type_8) == expected_8 ) specification_type_9 = "pt:list[pt:bool]" expected_9 = "Tuple[bool, ...]" assert ( _pct_specification_type_to_python_type(specification_type_9) == expected_9 ) specification_type_10 = "pt:list[pt:str]" expected_10 = "Tuple[str, ...]" assert ( _pct_specification_type_to_python_type(specification_type_10) == expected_10 ) def test_pmt_specification_type_to_python_type(self): """Test the '_pmt_specification_type_to_python_type' method.""" specification_type_1 = "pt:dict[pt:int, pt:bytes]" expected_1 = "Dict[int, bytes]" assert ( _pmt_specification_type_to_python_type(specification_type_1) == expected_1 ) specification_type_2 = "pt:dict[pt:int, pt:int]" expected_2 = "Dict[int, int]" assert ( _pmt_specification_type_to_python_type(specification_type_2) == expected_2 ) specification_type_3 = "pt:dict[pt:int, pt:float]" expected_3 = "Dict[int, float]" assert ( _pmt_specification_type_to_python_type(specification_type_3) == expected_3 ) specification_type_4 = "pt:dict[pt:int, pt:bool]" expected_4 = "Dict[int, bool]" assert ( _pmt_specification_type_to_python_type(specification_type_4) == expected_4 ) specification_type_5 = "pt:dict[pt:int, pt:str]" expected_5 = "Dict[int, str]" assert ( _pmt_specification_type_to_python_type(specification_type_5) == expected_5 ) specification_type_6 = "pt:dict[pt:bool, pt:bytes]" expected_6 = "Dict[bool, bytes]" assert ( _pmt_specification_type_to_python_type(specification_type_6) == expected_6 ) specification_type_7 = "pt:dict[pt:bool, pt:int]" expected_7 = "Dict[bool, int]" assert ( _pmt_specification_type_to_python_type(specification_type_7) == expected_7 ) specification_type_8 = "pt:dict[pt:bool, pt:float]" expected_8 = "Dict[bool, float]" assert ( _pmt_specification_type_to_python_type(specification_type_8) == expected_8 ) specification_type_9 = "pt:dict[pt:bool, pt:bool]" expected_9 = "Dict[bool, bool]" assert ( _pmt_specification_type_to_python_type(specification_type_9) == expected_9 ) specification_type_10 = "pt:dict[pt:bool, pt:str]" expected_10 = "Dict[bool, str]" assert ( _pmt_specification_type_to_python_type(specification_type_10) == expected_10 ) specification_type_11 = "pt:dict[pt:str, pt:bytes]" expected_11 = "Dict[str, bytes]" assert ( _pmt_specification_type_to_python_type(specification_type_11) == expected_11 ) specification_type_12 = "pt:dict[pt:str, pt:int]" expected_12 = "Dict[str, int]" assert ( _pmt_specification_type_to_python_type(specification_type_12) == expected_12 ) specification_type_13 = "pt:dict[pt:str, pt:float]" expected_13 = "Dict[str, float]" assert ( _pmt_specification_type_to_python_type(specification_type_13) == expected_13 ) specification_type_14 = "pt:dict[pt:str, pt:bool]" expected_14 = "Dict[str, bool]" assert ( _pmt_specification_type_to_python_type(specification_type_14) == expected_14 ) specification_type_15 = "pt:dict[pt:str, pt:str]" expected_15 = "Dict[str, str]" assert ( _pmt_specification_type_to_python_type(specification_type_15) == expected_15 ) def test_mt_specification_type_to_python_type(self): """Test the '_mt_specification_type_to_python_type' method.""" specification_type_1 = "pt:union[pt:int, pt:bytes]" expected_1 = "Union[int, bytes]" assert _mt_specification_type_to_python_type(specification_type_1) == expected_1 specification_type_2 = "pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]" expected_2 = "Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]" assert _mt_specification_type_to_python_type(specification_type_2) == expected_2 specification_type_3 = ( "pt:union[ct:DataModel, pt:set[pt:int], pt:list[pt:bool], pt:bytes, pt:dict[pt:bool,pt:float], pt:int, " "pt:set[pt:bool], pt:dict[pt:int, pt:str], pt:list[pt:str], pt:bool, pt:float, pt:str, pt:dict[pt:str, pt:str]]" ) expected_3 = ( "Union[DataModel, FrozenSet[int], Tuple[bool, ...], bytes, Dict[bool, float], int, " "FrozenSet[bool], Dict[int, str], Tuple[str, ...], bool, float, str, Dict[str, str]]" ) assert _mt_specification_type_to_python_type(specification_type_3) == expected_3 def test_optional_specification_type_to_python_type(self): """Test the '_optional_specification_type_to_python_type' method.""" specification_type_1 = ( "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], " "pt:list[pt:bool], pt:dict[pt:str, pt:str]]]" ) expected_1 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]]" assert ( _optional_specification_type_to_python_type(specification_type_1) == expected_1 ) specification_type_2 = ( "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], " "pt:list[pt:bool], pt:dict[pt:str,pt:str]]]" ) expected_2 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]]" assert ( _optional_specification_type_to_python_type(specification_type_2) == expected_2 ) specification_type_3 = "pt:optional[ct:DataModel]" expected_3 = "Optional[DataModel]" assert ( _optional_specification_type_to_python_type(specification_type_3) == expected_3 ) def test_specification_type_to_python_type(self): """Test the '_specification_type_to_python_type' method.""" specification_type_1 = "ct:DataModel" expected_1 = "DataModel" assert _specification_type_to_python_type(specification_type_1) == expected_1 specification_type_2 = "pt:bytes" expected_2 = "bytes" assert _specification_type_to_python_type(specification_type_2) == expected_2 specification_type_3 = "pt:set[pt:int]" expected_3 = "FrozenSet[int]" assert _specification_type_to_python_type(specification_type_3) == expected_3 specification_type_4 = "pt:list[pt:float]" expected_4 = "Tuple[float, ...]" assert _specification_type_to_python_type(specification_type_4) == expected_4 specification_type_5 = "pt:dict[pt:bool, pt:str]" expected_5 = "Dict[bool, str]" assert _specification_type_to_python_type(specification_type_5) == expected_5 specification_type_6 = "pt:union[pt:int, pt:bytes]" expected_6 = "Union[int, bytes]" assert _specification_type_to_python_type(specification_type_6) == expected_6 specification_type_7 = ( "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], " "pt:list[pt:bool], pt:dict[pt:str,pt:str]]]" ) expected_7 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]]" assert _specification_type_to_python_type(specification_type_7) == expected_7 specification_type_8 = "wrong_type" with self.assertRaises(ProtocolSpecificationParseError) as cm: _specification_type_to_python_type(specification_type_8) self.assertEqual( str(cm.exception), "Unsupported type: '{}'".format(specification_type_8) ) specification_type_9 = "pt:integer" with self.assertRaises(ProtocolSpecificationParseError) as cm: _specification_type_to_python_type(specification_type_9) self.assertEqual( str(cm.exception), "Unsupported type: '{}'".format(specification_type_9) ) specification_type_10 = "pt: list" with self.assertRaises(ProtocolSpecificationParseError) as cm: _specification_type_to_python_type(specification_type_10) self.assertEqual( str(cm.exception), "Unsupported type: '{}'".format(specification_type_10) ) specification_type_11 = "pt:list[wrong_sub_type]" with self.assertRaises(ProtocolSpecificationParseError) as cm: _specification_type_to_python_type(specification_type_11) self.assertEqual(str(cm.exception), "Unsupported type: 'wrong_sub_type'") def test_pythonic_protocol_specification_class(self): """Test the 'PythonicProtocolSpecification' class.""" spec = PythonicProtocolSpecification() assert spec.speech_acts == {} assert spec.all_performatives == [] assert spec.all_unique_contents == {} assert spec.all_custom_types == [] assert spec.custom_custom_types == {} assert spec.initial_performatives == [] assert spec.reply == {} assert spec.terminal_performatives == [] assert spec.roles == [] assert spec.end_states == [] assert spec.typing_imports == { "Set": True, "Tuple": True, "cast": True, "FrozenSet": False, "Dict": False, "Union": False, "Optional": False, } def test_extract_positive(self): """Positive test the 'extract' method.""" protocol_specification = load_protocol_specification( PATH_TO_T_PROTOCOL_SPECIFICATION ) spec = extract(protocol_specification) assert spec.speech_acts == { "performative_ct": {"content_ct": "DataModel"}, "performative_pt": { "content_bytes": "bytes", "content_int": "int", "content_float": "float", "content_bool": "bool", "content_str": "str", }, "performative_pct": { "content_set_bytes": "FrozenSet[bytes]", "content_set_int": "FrozenSet[int]", "content_set_float": "FrozenSet[float]", "content_set_bool": "FrozenSet[bool]", "content_set_str": "FrozenSet[str]", "content_list_bytes": "Tuple[bytes, ...]", "content_list_int": "Tuple[int, ...]", "content_list_float": "Tuple[float, ...]", "content_list_bool": "Tuple[bool, ...]", "content_list_str": "Tuple[str, ...]", }, "performative_pmt": { "content_dict_int_bytes": "Dict[int, bytes]", "content_dict_int_int": "Dict[int, int]", "content_dict_int_float": "Dict[int, float]", "content_dict_int_bool": "Dict[int, bool]", "content_dict_int_str": "Dict[int, str]", "content_dict_bool_bytes": "Dict[bool, bytes]", "content_dict_bool_int": "Dict[bool, int]", "content_dict_bool_float": "Dict[bool, float]", "content_dict_bool_bool": "Dict[bool, bool]", "content_dict_bool_str": "Dict[bool, str]", "content_dict_str_bytes": "Dict[str, bytes]", "content_dict_str_int": "Dict[str, int]", "content_dict_str_float": "Dict[str, float]", "content_dict_str_bool": "Dict[str, bool]", "content_dict_str_str": "Dict[str, str]", }, "performative_mt": { "content_union_1": "Union[DataModel1, bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int]]", "content_union_2": "Union[FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes], int]", "content_union_3": "Union[DataModel2, DataModel3]", }, "performative_o": { "content_o_ct": "Optional[DataModel4]", "content_o_bool": "Optional[bool]", "content_o_set_int": "Optional[FrozenSet[int]]", "content_o_list_bytes": "Optional[Tuple[bytes, ...]]", "content_o_dict_str_int": "Optional[Dict[str, int]]", }, "performative_empty_contents": {}, } assert spec.all_performatives == [ "performative_ct", "performative_empty_contents", "performative_mt", "performative_o", "performative_pct", "performative_pmt", "performative_pt", ] assert spec.all_unique_contents == { "content_ct": "DataModel", "content_bytes": "bytes", "content_int": "int", "content_float": "float", "content_bool": "bool", "content_str": "str", "content_set_bytes": "FrozenSet[bytes]", "content_set_int": "FrozenSet[int]", "content_set_float": "FrozenSet[float]", "content_set_bool": "FrozenSet[bool]", "content_set_str": "FrozenSet[str]", "content_list_bytes": "Tuple[bytes, ...]", "content_list_int": "Tuple[int, ...]", "content_list_float": "Tuple[float, ...]", "content_list_bool": "Tuple[bool, ...]", "content_list_str": "Tuple[str, ...]", "content_dict_int_bytes": "Dict[int, bytes]", "content_dict_int_int": "Dict[int, int]", "content_dict_int_float": "Dict[int, float]", "content_dict_int_bool": "Dict[int, bool]", "content_dict_int_str": "Dict[int, str]", "content_dict_bool_bytes": "Dict[bool, bytes]", "content_dict_bool_int": "Dict[bool, int]", "content_dict_bool_float": "Dict[bool, float]", "content_dict_bool_bool": "Dict[bool, bool]", "content_dict_bool_str": "Dict[bool, str]", "content_dict_str_bytes": "Dict[str, bytes]", "content_dict_str_int": "Dict[str, int]", "content_dict_str_float": "Dict[str, float]", "content_dict_str_bool": "Dict[str, bool]", "content_dict_str_str": "Dict[str, str]", "content_union_1": "Union[DataModel1, bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int]]", "content_union_2": "Union[FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes], int]", "content_union_3": "Union[DataModel2, DataModel3]", "content_o_ct": "Optional[DataModel4]", "content_o_bool": "Optional[bool]", "content_o_set_int": "Optional[FrozenSet[int]]", "content_o_list_bytes": "Optional[Tuple[bytes, ...]]", "content_o_dict_str_int": "Optional[Dict[str, int]]", } assert spec.all_custom_types == [ "DataModel", "DataModel1", "DataModel2", "DataModel3", "DataModel4", ] assert spec.custom_custom_types == { "DataModel": "CustomDataModel", "DataModel1": "CustomDataModel1", "DataModel2": "CustomDataModel2", "DataModel3": "CustomDataModel3", "DataModel4": "CustomDataModel4", } assert spec.initial_performatives == ["PERFORMATIVE_CT", "PERFORMATIVE_PT"] assert spec.reply == { "performative_ct": ["performative_pct"], "performative_pt": ["performative_pt", "performative_pmt"], "performative_pct": ["performative_mt", "performative_o"], "performative_pmt": ["performative_mt", "performative_o"], "performative_mt": [], "performative_o": [], "performative_empty_contents": ["performative_empty_contents"], } assert spec.terminal_performatives == [ "PERFORMATIVE_MT", "PERFORMATIVE_O", ] assert spec.roles == ["role_1", "role_2"] assert spec.end_states == ["end_state_1", "end_state_2", "end_state_3"] assert spec.typing_imports == { "Set": True, "Tuple": True, "cast": True, "FrozenSet": True, "Dict": True, "Union": True, "Optional": True, } @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_protocols/test_generator/test_generator.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains miscellaneous tests for the protocol generator.""" import logging import os import shutil import tempfile from pathlib import Path from typing import cast from unittest import TestCase, mock import pytest from aea.configurations.base import ( ProtocolSpecification, ProtocolSpecificationParseError, ) from aea.configurations.constants import SUPPORTED_PROTOCOL_LANGUAGES from aea.configurations.data_types import PublicId from aea.protocols.generator.base import ( CUSTOM_TYPES_DOT_PY_FILE_NAME, ProtocolGenerator, ) from aea.protocols.generator.common import _to_camel_case from tests.conftest import match_files from tests.data.generator.t_protocol.message import TProtocolMessage # type: ignore from tests.test_aea.test_protocols.test_generator.common import ( PATH_TO_T_PROTOCOL, PATH_TO_T_PROTOCOL_NO_CT, PATH_TO_T_PROTOCOL_NO_CT_SPECIFICATION, PATH_TO_T_PROTOCOL_SPECIFICATION, T_PROTOCOL_NAME, T_PROTOCOL_NO_CT_NAME, ) logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) class TestCompareLatestGeneratorOutputWithTestProtocol: """Test that the "t_protocol" test protocol matches with the latest generator output based on its specification.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def test_compare_latest_generator_output_with_test_protocol(self): """ Test that the "t_protocol" test protocol matches with the latest generator output based on its specification. Note: - custom_types.py files are not compared as the generated one is only a template. - protocol.yaml files are consequently not compared either because the different custom_types.py files makes their IPFS hashes different. """ path_to_generated_protocol = self.t dotted_path_to_package_for_imports = "tests.data.generator." # Generate the protocol try: protocol_generator = ProtocolGenerator( path_to_protocol_specification=PATH_TO_T_PROTOCOL_SPECIFICATION, output_path=path_to_generated_protocol, dotted_path_to_protocol_package=dotted_path_to_package_for_imports, ) protocol_generator.generate() except Exception as e: pytest.skip( "Something went wrong when generating the protocol. The exception:" + str(e) ) # compare __init__.py init_file_generated = Path(self.t, T_PROTOCOL_NAME, "__init__.py") init_file_original = Path( PATH_TO_T_PROTOCOL, "__init__.py", ) is_matched, diff = match_files(init_file_generated, init_file_original) assert ( is_matched or len(diff) == 194 ), f"Difference Found between __init__.py files:\n{diff}" # compare message.py message_file_generated = Path(self.t, T_PROTOCOL_NAME, "message.py") message_file_original = Path( PATH_TO_T_PROTOCOL, "message.py", ) is_matched, diff = match_files(message_file_generated, message_file_original) assert is_matched, f"Difference Found between message.py files:\n{diff}" # compare serialization.py serialization_file_generated = Path(self.t, T_PROTOCOL_NAME, "serialization.py") serialization_file_original = Path( PATH_TO_T_PROTOCOL, "serialization.py", ) is_matched, diff = match_files( serialization_file_generated, serialization_file_original ) assert is_matched, f"Difference Found between serialization.py files:\n{diff}" # compare dialogues.py dialogue_file_generated = Path(self.t, T_PROTOCOL_NAME, "dialogues.py") dialogue_file_original = Path( PATH_TO_T_PROTOCOL, "dialogues.py", ) is_matched, diff = match_files(dialogue_file_generated, dialogue_file_original) assert is_matched, f"Difference Found between dialogues.py files:\n{diff}" # compare .proto proto_file_generated = Path( self.t, T_PROTOCOL_NAME, "{}.proto".format(T_PROTOCOL_NAME) ) proto_file_original = Path( PATH_TO_T_PROTOCOL, "{}.proto".format(T_PROTOCOL_NAME), ) is_matched, diff = match_files(proto_file_generated, proto_file_original) assert is_matched, f"Difference Found between .proto files:\n{diff}" # compare _pb2.py # noqa: E800 # ToDo this part fails in CI. Investigate why? # pb2_file_generated = Path( # noqa: E800 # self.t, T_PROTOCOL_NAME, "{}_pb2.py".format(T_PROTOCOL_NAME) # noqa: E800 # ) # noqa: E800 # pb2_file_original = Path( # noqa: E800 # PATH_TO_T_PROTOCOL, "{}_pb2.py".format(T_PROTOCOL_NAME), # noqa: E800 # ) # noqa: E800 # is_matched, diff = match_files(pb2_file_generated, pb2_file_original) # noqa: E800 # assert is_matched, f"Difference Found between _pb2.py files:\n{diff}" # noqa: E800 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestCompareLatestGeneratorOutputWithTestProtocolWithNoCustomTypes: """Test that the "t_protocol" test protocol matches with the latest generator output based on its specification.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def test_compare_latest_generator_output_with_test_protocol(self): """ Test that the "t_protocol" test protocol matches with the latest generator output based on its specification. Note: - custom_types.py files are not compared as the generated one is only a template. - protocol.yaml files are consequently not compared either because the different custom_types.py files makes their IPFS hashes different. """ path_to_generated_protocol = self.t dotted_path_to_package_for_imports = "tests.data.generator." # Generate the protocol try: protocol_generator = ProtocolGenerator( path_to_protocol_specification=PATH_TO_T_PROTOCOL_NO_CT_SPECIFICATION, output_path=path_to_generated_protocol, dotted_path_to_protocol_package=dotted_path_to_package_for_imports, ) protocol_generator.generate() except Exception as e: pytest.skip( "Something went wrong when generating the protocol. The exception:" + str(e) ) # compare __init__.py init_file_generated = Path(self.t, T_PROTOCOL_NO_CT_NAME, "__init__.py") init_file_original = Path( PATH_TO_T_PROTOCOL_NO_CT, "__init__.py", ) is_matched, diff = match_files(init_file_generated, init_file_original) assert ( is_matched or len(diff) == 194 ), f"Difference Found between __init__.py files:\n{diff}" # compare message.py message_file_generated = Path(self.t, T_PROTOCOL_NO_CT_NAME, "message.py") message_file_original = Path( PATH_TO_T_PROTOCOL_NO_CT, "message.py", ) is_matched, diff = match_files(message_file_generated, message_file_original) assert is_matched, f"Difference Found between message.py files:\n{diff}" # compare serialization.py serialization_file_generated = Path( self.t, T_PROTOCOL_NO_CT_NAME, "serialization.py" ) serialization_file_original = Path( PATH_TO_T_PROTOCOL_NO_CT, "serialization.py", ) is_matched, diff = match_files( serialization_file_generated, serialization_file_original ) assert is_matched, f"Difference Found between serialization.py files:\n{diff}" # compare dialogues.py dialogue_file_generated = Path(self.t, T_PROTOCOL_NO_CT_NAME, "dialogues.py") dialogue_file_original = Path( PATH_TO_T_PROTOCOL_NO_CT, "dialogues.py", ) is_matched, diff = match_files(dialogue_file_generated, dialogue_file_original) assert is_matched, f"Difference Found between dialogues.py files:\n{diff}" # compare .proto proto_file_generated = Path( self.t, T_PROTOCOL_NO_CT_NAME, "{}.proto".format(T_PROTOCOL_NO_CT_NAME) ) proto_file_original = Path( PATH_TO_T_PROTOCOL_NO_CT, "{}.proto".format(T_PROTOCOL_NO_CT_NAME), ) is_matched, diff = match_files(proto_file_generated, proto_file_original) assert is_matched, f"Difference Found between .proto files:\n{diff}" # compare _pb2.py # noqa: E800 # ToDo this part fails in CI. Investigate why? # noqa: E800 # pb2_file_generated = Path( # noqa: E800 # self.t, protocol_name, "{}_pb2.py".format(protocol_name) # noqa: E800 # ) # noqa: E800 # pb2_file_original = Path(path_to_protocol, "{}_pb2.py".format(protocol_name),) # noqa: E800 # is_matched, diff = match_files(pb2_file_generated, pb2_file_original) # noqa: E800 # assert is_matched, f"Difference Found between _pb2.py files:\n{diff}" # noqa: E800 @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestSerialisations: """ Test that the generating a protocol works correctly in correct preconditions. Note: Types involving Floats seem to lose some precision when serialised then deserialised using protobuf. So tests for these types are commented out throughout for now. """ def test_generated_protocol_serialisation_ct(self): """Test serialisation and deserialisation of a message involving a ct type.""" some_dict = {1: True, 2: False, 3: True, 4: False} data_model = TProtocolMessage.DataModel( bytes_field=b"some bytes", int_field=42, float_field=42.7, bool_field=True, str_field="some string", set_field={1, 2, 3, 4, 5}, list_field=["some string 1", "some string 2"], dict_field=some_dict, ) message = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_CT, content_ct=data_model, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message.message_id assert decoded_message.dialogue_reference == message.dialogue_reference assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] assert decoded_message.target == message.target assert decoded_message.performative == message.performative assert decoded_message.content_ct == message.content_ct def test_generated_protocol_serialisation_pt(self): """Test serialisation and deserialisation of a message involving a pt type.""" message = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_PT, content_bytes=b"some bytes", content_int=42, content_float=42.7, content_bool=True, content_str="some string", ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message.message_id assert decoded_message.dialogue_reference == message.dialogue_reference assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] assert decoded_message.target == message.target assert decoded_message.performative == message.performative assert decoded_message.content_bytes == message.content_bytes assert decoded_message.content_int == message.content_int # assert decoded_message.content_float == message.content_float # noqa: E800 assert decoded_message.content_bool == message.content_bool assert decoded_message.content_str == message.content_str def test_generated_protocol_serialisation_pct(self): """Test serialisation and deserialisation of a message involving a pct type.""" message = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_PCT, content_set_bytes=frozenset([b"byte 1", b"byte 2", b"byte 3"]), content_set_int=frozenset([1, 2, 3]), content_set_float=frozenset([1.2, 2.3, 3.4]), content_set_bool=frozenset([True, False, False, True]), content_set_str=frozenset(["string1", "string2", "string3"]), content_list_bytes=(b"byte 4", b"byte 5", b"byte 6"), content_list_int=(4, 5, 6), content_list_float=(4.5, 5.6, 6.7), content_list_bool=(False, True, False, False), content_list_str=("string4", "string5", "string6"), ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message.message_id assert decoded_message.dialogue_reference == message.dialogue_reference assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] assert decoded_message.target == message.target assert decoded_message.performative == message.performative assert decoded_message.content_set_bytes == message.content_set_bytes assert decoded_message.content_set_int == message.content_set_int # assert decoded_message.content_set_float == message.content_set_float # noqa: E800 assert decoded_message.content_set_bool == message.content_set_bool assert decoded_message.content_set_str == message.content_set_str assert decoded_message.content_list_bytes == message.content_list_bytes assert decoded_message.content_list_int == message.content_list_int # assert decoded_message.content_list_float == message.content_list_float # noqa: E800 assert decoded_message.content_list_bool == message.content_list_bool assert decoded_message.content_list_str == message.content_list_str def test_generated_protocol_serialisation_pmt(self): """Test serialisation and deserialisation of a message involving a pmt type.""" message = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_PMT, content_dict_int_bytes={1: b"bytes1", 2: b"bytes2", 3: b"bytes3"}, content_dict_int_int={1: 2, 2: 3, 3: 4}, content_dict_int_float={1: 3.4, 2: 4.7, 3: 4.6}, content_dict_int_bool={1: True, 2: True, 3: False}, content_dict_int_str={1: "string1", 2: "string2", 3: "string3"}, content_dict_bool_bytes={True: b"bytes1", False: b"bytes2"}, content_dict_bool_int={True: 5, False: 7}, content_dict_bool_float={True: 5.4, False: 4.6}, content_dict_bool_bool={True: False, False: False}, content_dict_bool_str={True: "string1", False: "string2"}, content_dict_str_bytes={ "string1": b"bytes1", "string2": b"bytes2", "string3": b"bytes3", }, content_dict_str_int={"string1": 2, "string2": 3, "string3": 4}, content_dict_str_float={"string1": 3.4, "string2": 4.7, "string3": 4.6}, content_dict_str_bool={"string1": True, "string2": True, "string3": False}, content_dict_str_str={ "string1": "string4", "string2": "string5", "string3": "string6", }, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message.message_id assert decoded_message.dialogue_reference == message.dialogue_reference assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] assert decoded_message.target == message.target assert decoded_message.performative == message.performative assert decoded_message.content_dict_int_bytes == message.content_dict_int_bytes assert decoded_message.content_dict_int_int == message.content_dict_int_int # assert decoded_message.content_dict_int_float == message.content_dict_int_float # noqa: E800 assert decoded_message.content_dict_int_bool == message.content_dict_int_bool assert decoded_message.content_dict_int_str == message.content_dict_int_str assert ( decoded_message.content_dict_bool_bytes == message.content_dict_bool_bytes ) assert decoded_message.content_dict_bool_int == message.content_dict_bool_int # assert decoded_message.content_dict_bool_float == message.content_dict_bool_float # noqa: E800 assert decoded_message.content_dict_bool_bool == message.content_dict_bool_bool assert decoded_message.content_dict_bool_str == message.content_dict_bool_str assert decoded_message.content_dict_str_bytes == message.content_dict_str_bytes assert decoded_message.content_dict_str_int == message.content_dict_str_int # assert decoded_message.content_dict_str_float == message.content_dict_str_float # noqa: E800 assert decoded_message.content_dict_str_bool == message.content_dict_str_bool assert decoded_message.content_dict_str_str == message.content_dict_str_str def test_generated_protocol_serialisation_mt(self): """Test serialisation and deserialisation of a message involving an mt type.""" some_dict = {1: True, 2: False, 3: True, 4: False} def make_data_model(type_): return type_( bytes_field=b"some bytes", int_field=42, float_field=42.7, bool_field=True, str_field="some string", set_field={1, 2, 3, 4, 5}, list_field=["some string 1", "some string 2"], dict_field=some_dict, ) data_model1 = make_data_model(TProtocolMessage.DataModel1) data_model2 = make_data_model(TProtocolMessage.DataModel2) message_ct = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1=data_model1, content_union_2=frozenset([1, 2, 3]), content_union_3=data_model2, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_ct) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_ct.message_id assert decoded_message.dialogue_reference == message_ct.dialogue_reference assert decoded_message.dialogue_reference[0] == message_ct.dialogue_reference[0] assert decoded_message.dialogue_reference[1] == message_ct.dialogue_reference[1] assert decoded_message.target == message_ct.target assert decoded_message.performative == message_ct.performative assert decoded_message.content_union_1 == message_ct.content_union_1 assert decoded_message.content_union_2 == message_ct.content_union_2 ##################### message_pt_bytes = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1=b"some bytes", content_union_2=2, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_bytes) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_pt_bytes.message_id assert decoded_message.dialogue_reference == message_pt_bytes.dialogue_reference assert ( decoded_message.dialogue_reference[0] == message_pt_bytes.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_pt_bytes.dialogue_reference[1] ) assert decoded_message.target == message_pt_bytes.target assert decoded_message.performative == message_pt_bytes.performative assert decoded_message.content_union_1 == message_pt_bytes.content_union_1 assert decoded_message.content_union_2 == message_pt_bytes.content_union_2 ##################### message_pt_int = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1=3453, content_union_2=tuple([b"1", b"2", b"3"]), ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_int) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_pt_int.message_id assert decoded_message.dialogue_reference == message_pt_int.dialogue_reference assert ( decoded_message.dialogue_reference[0] == message_pt_int.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_pt_int.dialogue_reference[1] ) assert decoded_message.target == message_pt_int.target assert decoded_message.performative == message_pt_int.performative assert decoded_message.content_union_1 == message_pt_int.content_union_1 assert decoded_message.content_union_2 == message_pt_int.content_union_2 ##################### # float does not decoded properly """ message_pt_float = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1=34.4, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_float) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_pt_float.message_id assert decoded_message.dialogue_reference == message_pt_float.dialogue_reference assert ( decoded_message.dialogue_reference[0] == message_pt_float.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_pt_float.dialogue_reference[1] ) assert decoded_message.target == message_pt_float.target assert decoded_message.performative == message_pt_float.performative assert decoded_message.content_union_1 == message_pt_float.content_union_1 """ ##################### message_pt_bool = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1=True, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_bool) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_pt_bool.message_id assert decoded_message.dialogue_reference == message_pt_bool.dialogue_reference assert ( decoded_message.dialogue_reference[0] == message_pt_bool.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_pt_bool.dialogue_reference[1] ) assert decoded_message.target == message_pt_bool.target assert decoded_message.performative == message_pt_bool.performative assert decoded_message.content_union_1 == message_pt_bool.content_union_1 ##################### message_pt_str = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1="some string", ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_str) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_pt_str.message_id assert decoded_message.dialogue_reference == message_pt_str.dialogue_reference assert ( decoded_message.dialogue_reference[0] == message_pt_str.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_pt_str.dialogue_reference[1] ) assert decoded_message.target == message_pt_str.target assert decoded_message.performative == message_pt_str.performative assert decoded_message.content_union_1 == message_pt_str.content_union_1 ##################### """ NESTED TYPES AR NOT SUPPORTED message_set_int = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1=frozenset([1, 2, 3]), ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_set_int) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_set_int.message_id assert decoded_message.dialogue_reference == message_set_int.dialogue_reference assert ( decoded_message.dialogue_reference[0] == message_set_int.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_set_int.dialogue_reference[1] ) assert decoded_message.target == message_set_int.target assert decoded_message.performative == message_set_int.performative assert decoded_message.content_union_1 == message_set_int.content_union_1 ##################### message_list_bool = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1=(True, False, False, True, True), ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_list_bool) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_list_bool.message_id assert ( decoded_message.dialogue_reference == message_list_bool.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_list_bool.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_list_bool.dialogue_reference[1] ) assert decoded_message.target == message_list_bool.target assert decoded_message.performative == message_list_bool.performative assert decoded_message.content_union_1 == message_list_bool.content_union_1 ##################### message_dict_str_int = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_MT, content_union_1={"string1": 2, "string2": 3, "string3": 4}, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_dict_str_int ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_dict_str_int.message_id assert ( decoded_message.dialogue_reference == message_dict_str_int.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_dict_str_int.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_dict_str_int.dialogue_reference[1] ) assert decoded_message.target == message_dict_str_int.target assert decoded_message.performative == message_dict_str_int.performative assert decoded_message.content_union_1 == message_dict_str_int.content_union_1 """ def test_generated_protocol_serialisation_o(self): """Test serialisation and deserialisation of a message involving an optional type.""" some_dict = {1: True, 2: False, 3: True, 4: False} data_model = TProtocolMessage.DataModel4( bytes_field=b"some bytes", int_field=42, float_field=42.7, bool_field=True, str_field="some string", set_field={1, 2, 3, 4, 5}, list_field=["some string 1", "some string 2"], dict_field=some_dict, ) message_o_ct_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, content_o_ct=data_model, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_o_ct_set) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_ct_set.message_id assert decoded_message.dialogue_reference == message_o_ct_set.dialogue_reference assert ( decoded_message.dialogue_reference[0] == message_o_ct_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_ct_set.dialogue_reference[1] ) assert decoded_message.target == message_o_ct_set.target assert decoded_message.performative == message_o_ct_set.performative assert decoded_message.content_o_ct == message_o_ct_set.content_o_ct ##################### message_o_ct_not_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_ct_not_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_ct_not_set.message_id assert ( decoded_message.dialogue_reference == message_o_ct_not_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_ct_not_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_ct_not_set.dialogue_reference[1] ) assert decoded_message.target == message_o_ct_not_set.target assert decoded_message.performative == message_o_ct_not_set.performative assert decoded_message.content_o_ct == message_o_ct_not_set.content_o_ct ##################### message_o_bool_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, content_o_bool=True, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_bool_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_bool_set.message_id assert ( decoded_message.dialogue_reference == message_o_bool_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_bool_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_bool_set.dialogue_reference[1] ) assert decoded_message.target == message_o_bool_set.target assert decoded_message.performative == message_o_bool_set.performative assert decoded_message.content_o_ct == message_o_bool_set.content_o_ct ##################### message_o_bool_not_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_bool_not_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_bool_not_set.message_id assert ( decoded_message.dialogue_reference == message_o_bool_not_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_bool_not_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_bool_not_set.dialogue_reference[1] ) assert decoded_message.target == message_o_bool_not_set.target assert decoded_message.performative == message_o_bool_not_set.performative assert decoded_message.content_o_bool == message_o_bool_not_set.content_o_bool ##################### message_o_set_int_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, content_o_set_int=frozenset([1, 2, 3]), ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_set_int_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_set_int_set.message_id assert ( decoded_message.dialogue_reference == message_o_set_int_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_set_int_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_set_int_set.dialogue_reference[1] ) assert decoded_message.target == message_o_set_int_set.target assert decoded_message.performative == message_o_set_int_set.performative assert ( decoded_message.content_o_set_int == message_o_set_int_set.content_o_set_int ) ##################### message_o_set_int_not_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_set_int_not_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_set_int_not_set.message_id assert ( decoded_message.dialogue_reference == message_o_set_int_not_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_set_int_not_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_set_int_not_set.dialogue_reference[1] ) assert decoded_message.target == message_o_set_int_not_set.target assert decoded_message.performative == message_o_set_int_not_set.performative assert ( decoded_message.content_o_set_int == message_o_set_int_not_set.content_o_set_int ) ##################### message_o_list_bytes_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, content_o_list_bytes=(b"bytes1", b"bytes2", b"bytes3"), ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_list_bytes_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_list_bytes_set.message_id assert ( decoded_message.dialogue_reference == message_o_list_bytes_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_list_bytes_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_list_bytes_set.dialogue_reference[1] ) assert decoded_message.target == message_o_list_bytes_set.target assert decoded_message.performative == message_o_list_bytes_set.performative assert ( decoded_message.content_o_list_bytes == message_o_list_bytes_set.content_o_list_bytes ) ##################### message_o_list_bytes_not_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_list_bytes_not_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_list_bytes_not_set.message_id assert ( decoded_message.dialogue_reference == message_o_list_bytes_not_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_list_bytes_not_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_list_bytes_not_set.dialogue_reference[1] ) assert decoded_message.target == message_o_list_bytes_not_set.target assert decoded_message.performative == message_o_list_bytes_not_set.performative assert ( decoded_message.content_o_list_bytes == message_o_list_bytes_not_set.content_o_list_bytes ) ##################### message_o_dict_str_int_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, content_o_dict_str_int={"string1": 2, "string2": 3, "string3": 4}, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_dict_str_int_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_dict_str_int_set.message_id assert ( decoded_message.dialogue_reference == message_o_dict_str_int_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_dict_str_int_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_dict_str_int_set.dialogue_reference[1] ) assert decoded_message.target == message_o_dict_str_int_set.target assert decoded_message.performative == message_o_dict_str_int_set.performative assert ( decoded_message.content_o_list_bytes == message_o_dict_str_int_set.content_o_list_bytes ) ##################### message_o_dict_str_int_not_set = TProtocolMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=TProtocolMessage.Performative.PERFORMATIVE_O, ) encoded_message_in_bytes = TProtocolMessage.serializer.encode( message_o_dict_str_int_not_set ) decoded_message = cast( TProtocolMessage, TProtocolMessage.serializer.decode(encoded_message_in_bytes), ) assert decoded_message.message_id == message_o_dict_str_int_not_set.message_id assert ( decoded_message.dialogue_reference == message_o_dict_str_int_not_set.dialogue_reference ) assert ( decoded_message.dialogue_reference[0] == message_o_dict_str_int_not_set.dialogue_reference[0] ) assert ( decoded_message.dialogue_reference[1] == message_o_dict_str_int_not_set.dialogue_reference[1] ) assert decoded_message.target == message_o_dict_str_int_not_set.target assert ( decoded_message.performative == message_o_dict_str_int_not_set.performative ) assert ( decoded_message.content_o_list_bytes == message_o_dict_str_int_not_set.content_o_list_bytes ) class ProtocolGeneratorTestCase(TestCase): """Test for generator/base.py.""" @classmethod def setup_class(cls): """Setup class.""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) def _mock_config(self): return """lint: rules: remove: - MESSAGE_NAMES_UPPER_CAMEL_CASE - ENUM_FIELD_NAMES_ZERO_VALUE_END_WITH - PACKAGE_NAME_LOWER_CASE - REPEATED_FIELD_NAMES_PLURALIZED - FIELD_NAMES_LOWER_SNAKE_CASE""" @mock.patch( "aea.protocols.generator.base.check_prerequisites", side_effect=FileNotFoundError("Some error!"), ) def test_init_negative_no_prerequisits(self, mocked_check_prerequisites): """Negative test for the '__init__' method: check_prerequisites fails.""" with self.assertRaises(FileNotFoundError) as cm: ProtocolGenerator(PATH_TO_T_PROTOCOL, self.t) expected_msg = "Some error!" assert str(cm.exception) == expected_msg @mock.patch( "aea.protocols.generator.base.load_protocol_specification", side_effect=ValueError("Some error!"), ) def test_init_negative_loading_specification_fails(self, mocked_load): """Negative test for the '__init__' method: loading the specification fails.""" with mock.patch("aea.protocols.generator.base.check_prerequisites"): with self.assertRaises(ValueError) as cm: ProtocolGenerator(PATH_TO_T_PROTOCOL, self.t) expected_msg = "Some error!" assert str(cm.exception) == expected_msg @mock.patch( "aea.protocols.generator.base.extract", side_effect=ProtocolSpecificationParseError("Some error!"), ) def test_init_negative_extracting_specification_fails(self, mocked_extract): """Negative test for the '__init__' method: extracting the specification fails.""" with mock.patch("aea.protocols.generator.base.check_prerequisites"): p_spec_mock = mock.MagicMock(ProtocolSpecification) p_spec_mock.name = "some_name" p_spec_mock.author = "some_author" with mock.patch( "aea.protocols.generator.base.load_protocol_specification", return_value=p_spec_mock, ): with mock.patch( "aea.protocols.generator.base.validate", return_value=(True, "valid!"), ): with self.assertRaises(ProtocolSpecificationParseError) as cm: ProtocolGenerator( "some_path_to_protocol_specification", "some_path_to_output" ) expected_msg = "Some error!" assert str(cm.exception) == expected_msg @mock.patch( "aea.protocols.generator.base.validate", return_value=(False, "Some error!"), ) def test_extract_negative_invalid_specification(self, mocked_validate): """Negative test the 'extract' method: invalid protocol specification""" with mock.patch("aea.protocols.generator.base.check_prerequisites"): p_spec_mock = mock.MagicMock(ProtocolSpecification) p_spec_mock.name = "some_name" p_spec_mock.author = "some_author" with mock.patch( "aea.protocols.generator.base.load_protocol_specification", return_value=p_spec_mock, ): with self.assertRaises(ProtocolSpecificationParseError) as cm: ProtocolGenerator( "some_path_to_protocol_specification", "some_path_to_output" ) expected_msg = "Some error!" assert str(cm.exception) == expected_msg def test_change_indent_negative_set_indent_to_negative_value(self): """Negative test for the '_change_indent' method: setting indent level to negative value.""" with mock.patch("aea.protocols.generator.base.check_prerequisites"): p_spec_mock = mock.MagicMock(ProtocolSpecification) p_spec_mock.name = "some_name" p_spec_mock.author = "some_author" with mock.patch( "aea.protocols.generator.base.load_protocol_specification", return_value=p_spec_mock, ): with mock.patch( "aea.protocols.generator.base.validate", return_value=(True, "valid!"), ): with mock.patch("aea.protocols.generator.base.extract"): protocol_generator = ProtocolGenerator( "some_path_to_protocol_specification", "some_path_to_output" ) with self.assertRaises(ValueError) as cm: protocol_generator._change_indent(-1, "s") expected_msg = ( "Error: setting indent to be a negative number." ) assert str(cm.exception) == expected_msg def test_change_indent_negative_decreasing_more_spaces_than_available(self): """Negative test for the '_change_indent' method: decreasing more spaces than available.""" with mock.patch("aea.protocols.generator.base.check_prerequisites"): p_spec_mock = mock.MagicMock(ProtocolSpecification) p_spec_mock.name = "some_name" p_spec_mock.author = "some_author" with mock.patch( "aea.protocols.generator.base.load_protocol_specification", return_value=p_spec_mock, ): with mock.patch( "aea.protocols.generator.base.validate", return_value=(True, "valid!"), ): with mock.patch("aea.protocols.generator.base.extract"): protocol_generator = ProtocolGenerator( "some_path_to_protocol_specification", "some_path_to_output" ) protocol_generator.indent = " " with self.assertRaises(ValueError) as cm: protocol_generator._change_indent(-2) expected_msg = ( "Not enough spaces in the 'indent' variable to remove." ) assert str(cm.exception) == expected_msg def test_import_from_custom_types_module_no_custom_types(self): """Test the '_import_from_custom_types_module' method: no custom types.""" with mock.patch("aea.protocols.generator.base.check_prerequisites"): p_spec_mock = mock.MagicMock(ProtocolSpecification) p_spec_mock.name = "some_name" p_spec_mock.author = "some_author" with mock.patch( "aea.protocols.generator.base.load_protocol_specification", return_value=p_spec_mock, ): with mock.patch( "aea.protocols.generator.base.validate", return_value=(True, "valid!"), ): with mock.patch("aea.protocols.generator.base.extract"): protocol_generator = ProtocolGenerator( "some_path_to_protocol_specification", "some_path_to_output" ) protocol_generator.spec.all_custom_types = [] assert ( protocol_generator._import_from_custom_types_module() == "" ) def test_protocol_buffer_schema_str(self): """Negative test for the '_protocol_buffer_schema_str' method: 1 line protobuf snippet.""" with mock.patch("aea.protocols.generator.base.check_prerequisites"): p_spec_mock = mock.MagicMock(ProtocolSpecification) p_spec_mock.name = "some_name" p_spec_mock.author = "some_author" p_spec_mock.protocol_specification_id = PublicId( "some_author", "some_protocol_name", "0.1.0" ) with mock.patch( "aea.protocols.generator.base.load_protocol_specification", return_value=p_spec_mock, ): with mock.patch( "aea.protocols.generator.base.validate", return_value=(True, "valid!"), ): with mock.patch("aea.protocols.generator.base.extract"): protocol_generator = ProtocolGenerator( "some_path_to_protocol_specification", "some_path_to_output" ) protocol_generator.spec.all_custom_types = ["SomeCustomType"] protocol_generator.protocol_specification.protobuf_snippets = { "ct:SomeCustomType": "bytes description = 1;" } proto_buff_schema_str = ( protocol_generator._protocol_buffer_schema_str() ) print(proto_buff_schema_str) expected = ( 'syntax = "proto3";\n\n' "package aea.some_author.some_protocol_name.v0_1_0;\n\n" "message SomeNameMessage{\n\n" " // Custom Types\n" " message SomeCustomType{\n" " bytes description = 1; }\n\n\n" " // Performatives and contents\n\n" " oneof performative{\n" " }\n" "}\n" ) assert proto_buff_schema_str == expected def test_generate_protobuf_only_mode_positive_python(self): """Positive test for the 'generate_protobuf_only_mode' where language is Python.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate_protobuf_only_mode() path_to_protobuf_schema_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) path_to_protobuf_python_implementation = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + "_pb2.py" ) assert Path(path_to_protobuf_schema_file).exists() assert Path(path_to_protobuf_python_implementation).exists() def test_generate_protobuf_only_mode_positive_cpp(self): """Positive test for the 'generate_protobuf_only_mode' where language is C++.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate_protobuf_only_mode(language="cpp") path_to_protobuf_schema_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) path_to_protobuf_cpp_headers = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".pb.h" ) path_to_protobuf_cpp_implementation = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".pb.cc" ) assert Path(path_to_protobuf_schema_file).exists() assert Path(path_to_protobuf_cpp_headers).exists() assert Path(path_to_protobuf_cpp_implementation).exists() def test_generate_protobuf_only_mode_positive_java(self): """Positive test for the 'generate_protobuf_only_mode' where language is Java.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate_protobuf_only_mode(language="java") path_to_protobuf_schema_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) assert Path(path_to_protobuf_schema_file).exists() java_implementation_exists = False for _, _, files in os.walk(os.path.join(self.t, T_PROTOCOL_NAME)): for file in files: # loops through directories and files if file == _to_camel_case(T_PROTOCOL_NAME) + ".java": java_implementation_exists = True break assert java_implementation_exists def test_generate_protobuf_only_mode_positive_csharp(self): """Positive test for the 'generate_protobuf_only_mode' where language is C#.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate_protobuf_only_mode(language="csharp") path_to_protobuf_schema_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) path_to_protobuf_csharp_implementation = os.path.join( self.t, T_PROTOCOL_NAME, _to_camel_case(T_PROTOCOL_NAME) + ".cs" ) assert Path(path_to_protobuf_schema_file).exists() assert Path(path_to_protobuf_csharp_implementation).exists() def test_generate_protobuf_only_mode_positive_ruby(self): """Positive test for the 'generate_protobuf_only_mode' where language is Ruby.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate_protobuf_only_mode(language="ruby") path_to_protobuf_schema_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) path_to_protobuf_ruby_implementation = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + "_pb.rb" ) assert Path(path_to_protobuf_schema_file).exists() assert Path(path_to_protobuf_ruby_implementation).exists() def test_generate_protobuf_only_mode_positive_objc(self): """Positive test for the 'generate_protobuf_only_mode' where language is objective-c.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate_protobuf_only_mode(language="objc") path_to_protobuf_schema_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) path_to_protobuf_objc_headers = os.path.join( self.t, T_PROTOCOL_NAME, _to_camel_case(T_PROTOCOL_NAME) + ".pbobjc.h" ) path_to_protobuf_objc_implementation = os.path.join( self.t, T_PROTOCOL_NAME, _to_camel_case(T_PROTOCOL_NAME) + ".pbobjc.m" ) assert Path(path_to_protobuf_schema_file).exists() assert Path(path_to_protobuf_objc_headers).exists() assert Path(path_to_protobuf_objc_implementation).exists() def test_generate_protobuf_only_mode_positive_js(self): """Positive test for the 'generate_protobuf_only_mode' where language is JS.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate_protobuf_only_mode(language="js") path_to_protobuf_schema_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) path_to_protobuf_js_implementation = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + "_pb.js" ) assert Path(path_to_protobuf_schema_file).exists() assert Path(path_to_protobuf_js_implementation).exists() def test_generate_protobuf_only_mode_negative_incorrect_language(self): """Negative test for the 'generate_protobuf_only_mode' method: invalid language.""" invalid_language = "wrong_language" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) with self.assertRaises(ValueError) as cm: protocol_generator.generate_protobuf_only_mode(language=invalid_language) expected_msg = f"Unsupported language. Expected one of {SUPPORTED_PROTOCOL_LANGUAGES}. Found {invalid_language}." assert str(cm.exception) == expected_msg @mock.patch( "aea.protocols.generator.base.compile_protobuf_using_protoc", return_value=(False, "Some error!"), ) def test_generate_protobuf_only_mode_negative_compile_fails( self, mocked_compile_protobuf ): """Negative test for the 'generate_protobuf_only_mode' method: compiling protobuf schema file fails""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) with self.assertRaises(SyntaxError) as cm: protocol_generator.generate_protobuf_only_mode() expected_msg = ( "Error when trying to compile the protocol buffer schema file:\n" + "Some error!" ) assert str(cm.exception) == expected_msg path_to_protobuf_file = os.path.join( self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" ) assert not Path(path_to_protobuf_file).exists() @mock.patch( "aea.protocols.generator.base.apply_protolint", return_value=(False, "error line 1\nerror line 2"), ) def test_generate_protobuf_only_mode_protolint_error(self, mocked_apply_protolint): """Positive test for the 'generate_protobuf_only_mode' where protolint has some error.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) output = protocol_generator.generate_protobuf_only_mode() expected_output = "Protolint warnings:\n" + "error line 1\nerror line 2" assert output == expected_output def test_generate_full_mode_negative_incorrect_language(self): """Negative test for the 'generate_protobuf_only_mode' method: invalid language.""" invalid_language = "wrong_language" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) with self.assertRaises(ValueError) as cm: protocol_generator.generate_full_mode(language=invalid_language) expected_msg = f"Unsupported language. Expected 'python' because currently the framework supports full generation of protocols only in Python. Found {invalid_language}." assert str(cm.exception) == expected_msg @mock.patch( "aea.protocols.generator.base.apply_protolint", return_value=(False, "error line 1\nerror line 2"), ) def test_generate_full_mode_protolint_error(self, mocked_apply_protolint): """Positive test for the 'generate_full_mode' where protolint has some error.""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) output = protocol_generator.generate_full_mode("python") expected_output = ( "Protolint warnings:\n" + "error line 1\nerror line 2" + "The generated protocol is incomplete, because the protocol specification contains the following custom types: " + "{}. Update the generated '{}' file with the appropriate implementations of these custom types.".format( protocol_generator.spec.all_custom_types, CUSTOM_TYPES_DOT_PY_FILE_NAME ) ) assert output == expected_output @mock.patch( "aea.protocols.generator.base.ProtocolGenerator.generate_protobuf_only_mode" ) @mock.patch("aea.protocols.generator.base.ProtocolGenerator.generate_full_mode") def test_generate_1(self, mocked_full_mode, mocked_protobuf_mode): """Test the 'generate' method: protobuf_only mode""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate(protobuf_only=True) mocked_protobuf_mode.assert_called_once() mocked_full_mode.assert_not_called() @mock.patch( "aea.protocols.generator.base.ProtocolGenerator.generate_protobuf_only_mode" ) @mock.patch("aea.protocols.generator.base.ProtocolGenerator.generate_full_mode") def test_generate_2(self, mocked_full_mode, mocked_protobuf_mode): """Test the 'generate' method: full mode""" protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) protocol_generator.generate(protobuf_only=False) mocked_protobuf_mode.assert_not_called() mocked_full_mode.assert_called_once() @classmethod def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_aea/test_protocols/test_generator/test_validate.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for generator/validate.py module.""" import logging from unittest import TestCase, mock from aea.configurations.base import CRUDCollection, SpeechActContentConfig from aea.protocols.generator.validate import ( CONTENT_NAME_REGEX_PATTERN, END_STATE_REGEX_PATTERN, PERFORMATIVE_REGEX_PATTERN, ROLE_REGEX_PATTERN, _has_brackets, _is_reserved_name, _is_valid_content_type_format, _is_valid_ct, _is_valid_dict, _is_valid_list, _is_valid_optional, _is_valid_pt, _is_valid_regex, _is_valid_set, _is_valid_union, _validate_content_name, _validate_content_type, _validate_dialogue_section, _validate_end_states, _validate_field_existence, _validate_initiation, _validate_keep_terminal, _validate_performatives, _validate_protocol_buffer_schema_code_snippets, _validate_reply, _validate_roles, _validate_speech_acts_section, _validate_termination, validate, ) logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) class TestValidate(TestCase): """Test for generator/validate.py.""" def test_is_reserved_name(self): """Test for the '_is_reserved_name' method.""" invalid_content_name_1 = "_body" assert _is_reserved_name(invalid_content_name_1) is True invalid_content_name_2 = "message_id" assert _is_reserved_name(invalid_content_name_2) is True invalid_content_name_3 = "dialogue_reference" assert _is_reserved_name(invalid_content_name_3) is True invalid_content_name_4 = "target" assert _is_reserved_name(invalid_content_name_4) is True invalid_content_name_5 = "performative" assert _is_reserved_name(invalid_content_name_5) is True valid_content_nam_1 = "content_name" assert _is_reserved_name(valid_content_nam_1) is False valid_content_name_2 = "query" assert _is_reserved_name(valid_content_name_2) is False valid_content_name_3 = "ThiSiSAConTEnT234" assert _is_reserved_name(valid_content_name_3) is False def test_is_valid_regex(self): """Test for the '_is_valid_regex' method.""" regex_1 = "^[0-9][a-zA-Z0-9]*[A-Z]$" valid_text_1 = "53453hKb35nDkG" assert _is_valid_regex(regex_1, valid_text_1) is True invalid_text_1 = "hKbnDkG" assert _is_valid_regex(regex_1, invalid_text_1) is False invalid_text_2 = "4f nkG" assert _is_valid_regex(regex_1, invalid_text_2) is False def test_has_brackets(self): """Test for the '_has_brackets' method.""" valid_content_type_1 = "pt:set[pt:int]" assert _has_brackets(valid_content_type_1) is True valid_content_type_2 = "pt:union[hskdjf-8768&^${]hsdkjhfk]" assert _has_brackets(valid_content_type_2) is True valid_content_type_3 = "pt:optional[[]]" assert _has_brackets(valid_content_type_3) is True ################################################### invalid_content_type_1 = "ct:set[pt:int]" with self.assertRaises(SyntaxError) as cm: _has_brackets(invalid_content_type_1) self.assertEqual( str(cm.exception), "Content type must be a compositional type!" ) invalid_content_type_2 = "pt:tuple[pt:float]" with self.assertRaises(SyntaxError) as cm: _has_brackets(invalid_content_type_2) self.assertEqual( str(cm.exception), "Content type must be a compositional type!" ) invalid_content_type_3 = "pt:optinal[pt:bool]" with self.assertRaises(SyntaxError) as cm: _has_brackets(invalid_content_type_3) self.assertEqual( str(cm.exception), "Content type must be a compositional type!" ) ################################################### invalid_content_type_4 = "pt:optional{}" assert _has_brackets(invalid_content_type_4) is False invalid_content_type_5 = "pt:set[]7657" assert _has_brackets(invalid_content_type_5) is False invalid_content_type_6 = "pt:union [pt:int, pt:bool]" assert _has_brackets(invalid_content_type_6) is False invalid_content_type_7 = "pt:dict[pt:int, pt:bool] " assert _has_brackets(invalid_content_type_7) is False def test_is_valid_ct(self): """Test for the '_is_valid_ct' method.""" valid_content_type_1 = "ct:DataModel" assert _is_valid_ct(valid_content_type_1) is True valid_content_type_2 = "ct:ThisIsACustomContent" assert _is_valid_ct(valid_content_type_2) is True valid_content_type_3 = "ct:Query" assert _is_valid_ct(valid_content_type_3) is True valid_content_type_4 = " ct:Proposal " assert _is_valid_ct(valid_content_type_4) is True valid_content_type_5 = "ct:DSA" assert _is_valid_ct(valid_content_type_5) is True valid_content_type_6 = "ct:DataF" assert _is_valid_ct(valid_content_type_6) is True valid_content_type_7 = "ct:DataModel2" assert _is_valid_ct(valid_content_type_7) is True ################################################### invalid_content_type_1 = "ct:data" assert _is_valid_ct(invalid_content_type_1) is False invalid_content_type_2 = "Model" assert _is_valid_ct(invalid_content_type_2) is False invalid_content_type_3 = "ct: DataModel" assert _is_valid_ct(invalid_content_type_3) is False def test_is_valid_pt(self): """Test for the '_is_valid_pt' method.""" valid_content_type_1 = "pt:bytes" assert _is_valid_pt(valid_content_type_1) is True valid_content_type_2 = "pt:int" assert _is_valid_pt(valid_content_type_2) is True valid_content_type_3 = "pt:float" assert _is_valid_pt(valid_content_type_3) is True valid_content_type_4 = "pt:bool" assert _is_valid_pt(valid_content_type_4) is True valid_content_type_5 = "pt:str" assert _is_valid_pt(valid_content_type_5) is True valid_content_type_6 = " pt:int " assert _is_valid_pt(valid_content_type_6) is True ################################################### invalid_content_type_1 = "pt:integer" assert _is_valid_pt(invalid_content_type_1) is False invalid_content_type_2 = "bool" assert _is_valid_pt(invalid_content_type_2) is False invalid_content_type_3 = "pt: str" assert _is_valid_pt(invalid_content_type_3) is False invalid_content_type_4 = "pt;float" assert _is_valid_pt(invalid_content_type_4) is False def test_is_valid_set(self): """Test for the '_is_valid_set' method.""" valid_content_type_1 = "pt:set[pt:bytes]" assert _is_valid_set(valid_content_type_1) is True valid_content_type_2 = "pt:set[pt:int]" assert _is_valid_set(valid_content_type_2) is True valid_content_type_3 = "pt:set[pt:float]" assert _is_valid_set(valid_content_type_3) is True valid_content_type_4 = "pt:set[pt:bool]" assert _is_valid_set(valid_content_type_4) is True valid_content_type_5 = "pt:set[pt:str]" assert _is_valid_set(valid_content_type_5) is True valid_content_type_6 = " pt:set[ pt:int ] " assert _is_valid_set(valid_content_type_6) is True ################################################### invalid_content_type_1 = "pt:frozenset[pt:int]" assert _is_valid_set(invalid_content_type_1) is False invalid_content_type_2 = "set[pt:int]" assert _is_valid_set(invalid_content_type_2) is False invalid_content_type_3 = "pt: set[pt:int]" assert _is_valid_set(invalid_content_type_3) is False invalid_content_type_4 = "pt:set[integer]" assert _is_valid_set(invalid_content_type_4) is False invalid_content_type_5 = "pt:set[int]" assert _is_valid_set(invalid_content_type_5) is False invalid_content_type_6 = "pt:set{int]" assert _is_valid_set(invalid_content_type_6) is False invalid_content_type_7 = "pt:set[pt:int, pt:str]" assert _is_valid_set(invalid_content_type_7) is False invalid_content_type_8 = "pt:set[]" assert _is_valid_set(invalid_content_type_8) is False invalid_content_type_9 = "pt:set[pt:list[pt:int, pt:list[pt:bool]]" assert _is_valid_set(invalid_content_type_9) is False invalid_content_type_10 = "pt:set" assert _is_valid_set(invalid_content_type_10) is False def test_is_valid_list(self): """Test for the '_is_valid_list' method.""" valid_content_type_1 = "pt:list[pt:bytes]" assert _is_valid_list(valid_content_type_1) is True valid_content_type_2 = "pt:list[pt:int]" assert _is_valid_list(valid_content_type_2) is True valid_content_type_3 = "pt:list[pt:float]" assert _is_valid_list(valid_content_type_3) is True valid_content_type_4 = "pt:list[pt:bool]" assert _is_valid_list(valid_content_type_4) is True valid_content_type_5 = "pt:list[pt:str]" assert _is_valid_list(valid_content_type_5) is True valid_content_type_6 = " pt:list[ pt:bool ] " assert _is_valid_list(valid_content_type_6) is True ################################################### invalid_content_type_1 = "pt:tuple[pt:bytes]" assert _is_valid_list(invalid_content_type_1) is False invalid_content_type_2 = "list[pt:bool]" assert _is_valid_list(invalid_content_type_2) is False invalid_content_type_3 = "pt: list[pt:float]" assert _is_valid_list(invalid_content_type_3) is False invalid_content_type_4 = "pt:list[string]" assert _is_valid_list(invalid_content_type_4) is False invalid_content_type_5 = "pt:list[bool]" assert _is_valid_list(invalid_content_type_5) is False invalid_content_type_6 = "pt:list[bytes" assert _is_valid_list(invalid_content_type_6) is False invalid_content_type_7 = "pt:list[pt:float, pt:bool]" assert _is_valid_list(invalid_content_type_7) is False invalid_content_type_8 = "pt:list[]" assert _is_valid_list(invalid_content_type_8) is False invalid_content_type_9 = "pt:list[pt:set[pt:bool, pt:set[pt:str]]" assert _is_valid_list(invalid_content_type_9) is False invalid_content_type_10 = "pt:list" assert _is_valid_list(invalid_content_type_10) is False def test_is_valid_dict(self): """Test for the '_is_valid_dict' method.""" valid_content_type_1 = "pt:dict[pt:bytes, pt:int]" assert _is_valid_dict(valid_content_type_1) is True valid_content_type_2 = "pt:dict[pt:int, pt:int]" assert _is_valid_dict(valid_content_type_2) is True valid_content_type_3 = "pt:dict[pt:float, pt:str]" assert _is_valid_dict(valid_content_type_3) is True valid_content_type_4 = "pt:dict[pt:bool, pt:str]" assert _is_valid_dict(valid_content_type_4) is True valid_content_type_5 = "pt:dict[pt:bool,pt:float]" assert _is_valid_dict(valid_content_type_5) is True valid_content_type_6 = " pt:dict[ pt:bytes , pt:int ] " assert _is_valid_dict(valid_content_type_6) is True ################################################### invalid_content_type_1 = "pt:map[pt:bool, pt:str]" assert _is_valid_dict(invalid_content_type_1) is False invalid_content_type_2 = "dict[pt:int, pt:float]" assert _is_valid_dict(invalid_content_type_2) is False invalid_content_type_3 = "pt: dict[pt:bytes, pt:bool]" assert _is_valid_dict(invalid_content_type_3) is False invalid_content_type_4 = "pt:dict[float, pt:str]" assert _is_valid_dict(invalid_content_type_4) is False invalid_content_type_5 = "pt:dict[pt:bool, pt:integer]" assert _is_valid_dict(invalid_content_type_5) is False invalid_content_type_6 = "pt:dict(pt:boolean, pt:int" assert _is_valid_dict(invalid_content_type_6) is False invalid_content_type_7 = "pt:dict[pt:boolean]" assert _is_valid_dict(invalid_content_type_7) is False invalid_content_type_8 = "pt:dict[]" assert _is_valid_dict(invalid_content_type_8) is False invalid_content_type_9 = "pt:dict[pt:str, pt:float, pt:int, pt:bytes]" assert _is_valid_dict(invalid_content_type_9) is False invalid_content_type_10 = "pt:dict[pt:set[pt:bool, pt:str]" assert _is_valid_dict(invalid_content_type_10) is False invalid_content_type_11 = "pt:dict" assert _is_valid_dict(invalid_content_type_11) is False def test_is_valid_union(self): """Test for the '_is_valid_union' method.""" valid_content_type_1 = "pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str]" assert _is_valid_union(valid_content_type_1) is True valid_content_type_3 = "pt:union[pt:float, pt:bool]" assert _is_valid_union(valid_content_type_3) is True valid_content_type_3 = "pt:union[ct:DataModel, pt:bool]" assert _is_valid_union(valid_content_type_3) is True valid_content_type_3 = "pt:union[ct:DataModel, ct:DataModel2]" assert _is_valid_union(valid_content_type_3) is True valid_content_type_5 = "pt:union[pt:bool,pt:bytes]" assert _is_valid_union(valid_content_type_5) is True valid_content_type_4 = "pt:union[pt:set[pt:int], pt:set[pt:float]]" assert _is_valid_union(valid_content_type_4) is True valid_content_type_6 = " pt:union[ pt:bytes , pt:set[ pt:int ] ] " assert _is_valid_union(valid_content_type_6) is True valid_content_type_13 = "pt:union[pt:bytes, pt:set[pt:int]]" assert _is_valid_union(valid_content_type_13) is True ################################################### invalid_content_type_1 = "pt:onion[pt:bool, pt:str]" assert _is_valid_union(invalid_content_type_1) is False invalid_content_type_2 = "union[pt:int, pt:float]" assert _is_valid_union(invalid_content_type_2) is False invalid_content_type_3 = "pt: union[pt:set[pt:int], pt:bool]" assert _is_valid_union(invalid_content_type_3) is False invalid_content_type_4 = "pt:union[float, pt:str" assert _is_valid_union(invalid_content_type_4) is False invalid_content_type_5 = "pt:union[pt:int, pt:dict[pt:str, pt:bool]" assert _is_valid_union(invalid_content_type_5) is False invalid_content_type_6 = "pt:union{pt:boolean, pt:int]" assert _is_valid_union(invalid_content_type_6) is False invalid_content_type_7 = "pt:union[pt:boolean]" assert _is_valid_union(invalid_content_type_7) is False invalid_content_type_8 = "pt:union[]" assert _is_valid_union(invalid_content_type_8) is False invalid_content_type_9 = "pt:union[pt:str, pt:int, pt:str]" assert _is_valid_union(invalid_content_type_9) is False invalid_content_type_10 = "pt:union[pt:set[pt:integer], pt:float]" assert _is_valid_union(invalid_content_type_10) is False invalid_content_type_11 = ( "pt:union[pt:dict[pt:set[pt:bool]], pt:list[pt:set[pt:str]]]" ) assert _is_valid_union(invalid_content_type_11) is False invalid_content_type_12 = "pt:union" assert _is_valid_union(invalid_content_type_12) is False def test_is_valid_optional(self): """Test for the '_is_valid_optional' method.""" valid_content_type_1 = ( "pt:optional[pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str]]" ) assert _is_valid_optional(valid_content_type_1) is True valid_content_type_2 = "pt:optional[pt:union[pt:bytes, pt:str]]" assert _is_valid_optional(valid_content_type_2) is True valid_content_type_3 = "pt:optional[pt:bytes]" assert _is_valid_optional(valid_content_type_3) is True valid_content_type_4 = "pt:optional[pt:int]" assert _is_valid_optional(valid_content_type_4) is True valid_content_type_5 = "pt:optional[pt:float]" assert _is_valid_optional(valid_content_type_5) is True valid_content_type_6 = "pt:optional[pt:bool]" assert _is_valid_optional(valid_content_type_6) is True valid_content_type_7 = "pt:optional[pt:str]" assert _is_valid_optional(valid_content_type_7) is True valid_content_type_8 = "pt:optional[pt:set[pt:bytes]]" assert _is_valid_optional(valid_content_type_8) is True valid_content_type_9 = "pt:optional[pt:list[pt:int]]" assert _is_valid_optional(valid_content_type_9) is True valid_content_type_10 = ( " pt:optional[ pt:dict[ pt:float , pt:bool ] ] " ) assert _is_valid_optional(valid_content_type_10) is True ################################################### invalid_content_type_1 = "pt:optinal[pt:bytes]" assert _is_valid_optional(invalid_content_type_1) is False invalid_content_type_2 = "optional[pt:int]" assert _is_valid_optional(invalid_content_type_2) is False invalid_content_type_3 = "pt: optional[pt:float]" assert _is_valid_optional(invalid_content_type_3) is False invalid_content_type_4 = "pt:optional[bool]" assert _is_valid_optional(invalid_content_type_4) is False invalid_content_type_5 = "pt:optional[pt:str" assert _is_valid_optional(invalid_content_type_5) is False invalid_content_type_6 = "pt:optional{pt:set[pt:int]]" assert _is_valid_optional(invalid_content_type_6) is False invalid_content_type_7 = "pt:optional[pt:string]" assert _is_valid_optional(invalid_content_type_7) is False invalid_content_type_8 = "pt:optional[]" assert _is_valid_optional(invalid_content_type_8) is False invalid_content_type_9 = "pt:optional[pt:str, pt:int, pt:list[pt:bool]]" assert _is_valid_optional(invalid_content_type_9) is False invalid_content_type_10 = "pt:optional[pt:list[pt:boolean]]" assert _is_valid_optional(invalid_content_type_10) is False invalid_content_type_11 = "pt:optional[pt:dict[pt:set[pt:int]]]" assert _is_valid_optional(invalid_content_type_11) is False invalid_content_type_12 = "pt:optional" assert _is_valid_optional(invalid_content_type_12) is False def test_is_valid_content_type_format(self): """Test for the '_is_valid_content_type_format' method.""" valid_content_type_1 = "ct:DataModel" assert _is_valid_content_type_format(valid_content_type_1) is True valid_content_type_2 = "pt:int" assert _is_valid_content_type_format(valid_content_type_2) is True valid_content_type_3 = "pt:set[pt:float]" assert _is_valid_content_type_format(valid_content_type_3) is True valid_content_type_4 = "pt:list[pt:bool]" assert _is_valid_content_type_format(valid_content_type_4) is True valid_content_type_5 = "pt:dict[pt:bool,pt:float]" assert _is_valid_content_type_format(valid_content_type_5) is True valid_content_type_6 = ( "pt:optional[pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str]]" ) assert _is_valid_content_type_format(valid_content_type_6) is True valid_content_type_7 = ( " pt:optional[ pt:dict[ pt:float , pt:bool ] ] " ) assert _is_valid_content_type_format(valid_content_type_7) is True ################################################### invalid_content_type_1 = "ct:data" assert _is_valid_content_type_format(invalid_content_type_1) is False invalid_content_type_2 = "bool" assert _is_valid_content_type_format(invalid_content_type_2) is False invalid_content_type_3 = "pt: set[pt:int]" assert _is_valid_content_type_format(invalid_content_type_3) is False invalid_content_type_4 = "pt:list[string]" assert _is_valid_content_type_format(invalid_content_type_4) is False invalid_content_type_5 = "pt:dict[pt:bool, pt:integer]" assert _is_valid_content_type_format(invalid_content_type_5) is False invalid_content_type_6 = "pt:union{pt:boolean, pt:int]" assert _is_valid_content_type_format(invalid_content_type_6) is False invalid_content_type_7 = "pt:optional[pt:str, pt:int, pt:list[pt:bool]]" assert _is_valid_content_type_format(invalid_content_type_7) is False def test_validate_performatives(self): """Test for the '_validate_performatives' method.""" valid_content_type_1 = "offer" valid_result_1, valid_msg_1 = _validate_performatives(valid_content_type_1) assert valid_result_1 is True assert valid_msg_1 == "Performative '{}' is valid.".format(valid_content_type_1) valid_content_type_2 = "send_HTTP_message" valid_result_2, valid_msg_2 = _validate_performatives(valid_content_type_2) assert valid_result_2 is True assert valid_msg_2 == "Performative '{}' is valid.".format(valid_content_type_2) valid_content_type_3 = "request_2PL" valid_result_3, valid_msg_3 = _validate_performatives(valid_content_type_3) assert valid_result_3 is True assert valid_msg_3 == "Performative '{}' is valid.".format(valid_content_type_3) valid_content_type_4 = "argue" valid_result_4, valid_msg_4 = _validate_performatives(valid_content_type_4) assert valid_result_4 is True assert valid_msg_4 == "Performative '{}' is valid.".format(valid_content_type_4) ################################################### invalid_content_type_1 = "_offer" invalid_result_1, invalid_msg_1 = _validate_performatives( invalid_content_type_1 ) assert invalid_result_1 is False assert ( invalid_msg_1 == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( invalid_content_type_1, PERFORMATIVE_REGEX_PATTERN ) ) invalid_content_type_2 = "request_" invalid_result_2, invalid_msg_2 = _validate_performatives( invalid_content_type_2 ) assert invalid_result_2 is False assert ( invalid_msg_2 == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( invalid_content_type_2, PERFORMATIVE_REGEX_PATTERN ) ) invalid_content_type_3 = "_query_" invalid_result_3, invalid_msg_3 = _validate_performatives( invalid_content_type_3 ) assert invalid_result_3 is False assert ( invalid_msg_3 == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( invalid_content_type_3, PERFORMATIVE_REGEX_PATTERN ) ) invalid_content_type_4 = "$end" invalid_result_4, invalid_msg_4 = _validate_performatives( invalid_content_type_4 ) assert invalid_result_4 is False assert ( invalid_msg_4 == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( invalid_content_type_4, PERFORMATIVE_REGEX_PATTERN ) ) invalid_content_type_5 = "create()" invalid_result_5, invalid_msg_5 = _validate_performatives( invalid_content_type_5 ) assert invalid_result_5 is False assert ( invalid_msg_5 == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( invalid_content_type_5, PERFORMATIVE_REGEX_PATTERN ) ) invalid_content_type_6 = "_body" invalid_result_6, invalid_msg_6 = _validate_performatives( invalid_content_type_6 ) assert invalid_result_6 is False assert ( invalid_msg_6 == "Invalid name for performative '{}'. This name is reserved.".format( invalid_content_type_6, ) ) invalid_content_type_7 = "message_id" invalid_result_7, invalid_msg_7 = _validate_performatives( invalid_content_type_7 ) assert invalid_result_7 is False assert ( invalid_msg_6 == "Invalid name for performative '{}'. This name is reserved.".format( invalid_content_type_6, ) ) def test_validate_content_name(self): """Test for the '_validate_content_name' method.""" performative = "some_performative" valid_content_type_1 = "content" valid_result_1, valid_msg_1 = _validate_content_name( valid_content_type_1, performative ) assert valid_result_1 is True assert valid_msg_1 == "Content name '{}' of performative '{}' is valid.".format( valid_content_type_1, performative ) valid_content_type_2 = "HTTP_msg_name" valid_result_2, valid_msg_2 = _validate_content_name( valid_content_type_2, performative ) assert valid_result_2 is True assert valid_msg_2 == "Content name '{}' of performative '{}' is valid.".format( valid_content_type_2, performative ) valid_content_type_3 = "number_of_3PLs" valid_result_3, valid_msg_3 = _validate_content_name( valid_content_type_3, performative ) assert valid_result_3 is True assert valid_msg_3 == "Content name '{}' of performative '{}' is valid.".format( valid_content_type_3, performative ) valid_content_type_4 = "model" valid_result_4, valid_msg_4 = _validate_content_name( valid_content_type_4, performative ) assert valid_result_4 is True assert valid_msg_4 == "Content name '{}' of performative '{}' is valid.".format( valid_content_type_4, performative ) ################################################### invalid_content_type_1 = "_content" invalid_result_1, invalid_msg_1 = _validate_content_name( invalid_content_type_1, performative ) assert invalid_result_1 is False assert ( invalid_msg_1 == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( invalid_content_type_1, performative, CONTENT_NAME_REGEX_PATTERN ) ) invalid_content_type_2 = "content_" invalid_result_2, invalid_msg_2 = _validate_content_name( invalid_content_type_2, performative ) assert invalid_result_2 is False assert ( invalid_msg_2 == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( invalid_content_type_2, performative, CONTENT_NAME_REGEX_PATTERN ) ) invalid_content_type_3 = "_content_" invalid_result_3, invalid_msg_3 = _validate_content_name( invalid_content_type_3, performative ) assert invalid_result_3 is False assert ( invalid_msg_3 == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( invalid_content_type_3, performative, CONTENT_NAME_REGEX_PATTERN ) ) invalid_content_type_4 = "con^en^" invalid_result_4, invalid_msg_4 = _validate_content_name( invalid_content_type_4, performative ) assert invalid_result_4 is False assert ( invalid_msg_4 == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( invalid_content_type_4, performative, CONTENT_NAME_REGEX_PATTERN ) ) invalid_content_type_5 = "some_content()" invalid_result_5, invalid_msg_5 = _validate_content_name( invalid_content_type_5, performative ) assert invalid_result_5 is False assert ( invalid_msg_5 == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( invalid_content_type_5, performative, CONTENT_NAME_REGEX_PATTERN ) ) invalid_content_type_6 = "target" invalid_result_6, invalid_msg_6 = _validate_content_name( invalid_content_type_6, performative ) assert invalid_result_6 is False assert ( invalid_msg_6 == "Invalid name for content '{}' of performative '{}'. This name is reserved.".format( invalid_content_type_6, performative, ) ) invalid_content_type_7 = "performative" invalid_result_7, invalid_msg_7 = _validate_content_name( invalid_content_type_7, performative ) assert invalid_result_7 is False assert ( invalid_msg_7 == "Invalid name for content '{}' of performative '{}'. This name is reserved.".format( invalid_content_type_7, performative, ) ) def test_validate_content_type(self): """Test for the '_validate_content_type' method.""" performative = "some_performative" content_name = "some_content_name" valid_content_type_1 = "ct:DataModel" valid_result_1, valid_msg_1 = _validate_content_type( valid_content_type_1, content_name, performative ) assert valid_result_1 is True assert ( valid_msg_1 == "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ) ) valid_content_type_2 = "pt:int" valid_result_2, valid_msg_2 = _validate_content_type( valid_content_type_2, content_name, performative ) assert valid_result_2 is True assert ( valid_msg_2 == "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ) ) valid_content_type_3 = "pt:set[pt:float]" valid_result_3, valid_msg_3 = _validate_content_type( valid_content_type_3, content_name, performative ) assert valid_result_3 is True assert ( valid_msg_3 == "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ) ) valid_content_type_4 = "pt:list[pt:bool]" valid_result_4, valid_msg_4 = _validate_content_type( valid_content_type_4, content_name, performative ) assert valid_result_4 is True assert ( valid_msg_4 == "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ) ) valid_content_type_5 = "pt:dict[pt:bool,pt:float]" valid_result_5, valid_msg_5 = _validate_content_type( valid_content_type_5, content_name, performative ) assert valid_result_5 is True assert ( valid_msg_5 == "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ) ) valid_content_type_6 = ( "pt:optional[pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str]]" ) valid_result_6, valid_msg_6 = _validate_content_type( valid_content_type_6, content_name, performative ) assert valid_result_6 is True assert ( valid_msg_6 == "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ) ) valid_content_type_7 = ( " pt:optional[ pt:dict[ pt:float , pt:bool ] ] " ) valid_result_7, valid_msg_7 = _validate_content_type( valid_content_type_7, content_name, performative ) assert valid_result_7 is True assert ( valid_msg_7 == "Type of content '{}' of performative '{}' is valid.".format( content_name, performative ) ) ################################################### invalid_content_type_1 = "ct:data" invalid_result_1, invalid_msg_1 = _validate_content_type( invalid_content_type_1, content_name, performative ) assert invalid_result_1 is False assert ( invalid_msg_1 == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ) ) invalid_content_type_2 = "bool" invalid_result_2, invalid_msg_2 = _validate_content_type( invalid_content_type_2, content_name, performative ) assert invalid_result_2 is False assert ( invalid_msg_2 == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ) ) invalid_content_type_3 = "pt: set[pt:int]" invalid_result_3, invalid_msg_3 = _validate_content_type( invalid_content_type_3, content_name, performative ) assert invalid_result_3 is False assert ( invalid_msg_3 == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ) ) invalid_content_type_4 = "pt:list[string]" invalid_result_4, invalid_msg_4 = _validate_content_type( invalid_content_type_4, content_name, performative ) assert invalid_result_4 is False assert ( invalid_msg_4 == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ) ) invalid_content_type_5 = "pt:dict[pt:bool, pt:integer]" invalid_result_5, invalid_msg_5 = _validate_content_type( invalid_content_type_5, content_name, performative ) assert invalid_result_5 is False assert ( invalid_msg_5 == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ) ) invalid_content_type_6 = "pt:union{pt:boolean, pt:int]" invalid_result_6, invalid_msg_6 = _validate_content_type( invalid_content_type_6, content_name, performative ) assert invalid_result_6 is False assert ( invalid_msg_6 == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ) ) invalid_content_type_7 = "pt:optional[pt:str, pt:int, pt:list[pt:bool]]" invalid_result_7, invalid_msg_7 = _validate_content_type( invalid_content_type_7, content_name, performative ) assert invalid_result_7 is False assert ( invalid_msg_7 == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( content_name, performative, ) ) @mock.patch( "aea.configurations.base.ProtocolSpecification", ) def test_validate_speech_acts_section(self, mocked_spec): """Test for the '_validate_speech_acts_section' method.""" valid_speech_act_content_config_1 = SpeechActContentConfig( content_1="ct:CustomType", content_2="pt:int" ) valid_speech_act_content_config_2 = SpeechActContentConfig( content_3="ct:DataModel" ) valid_speech_act_content_config_3 = SpeechActContentConfig() speech_act_1 = CRUDCollection() speech_act_1.create("perm_1", valid_speech_act_content_config_1) speech_act_1.create("perm_2", valid_speech_act_content_config_2) speech_act_1.create("perm_3", valid_speech_act_content_config_3) mocked_spec.speech_acts = speech_act_1 ( valid_result_1, valid_msg_1, valid_all_per_1, valid_all_content_1, ) = _validate_speech_acts_section(mocked_spec) assert valid_result_1 is True assert valid_msg_1 == "Speech-acts are valid." assert valid_all_per_1 == {"perm_1", "perm_2", "perm_3"} assert valid_all_content_1 == {"ct:CustomType", "ct:DataModel"} ################################################### speech_act_3 = CRUDCollection() invalid_perm = "_query_" speech_act_3.create(invalid_perm, valid_speech_act_content_config_1) mocked_spec.speech_acts = speech_act_3 ( invalid_result_1, invalid_msg_1, invalid_all_per_1, invalid_all_content_1, ) = _validate_speech_acts_section(mocked_spec) assert invalid_result_1 is False assert ( invalid_msg_1 == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( invalid_perm, PERFORMATIVE_REGEX_PATTERN ) ) assert invalid_all_per_1 is None assert invalid_all_content_1 is None invalid_speech_act_content_config_1 = SpeechActContentConfig(target="pt:int") speech_act_4 = CRUDCollection() valid_perm = "perm_1" speech_act_4.create(valid_perm, invalid_speech_act_content_config_1) mocked_spec.speech_acts = speech_act_4 ( invalid_result_2, invalid_msg_2, invalid_all_per_2, invalid_all_content_2, ) = _validate_speech_acts_section(mocked_spec) assert invalid_result_2 is False assert ( invalid_msg_2 == "Invalid name for content '{}' of performative '{}'. This name is reserved.".format( "target", valid_perm, ) ) assert invalid_all_per_2 is None assert invalid_all_content_2 is None invalid_speech_act_content_config_2 = SpeechActContentConfig( content_name_1="pt: set[pt:int]" ) speech_act_5 = CRUDCollection() speech_act_5.create(valid_perm, invalid_speech_act_content_config_2) mocked_spec.speech_acts = speech_act_5 ( invalid_result_3, invalid_msg_3, invalid_all_per_3, invalid_all_content_3, ) = _validate_speech_acts_section(mocked_spec) assert invalid_result_3 is False assert ( invalid_msg_3 == "Invalid type for content 'content_name_1' of performative '{}'. See documentation for the correct format of specification types.".format( valid_perm, ) ) assert invalid_all_per_3 is None assert invalid_all_content_3 is None speech_act_6 = CRUDCollection() mocked_spec.speech_acts = speech_act_6 ( invalid_result_4, invalid_msg_4, invalid_all_per_4, invalid_all_content_4, ) = _validate_speech_acts_section(mocked_spec) assert invalid_result_4 is False assert invalid_msg_4 == "Speech-acts cannot be empty!" assert invalid_all_per_4 is None assert invalid_all_content_4 is None invalid_speech_act_content_config_3 = SpeechActContentConfig(content_name_1=123) speech_act_7 = CRUDCollection() speech_act_7.create(valid_perm, invalid_speech_act_content_config_3) mocked_spec.speech_acts = speech_act_7 ( invalid_result_5, invalid_msg_5, invalid_all_per_5, invalid_all_content_5, ) = _validate_speech_acts_section(mocked_spec) assert invalid_result_5 is False assert ( invalid_msg_5 == f"Invalid type for '{'content_name_1'}'. Expected str. Found {type(123)}." ) assert invalid_all_per_5 is None assert invalid_all_content_5 is None invalid_speech_act_content_config_4 = SpeechActContentConfig( content_name_1="pt:int" ) invalid_speech_act_content_config_5 = SpeechActContentConfig( content_name_1="pt:float" ) speech_act_8 = CRUDCollection() speech_act_8.create("perm_1", invalid_speech_act_content_config_4) speech_act_8.create("perm_2", invalid_speech_act_content_config_5) mocked_spec.speech_acts = speech_act_8 ( invalid_result_6, invalid_msg_6, invalid_all_per_6, invalid_all_content_6, ) = _validate_speech_acts_section(mocked_spec) assert invalid_result_6 is False assert ( invalid_msg_6 == "Content 'content_name_1' with type 'pt:float' under performative 'perm_2' is already defined under performative 'perm_1' with a different type ('pt:int')." ) assert invalid_all_per_6 is None assert invalid_all_content_6 is None @mock.patch( "aea.configurations.base.ProtocolSpecification", ) def test_validate_protocol_buffer_schema_code_snippets(self, mocked_spec): """Test for the '_validate_protocol_buffer_schema_code_snippets' method.""" valid_protobuf_snippet_1 = { "ct:DataModel": "bytes bytes_field = 1;\nint32 int_field = 2;\nfloat float_field = 3;\nbool bool_field = 4;\nstring str_field = 5;\nrepeated int32 set_field = 6;\nrepeated string list_field = 7;\nmap dict_field = 8;\n" } valid_all_content_1 = {"ct:DataModel"} mocked_spec.protobuf_snippets = valid_protobuf_snippet_1 valid_result_1, valid_msg_1, = _validate_protocol_buffer_schema_code_snippets( mocked_spec, valid_all_content_1 ) assert valid_result_1 is True assert valid_msg_1 == "Protobuf code snippet section is valid." valid_protobuf_snippet_2 = {} valid_all_content_2 = set() mocked_spec.protobuf_snippets = valid_protobuf_snippet_2 valid_result_2, valid_msg_2, = _validate_protocol_buffer_schema_code_snippets( mocked_spec, valid_all_content_2 ) assert valid_result_2 is True assert valid_msg_2 == "Protobuf code snippet section is valid." ################################################### invalid_protobuf_snippet_1 = { "ct:DataModel": "bytes bytes_field = 1;\nint32 int_field = 2;\nfloat float_field = 3;\nbool bool_field = 4;\nstring str_field = 5;", "ct:Query": "bytes bytes_field = 1;", } invalid_all_content_1 = {"ct:DataModel"} mocked_spec.protobuf_snippets = invalid_protobuf_snippet_1 ( invalid_result_1, invalid_msg_1, ) = _validate_protocol_buffer_schema_code_snippets( mocked_spec, invalid_all_content_1 ) assert invalid_result_1 is False assert ( invalid_msg_1 == "Extra protobuf code snippet provided. Type 'ct:Query' is not used anywhere in your protocol definition." ) invalid_protobuf_snippet_2 = { "ct:DataModel": "bytes bytes_field = 1;\nint32 int_field = 2;\nfloat float_field = 3;", } invalid_all_content_2 = {"ct:DataModel", "ct:Frame"} mocked_spec.protobuf_snippets = invalid_protobuf_snippet_2 ( invalid_result_2, invalid_msg_2, ) = _validate_protocol_buffer_schema_code_snippets( mocked_spec, invalid_all_content_2 ) assert invalid_result_2 is False assert ( invalid_msg_2 == "No protobuf code snippet is provided for the following custom types: {}".format( {"ct:Frame"}, ) ) def test_validate_field_existence(self): """Test for the '_validate_field_existence' method.""" valid_dialogue_config_1 = { "initiation": ["performative_ct", "performative_pt"], "reply": { "performative_ct": ["performative_pct"], "performative_pt": ["performative_pmt"], "performative_pct": ["performative_mt", "performative_o"], "performative_pmt": ["performative_mt", "performative_o"], "performative_mt": [], "performative_o": [], "performative_empty_contents": ["performative_empty_contents"], }, "termination": [ "performative_mt", "performative_o", "performative_empty_contents", ], "roles": {"role_1": None, "role_2": None}, "end_states": ["end_state_1", "end_state_2", "end_state_3"], "keep_terminal_state_dialogues": True, } ( valid_result_1, valid_msg_1, ) = _validate_field_existence(valid_dialogue_config_1) assert valid_result_1 is True assert valid_msg_1 == "Dialogue section has all the required fields." ################################################### invalid_dialogue_config_1 = valid_dialogue_config_1.copy() invalid_dialogue_config_1.pop("initiation") ( invalid_result_1, invalid_msg_1, ) = _validate_field_existence(invalid_dialogue_config_1) assert invalid_result_1 is False assert ( invalid_msg_1 == "Missing required field 'initiation' in the dialogue section of the protocol specification." ) invalid_dialogue_config_2 = valid_dialogue_config_1.copy() invalid_dialogue_config_2.pop("reply") ( invalid_result_2, invalid_msg_2, ) = _validate_field_existence(invalid_dialogue_config_2) assert invalid_result_2 is False assert ( invalid_msg_2 == "Missing required field 'reply' in the dialogue section of the protocol specification." ) def test_validate_initiation(self): """Test for the '_validate_initiation' method.""" valid_initiation_1 = ["perm_1", "perm_2"] valid_performatives_set = {"perm_1", "perm_2", "perm_3", "perm_4"} valid_result_1, valid_msg_1 = _validate_initiation( valid_initiation_1, valid_performatives_set ) assert valid_result_1 is True assert valid_msg_1 == "Initial messages are valid." ################################################### invalid_initiation_1 = [] invalid_result_1, invalid_msg_1 = _validate_initiation( invalid_initiation_1, valid_performatives_set ) assert invalid_result_1 is False assert ( invalid_msg_1 == "At least one initial performative for this dialogue must be specified." ) invalid_initiation_2 = ["perm_5"] invalid_result_2, invalid_msg_2 = _validate_initiation( invalid_initiation_2, valid_performatives_set ) assert invalid_result_2 is False assert ( invalid_msg_2 == "Performative 'perm_5' specified in \"initiation\" is not defined in the protocol's speech-acts." ) invalid_initiation_3 = "perm_1" invalid_result_3, invalid_msg_3 = _validate_initiation( invalid_initiation_3, valid_performatives_set ) assert invalid_result_3 is False assert ( invalid_msg_3 == f"Invalid type for initiation. Expected list. Found '{type(invalid_initiation_3)}'." ) def test_validate_reply(self): """Test for the '_validate_reply' method.""" valid_reply_1 = { "performative_ct": ["performative_pct"], "performative_pt": ["performative_pmt"], "performative_pct": ["performative_mt", "performative_o"], "performative_pmt": ["performative_mt", "performative_o"], "performative_mt": [], "performative_o": [], "performative_empty_contents": ["performative_empty_contents"], } valid_performatives_set_1 = { "performative_ct", "performative_pt", "performative_pct", "performative_pmt", "performative_mt", "performative_o", "performative_empty_contents", } ( valid_result_1, valid_msg_1, terminal_performatives_from_reply_1, ) = _validate_reply(valid_reply_1, valid_performatives_set_1) assert valid_result_1 is True assert valid_msg_1 == "Reply structure is valid." assert terminal_performatives_from_reply_1 == { "performative_mt", "performative_o", } ################################################### invalid_reply_1 = { "perm_1": ["perm_2"], "perm_2": ["perm_3"], "perm_3": ["perm_4"], "perm_4": [], } invalid_performatives_set_1 = {"perm_1", "perm_2", "perm_3", "perm_4", "perm_5"} ( invalid_result_1, invalid_msg_1, invalid_terminal_performatives_from_reply_1, ) = _validate_reply(invalid_reply_1, invalid_performatives_set_1) assert invalid_result_1 is False assert ( invalid_msg_1 == "No reply is provided for the following performatives: {}".format( {"perm_5"}, ) ) assert invalid_terminal_performatives_from_reply_1 is None invalid_reply_2 = { "perm_1": ["perm_2"], "perm_2": ["perm_3"], "perm_3": ["perm_4"], "perm_4": ["perm_5"], "perm_5": [], } invalid_performatives_set_2 = {"perm_1", "perm_2", "perm_3", "perm_4"} ( invalid_result_2, invalid_msg_2, invalid_terminal_performatives_from_reply_2, ) = _validate_reply(invalid_reply_2, invalid_performatives_set_2) assert invalid_result_2 is False assert ( invalid_msg_2 == "Performative 'perm_5' in the list of replies for 'perm_4' is not defined in speech-acts." ) assert invalid_terminal_performatives_from_reply_2 is None invalid_reply_3 = ["perm_1", "perm_2", "perm_3", "perm_4", "perm_5"] ( invalid_result_3, invalid_msg_3, invalid_terminal_performatives_from_reply_3, ) = _validate_reply(invalid_reply_3, invalid_performatives_set_1) assert invalid_result_3 is False assert ( invalid_msg_3 == f"Invalid type for the reply definition. Expected dict. Found '{type(invalid_reply_3)}'." ) assert invalid_terminal_performatives_from_reply_3 is None invalid_reply_4 = { "perm_1": {"perm_2"}, "perm_2": {"perm_3"}, "perm_3": {"perm_4"}, "perm_4": {"perm_5"}, "perm_5": set(), } ( invalid_result_4, invalid_msg_4, invalid_terminal_performatives_from_reply_4, ) = _validate_reply(invalid_reply_4, invalid_performatives_set_1) assert invalid_result_4 is False assert ( invalid_msg_4 == f"Invalid type for replies of performative perm_1. Expected list. Found '{type({'perm_2'})}'." ) assert invalid_terminal_performatives_from_reply_4 is None invalid_reply_5 = { "perm_1": ["perm_2"], "perm_2": ["perm_3"], "perm_3": ["perm_4"], "perm_4": ["perm_1"], "perm_5": [], } ( invalid_result_5, invalid_msg_5, invalid_terminal_performatives_from_reply_5, ) = _validate_reply(invalid_reply_5, invalid_performatives_set_2) assert invalid_result_5 is False assert ( invalid_msg_5 == "Performative 'perm_5' specified in \"reply\" is not defined in the protocol's speech-acts." ) assert invalid_terminal_performatives_from_reply_5 is None def test_validate_termination(self): """Test for the '_validate_termination' method.""" valid_termination_1 = ["perm_4", "perm_3"] valid_performatives_set = {"perm_1", "perm_2", "perm_3", "perm_4"} valid_terminal_performatives_from_reply_1 = {"perm_4", "perm_3"} valid_result_1, valid_msg_1 = _validate_termination( valid_termination_1, valid_performatives_set, valid_terminal_performatives_from_reply_1, ) assert valid_result_1 is True assert valid_msg_1 == "Terminal messages are valid." ################################################### invalid_termination_1 = [] invalid_terminal_performatives_from_reply_1 = set() invalid_result_1, invalid_msg_1 = _validate_termination( invalid_termination_1, valid_performatives_set, invalid_terminal_performatives_from_reply_1, ) assert invalid_result_1 is False assert ( invalid_msg_1 == "At least one terminal performative for this dialogue must be specified." ) invalid_termination_2 = ["perm_5"] invalid_terminal_performatives_from_reply_2 = {"perm_5"} invalid_result_2, invalid_msg_2 = _validate_termination( invalid_termination_2, valid_performatives_set, invalid_terminal_performatives_from_reply_2, ) assert invalid_result_2 is False assert ( invalid_msg_2 == "Performative 'perm_5' specified in \"termination\" is not defined in the protocol's speech-acts." ) invalid_termination_3 = {"perm_5"} invalid_terminal_performatives_from_reply_3 = {"perm_5"} invalid_result_3, invalid_msg_3 = _validate_termination( invalid_termination_3, valid_performatives_set, invalid_terminal_performatives_from_reply_3, ) assert invalid_result_3 is False assert ( invalid_msg_3 == f"Invalid type for termination. Expected list. Found '{type(invalid_termination_3)}'." ) invalid_termination_4 = ["perm_4", "perm_3", "perm_4", "perm_3", "perm_1"] invalid_terminal_performatives_from_reply_4 = {"perm_4", "perm_3", "perm_1"} invalid_result_4, invalid_msg_4 = _validate_termination( invalid_termination_4, valid_performatives_set, invalid_terminal_performatives_from_reply_4, ) assert invalid_result_4 is False assert ( invalid_msg_4 == f'There are {2} duplicate performatives in "termination".' ) invalid_termination_5 = ["perm_4", "perm_3"] invalid_terminal_performatives_from_reply_5 = {"perm_4"} invalid_result_5, invalid_msg_5 = _validate_termination( invalid_termination_5, valid_performatives_set, invalid_terminal_performatives_from_reply_5, ) assert invalid_result_5 is False assert ( invalid_msg_5 == 'The terminal performative \'perm_3\' specified in "termination" is assigned replies in "reply".' ) invalid_termination_6 = ["perm_4"] invalid_terminal_performatives_from_reply_6 = {"perm_4", "perm_3"} invalid_result_6, invalid_msg_6 = _validate_termination( invalid_termination_6, valid_performatives_set, invalid_terminal_performatives_from_reply_6, ) assert invalid_result_6 is False assert ( invalid_msg_6 == "The performative 'perm_3' has no replies but is not listed as a terminal performative in \"termination\"." ) def test_validate_roles(self): """Test for the '_validate_roles' method.""" valid_roles_1 = {"role_1": None, "role_2": None} valid_result_1, valid_msg_1 = _validate_roles(valid_roles_1) assert valid_result_1 is True assert valid_msg_1 == "Dialogue roles are valid." valid_roles_2 = {"role_1": None} valid_result_2, valid_msg_2 = _validate_roles(valid_roles_2) assert valid_result_2 is True assert valid_msg_2 == "Dialogue roles are valid." ################################################### invalid_roles_1 = {} invalid_result_1, invalid_msg_1 = _validate_roles(invalid_roles_1) assert invalid_result_1 is False assert ( invalid_msg_1 == "There must be either 1 or 2 roles defined in this dialogue. Found 0" ) invalid_roles_2 = {"role_1": None, "role_2": None, "role_3": None} invalid_result_2, invalid_msg_2 = _validate_roles(invalid_roles_2) assert invalid_result_2 is False assert ( invalid_msg_2 == "There must be either 1 or 2 roles defined in this dialogue. Found 3" ) invalid_roles_3 = {"_agent_": None} invalid_result_3, invalid_msg_3 = _validate_roles(invalid_roles_3) assert invalid_result_3 is False assert ( invalid_msg_3 == "Invalid name for role '_agent_'. Role names must match the following regular expression: {} ".format( ROLE_REGEX_PATTERN ) ) invalid_roles_4 = {"client"} invalid_result_4, invalid_msg_4 = _validate_roles(invalid_roles_4) assert invalid_result_4 is False assert ( invalid_msg_4 == f"Invalid type for roles. Expected dict. Found '{type(invalid_roles_4)}'." ) def test_validate_end_states(self): """Test for the '_validate_end_states' method.""" valid_end_states_1 = ["end_state_1", "end_state_2"] valid_result_1, valid_msg_1 = _validate_end_states(valid_end_states_1) assert valid_result_1 is True assert valid_msg_1 == "Dialogue end_states are valid." valid_end_states_2 = [] valid_result_2, valid_msg_2 = _validate_end_states(valid_end_states_2) assert valid_result_2 is True assert valid_msg_2 == "Dialogue end_states are valid." ################################################### invalid_end_states_1 = ["_end_state_1"] invalid_result_1, invalid_msg_1 = _validate_end_states(invalid_end_states_1) assert invalid_result_1 is False assert ( invalid_msg_1 == "Invalid name for end_state '_end_state_1'. End_state names must match the following regular expression: {} ".format( END_STATE_REGEX_PATTERN ) ) invalid_end_states_2 = ["end_$tate_1"] invalid_result_2, invalid_msg_2 = _validate_end_states(invalid_end_states_2) assert invalid_result_2 is False assert ( invalid_msg_2 == "Invalid name for end_state 'end_$tate_1'. End_state names must match the following regular expression: {} ".format( END_STATE_REGEX_PATTERN ) ) invalid_end_states_3 = {"end_state_1"} invalid_result_3, invalid_msg_3 = _validate_end_states(invalid_end_states_3) assert invalid_result_3 is False assert ( invalid_msg_3 == f"Invalid type for roles. Expected list. Found '{type(invalid_end_states_3)}'." ) def test_validate_keep_terminal(self): """Test for the '_validate_keep_terminal' method.""" valid_keep_terminal_state_dialogues_1 = True valid_result_1, valid_msg_1 = _validate_keep_terminal( valid_keep_terminal_state_dialogues_1 ) assert valid_result_1 is True assert valid_msg_1 == "Dialogue keep_terminal_state_dialogues is valid." valid_keep_terminal_state_dialogues_2 = False valid_result_2, valid_msg_2 = _validate_keep_terminal( valid_keep_terminal_state_dialogues_2 ) assert valid_result_2 is True assert valid_msg_2 == "Dialogue keep_terminal_state_dialogues is valid." ################################################### invalid_keep_terminal_state_dialogues_1 = "some_non_boolean_value" invalid_result_1, invalid_msg_1 = _validate_keep_terminal( invalid_keep_terminal_state_dialogues_1 ) assert invalid_result_1 is False assert ( invalid_msg_1 == f"Invalid type for keep_terminal_state_dialogues. Expected bool. Found {type(invalid_keep_terminal_state_dialogues_1)}." ) @mock.patch( "aea.configurations.base.ProtocolSpecification", ) def test_validate_dialogue_section(self, mocked_spec): """Test for the '_validate_dialogue_section' method.""" valid_dialogue_config_1 = { "initiation": ["performative_ct", "performative_pt"], "reply": { "performative_ct": ["performative_pct"], "performative_pt": ["performative_pmt"], "performative_pct": ["performative_mt", "performative_o"], "performative_pmt": ["performative_mt", "performative_o"], "performative_mt": [], "performative_o": [], "performative_empty_contents": ["performative_empty_contents"], }, "termination": ["performative_mt", "performative_o"], "roles": {"role_1": None, "role_2": None}, "end_states": ["end_state_1", "end_state_2", "end_state_3"], "keep_terminal_state_dialogues": True, } valid_performatives_set_1 = { "performative_ct", "performative_pt", "performative_pct", "performative_pmt", "performative_mt", "performative_o", "performative_empty_contents", } mocked_spec.dialogue_config = valid_dialogue_config_1 ( valid_result_1, valid_msg_1, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert valid_result_1 is True assert valid_msg_1 == "Dialogue section of the protocol specification is valid." ################################################### invalid_dialogue_config_1 = valid_dialogue_config_1.copy() invalid_dialogue_config_1["initiation"] = ["new_performative"] mocked_spec.dialogue_config = invalid_dialogue_config_1 ( invalid_result_1, invalid_msg_1, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert invalid_result_1 is False assert ( invalid_msg_1 == "Performative 'new_performative' specified in \"initiation\" is not defined in the protocol's speech-acts." ) invalid_dialogue_config_2 = valid_dialogue_config_1.copy() invalid_dialogue_config_2["reply"] = { "performative_ct": ["performative_pct"], "performative_pt": ["performative_pmt"], "performative_pct": ["performative_mt", "performative_o"], "performative_pmt": ["performative_mt", "performative_o"], "performative_mt": [], "performative_o": [], } mocked_spec.dialogue_config = invalid_dialogue_config_2 ( invalid_result_2, invalid_msg_2, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert invalid_result_2 is False assert ( invalid_msg_2 == "No reply is provided for the following performatives: {}".format( {"performative_empty_contents"}, ) ) invalid_dialogue_config_3 = valid_dialogue_config_1.copy() invalid_dialogue_config_3["termination"] = ["new_performative"] mocked_spec.dialogue_config = invalid_dialogue_config_3 ( invalid_result_3, invalid_msg_3, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert invalid_result_3 is False assert ( invalid_msg_3 == "Performative 'new_performative' specified in \"termination\" is not defined in the protocol's speech-acts." ) invalid_dialogue_config_4 = valid_dialogue_config_1.copy() invalid_dialogue_config_4["roles"] = { "role_1": None, "role_2": None, "role_3": None, } mocked_spec.dialogue_config = invalid_dialogue_config_4 ( invalid_result_4, invalid_msg_4, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert invalid_result_4 is False assert ( invalid_msg_4 == "There must be either 1 or 2 roles defined in this dialogue. Found 3" ) invalid_dialogue_config_5 = valid_dialogue_config_1.copy() invalid_dialogue_config_5["end_states"] = ["end_$tate_1"] mocked_spec.dialogue_config = invalid_dialogue_config_5 ( invalid_result_5, invalid_msg_5, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert invalid_result_5 is False assert ( invalid_msg_5 == "Invalid name for end_state 'end_$tate_1'. End_state names must match the following regular expression: {} ".format( END_STATE_REGEX_PATTERN ) ) invalid_dialogue_config_6 = valid_dialogue_config_1.copy() invalid_dialogue_config_6.pop("termination") mocked_spec.dialogue_config = invalid_dialogue_config_6 ( invalid_result_6, invalid_msg_6, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert invalid_result_6 is False assert ( invalid_msg_6 == "Missing required field 'termination' in the dialogue section of the protocol specification." ) invalid_value = 521 invalid_dialogue_config_7 = valid_dialogue_config_1.copy() invalid_dialogue_config_7["keep_terminal_state_dialogues"] = invalid_value mocked_spec.dialogue_config = invalid_dialogue_config_7 ( invalid_result_7, invalid_msg_7, ) = _validate_dialogue_section(mocked_spec, valid_performatives_set_1) assert invalid_result_7 is False assert ( invalid_msg_7 == f"Invalid type for keep_terminal_state_dialogues. Expected bool. Found {type(invalid_value)}." ) @mock.patch("aea.configurations.base.ProtocolSpecification") @mock.patch( "aea.protocols.generator.validate._validate_speech_acts_section", return_value=tuple([True, "Speech_acts are correct!", set(), set()]), ) @mock.patch( "aea.protocols.generator.validate._validate_protocol_buffer_schema_code_snippets", return_value=tuple([True, "Protobuf snippets are correct!"]), ) @mock.patch( "aea.protocols.generator.validate._validate_dialogue_section", return_value=tuple([True, "Dialogue section is correct!"]), ) def test_validate_positive( self, mocked_spec, macked_validate_speech_acts, macked_validate_protobuf, macked_validate_dialogue, ): """Positive test for the 'validate' method: invalid dialogue section.""" ( valid_result_1, valid_msg_1, ) = validate(mocked_spec) assert valid_result_1 is True assert valid_msg_1 == "Protocol specification is valid." @mock.patch("aea.configurations.base.ProtocolSpecification") @mock.patch( "aea.protocols.generator.validate._validate_speech_acts_section", return_value=tuple([False, "Some error on speech_acts.", None, None]), ) def test_validate_negative_invalid_speech_acts( self, mocked_spec, macked_validate_speech_acts ): """Negative test for the 'validate' method: invalid speech_acts.""" ( invalid_result_1, invalid_msg_1, ) = validate(mocked_spec) assert invalid_result_1 is False assert invalid_msg_1 == "Some error on speech_acts." @mock.patch("aea.configurations.base.ProtocolSpecification") @mock.patch( "aea.protocols.generator.validate._validate_speech_acts_section", return_value=tuple([True, "Speech_acts are correct!", set(), set()]), ) @mock.patch( "aea.protocols.generator.validate._validate_protocol_buffer_schema_code_snippets", return_value=tuple([False, "Some error on protobuf snippets."]), ) def test_validate_negative_invalid_protobuf_snippets( self, mocked_spec, macked_validate_speech_acts, macked_validate_protobuf ): """Negative test for the 'validate' method: invalid protobuf snippets.""" ( invalid_result_1, invalid_msg_1, ) = validate(mocked_spec) assert invalid_result_1 is False assert invalid_msg_1 == "Some error on protobuf snippets." @mock.patch("aea.configurations.base.ProtocolSpecification") @mock.patch( "aea.protocols.generator.validate._validate_speech_acts_section", return_value=tuple([True, "Speech_acts are correct!", set(), set()]), ) @mock.patch( "aea.protocols.generator.validate._validate_protocol_buffer_schema_code_snippets", return_value=tuple([True, "Protobuf snippets are correct!"]), ) @mock.patch( "aea.protocols.generator.validate._validate_dialogue_section", return_value=tuple([False, "Some error on dialogue section."]), ) def test_validate_negative_invalid_dialogue_section( self, mocked_spec, macked_validate_speech_acts, macked_validate_protobuf, macked_validate_dialogue, ): """Negative test for the 'validate' method: invalid dialogue section.""" ( invalid_result_1, invalid_msg_1, ) = validate(mocked_spec) assert invalid_result_1 is False assert invalid_msg_1 == "Some error on dialogue section." ================================================ FILE: tests/test_aea/test_protocols/test_scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the Scaffold protocol.""" import pytest from aea.protocols.scaffold.message import MyScaffoldMessage def test_scaffold_message(): """Testing the creation of a scaffold message.""" with pytest.raises(NotImplementedError): msg = MyScaffoldMessage(performative="") assert not msg._check_consistency(), "Not Implemented Error" ================================================ FILE: tests/test_aea/test_registries/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.registries.""" ================================================ FILE: tests/test_aea/test_registries/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for aea/registries/base.py.""" import os import random import shutil import tempfile import unittest.mock from pathlib import Path from typing import cast from unittest.mock import MagicMock import pytest import aea import aea.registries.base from aea.aea import AEA from aea.configurations.base import ComponentId, ComponentType, PublicId from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE from aea.connections.base import Connection from aea.contracts.base import Contract from aea.crypto.wallet import Wallet from aea.helpers.transaction.base import SignedTransaction from aea.identity.base import Identity from aea.protocols.base import Protocol from aea.registries.base import ( AgentComponentRegistry, ComponentRegistry, HandlerRegistry, PublicIdRegistry, ) from aea.registries.resources import Resources from aea.skills.base import Skill from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as ERC1155_PUBLIC_ID from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.protocols.state_update import StateUpdateMessage from packages.fetchai.skills.error import PUBLIC_ID as ERROR_SKILL_PUBLIC_ID from tests.conftest import CUR_PATH, ROOT_DIR, _make_dummy_connection class TestContractRegistry: """Test the contract registry.""" @classmethod def setup_class(cls): """Set the tests up.""" cls.oldcwd = os.getcwd() cls.agent_name = "agent_dir_test" cls.t = tempfile.mkdtemp() cls.agent_folder = os.path.join(cls.t, cls.agent_name) shutil.copytree(os.path.join(CUR_PATH, "data", "dummy_aea"), cls.agent_folder) os.chdir(cls.agent_folder) contract = Contract.from_dir( str(Path(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155")) ) cls.registry = AgentComponentRegistry() cls.patch = unittest.mock.patch.object(cls.registry.logger, "exception") cls.mocked_logger = cls.patch.start() cls.registry.register(contract.component_id, cast(Contract, contract)) cls.expected_contract_ids = {ERC1155_PUBLIC_ID} def test_fetch_all(self): """Test that the 'fetch_all' method works as expected.""" contracts = self.registry.fetch_by_type(ComponentType.CONTRACT) assert all(isinstance(c, Contract) for c in contracts) assert set(c.public_id for c in contracts) == self.expected_contract_ids def test_fetch(self): """Test that the `fetch` method works as expected.""" contract_id = ERC1155_PUBLIC_ID contract = self.registry.fetch(ComponentId(ComponentType.CONTRACT, contract_id)) assert isinstance(contract, Contract) assert contract.id == contract_id def test_unregister(self): """Test that the 'unregister' method works as expected.""" contract_id_removed = ERC1155_PUBLIC_ID component_id = ComponentId(ComponentType.CONTRACT, contract_id_removed) contract_removed = self.registry.fetch(component_id) self.registry.unregister(contract_removed.component_id) expected_contract_ids = set(self.expected_contract_ids) expected_contract_ids.remove(contract_id_removed) assert ( set( c.public_id for c in self.registry.fetch_by_type(ComponentType.CONTRACT) ) == expected_contract_ids ) # restore the contract self.registry.register(component_id, contract_removed) @classmethod def teardown_class(cls): """Tear down the tests.""" cls.mocked_logger.__exit__() os.chdir(cls.oldcwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestProtocolRegistry: """Test the protocol registry.""" @classmethod def setup_class(cls): """Set the tests up.""" cls.oldcwd = os.getcwd() cls.agent_name = "agent_dir_test" cls.t = tempfile.mkdtemp() cls.agent_folder = os.path.join(cls.t, cls.agent_name) shutil.copytree(os.path.join(CUR_PATH, "data", "dummy_aea"), cls.agent_folder) os.chdir(cls.agent_folder) cls.registry = AgentComponentRegistry() cls.patch = unittest.mock.patch.object(cls.registry.logger, "exception") cls.mocked_logger = cls.patch.start() protocol_1 = Protocol.from_dir( Path(ROOT_DIR, "packages", "fetchai", "protocols", "default") ) protocol_2 = Protocol.from_dir( Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa"), ) cls.registry.register(protocol_1.component_id, protocol_1) cls.registry.register(protocol_2.component_id, protocol_2) cls.expected_protocol_ids = { DefaultMessage.protocol_id, FipaMessage.protocol_id, } def test_fetch_all(self): """Test that the 'fetch_all' method works as expected.""" protocols = self.registry.fetch_by_type(ComponentType.PROTOCOL) assert all(isinstance(p, Protocol) for p in protocols) assert set(p.public_id for p in protocols) == self.expected_protocol_ids def test_unregister(self): """Test that the 'unregister' method works as expected.""" protocol_id_removed = DefaultMessage.protocol_id component_id = ComponentId(ComponentType.PROTOCOL, protocol_id_removed) protocol_removed = self.registry.fetch(component_id) self.registry.unregister(component_id) expected_protocols_ids = set(self.expected_protocol_ids) expected_protocols_ids.remove(protocol_id_removed) assert ( set( p.public_id for p in self.registry.fetch_by_type(ComponentType.PROTOCOL) ) == expected_protocols_ids ) # restore the protocol self.registry.register(component_id, protocol_removed) @classmethod def teardown_class(cls): """Tear down the tests.""" cls.mocked_logger.__exit__() os.chdir(cls.oldcwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestResources: """Test the resources class.""" @classmethod def _patch_logger(cls): cls.patch_logger_exception = unittest.mock.patch.object( aea.registries.base._default_logger, "exception" ) cls.mocked_logger_exception = cls.patch_logger_exception.__enter__() cls.patch_logger_warning = unittest.mock.patch.object( aea.registries.base._default_logger, "warning" ) cls.mocked_logger_warning = cls.patch_logger_warning.__enter__() @classmethod def _unpatch_logger(cls): cls.mocked_logger_exception.__exit__() cls.mocked_logger_warning.__exit__() @classmethod def setup_class(cls): """Set the tests up.""" # cls._patch_logger() # noqa: E800 # create temp agent folder cls.oldcwd = os.getcwd() cls.agent_name = "agent_test" + str(random.randint(0, 1000)) # nosec cls.t = tempfile.mkdtemp() cls.agent_folder = os.path.join(cls.t, cls.agent_name) shutil.copytree(os.path.join(CUR_PATH, "data", "dummy_aea"), cls.agent_folder) os.chdir(cls.agent_folder) cls.resources = Resources() cls.resources.add_component( Protocol.from_dir( Path(ROOT_DIR, "packages", "fetchai", "protocols", "default") ) ) cls.resources.add_component( Protocol.from_dir( Path(ROOT_DIR, "packages", "fetchai", "protocols", "signing") ) ) cls.resources.add_component( Protocol.from_dir( Path(ROOT_DIR, "packages", "fetchai", "protocols", "state_update") ) ) cls.resources.add_component( Skill.from_dir( Path(CUR_PATH, "data", "dummy_skill"), agent_context=MagicMock(agent_name="name"), ) ) cls.resources.add_component( Skill.from_dir( Path(ROOT_DIR, "packages", "fetchai", "skills", "error"), agent_context=MagicMock(agent_name="name"), ) ) cls.error_skill_public_id = ERROR_SKILL_PUBLIC_ID cls.dummy_skill_public_id = PublicId.from_str("dummy_author/dummy:0.1.0") cls.contract_public_id = ERC1155_PUBLIC_ID def test_unregister_handler(self): """Test that the unregister of handlers work correctly.""" assert len(self.resources.get_all_handlers()) == 4 # unregister the error handler and test that it has been actually unregistered. # TODO shouldn't we prevent the unregistration of this? error_handler = self.resources._handler_registry.fetch( (self.error_skill_public_id, "error_handler") ) assert error_handler is not None self.resources._handler_registry.unregister( (self.error_skill_public_id, "error_handler") ) assert ( self.resources._handler_registry.fetch( (self.error_skill_public_id, "error_handler") ) is None ) # unregister the dummy handler and test that it has been actually unregistered. dummy_handler = self.resources._handler_registry.fetch( (self.dummy_skill_public_id, "dummy") ) assert dummy_handler is not None self.resources._handler_registry.unregister( (self.dummy_skill_public_id, "dummy") ) assert ( self.resources._handler_registry.fetch( (self.dummy_skill_public_id, "dummy") ) is None ) # restore the handlers self.resources._handler_registry.register( (self.error_skill_public_id, "error"), error_handler ) self.resources._handler_registry.register( (self.dummy_skill_public_id, "dummy"), dummy_handler ) assert len(self.resources.get_all_handlers()) == 4 def test_add_and_remove_protocol(self): """Test that the 'add protocol' and 'remove protocol' method work correctly.""" a_protocol = Protocol.from_dir( Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search"), ) self.resources.add_component(cast(Protocol, a_protocol)) assert self.resources.get_protocol(a_protocol.public_id) == a_protocol assert ( self.resources.get_protocol_by_specification_id( a_protocol.protocol_specification_id ) == a_protocol ) # restore state self.resources.remove_protocol(a_protocol.public_id) assert self.resources.get_protocol(a_protocol.public_id) is None assert ( self.resources.get_protocol_by_specification_id( a_protocol.protocol_specification_id ) is None ) def test_get_all_protocols(self): """Test get all protocols.""" all_protocols = self.resources.get_all_protocols() assert len(all_protocols) == 3 expected_pids = { DefaultMessage.protocol_id, SigningMessage.protocol_id, StateUpdateMessage.protocol_id, } actual_pids = {p.public_id for p in all_protocols} assert expected_pids == actual_pids def test_add_remove_contract(self): """Test that the 'add contract' and 'remove contract' method work correctly.""" a_contract = Contract.from_dir( Path(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155"), ) self.resources.add_component(a_contract) assert self.resources.get_contract(a_contract.public_id) == a_contract # restore state self.resources.remove_contract(a_contract.public_id) assert self.resources.get_contract(a_contract.public_id) is None def test_get_all_contracts(self): """Test get all contracts.""" a_contract = Contract.from_dir( Path(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155"), ) self.resources.add_component(a_contract) all_contracts = self.resources.get_all_contracts() assert len(all_contracts) == 1 # restore state self.resources.remove_contract(a_contract.public_id) def test_add_remove_connection(self): """Test that the 'add connection' and 'remove connection' methods work correctly.""" a_connection = Connection.from_dir( Path(ROOT_DIR, "packages", "fetchai", "connections", "oef"), data_dir=MagicMock(), identity=Identity("name", "address", "public_key"), crypto_store=MagicMock(), ) self.resources.add_component(a_connection) assert self.resources.get_connection(a_connection.public_id) is not None # restore state self.resources.remove_connection(a_connection.public_id) def test_get_all_connections(self): """Test get all connections.""" a_connection = Connection.from_dir( Path(ROOT_DIR, "packages", "fetchai", "connections", "oef"), data_dir=MagicMock(), identity=Identity("name", "address", "public_key"), crypto_store=MagicMock(), ) self.resources.add_component(a_connection) all_connections = self.resources.get_all_connections() assert len(all_connections) == 1 assert all_connections[0] == a_connection # restore state self.resources.remove_connection(a_connection.public_id) def test_add_remove_skill(self): """Test that the 'remove skill' and 'add skill' method work correctly.""" a_skill = self.resources.get_skill(self.dummy_skill_public_id) self.resources.remove_skill(self.dummy_skill_public_id) assert self.resources.get_skill(self.dummy_skill_public_id) is None self.resources.add_skill(a_skill) assert self.resources.get_skill(self.dummy_skill_public_id) == a_skill def test_get_handler(self): """Test get handler.""" handler = self.resources.get_handler( DefaultMessage.protocol_id, self.dummy_skill_public_id ) assert handler is not None def test_get_handlers(self): """Test get handlers.""" default_handlers = self.resources.get_handlers(DefaultMessage.protocol_id) assert len(default_handlers) == 2 def test_get_behaviours(self): """Test get handlers.""" dummy_behaviours = self.resources.get_behaviours(self.dummy_skill_public_id) assert len(dummy_behaviours) == 2 def test_add_component_raises_error(self): """Test add component with unknown component type.""" a_component = MagicMock() a_component.component_type = unittest.mock.PropertyMock(return_value=None) with pytest.raises(ValueError): self.resources.add_component(a_component) def test_register_behaviour_with_already_existing_skill_id(self): """Test that registering a behaviour with an already existing skill id behaves as expected.""" # this should raise an error, since the 'dummy" skill already has a behaviour named "dummy" with pytest.raises( ValueError, match="Item already registered with skill id '{}' and name '{}'".format( self.dummy_skill_public_id, "dummy" ), ): self.resources._behaviour_registry.register( (self.dummy_skill_public_id, "dummy"), None ) def test_behaviour_registry(self): """Test that the behaviour registry behaves as expected.""" dummy_behaviour = self.resources.get_behaviour( self.dummy_skill_public_id, "dummy" ) assert len(self.resources.get_all_behaviours()) == 2 assert dummy_behaviour is not None self.resources._behaviour_registry.unregister( (self.dummy_skill_public_id, "dummy") ) assert self.resources.get_behaviour(self.dummy_skill_public_id, "dummy") is None assert len(self.resources.get_all_behaviours()) == 1 self.resources._behaviour_registry.register( (self.dummy_skill_public_id, "dummy"), dummy_behaviour ) def test_skill_loading(self): """Test that the skills have been loaded correctly.""" dummy_skill = self.resources.get_skill(self.dummy_skill_public_id) skill_context = dummy_skill.skill_context handlers = dummy_skill.handlers behaviours = dummy_skill.behaviours models = dummy_skill.models assert len(handlers) == len(skill_context.handlers.__dict__) assert len(behaviours) == len(skill_context.behaviours.__dict__) assert handlers["dummy"] == skill_context.handlers.dummy assert behaviours["dummy"] == skill_context.behaviours.dummy assert models["dummy"] == skill_context.dummy assert handlers["dummy"].context == dummy_skill.skill_context assert behaviours["dummy"].context == dummy_skill.skill_context assert models["dummy"].context == dummy_skill.skill_context def test_handler_configuration_loading(self): """Test that the handler configurations are loaded correctly.""" default_handlers = self.resources.get_handlers(DefaultMessage.protocol_id) assert len(default_handlers) == 2 handler1, handler2 = default_handlers[0], default_handlers[1] dummy_handler = ( handler1 if handler1.__class__.__name__ == "DummyHandler" else handler2 ) assert dummy_handler.config == {"handler_arg_1": 1, "handler_arg_2": "2"} def test_behaviour_configuration_loading(self): """Test that the behaviour configurations are loaded correctly.""" dummy_behaviour = self.resources.get_behaviour( self.dummy_skill_public_id, "dummy" ) assert dummy_behaviour.config == {"behaviour_arg_1": 1, "behaviour_arg_2": "2"} def test_model_configuration_loading(self): """Test that the model configurations are loaded correctly.""" dummy_skill = self.resources.get_skill(self.dummy_skill_public_id) assert dummy_skill is not None assert len(dummy_skill.models) == 1 dummy_model = dummy_skill.models["dummy"] assert dummy_model.config == { "model_arg_1": 1, "model_arg_2": "2", } @classmethod def teardown_class(cls): """Tear the tests down.""" # cls._unpatch_logger() # noqa: E800 os.chdir(cls.oldcwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestFilter: """Test the resources class.""" @classmethod def setup_class(cls): """Set the tests up.""" # create temp agent folder cls.oldcwd = os.getcwd() cls.agent_name = "agent_test" + str(random.randint(0, 1000)) # nosec cls.t = tempfile.mkdtemp() cls.agent_folder = os.path.join(cls.t, cls.agent_name) shutil.copytree(os.path.join(CUR_PATH, "data", "dummy_aea"), cls.agent_folder) os.chdir(cls.agent_folder) connection = _make_dummy_connection() private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) wallet = Wallet({DEFAULT_LEDGER: private_key_path}) identity = Identity( cls.agent_name, address=wallet.addresses[DEFAULT_LEDGER], public_key=wallet.public_keys[DEFAULT_LEDGER], ) resources = Resources() resources.add_component( Skill.from_dir( Path(CUR_PATH, "data", "dummy_skill"), agent_context=MagicMock(agent_name="name"), ) ) resources.add_connection(connection) cls.aea = AEA(identity, wallet, resources=resources, data_dir=MagicMock()) cls.aea.setup() def test_handle_internal_messages(self): """Test that the internal messages are handled.""" t = SigningMessage( performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SignedTransaction("ledger_id", {"tx": "v"}), ) t.to = str(PublicId("dummy_author", "dummy", "0.1.0")) t.sender = "decision_maker" self.aea._filter.handle_internal_message(t) internal_handlers_list = self.aea.resources.get_handlers(t.protocol_id) assert len(internal_handlers_list) == 1 internal_handler = internal_handlers_list[0] assert len(internal_handler.handled_internal_messages) == 1 self.aea.teardown() @classmethod def teardown_class(cls): """Tear the tests down.""" os.chdir(cls.oldcwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestAgentComponentRegistry: """Test agent component registry.""" def setup_class(self): """Set up the test.""" self.registry = AgentComponentRegistry() def test_ids(self): """Test all ids getter.""" assert self.registry.ids() == set() def test_register_when_component_is_already_registered(self): """Test AgentComponentRegistry.register when the component is already registered.""" component_id = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0") ) component_mock = MagicMock(component_id=component_id) self.registry._registered_keys.add(component_id) with pytest.raises( ValueError, match=r"Component already registered with item id" ): self.registry.register(component_id, component_mock) self.registry._registered_keys = set() def test_register_when_component_id_mismatch(self): """Test AgentComponentRegistry.register when the component ids mismatch.""" component_id_1 = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0") ) component_id_2 = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.2.0") ) component_mock = MagicMock(component_id=component_id_1) with pytest.raises( ValueError, match="Component id '.*' is different to the id '.*' specified." ): self.registry.register(component_id_2, component_mock) self.registry._registered_keys = set() def test_unregister_when_no_item_registered(self): """Test AgentComponentRegistry.register when the item was not registered.""" component_id = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0") ) component_mock = MagicMock(component_id=component_id) self.registry.register(component_id, component_mock) self.registry._registered_keys.remove(component_id) with pytest.raises(ValueError, match="No item registered with item id '.*'"): self.registry.unregister(component_id) self.registry._registered_keys.add(component_id) self.registry.unregister(component_id) def test_fetch_all(self): """Test fetch all.""" all_components = self.registry.fetch_all() assert len(all_components) == 0 component_id = ComponentId( ComponentType.PROTOCOL, PublicId("author", "name", "0.1.0") ) component_mock = MagicMock(component_id=component_id) self.registry.register(component_id, component_mock) all_components = self.registry.fetch_all() assert len(all_components) == 1 # restore state self.registry.unregister(component_id) class TestComponentRegistry: """Tests for the component registry.""" def setup_class(self): """Set up the tests.""" self.registry = ComponentRegistry() def test_ids(self): """Test the getter of all ids.""" assert self.registry.ids() == set() def test_ids_non_empty(self): """Test ids, non-empty case.""" dummy_skill = Skill.from_dir( Path(CUR_PATH, "data", "dummy_skill"), agent_context=MagicMock(agent_name="name"), ) behaviour = next(iter(dummy_skill.behaviours.values())) skill_component_id = (dummy_skill.public_id, behaviour.name) self.registry.register(skill_component_id, behaviour) assert self.registry.ids() == {skill_component_id} self.registry.unregister(skill_component_id) def test_unregister_when_item_not_registered(self): """Test 'unregister' in case the item is not registered.""" with pytest.raises(ValueError): self.registry.unregister( (PublicId.from_str("author/name:0.1.0"), "component_name") ) def test_unregister_by_skill_when_item_not_registered(self): """Test 'unregister_by_skill' in case the item is not registered.""" with pytest.raises( ValueError, match="No component of skill .* present in the registry." ): self.registry.unregister_by_skill(PublicId.from_str("author/skill:0.1.0")) def test_setup_with_inactive_skill(self): """Test setup with inactive skill.""" mock_item = MagicMock( name="name", skill_id="skill", context=MagicMock(is_active=False) ) with unittest.mock.patch.object( self.registry, "fetch_all", return_value=[mock_item] ): with unittest.mock.patch.object( self.registry.logger, "debug" ) as mock_debug: self.registry.setup() mock_debug.assert_called_with( f"Ignoring setup() of component {mock_item.name} of skill {mock_item.skill_id}, because the skill is not active." ) def test_fetch_with_latest_version(self): """Test fetch with public id :latest version.""" item_id_1 = PublicId("author", "package", "0.1.0") item_id_2 = PublicId("author", "package", "0.2.0") item_id_3 = PublicId("author", "package", "0.3.0") item_id_latest = PublicId("author", "package") name = "name" self.registry.register((item_id_1, name), MagicMock(id=1)) self.registry.register((item_id_3, name), MagicMock(id=3)) self.registry.register((item_id_2, name), MagicMock(id=2)) latest = self.registry.fetch((item_id_latest, name)) assert latest is not None assert latest.id == 3 # restore previous state self.registry.unregister((item_id_1, name)) self.registry.unregister((item_id_2, name)) self.registry.unregister((item_id_3, name)) class TestHandlerRegistry: """Test handler registry.""" @classmethod def setup_class(cls): """Set up the tests.""" cls.registry = HandlerRegistry() def test_fetch_skill_id_not_present(self): """Test fetch, negative case for skill id..""" # register an item protocol_id = MagicMock() protocol_id.package_version.is_latest = False skill_id = MagicMock() skill_id.package_version.is_latest = False handler_name = "handler" handler_mock = MagicMock(name=handler_name, SUPPORTED_PROTOCOL=protocol_id) self.registry.register((skill_id, handler_name), handler_mock) # fetch an item with right protocol but unknown skill id result = self.registry.fetch_by_protocol_and_skill(protocol_id, MagicMock()) assert result is None self.registry.unregister((skill_id, handler_name)) def test_register_and_unregister_dynamically(self): """Test register when protocol id is None.""" assert len(self.registry._dynamically_added) == 0 self.registry.register( (PublicId.from_str("author/name:0.1.0"), "name"), MagicMock(SUPPORTED_PROTOCOL=PublicId.from_str("author/protocol:0.1.0")), is_dynamically_added=True, ) assert len(self.registry._dynamically_added) == 1 self.registry.unregister( (PublicId.from_str("author/name:0.1.0"), "name"), ) assert len(self.registry._dynamically_added) == 0 def test_register_and_teardown_dynamically(self): """Test register when protocol id is None.""" assert len(self.registry._dynamically_added) == 0 self.registry.register( (PublicId.from_str("author/name:0.1.0"), "name"), MagicMock(SUPPORTED_PROTOCOL=PublicId.from_str("author/protocol:0.1.0")), is_dynamically_added=True, ) assert len(self.registry._dynamically_added) == 1 self.registry.teardown() assert len(self.registry._dynamically_added) == 0 def test_register_when_protocol_id_is_none(self): """Test register when protocol id is None.""" with pytest.raises( ValueError, match="Please specify a supported protocol for handler class" ): self.registry.register( (PublicId.from_str("author/name:0.1.0"), "name"), MagicMock(SUPPORTED_PROTOCOL=None), ) def test_register_when_skill_protocol_id_exist(self): """Test register when protocol id is None.""" skill_id = PublicId.from_str("author/name:0.1.0") protocol_id = PublicId.from_str("author/name:0.1.0") self.registry.register( (skill_id, "name"), MagicMock(SUPPORTED_PROTOCOL=protocol_id) ) with pytest.raises( ValueError, match="A handler already registered with pair of protocol id .* and skill id .*", ): self.registry.register( (skill_id, "name"), MagicMock(SUPPORTED_PROTOCOL=protocol_id) ) self.registry.unregister((skill_id, "name")) def test_unregister_when_no_item_is_registered(self): """Test unregister when there is no item with that item id.""" item_id = (PublicId.from_str("author/name:0.1.0"), "name") with pytest.raises( ValueError, match="No item registered with component id '.*'" ): self.registry.unregister(item_id) def test_unregister_by_skill(self): """Test unregister by skill.""" skill_id = PublicId.from_str("author/name:0.1.0") with pytest.raises( ValueError, match="No component of skill .* present in the registry." ): self.registry.unregister_by_skill(skill_id) class TestPublicIdRegistry: """Tests for the public id registry class.""" @classmethod def setup_class(cls): """Set up the class.""" cls.registry = PublicIdRegistry() def test_register_fails_when_version_latest(self): """Test that version 'latest' are not allowed for registration.""" public_id = PublicId("author", "package", "latest") with pytest.raises( ValueError, match=f"Cannot register item with public id 'latest': {public_id}", ): self.registry.register(public_id, MagicMock()) def test_register_fails_when_already_registered(self): """Test that register fails when the same public id is already registered.""" public_id = PublicId("author", "package", "0.1.0") self.registry._public_id_to_item[public_id] = MagicMock() with pytest.raises( ValueError, match=f"Item already registered with item id '{public_id}'" ): self.registry.register(public_id, MagicMock()) self.registry._public_id_to_item.pop(public_id) def test_unregister_fails_when_item_not_registered(self): """Test that unregister fails when the item is not registered..""" public_id = PublicId("author", "package", "0.1.0") with pytest.raises( ValueError, match=f"No item registered with item id '{public_id}'" ): self.registry.unregister(public_id) def test_fetch_none(self): """Test fetch, negative case.""" assert self.registry.fetch(MagicMock()) is None ================================================ FILE: tests/test_aea/test_registries/test_filter.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for aea/registries/filter.py.""" import unittest.mock from unittest.mock import MagicMock import pytest from aea.configurations.base import PublicId, SkillConfig from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.registries.filter import Filter from aea.registries.resources import Resources from aea.skills.base import Skill from packages.fetchai.protocols.signing import SigningMessage from tests.data.dummy_skill.behaviours import DummyBehaviour from tests.data.dummy_skill.handlers import DummyHandler class TestFilter: """Test class for filter.""" @classmethod def setup_class(cls): """Set the tests up.""" cls.resources = Resources() cls.decision_make_queue = AsyncFriendlyQueue() cls.filter = Filter(cls.resources, cls.decision_make_queue) @pytest.mark.asyncio async def test_get_internal_message(self): """Test get internal message.""" msg = MagicMock() self.decision_make_queue.put(msg) _msg = await self.filter.get_internal_message() assert msg == _msg, "Should get message" def test_get_active_handlers_skill_id_none(self): """Test get active handlers with skill id None.""" protocol_id = PublicId.from_str("author/name:0.1.0") active_handlers = self.filter.get_active_handlers(protocol_id, skill_id=None) assert len(active_handlers) == 0 def test_get_active_handlers_skill_id_not_none(self): """Test get active handlers with skill id not None.""" protocol_id = PublicId.from_str("author/name:0.1.0") skill_id = PublicId.from_str("author/skill:0.1.0") active_handlers = self.filter.get_active_handlers( protocol_id, skill_id=skill_id ) assert len(active_handlers) == 0 def test_get_active_behaviours(self): """Test get active behaviours.""" active_behaviours = self.filter.get_active_behaviours() assert len(active_behaviours) == 0 def test_handle_internal_message_when_none(self): """Test handle internal message when the received message is None.""" with unittest.mock.patch.object( self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(None) mock_logger_warning.assert_called_with( "Got 'None' while processing internal messages." ) def test_handle_internal_message_when_no_handler(self): """Test handle internal message when the message has no matching handler.""" msg = MagicMock() msg.to = "author/name:0.1.0" with unittest.mock.patch.object( self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(msg) mock_logger_warning.assert_called_with( "No internal handler fetched for skill_id={}".format(msg.to) ) assert self.decision_make_queue.empty() def test_handle_internal_message_when_invalid_to(self): """Test handle internal message when the message has an invalid to.""" msg = MagicMock() msg.to = "author!name" with unittest.mock.patch.object( self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(msg) mock_logger_warning.assert_called_with( "Invalid public id as destination={}".format(msg.to) ) assert self.decision_make_queue.empty() def test_handle_internal_message_new_behaviours(self): """Test handle internal message when there are new behaviours to register.""" skill = Skill( SkillConfig("name", "author", "0.1.0"), handlers={}, behaviours={}, models={}, ) self.resources.add_skill(skill) new_behaviour = DummyBehaviour(name="dummy2", skill_context=skill.skill_context) skill.skill_context.new_behaviours.put(new_behaviour) self.filter.handle_new_handlers_and_behaviours() assert self.decision_make_queue.empty() assert len(self.resources.behaviour_registry.fetch_all()) == 1 # restore previous state self.resources.remove_skill(skill.public_id) assert len(self.resources.behaviour_registry.fetch_all()) == 0 def test_handle_internal_message_new_behaviours_with_error(self): """Test handle internal message when an error happens while registering a new behaviour.""" skill = Skill( SkillConfig("name", "author", "0.1.0"), handlers={}, behaviours={}, models={}, ) self.resources.add_skill(skill) new_behaviour = DummyBehaviour(name="dummy2", skill_context=skill.skill_context) with unittest.mock.patch.object( self.resources.behaviour_registry, "register", side_effect=ValueError ): with unittest.mock.patch.object( self.filter.logger, "warning" ) as mock_logger_warning: skill.skill_context.new_behaviours.put(new_behaviour) self.filter.handle_new_handlers_and_behaviours() mock_logger_warning.assert_called_with( "Error when trying to add a new behaviour: " ) assert self.decision_make_queue.empty() assert len(self.resources.behaviour_registry.fetch_all()) == 0 # restore previous state self.resources.component_registry.unregister(skill.component_id) def test_handle_internal_message_new_handlers(self): """Test handle internal message when there are new handlers to register.""" skill = Skill( SkillConfig("name", "author", "0.1.0"), handlers={}, behaviours={}, models={}, ) self.resources.add_skill(skill) new_handler = DummyHandler(name="dummy2", skill_context=skill.skill_context) skill.skill_context.new_handlers.put(new_handler) self.filter.handle_new_handlers_and_behaviours() assert self.decision_make_queue.empty() assert len(self.resources.handler_registry.fetch_all()) == 1 # restore previous state self.resources.remove_skill(skill.public_id) assert len(self.resources.handler_registry.fetch_all()) == 0 def test_handle_internal_message_new_handlers_with_error(self): """Test handle internal message when an error happens while registering a new handler.""" skill = Skill( SkillConfig("name", "author", "0.1.0"), handlers={}, behaviours={}, models={}, ) self.resources.add_skill(skill) new_handler = DummyHandler(name="dummy2", skill_context=skill.skill_context) with unittest.mock.patch.object( self.resources.handler_registry, "register", side_effect=ValueError ): with unittest.mock.patch.object( self.filter.logger, "warning" ) as mock_logger_warning: skill.skill_context.new_handlers.put(new_handler) self.filter.handle_new_handlers_and_behaviours() mock_logger_warning.assert_called_with( "Error when trying to add a new handler: " ) assert self.decision_make_queue.empty() assert len(self.resources.handler_registry.fetch_all()) == 0 # restore previous state self.resources.component_registry.unregister(skill.component_id) def test_handle_signing_message(self): """Test the handling of a signing message.""" public_id = "author/non_existing_skill:0.1.0" message = SigningMessage( SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, ) message.to = public_id with unittest.mock.patch.object( self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(message) mock_logger_warning.assert_called_with( f"No internal handler fetched for skill_id={public_id}" ) assert self.decision_make_queue.empty() ================================================ FILE: tests/test_aea/test_runner.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea runner.""" import time from unittest.mock import call, patch import pytest from aea.aea_builder import AEABuilder from aea.configurations.base import SkillConfig from aea.configurations.constants import DEFAULT_LEDGER from aea.helpers.multiple_executor import ExecutorExceptionPolicies from aea.helpers.multiple_executor import _default_logger as executor_logger from aea.runner import AEARunner from aea.skills.base import Skill, SkillContext from tests.common.utils import make_behaviour_cls_from_funcion, wait_for_condition from tests.conftest import FETCHAI_PRIVATE_KEY_PATH class TestThreadedRunner: """Test runner in threaded mode.""" RUNNER_MODE = "threaded" def _builder(self, agent_name="agent1", act_func=None) -> AEABuilder: """Build an aea instance.""" builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, FETCHAI_PRIVATE_KEY_PATH) skill_context = SkillContext() act_func = act_func or (lambda self: self) behaviour_cls = make_behaviour_cls_from_funcion(act_func) behaviour = behaviour_cls(name="behaviour", skill_context=skill_context) test_skill = Skill( SkillConfig(name="test_skill", author="fetchai"), skill_context=skill_context, behaviours={"behaviour": behaviour}, ) skill_context._skill = test_skill # weird hack builder.add_component_instance(test_skill) builder.set_runtime_mode("async") return builder def setup(self): """Set up aea instances.""" self.aea1 = self._builder("agent1").build() self.aea2 = self._builder("agent2").build() self.failing_aea = self._builder( "failing_agent", act_func=self.raise_exception ).build() self.agents = [self.aea1, self.aea2] def raise_exception(self, *args, **kwargs): """Raise a test exception.""" raise Exception("expected!") def test_start_stop(self): """Test agents started stopped.""" runner = AEARunner([self.aea1, self.aea2], self.RUNNER_MODE) runner.start(True) wait_for_condition(lambda: runner.is_running, timeout=5) time.sleep(1) runner.stop() assert not runner.is_running def test_one_fails_propagate_policy(self) -> None: """Test agents started, one agent failed, exception is raised.""" runner = AEARunner( [self.aea1, self.aea2, self.failing_aea], self.RUNNER_MODE, fail_policy=ExecutorExceptionPolicies.propagate, ) with pytest.raises(Exception, match="expected!"): runner.start() runner.stop() def test_one_fails_log_only_policy(self) -> None: """Test agents started, one agent failed, exception is raised.""" runner = AEARunner( [self.aea1, self.aea2, self.failing_aea], self.RUNNER_MODE, fail_policy=ExecutorExceptionPolicies.log_only, ) with patch.object(executor_logger, "exception") as mock: runner.start(threaded=True) time.sleep(1) mock.assert_called_with( f"Exception raised during {self.failing_aea.name} running." ) assert runner.is_running runner.stop() def test_one_fails_stop_policy(self) -> None: """Test agents started, one agent failed, exception is raised.""" runner = AEARunner( [self.aea1, self.aea2, self.failing_aea], self.RUNNER_MODE, fail_policy=ExecutorExceptionPolicies.stop_all, ) with patch.object(executor_logger, "exception") as mock: runner.start(threaded=True) time.sleep(1) mock.assert_has_calls( [call(f"Exception raised during {self.failing_aea.name} running.")] ) assert not runner.is_running runner.stop() class TestAsyncRunner(TestThreadedRunner): """Test runner in async mode.""" RUNNER_MODE = "async" ================================================ FILE: tests/test_aea/test_runtime.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea runtime.""" import asyncio import os from pathlib import Path from typing import Type from unittest.mock import patch import pytest from aea.aea_builder import AEABuilder from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE from aea.exceptions import _StopRuntime from aea.runtime import AsyncRuntime, BaseRuntime, RuntimeStates, ThreadedRuntime from tests.common.utils import wait_for_condition from tests.conftest import CUR_PATH, MAX_FLAKY_RERUNS from tests.data.dummy_skill import PUBLIC_ID as DUMMY_SKILL_PUBLIC_ID class TestAsyncRuntime: """Test async runtime.""" RUNTIME: Type[BaseRuntime] = AsyncRuntime def setup(self): """Set up case.""" agent_name = "MyAgent" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() builder.set_name(agent_name) builder.add_private_key(DEFAULT_LEDGER, private_key_path) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) builder.set_storage_uri("sqlite://:memory:") self.agent = builder.build() self.runtime = self.RUNTIME( self.agent, threaded=True, multiplexer_options={ "connections": self.agent.runtime.multiplexer.connections }, ) self.agent._runtime = self.runtime def teardown(self): """Tear down.""" self.runtime.stop() self.runtime.wait_completed(sync=True) def test_start_stop(self): """Test runtime tart/stop.""" self.runtime.start() wait_for_condition(lambda: self.runtime.is_running, timeout=20) self.runtime.stop() self.runtime.wait_completed(sync=True) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.asyncio async def test_stop_with_stopped_exception(self): """Test runtime stopped by stopruntime exception.""" behaviour = self.agent.resources.get_behaviour(DUMMY_SKILL_PUBLIC_ID, "dummy") with patch.object( behaviour, "act", side_effect=_StopRuntime(reraise=ValueError("expected")) ): self.runtime.start() with pytest.raises(ValueError, match="expected"): self.runtime.wait_completed(timeout=20, sync=True) def test_double_start(self): """Test runtime double start do nothing.""" assert self.runtime.start() assert not self.runtime.start() wait_for_condition(lambda: self.runtime.is_running, timeout=20) def test_set_loop(self): """Test set loop method.""" loop = asyncio.new_event_loop() self.runtime.set_loop(loop) assert self.runtime.loop is loop def test_double_stop(self): """Test runtime double stop do nothing.""" self.runtime.start() wait_for_condition(lambda: self.runtime.is_running, timeout=20) self.runtime.stop() self.runtime.wait_completed(sync=True) assert self.runtime.is_stopped self.runtime.stop() self.runtime.wait_completed(sync=True) assert self.runtime.is_stopped def test_error_state(self): """Test runtime fails on start.""" async def error(*args, **kwargs): raise ValueError("oops") with patch.object(self.runtime, "_start_agent_loop", error): with pytest.raises(ValueError, match="oops"): self.runtime.start_and_wait_completed(sync=True) assert self.runtime.state == RuntimeStates.error, self.runtime.state class TestThreadedRuntime(TestAsyncRuntime): """Test threaded runtime.""" RUNTIME = ThreadedRuntime def test_error_state(self): """Test runtime fails on start.""" with patch.object( self.runtime.agent_loop, "start", side_effect=ValueError("oops") ): with pytest.raises(ValueError, match="oops"): self.runtime.start_and_wait_completed(sync=True) assert self.runtime.state == RuntimeStates.error, self.runtime.state ================================================ FILE: tests/test_aea/test_skills/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests for the skills module contains the tests for the skills.""" ================================================ FILE: tests/test_aea/test_skills/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the base classes for the skills.""" import shutil import unittest.mock from pathlib import Path from queue import Queue from textwrap import dedent from types import SimpleNamespace from unittest import TestCase, mock from unittest.mock import MagicMock, Mock, patch import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto import aea from aea.aea import AEA from aea.common import Address from aea.configurations.base import PublicId, SkillComponentConfiguration, SkillConfig from aea.configurations.data_types import ComponentType from aea.configurations.loader import load_component_configuration from aea.crypto.wallet import Wallet from aea.decision_maker.gop import DecisionMakerHandler as GOPDecisionMakerHandler from aea.decision_maker.gop import GoalPursuitReadiness, OwnershipState, Preferences from aea.exceptions import AEAHandleException, _StopRuntime from aea.identity.base import Identity from aea.multiplexer import MultiplexerStatus from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, Dialogues from aea.registries.resources import Resources from aea.skills.base import ( Behaviour, Handler, Model, Skill, SkillComponent, SkillContext, _SkillComponentLoader, _print_warning_message_for_non_declared_skill_components, ) from aea.test_tools.test_cases import BaseAEATestCase from tests.conftest import ( CUR_PATH, ETHEREUM_PRIVATE_KEY_PATH, FETCHAI_PRIVATE_KEY_PATH, ROOT_DIR, _make_dummy_connection, ) class BaseTestSkillContext: """Test the skill context.""" @classmethod def setup_class(cls, decision_maker_handler_class=None): """Test the initialisation of the AEA.""" cls.wallet = Wallet( { FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, } ) cls.connection = _make_dummy_connection() resources = Resources() resources.add_connection(cls.connection) cls.identity = Identity( "name", addresses=cls.wallet.addresses, public_keys=cls.wallet.public_keys, default_address_key=FetchAICrypto.identifier, ) cls.my_aea = AEA( cls.identity, cls.wallet, data_dir=MagicMock(), resources=resources, decision_maker_handler_class=decision_maker_handler_class, ) cls.skill_context = SkillContext( cls.my_aea.context, skill=MagicMock(contracts={}) ) def test_agent_name(self): """Test the agent's name.""" assert self.skill_context.agent_name == self.my_aea.name def test_agent_addresses(self): """Test the agent's addresses.""" assert self.skill_context.agent_addresses == self.my_aea.identity.addresses def test_agent_public_keys(self): """Test the agent's public_keys.""" assert self.skill_context.public_keys == self.my_aea.identity.public_keys def test_agent_address(self): """Test the default agent's address.""" assert self.skill_context.agent_address == self.my_aea.identity.address def test_agent_public_key(self): """Test the default agent's public_key.""" assert self.skill_context.public_key == self.my_aea.identity.public_key def test_connection_status(self): """Test the default agent's connection status.""" assert isinstance(self.skill_context.connection_status, MultiplexerStatus) def test_decision_maker_message_queue(self): """Test the decision maker's queue.""" assert isinstance(self.skill_context.decision_maker_message_queue, Queue) def test_decision_maker_handler_context(self): """Test the decision_maker_handler_context.""" assert isinstance( self.skill_context.decision_maker_handler_context, SimpleNamespace, ) def test_storage(self): """Test the agent's storage.""" assert self.skill_context.storage is None def test_message_in_queue(self): """Test the 'message_in_queue' property.""" assert isinstance(self.skill_context.message_in_queue, Queue) def test_logger_setter(self): """Test the logger setter.""" logger = self.skill_context.logger self.skill_context._logger = None self.skill_context.logger = logger assert self.skill_context.logger == logger def test_agent_context_setter(self): """Test the agent context setter.""" agent_context = self.skill_context._agent_context self.skill_context.set_agent_context(agent_context) assert self.skill_context.agent_name == agent_context.agent_name assert self.skill_context.agent_address == agent_context.address assert self.skill_context.agent_addresses == agent_context.addresses def test_is_active_property(self): """Test is_active property getter.""" assert self.skill_context.is_active is True def test_new_behaviours_queue(self): """Test 'new_behaviours_queue' property getter.""" assert isinstance(self.skill_context.new_behaviours, Queue) def test_new_handlers_queue(self): """Test 'new_behaviours_queue' property getter.""" assert isinstance(self.skill_context.new_handlers, Queue) def test_search_service_address(self): """Test 'search_service_address' property getter.""" assert ( self.skill_context.search_service_address == self.my_aea.context.search_service_address ) def test_decision_maker_address(self): """Test 'decision_maker_address' property getter.""" assert ( self.skill_context.decision_maker_address == self.my_aea.context.decision_maker_address ) def test_default_ledger_id(self): """Test 'default_ledger_id' property getter.""" assert ( self.skill_context.default_ledger_id == self.my_aea.context.default_ledger_id ) def test_currency_denominations(self): """Test 'currency_denominations' property getter.""" assert ( self.skill_context.currency_denominations == self.my_aea.context.currency_denominations ) def test_namespace(self): """Test the 'namespace' property getter.""" assert isinstance(self.skill_context.namespace, SimpleNamespace) def test_send_to_skill(self): """Test the send_to_skill method.""" with unittest.mock.patch.object( self.my_aea.context, "_send_to_skill", return_value=None ): self.skill_context.send_to_skill("envelope", "context") @classmethod def teardown_class(cls): """Test teardown.""" pass class TestSkillContextDefault(BaseTestSkillContext): """Test skill context with default dm.""" class TestSkillContextGOP(BaseTestSkillContext): """Test skill context with GOP dm.""" @classmethod def setup_class(cls, decision_maker_handler_class=GOPDecisionMakerHandler): """Setup test class.""" super().setup_class(decision_maker_handler_class) def test_agent_ownership_state(self): """Test the ownership state.""" assert isinstance( self.skill_context.decision_maker_handler_context.ownership_state, OwnershipState, ) def test_agent_preferences(self): """Test the agents_preferences.""" assert isinstance( self.skill_context.decision_maker_handler_context.preferences, Preferences ) def test_agent_is_ready_to_pursuit_goals(self): """Test if the agent is ready to pursuit his goals.""" assert isinstance( self.skill_context.decision_maker_handler_context.goal_pursuit_readiness, GoalPursuitReadiness, ) class SkillContextTestCase(TestCase): """Test case for SkillContext class.""" def test_shared_state_positive(self): """Test shared_state property positive result""" agent_context = mock.Mock() agent_context.shared_state = "shared_state" obj = SkillContext(agent_context) obj.shared_state def test_skill_id_positive(self): """Test skill_id property positive result""" obj = SkillContext("agent_context") obj._skill = mock.Mock() obj._skill.config = mock.Mock() obj._skill.config.public_id = "public_id" obj.skill_id @mock.patch("aea.skills.base._default_logger.debug") @mock.patch("aea.skills.base.SkillContext.skill_id") def test_is_active_positive(self, skill_id_mock, debug_mock): """Test is_active setter positive result""" obj = SkillContext("agent_context") obj.is_active = "value" debug_mock.assert_called_once() def test_task_manager_positive(self): """Test task_manager property positive result""" agent_context = mock.Mock() agent_context.task_manager = "task_manager" obj = SkillContext(agent_context) with self.assertRaises(ValueError): obj.task_manager obj._skill = mock.Mock() obj.task_manager @mock.patch("aea.skills.base.SimpleNamespace") def test_handlers_positive(self, *mocks): """Test handlers property positive result""" obj = SkillContext("agent_context") with self.assertRaises(ValueError): obj.handlers obj._skill = mock.Mock() obj._skill.handlers = {} obj.handlers @mock.patch("aea.skills.base.SimpleNamespace") def test_behaviours_positive(self, *mocks): """Test behaviours property positive result""" obj = SkillContext("agent_context") with self.assertRaises(ValueError): obj.behaviours obj._skill = mock.Mock() obj._skill.behaviours = {} obj.behaviours def test_logger_positive(self): """Test logger property positive result""" obj = SkillContext("agent_context") obj.logger obj._logger = mock.Mock() obj.logger class SkillComponentTestCase(TestCase): """Test case for SkillComponent class.""" def setUp(self): """Set the test up.""" class TestComponent(SkillComponent): """Test class for SkillComponent""" def parse_module(self, *args): """Parse module.""" pass def setup(self, *args): """Set up.""" pass def teardown(self, *args): """Tear down.""" pass self.TestComponent = TestComponent def test_init_no_ctx(self): """Test init method no context provided.""" with self.assertRaises(ValueError): self.TestComponent(name="some_name", skill_context=None) with self.assertRaises(ValueError): self.TestComponent(name=None, skill_context="skill_context") def test_skill_id_positive(self): """Test skill_id property positive.""" ctx = mock.Mock() ctx.skill_id = "skill_id" component = self.TestComponent( name="name", skill_context=ctx, configuration=Mock() ) component.skill_id def test_config_positive(self): """Test config property positive.""" component = self.TestComponent( configuration=Mock(args={}), skill_context="ctx", name="name" ) component.config def test_kwargs_not_empty(self): """Test the case when there are some kwargs not-empty""" kwargs = dict(foo="bar") component_name = "component_name" skill_context = SkillContext() with mock.patch.object(skill_context.logger, "warning") as mock_logger: self.TestComponent(component_name, skill_context, **kwargs) mock_logger.assert_any_call( f"The kwargs={kwargs} passed to {component_name} have not been set!" ) def test_load_skill(): """Test the loading of a skill.""" agent_context = MagicMock(agent_name="name") skill = Skill.from_dir( Path(ROOT_DIR, "tests", "data", "dummy_skill"), agent_context=agent_context ) assert isinstance(skill, Skill) def test_behaviour(): """Test behaviour initialization.""" class CustomBehaviour(Behaviour): def setup(self) -> None: pass def teardown(self) -> None: pass def act(self) -> None: pass behaviour = CustomBehaviour("behaviour", skill_context=MagicMock()) # test getters (default values) assert behaviour.tick_interval == 0.001 assert behaviour.start_at is None assert behaviour.is_done() is False def test_behaviour_parse_module_without_configs(): """Call Behaviour.parse_module without configurations.""" assert Behaviour.parse_module(MagicMock(), {}, MagicMock()) == {} def test_behaviour_parse_module_missing_class(): """Test Behaviour.parse_module when a class is missing.""" skill_context = SkillContext( skill=MagicMock(skill_id=PublicId.from_str("author/name:0.1.0")) ) dummy_behaviours_path = Path( ROOT_DIR, "tests", "data", "dummy_skill", "behaviours.py" ) with unittest.mock.patch.object( aea.skills.base._default_logger, "warning" ) as mock_logger_warning: behaviours_by_id = Behaviour.parse_module( dummy_behaviours_path, { "dummy_behaviour": SkillComponentConfiguration("DummyBehaviour"), "unknown_behaviour": SkillComponentConfiguration("UnknownBehaviour"), }, skill_context, ) mock_logger_warning.assert_called_with( "Behaviour 'UnknownBehaviour' cannot be found." ) assert "dummy_behaviour" in behaviours_by_id def test_handler_parse_module_without_configs(): """Call Handler.parse_module without configurations.""" assert Handler.parse_module(MagicMock(), {}, MagicMock()) == {} def test_handler_parse_module_missing_class(): """Test Handler.parse_module when a class is missing.""" skill_context = SkillContext( skill=MagicMock(skill_id=PublicId.from_str("author/name:0.1.0")) ) dummy_handlers_path = Path(ROOT_DIR, "tests", "data", "dummy_skill", "handlers.py") with unittest.mock.patch.object( aea.skills.base._default_logger, "warning" ) as mock_logger_warning: behaviours_by_id = Handler.parse_module( dummy_handlers_path, { "dummy_handler": SkillComponentConfiguration("DummyHandler"), "unknown_handelr": SkillComponentConfiguration("UnknownHandler"), }, skill_context, ) mock_logger_warning.assert_called_with( "Handler 'UnknownHandler' cannot be found." ) assert "dummy_handler" in behaviours_by_id def test_model_parse_module_without_configs(): """Call Model.parse_module without configurations.""" assert Model.parse_module(MagicMock(), {}, MagicMock()) == {} def test_model_parse_module_missing_class(): """Test Model.parse_module when a class is missing.""" skill_context = SkillContext( skill=MagicMock(skill_id=PublicId.from_str("author/name:0.1.0")) ) dummy_models_path = Path(ROOT_DIR, "tests", "data", "dummy_skill", "dummy.py") with unittest.mock.patch.object( aea.skills.base._default_logger, "warning" ) as mock_logger_warning: models_by_id = Model.parse_module( dummy_models_path, { "dummy_model": SkillComponentConfiguration("DummyModel"), "unknown_model": SkillComponentConfiguration("UnknownModel"), }, skill_context, ) mock_logger_warning.assert_called_with("Model 'UnknownModel' cannot be found.") assert "dummy_model" in models_by_id def test_print_warning_message_for_non_declared_skill_components(): """Test the helper function '_print_warning_message_for_non_declared_skill_components'.""" with unittest.mock.patch.object( aea.skills.base._default_logger, "warning" ) as mock_logger_warning: _print_warning_message_for_non_declared_skill_components( SkillContext(), {"unknown_class_1", "unknown_class_2"}, set(), "type", "path", ) mock_logger_warning.assert_any_call( "Class unknown_class_1 of type type found in skill module path but not declared in the configuration file." ) mock_logger_warning.assert_any_call( "Class unknown_class_2 of type type found in skill module path but not declared in the configuration file." ) class TestSkill: """Test skill attributes.""" @classmethod def setup_class(cls): """Set the tests up.""" cls.skill = Skill.from_dir( Path(ROOT_DIR, "tests", "data", "dummy_skill"), MagicMock(agent_name="agent_name"), ) def test_logger(self): """Test the logger getter.""" self.skill.logger def test_logger_setter_raises_error(self): """Test that the logger setter raises error.""" with pytest.raises(ValueError, match="Cannot set logger to a skill component."): logger = self.skill.logger self.skill.logger = logger def test_skill_context(self): """Test the skill context getter.""" context = self.skill.skill_context assert isinstance(context, SkillContext) class TestSkillProgrammatic: """Test skill attributes.""" @classmethod def setup_class(cls): """Set the tests up.""" skill_context = SkillContext() skill_config = SkillConfig( name="simple_skill", author="fetchai", version="0.1.0" ) class MyHandler(Handler): def setup(self): pass def handle(self, message: Message): pass def teardown(self): pass class MyBehaviour(Behaviour): def setup(self): pass def act(self): pass def teardown(self): pass cls.handler_name = "some_handler" cls.handler = MyHandler(skill_context=skill_context, name=cls.handler_name) cls.model_name = "some_model" cls.model = Model(skill_context=skill_context, name=cls.model_name) cls.behaviour_name = "some_behaviour" cls.behaviour = MyBehaviour( skill_context=skill_context, name=cls.behaviour_name ) cls.skill = Skill( skill_config, skill_context, handlers={cls.handler.name: cls.handler}, models={cls.model.name: cls.model}, behaviours={cls.behaviour.name: cls.behaviour}, ) def test_behaviours(self): """Test the behaviours getter on skill context.""" assert ( getattr(self.skill.skill_context.behaviours, self.behaviour_name, None) == self.behaviour ) def test_handlers(self): """Test the handlers getter on skill context.""" assert ( getattr(self.skill.skill_context.handlers, self.handler_name, None) == self.handler ) def test_models(self): """Test the handlers getter on skill context.""" assert getattr(self.skill.skill_context, self.model_name, None) == self.model class TestHandlerHandleExceptions: """Test exceptions in the handle wrapper.""" @classmethod def setup_class(cls): """Setup test class.""" class StandardExceptionHandler(Handler): def setup(self): pass def handle(self, message: Message): raise ValueError("expected") def teardown(self): pass cls.handler = StandardExceptionHandler(skill_context=mock.Mock(), name="name") def test_handler_standard_exception(self): """Test the handler exception.""" with pytest.raises(AEAHandleException): with pytest.raises(ValueError): self.handler.handle_wrapper("msg") def test_handler_stop_exception(self): """Test the handler exception.""" with pytest.raises(_StopRuntime): with mock.patch.object(self.handler, "handle", side_effect=_StopRuntime()): self.handler.handle_wrapper("msg") class DefaultDialogues(Model, Dialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs) -> None: """ Initialize dialogues. :return: None """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return 1 # type: ignore Dialogues.__init__( self, self_address=self.context.agent_name, end_states=[Mock()], # type: ignore message_class=Message, dialogue_class=Dialogue, role_from_first_message=role_from_first_message, ) def test_model_dialogues_keep_terminal_dialogues_option(): """Test Model Dialogues class.""" dialogues = DefaultDialogues(name="test", skill_context=Mock()) assert ( DefaultDialogues._keep_terminal_state_dialogues == dialogues.is_keep_dialogues_in_terminal_state ) dialogues = DefaultDialogues( name="test", skill_context=Mock(), keep_terminal_state_dialogues=True ) assert dialogues.is_keep_dialogues_in_terminal_state is True assert ( DefaultDialogues._keep_terminal_state_dialogues == Dialogues._keep_terminal_state_dialogues ) dialogues = DefaultDialogues( name="test", skill_context=Mock(), keep_terminal_state_dialogues=False ) assert dialogues.is_keep_dialogues_in_terminal_state is False assert ( DefaultDialogues._keep_terminal_state_dialogues == Dialogues._keep_terminal_state_dialogues ) def test_setup_teardown_methods(): """Test skill etup/teardown methods with proper super() calls.""" def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: return None # type: ignore class Test(Model, Dialogues): def __init__(self, name, skill_context): Model.__init__(self, name, skill_context) Dialogues.__init__( self, "addr", MagicMock(), Message, Dialogue, role_from_first_message ) def setup(self) -> None: super().setup() def teardown(self) -> None: super().teardown() skill_context = MagicMock() skill_context.skill_id = PublicId("test", "test", "1.0.1") t = Test(name="test", skill_context=skill_context) with patch.object(t._dialogues_storage, "setup") as mock_setup, patch.object( t._dialogues_storage, "teardown" ) as mock_teardown: t.setup() t.teardown() mock_setup.assert_called_once() mock_teardown.assert_called_once() class TestSkillLoadingWarningMessages(BaseAEATestCase): """ Test warning message in case undeclared skill are found. That is: - copy dummy_aea in a temporary directory - add a skill module with two skill components - one that has 'is_programmatically_defined' set to False - one that has 'is_programmatically_defined' set to True - test that we have a warning message only from the first. """ agent_name = "dummy_aea" cli_log_options = ["-v", "DEBUG"] _TEST_HANDLER_CLASS_NAME = "TestHandler" _TEST_BEHAVIOUR_CLASS_NAME = "TestBehaviour" _test_skill_module_path = "skill_module_for_testing.py" _test_skill_module_content = dedent( f""" from aea.skills.base import Behaviour, Handler class {_TEST_HANDLER_CLASS_NAME}(Handler): is_programmatically_defined = False def setup(self): pass def handle(self, message): pass def teardown(self): pass class {_TEST_BEHAVIOUR_CLASS_NAME}(Behaviour): is_programmatically_defined = True def setup(self): pass def act(self): pass def teardown(self): pass """ ) @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() path_to_aea = Path(CUR_PATH, "data", "dummy_aea") shutil.copytree(path_to_aea, cls.t / cls.agent_name) # add a module in 'dummy' skill with a Handler and a Behaviour dummy_skill_path = cls.t / cls.agent_name / "skills" / "dummy" (dummy_skill_path / cls._test_skill_module_path).write_text( cls._test_skill_module_content ) skill_config = load_component_configuration( ComponentType.SKILL, dummy_skill_path, skip_consistency_check=True ) skill_config._directory = dummy_skill_path cls.skill_context_mock = MagicMock() cls.skill_component_loader = _SkillComponentLoader( skill_config, cls.skill_context_mock ) # load the skill - it will trigger the warning messages. cls.skill_component_loader.load_skill() def test_warning_message_when_component_not_declared_and_flag_is_false(self): """ Test warning message. Test that the warning message is printed when component not declared and when the flag 'is_programmatically_defined' is false. """ expected_message = f"Class {self._TEST_HANDLER_CLASS_NAME} of type handler found in skill module {self._test_skill_module_path} but not declared in the configuration file." self.skill_context_mock.logger.warning.assert_any_call(expected_message) def test_no_warning_message_when_component_not_declared_but_flag_is_true(self): """ Test warning message. Test that the warning message is NOT printed when component not declared AND the flag 'is_programmatically_defined' is true. """ not_expected_message = f"Class {self._TEST_BEHAVIOUR_CLASS_NAME} of type behaviour found in skill module {self._test_skill_module_path} but not declared in the configuration file." # note: we do want the mock assert to fail with pytest.raises(AssertionError): self.skill_context_mock.logger.warning.assert_any_call(not_expected_message) ================================================ FILE: tests/test_aea/test_skills/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the behaviours.""" from collections import Counter from unittest import TestCase, mock import pytest from aea.exceptions import AEAActException, _StopRuntime from aea.skills.behaviours import ( CyclicBehaviour, FSMBehaviour, OneShotBehaviour, SequenceBehaviour, State, TickerBehaviour, ) def test_sequence_behaviour(): """Test the sequence behaviour.""" outputs = [] class MySequenceBehaviour(SequenceBehaviour): """Custom sequence behaviour.""" def setup(self) -> None: """Setup behaviour.""" pass def teardown(self) -> None: """Teardown behaviour.""" pass class SimpleOneShotBehaviour(OneShotBehaviour): """Custom simple behaviour.""" def __init__(self, name, **kwargs): super().__init__(name=name, **kwargs) def setup(self) -> None: """Setup behaviour.""" pass def teardown(self) -> None: """Teardown behaviour.""" pass def act(self) -> None: """Act implementation.""" outputs.append(self.name) # TODO let the initialization of a behaviour action from constructor a = SimpleOneShotBehaviour("a", skill_context=object()) b = SimpleOneShotBehaviour("b", skill_context=object()) c = SimpleOneShotBehaviour("c", skill_context=object()) sequence = MySequenceBehaviour([a, b, c], name="abc", skill_context=object()) max_iterations = 10 i = 0 while not sequence.is_done() and i < max_iterations: sequence.act() i += 1 assert outputs == ["a", "b", "c"] def test_act_parameter(): """Test the 'act' parameter.""" counter = Counter(i=0) def increment_counter(counter=counter): counter += Counter(i=1) assert counter["i"] == 0 one_shot_behaviour = OneShotBehaviour( act=lambda: increment_counter(), skill_context=object(), name="my_behaviour" ) one_shot_behaviour.act() assert counter["i"] == 1 class SimpleFSMBehaviour(FSMBehaviour): """A Finite-State Machine behaviour for testing purposes.""" def setup(self) -> None: """Setup behaviour.""" pass def teardown(self) -> None: """Teardown behaviour.""" pass class SimpleStateBehaviour(State): """A simple state behaviour to be added in a FSMBehaviour.""" def __init__(self, shared_list, event_to_trigger=None, **kwargs): """Initialise simple behaviour.""" super().__init__(**kwargs) self.shared_list = shared_list self.event_to_trigger = event_to_trigger self.executed = False def setup(self) -> None: """Setup behaviour.""" pass def teardown(self) -> None: """Teardown behaviour.""" pass def act(self) -> None: """Act implementation.""" self.shared_list.append(self.name) self.executed = True self._event = self.event_to_trigger def is_done(self) -> bool: """Get is done.""" return self.executed def test_fms_behaviour(): """Test the finite-state machine behaviour.""" outputs = [] a = SimpleStateBehaviour( outputs, name="a", event_to_trigger="move_to_b", skill_context=object() ) b = SimpleStateBehaviour( outputs, name="b", event_to_trigger="move_to_c", skill_context=object() ) c = SimpleStateBehaviour(outputs, name="c", skill_context=object()) fsm = SimpleFSMBehaviour(name="abc", skill_context=object()) fsm.register_state(str(a.name), a, initial=True) fsm.register_state(str(b.name), b) fsm.register_final_state(str(c.name), c) fsm.register_transition("a", "b", "move_to_b") fsm.register_transition("b", "c", "move_to_c") max_iterations = 10 i = 0 while not fsm.is_done() and i < max_iterations: fsm.act() i += 1 assert outputs == ["a", "b", "c"] class TestFSMBehaviourCreation: """Test FSMBehaviour creation.""" @classmethod def setup_class(cls): """Set the test up.""" cls.fsm_behaviour = SimpleFSMBehaviour(name="fsm", skill_context=object()) cls.outputs = [] cls.a = SimpleStateBehaviour(cls.outputs, name="a", skill_context=object()) cls.b = SimpleStateBehaviour(cls.outputs, name="b", skill_context=object()) cls.c = SimpleStateBehaviour(cls.outputs, name="c", skill_context=object()) def test_initial_state_is_none(self): """Test that the initial state is None.""" assert self.fsm_behaviour.initial_state is None def test_states_is_empty(self): """Test that the states is an empty set.""" assert self.fsm_behaviour.states == set() def test_final_states_is_empty(self): """Test that the final states is an empty set.""" assert self.fsm_behaviour.final_states == set() def test_add_and_remove_state(self): """Test that adding and removing a state works correctly.""" assert self.fsm_behaviour.states == set() self.fsm_behaviour.register_state("a", self.a) assert self.fsm_behaviour.states == {"a"} self.fsm_behaviour.unregister_state("a") assert self.fsm_behaviour.states == set() def test_add_and_remove_initial_state(self): """Test that adding and removing an initial state works correctly.""" assert self.fsm_behaviour.states == set() self.fsm_behaviour.register_state("a", self.a, initial=True) assert self.fsm_behaviour.states == {"a"} assert self.fsm_behaviour.initial_state == "a" assert self.fsm_behaviour.final_states == set() self.fsm_behaviour.unregister_state("a") assert self.fsm_behaviour.states == set() assert self.fsm_behaviour.initial_state is None assert self.fsm_behaviour.final_states == set() def test_add_and_remove_final_state(self): """Test that adding and removing final states works correctly.""" assert self.fsm_behaviour.states == set() self.fsm_behaviour.register_final_state("a", self.a) assert self.fsm_behaviour.states == {"a"} assert self.fsm_behaviour.initial_state is None assert self.fsm_behaviour.final_states == {"a"} self.fsm_behaviour.unregister_state("a") assert self.fsm_behaviour.states == set() assert self.fsm_behaviour.initial_state is None assert self.fsm_behaviour.final_states == set() def test_register_initial_state_twice(self): """Test that the register state with initial=True works correctly when called twice.""" assert self.fsm_behaviour.initial_state is None self.fsm_behaviour.register_state("a", self.a, initial=True) assert self.fsm_behaviour.initial_state == "a" self.fsm_behaviour.register_state("b", self.b, initial=True) assert self.fsm_behaviour.initial_state == "b" self.fsm_behaviour.unregister_state("a") self.fsm_behaviour.unregister_state("b") assert self.fsm_behaviour.initial_state is None def test_register_twice_same_state(self): """Test that registering twice a state with the same name raises an error.""" self.fsm_behaviour.register_state("a", self.a) with pytest.raises(ValueError, match="State name already existing."): self.fsm_behaviour.register_state("a", self.a) self.fsm_behaviour.unregister_state("a") def test_register_transition(self): """Test register transition.""" self.fsm_behaviour.register_transition("state_1", "state_2") self.fsm_behaviour.register_transition("state_1", "state_2", "an_event") assert self.fsm_behaviour.transitions == { "state_1": {None: "state_2", "an_event": "state_2"} } self.fsm_behaviour.unregister_transition("state_1", "state_2", None) self.fsm_behaviour.unregister_transition("state_1", "state_2", "an_event") assert self.fsm_behaviour.transitions == {} def test_register_same_transition_twice(self): """Test that when we try to register twice the same transition we raise an error.""" self.fsm_behaviour.register_transition("state_1", "state_2") with pytest.raises(ValueError, match="Transition already registered."): self.fsm_behaviour.register_transition("state_1", "state_2") self.fsm_behaviour.unregister_transition("state_1", "state_2") self.fsm_behaviour.register_transition("state_1", "state_2", "an_event") with pytest.raises(ValueError, match="Transition already registered."): self.fsm_behaviour.register_transition("state_1", "state_2", "an_event") self.fsm_behaviour.unregister_transition("state_1", "state_2", "an_event") class CyclicBehaviourTestCase(TestCase): """Test case for CyclicBehaviour class.""" def setUp(self): """Set the test up.""" class TestCyclicBehaviour(CyclicBehaviour): """Class for testing CyclicBehaviour abstract class.""" def setup(self, *args): """Set up.""" pass def teardown(self, *args): """Tear down.""" pass self.TestCyclicBehaviour = TestCyclicBehaviour def test_init_positive(self): """Test for init positive result.""" self.TestCyclicBehaviour(skill_context="skill_context", name="name") def test_act_wrapper_positive(self): """Test for act_wrapper positive result.""" obj = self.TestCyclicBehaviour(skill_context="skill_context", name="name") obj.act = mock.Mock() assert obj.number_of_executions == 0 obj.act_wrapper() obj.act.assert_called_once() assert obj.number_of_executions == 1 def test_act_wrapper_negative_standard_exception(self): """Test for act_wrapper negative result.""" def exception_act(): """Act method with exception.""" raise ValueError("expected") with pytest.raises(AEAActException): with pytest.raises(ValueError): obj = self.TestCyclicBehaviour(skill_context=mock.Mock(), name="name") obj.act = exception_act assert obj.number_of_executions == 0 obj.act_wrapper() obj.act.assert_called_once() assert obj.number_of_executions == 1 def test_act_wrapper_negative_stop_runtime(self): """Test for act_wrapper negative result.""" obj = self.TestCyclicBehaviour(skill_context="skill_context", name="name") obj.act = mock.Mock() with pytest.raises(_StopRuntime): with mock.patch.object(obj, "act", side_effect=_StopRuntime()): assert obj.number_of_executions == 0 obj.act_wrapper() obj.act.assert_called_once() assert obj.number_of_executions == 1 class TickerBehaviourTestCase(TestCase): """Test case for TickerBehaviour class.""" def setUp(self): """Set the test up.""" class TestTickerBehaviour(TickerBehaviour): """Class for testing TickerBehaviour abstract class.""" def setup(self, *args): """Set up.""" pass def teardown(self, *args): """Tear down.""" pass self.TestTickerBehaviour = TestTickerBehaviour def test_init_positive(self): """Test for init positive result.""" self.TestTickerBehaviour(skill_context="skill_context", name="name") def test_tick_interval_positive(self): """Test for tick_interval property positive result.""" obj = self.TestTickerBehaviour(skill_context="skill_context", name="name") obj.tick_interval def test_start_at_positive(self): """Test for start_at property positive result.""" obj = self.TestTickerBehaviour(skill_context="skill_context", name="name") obj.start_at def test_last_act_time_positive(self): """Test for last_act_time property positive result.""" obj = self.TestTickerBehaviour(skill_context="skill_context", name="name") obj.last_act_time def test_act_wrapper_positive(self): """Test for act_wrapper positive result.""" obj = self.TestTickerBehaviour(skill_context="skill_context", name="name") obj.is_done = mock.Mock(return_value=False) obj.is_time_to_act = mock.Mock(return_value=True) obj.act = mock.Mock() obj.act_wrapper() obj.act.assert_called_once() def test_is_time_to_act_positive(self): """Test for is_time_to_act positive result.""" obj = self.TestTickerBehaviour(skill_context="skill_context", name="name") obj.is_time_to_act() class FSMBehaviourTestCase(TestCase): """Test case for FSMBehaviour class.""" def setUp(self): """Set the test up.""" class TestFSMBehaviour(FSMBehaviour): """Class for testing FSMBehaviour abstract class.""" def setup(self, *args): """Set up.""" pass def teardown(self, *args): """Tear down.""" pass self.TestFSMBehaviour = TestFSMBehaviour def test_is_started_positive(self): """Test for is_started property positive result.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj.is_started def test_register_final_state_already_exists(self): """Test for register_final_state already exists.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj._name_to_state = ["name"] with self.assertRaises(ValueError): obj.register_final_state("name", "state") def test_unregister_state_not_exists(self): """Test for unregister_state not exists.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj._name_to_state = [] with self.assertRaises(ValueError): obj.unregister_state("name") def test_initial_state_not_state(self): """Test for initial_state not a state.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj._name_to_state = [] with self.assertRaises(ValueError): obj.initial_state = "name" def test_initial_state_positive(self): """Test for initial_state positive result.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj._name_to_state = ["name"] obj.initial_state = "name" def test_act_no_current(self): """Test for act method no current state.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj.current = None obj.act() def test_act_no_current_got(self): """Test for act method no current state got.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj.get_state = mock.Mock(return_value=None) obj.current = "current" obj.act() obj.get_state.assert_called_once() def test_act_current_in_final_states(self): """Test for act method current in final_states.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") current_state = mock.Mock() current_state.act_wrapper = mock.Mock() current_state.is_done = mock.Mock(return_value=True) obj.final_states.add(current_state) obj.get_state = mock.Mock(return_value=current_state) obj.current = "current" obj.act() obj.get_state.assert_called_once() current_state.act_wrapper.assert_called_once() current_state.is_done.assert_called_once() def test_unregister_transition_value_error(self): """Test for unregister_transition method ValueError raises.""" obj = self.TestFSMBehaviour(skill_context="skill_context", name="name") obj.transitions = {} with self.assertRaises(ValueError): obj.unregister_transition("source", "destination") ================================================ FILE: tests/test_aea/test_skills/test_error.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The test error skill module contains the tests of the error skill.""" import logging import os import unittest.mock from threading import Thread from unittest.mock import MagicMock from aea.aea import AEA from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE from aea.crypto.wallet import Wallet from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import InBox, Multiplexer from aea.registries.resources import Resources from aea.skills.base import SkillContext from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.skills.error.handlers import ErrorHandler from tests.common.utils import wait_for_condition from tests.conftest import CUR_PATH, _make_dummy_connection logger = logging.getLogger(__file__) class InboxWithHistory(InBox): """Inbox with history of all messages every fetched.""" def __init__(self, multiplexer: Multiplexer): """Inin inbox.""" super().__init__(multiplexer) self._history = [] # type: ignore def get(self, *args, **kwargs) -> Envelope: """Get envelope.""" item = super().get(*args, **kwargs) self._history.append(item) return item async def async_get(self, *args, **kwargs) -> Envelope: """Get envelope.""" item = await super().async_get() self._history.append(item) return item class TestSkillError: """Test the skill: Error.""" def setup(self): """Test the initialisation of the AEA.""" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) self.wallet = Wallet({DEFAULT_LEDGER: private_key_path}) self.agent_name = "Agent0" self.data_dir = MagicMock() self.connection = _make_dummy_connection() self.identity = Identity( self.agent_name, address=self.wallet.addresses[DEFAULT_LEDGER], public_key=self.wallet.public_keys[DEFAULT_LEDGER], ) self.address = self.identity.address resources = Resources() resources.add_connection(self.connection) self.my_aea = AEA( self.identity, self.wallet, resources=resources, data_dir=self.data_dir, period=0.1, default_connection=self.connection.public_id, ) self.my_aea._inbox = InboxWithHistory(self.my_aea.runtime.multiplexer) self.skill_context = SkillContext(self.my_aea._context) logger_name = "aea.{}.skills.{}.{}".format( self.my_aea._context.agent_name, "fetchai", "error" ) self.skill_context._logger = logging.getLogger(logger_name) self.my_error_handler = ErrorHandler( name="error", skill_context=self.skill_context ) self.t = Thread(target=self.my_aea.start) self.t.start() wait_for_condition( lambda: self.my_aea.runtime and self.my_aea.runtime.is_running, 10 ) def test_error_handler_handle(self): """Test the handle function.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT, ) msg.to = "a_counterparty" self.my_error_handler.handle(message=msg) def test_error_skill_unsupported_protocol(self): """Test the unsupported error message.""" self.my_aea._inbox._history = [] msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT, ) msg.to = self.address envelope = Envelope( to=msg.to, sender=self.address, message=msg, ) self.my_error_handler.send_unsupported_protocol(envelope) wait_for_condition(lambda: len(self.my_aea._inbox._history) >= 1, timeout=5) envelope = self.my_aea._inbox._history[-1] msg = envelope.message assert msg.performative == DefaultMessage.Performative.ERROR assert msg.error_code == DefaultMessage.ErrorCode.UNSUPPORTED_PROTOCOL def test_error_decoding_error(self): """Test the decoding error.""" self.my_aea._inbox._history = [] msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT, ) msg.to = self.address envelope = Envelope( to=msg.to, sender=self.address, message=msg, ) self.my_error_handler.send_decoding_error(envelope) wait_for_condition(lambda: len(self.my_aea._inbox._history) >= 1, timeout=5) envelope = self.my_aea._inbox._history[-1] msg = envelope.message assert msg.performative == DefaultMessage.Performative.ERROR assert msg.error_code == DefaultMessage.ErrorCode.DECODING_ERROR def test_error_unsupported_skill(self): """Test the unsupported skill.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT, ) msg.to = self.address msg.sender = self.address envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) self.my_error_handler.send_unsupported_skill(envelope=envelope) wait_for_condition(lambda: len(self.my_aea._inbox._history) >= 1, timeout=5) envelope = self.my_aea._inbox._history[-1] msg = envelope.message assert msg.performative == DefaultMessage.Performative.ERROR assert msg.error_code == DefaultMessage.ErrorCode.UNSUPPORTED_SKILL def test_error_unsupported_skill_when_skill_id_is_none(self): """Test the 'send_unsupported_skill' when the skill id in the envelope is None.""" protocol_id = PublicId.from_str("author/name:0.1.0") envelope = Envelope( to="", sender="", protocol_specification_id=protocol_id, message=b"", ) with unittest.mock.patch.object(self.skill_context.outbox, "put_message"): with unittest.mock.patch.object( self.skill_context._logger, "warning" ) as mock_logger_warning: self.my_error_handler.send_unsupported_skill(envelope) mock_logger_warning.assert_called_with( f"Cannot handle envelope: no active handler registered for the protocol_specification_id='{protocol_id}'." ) def teardown(self): """Teardown method.""" self.my_aea.stop() self.t.join() ================================================ FILE: tests/test_aea/test_skills/test_scaffold.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for the scaffold skill.""" from unittest.mock import MagicMock import pytest from aea.skills.base import SkillContext from aea.skills.scaffold.behaviours import MyScaffoldBehaviour from aea.skills.scaffold.handlers import MyScaffoldHandler from aea.skills.scaffold.my_model import MyModel class TestScaffoldHandler: """Tests for the scaffold handler.""" @classmethod def setup_class(cls): """Set up the tests.""" cls.handler = MyScaffoldHandler("handler", SkillContext()) def test_supported_protocol(self): """Test that the supported protocol is None.""" assert self.handler.SUPPORTED_PROTOCOL is None def test_setup(self): """Test that the setup method raises 'NotImplementedError'.""" with pytest.raises(NotImplementedError): self.handler.setup() def test_handle(self): """Test that the handle method raises 'NotImplementedError'.""" with pytest.raises(NotImplementedError): self.handler.handle(MagicMock()) def test_teardown(self): """Test that the teardown method raises 'NotImplementedError'.""" with pytest.raises(NotImplementedError): self.handler.teardown() class TestScaffoldBehaviour: """Tests for the scaffold behaviour.""" @classmethod def setup_class(cls): """Set up the tests.""" cls.behaviour = MyScaffoldBehaviour("behaviour", SkillContext()) def test_setup(self): """Test that the setup method raises 'NotImplementedError'.""" with pytest.raises(NotImplementedError): self.behaviour.setup() def test_handle(self): """Test that the handle method raises 'NotImplementedError'.""" with pytest.raises(NotImplementedError): self.behaviour.act() def test_teardown(self): """Test that the teardown method raises 'NotImplementedError'.""" with pytest.raises(NotImplementedError): self.behaviour.teardown() def test_model_initialization(): """Test scaffold model initialization.""" MyModel("model", SkillContext()) ================================================ FILE: tests/test_aea/test_skills/test_task_subprocess.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for subprocess task manager/tasl_test_skill.""" from aea.test_tools.test_cases import AEATestCaseEmpty class TestTaskTestSkill(AEATestCaseEmpty): """Test task manager subprocess run a simple task.""" capture_log = True @classmethod def setup_class(cls) -> None: """Init the test case.""" super(TestTaskTestSkill, cls).setup_class() cls.add_item("skill", "fetchai/task_test_skill:0.1.2", local=True) cls.generate_private_key() cls.add_private_key() cls.set_config("agent.task_manager_mode", "multiprocess", "str") def test_task_run_in_a_subprocess(self): """Test task run in a subporocess.""" process = self.run_agent() try: is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" assert not self.missing_from_output( process, ["Task id is"], 10, is_terminating=False ) assert not self.missing_from_output( process, ["result is"], 10, is_terminating=False ) finally: process.terminate() process.wait(10) print(self.stdout[process.pid]) print(self.stderr[process.pid]) ================================================ FILE: tests/test_aea/test_skills/test_tasks.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the tasks module.""" from unittest import TestCase, mock from unittest.mock import Mock, patch from aea.skills.tasks import Task, TaskManager def _raise_exception(self, *args, **kwargs): raise Exception() class TaskTestCase(TestCase): """Test case for Task class.""" def test_call_positive(self): """Test call obj positive result.""" obj = Task() obj.setup = mock.Mock() obj.execute = mock.Mock() obj.teardown = mock.Mock() obj() def test_call_already_executed(self): """Test call obj already executed.""" obj = Task() obj._is_executed = True with self.assertRaises(ValueError): obj() def test_call_exception_while_executing(self): """Test call obj exception raised while executing.""" obj = Task() with mock.patch.object(obj.logger, "debug") as debug_mock: obj.setup = mock.Mock() obj.execute = _raise_exception obj.teardown = mock.Mock() obj() debug_mock.assert_called_once() def test_is_executed_positive(self): """Test is_executed property positive result.""" obj = Task() obj.is_executed def test_result_positive(self): """Test result property positive result.""" obj = Task() obj._is_executed = True obj.result def test_result_not_executed(self): """Test result property task not executed.""" obj = Task() with self.assertRaises(ValueError): obj.result class InitWorkerTestCase(TestCase): """Test case for init_worker method.""" @mock.patch("aea.skills.tasks._init_worker") def test_init_worker_positive(self, init_worker_mock): """Test init_worker method positive result.""" task_manager = TaskManager(is_lazy_pool_start=False) task_manager.start() try: init_worker_mock.assert_called() finally: task_manager.stop() class TaskManagerTestCase(TestCase): """Test case for TaskManager class.""" def test_nb_workers_positive(self): """Test nb_workers property positive result.""" obj = TaskManager() obj.nb_workers def test_stop_already_stopped(self): """Test stop method already stopped.""" obj = TaskManager() with mock.patch.object(obj.logger, "debug") as debug_mock: obj.stop() debug_mock.assert_called_once() def test_start_already_started(self): """Test start method already started.""" obj = TaskManager() with mock.patch.object(obj.logger, "debug") as debug_mock: obj._stopped = False obj.start() debug_mock.assert_called_once() obj.stop() def test_start_lazy_pool_start(self): """Test start method with lazy pool start.""" obj = TaskManager(is_lazy_pool_start=False) with mock.patch.object(obj.logger, "debug") as debug_mock: obj.start() obj._stopped = True obj.start() debug_mock.assert_called_with("Pool was already started!") obj.start() def test_enqueue_task_stopped(self): """Test enqueue_task method manager stopped.""" obj = TaskManager() func = mock.Mock() with self.assertRaises(ValueError): obj.enqueue_task(func) func.assert_not_called() def test_enqueue_task_positive(self): """Test enqueue_task method positive result.""" obj = TaskManager() func = mock.Mock() obj._stopped = False obj._pool = mock.Mock() obj._pool.apply_async = mock.Mock(return_value="async_result") obj.enqueue_task(func) obj._pool.apply_async.assert_called_once() def test_get_task_result_id_not_present(self): """Test get_task_result method id not present.""" obj = TaskManager() with self.assertRaises(ValueError): obj.get_task_result("task_id") def test_get_task_result_positive(self): """Test get_task_result method positive result.""" obj = TaskManager() obj._results_by_task_id = {"task_id": "result"} obj.get_task_result("task_id") class TestTaskPoolManagementManager(TestCase): """Tests for pool management by task manager. Lazy and non lazy.""" def tearDown(self): """Stop task manager. assumed it's created on each test.""" self.task_manager.stop() def test_start_stop_reflected_by_is_started(self) -> None: """Test is_started property of task manaher.""" self.task_manager = TaskManager() assert not self.task_manager.is_started self.task_manager.start() assert self.task_manager.is_started self.task_manager.stop() assert not self.task_manager.is_started def test_lazy_pool_not_started(self) -> None: """Lazy pool creation assumes pool create on first task enqueue.""" self.task_manager = TaskManager(is_lazy_pool_start=True) self.task_manager.start() assert not self.task_manager._pool def test_not_lazy_pool_is_started(self) -> None: """Lazy pool creation assumes pool create on first task enqueue.""" self.task_manager = TaskManager(is_lazy_pool_start=False) self.task_manager.start() assert self.task_manager._pool @patch("aea.skills.tasks.Pool.apply_async") def test_lazy_pool_start_on_enqueue(self, apply_async_mock: Mock) -> None: """ Test lazy pool created on enqueue once. :param apply_async_mock: is mock for aea.skills.tasks.Pool.apply_async """ self.task_manager = TaskManager(is_lazy_pool_start=True) self.task_manager.start() assert not self.task_manager._pool self.task_manager.enqueue_task(print) apply_async_mock.assert_called_once() assert self.task_manager._pool """Check pool created once on several enqueues""" pool = self.task_manager._pool self.task_manager.enqueue_task(print) assert self.task_manager._pool is pool ================================================ FILE: tests/test_aea/test_task.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the task manager.""" from multiprocessing.pool import AsyncResult import pytest from aea.skills.tasks import Task, TaskManager class MyTask(Task): """Test class for a task.""" def __init__(self, return_value): """Initialise test task.""" super().__init__() self.setup_called = False self.teardown_called = False self.execute_called = False self.execute_args = None self.execute_kwargs = None self.return_value = return_value def setup(self) -> None: """Setup task.""" self.setup_called = True def execute(self, *args, **kwargs) -> None: """Execute task.""" self.execute_called = True self.execute_args = args self.execute_kwargs = kwargs return self.return_value def teardown(self) -> None: """Teardown task.""" self.teardown_called = True class TestTaskManager: """Test the features of the task manager.""" WAIT_TIMEOUT = 20.0 def _return_a_constant(self, a: int, b: int = 10): return a + b @classmethod def setup_class(cls): """Set the tests up.""" cls.task_manager = TaskManager(nb_workers=5) cls.task_manager.start() def test_task_manager_function_with_default_arguments(self): """Test a function submitted to the task manager with default arguments.""" task_id = self.task_manager.enqueue_task(self._return_a_constant, args=(32,)) task_result = self.task_manager.get_task_result(task_id) assert isinstance(task_result, AsyncResult) result = task_result.get(self.WAIT_TIMEOUT) assert result == 42 def test_task_manager_function_with_keyword_arguments(self): """Test a function submitted to the task manager with keyword arguments.""" task_id = self.task_manager.enqueue_task( self._return_a_constant, args=(32,), kwargs={"b": 10} ) task_result = self.task_manager.get_task_result(task_id) assert isinstance(task_result, AsyncResult) result = task_result.get(self.WAIT_TIMEOUT) assert result == 42 def test_task_manager_function_with_wrong_argument_number(self): """Test wrong number of arguments.""" task_id = self.task_manager.enqueue_task( self._return_a_constant, args=(), kwargs={"b": 10} ) task_result = self.task_manager.get_task_result(task_id) assert isinstance(task_result, AsyncResult) with pytest.raises(TypeError, match="missing .+ required positional argument:"): task_result.get(self.WAIT_TIMEOUT) def test_task_manager_task_object(self): """Test task manager with task object.""" expected_args = (0, 1) expected_kwargs = {"a": 0, "b": 2} expected_return_value = 42 my_task = MyTask(return_value=expected_return_value) task_id = self.task_manager.enqueue_task( my_task, args=expected_args, kwargs=expected_kwargs ) task_result = self.task_manager.get_task_result(task_id) assert isinstance(task_result, AsyncResult) result = task_result.get(self.WAIT_TIMEOUT) assert result == expected_return_value @pytest.mark.skip def test_task_manager_task_object_fails_when_not_pickable(self): """Test task manager with task object fails when the task is not pickable.""" expected_args = [lambda x: x] my_task = MyTask(return_value=None) task_id = self.task_manager.enqueue_task(my_task, args=expected_args) task_result = self.task_manager.get_task_result(task_id) assert isinstance(task_result, AsyncResult) with pytest.raises(AttributeError, match="Can't pickle local object"): task_result.get(self.WAIT_TIMEOUT) # noqaexpected_task.result @classmethod def teardown_class(cls): """Tear the test down.""" cls.task_manager.stop() ================================================ FILE: tests/test_aea/test_test_tools/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.test_tools.""" ================================================ FILE: tests/test_aea/test_test_tools/test_click_testing.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.test_tools.""" from unittest.mock import patch import pytest from aea.cli.core import cli from aea.test_tools.click_testing import CliRunner def test_invoke(): """Test runner invoke method.""" cli_runner = CliRunner() result = cli_runner.invoke(cli, ["--help"]) assert ( "Command-line tool for setting up an Autonomous Economic Agent" in result.output ) result = cli_runner.invoke(cli, "--help") assert ( "Command-line tool for setting up an Autonomous Economic Agent" in result.output ) def test_invoke_error(): """Test runner invoke method raises an error.""" cli_runner = CliRunner() with patch.object(cli, "main", side_effect=SystemExit(1)): result = cli_runner.invoke(cli, ["--help"]) assert result.exit_code == 1 with patch.object(cli, "main", side_effect=SystemExit(object())): result = cli_runner.invoke(cli, ["--help"]) assert result.exit_code == 1 def test_catch_exception(): """Test runner invoke method raises an exception and its propogated.""" cli_runner = CliRunner() # True with patch.object(cli, "main", side_effect=ValueError("expected")): result = cli_runner.invoke(cli, ["--help"]) assert result.exit_code == 1 # False with pytest.raises(ValueError, match="expected"): with patch.object(cli, "main", side_effect=ValueError("expected")): result = cli_runner.invoke(cli, ["--help"], catch_exceptions=False) def test_mix_std_err_False(): """Test stderr and stdout not mixed.""" cli_runner = CliRunner(mix_stderr=False) result = cli_runner.invoke(cli, "-v DEBUG run") assert result.exit_code == 1 # check for access, no exception should be raised result.stderr def test_mix_std_err_True(): """Test stderr and stdout are mixed.""" cli_runner = CliRunner(mix_stderr=True) result = cli_runner.invoke(cli, "-v DEBUG run") assert result.exit_code == 1 with pytest.raises(ValueError): result.stderr ================================================ FILE: tests/test_aea/test_test_tools/test_test_cases.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.test_tools.test_cases.""" import os import time from pathlib import Path from unittest import mock import pytest import yaml from aea_ledger_fetchai import FetchAICrypto import aea from aea.configurations.base import AgentConfig from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue from aea.test_tools.exceptions import AEATestingException from aea.test_tools.test_cases import ( AEATestCase, AEATestCaseEmpty, AEATestCaseEmptyFlaky, AEATestCaseManyFlaky, BaseAEATestCase, ) from packages.fetchai.connections.stub.connection import PUBLIC_ID as STUB_CONNECTION_ID from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue, DefaultDialogues, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_SKILL_PUBLIC_ID from packages.fetchai.skills.error import PUBLIC_ID as ERROR_SKILL_PUBLIC_ID from tests.conftest import MAX_FLAKY_RERUNS, MY_FIRST_AEA_PUBLIC_ID from tests.test_aea.test_cli import test_generate_wealth, test_interact TestWealthCommandsPositive = test_generate_wealth.TestWealthCommandsPositive TestInteractCommand = test_interact.TestInteractCommand class TestConfigCases(AEATestCaseEmpty): """Test config set/get.""" @classmethod def setup_class(cls): """Setup class.""" super(TestConfigCases, cls).setup_class() cls.add_item("connection", str(STUB_CONNECTION_ID)) cls.add_item("skill", str(ERROR_SKILL_PUBLIC_ID)) def test_agent_nested_set_agent_crudcollection(self): """Test agent test nested set from path.""" key_name = "agent.private_key_paths.cosmos" self.nested_set_config(key_name, "testdata2000") result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) assert b"testdata2000" in result.stdout_bytes def test_agent_nested_set_agent_crudcollection_all(self): """Test agent test nested set from path.""" key_name = "agent.private_key_paths" self.nested_set_config(key_name, {"cosmos": "testdata2000"}) result = self.run_cli_command( "config", "get", f"{key_name}.cosmos", cwd=self._get_cwd() ) assert b"testdata2000" in result.stdout_bytes def test_agent_nested_set_agent_simple(self): """Test agent test nested set from path.""" key_name = "agent.default_ledger" self.nested_set_config(key_name, "some_ledger") result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) assert b"some_ledger" in result.stdout_bytes def test_agent_nested_set_skill_simple(self): """Test agent test nested set from path.""" key_name = "vendor.fetchai.skills.error.handlers.error_handler.args.some_key" self.nested_set_config(key_name, "some_value") result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) assert b"some_value" in result.stdout_bytes def test_agent_nested_set_skill_simple_nested(self): """Test agent test nested set from path.""" key_name = "vendor.fetchai.skills.error.handlers.error_handler.args.some_key" self.nested_set_config(f"{key_name}.some_nested_key", "some_value") def test_agent_nested_set_skill_all(self): """Test agent test nested set from path.""" key_name = "vendor.fetchai.skills.error.handlers.error_handler.args" self.nested_set_config(key_name, {"some_key": "some_value"}) result = self.run_cli_command( "config", "get", f"{key_name}.some_key", cwd=self._get_cwd() ) assert b"some_value" in result.stdout_bytes def test_agent_nested_set_skill_all_nested(self): """Test agent test nested set from path.""" key_name = "vendor.fetchai.skills.error.handlers.error_handler.args" self.nested_set_config( key_name, {"some_key": {"some_nested_key": "some_value"}} ) def test_agent_nested_set_connection_simple(self): """Test agent test nested set from path.""" key_name = "vendor.fetchai.connections.stub.config.input_file" self.nested_set_config(key_name, "some_value") result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) assert b"some_value" in result.stdout_bytes def test_agent_nested_set_connection_dependency(self): """Test agent test nested set from path.""" key_name = "vendor.fetchai.connections.stub.dependencies" self.nested_set_config(key_name, {"dep": {"version": "==1.0.0"}}) def test_agent_set(self): """Test agent test set from path.""" value = True key_name = "agent.logging_config.disable_existing_loggers" self.set_config(key_name, value) result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) assert str(value) in str(result.stdout_bytes) def test_agent_get_exception(self): """Test agent test get non exists key.""" with pytest.raises(Exception, match=".*bad_key.*"): self.run_cli_command("config", "get", "agent.bad_key", cwd=self._get_cwd()) class TestRunAgent(AEATestCaseEmpty): """Tests test for generic cases of AEATestCases.""" def test_run_agent(self): """Run agent and test it's launched.""" self.generate_private_key() self.add_private_key() process = self.run_agent() assert self.is_running(process, timeout=30) class TestGenericCases(AEATestCaseEmpty): """Tests test for generic cases of AEATestCases.""" def test_disable_aea_logging(self): """Call logging disable.""" self.disable_aea_logging() def test_start_subprocess(self): """Start a python subprocess and check output.""" proc = self.start_subprocess("-c", "print('hi')") proc.wait(10) assert "hi" in self.stdout[proc.pid] def test_start_thread(self): """Start and join thread for python code.""" called = False def fn(): nonlocal called called = True thread = self.start_thread(fn) thread.join() assert called def test_fetch_and_delete(self): """Fetch and delete agent from repo.""" agent_name = "some_agent_for_tests" self.fetch_agent(str(MY_FIRST_AEA_PUBLIC_ID), agent_name) assert os.path.exists(agent_name) self.delete_agents(agent_name) assert not os.path.exists(agent_name) def test_diff(self): """Test difference_to_fetched_agent.""" agent_name = "some_agent_for_tests2" self.fetch_agent(str(MY_FIRST_AEA_PUBLIC_ID), agent_name) self.run_cli_command( "config", "set", "agent.default_ledger", "test_ledger", cwd=agent_name ) result = self.run_cli_command( "config", "get", "agent.default_ledger", cwd=agent_name ) assert b"test_ledger" in result.stdout_bytes diff = self.difference_to_fetched_agent(str(MY_FIRST_AEA_PUBLIC_ID), agent_name) assert diff assert "test_ledger" in diff[1] def test_no_diff(self): """Test no difference for two aea configs.""" agent_name = "some_agent_for_tests3" self.fetch_agent(str(MY_FIRST_AEA_PUBLIC_ID), agent_name) diff = self.difference_to_fetched_agent(str(MY_FIRST_AEA_PUBLIC_ID), agent_name) assert not diff def test_diff_different_overrides(self): """Test difference due to overrides.""" agent_name = "some_agent_for_tests4" self.fetch_agent(str(MY_FIRST_AEA_PUBLIC_ID), agent_name) self.set_agent_context(agent_name) self.run_cli_command( "config", "set", "vendor.fetchai.skills.echo.behaviours.echo.args.tick_interval", "2.0", cwd=self._get_cwd(), ) diff = self.difference_to_fetched_agent(str(MY_FIRST_AEA_PUBLIC_ID), agent_name) assert diff assert diff[0] == DEFAULT_AEA_CONFIG_FILE def test_terminate_subprocesses(self): """Start and terminate long running python subprocess.""" proc = self.start_subprocess("-c", "import time; time.sleep(10)") assert proc.returncode is None self._terminate_subprocesses() assert proc.returncode is not None def test_miss_from_output(self): """Test subprocess output missing output.""" proc = self.start_subprocess("-c", "print('hi')") assert len(self.missing_from_output(proc, ["hi"], timeout=5)) == 0 assert "HI" in self.missing_from_output(proc, ["HI"], timeout=5) def test_replace_file_content(self): """Replace content of the file with another one.""" file1 = "file1.txt" file2 = "file2.txt" with open(file1, "w") as f: f.write("hi") with open(file2, "w") as f: f.write("world") self.replace_file_content(Path(file1), Path(file2)) with open(file2, "r") as f: assert f.read() == "hi" class TestDifferenceToFetchedAgent(BaseAEATestCase): """Test 'different_to_fetched_agent' in case of component overrides.""" _mock_called = False original_function = yaml.safe_load_all test_agent_name: str @classmethod def setup_class(cls) -> None: """Set up the class.""" super().setup_class() # build aea, and override the tick interval cls.test_agent_name = "test_agent" cls.fetch_agent(str(MY_FIRST_AEA_PUBLIC_ID), cls.test_agent_name) cls.set_agent_context(cls.test_agent_name) cls.run_cli_command( "config", "set", "vendor.fetchai.skills.echo.behaviours.echo.args.tick_interval", "2.0", cwd=cls._get_cwd(), ) @classmethod def _safe_load_all_side_effect(cls, file): """Implement yaml.safe_load_all side-effect for testing.""" result = list(cls.original_function(file)) if not cls._mock_called: cls._mock_called = True fake_override = {} result.append(fake_override) return iter(result) def test_difference_to_fetched_agent(self, *_mocks): """Test difference to fetched agent.""" with mock.patch( "yaml.safe_load_all", side_effect=self._safe_load_all_side_effect ): file_diff = self.difference_to_fetched_agent( str(MY_FIRST_AEA_PUBLIC_ID), self.test_agent_name ) assert file_diff class TestLoadAgentConfig(AEATestCaseEmpty): """Test function 'load_agent_config'.""" def test_load_agent_config(self): """Test load_agent_config.""" agent_config = self.load_agent_config(self.agent_name) assert isinstance(agent_config, AgentConfig) def test_load_agent_config_when_agent_name_not_exists(self): """Test load_agent_config with a wrong agent name.""" wrong_agent_name = "non-existing-agent-name" with pytest.raises( AEATestingException, match=f"Cannot find agent '{wrong_agent_name}' in the current test case.", ): self.load_agent_config(wrong_agent_name) class TestAddAndEjectComponent(AEATestCaseEmpty): """Test add/reject components.""" def test_add_and_eject(self): """Test add/reject components.""" result = self.add_item("skill", str(ECHO_SKILL_PUBLIC_ID), local=True) assert result.exit_code == 0 result = self.eject_item("skill", str(ECHO_SKILL_PUBLIC_ID)) assert result.exit_code == 0 class TestAddAndRemoveComponent(AEATestCaseEmpty): """Test add/remove components.""" def test_add_and_eject(self): """Test add/reject components.""" result = self.add_item("skill", str(ECHO_SKILL_PUBLIC_ID), local=True) assert result.exit_code == 0 result = self.remove_item("skill", str(ECHO_SKILL_PUBLIC_ID)) assert result.exit_code == 0 class TestGenerateAndAddKey(AEATestCaseEmpty): """Test generate and add private key.""" def test_generate_and_add_key(self): """Test generate and add private key.""" result = self.generate_private_key("cosmos") assert result.exit_code == 0 result = self.add_private_key( "cosmos", "cosmos_private_key.txt", connection=True ) assert result.exit_code == 0 result = self.add_private_key("cosmos", "cosmos_private_key.txt") assert result.exit_code == 0 result = self.remove_private_key("cosmos") assert result.exit_code == 0 class TestGetWealth(AEATestCaseEmpty): """Test get_wealth.""" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_get_wealth(self): """Test get_wealth.""" # just call it, network related and quite unstable self.generate_private_key() self.add_private_key() self.get_wealth(FetchAICrypto.identifier) class TestGetAddress(AEATestCaseEmpty): """Test get_address.""" def test_get_address(self): """Test get_address.""" # just call it, network related and quite unstable self.generate_private_key() self.add_private_key() result = self.get_address(FetchAICrypto.identifier) assert len(result) == 44 assert result.startswith("fetch") class TestAEA(AEATestCase): """Test agent test set from path.""" path_to_aea = Path("tests") / "data" / "dummy_aea" def test_scaffold_and_fingerprint(self): """Test component scaffold and fingerprint.""" result = self.scaffold_item("skill", "skill1") assert result.exit_code == 0 result = self.fingerprint_item("skill", "fetchai/skill1:0.1.0") assert result.exit_code == 0 def test_scaffold_and_fingerprint_protocol(self): """Test component scaffold and fingerprint protocol.""" result = self.scaffold_item("protocol", "protocol1") assert result.exit_code == 0 result = self.fingerprint_item("protocol", "fetchai/protocol1:0.1.0") assert result.exit_code == 0 class TestSendReceiveEnvelopesSkill(AEATestCaseEmpty): """Test that we can communicate with agent via stub connection.""" def test_send_receive_envelope(self): """Run the echo skill sequence.""" self.generate_private_key() self.add_private_key() self.add_item("connection", str(STUB_CONNECTION_ID)) self.add_item("skill", str(ECHO_SKILL_PUBLIC_ID)) process = self.run_agent() is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" # add sending and receiving envelope from input/output files sender = "sender" def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: str ) -> Dialogue.Role: return DefaultDialogue.Role.AGENT default_dialogues = DefaultDialogues(sender, role_from_first_message) message_content = b"hello" message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), content=message_content, ) sent_envelope = Envelope( to=self.agent_name, sender=sender, protocol_specification_id=message.protocol_specification_id, message=message, ) self.send_envelope_to_agent(sent_envelope, self.agent_name) time.sleep(2.0) received_envelope = self.read_envelope_from_agent(self.agent_name) received_message = DefaultMessage.serializer.decode(received_envelope.message) assert sent_envelope.message.content == received_message.content class TestInvoke(AEATestCaseEmpty): """Test invoke method.""" def test_invoke(self): """Test invoke method.""" result = self.invoke("--version") assert result.exit_code == 0 assert f"aea, version {aea.__version__}" in result.stdout class TestFlakyMany(AEATestCaseManyFlaky): """Test that flaky tests are properly rerun.""" @pytest.mark.flaky(reruns=1) def test_fail_on_first_run(self): """Test failure on first run leads to second run.""" file = os.path.join(self.t, "test_file") if self.run_count == 1: open(file, "a").close() raise AssertionError("Expected error to trigger rerun!") assert self.run_count == 2, "Should only be rerun once!" assert not os.path.isfile(file), "File should not exist" class TestFlakyEmpty(AEATestCaseEmptyFlaky): """Test that flaky tests are properly rerun.""" @pytest.mark.flaky(reruns=1) def test_fail_on_first_run(self): """Test failure on first run leads to second run.""" file = os.path.join(self.t, "test_file") if self.run_count == 1: open(file, "a").close() raise AssertionError("Expected error to trigger rerun!") assert self.run_count == 2, "Should only be rerun once!" assert not os.path.isfile(file), "File should not exist" ================================================ FILE: tests/test_aea/test_test_tools/test_test_contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea.test_tools.test_contract.BaseContractTestCase.""" from pathlib import Path from unittest import mock import pytest from aea_ledger_fetchai import CosmosCrypto, FetchAIApi, FetchAICrypto, FetchAIFaucetApi from aea.test_tools.test_contract import BaseContractTestCase from tests.conftest import ROOT_DIR LEDGER_ID = "fetchai" CONTRACT_ADDRESS = "contract_address" TX_RECEIPT_EXAMPLE = {"json": "like", "raw_log": "log"} class TestContractTestCase(BaseContractTestCase): """Test case for BaseContractTestCase.""" path_to_contract = Path(ROOT_DIR, "tests", "data", "dummy_contract") @classmethod def setup(cls): """Setup the test class.""" cls.ledger_identifier = LEDGER_ID cls.fund_from_faucet = True with mock.patch.object( BaseContractTestCase, "sign_send_confirm_receipt_multisig_transaction", return_value=TX_RECEIPT_EXAMPLE, ): with mock.patch.object( BaseContractTestCase, "refill_from_faucet" ) as refill_from_faucet_mock: with mock.patch.object(CosmosCrypto, "sign_transaction"): with mock.patch.object(FetchAIApi, "get_deploy_transaction"): super().setup() refill_from_faucet_mock.assert_called_with( cls.ledger_api, cls.faucet_api, cls.item_owner_crypto.address, ) @classmethod def finish_contract_deployment(cls): """Finish contract deployment method.""" return CONTRACT_ADDRESS def test_setup(self): """Test the setup class method.""" assert self.ledger_identifier == LEDGER_ID assert type(self.ledger_api) is FetchAIApi assert self.ledger_api.identifier == LEDGER_ID assert type(self.deployer_crypto) is FetchAICrypto assert self.deployer_crypto.identifier == LEDGER_ID assert type(self.item_owner_crypto) is FetchAICrypto assert self.item_owner_crypto.identifier == LEDGER_ID assert type(self.faucet_api) is FetchAIFaucetApi assert self.faucet_api.identifier == LEDGER_ID assert self._contract.__class__.__name__ == "DummyContract" assert self.contract_address == CONTRACT_ADDRESS assert self.fund_from_faucet is True assert self.deployment_tx_receipt == TX_RECEIPT_EXAMPLE def test_contract_property(self): """Test contract property.""" assert self.contract is self._contract delattr(self.__class__, "_contract") with pytest.raises( ValueError, match="Ensure the contract is set during setup." ): self.contract @mock.patch.object(FetchAIFaucetApi, "get_wealth") def test_refill_from_faucet(self, get_wealth_mock): """Test the refill_from_faucet static method.""" with pytest.raises(ValueError) as e: self.refill_from_faucet( self.ledger_api, self.faucet_api, self.contract_address ) assert str(e) == "Balance not increased!" get_wealth_mock.assert_called_once_with(CONTRACT_ADDRESS) @mock.patch("aea.contracts.base.Contract.get_deploy_transaction", return_value="tx") @mock.patch.object( BaseContractTestCase, "sign_send_confirm_receipt_multisig_transaction", return_value=TX_RECEIPT_EXAMPLE, ) def test__deploy_contract( self, sign_send_confirm_receipt_multisig_transaction_mock, get_deploy_transaction_mock, ): """Test the _deploy_contract class method.""" gas = 1 result = self._deploy_contract( self.contract, self.ledger_api, self.deployer_crypto, gas ) assert result == TX_RECEIPT_EXAMPLE sign_send_confirm_receipt_multisig_transaction_mock.assert_called_once_with( "tx", self.ledger_api, [self.deployer_crypto] ) get_deploy_transaction_mock.assert_called_once_with( ledger_api=self.ledger_api, deployer_address=self.deployer_crypto.address, gas=gas, ) @mock.patch("aea.contracts.base.Contract.get_deploy_transaction", return_value=None) @mock.patch.object( BaseContractTestCase, "sign_send_confirm_receipt_multisig_transaction", return_value=TX_RECEIPT_EXAMPLE, ) def test__deploy_contract_tx_not_found( self, sign_send_confirm_receipt_multisig_transaction_mock, get_deploy_transaction_mock, ): """Test the _deploy_contract class method.""" gas = 1 with pytest.raises(ValueError) as e: self._deploy_contract( self.contract, self.ledger_api, self.deployer_crypto, gas ) assert str(e) == "Deploy transaction not found!" sign_send_confirm_receipt_multisig_transaction_mock.assert_not_called() get_deploy_transaction_mock.assert_called_once_with( ledger_api=self.ledger_api, deployer_address=self.deployer_crypto.address, gas=gas, ) @mock.patch.object(FetchAICrypto, "sign_transaction", return_value="tx") @mock.patch.object(FetchAIApi, "send_signed_transaction", return_value="tx_digest") @mock.patch.object( FetchAIApi, "get_transaction_receipt", return_value=TX_RECEIPT_EXAMPLE ) @mock.patch.object(FetchAIApi, "is_transaction_settled", return_value=True) def test_sign_send_confirm_receipt_multisig_transaction( self, is_transaction_settled_mock, get_transaction_receipt_mock, send_signed_transaction_mock, sign_transaction_mock, ): """Test the sign_send_confirm_receipt_multisig_transaction static method.""" sleep_time = 0 tx = "tx" result = self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto], sleep_time=sleep_time ) assert result == TX_RECEIPT_EXAMPLE is_transaction_settled_mock.assert_called_once_with(TX_RECEIPT_EXAMPLE) get_transaction_receipt_mock.assert_called_with("tx_digest") send_signed_transaction_mock.assert_called_once_with(tx) sign_transaction_mock.assert_called_once_with(tx) @mock.patch.object(FetchAICrypto, "sign_transaction", return_value="tx") @mock.patch.object(FetchAIApi, "send_signed_transaction", return_value=None) @mock.patch.object(FetchAIApi, "get_transaction_receipt") @mock.patch.object(FetchAIApi, "is_transaction_settled") def test_sign_send_confirm_receipt_multisig_transaction_digest_not_found( self, is_transaction_settled_mock, get_transaction_receipt_mock, send_signed_transaction_mock, sign_transaction_mock, ): """Test the sign_send_confirm_receipt_multisig_transaction static method: digest not found.""" tx = "tx" with pytest.raises(ValueError, match="Transaction digest not found!"): self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto], ) is_transaction_settled_mock.assert_not_called() get_transaction_receipt_mock.assert_not_called() send_signed_transaction_mock.assert_called_once_with(tx) sign_transaction_mock.assert_called_once_with(tx) @mock.patch.object(FetchAICrypto, "sign_transaction", return_value="tx") @mock.patch.object(FetchAIApi, "send_signed_transaction", return_value="tx_digest") @mock.patch.object(FetchAIApi, "get_transaction_receipt", return_value=None) @mock.patch.object(FetchAIApi, "is_transaction_settled") def test_sign_send_confirm_receipt_multisig_transaction_receipt_not_found( self, is_transaction_settled_mock, get_transaction_receipt_mock, send_signed_transaction_mock, sign_transaction_mock, ): """Test the sign_send_confirm_receipt_multisig_transaction static method: receipt not found.""" tx = "tx" sleep_time = 0 with pytest.raises(ValueError, match="Transaction receipt not found!"): self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto], sleep_time=sleep_time ) is_transaction_settled_mock.assert_not_called() get_transaction_receipt_mock.assert_called_with("tx_digest") send_signed_transaction_mock.assert_called_once_with(tx) sign_transaction_mock.assert_called_once_with(tx) @mock.patch.object(FetchAICrypto, "sign_transaction", return_value="tx") @mock.patch.object(FetchAIApi, "send_signed_transaction", return_value="tx_digest") @mock.patch.object( FetchAIApi, "get_transaction_receipt", return_value=TX_RECEIPT_EXAMPLE ) @mock.patch.object(FetchAIApi, "is_transaction_settled", return_value=False) def test_sign_send_confirm_receipt_multisig_transaction_receipt_not_valid( self, is_transaction_settled_mock, get_transaction_receipt_mock, send_signed_transaction_mock, sign_transaction_mock, ): """Test the sign_send_confirm_receipt_multisig_transaction static method: receipt not valid.""" tx = "tx" sleep_time = 0 with pytest.raises(ValueError) as e: self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto], sleep_time=sleep_time ) assert str(e) == "Transaction receipt not valid!\nlog" is_transaction_settled_mock.assert_called_with(TX_RECEIPT_EXAMPLE) get_transaction_receipt_mock.assert_called_with("tx_digest") send_signed_transaction_mock.assert_called_once_with(tx) sign_transaction_mock.assert_called_once_with(tx) @mock.patch.object( BaseContractTestCase, "sign_send_confirm_receipt_multisig_transaction", return_value=TX_RECEIPT_EXAMPLE, ) def test_sign_send_confirm_receipt_transaction_positive( self, sign_send_confirm_receipt_multisig_transaction_mock ): """Test sign_send_confirm_receipt_transaction method for positive result.""" tx = "tx" sleep_time = 0.2 ledger_api, crypto = self.ledger_api, self.deployer_crypto self.sign_send_confirm_receipt_transaction(tx, ledger_api, crypto, sleep_time) sign_send_confirm_receipt_multisig_transaction_mock.assert_called_once_with( tx, ledger_api, [crypto], sleep_time ) ================================================ FILE: tests/test_aea/test_test_tools/test_test_skill.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.test_tools.test_cases.""" from pathlib import Path from typing import cast import pytest from aea.decision_maker.gop import GoalPursuitReadiness, OwnershipState, Preferences from aea.exceptions import AEAEnforceError from aea.mail.base import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue, DialogueLabel, DialogueMessage from aea.skills.base import Skill from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.dialogues import FipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from tests.conftest import ROOT_DIR class TestSkillTestCase(BaseSkillTestCase): """Test case for BaseSkillTestCase.""" path_to_skill = Path(ROOT_DIR, "tests", "data", "dummy_skill") @classmethod def setup(cls): """Setup the test class.""" cls.behaviour_arg_1 = 2 cls.behaviour_arg_2 = "3" config_overrides = { "behaviours": { "dummy": { "args": { "behaviour_arg_1": cls.behaviour_arg_1, "behaviour_arg_2": cls.behaviour_arg_2, } } }, } cls.shared_state_key = "some_shared_state_key" cls.shared_state_value = "some_shared_state_value" cls.shared_state = {cls.shared_state_key: cls.shared_state_value} tac_dm_context_kwargs = { "goal_pursuit_readiness": GoalPursuitReadiness(), "ownership_state": OwnershipState(), "preferences": Preferences(), } super().setup( config_overrides=config_overrides, shared_state=cls.shared_state, dm_context_kwargs=tac_dm_context_kwargs, ) def test_setup(self): """Test the setup() class method.""" assert self.skill.skill_context.agent_address == "test_agent_address" assert self.skill.skill_context.agent_name == "test_agent_name" assert ( self.skill.skill_context.search_service_address == "dummy_author/dummy_search_skill:0.1.0" ) assert ( self.skill.skill_context.decision_maker_address == "dummy_decision_maker_address" ) assert "dummy" in self.skill.behaviours.keys() assert "dummy" in self.skill.handlers.keys() assert "dummy_internal" in self.skill.handlers.keys() assert "dummy" in self.skill.models.keys() assert ( self.skill.skill_context._agent_context.shared_state[self.shared_state_key] == self.shared_state_value ) assert ( self.skill.skill_context.behaviours.dummy.kwargs["behaviour_arg_1"] == self.behaviour_arg_1 ) assert ( self.skill.skill_context.behaviours.dummy.kwargs["behaviour_arg_2"] == self.behaviour_arg_2 ) assert ( type( self.skill.skill_context.decision_maker_handler_context.goal_pursuit_readiness ) == GoalPursuitReadiness ) assert ( type( self.skill.skill_context.decision_maker_handler_context.ownership_state ) == OwnershipState ) assert ( type(self.skill.skill_context.decision_maker_handler_context.preferences) == Preferences ) def test_properties(self): """Test the properties.""" assert isinstance(self.skill, Skill) assert self.skill.behaviours.get("dummy") is not None def test_get_quantity_in_outbox(self): """Test the get_quantity_in_outbox method.""" assert self.get_quantity_in_outbox() == 0 dummy_message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy" ) dummy_message.to = "some_to" dummy_message.sender = "some_sender" self.skill.skill_context.outbox.put_message(dummy_message) assert self.get_quantity_in_outbox() == 1 def test_get_message_from_outbox(self): """Test the get_message_from_outbox method.""" assert self.get_message_from_outbox() is None dummy_message_1 = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_1" ) dummy_message_1.to = "some_to_1" dummy_message_1.sender = "some_sender_1" self.skill.skill_context.outbox.put_message(dummy_message_1) dummy_message_2 = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_2" ) dummy_message_2.to = "some_to_2" dummy_message_2.sender = "some_sender_2" self.skill.skill_context.outbox.put_message(dummy_message_2) assert self.get_message_from_outbox() == dummy_message_1 assert self.get_message_from_outbox() == dummy_message_2 def test_drop_messages_from_outbox(self): """Test the drop_messages_from_outbox method.""" assert self.get_quantity_in_outbox() == 0 self.drop_messages_from_outbox(5) assert self.get_quantity_in_outbox() == 0 dummy_message_1 = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_2" ) dummy_message_1.to = "some_to_1" dummy_message_1.sender = "some_sender_1" self.skill.skill_context.outbox.put_message(dummy_message_1) dummy_message_2 = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_2" ) dummy_message_2.to = "some_to_2" dummy_message_2.sender = "some_sender_2" self.skill.skill_context.outbox.put_message(dummy_message_2) dummy_message_3 = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_2" ) dummy_message_3.to = "some_to_3" dummy_message_3.sender = "some_sender_3" self.skill.skill_context.outbox.put_message(dummy_message_3) assert self.get_quantity_in_outbox() == 3 self.drop_messages_from_outbox(2) assert self.get_quantity_in_outbox() == 1 assert self.get_message_from_outbox() == dummy_message_3 def test_get_quantity_in_decision_maker_inbox(self): """Test the get_quantity_in_decision_maker_inbox method.""" assert self.get_quantity_in_decision_maker_inbox() == 0 dummy_message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy" ) dummy_message.to = "some_to" dummy_message.sender = "some_sender" self.skill.skill_context.decision_maker_message_queue.put(dummy_message) assert self.get_quantity_in_decision_maker_inbox() == 1 def test_get_message_from_decision_maker_inbox(self): """Test the get_message_from_decision_maker_inbox method.""" assert self.get_message_from_decision_maker_inbox() is None dummy_message_1 = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_1" ) dummy_message_1.to = "some_to_1" dummy_message_1.sender = "some_sender_1" self.skill.skill_context.decision_maker_message_queue.put(dummy_message_1) dummy_message_2 = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_2" ) dummy_message_2.to = "some_to_2" dummy_message_2.sender = "some_sender_2" self.skill.skill_context.decision_maker_message_queue.put(dummy_message_2) assert self.get_message_from_decision_maker_inbox() == dummy_message_1 assert self.get_message_from_decision_maker_inbox() == dummy_message_2 def test_drop_messages_from_decision_maker_inbox(self): """Test the drop_messages_from_decision_maker_inbox method.""" assert self.get_quantity_in_decision_maker_inbox() == 0 self.drop_messages_from_decision_maker_inbox(5) assert self.get_quantity_in_decision_maker_inbox() == 0 dummy_message_1 = Message() dummy_message_1.to = "some_to_1" dummy_message_1.sender = "some_sender_1" self.skill.skill_context.decision_maker_message_queue.put(dummy_message_1) dummy_message_2 = Message() dummy_message_2.to = "some_to_2" dummy_message_2.sender = "some_sender_2" self.skill.skill_context.decision_maker_message_queue.put(dummy_message_2) dummy_message_3 = Message() dummy_message_3.to = "some_to_3" dummy_message_3.sender = "some_sender_3" self.skill.skill_context.decision_maker_message_queue.put(dummy_message_3) assert self.get_quantity_in_decision_maker_inbox() == 3 self.drop_messages_from_decision_maker_inbox(2) assert self.get_quantity_in_decision_maker_inbox() == 1 assert self.get_message_from_decision_maker_inbox() == dummy_message_3 def test_assert_quantity_in_outbox(self): """Test the assert_quantity_in_outbox method.""" with pytest.raises( AssertionError, match=f"Invalid number of messages in outbox. Expected {1}. Found {0}.", ): self.assert_quantity_in_outbox(1) dummy_message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy" ) dummy_message.to = "some_to" dummy_message.sender = "some_sender" self.skill.skill_context.outbox.put_message(dummy_message) self.assert_quantity_in_outbox(1) def test_assert_quantity_in_decision_making_queue(self): """Test the assert_quantity_in_decision_making_queue method.""" with pytest.raises( AssertionError, match=f"Invalid number of messages in decision maker queue. Expected {1}. Found {0}.", ): self.assert_quantity_in_decision_making_queue(1) dummy_message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content="dummy_1" ) dummy_message.to = "some_to_1" dummy_message.sender = "some_sender_1" self.skill.skill_context.decision_maker_message_queue.put(dummy_message) self.assert_quantity_in_decision_making_queue(1) def test_positive_message_has_attributes_valid_type(self): """Test the message_has_attributes method where the message is of the specified type.""" dummy_message = FipaMessage( dialogue_reference=("0", "0"), message_id=1, performative=FipaMessage.Performative.CFP, target=0, query="some_query", ) dummy_message.to = "some_to" dummy_message.sender = "some_sender" valid_has_attribute, valid_has_attribute_msg = self.message_has_attributes( actual_message=dummy_message, message_type=FipaMessage, message_id=1, performative=FipaMessage.Performative.CFP, target=0, query="some_query", to="some_to", sender="some_sender", ) assert valid_has_attribute assert ( valid_has_attribute_msg == "The message has the provided expected attributes." ) def test_negative_message_has_attributes_invalid_message_id(self): """Negative test for message_has_attributes method where the message id does NOT match.""" dummy_message = FipaMessage( dialogue_reference=("0", "0"), message_id=1, performative=FipaMessage.Performative.CFP, target=0, query="some_query", ) dummy_message.to = "some_to" dummy_message.sender = "some_sender" invalid_has_attribute, invalid_has_attribute_msg = self.message_has_attributes( actual_message=dummy_message, message_type=FipaMessage, message_id=2, performative=FipaMessage.Performative.CFP, target=0, query="some_query", to="some_to", sender="some_sender", ) assert not invalid_has_attribute assert ( invalid_has_attribute_msg == "The 'message_id' fields do not match. Actual 'message_id': 1. Expected 'message_id': 2" ) def test_negative_message_has_attributes_invalid_type(self): """Test the message_has_attributes method where the message is NOT of the specified type.""" dummy_message = FipaMessage( dialogue_reference=("0", "0"), message_id=1, performative=FipaMessage.Performative.CFP, target=0, query="some_query", ) dummy_message.to = "some_to" dummy_message.sender = "some_sender" valid_has_attribute, valid_has_attribute_msg = self.message_has_attributes( actual_message=dummy_message, message_type=DefaultMessage, message_id=1, performative=FipaMessage.Performative.CFP, target=0, query="some_query", to="some_to", sender="some_sender", ) assert not valid_has_attribute assert ( valid_has_attribute_msg == f"The message types do not match. Actual type: {FipaMessage}. Expected type: {DefaultMessage}" ) invalid_has_attribute, invalid_has_attribute_msg = self.message_has_attributes( actual_message=dummy_message, message_type=FipaMessage, message_id=2, performative=FipaMessage.Performative.CFP, target=0, query="some_query", to="some_to", sender="some_sender", ) assert not invalid_has_attribute assert ( invalid_has_attribute_msg == "The 'message_id' fields do not match. Actual 'message_id': 1. Expected 'message_id': 2" ) def test_build_incoming_message(self): """Test the build_incoming_message method.""" message_type = FipaMessage performative = FipaMessage.Performative.CFP dialogue_reference = ("1", "1") to = "some_to" query = "some_query" incoming_message = self.build_incoming_message( message_type=message_type, performative=performative, dialogue_reference=dialogue_reference, to=to, query=query, ) assert type(incoming_message) == message_type incoming_message = cast(FipaMessage, incoming_message) assert incoming_message.dialogue_reference == dialogue_reference assert incoming_message.message_id == 1 assert incoming_message.target == 0 assert incoming_message.performative == performative assert incoming_message.query == query assert incoming_message.sender == "counterparty" assert incoming_message.to == to def test_positive_build_incoming_message_for_skill_dialogue(self): """Positive test for build_incoming_message_for_skill_dialogue method.""" fipa_dialogues = FipaDialogues( self_address=self.skill.skill_context.agent_address ) base_msg, dialogue = fipa_dialogues.create( counterparty="some_counterparty", performative=FipaMessage.Performative.CFP, query="some_query", ) performative = FipaMessage.Performative.PROPOSE proposal = "some_proposal" incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=performative, proposal=proposal, ) assert type(incoming_message) == FipaMessage incoming_message = cast(FipaMessage, incoming_message) assert ( incoming_message.dialogue_reference == dialogue.dialogue_label.dialogue_reference ) assert incoming_message.message_id != base_msg.message_id assert incoming_message.target == base_msg.message_id assert incoming_message.performative == performative assert incoming_message.proposal == proposal assert incoming_message.sender == dialogue.dialogue_label.dialogue_opponent_addr assert incoming_message.to == dialogue.self_address def test_negative_build_incoming_message_for_skill_dialogue_dialogue_is_none(self): """Negative test for build_incoming_message_for_skill_dialogue method where the provided dialogue is None.""" performative = FipaMessage.Performative.PROPOSE proposal = "some_proposal" with pytest.raises(AEAEnforceError, match="dialogue cannot be None."): self.build_incoming_message_for_skill_dialogue( dialogue=None, performative=performative, proposal=proposal, ) def test_negative_build_incoming_message_for_skill_dialogue_dialogue_is_empty(self): """Negative test for build_incoming_message_for_skill_dialogue method where the provided dialogue is empty.""" performative = FipaMessage.Performative.PROPOSE proposal = "some_proposal" fipa_dialogues = FipaDialogues( self_address=self.skill.skill_context.agent_address ) dialogue = fipa_dialogues._create_self_initiated( dialogue_opponent_addr="some_counterparty", dialogue_reference=("0", ""), role=FipaDialogue.Role.BUYER, ) with pytest.raises(AEAEnforceError, match="dialogue cannot be empty."): self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=performative, proposal=proposal, ) def test_provide_unspecified_fields(self): """Test the _provide_unspecified_fields method.""" dialogue_message_unspecified = DialogueMessage( FipaMessage.Performative.ACCEPT, {} ) is_incoming, target = self._provide_unspecified_fields( dialogue_message_unspecified, last_is_incoming=False ) assert is_incoming is True assert target is None dialogue_message_specified = DialogueMessage( FipaMessage.Performative.ACCEPT, {}, False, 4 ) is_incoming, target = self._provide_unspecified_fields( dialogue_message_specified, last_is_incoming=True ) assert is_incoming is False assert target == 4 def test_non_initial_incoming_message_dialogue_reference(self): """Test the _non_initial_incoming_message_dialogue_reference method.""" dialogue_incomplete_ref = FipaDialogue( DialogueLabel(("2", ""), "opponent", "self_address"), "self_address", FipaDialogue.Role.BUYER, ) reference_incomplete = self._non_initial_incoming_message_dialogue_reference( dialogue_incomplete_ref ) assert reference_incomplete[1] != "" dialogue_complete_ref = FipaDialogue( DialogueLabel(("2", "7"), "opponent", "self_address"), "self_address", FipaDialogue.Role.BUYER, ) reference_complete = self._non_initial_incoming_message_dialogue_reference( dialogue_complete_ref ) assert reference_complete[1] == "7" def test_extract_message_fields(self): """Test the _extract_message_fields method.""" expected_performative = FipaMessage.Performative.ACCEPT expected_contents = {} expected_is_incoming = False expected_target = 4 dialogue_message = DialogueMessage( expected_performative, expected_contents, expected_is_incoming, expected_target, ) ( actual_performative, actual_contents, actual_message_id, actual_is_incoming, actual_target, ) = self._extract_message_fields( message=dialogue_message, index=3, last_is_incoming=True ) assert actual_message_id == 4 assert actual_target == expected_target assert actual_performative == expected_performative assert actual_contents == expected_contents assert actual_is_incoming == expected_is_incoming def test_prepare_skill_dialogue_valid_self_initiated(self): """Positive test for prepare_skill_dialogue method with a valid dialogue initiated by self.""" fipa_dialogues = FipaDialogues( self_address=self.skill.skill_context.agent_address ) dialogue_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_1"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_2"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_3"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_4"}, ), DialogueMessage(FipaMessage.Performative.ACCEPT, {}), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": "some_info"} ), ) dialogue = self.prepare_skill_dialogue( fipa_dialogues, dialogue_messages, "counterparty", ) assert type(dialogue) == FipaDialogue assert dialogue.is_self_initiated assert len(dialogue._outgoing_messages) == 4 assert len(dialogue._incoming_messages) == 4 assert dialogue._incoming_messages[1].proposal == "some_counter_proposal_2" assert dialogue._incoming_messages[3].info == "some_info" def test_prepare_skill_dialogue_valid_opponent_initiated(self): """Positive test for prepare_skill_dialogue method with a valid dialogue initiated by the opponent.""" fipa_dialogues = FipaDialogues( self_address=self.skill.skill_context.agent_address ) dialogue_messages = ( DialogueMessage( FipaMessage.Performative.CFP, {"query": "some_query"}, True ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_1"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_2"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_3"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_4"}, ), DialogueMessage(FipaMessage.Performative.ACCEPT, {}), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": "some_info"} ), ) dialogue = self.prepare_skill_dialogue( fipa_dialogues, dialogue_messages, "counterparty", ) assert type(dialogue) == FipaDialogue assert not dialogue.is_self_initiated assert len(dialogue._outgoing_messages) == 4 assert len(dialogue._incoming_messages) == 4 assert dialogue._outgoing_messages[1].proposal == "some_counter_proposal_2" assert dialogue._outgoing_messages[-1].info == "some_info" def test_negative_prepare_skill_dialogue_invalid_opponent_initiated(self): """Negative test for prepare_skill_dialogue method with an invalid dialogue initiated by the opponent.""" fipa_dialogues = FipaDialogues( self_address=self.skill.skill_context.agent_address ) dialogue_messages = ( DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"}, True ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_1"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_2"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_3"}, ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_counter_proposal_4"}, ), DialogueMessage(FipaMessage.Performative.ACCEPT, {}), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": "some_info"} ), ) with pytest.raises( AEAEnforceError, match="Cannot update the dialogue with message number 1" ): self.prepare_skill_dialogue( fipa_dialogues, dialogue_messages, "counterparty", ) def test_negative_prepare_skill_dialogue_empty_messages(self): """Negative test for prepare_skill_dialogue method where the list of DialogueMessages is emoty.""" fipa_dialogues = FipaDialogues( self_address=self.skill.skill_context.agent_address ) dialogue_messages = tuple() with pytest.raises( AEAEnforceError, match="the list of messages must be positive." ): self.prepare_skill_dialogue( fipa_dialogues, dialogue_messages, "counterparty", ) def test_negative_prepare_skill_dialogue_invalid(self): """Negative test for prepare_skill_dialogue method with an invalid dialogue (a message has invalid target).""" fipa_dialogues = FipaDialogues( self_address=self.skill.skill_context.agent_address ) dialogue_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"}, target=2, ), ) with pytest.raises( AEAEnforceError, match="Cannot update the dialogue with message number .*" ): self.prepare_skill_dialogue( fipa_dialogues, dialogue_messages, "counterparty", ) class FipaDialogues(BaseFipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.BUYER BaseFipaDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=FipaDialogue, ) ================================================ FILE: tests/test_aea_core_packages/test_connections/test_http_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the HTTP client connection implementation.""" ================================================ FILE: tests/test_aea_core_packages/test_connections/test_http_client/test_http_client.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for the HTTP Client connection and channel.""" import asyncio import logging from asyncio import CancelledError from typing import Any, cast from unittest.mock import MagicMock, Mock, patch import aiohttp import pytest from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.http_client.connection import HTTPClientConnection from packages.fetchai.protocols.http.dialogues import HttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage from tests.common.mocks import AnyStringWith from tests.conftest import UNKNOWN_PROTOCOL_PUBLIC_ID, get_host, get_unused_tcp_port logger = logging.getLogger(__name__) class _MockRequest: """Fake request for aiohttp client session.""" def __init__(self, response: Mock) -> None: """Init with mock response.""" self.response = response async def __aenter__(self) -> Any: """Enter async context.""" return self.response async def __aexit__(self, *args, **kwargs) -> None: """Exit async context.""" return None class HttpDialogues(BaseHttpDialogues): """The dialogues class keeps track of all http dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return HttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) @pytest.mark.asyncio class TestHTTPClientConnect: """Tests the http client connection's 'connect' functionality.""" def setup(self): """Initialise the class.""" self.address = get_host() self.port = get_unused_tcp_port() self.agent_identity = Identity( "name", address="some string", public_key="some public_key" ) self.client_skill_id = "some/skill:0.1.0" configuration = ConnectionConfig( host=self.address, port=self.port, connection_id=HTTPClientConnection.connection_id, ) self.http_client_connection = HTTPClientConnection( configuration=configuration, data_dir=MagicMock(), identity=self.agent_identity, ) self.connection_address = str(HTTPClientConnection.connection_id) self.http_dialogs = HttpDialogues(self.client_skill_id) @pytest.mark.asyncio async def test_initialization(self): """Test the initialisation of the class.""" assert self.http_client_connection.address == self.agent_identity.address @pytest.mark.asyncio async def test_connection(self): """Test the connect functionality of the http client connection.""" await self.http_client_connection.connect() assert self.http_client_connection.is_connected is True @pytest.mark.asyncio async def test_disconnect(self): """Test the disconnect functionality of the http client connection.""" await self.http_client_connection.connect() assert self.http_client_connection.is_connected is True await self.http_client_connection.disconnect() assert self.http_client_connection.is_connected is False @pytest.mark.asyncio async def test_http_send_error(self): """Test request fails and send back result with code 600.""" await self.http_client_connection.connect() request_http_message, _ = self.http_dialogs.create( counterparty=self.connection_address, performative=HttpMessage.Performative.REQUEST, method="get", url="bad url", headers="", version="", body=b"", ) request_envelope = Envelope( to=self.connection_address, sender=self.client_skill_id, protocol_specification_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=request_http_message, ) connection_response_mock = Mock() connection_response_mock.status_code = 200 await self.http_client_connection.send(envelope=request_envelope) # TODO: Consider returning the response from the server in order to be able to assert that the message send! envelope = await asyncio.wait_for( self.http_client_connection.receive(), timeout=10 ) assert envelope assert envelope.message.status_code == 600 await self.http_client_connection.disconnect() @pytest.mark.asyncio async def test_http_client_send_not_connected_error(self): """Test connection.send error if not conencted.""" with pytest.raises(ConnectionError): await self.http_client_connection.send(Mock()) @pytest.mark.asyncio async def test_http_channel_send_not_connected_error(self): """Test channel.send error if not conencted.""" with pytest.raises(ValueError): self.http_client_connection.channel.send(Mock()) @pytest.mark.asyncio async def test_send_empty_envelope_skip(self): """Test skip on empty envelope request sent.""" await self.http_client_connection.connect() with patch.object( self.http_client_connection.channel, "_http_request_task" ) as mock: await self.http_client_connection.send(None) mock.assert_not_called() @pytest.mark.asyncio async def test_channel_get_message_not_connected(self): """Test errro on message get if not connected.""" with pytest.raises(ValueError): await self.http_client_connection.channel.get_message() @pytest.mark.asyncio async def test_channel_cancel_tasks_on_disconnect(self): """Test requests tasks cancelled on disconnect.""" await self.http_client_connection.connect() request_http_message, _ = self.http_dialogs.create( counterparty=self.connection_address, performative=HttpMessage.Performative.REQUEST, method="get", url="https://not-a-google.com", headers="", version="", body=b"", ) request_envelope = Envelope( to=self.connection_address, sender=self.client_skill_id, protocol_specification_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=request_http_message, ) connection_response_mock = Mock() connection_response_mock.status_code = 200 response_mock = Mock() response_mock.status = 200 response_mock.headers = {"headers": "some header"} response_mock.reason = "OK" response_mock._body = b"Some content" response_mock.read.return_value = asyncio.Future() with patch.object( aiohttp.ClientSession, "request", return_value=_MockRequest(response_mock), ): await self.http_client_connection.send(envelope=request_envelope) assert self.http_client_connection.channel._tasks task = list(self.http_client_connection.channel._tasks)[0] assert not task.done() await self.http_client_connection.disconnect() assert not self.http_client_connection.channel._tasks assert task.done() with pytest.raises(CancelledError): await task @pytest.mark.asyncio async def test_http_send_ok(self): """Test request is ok cause mocked.""" await self.http_client_connection.connect() request_http_message, sending_dialogue = self.http_dialogs.create( counterparty=self.connection_address, performative=HttpMessage.Performative.REQUEST, method="get", url="https://not-a-google.com", headers="", version="", body=b"", ) request_envelope = Envelope( to=self.connection_address, sender=self.client_skill_id, message=request_http_message, ) connection_response_mock = Mock() connection_response_mock.status_code = 200 response_mock = Mock() response_mock.status = 200 response_mock.headers = {"headers": "some header"} response_mock.reason = "OK" response_mock._body = b"Some content" response_mock.read.return_value = asyncio.Future() response_mock.read.return_value.set_result("") with patch.object( aiohttp.ClientSession, "request", return_value=_MockRequest(response_mock), ): await self.http_client_connection.send(envelope=request_envelope) # TODO: Consider returning the response from the server in order to be able to assert that the message send! envelope = await asyncio.wait_for( self.http_client_connection.receive(), timeout=10 ) assert envelope is not None and envelope.message is not None message = envelope.message response_dialogue = self.http_dialogs.update(message) assert message.status_code == response_mock.status, message.body.decode("utf-8") assert sending_dialogue == response_dialogue await self.http_client_connection.disconnect() @pytest.mark.asyncio async def test_http_dialogue_construct_fail(self): """Test dialogue not properly constructed.""" await self.http_client_connection.connect() incorrect_http_message = HttpMessage( dialogue_reference=self.http_dialogs.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.RESPONSE, status_code=500, headers="", status_text="", body=b"", version="", ) incorrect_http_message.to = self.connection_address incorrect_http_message.sender = self.client_skill_id # the incorrect message cannot be sent into a dialogue, so this is omitted. envelope = Envelope( to=incorrect_http_message.to, sender=incorrect_http_message.sender, message=incorrect_http_message, ) with patch.object( self.http_client_connection.channel.logger, "warning" ) as mock_logger: await self.http_client_connection.channel._http_request_task(envelope) mock_logger.assert_any_call( AnyStringWith("Could not create dialogue for message=") ) @pytest.mark.asyncio async def test_http_send_exception(self): """Test request is ok cause mocked.""" await self.http_client_connection.connect() request_http_message, sending_dialogue = self.http_dialogs.create( counterparty=self.connection_address, performative=HttpMessage.Performative.REQUEST, method="get", url="https://not-a-google.com", headers="", version="", body=b"", ) request_envelope = Envelope( to=self.connection_address, sender=self.client_skill_id, message=request_http_message, ) with patch.object( aiohttp.ClientSession, "request", side_effect=asyncio.TimeoutError("expected exception"), ): await self.http_client_connection.send(envelope=request_envelope) envelope = await asyncio.wait_for( self.http_client_connection.receive(), timeout=10 ) assert envelope message = cast(HttpMessage, envelope.message) assert message.performative == HttpMessage.Performative.RESPONSE assert b"expected exception" in message.body ================================================ FILE: tests/test_aea_core_packages/test_connections/test_http_server/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the HTTP Server connection implementation.""" ================================================ FILE: tests/test_aea_core_packages/test_connections/test_http_server/test_http_server.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the HTTP Server connection module.""" import asyncio import logging import os import ssl from traceback import print_exc from typing import Tuple, cast from unittest.mock import MagicMock, Mock, patch import aiohttp import pytest from aiohttp.client_reqrep import ClientResponse from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.http_server.connection import ( APISpec, HTTPServerConnection, Response, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage from tests.common.mocks import RegexComparator from tests.conftest import ROOT_DIR, get_host, get_unused_tcp_port logger = logging.getLogger(__name__) class HttpDialogues(BaseHttpDialogues): """The dialogues class keeps track of all http dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return HttpDialogue.Role.SERVER BaseHttpDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) @pytest.mark.asyncio class TestHTTPServer: """Tests for HTTPServer connection.""" async def request(self, method: str, path: str, **kwargs) -> ClientResponse: """ Make a http request. :param method: HTTP method: GET, POST etc :param path: path to request on server. full url constructed automatically :return: http response """ try: url = f"http://{self.host}:{self.port}{path}" async with aiohttp.ClientSession() as session: async with session.request(method, url, **kwargs) as resp: await resp.read() return resp except Exception: print_exc() raise def setup(self): """Initialise the test case.""" self.identity = Identity("name", address="my_key", public_key="my_public_key") self.agent_address = self.identity.address self.host = get_host() self.port = get_unused_tcp_port() self.api_spec_path = os.path.join( ROOT_DIR, "tests", "data", "petstore_sim.yaml" ) self.connection_id = HTTPServerConnection.connection_id self.protocol_id = HttpMessage.protocol_id self.target_skill_id = "some_author/some_skill:0.1.0" self.configuration = ConnectionConfig( host=self.host, port=self.port, target_skill_id=self.target_skill_id, api_spec_path=self.api_spec_path, connection_id=HTTPServerConnection.connection_id, restricted_to_protocols={HttpMessage.protocol_id}, ) self.http_connection = HTTPServerConnection( configuration=self.configuration, data_dir=MagicMock(), identity=self.identity, ) self.loop = asyncio.get_event_loop() self.loop.run_until_complete(self.http_connection.connect()) self.connection_address = str(HTTPServerConnection.connection_id) self._dialogues = HttpDialogues(self.target_skill_id) self.original_timeout = self.http_connection.channel.timeout_window @pytest.mark.asyncio async def test_http_connection_disconnect_channel(self): """Test the disconnect.""" await self.http_connection.channel.disconnect() assert self.http_connection.channel.is_stopped def _get_message_and_dialogue( self, envelope: Envelope ) -> Tuple[HttpMessage, HttpDialogue]: message = cast(HttpMessage, envelope.message) dialogue = cast(HttpDialogue, self._dialogues.update(message)) assert dialogue is not None return message, dialogue @pytest.mark.asyncio async def test_get_200(self): """Test send get request w/ 200 response.""" request_task = self.loop.create_task(self.request("get", "/pets")) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = dialogue.reply( target_message=incoming_message, performative=HttpMessage.Performative.RESPONSE, version=incoming_message.version, status_code=200, status_text="Success", body=b"Response body", ) response_envelope = Envelope( to=envelope.sender, sender=envelope.to, context=envelope.context, message=message, ) await self.http_connection.send(response_envelope) response = await asyncio.wait_for( request_task, timeout=20, ) assert ( response.status == 200 and response.reason == "Success" and await response.text() == "Response body" ) @pytest.mark.asyncio async def test_header_content_type(self): """Test send get request w/ 200 response.""" content_type = "something/unique" request_task = self.loop.create_task(self.request("get", "/pets")) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = dialogue.reply( target_message=incoming_message, performative=HttpMessage.Performative.RESPONSE, version=incoming_message.version, headers=f"Content-Type: {content_type}", status_code=200, status_text="Success", body=b"Response body", ) response_envelope = Envelope( to=envelope.sender, sender=envelope.to, context=envelope.context, message=message, ) await self.http_connection.send(response_envelope) response = await asyncio.wait_for( request_task, timeout=20, ) assert ( response.status == 200 and response.reason == "Success" and await response.text() == "Response body" ) assert response.headers["Content-Type"] == content_type @pytest.mark.asyncio async def test_bad_performative_get_timeout_error(self): """Test send get request w/ 200 response.""" self.http_connection.channel.timeout_window = 3 request_task = self.loop.create_task(self.request("get", "/pets")) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=10) assert envelope incoming_message, dialogue = self._get_message_and_dialogue(envelope) incorrect_message = HttpMessage( performative=HttpMessage.Performative.REQUEST, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=incoming_message.message_id, message_id=incoming_message.message_id + 1, method="post", url="/pets", version=incoming_message.version, headers=incoming_message.headers, body=b"Request body", ) incorrect_message.to = incoming_message.sender # the incorrect message cannot be sent into a dialogue, so this is omitted. response_envelope = Envelope( to=incorrect_message.to, sender=envelope.to, context=envelope.context, message=incorrect_message, ) with patch.object(self.http_connection.logger, "warning") as mock_logger: await self.http_connection.send(response_envelope) mock_logger.assert_any_call( f"Could not create dialogue for message={incorrect_message}" ) response = await asyncio.wait_for(request_task, timeout=10) assert ( response.status == 408 and response.reason == "Request Timeout" and await response.text() == "" ) @pytest.mark.asyncio async def test_late_message_get_timeout_error(self): """Test send get request w/ 200 response.""" self.http_connection.channel.timeout_window = 1 request_task = self.loop.create_task(self.request("get", "/pets")) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=10) assert envelope incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = dialogue.reply( target_message=incoming_message, performative=HttpMessage.Performative.RESPONSE, version=incoming_message.version, headers=incoming_message.headers, status_code=200, status_text="Success", body=b"Response body", ) response_envelope = Envelope( to=message.to, sender=envelope.to, context=envelope.context, message=message, ) await asyncio.sleep(1.5) with patch.object(self.http_connection.logger, "warning") as mock_logger: await self.http_connection.send(response_envelope) mock_logger.assert_any_call( RegexComparator( "Dropping message=.* for incomplete_dialogue_label=.* which has timed out." ) ) response = await asyncio.wait_for(request_task, timeout=10) assert ( response.status == 408 and response.reason == "Request Timeout" and await response.text() == "" ) @pytest.mark.asyncio async def test_post_201(self): """Test send get request w/ 200 response.""" request_task = self.loop.create_task( self.request( "post", "/pets", ) ) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = dialogue.reply( target_message=incoming_message, performative=HttpMessage.Performative.RESPONSE, version=incoming_message.version, status_code=201, status_text="Created", body=b"Response body", ) response_envelope = Envelope( to=message.to, sender=envelope.to, context=envelope.context, message=message, ) await self.http_connection.send(response_envelope) response = await asyncio.wait_for( request_task, timeout=20, ) assert ( response.status == 201 and response.reason == "Created" and await response.text() == "Response body" ) @pytest.mark.asyncio async def test_get_404(self): """Test send post request w/ 404 response.""" response = await self.request("get", "/url-non-exists") assert ( response.status == 404 and response.reason == "Request Not Found" and await response.text() == "" ) @pytest.mark.asyncio async def test_post_404(self): """Test send post request w/ 404 response.""" response = await self.request("get", "/url-non-exists", data="some data") assert ( response.status == 404 and response.reason == "Request Not Found" and await response.text() == "" ) @pytest.mark.asyncio async def test_get_408(self): """Test send post request w/ 404 response.""" await self.http_connection.connect() self.http_connection.channel.timeout_window = 0.1 with patch.object( self.http_connection.channel.logger, "warning" ) as mock_logger: response = await self.request("get", "/pets") mock_logger.assert_any_call( RegexComparator("Request timed out! Request=.*") ) assert ( response.status == 408 and response.reason == "Request Timeout" and await response.text() == "" ) @pytest.mark.asyncio async def test_post_408(self): """Test send post request w/ 404 response.""" self.http_connection.channel.timeout_window = 0.1 response = await self.request("post", "/pets", data="somedata") assert ( response.status == 408 and response.reason == "Request Timeout" and await response.text() == "" ) @pytest.mark.asyncio async def test_send_connection_drop(self): """Test unexpected response.""" message = HttpMessage( performative=HttpMessage.Performative.RESPONSE, dialogue_reference=("", ""), target=1, message_id=2, headers="", version="", status_code=200, status_text="Success", body=b"", ) message.to = str(HTTPServerConnection.connection_id) message.sender = self.target_skill_id envelope = Envelope( to=message.to, sender=message.sender, message=message, ) await self.http_connection.send(envelope) @pytest.mark.asyncio async def test_get_message_channel_not_connected(self): """Test error on channel get message if not connected.""" await self.http_connection.disconnect() with pytest.raises(ValueError): await self.http_connection.channel.get_message() @pytest.mark.asyncio async def test_fail_connect(self): """Test error on server connection.""" await self.http_connection.disconnect() with patch.object( self.http_connection.channel, "_start_http_server", side_effect=Exception("expected"), ): await self.http_connection.connect() assert not self.http_connection.is_connected @pytest.mark.asyncio async def test_server_error_on_send_response(self): """Test exception raised on response sending to the client.""" request_task = self.loop.create_task( self.request( "post", "/pets", ) ) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = dialogue.reply( target_message=incoming_message, performative=HttpMessage.Performative.RESPONSE, version=incoming_message.version, headers=incoming_message.headers, status_code=201, status_text="Created", body=b"Response body", ) response_envelope = Envelope( to=message.to, sender=envelope.to, context=envelope.context, message=message, ) with patch.object(Response, "from_message", side_effect=Exception("expected")): await self.http_connection.send(response_envelope) response = await asyncio.wait_for( request_task, timeout=20, ) assert response and response.status == 500 and response.reason == "Server Error" def teardown(self): """Teardown the test case.""" self.loop.run_until_complete(self.http_connection.disconnect()) self.http_connection.channel.timeout_window = self.original_timeout def test_bad_api_spec(): """Test error on apispec file is invalid.""" with pytest.raises(FileNotFoundError): APISpec("not_exist_file") def test_apispec_verify_if_no_validator_set(): """Test api spec ok if no spec file provided.""" assert APISpec().verify(Mock()) @pytest.mark.asyncio class TestHTTPSServer: """Tests for HTTPServer connection.""" async def request(self, method: str, path: str, **kwargs) -> ClientResponse: """ Make a http request. :param method: HTTP method: GET, POST etc :param path: path to request on server. full url constructed automatically :return: http response """ try: url = f"https://{self.host}:{self.port}{path}" sslcontext = ssl.create_default_context(cafile=self.ssl_cert) async with aiohttp.ClientSession() as session: async with session.request( method, url, **kwargs, ssl=sslcontext ) as resp: await resp.read() return resp except Exception: print_exc() raise def setup(self): """Initialise the test case.""" self.identity = Identity("name", address="my_key", public_key="my_public_key") self.agent_address = self.identity.address self.host = "localhost" self.port = get_unused_tcp_port() self.api_spec_path = os.path.join( ROOT_DIR, "tests", "data", "petstore_sim.yaml" ) self.connection_id = HTTPServerConnection.connection_id self.protocol_id = HttpMessage.protocol_id self.target_skill_id = "some_author/some_skill:0.1.0" self.ssl_cert = os.path.join(ROOT_DIR, "tests", "data", "certs", "server.crt") self.ssl_key = os.path.join(ROOT_DIR, "tests", "data", "certs", "server.key") self.configuration = ConnectionConfig( host=self.host, port=self.port, target_skill_id=self.target_skill_id, api_spec_path=self.api_spec_path, connection_id=HTTPServerConnection.connection_id, restricted_to_protocols={HttpMessage.protocol_id}, ssl_cert=self.ssl_cert, ssl_key=self.ssl_key, ) self.http_connection = HTTPServerConnection( configuration=self.configuration, data_dir=MagicMock(), identity=self.identity, ) self.loop = asyncio.get_event_loop() self.loop.run_until_complete(self.http_connection.connect()) self.connection_address = str(HTTPServerConnection.connection_id) self._dialogues = HttpDialogues(self.target_skill_id) self.original_timeout = self.http_connection.channel.timeout_window @pytest.mark.asyncio async def test_get_200(self): """Test send get request w/ 200 response.""" request_task = self.loop.create_task(self.request("get", "/pets")) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = dialogue.reply( target_message=incoming_message, performative=HttpMessage.Performative.RESPONSE, version=incoming_message.version, status_code=200, status_text="Success", body=b"Response body", ) response_envelope = Envelope( to=envelope.sender, sender=envelope.to, context=envelope.context, message=message, ) await self.http_connection.send(response_envelope) response = await asyncio.wait_for( request_task, timeout=20, ) assert ( response.status == 200 and response.reason == "Success" and await response.text() == "Response body" ) def _get_message_and_dialogue( self, envelope: Envelope ) -> Tuple[HttpMessage, HttpDialogue]: message = cast(HttpMessage, envelope.message) dialogue = cast(HttpDialogue, self._dialogues.update(message)) assert dialogue is not None return message, dialogue ================================================ FILE: tests/test_aea_core_packages/test_connections/test_http_server/test_http_server_and_client.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for the HTTP Client and Server connections together.""" import asyncio import email import logging import urllib from typing import Dict, Optional, cast from unittest.mock import MagicMock import pytest from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.http_client.connection import HTTPClientConnection from packages.fetchai.connections.http_server.connection import ( HTTPServerConnection, headers_to_string, ) from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage from tests.conftest import get_host, get_unused_tcp_port logger = logging.getLogger(__name__) SKILL_ID_STR = "some_author/some_skill:0.1.0" class TestClientServer: """Client-Server end-to-end test.""" def setup_server(self): """Set up server connection.""" self.server_agent_address = "server_agent_address" self.server_agent_public_key = "server_agent_public_key" self.server_agent_identity = Identity( "agent_running_server", address=self.server_agent_address, public_key=self.server_agent_public_key, ) self.host = get_host() self.port = get_unused_tcp_port() self.connection_id = HTTPServerConnection.connection_id self.protocol_id = HttpMessage.protocol_id self.target_skill_id = SKILL_ID_STR self.configuration = ConnectionConfig( host=self.host, port=self.port, target_skill_id=self.target_skill_id, api_spec_path=None, # do not filter on API spec connection_id=HTTPServerConnection.connection_id, ) self.server = HTTPServerConnection( configuration=self.configuration, data_dir=MagicMock(), identity=self.server_agent_identity, ) self.loop = asyncio.get_event_loop() self.loop.run_until_complete(self.server.connect()) # skill side dialogues def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return HttpDialogue.Role.SERVER self._skill_dialogues = HttpDialogues( SKILL_ID_STR, role_from_first_message=role_from_first_message ) def setup_client(self): """Set up client connection.""" self.client_agent_address = "client_agent_address" self.client_agent_public_key = "client_agent_public_key" self.client_agent_skill_id = "some/skill:0.1.0" self.client_agent_identity = Identity( "agent_running_client", address=self.client_agent_address, public_key=self.client_agent_public_key, ) configuration = ConnectionConfig( host="localhost", port="8888", # TODO: remove host/port for client? connection_id=HTTPClientConnection.connection_id, ) self.client = HTTPClientConnection( configuration=configuration, data_dir=MagicMock(), identity=self.client_agent_identity, ) self.loop.run_until_complete(self.client.connect()) # skill side dialogues def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return HttpDialogue.Role.CLIENT self._client_dialogues = HttpDialogues( self.client_agent_skill_id, role_from_first_message=role_from_first_message ) def setup(self): """Set up test case.""" self.setup_server() self.setup_client() def _make_request( self, path: str, method: str = "get", headers: Optional[Dict] = None, body: bytes = b"", ) -> Envelope: """Make request envelope.""" request_http_message, _ = self._client_dialogues.create( counterparty=str(HTTPClientConnection.connection_id), performative=HttpMessage.Performative.REQUEST, method=method, url=f"http://{self.host}:{self.port}{path}", headers=headers_to_string(headers) if headers else "", version="", body=b"", ) request_envelope = Envelope( to=request_http_message.to, sender=request_http_message.sender, message=request_http_message, ) return request_envelope def _make_response( self, request_envelope: Envelope, status_code: int = 200, status_text: str = "" ) -> Envelope: """Make response envelope.""" incoming_message = cast(HttpMessage, request_envelope.message) dialogue = self._skill_dialogues.update(incoming_message) assert dialogue is not None message = dialogue.reply( target_message=incoming_message, performative=HttpMessage.Performative.RESPONSE, version=incoming_message.version, headers=incoming_message.headers, status_code=status_code, status_text=status_text, body=incoming_message.body, ) response_envelope = Envelope( to=message.to, sender=message.sender, context=request_envelope.context, message=message, ) return response_envelope @pytest.mark.asyncio async def test_post_with_payload(self): """Test client and server with post request.""" initial_request = self._make_request("/test", "POST", body=b"1234567890") await self.client.send(initial_request) request = await asyncio.wait_for(self.server.receive(), timeout=5) # this is "inside" the server agent initial_response = self._make_response(request) await self.server.send(initial_response) response = await asyncio.wait_for(self.client.receive(), timeout=5) assert ( cast(HttpMessage, initial_request.message).body == cast(HttpMessage, response.message).body ) assert ( initial_request.message.dialogue_reference[0] == response.message.dialogue_reference[0] ) @pytest.mark.asyncio async def test_get_with_query(self): """Test client and server with url query.""" query = {"key": "value"} path = "/test?{}".format(urllib.parse.urlencode(query)) initial_request = self._make_request(path, "GET") await self.client.send(initial_request) request = await asyncio.wait_for(self.server.receive(), timeout=5) # this is "inside" the server agent parsed_query = dict( urllib.parse.parse_qsl( urllib.parse.urlparse(cast(HttpMessage, request.message).url).query ) ) assert parsed_query == query initial_response = self._make_response(request) await self.server.send(initial_response) response = await asyncio.wait_for(self.client.receive(), timeout=5) assert ( initial_request.message.dialogue_reference[0] == response.message.dialogue_reference[0] ) @pytest.mark.asyncio async def test_headers(self): """Test client and server with url query.""" headers = {"key1": "value1", "key2": "value2"} path = "/test" initial_request = self._make_request(path, "GET", headers=headers) await self.client.send(initial_request) request = await asyncio.wait_for(self.server.receive(), timeout=5) parsed_headers = dict( email.message_from_string( cast(HttpMessage, request.message).headers ).items() ) assert parsed_headers.items() >= headers.items() initial_response = self._make_response(request) await self.server.send(initial_response) response = await asyncio.wait_for(self.client.receive(), timeout=5) parsed_headers = dict( email.message_from_string( cast(HttpMessage, response.message).headers ).items() ) assert parsed_headers.items() >= headers.items() assert ( initial_request.message.dialogue_reference[0] == response.message.dialogue_reference[0] ) def teardown(self): """Tear down testcase.""" self.loop.run_until_complete(self.client.disconnect()) self.loop.run_until_complete(self.server.disconnect()) ================================================ FILE: tests/test_aea_core_packages/test_connections/test_ledger/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for the ledger API connection module, plus some utils.""" ================================================ FILE: tests/test_aea_core_packages/test_connections/test_ledger/test_contract_api.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the ledger API connection for the contract APIs.""" import asyncio import logging import unittest.mock from typing import cast from unittest.mock import MagicMock, patch import pytest from aea_ledger_ethereum import EthereumCrypto from aea.common import Address from aea.helpers.transaction.base import RawMessage, RawTransaction, State from aea.mail.base import Envelope from aea.multiplexer import MultiplexerStatus from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue from packages.fetchai.connections.ledger.contract_dispatcher import ( ContractApiRequestDispatcher, ) from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID as ERC1155_PUBLIC_ID from packages.fetchai.protocols.contract_api.dialogues import ContractApiDialogue from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogues as BaseContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from tests.conftest import ETHEREUM_ADDRESS_ONE SOME_SKILL_ID = "some/skill:0.1.0" class ContractApiDialogues(BaseContractApiDialogues): """This class keeps track of all contract_api dialogues.""" def __init__(self, self_address: str) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT BaseContractApiDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_erc1155_get_deploy_transaction(erc1155_contract, ledger_apis_connection): """Test get state with contract erc1155.""" contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) request, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs({"deployer_address": ETHEREUM_ADDRESS_ONE}), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == ContractApiMessage response_message = cast(ContractApiMessage, response.message) assert ( response_message.performative == ContractApiMessage.Performative.RAW_TRANSACTION ), "Error: {}".format(response_message.message) response_dialogue = contract_api_dialogues.update(response_message) assert response_dialogue == contract_api_dialogue assert type(response_message.raw_transaction) == RawTransaction assert response_message.raw_transaction.ledger_id == EthereumCrypto.identifier assert len(response.message.raw_transaction.body) == 6 assert len(response.message.raw_transaction.body["data"]) > 0 @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_erc1155_get_raw_transaction( erc1155_contract, ledger_apis_connection, update_default_ethereum_ledger_api, ganache, ): """Test get state with contract erc1155.""" contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) request, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), contract_address=contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": ETHEREUM_ADDRESS_ONE, "token_ids": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], } ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == ContractApiMessage response_message = cast(ContractApiMessage, response.message) assert ( response_message.performative == ContractApiMessage.Performative.RAW_TRANSACTION ), "Error: {}".format(response_message.message) response_dialogue = contract_api_dialogues.update(response_message) assert response_dialogue == contract_api_dialogue assert type(response_message.raw_transaction) == RawTransaction assert response_message.raw_transaction.ledger_id == EthereumCrypto.identifier assert len(response.message.raw_transaction.body) == 7 assert len(response.message.raw_transaction.body["data"]) > 0 @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_erc1155_get_raw_message(erc1155_contract, ledger_apis_connection): """Test get state with contract erc1155.""" contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) request, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), contract_address=contract_address, callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( { "from_address": ETHEREUM_ADDRESS_ONE, "to_address": ETHEREUM_ADDRESS_ONE, "token_id": 1, "from_supply": 10, "to_supply": 0, "value": 0, "trade_nonce": 1, } ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == ContractApiMessage response_message = cast(ContractApiMessage, response.message) assert ( response_message.performative == ContractApiMessage.Performative.RAW_MESSAGE ), "Error: {}".format(response_message.message) response_dialogue = contract_api_dialogues.update(response_message) assert response_dialogue == contract_api_dialogue assert type(response_message.raw_message) == RawMessage assert response_message.raw_message.ledger_id == EthereumCrypto.identifier assert type(response.message.raw_message.body) == bytes @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_erc1155_get_state(erc1155_contract, ledger_apis_connection): """Test get state with contract erc1155.""" contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) token_id = 1 request, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), contract_address=contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( {"agent_address": ETHEREUM_ADDRESS_ONE, "token_id": token_id} ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == ContractApiMessage response_message = cast(ContractApiMessage, response.message) assert ( response_message.performative == ContractApiMessage.Performative.STATE ), "Error: {}".format(response_message.message) response_dialogue = contract_api_dialogues.update(response_message) assert response_dialogue == contract_api_dialogue assert type(response_message.state) == State assert response_message.state.ledger_id == EthereumCrypto.identifier result = response_message.state.body.get("balance", None) expected_result = {token_id: 0} assert result is not None and result == expected_result @pytest.mark.asyncio async def test_run_async(): """Test run async error handled.""" # for pydocstyle def _raise(): raise Exception("Expected") contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) request, dialogue = contract_api_dialogues.create( counterparty="str(ledger_apis_connection.connection_id)", performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), contract_address="test addr", callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": "test_addr", "token_ids": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], } ), ) api = None msg = await ContractApiRequestDispatcher(MultiplexerStatus()).run_async( _raise, api, request, dialogue ) assert msg.performative == ContractApiMessage.Performative.ERROR @pytest.mark.asyncio async def test_get_handler(): """Test failed to get handler.""" with pytest.raises(Exception, match="Performative not recognized."): ContractApiRequestDispatcher(MultiplexerStatus()).get_handler( ContractApiMessage.Performative.ERROR ) @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_callable_wrong_number_of_arguments_api_and_contract_address( erc1155_contract, ledger_apis_connection ): """ Test a contract callable with wrong number of arguments. Test the case of either GET_STATE, GET_RAW_MESSAGE or GET_RAW_TRANSACTION. """ contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) token_id = 1 request, _ = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), contract_address=contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( {"agent_address": ETHEREUM_ADDRESS_ONE, "token_id": token_id} ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) with unittest.mock.patch( "inspect.getfullargspec", return_value=unittest.mock.MagicMock(args=[None]) ): with unittest.mock.patch.object( ledger_apis_connection._logger, "error" ) as mock_logger: await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() mock_logger.assert_any_call( "Exception during contract request: Expected two or more positional arguments, got 1" ) assert ( response.message.performative == ContractApiMessage.Performative.ERROR ) assert ( response.message.message == "Expected two or more positional arguments, got 1" ) @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_callable_wrong_number_of_arguments_apis( erc1155_contract, ledger_apis_connection ): """ Test a contract callable with wrong number of arguments. Test the case of either GET_DEPLOY_TRANSACTION. """ contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) request, _ = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs({}), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) with unittest.mock.patch( "inspect.getfullargspec", return_value=unittest.mock.MagicMock(args=[]) ): with unittest.mock.patch.object( ledger_apis_connection._contract_dispatcher, "_call_stub", return_value=None ): with unittest.mock.patch.object( ledger_apis_connection._contract_dispatcher.logger, "error" ) as mock_logger: await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() mock_logger.assert_any_call( "Exception during contract request: Expected one or more positional arguments, got 0" ) assert ( response.message.performative == ContractApiMessage.Performative.ERROR ) assert ( response.message.message == "Expected one or more positional arguments, got 0" ) @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_callable_wrong_number_of_arguments_apis_method_call( erc1155_contract, ledger_apis_connection, caplog ): """ Test a contract callable with wrong number of arguments. Test the case of either GET_DEPLOY_TRANSACTION. """ contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) request, _ = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs({}), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) with unittest.mock.patch.object( ledger_apis_connection._contract_dispatcher, "_call_stub", return_value=None ): with caplog.at_level(logging.DEBUG, "aea.packages.fetchai.connections.ledger"): await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) assert ( "An error occurred while processing the contract api request:" in caplog.text ) assert ( "get_deploy_transaction() missing 1 required positional argument: 'deployer_address" in caplog.text ) @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_callable_generic_error(erc1155_contract, ledger_apis_connection): """Test error messages when an exception is raised while processing the request.""" contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) token_id = 1 request, _ = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), contract_address=contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( {"agent_address": ETHEREUM_ADDRESS_ONE, "token_id": token_id} ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) with unittest.mock.patch( "inspect.getfullargspec", side_effect=Exception("Generic error") ): with unittest.mock.patch.object( ledger_apis_connection._logger, "error" ) as mock_logger: await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() mock_logger.assert_any_call( "An error occurred while processing the contract api request: 'Generic error'." ) assert ( response.message.performative == ContractApiMessage.Performative.ERROR ) assert response.message.message == "Generic error" @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio async def test_callable_cannot_find(erc1155_contract, ledger_apis_connection, caplog): """Test error messages when an exception is raised while processing the request.""" contract, contract_address = erc1155_contract contract_api_dialogues = ContractApiDialogues(SOME_SKILL_ID) token_id = 1 request, _ = contract_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=EthereumCrypto.identifier, contract_id=str(ERC1155_PUBLIC_ID), contract_address=contract_address, callable="unknown_callable", kwargs=ContractApiMessage.Kwargs( {"agent_address": ETHEREUM_ADDRESS_ONE, "token_id": token_id} ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) with caplog.at_level(logging.DEBUG, "aea.packages.fetchai.connections.ledger"): await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) assert f"Cannot find {request.callable} in contract" in caplog.text def test_build_response_fails_on_bad_data_type(): """Test internal build_response functions for data type check.""" dispatcher = ContractApiRequestDispatcher(MagicMock()) with patch.object( dispatcher, "dispatch_request", lambda x, x1, x2, fn: fn(data=b"some_data", dialogue=MagicMock()), ), pytest.raises( ValueError, match=r"Invalid state type, got=, expected=typing.Dict" ): dispatcher.get_state(MagicMock(), MagicMock(), MagicMock()) with patch.object( dispatcher, "dispatch_request", lambda x, x1, x2, fn: fn(rm=12, dialogue=MagicMock()), ), pytest.raises(ValueError, match=r"Invalid message type"): dispatcher.get_raw_message(MagicMock(), MagicMock(), MagicMock()) with patch.object( dispatcher, "dispatch_request", lambda x, x1, x2, fn: fn(tx=b"some_data", dialogue=MagicMock()), ): with pytest.raises( ValueError, match=r"Invalid transaction type, got=, expected=typing.Dict", ): dispatcher.get_deploy_transaction(MagicMock(), MagicMock(), MagicMock()) with pytest.raises( ValueError, match=r"Invalid transaction type, got=, expected=typing.Dict", ): dispatcher.get_raw_transaction(MagicMock(), MagicMock(), MagicMock()) ================================================ FILE: tests/test_aea_core_packages/test_connections/test_ledger/test_ledger_api.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the ledger API connection module.""" import asyncio import logging from typing import cast from unittest.mock import Mock, patch import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates from aea.crypto.ledger_apis import LedgerApis from aea.crypto.registries import make_crypto, make_ledger_api from aea.helpers.async_utils import AsyncState from aea.helpers.transaction.base import ( RawTransaction, SignedTransaction, Terms, TransactionDigest, TransactionReceipt, ) from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.ledger.connection import LedgerConnection from packages.fetchai.connections.ledger.ledger_dispatcher import ( LedgerApiRequestDispatcher, ) from packages.fetchai.protocols.ledger_api.custom_types import Kwargs from packages.fetchai.protocols.ledger_api.dialogues import LedgerApiDialogue from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogues as BaseLedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from tests.conftest import ( ETHEREUM_PRIVATE_KEY_PATH, FETCHAI_ADDRESS_ONE, FETCHAI_TESTNET_CONFIG, ) logger = logging.getLogger(__name__) ledger_ids = pytest.mark.parametrize( "ledger_id,address", [ (FetchAICrypto.identifier, FETCHAI_ADDRESS_ONE), (EthereumCrypto.identifier, EthereumCrypto(ETHEREUM_PRIVATE_KEY_PATH).address), ], ) gas_price_strategies = pytest.mark.parametrize( "gas_price_strategy", [None, "average"], ) SOME_SKILL_ID = "some/skill:0.1.0" class LedgerApiDialogues(BaseLedgerApiDialogues): """The dialogues class keeps track of all ledger_api dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return LedgerApiDialogue.Role.AGENT BaseLedgerApiDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio @ledger_ids async def test_get_balance( ledger_id, address, ledger_apis_connection: Connection, update_default_ethereum_ledger_api, ethereum_testnet_config, ganache, ): """Test get balance.""" import aea # noqa # to load registries if ledger_id == FetchAICrypto.identifier: config = FETCHAI_TESTNET_CONFIG else: config = ethereum_testnet_config ledger_api_dialogues = LedgerApiDialogues(SOME_SKILL_ID) request, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ledger_id, address=address, ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == LedgerApiMessage response_msg = cast(LedgerApiMessage, response.message) response_dialogue = ledger_api_dialogues.update(response_msg) assert response_dialogue == ledger_api_dialogue assert response_msg.performative == LedgerApiMessage.Performative.BALANCE actual_balance_amount = response_msg.balance expected_balance_amount = make_ledger_api(ledger_id, **config).get_balance(address) assert actual_balance_amount == expected_balance_amount @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio @ledger_ids async def test_get_state( ledger_id, address, ledger_apis_connection: Connection, update_default_ethereum_ledger_api, ethereum_testnet_config, ganache, ): """Test get state.""" import aea # noqa # to load registries if ledger_id == FetchAICrypto.identifier: config = FETCHAI_TESTNET_CONFIG else: config = ethereum_testnet_config if "ethereum" in ledger_id: callable_name = "getBlock" else: callable_name = "blocks" args = ("latest",) kwargs = Kwargs({}) ledger_api_dialogues = LedgerApiDialogues(SOME_SKILL_ID) request, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=LedgerApiMessage.Performative.GET_STATE, ledger_id=ledger_id, callable=callable_name, args=args, kwargs=kwargs, ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == LedgerApiMessage response_msg = cast(LedgerApiMessage, response.message) response_dialogue = ledger_api_dialogues.update(response_msg) assert response_dialogue == ledger_api_dialogue assert ( response_msg.performative == LedgerApiMessage.Performative.STATE ), response_msg actual_block = response_msg.state.body expected_block = make_ledger_api(ledger_id, **config).get_state( callable_name, *args ) assert actual_block == expected_block @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio @gas_price_strategies async def test_send_signed_transaction_ethereum( gas_price_strategy, ledger_apis_connection: Connection, update_default_ethereum_ledger_api, ganache, ): """Test send signed transaction with Ethereum APIs.""" import aea # noqa # to load registries crypto1 = make_crypto( EthereumCrypto.identifier, private_key_path=ETHEREUM_PRIVATE_KEY_PATH ) crypto2 = make_crypto(EthereumCrypto.identifier) ledger_api_dialogues = LedgerApiDialogues(SOME_SKILL_ID) amount = 40000 fee = 30000 request, ledger_api_dialogue = ledger_api_dialogues.create( counterparty=str(ledger_apis_connection.connection_id), performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, terms=Terms( ledger_id=EthereumCrypto.identifier, sender_address=crypto1.address, counterparty_address=crypto2.address, amount_by_currency_id={"ETH": -amount}, quantities_by_good_id={"some_service_id": 1}, is_sender_payable_tx_fee=True, nonce="", fee_by_currency_id={"ETH": fee}, chain_id=3, gas_price_strategy=gas_price_strategy, ), ) request = cast(LedgerApiMessage, request) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == LedgerApiMessage response_message = cast(LedgerApiMessage, response.message) assert ( response_message.performative == LedgerApiMessage.Performative.RAW_TRANSACTION ) response_dialogue = ledger_api_dialogues.update(response_message) assert response_dialogue == ledger_api_dialogue assert type(response_message.raw_transaction) == RawTransaction assert response_message.raw_transaction.ledger_id == request.terms.ledger_id signed_transaction = crypto1.sign_transaction(response_message.raw_transaction.body) request = cast( LedgerApiMessage, ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, target_message=response_message, signed_transaction=SignedTransaction( EthereumCrypto.identifier, signed_transaction ), ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == LedgerApiMessage response_message = cast(LedgerApiMessage, response.message) assert ( response_message.performative != LedgerApiMessage.Performative.ERROR ), f"Received error: {response_message.message}" assert ( response_message.performative == LedgerApiMessage.Performative.TRANSACTION_DIGEST ) response_dialogue = ledger_api_dialogues.update(response_message) assert response_dialogue == ledger_api_dialogue assert type(response_message.transaction_digest) == TransactionDigest assert type(response_message.transaction_digest.body) == str assert ( response_message.transaction_digest.ledger_id == request.signed_transaction.ledger_id ) assert type(response_message.transaction_digest.body.startswith("0x")) request = cast( LedgerApiMessage, ledger_api_dialogue.reply( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target_message=response_message, transaction_digest=response_message.transaction_digest, ), ) envelope = Envelope( to=request.to, sender=request.sender, message=request, ) await ledger_apis_connection.send(envelope) await asyncio.sleep(0.01) response = await ledger_apis_connection.receive() assert response is not None assert type(response.message) == LedgerApiMessage response_message = cast(LedgerApiMessage, response.message) assert ( response_message.performative == LedgerApiMessage.Performative.TRANSACTION_RECEIPT ) response_dialogue = ledger_api_dialogues.update(response_message) assert response_dialogue == ledger_api_dialogue assert type(response_message.transaction_receipt) == TransactionReceipt assert response_message.transaction_receipt.receipt is not None assert response_message.transaction_receipt.transaction is not None assert ( response_message.transaction_receipt.ledger_id == request.transaction_digest.ledger_id ) assert LedgerApis.is_transaction_settled( response_message.transaction_receipt.ledger_id, response_message.transaction_receipt.receipt, ), "Transaction not settled." @pytest.mark.asyncio async def test_unsupported_protocol(ledger_apis_connection: LedgerConnection): """Test fail on protocol not supported.""" envelope = Envelope( to=str(ledger_apis_connection.connection_id), sender="test/skill:0.1.0", protocol_specification_id=PublicId.from_str("author/package_name:0.1.0"), message=b"message", ) with pytest.raises(ValueError): ledger_apis_connection._schedule_request(envelope) @pytest.mark.asyncio async def test_new_message_wait_flag(ledger_apis_connection: LedgerConnection): """Test wait for new message.""" task = asyncio.ensure_future(ledger_apis_connection.receive()) await asyncio.sleep(0.1) assert not task.done() task.cancel() @pytest.mark.asyncio async def test_no_balance(): """Test no balance.""" dispatcher = LedgerApiRequestDispatcher(AsyncState()) mock_api = Mock() message = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_BALANCE, dialogue_reference=dispatcher.dialogues.new_self_initiated_dialogue_reference(), ledger_id=EthereumCrypto.identifier, address="test", ) message.to = dispatcher.dialogues.self_address message.sender = "test" dialogue = dispatcher.dialogues.update(message) assert dialogue is not None mock_api.get_balance.return_value = None msg = dispatcher.get_balance(mock_api, message, dialogue) assert msg.performative == LedgerApiMessage.Performative.ERROR @pytest.mark.asyncio async def test_no_raw_tx(): """Test no raw tx returned.""" dispatcher = LedgerApiRequestDispatcher(AsyncState()) mock_api = Mock() message = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=dispatcher.dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=EthereumCrypto.identifier, sender_address="1111", counterparty_address="22222", amount_by_currency_id={"ETH": -1}, quantities_by_good_id={"some_service_id": 1}, is_sender_payable_tx_fee=True, nonce="", fee_by_currency_id={"ETH": 10}, chain_id=3, ), ) message.to = dispatcher.dialogues.self_address message.sender = "test" dialogue = dispatcher.dialogues.update(message) assert dialogue is not None mock_api.get_transfer_transaction.return_value = None msg = dispatcher.get_raw_transaction(mock_api, message, dialogue) assert msg.performative == LedgerApiMessage.Performative.ERROR @pytest.mark.asyncio async def test_attempts_get_transaction_receipt(): """Test retry and sleep.""" dispatcher = LedgerApiRequestDispatcher(AsyncState(ConnectionStates.connected)) mock_api = Mock() message = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, dialogue_reference=dispatcher.dialogues.new_self_initiated_dialogue_reference(), transaction_digest=TransactionDigest("asdad", "sdfdsf"), ) message.to = dispatcher.dialogues.self_address message.sender = "test" dialogue = dispatcher.dialogues.update(message) assert dialogue is not None mock_api.get_transaction.return_value = None mock_api.is_transaction_settled.return_value = True with patch.object(dispatcher, "MAX_ATTEMPTS", 2): with patch.object(dispatcher, "TIMEOUT", 0.001): msg = dispatcher.get_transaction_receipt(mock_api, message, dialogue) assert msg.performative == LedgerApiMessage.Performative.ERROR ================================================ FILE: tests/test_aea_core_packages/test_contracts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/contracts dir.""" ================================================ FILE: tests/test_aea_core_packages/test_contracts/test_erc1155/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/contracts/erc1155 dir.""" ================================================ FILE: tests/test_aea_core_packages/test_contracts/test_erc1155/test_contract.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/contracts/erc1155 dir.""" import re import time from pathlib import Path from typing import cast from unittest import mock import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAIApi, FetchAICrypto from aea.common import JSONLike from aea.configurations.loader import ( ComponentType, ContractConfig, load_component_configuration, ) from aea.contracts.base import Contract, contract_registry from aea.crypto.base import Crypto, LedgerApi from aea.test_tools.test_contract import BaseContractTestCase from tests.conftest import ( ETHEREUM_ADDRESS_ONE, ETHEREUM_ADDRESS_TWO, ETHEREUM_PRIVATE_KEY_PATH, ETHEREUM_PRIVATE_KEY_TWO_PATH, ETHEREUM_TESTNET_CONFIG, FETCHAI_TESTNET_CONFIG, MAX_FLAKY_RERUNS, ROOT_DIR, UseGanache, ) @pytest.mark.ledger class TestERC1155ContractEthereum(BaseContractTestCase, UseGanache): """Test the ERC1155 contract on Ethereum.""" ledger_identifier = EthereumCrypto.identifier path_to_contract = Path(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155") @classmethod def setup(cls): """Setup.""" super().setup( ledger_config=ETHEREUM_TESTNET_CONFIG, deployer_private_key_path=ETHEREUM_PRIVATE_KEY_PATH, item_owner_private_key_path=ETHEREUM_PRIVATE_KEY_TWO_PATH, ) cls.token_ids_a = [ 340282366920938463463374607431768211456, 340282366920938463463374607431768211457, 340282366920938463463374607431768211458, 340282366920938463463374607431768211459, 340282366920938463463374607431768211460, 340282366920938463463374607431768211461, 340282366920938463463374607431768211462, 340282366920938463463374607431768211463, 340282366920938463463374607431768211464, 340282366920938463463374607431768211465, ] cls.token_id_b = 680564733841876926926749214863536422912 @classmethod def finish_contract_deployment(cls) -> str: """ Finish deploying contract. :return: contract address """ contract_address = cls.ledger_api.get_contract_address( cls.deployment_tx_receipt ) if contract_address is None: raise ValueError("Contract address not found!") # pragma: nocover return contract_address def test_generate_token_ids(self): """Test the generate_token_ids method of the ERC1155 contract.""" # setup nft_token_type = 1 nb_tokens = 2 expected_toke_ids = [ 340282366920938463463374607431768211456, 340282366920938463463374607431768211457, ] # operation actual_toke_ids = self.contract.generate_token_ids(nft_token_type, nb_tokens) # after assert actual_toke_ids == expected_toke_ids def test_generate_id(self): """Test the _generate_id method of the ERC1155 contract.""" # setup ft_token_type = 2 index = 0 expected_toke_id = 680564733841876926926749214863536422912 # operation actual_toke_id = self.contract._generate_id(index, ft_token_type) # after assert actual_toke_id == expected_toke_id def test_get_create_batch_transaction(self): """Test the get_create_batch_transaction method of the ERC1155 contract.""" # operation tx = self.contract.get_create_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, token_ids=self.token_ids_a, ) # after assert len(tx) == 7 assert all( key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to", "data"] ) self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) def test_get_create_single_transaction(self): """Test the get_create_single_transaction method of the ERC1155 contract.""" # operation tx = self.contract.get_create_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, token_id=self.token_id_b, ) # after assert len(tx) == 7 assert all( key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to", "data"] ) self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) def test_get_mint_batch_transaction(self): """Test the get_mint_batch_transaction method of the ERC1155 contract.""" # operation tx = self.contract.get_mint_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.item_owner_crypto.address, token_ids=self.token_ids_a, mint_quantities=[1] * len(self.token_ids_a), ) # after assert len(tx) == 7 assert all( key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to", "data"] ) self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) def test_validate_mint_quantities(self): """Test the validate_mint_quantities method of the ERC1155 contract.""" # Valid NFTs self.contract.validate_mint_quantities( token_ids=self.token_ids_a, mint_quantities=[1] * len(self.token_ids_a), ) # Valid FTs token_id = 680564733841876926926749214863536422912 mint_quantity = 1 self.contract.validate_mint_quantities( token_ids=[token_id], mint_quantities=[mint_quantity], ) # Invalid NFTs token_id = self.token_ids_a[0] mint_quantity = 2 with pytest.raises( ValueError, match=re.escape( f"Cannot mint NFT (token_id={token_id}) with mint_quantity more than 1 (found={mint_quantity})" ), ): self.contract.validate_mint_quantities( token_ids=[token_id], mint_quantities=[mint_quantity], ) # Invalid: neither NFT nor FT token_id = 1020847100762815390390123822295304634368 mint_quantity = 1 with pytest.raises( ValueError, match=re.escape( f"The token type must be 1 or 2. Found type=3 for token_id={token_id}" ), ): self.contract.validate_mint_quantities( token_ids=[token_id], mint_quantities=[mint_quantity], ) def test_decode_id(self): """Test the decode_id method of the ERC1155 contract.""" # FT expected_token_type = 2 token_id = 680564733841876926926749214863536422912 actual_token_type = self.contract.decode_id(token_id) assert actual_token_type == expected_token_type # NFT expected_token_type = 1 token_id = 340282366920938463463374607431768211456 actual_token_type = self.contract.decode_id(token_id) assert actual_token_type == expected_token_type def test_get_mint_single_transaction(self): """Test the get_mint_single_transaction method of the ERC1155 contract.""" # operation tx = self.contract.get_mint_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.item_owner_crypto.address, token_id=self.token_id_b, mint_quantity=1, ) # after assert len(tx) == 7 assert all( key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to", "data"] ) self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) def test_get_balance(self): """Test the get_balance method of the ERC1155 contract.""" # operation result = self.contract.get_balance( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.item_owner_crypto.address, token_id=self.token_id_b, ) # after assert "balance" in result assert result["balance"][self.token_id_b] == 0 def test_get_balances(self): """Test the get_balances method of the ERC1155 contract.""" # operation result = self.contract.get_balances( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.item_owner_crypto.address, token_ids=self.token_ids_a, ) # after assert "balances" in result assert all(result["balances"][token_id] == 0 for token_id in self.token_ids_a) def test_get_hash_single(self): """Test the get_hash_single method of the ERC1155 contract.""" # operation result = self.contract.get_hash_single( ledger_api=self.ledger_api, contract_address=self.contract_address, from_address=self.deployer_crypto.address, to_address=self.item_owner_crypto.address, token_id=self.token_id_b, from_supply=0, to_supply=10, value=1, trade_nonce=1, ) # after assert isinstance(result, bytes) def test_get_hash_batch(self): """Test the get_hash_batch method of the ERC1155 contract.""" # operation result = self.contract.get_hash_batch( ledger_api=self.ledger_api, contract_address=self.contract_address, from_address=self.deployer_crypto.address, to_address=self.item_owner_crypto.address, token_ids=self.token_ids_a, from_supplies=[0, 1, 0, 0, 1, 0, 0, 0, 0, 1], to_supplies=[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], value=1, trade_nonce=1, ) # after assert isinstance(result, bytes) def test_generate_trade_nonce(self): """Test the generate_trade_nonce method of the ERC1155 contract.""" # operation result = self.contract.generate_trade_nonce( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.item_owner_crypto.address, ) # after assert "trade_nonce" in result assert isinstance(result["trade_nonce"], int) @pytest.mark.integration def test_helper_methods_and_get_transactions(self): """Test helper methods and get transactions.""" expected_a = [ 340282366920938463463374607431768211456, 340282366920938463463374607431768211457, 340282366920938463463374607431768211458, 340282366920938463463374607431768211459, 340282366920938463463374607431768211460, 340282366920938463463374607431768211461, 340282366920938463463374607431768211462, 340282366920938463463374607431768211463, 340282366920938463463374607431768211464, 340282366920938463463374607431768211465, ] actual = self.contract.generate_token_ids(token_type=1, nb_tokens=10) assert expected_a == actual expected_b = [ 680564733841876926926749214863536422912, 680564733841876926926749214863536422913, ] actual = self.contract.generate_token_ids(token_type=2, nb_tokens=2) assert expected_b == actual tx = self.contract.get_deploy_transaction( ledger_api=self.ledger_api, deployer_address=ETHEREUM_ADDRESS_ONE ) assert len(tx) == 6 data = tx.pop("data") assert len(data) > 0 and data.startswith("0x") assert all( [key in tx for key in ["value", "from", "gas", "gasPrice", "nonce"]] ), "Error, found: {}".format(tx) tx = self.contract.get_create_batch_transaction( ledger_api=self.ledger_api, contract_address=ETHEREUM_ADDRESS_ONE, deployer_address=ETHEREUM_ADDRESS_ONE, token_ids=expected_a, ) assert len(tx) == 7 data = tx.pop("data") assert len(data) > 0 and data.startswith("0x") assert all( [ key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to"] ] ), "Error, found: {}".format(tx) tx = self.contract.get_create_single_transaction( ledger_api=self.ledger_api, contract_address=ETHEREUM_ADDRESS_ONE, deployer_address=ETHEREUM_ADDRESS_ONE, token_id=expected_b[0], ) assert len(tx) == 7 data = tx.pop("data") assert len(data) > 0 and data.startswith("0x") assert all( [ key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to"] ] ), "Error, found: {}".format(tx) mint_quantities = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] tx = self.contract.get_mint_batch_transaction( ledger_api=self.ledger_api, contract_address=ETHEREUM_ADDRESS_ONE, deployer_address=ETHEREUM_ADDRESS_ONE, recipient_address=ETHEREUM_ADDRESS_ONE, token_ids=expected_a, mint_quantities=mint_quantities, ) assert len(tx) == 7 data = tx.pop("data") assert len(data) > 0 and data.startswith("0x") assert all( [ key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to"] ] ), "Error, found: {}".format(tx) mint_quantity = 1 tx = self.contract.get_mint_single_transaction( ledger_api=self.ledger_api, contract_address=ETHEREUM_ADDRESS_ONE, deployer_address=ETHEREUM_ADDRESS_ONE, recipient_address=ETHEREUM_ADDRESS_ONE, token_id=expected_b[1], mint_quantity=mint_quantity, ) assert len(tx) == 7 data = tx.pop("data") assert len(data) > 0 and data.startswith("0x") assert all( [ key in tx for key in ["value", "chainId", "gas", "gasPrice", "nonce", "to"] ] ), "Error, found: {}".format(tx) @pytest.mark.integration def test_get_single_atomic_swap(self): """Test get single atomic swap.""" from_address = ETHEREUM_ADDRESS_ONE to_address = ETHEREUM_ADDRESS_TWO token_id = self.contract.generate_token_ids(token_type=2, nb_tokens=1)[0] from_supply = 0 to_supply = 10 value = 1 trade_nonce = 1 tx_hash = self.contract.get_hash_single( self.ledger_api, self.contract_address, from_address, to_address, token_id, from_supply, to_supply, value, trade_nonce, ) assert isinstance(tx_hash, bytes) signature = self.deployer_crypto.sign_message(tx_hash) tx = self.contract.get_atomic_swap_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, from_address=from_address, to_address=to_address, token_id=token_id, from_supply=from_supply, to_supply=to_supply, value=value, trade_nonce=trade_nonce, signature=signature, ) assert len(tx) == 8 data = tx.pop("data") assert len(data) > 0 and data.startswith("0x") assert all( [ key in tx for key in [ "value", "chainId", "gas", "gasPrice", "nonce", "to", "from", ] ] ), "Error, found: {}".format(tx) @pytest.mark.integration def test_get_batch_atomic_swap(self): """Test get batch atomic swap.""" from_address = ETHEREUM_ADDRESS_ONE to_address = ETHEREUM_ADDRESS_TWO token_ids = self.contract.generate_token_ids(token_type=2, nb_tokens=10) from_supplies = [0, 1, 0, 0, 1, 0, 0, 0, 0, 1] to_supplies = [0, 0, 0, 0, 0, 1, 0, 0, 0, 0] value = 1 trade_nonce = 1 tx_hash = self.contract.get_hash_batch( self.ledger_api, self.contract_address, from_address, to_address, token_ids, from_supplies, to_supplies, value, trade_nonce, ) assert isinstance(tx_hash, bytes) signature = self.deployer_crypto.sign_message(tx_hash) tx = self.contract.get_atomic_swap_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, from_address=from_address, to_address=to_address, token_ids=token_ids, from_supplies=from_supplies, to_supplies=to_supplies, value=value, trade_nonce=trade_nonce, signature=signature, ) assert len(tx) == 8 data = tx.pop("data") assert len(data) > 0 and data.startswith("0x") assert all( [ key in tx for key in [ "value", "chainId", "gas", "gasPrice", "nonce", "to", "from", ] ] ), "Error, found: {}".format(tx) @pytest.mark.integration def test_full(self): """Setup.""" # Test tokens IDs token_ids = self.contract.generate_token_ids(token_type=2, nb_tokens=10) # create tx = self.contract.get_create_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, token_ids=token_ids, ) tx_signed = self.deployer_crypto.sign_transaction(tx) tx_receipt = self.ledger_api.send_signed_transaction(tx_signed) time.sleep(1) receipt = self.ledger_api.get_transaction_receipt(tx_receipt) assert self.ledger_api.is_transaction_settled(receipt) mint_quantities = [10] * len(token_ids) # mint tx = self.contract.get_mint_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.deployer_crypto.address, token_ids=token_ids, mint_quantities=mint_quantities, ) tx_signed = self.deployer_crypto.sign_transaction(tx) tx_receipt = self.ledger_api.send_signed_transaction(tx_signed) time.sleep(1) receipt = self.ledger_api.get_transaction_receipt(tx_receipt) assert self.ledger_api.is_transaction_settled(receipt) tx = self.contract.get_mint_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.item_owner_crypto.address, token_ids=token_ids, mint_quantities=mint_quantities, ) tx_signed = self.deployer_crypto.sign_transaction(tx) tx_receipt = self.ledger_api.send_signed_transaction(tx_signed) time.sleep(1) receipt = self.ledger_api.get_transaction_receipt(tx_receipt) assert self.ledger_api.is_transaction_settled(receipt) #  batch trade from_address = self.deployer_crypto.address to_address = self.item_owner_crypto.address from_supplies = [0, 1, 0, 0, 1, 0, 0, 0, 0, 1] to_supplies = [0, 0, 0, 0, 0, 1, 0, 0, 0, 0] value = 0 trade_nonce = 1 tx_hash = self.contract.get_hash_batch( self.ledger_api, self.contract_address, from_address, to_address, token_ids, from_supplies, to_supplies, value, trade_nonce, ) signature = self.item_owner_crypto.sign_message( tx_hash, is_deprecated_mode=True ) tx = self.contract.get_atomic_swap_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, from_address=from_address, to_address=to_address, token_ids=token_ids, from_supplies=from_supplies, to_supplies=to_supplies, value=value, trade_nonce=trade_nonce, signature=signature, ) tx_signed = self.deployer_crypto.sign_transaction(tx) tx_receipt = self.ledger_api.send_signed_transaction(tx_signed) time.sleep(1) receipt = self.ledger_api.get_transaction_receipt(tx_receipt) assert self.ledger_api.is_transaction_settled(receipt) class TestCosmWasmContract(BaseContractTestCase): """Test the cosmwasm contract.""" ledger_identifier = FetchAICrypto.identifier path_to_contract = Path(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155") fund_from_faucet = True deploy_tx_fee = 10000000000000000 @classmethod def _deploy_contract( cls, contract: Contract, ledger_api: LedgerApi, deployer_crypto: Crypto, gas: int, ) -> JSONLike: """ Deploy contract on network. :param contract: the contract :param ledger_api: the ledger api :param deployer_crypto: the contract deployer crypto :param gas: the gas amount :return: the transaction receipt for initial transaction deployment """ tx = contract.get_deploy_transaction( ledger_api=ledger_api, deployer_address=deployer_crypto.address, gas=gas, tx_fee=cls.deploy_tx_fee, ) if tx is None: raise ValueError("Deploy transaction not found!") # pragma: nocover tx_receipt = cls.sign_send_confirm_receipt_multisig_transaction( tx, ledger_api, [deployer_crypto] ) return tx_receipt @classmethod def setup(cls): """Setup.""" # Test tokens IDs super().setup(ledger_config=FETCHAI_TESTNET_CONFIG, tx_fee=10000000000000000) cls.token_ids_a = [ 340282366920938463463374607431768211456, 340282366920938463463374607431768211457, 340282366920938463463374607431768211458, 340282366920938463463374607431768211459, 340282366920938463463374607431768211460, 340282366920938463463374607431768211461, 340282366920938463463374607431768211462, 340282366920938463463374607431768211463, 340282366920938463463374607431768211464, 340282366920938463463374607431768211465, ] cls.token_id_b = 680564733841876926926749214863536422912 @classmethod def finish_contract_deployment(cls) -> str: """ Finish deploying contract. :return: contract address """ code_id = cast(FetchAIApi, cls.ledger_api).get_code_id( cls.deployment_tx_receipt ) assert code_id is not None # Init contract tx = cls._contract.get_deploy_transaction( ledger_api=cls.ledger_api, deployer_address=cls.deployer_crypto.address, code_id=code_id, init_msg={}, amount=0, label="ERC1155", gas=1000000, tx_fee=5000000000000000, ) if tx is None: raise ValueError("Deploy transaction not found!") # pragma: nocover tx_receipt = cls.sign_send_confirm_receipt_multisig_transaction( tx, cls.ledger_api, [cls.deployer_crypto] ) contract_address = cls.ledger_api.get_contract_address(tx_receipt) if contract_address is None: raise ValueError("Contract address not found!") # pragma: nocover return contract_address @pytest.mark.integration @pytest.mark.ledger @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_create_and_mint_and_balances(self): """Test cosmwasm contract create, mint and balances functionalities.""" # Create single token tx = self.contract.get_create_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, token_id=self.token_id_b, tx_fee=1500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Create batch of tokens tx = self.contract.get_create_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, token_ids=self.token_ids_a, tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Mint single token tx = self.contract.get_mint_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.item_owner_crypto.address, token_id=self.token_id_b, mint_quantity=1, tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Get balance of single token res = self.contract.get_balance( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.item_owner_crypto.address, token_id=self.token_id_b, ) assert "balance" in res assert res["balance"][self.token_id_b] == 1 # Mint batch of tokens tx = self.contract.get_mint_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.item_owner_crypto.address, token_ids=self.token_ids_a, mint_quantities=[1] * len(self.token_ids_a), tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Get balances of multiple tokens res = self.contract.get_balances( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.item_owner_crypto.address, token_ids=self.token_ids_a, ) assert "balances" in res assert res["balances"] == {token_id: 1 for token_id in self.token_ids_a} @pytest.mark.integration @pytest.mark.ledger @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_cosmwasm_single_atomic_swap(self): """Test single atomic swap.""" # Create batch of tokens tx = self.contract.get_create_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, token_ids=self.token_ids_a, tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Mint single ERC1155 token a[0] to Deployer tx = self.contract.get_mint_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.deployer_crypto.address, token_id=self.token_ids_a[0], mint_quantity=1, tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Store balance of Deployer's native tokens before atomic swap original_deployer_balance = self.ledger_api.get_balance( self.deployer_crypto.address ) # Atomic swap # Send 1 ERC1155 token a[0] from Deployer to Item owner # Send 1 native token from Item owner to Deployer tx_fee = 7500000000000000 tx = self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address=self.contract_address, from_address=self.deployer_crypto.address, to_address=self.item_owner_crypto.address, token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=1, trade_nonce=0, from_pubkey=self.deployer_crypto.public_key, to_pubkey=self.item_owner_crypto.public_key, tx_fee=tx_fee, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto, self.item_owner_crypto] ) # Check Item owner's ERC1155 token balance result = self.contract.get_balance( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.item_owner_crypto.address, token_id=self.token_ids_a[0], ) assert "balance" in result assert result["balance"][self.token_ids_a[0]] == 1 # Check deployer's native token balance deployer_balance = self.ledger_api.get_balance(self.deployer_crypto.address) assert deployer_balance == original_deployer_balance + 1 - tx_fee # Other direction of atomic swap # Send 1 ERC1155 token a[0] from Item owner to Deployer # Send 1 native token from Item owner to Deployer tx = self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address=self.contract_address, from_address=self.deployer_crypto.address, to_address=self.item_owner_crypto.address, token_id=self.token_ids_a[0], from_supply=0, to_supply=1, value=1, trade_nonce=0, from_pubkey=self.deployer_crypto.public_key, to_pubkey=self.item_owner_crypto.public_key, tx_fee=tx_fee, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.item_owner_crypto] ) # Check Item owner's ERC1155 token balance result = self.contract.get_balance( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.deployer_crypto.address, token_id=self.token_ids_a[0], ) assert "balance" in result assert result["balance"][self.token_ids_a[0]] == 1 # Check deployer's native token balance deployer_balance = self.ledger_api.get_balance(self.deployer_crypto.address) assert deployer_balance == original_deployer_balance + 2 - tx_fee # Check invalid case with from_supply > 0 and to_supply > 0 with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address=self.contract_address, from_address=self.deployer_crypto.address, to_address=self.item_owner_crypto.address, token_id=self.token_ids_a[0], from_supply=1, to_supply=1, value=1, trade_nonce=0, from_pubkey=self.deployer_crypto.public_key, to_pubkey=self.item_owner_crypto.public_key, ) @pytest.mark.integration @pytest.mark.ledger @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_cosmwasm_batch_atomic_swap(self): """Test batch atomic swap.""" # Create batch of tokens tx = self.contract.get_create_batch_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, token_ids=self.token_ids_a, tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Mint single token a[0] to Deployer tx = self.contract.get_mint_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.deployer_crypto.address, token_id=self.token_ids_a[0], mint_quantity=1, tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Mint single token a[1] to Item owner tx = self.contract.get_mint_single_transaction( ledger_api=self.ledger_api, contract_address=self.contract_address, deployer_address=self.deployer_crypto.address, recipient_address=self.item_owner_crypto.address, token_id=self.token_ids_a[1], mint_quantity=1, tx_fee=2500000000000000, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto] ) # Store balance of Deployer's native tokens before atomic swap original_deployer_balance = self.ledger_api.get_balance( self.deployer_crypto.address ) # Atomic swap # Send 1 ERC1155 token a[0] from Deployer to Item owner # Send 1 ERC1155 token a[1] from Item owner to Deployer # Send 1 native token from Item owner to Deployer tx_fee = 7500000000000000 tx = self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address=self.contract_address, from_address=self.deployer_crypto.address, to_address=self.item_owner_crypto.address, token_ids=[self.token_ids_a[0], self.token_ids_a[1]], from_supplies=[1, 0], to_supplies=[0, 1], value=1, trade_nonce=0, from_pubkey=self.deployer_crypto.public_key, to_pubkey=self.item_owner_crypto.public_key, tx_fee=tx_fee, ) assert len(tx) == 2 self.sign_send_confirm_receipt_multisig_transaction( tx, self.ledger_api, [self.deployer_crypto, self.item_owner_crypto] ) # Check Item owner's ERC1155 token balance result = self.contract.get_balance( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.item_owner_crypto.address, token_id=self.token_ids_a[0], ) assert "balance" in result assert result["balance"][self.token_ids_a[0]] == 1 # Check Deployer's ERC1155 token balance result = self.contract.get_balance( ledger_api=self.ledger_api, contract_address=self.contract_address, agent_address=self.deployer_crypto.address, token_id=self.token_ids_a[1], ) assert "balance" in result assert result["balance"][self.token_ids_a[1]] == 1 # Check deployer's native token balance deployer_balance = self.ledger_api.get_balance(self.deployer_crypto.address) assert deployer_balance == original_deployer_balance + 1 - tx_fee class TestContractCommon: """Other tests for the contract.""" @classmethod def setup(cls): """Setup.""" # Register smart contract used for testing cls.path_to_contract = Path( ROOT_DIR, "packages", "fetchai", "contracts", "erc1155" ) # register contract configuration = cast( ContractConfig, load_component_configuration(ComponentType.CONTRACT, cls.path_to_contract), ) configuration._directory = ( # pylint: disable=protected-access cls.path_to_contract ) if str(configuration.public_id) not in contract_registry.specs: # load contract into sys modules Contract.from_config(configuration) cls.contract = contract_registry.make(str(configuration.public_id)) cls.token_ids_a = [ 340282366920938463463374607431768211456, ] # Create mock ledger with unknown identifier cls.ledger_api = mock.Mock() attrs = {"identifier": "dummy"} cls.ledger_api.configure_mock(**attrs) @pytest.mark.ledger def test_get_create_batch_transaction_wrong_identifier(self): """Test if get_create_batch_transaction with wrong api identifier fails.""" # Test if function is not implemented for unknown ledger with pytest.raises(NotImplementedError): self.contract.get_create_batch_transaction( ledger_api=self.ledger_api, contract_address="contract_address", deployer_address="address", token_ids=self.token_ids_a, ) @pytest.mark.ledger def test_get_create_single_transaction_wrong_identifier(self): """Test if get_create_single_transaction with wrong api identifier fails.""" # Test if function is not implemented for unknown ledger with pytest.raises(NotImplementedError): self.contract.get_create_single_transaction( ledger_api=self.ledger_api, contract_address="contract_address", deployer_address="address", token_id=self.token_ids_a[0], ) @pytest.mark.ledger def test_get_mint_batch_transaction_wrong_identifier(self): """Test if get_mint_batch_transaction with wrong api identifier fails.""" # Test if function is not implemented for unknown ledger with pytest.raises(NotImplementedError): self.contract.get_mint_batch_transaction( ledger_api=self.ledger_api, contract_address="contract_address", deployer_address="address", recipient_address="address", token_ids=self.token_ids_a, mint_quantities=[1], ) @pytest.mark.ledger def test_get_mint_single_transaction_wrong_identifier(self): """Test if get_mint_single_transaction with wrong api identifier fails.""" # Test if function is not implemented for unknown ledger with pytest.raises(NotImplementedError): self.contract.get_mint_single_transaction( ledger_api=self.ledger_api, contract_address="contract_address", deployer_address="address", recipient_address="address", token_id=self.token_ids_a[0], mint_quantity=1, ) @pytest.mark.ledger def test_get_balance_wrong_identifier(self): """Test if get_balance with wrong api identifier fails.""" # Test if function is not implemented for unknown ledger with pytest.raises(NotImplementedError): self.contract.get_balance( ledger_api=self.ledger_api, contract_address="contract_address", agent_address="address", token_id=self.token_ids_a[0], ) @pytest.mark.ledger def test_get_balance_wrong_query_res(self): """Test if get_balance with wrong api identifier fails.""" # Create mock fetchai ledger that returns None on execute_contract_query attrs = {"identifier": "fetchai", "execute_contract_query.return_value": None} self.ledger_api.configure_mock(**attrs) # Test if get balance returns ValueError when querying contract returns None with pytest.raises(ValueError): self.contract.get_balance( ledger_api=self.ledger_api, contract_address="contract_address", agent_address="address", token_id=self.token_ids_a[0], ) @pytest.mark.ledger def test_get_balances_wrong_query_res(self): """Test if get_balances with wrong api identifier fails.""" # Create mock fetchai ledger that returns None on execute_contract_query attrs = {"identifier": "fetchai", "execute_contract_query.return_value": None} self.ledger_api.configure_mock(**attrs) # Test if get balance returns ValueError when querying contract returns None with pytest.raises(ValueError): self.contract.get_balances( ledger_api=self.ledger_api, contract_address="contract_address", agent_address="address", token_ids=self.token_ids_a, ) @pytest.mark.ledger def test_get_hash_batch_not_same(self): """Test if get_hash_batch returns ValueError when on-chain hash is not same as computed hash.""" self.ledger_api.identifier = "ethereum" # Test if get hash returns ValueError when on chain hash is not same as computed hash with mock.patch.object(type(self.contract), "_get_hash_batch", new=mock.Mock()): with pytest.raises(ValueError): self.contract.get_hash_batch( ledger_api=self.ledger_api, contract_address="contract_address", from_address="address", to_address="address", token_ids=self.token_ids_a, from_supplies=[1], to_supplies=[0], value=123, trade_nonce=123, ) @pytest.mark.ledger def test_generate_trade_nonce_if_exist(self): """Test if generate_trade_nonce retries when nonce already exist.""" # Etherem ledger api mock self.ledger_api.identifier = "ethereum" # instance.functions.is_nonce_used(agent_address, trade_nonce).call() -> True, False is_nonce_used_mock = mock.Mock() is_nonce_used_mock.configure_mock(**{"call.side_effect": [True, False]}) # instance.functions.is_nonce_used(agent_address, trade_nonce) -> is_nonce_used_mock with call method instance_mock = mock.Mock() instance_mock.configure_mock( **{"functions.is_nonce_used.return_value": is_nonce_used_mock} ) # cls.get_instance(ledger_api, contract_address) -> instance_mock get_instance_mock = mock.Mock() get_instance_mock.configure_mock(**{"return_value": instance_mock}) # Patch get_instance method to return get_instance_mock which returns instance of instance_mock when called with mock.patch.object( type(self.contract), "get_instance", new=get_instance_mock ): self.contract.generate_trade_nonce( ledger_api=self.ledger_api, contract_address="contract_address", agent_address="address", ) # Check if is_nonce_used was called twice assert is_nonce_used_mock.call.call_count == 2 @pytest.mark.ledger def test_get_atomic_swap_single_transaction_eth_no_signature(self): """Test if get_atomic_swap_single_transaction returns RuntimeError if signature not present on Ethereum case.""" self.ledger_api.identifier = "ethereum" # Test if get_atomic_swap_single_transaction returns RuntimeError when signature is missing with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=1, trade_nonce=0, ) @pytest.mark.ledger def test_get_atomic_swap_single_transaction_eth_pubkeys(self): """Test if get_atomic_swap_single_transaction returns RuntimeError if pubkeys are present on Ethereum case.""" self.ledger_api.identifier = "ethereum" # Test if get_atomic_swap_single_transaction returns RuntimeError when pubkey is present with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=1, trade_nonce=0, signature="signature", from_pubkey="deadbeef", to_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_single_transaction_cosmos_signature(self): """Test if get_atomic_swap_single_transaction returns RuntimeError if signature is present on Cosmos/Fetch case.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction returns RuntimeError when signature is present with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=1, trade_nonce=0, signature="signature", from_pubkey="deadbeef", to_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_single_transaction_cosmos_one_pubkey_valid(self): """Test if get_atomic_swap_single_transaction allows one pubkey in case of only one direction of transfers.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction works with only to_pubkey tx = self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=0, to_supply=1, value=1, trade_nonce=0, to_pubkey="deadbeef", ) assert tx is not None @pytest.mark.ledger def test_get_atomic_swap_single_transaction_cosmos_one_pubkey_invalid(self): """Test if get_atomic_swap_single_transaction returns RuntimeError with missing from_pubkey.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with missing from_key with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=1, trade_nonce=0, to_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_single_transaction_cosmos_to_pubkey_missing(self): """Test if get_atomic_swap_single_transaction returns RuntimeError with missing to_pubkey.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with missing from_key with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=1, trade_nonce=0, from_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_eth_pubkeys(self): """Test if get_atomic_swap_batch_transaction returns RuntimeError if pubkeys are present on Ethereum case.""" self.ledger_api.identifier = "ethereum" # Test if get_atomic_swap_batch_transaction returns RuntimeError when pubkey is present with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[1], to_supplies=[0], value=1, trade_nonce=0, signature="signature", from_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_cosmos_signature(self): """Test if get_atomic_swap_batch_transaction returns RuntimeError if signature is present on Cosmos/Fetch case.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_batch_transaction returns RuntimeError when signature is present with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[1], to_supplies=[0], value=1, trade_nonce=0, signature="signature", from_pubkey="deadbeef", to_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_cosmos_one_pubkey_valid(self): """Test if get_atomic_swap_batch_transaction allows one pubkey in case of only one direction of transfers.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_batch_transaction works with only to_pubkey tx = self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[0], to_supplies=[1], value=1, trade_nonce=0, to_pubkey="deadbeef", ) assert tx is not None @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_cosmos_one_pubkey_invalid(self): """Test if get_atomic_swap_batch_transaction returns RuntimeError with missing from_pubkey.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_batch_transaction fails with missing from_key with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[1], to_supplies=[0], value=1, trade_nonce=0, to_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_ba_transaction_eth_no_signature(self): """Test if get_atomic_swap_single_transaction returns RuntimeError if signature not present on Ethereum case.""" self.ledger_api.identifier = "ethereum" # Test if get_atomic_swap_single_transaction returns RuntimeError when signature is missing with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[1], to_supplies=[0], value=1, trade_nonce=0, ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_cosmos_to_pubkey_missing(self): """Test if get_atomic_swap_batch_transaction returns RuntimeError with missing to_pubkey.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with all amounts to be zero with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[1], to_supplies=[0], value=1, trade_nonce=0, from_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_cosmos_to_pubkey_missing_no_from_pubkey_required( self, ): """Test if get_atomic_swap_batch_transaction returns RuntimeError with missing to_pubkey and from_pubkey not required.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with missing to_pubkey with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[0], to_supplies=[1], value=1, trade_nonce=0, ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_cosmos_from_pubkey_missing_no_to_pubkey_required( self, ): """Test if get_atomic_swap_batch_transaction returns RuntimeError with missing from_pubkey and to_pubkey not required.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with missing from_pubkey with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[1], to_supplies=[0], value=0, trade_nonce=0, ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_cosmos_from_pubkey_only(self): """Test if get_atomic_swap_batch_transaction returns Tx in case with only from_pubkey.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction works with only from_pubkey res = self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[1], to_supplies=[0], value=0, trade_nonce=0, from_pubkey="deadbeef", ) assert res is not None @pytest.mark.ledger def test_get_atomic_swap_single_transaction_amounts_missing(self): """Test if get_atomic_swap_single_transaction returns RuntimeError with missing amounts.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with all amounts to be zero with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=0, to_supply=0, value=0, trade_nonce=0, from_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_batch_transaction_amounts_missing(self): """Test if get_atomic_swap_batch_transaction returns RuntimeError with missing amounts.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with all amounts to be zero with pytest.raises(RuntimeError): self.contract.get_atomic_swap_batch_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_ids=[self.token_ids_a[0]], from_supplies=[0], to_supplies=[0], value=0, trade_nonce=0, from_pubkey="deadbeef", ) @pytest.mark.ledger def test_get_atomic_swap_single_transaction_cosmos_to_pubkey_missing_no_from_pubkey_required( self, ): """Test if get_atomic_swap_single_transaction returns RuntimeError with missing to_pubkey and from_pubkey not required.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with missing to_pubkey with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=0, to_supply=1, value=1, trade_nonce=0, ) @pytest.mark.ledger def test_get_atomic_swap_single_transaction_cosmos_from_pubkey_missing_no_to_pubkey_required( self, ): """Test if get_atomic_swap_single_transaction returns RuntimeError with missing from_pubkey and to_pubkey not required.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction fails with missing from_pubkey with pytest.raises(RuntimeError): self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=0, trade_nonce=0, ) @pytest.mark.ledger def test_get_atomic_swap_single_transaction_cosmos_from_pubkey_only(self): """Test if get_atomic_swap_single_transaction returns Tx in case with only from_pubkey.""" self.ledger_api.identifier = "fetchai" # Test if get_atomic_swap_single_transaction works with only from_pubkey res = self.contract.get_atomic_swap_single_transaction( self.ledger_api, contract_address="address", from_address="address", to_address="address", token_id=self.token_ids_a[0], from_supply=1, to_supply=0, value=0, trade_nonce=0, from_pubkey="deadbeef", ) assert res is not None ================================================ FILE: tests/test_aea_core_packages/test_skills_integration/test_echo.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the echo skill.""" import time from aea.common import Address from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.protocols.default.dialogues import DefaultDialogue from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.default.message import DefaultMessage class DefaultDialogues(BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class TestEchoSkill(AEATestCaseEmpty): """Test that echo skill works.""" capture_log = True def test_echo(self): """Run the echo skill sequence.""" self.generate_private_key() self.add_private_key() self.add_item("connection", "fetchai/stub:0.21.3") self.add_item("skill", "fetchai/echo:0.20.6") process = self.run_agent() is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" # add sending and receiving envelope from input/output files sender = "sender" default_dialogues = DefaultDialogues(sender) message_content = b"hello" message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), content=message_content, ) sent_envelope = Envelope( to=self.agent_name, sender=sender, message=message, ) self.send_envelope_to_agent(sent_envelope, self.agent_name) time.sleep(2.0) received_envelope = self.read_envelope_from_agent(self.agent_name) assert sent_envelope.sender == received_envelope.to assert ( sent_envelope.protocol_specification_id == received_envelope.protocol_specification_id ) msg = DefaultMessage.serializer.decode(received_envelope.message) assert sent_envelope.message.content == msg.content check_strings = ( "Echo Handler: setup method called.", "Echo Behaviour: setup method called.", "Echo Behaviour: act method called.", "content={}".format(message_content), ) missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) assert ( self.is_successfully_terminated() ), "Echo agent wasn't successfully terminated." ================================================ FILE: tests/test_aea_core_packages/test_skills_integration/test_generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the generic buyer and seller skills.""" from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, wait_for_localhost_ports_to_close, ) @pytest.mark.integration class TestGenericSkills(AEATestCaseManyFlaky): """Test that generic skills work.""" capture_log = True @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_generic(self, pytestconfig): """Run the generic skills sequence.""" seller_aea_name = "my_generic_seller" buyer_aea_name = "my_generic_buyer" self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # prepare seller agent self.set_agent_context(seller_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_seller:0.28.6") setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" self.set_config(setting_path, False, "bool") # replace location setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # prepare buyer agent self.set_agent_context(buyer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_buyer:0.27.6") setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # make runable: setting_path = "vendor.fetchai.skills.generic_buyer.is_abstract" self.set_config(setting_path, False, "bool") # replace location setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # run AEAs self.set_agent_context(seller_aea_name) seller_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) self.set_agent_context(buyer_aea_name) buyer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) self.terminate_agents(seller_aea_process, buyer_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) @pytest.mark.sync @pytest.mark.integration class TestGenericSkillsFetchaiLedger(AEATestCaseManyFlaky): """Test that generic skills work.""" capture_log = True @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_generic(self, pytestconfig): """Run the generic skills sequence.""" seller_aea_name = "my_generic_seller" buyer_aea_name = "my_generic_buyer" self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # prepare seller agent self.set_agent_context(seller_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_seller:0.28.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/generic_seller:0.29.5", seller_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" self.set_config(setting_path, False, "bool") # replace location setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # prepare buyer agent self.set_agent_context(buyer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_buyer:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/generic_buyer:0.30.5", buyer_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) setting_path = "vendor.fetchai.skills.generic_buyer.is_abstract" self.set_config(setting_path, False, "bool") self.set_config( "vendor.fetchai.skills.generic_buyer.models.strategy.args.max_tx_fee", 7750000000000000, ) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # run AEAs self.set_agent_context(seller_aea_name) seller_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) self.set_agent_context(buyer_aea_name) buyer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "checking whether transaction=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=120, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "requesting transfer transaction from ledger api for message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "transaction confirmed, informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) self.terminate_agents(seller_aea_process, buyer_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_aea_core_packages/test_skills_integration/test_hello_world.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the hello_world skill.""" from aea.test_tools.test_cases import AEATestCaseEmpty class TestHelloWorldSkill(AEATestCaseEmpty): """Test that hello_world skill works.""" capture_log = True def test_hello_world(self): """Run the hello_world skill sequence.""" self.generate_private_key() self.add_private_key() self.add_item("skill", "fetchai/hello_world:0.1.5") process = self.run_agent() is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" check_strings = ("Hello World!",) missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) ================================================ FILE: tests/test_aea_core_packages/test_skills_integration/test_http_echo.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the echo skill.""" from pathlib import Path from aea.helpers import http_requests as requests from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import ROOT_DIR API_SPEC_PATH = str(Path(ROOT_DIR, "examples", "http_ex", "petstore.yaml").absolute()) class TestHttpEchoSkill(AEATestCaseEmpty): """Test that http echo skill works.""" capture_log = True def test_echo(self): """Run the echo skill sequence.""" self.generate_private_key() self.add_private_key() self.add_item("connection", "fetchai/http_server:0.23.6") self.add_item("skill", "fetchai/http_echo:0.21.6") self.set_config("agent.default_connection", "fetchai/http_server:0.23.6") self.set_config( "vendor.fetchai.connections.http_server.config.target_skill_id", "fetchai/http_echo:0.21.6", ) self.set_config( "vendor.fetchai.connections.http_server.config.api_spec_path", API_SPEC_PATH ) self.run_install() process = self.run_agent() is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" # add sending and receiving envelope from input/output files response = requests.get("http://127.0.0.1:8000") assert response.status_code == 404, "Failed to receive not found" # we receive a not found since the path is not available in the api spec response = requests.get("http://127.0.0.1:8000/pets") assert response.status_code == 200, "Failed to receive ok" assert ( response.content == b'{"tom": {"type": "cat", "age": 10}}' ), "Wrong body on get" response = requests.post("http://127.0.0.1:8000/pets") assert response.status_code == 200 assert response.content == b"", "Wrong body on post" check_strings = ( "received http request with method=get, url=http://127.0.0.1:8000/pets and body=b''", "received http request with method=post, url=http://127.0.0.1:8000/pets and body=b''", ) missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) assert ( self.is_successfully_terminated() ), "Http echo agent wasn't successfully terminated." ================================================ FILE: tests/test_aea_extra/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the extra tests for the AEA core.""" ================================================ FILE: tests/test_aea_extra/test_manager/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains a test for aea.manager.""" ================================================ FILE: tests/test_aea_extra/test_manager/test_manager.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea manager.""" import contextlib import logging import os import pickle # nosec import re import time from contextlib import suppress from copy import copy from pathlib import Path from shutil import rmtree from tempfile import TemporaryDirectory from textwrap import dedent from typing import Optional from unittest.case import TestCase from unittest.mock import Mock, patch import pytest from aea.configurations.base import PublicId from aea.crypto.helpers import create_private_key from aea.crypto.plugin import load_all_plugins from aea.crypto.registries import crypto_registry from aea.helpers.install_dependency import call_pip from aea.manager import MultiAgentManager from aea.manager.manager import AgentRunProcessTask, ProjectPackageConsistencyCheckError from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.skills.echo import PUBLIC_ID as ECHO_SKILL_PUBLIC_ID from tests.common.utils import wait_for_condition from tests.conftest import MY_FIRST_AEA_PUBLIC_ID, PACKAGES_DIR, ROOT_DIR DEFAULT_TIMEOUT = 60 @patch("aea.aea_builder.AEABuilder.install_pypi_dependencies") class BaseCase(TestCase): """Base case setup/teardown.""" MODE = "async" PASSWORD: Optional[str] = None echo_skill_id = ECHO_SKILL_PUBLIC_ID def setUp(self): """Set test case.""" self.agent_name = "test_what_ever12" self.project_public_id = MY_FIRST_AEA_PUBLIC_ID self.tmp_dir = TemporaryDirectory() self.working_dir = os.path.join(self.tmp_dir.name, "MultiAgentManager_dir") self.project_path = os.path.join( self.working_dir, self.project_public_id.author, self.project_public_id.name ) assert not os.path.exists(self.working_dir) self.manager = MultiAgentManager( self.working_dir, mode=self.MODE, password=self.PASSWORD ) self._log_handlers = copy(logging.root.getChild("aea").handlers) def tearDown(self): """Tear down test case.""" try: self.manager.stop_manager() for task in self.manager._agents_tasks.values(): if isinstance(task, AgentRunProcessTask): task.process.terminate() task.process.join(5) time.sleep(1) logging.shutdown( [ handler for handler in logging._handlerList if getattr(handler, "baseFilename", None) ] ) logging.root.getChild("aea").handlers = [ handler for handler in self._log_handlers if not getattr(handler, "baseFilename", None) ] time.sleep(1) if os.path.exists(self.working_dir): rmtree(self.working_dir) finally: self.tmp_dir.cleanup() @patch("aea.aea_builder.AEABuilder.install_pypi_dependencies") class TestMultiAgentManagerDependencies(BaseCase): """Test plugin installed and loaded as a depencndecy.""" def test_plugin_dependencies(self, *args): """Test plugin installed and loaded as a depencndecy.""" plugin_path = str(Path(ROOT_DIR) / "plugins" / "aea-ledger-fetchai") install_cmd = f"install --no-deps {plugin_path}".split(" ") try: self.manager.start_manager() call_pip("uninstall aea-ledger-fetchai -y".split(" ")) from aea.crypto.registries import ledger_apis_registry ledger_apis_registry.specs.pop("fetchai", None) load_all_plugins(is_raising_exception=False) assert "fetchai" not in ledger_apis_registry.specs self.manager.add_project(self.project_public_id, local=True) assert "fetchai" not in ledger_apis_registry.specs self.manager.remove_project(self.project_public_id) def install_deps(*_): call_pip(install_cmd) with patch( "aea.aea_builder.AEABuilder.install_pypi_dependencies", install_deps ): self.manager.add_project(self.project_public_id, local=True) assert "fetchai" in ledger_apis_registry.specs finally: call_pip("uninstall aea-ledger-fetchai -y".split(" ")) call_pip(install_cmd) @patch("aea.aea_builder.AEABuilder.install_pypi_dependencies") class BaseTestMultiAgentManager(BaseCase): """Base test class for multi-agent manager""" def test_workdir_created_removed(self, *args): """Check work dit created removed on MultiAgentManager start and stop.""" assert not os.path.exists(self.working_dir) self.manager.start_manager() assert os.path.exists(self.working_dir) self.manager.stop_manager() assert not os.path.exists(self.working_dir) assert not os.path.exists(self.working_dir) def test_projects_property(self, *args): """Test projects property.""" self.assertEqual(self.manager.projects, self.manager._projects) def test_data_dir_presents(self, *args): """Check not fails on exists data dir.""" try: os.makedirs(self.working_dir) os.makedirs(self.manager._data_dir) self.manager.start_manager() self.manager.stop_manager() finally: with suppress(Exception): rmtree(self.working_dir) def test_MultiAgentManager_is_running(self, *args): """Check MultiAgentManager is running property reflects state.""" assert not self.manager.is_running self.manager.start_manager() assert self.manager.is_running self.manager.stop_manager() assert not self.manager.is_running def test_add_remove_project(self, *args): """Test add and remove project.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) assert self.project_public_id in self.manager.list_projects() assert os.path.exists(self.project_path) with pytest.raises(ValueError, match=r".*was already added.*"): self.manager.add_project(self.project_public_id, local=True) self.manager.remove_project(self.project_public_id) assert self.project_public_id not in self.manager.list_projects() with pytest.raises(ValueError, match=r"is not present"): self.manager.remove_project(self.project_public_id) self.manager.add_project(self.project_public_id, local=True) assert self.project_public_id in self.manager.list_projects() assert os.path.exists(self.project_path) def log_file(self, agent_name: str) -> str: """Get agent's log gile path.""" return f"{self.tmp_dir.name}/{agent_name}.log" def test_add_agent(self, *args): """Test add agent alias.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) new_tick_interval = 0.2111 component_overrides = [ { **self.echo_skill_id.json, "type": "skill", "behaviours": {"echo": {"args": {"tick_interval": new_tick_interval}}}, } ] agent_overrides = { "logging_config": { "version": 1, "formatters": {"standard": {"format": ""}}, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "standard", "level": "DEBUG", }, "file": { "class": "logging.FileHandler", "filename": self.log_file(self.agent_name), "mode": "w", "level": "DEBUG", "formatter": "standard", }, }, "loggers": {"aea": {"level": "DEBUG", "handlers": ["file", "console"]}}, } } self.manager.add_agent( self.project_public_id, self.agent_name, component_overrides=component_overrides, agent_overrides=agent_overrides, ) agent_alias = self.manager.get_agent_alias(self.agent_name) assert agent_alias.agent_name == self.agent_name assert ( agent_alias.get_aea_instance() .resources.get_behaviour(self.echo_skill_id, "echo") .tick_interval == new_tick_interval ) with pytest.raises(ValueError, match="already exists"): self.manager.add_agent( self.project_public_id, self.agent_name, ) def test_set_overrides(self, *args): """Test agent set overrides.""" self.test_add_agent() new_tick_interval = 1000.0 component_overrides = [ { **self.echo_skill_id.json, "type": "skill", "behaviours": {"echo": {"args": {"tick_interval": new_tick_interval}}}, } ] self.manager.set_agent_overrides( self.agent_name, agent_overides=None, components_overrides=component_overrides, ) agent_alias = self.manager.get_agent_alias(self.agent_name) assert agent_alias.agent_name == self.agent_name assert ( agent_alias.get_aea_instance() .resources.get_behaviour(self.echo_skill_id, "echo") .tick_interval == new_tick_interval ) def test_remove_agent(self, *args): """Test remove agent alias.""" self.test_add_agent() assert self.agent_name in self.manager.list_agents() self.manager.remove_agent(self.agent_name) assert self.agent_name not in self.manager.list_agents() with pytest.raises(ValueError, match="does not exist!"): self.manager.remove_agent(self.agent_name) def test_remove_project_with_alias(self, *args): """Test remove project with alias presents.""" self.test_add_agent() with pytest.raises( ValueError, match="Can not remove projects with aliases exists" ): self.manager.remove_project(self.project_public_id) def test_add_agent_for_non_exist_project(self, *args): """Test add agent when no project added.""" with pytest.raises(ValueError, match=" project is not added"): self.manager.add_agent(PublicId("test", "test", "0.1.0"), "another_agent") def test_agent_actually_running(self, *args): """Test MultiAgentManager starts agent correctly and agent perform acts.""" self.test_add_agent() self.manager.start_all_agents() wait_for_condition( lambda: "Echo Behaviour: act method called." in open(self.log_file(self.agent_name), "r").read(), timeout=DEFAULT_TIMEOUT, ) with contextlib.suppress(Exception): self.manager.stop_agent(self.agent_name) def test_exception_handling(self, *args): """Test error callback works.""" self.add_agent("test_agent", PublicId.from_str("fetchai/error_test:0.1.1")) self.manager.start_all_agents() callback_mock = Mock() self.manager.add_error_callback(callback_mock) self.manager.start_all_agents() wait_for_condition( lambda: callback_mock.call_count > 0, timeout=DEFAULT_TIMEOUT ) def add_agent(self, agent_name: str, project_id: PublicId) -> None: """Add agent and start manager.""" self.manager.start_manager() self.manager.add_project(project_id, local=True) self.manager.add_agent( project_id, agent_name, ) agent_alias = self.manager.get_agent_alias(agent_name) assert agent_alias def test_default_exception_handling(self, *args): """Test that the default error callback works.""" self.add_agent("test_agent", PublicId.from_str("fetchai/error_test:0.1.1")) with patch.object( self.manager, "_print_exception_occurred_but_no_error_callback", side_effect=self.manager._print_exception_occurred_but_no_error_callback, ) as callback_mock: self.manager.start_all_agents() wait_for_condition( lambda: callback_mock.call_count > 0, timeout=DEFAULT_TIMEOUT ) callback_mock.assert_called_once() def test_stop_from_exception_handling(self, *args): """Test stop MultiAgentManager from error callback.""" self.add_agent("test_agent", PublicId.from_str("fetchai/error_test:0.1.1")) def handler(*args, **kwargs): self.manager.stop_manager() self.manager.add_error_callback(handler) self.manager.start_all_agents() wait_for_condition(lambda: not self.manager.is_running, timeout=DEFAULT_TIMEOUT) def test_start_all(self, *args): """Test MultiAgentManager start all agents.""" self.test_add_agent() assert self.agent_name in self.manager.list_agents() assert self.agent_name not in self.manager.list_agents(running_only=True) self.manager.start_all_agents() assert self.agent_name in self.manager.list_agents(running_only=True) self.manager.start_all_agents() # check every agent started wait_for_condition( lambda: len(self.manager.list_agents()) == len(self.manager.list_agents(running_only=True)), timeout=DEFAULT_TIMEOUT, period=0.1, ) wait_for_condition( lambda: "Start processing messages" in open(self.log_file(self.agent_name), "r").read(), timeout=DEFAULT_TIMEOUT, ) with pytest.raises(ValueError, match="is already started!"): self.manager.start_agents(self.manager.list_agents()) with pytest.raises(ValueError, match="is already started!"): self.manager.start_agent(self.agent_name) with pytest.raises(ValueError, match="is not registered!"): self.manager.start_agent("non_exists_agent") def test_stop_agent(self, *args): """Test stop agent.""" self.test_start_all() wait_for_condition( lambda: self.manager.list_agents(running_only=True), timeout=DEFAULT_TIMEOUT ) wait_for_condition( lambda: "Start processing messages" in open(self.log_file(self.agent_name), "r").read(), timeout=DEFAULT_TIMEOUT, ) self.manager.stop_all_agents() wait_for_condition( lambda: len(self.manager.list_agents(running_only=True)) == 0, timeout=DEFAULT_TIMEOUT, ) assert not self.manager.list_agents(running_only=True) with pytest.raises(ValueError, match=" is not running!"): self.manager.stop_agent(self.agent_name) with pytest.raises(ValueError, match=" is not running!"): self.manager.stop_agents([self.agent_name]) wait_for_condition( lambda: "Runtime loop stopped!" in open(self.log_file(self.agent_name), "r").read(), timeout=DEFAULT_TIMEOUT, ) def test_do_no_allow_override_some_fields(self, *args): """Do not allo to override some values in agent config.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) BAD_OVERRIDES = [ "skills", "connections", "contracts", "protocols", "some_field?", ] for bad_override in BAD_OVERRIDES: with pytest.raises( ValueError, match=r"Attribute `.*` is not allowed to be updated!" ): self.manager.add_agent( self.project_public_id, self.agent_name, agent_overrides={bad_override: "some value"}, ) @staticmethod def test_invalid_mode(*args): """Test MultiAgentManager fails on invalid mode.""" with pytest.raises(ValueError, match="Invalid mode"): MultiAgentManager("test_dir", mode="invalid_mode") def test_double_start(self, *args): """Test double MultiAgentManager start.""" self.manager.start_manager() assert self.manager.is_running self.manager.start_manager() assert self.manager.is_running def test_double_stop(self, *args): """Test double MultiAgentManager stop.""" self.manager.start_manager() assert self.manager.is_running self.manager.stop_manager() assert not self.manager.is_running self.manager.stop_manager() assert not self.manager.is_running def test_remove_running_agent(self, *args): """Test fail on remove running agent.""" self.test_start_all() wait_for_condition( lambda: "Start processing messages" in open(self.log_file(self.agent_name), "r").read(), timeout=DEFAULT_TIMEOUT, ) with pytest.raises(ValueError, match="Agent is running. stop it first!"): self.manager.remove_agent(self.agent_name) self.manager.stop_all_agents() # wait all stopped wait_for_condition( lambda: len(self.manager.list_agents(running_only=True)) == 0, timeout=DEFAULT_TIMEOUT, period=0.1, ) assert self.agent_name not in self.manager.list_agents(running_only=True) self.manager.remove_agent(self.agent_name) assert self.agent_name not in self.manager.list_agents() def test_save_load_positive(self, *args): """Test save-load func of MultiAgentManager for positive result.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) self.manager.add_agent(self.project_public_id, self.agent_name) self.manager.stop_manager(save=True) assert os.path.exists(self.manager._save_path) self.manager.start_manager() assert self.project_public_id in self.manager._projects.keys() assert self.agent_name in self.manager._agents.keys() def test_list_agents_info_positive(self, *args): """Test list_agents_info method for positive result.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) self.manager.add_agent(self.project_public_id, self.agent_name) result = self.manager.list_agents_info() expected_result = [ { "agent_name": self.agent_name, "public_id": str(self.project_public_id), "addresses": self.manager.get_agent_alias( self.agent_name ).get_addresses(), "is_running": False, } ] assert result == expected_result def test_add_same_project_versions(self, *args): """Test add the same project twice.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) with pytest.raises( ValueError, match=r"The project \(fetchai/my_first_aea\) was already added!" ): self.manager.add_project( PublicId.from_str("fetchai/my_first_aea:0.15.0"), local=False ) def test_get_overridables(self, *args): """Test get overridables.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) self.manager.add_agent(self.project_public_id, self.agent_name) ( agent_overridables, components_overridables, ) = self.manager.get_agent_overridables(self.agent_name) assert "default_ledger" in agent_overridables assert "timeout" in agent_overridables assert "description" in agent_overridables assert len(components_overridables) == 2 assert "is_abstract" in components_overridables[0] def test_issue_certificates(self, *args): """Test agent alias issue certificates.""" self.manager.start_manager() self.manager.add_project(self.project_public_id, local=True) cert_filename = "cert.txt" cert_path = os.path.join(self.manager.data_dir, self.agent_name, cert_filename) assert not os.path.exists(cert_filename) priv_key_path = os.path.abspath(os.path.join(self.working_dir, "priv_key.txt")) create_private_key("fetchai", priv_key_path, password=self.PASSWORD) assert os.path.exists(priv_key_path) component_overrides = [ { **StubConnection.connection_id.json, "type": "connection", "cert_requests": [ { "identifier": "acn", "ledger_id": "fetchai", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "message_format": "{public_key}", "save_path": cert_filename, } ], } ] agent_overrides = { "private_key_paths": {"fetchai": priv_key_path}, "connection_private_key_paths": {"fetchai": priv_key_path}, } self.manager.add_agent( self.project_public_id, self.agent_name, agent_overrides=agent_overrides, component_overrides=component_overrides, ) agent_alias = self.manager.get_agent_alias(self.agent_name) agent_alias.issue_certificates() assert os.path.exists(cert_path) def test_get_addresses(self, *args) -> None: """Test get addresses for agent alias.""" self.test_add_agent() agent_alias = self.manager.get_agent_alias(self.agent_name) keys = { name: agent_alias._create_private_key( ledger=name, replace=True, is_connection=False ) for name in crypto_registry.supported_ids } connection_keys = { name: agent_alias._create_private_key( ledger=name, replace=True, is_connection=True ) for name in crypto_registry.supported_ids } agent_alias.set_overrides( {"private_key_paths": keys, "connection_private_key_paths": connection_keys} ) assert len(agent_alias.get_addresses()) == len(crypto_registry.supported_ids) assert len(agent_alias.get_connections_addresses()) == len( crypto_registry.supported_ids ) def test_addresses_autoadded(self, *args) -> None: """Test addresses automatically added on creation.""" self.test_add_agent() agent_alias = self.manager.get_agent_alias(self.agent_name) assert len(agent_alias.get_addresses()) == 1 assert len(agent_alias.get_connections_addresses()) == 1 @patch("aea.aea_builder.AEABuilder.install_pypi_dependencies") class TestMultiAgentManagerAsyncMode( BaseTestMultiAgentManager ): # pylint: disable=unused-argument,protected-access,attribute-defined-outside-init """Tests for MultiAgentManager in async mode.""" PASSWORD = "password" # nosec @patch("aea.aea_builder.AEABuilder.install_pypi_dependencies") class TestMultiAgentManagerThreadedMode(BaseTestMultiAgentManager): """Tests for MultiAgentManager in threaded mode.""" MODE = "threaded" @patch("aea.aea_builder.AEABuilder.install_pypi_dependencies") class TestMultiAgentManagerMultiprocessMode(BaseTestMultiAgentManager): """Tests for MultiAgentManager in multiprocess mode.""" MODE = "multiprocess" def test_plugin_dependencies(self, *args): """Skip test cause multiprocess works another way.""" @patch("aea.aea_builder.AEABuilder.install_pypi_dependencies") class TestMultiAgentManagerPackageConsistencyError: """ Test that the MultiAgentManager (MAM) raises an error on package version inconsistency. In particular, an invariant to keep for the MAM state is that there are no two AEA package dependencies across all the projects that have different versions. This is due to a known limitation in the package loading system. This test checks that when the invariant is violated when the method "add_project" is called, the framework raises an exception with a descriptive error message. """ EXPECTED_ERROR_MESSAGE = dedent( """ cannot add project 'fetchai/weather_client:0.27.0': the following AEA dependencies have conflicts with previously added projects: - 'fetchai/ledger' of type connection: the new version '0.17.0' conflicts with existing version '0.18.0' of the same package required by agents: [] - 'fetchai/p2p_libp2p' of type connection: the new version '0.20.0' conflicts with existing version '0.21.0' of the same package required by agents: [] - 'fetchai/soef' of type connection: the new version '0.21.0' conflicts with existing version '0.22.0' of the same package required by agents: [] - 'fetchai/contract_api' of type protocol: the new version '0.14.0' conflicts with existing version '1.0.0' of the same package required by agents: [] - 'fetchai/default' of type protocol: the new version '0.15.0' conflicts with existing version '1.0.0' of the same package required by agents: [] - 'fetchai/fipa' of type protocol: the new version '0.16.0' conflicts with existing version '1.0.0' of the same package required by agents: [] - 'fetchai/ledger_api' of type protocol: the new version '0.13.0' conflicts with existing version '1.0.0' of the same package required by agents: [] - 'fetchai/oef_search' of type protocol: the new version '0.16.0' conflicts with existing version '1.0.0' of the same package required by agents: [] - 'fetchai/signing' of type protocol: the new version '0.13.0' conflicts with existing version '1.0.0' of the same package required by agents: [] - 'fetchai/state_update' of type protocol: the new version '0.13.0' conflicts with existing version '1.0.0' of the same package required by agents: [] """ ) def setup(self): """Set up the test case.""" self.project_public_id = MY_FIRST_AEA_PUBLIC_ID self.tmp_dir = TemporaryDirectory() self.working_dir = os.path.join(self.tmp_dir.name, "MultiAgentManager_dir") self.project_path = os.path.join( self.working_dir, self.project_public_id.author, self.project_public_id.name ) assert not os.path.exists(self.working_dir) self.manager = MultiAgentManager(self.working_dir) def test_run(self, *args): """ Run the test. First add agent project "fetchai/weather_station:0.27.0", and then add "fetchai/weather_client:0.27.0". The second addition will fail because the projects contain conflicting AEA package versions. """ self.manager.start_manager() weather_station_id = PublicId.from_str("fetchai/weather_station:0.27.0") self.manager.add_project(weather_station_id) weather_client_id = PublicId.from_str("fetchai/weather_client:0.27.0") with pytest.raises( ProjectPackageConsistencyCheckError, match=re.escape(self.EXPECTED_ERROR_MESSAGE), ): self.manager.add_project(weather_client_id) def teardown(self): """Tear down test case.""" try: self.manager.stop_manager() if os.path.exists(self.working_dir): rmtree(self.working_dir) finally: self.tmp_dir.cleanup() class TestMultiAgentManagerWithPotentiallyConflictingPackages: """ Test that the MultiAgentManager (MAM) handles correctly potentially conflicting packages. "Potentially conflicting packages" are packages that have the same package id prefix. The reason why they are "potentially conflicting" is that their version may be different, i.e. conflicting, and therefore are incompatible within the same MAM. This test checks that, when adding a new project, in case there are potentially conflicting packages but that are not in fact conflicting, the operation is completed successfully. """ def setup(self): """Set up the test case.""" self.project_public_id = MY_FIRST_AEA_PUBLIC_ID self.tmp_dir = TemporaryDirectory() self.working_dir = os.path.join(self.tmp_dir.name, "MultiAgentManager_dir") self.project_path = os.path.join( self.working_dir, self.project_public_id.author, self.project_public_id.name ) assert not os.path.exists(self.working_dir) self.manager = MultiAgentManager(self.working_dir) def test_run(self): """ Run the test. First add agent project "fetchai/weather_station:0.27.0", and then add "fetchai/weather_client:0.27.0". The second addition will fail because the projects contain conflicting AEA package versions. """ self.manager.start_manager() weather_station_id = PublicId.from_str("fetchai/weather_station:0.27.0") self.manager.add_project(weather_station_id) weather_client_id = PublicId.from_str("fetchai/weather_client:0.28.0") self.manager.add_project(weather_client_id) def teardown(self): """Tear down test case.""" try: self.manager.stop_manager() if os.path.exists(self.working_dir): rmtree(self.working_dir) finally: self.tmp_dir.cleanup() def test_project_auto_added_removed(): """Check project auto added and auto removed on agent added/removed.""" agent_name = "test_agent" with TemporaryDirectory() as tmp_dir, patch( "aea.manager.project.Project.build" ), patch("aea.manager.project.Project.install_pypi_dependencies"): manager = MultiAgentManager( tmp_dir, mode="async", registry_path=PACKAGES_DIR, auto_add_remove_project=True, ) try: manager.start_manager() assert not manager.list_projects() manager.add_agent( PublicId("fetchai", "my_first_aea"), agent_name, local=True ) assert manager.list_projects() assert manager.list_agents() manager.remove_agent(agent_name) assert not manager.list_agents() assert not manager.list_projects() finally: manager.stop_manager() def test_dump(): """Check project auto added and auto removed on agent added/removed.""" agent_name = "test_agent" with TemporaryDirectory() as tmp_dir, patch( "aea.manager.project.Project.build" ), patch("aea.manager.project.Project.install_pypi_dependencies"): manager = MultiAgentManager( tmp_dir, mode="async", registry_path=PACKAGES_DIR, auto_add_remove_project=True, ) try: manager.start_manager() assert not manager.list_projects() manager.add_agent( PublicId("fetchai", "my_first_aea"), agent_name, local=True ) alias = manager.get_agent_alias(agent_name) data = pickle.dumps(alias) alias2 = pickle.loads(data) # nosec assert alias.dict == alias2.dict assert alias.project.public_id == alias2.project.public_id assert manager.list_projects() assert manager.list_agents() manager.remove_agent(agent_name) assert not manager.list_agents() assert not manager.list_projects() finally: manager.stop_manager() def test_handle_error_on_load_state(): """Check project auto added and auto removed on agent added/removed.""" agent_name = "test_agent" with TemporaryDirectory() as tmp_dir: manager = MultiAgentManager( tmp_dir, mode="async", registry_path=PACKAGES_DIR, auto_add_remove_project=True, ) with pytest.raises(ValueError, match="Manager was not started"): manager.last_start_status try: manager.start_manager() state_loaded, *_ = manager.last_start_status assert not state_loaded assert not manager.list_projects() manager.add_agent( PublicId("fetchai", "my_first_aea"), agent_name, local=True ) assert manager.list_projects() manager._save_state() finally: manager.stop_manager(cleanup=False) # check loaded well manager = MultiAgentManager( tmp_dir, mode="async", registry_path=PACKAGES_DIR, auto_add_remove_project=False, ) try: manager.start_manager() state_loaded, loaded_ok, *_ = manager.last_start_status assert state_loaded assert len(loaded_ok) == 1 assert manager.list_projects() assert manager.list_agents() finally: manager.stop_manager(cleanup=False) config_file = ( Path(tmp_dir) / "fetchai/my_first_aea/vendor/fetchai/skills/echo/skill.yaml" ) config_yaml = config_file.read_text() new_version = "'>=0.0.1, <0.0.2'" new_config = re.sub( r"'>=[0-9]+.[0-9]+.[0-9]+, <[0-9]+.[0-9]+.[0-9]+'", new_version, config_yaml, ) assert new_version in new_config config_file.write_text(new_config) # check load fails manager = MultiAgentManager( tmp_dir, mode="async", registry_path=PACKAGES_DIR, auto_add_remove_project=False, ) try: manager.start_manager() state_loaded, loaded_ok, load_failed = manager.last_start_status assert state_loaded assert len(loaded_ok) == 0 assert len(load_failed) == 1 assert isinstance(load_failed[0][0], PublicId) assert isinstance(load_failed[0][1], list) assert len(load_failed[0][1]) == 1 assert isinstance(load_failed[0][1][0], dict) assert isinstance(load_failed[0][2], Exception) assert re.match( "Failed to load project: fetchai/my_first_aea:latest Error: The CLI version is .*, but package fetchai/echo:0.20.6 requires version <0.0.2,>=0.0.1", str(load_failed[0][2]), ) assert not manager.list_projects() assert not manager.list_agents() finally: manager.stop_manager() ================================================ FILE: tests/test_aea_extra/test_manager/test_utils.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for aea manager utils.""" import time from random import randint from tempfile import TemporaryDirectory import pytest from aea.manager.utils import run_in_venv RETURN_VALUE = randint(0, 2000) # nosec DEFAULT_TIMEOUT = 20 def _process_long(): time.sleep(70) def _process_exception(): raise ValueError("Expected") def _process_return_value(value_to_return): return value_to_return def test_process_run_in_venv_timeout_error(): """Test timeout error raised for process running too long.""" with TemporaryDirectory() as tmp_dir: with pytest.raises(TimeoutError): run_in_venv(tmp_dir, _process_long, timeout=DEFAULT_TIMEOUT) def test_process_run_in_venv_raise_custom_exception(): """Test process returns expcetion.""" with TemporaryDirectory() as tmp_dir: with pytest.raises(Exception, match="Expected"): run_in_venv(tmp_dir, _process_exception, timeout=DEFAULT_TIMEOUT) def test_process_run_in_venv_return_value(): """Test process return value.""" with TemporaryDirectory() as tmp_dir: ret_value = run_in_venv( tmp_dir, _process_return_value, DEFAULT_TIMEOUT, RETURN_VALUE ) assert ret_value == RETURN_VALUE ================================================ FILE: tests/test_docs/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for docs modules.""" ================================================ FILE: tests/test_docs/helper.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains helper function to extract code from the .md files.""" import re import traceback from functools import partial from pathlib import Path from typing import Dict, List, Optional, cast import mistune import pytest MISTUNE_BLOCK_CODE_ID = "block_code" def block_code_filter(b: Dict) -> bool: """Check Mistune block is a code block.""" return b["type"] == MISTUNE_BLOCK_CODE_ID def type_filter(type_: Optional[str], b: Dict) -> bool: """ Check Mistune code block is of a certain type. If the field "info" is None, return False. If type_ is None, this function always return true. :param type_: the expected type of block (optional) :param b: the block dicionary. :return: True if the block should be accepted, false otherwise. """ if type_ is None: return True return b["info"].strip() == type_ if b["info"] is not None else False def extract_dicts(dictionary: Dict, collection_dict: List[Dict]) -> List[Dict]: """Extract code blocks from .md files.""" if all(not isinstance(v, list) for v in dictionary.values()): collection_dict.append(dictionary) else: for dict_el in dictionary.values(): if isinstance(dict_el, list): for list_el in dict_el: if isinstance(list_el, dict): extract_dicts(list_el, collection_dict) return collection_dict def flatten_blocks(blocks: List[Dict]) -> List[Dict]: """Convert a list of dicts with nested dicts, into a list containing all dicts.""" new_blocks: List[Dict] = [] if isinstance(blocks, list): for el in blocks: if isinstance(el, dict): extract_dicts(el, new_blocks) return new_blocks def correct_spacing(string: str) -> str: """Convert a list of dicts with nested dicts, into a list containing all dicts.""" new_string = "" string_no_back_quote = string[:-3] last_new_line = string.rfind("\n") last_none_space = len(string_no_back_quote.strip(" ")) - 1 if last_new_line != -1 and last_new_line == last_none_space: number_of_trailing_spaces = (len(string_no_back_quote) - 1) - last_none_space for line in string.splitlines(): number_of_leading_spaces = len(line) - len(line.lstrip(" ")) if number_of_leading_spaces >= number_of_trailing_spaces: new_line = line[number_of_trailing_spaces:] new_string += new_line else: new_string += line new_string += "\n" return new_string return string def extract_inner_code_blocks(block: Dict, code_blocks: List[Dict]) -> None: """Replace code_block with any sub code_blocks it may have""" text = cast(str, block["text"]) indexes = [m.start() for m in re.finditer("```", text)] if indexes: if len(indexes) % 2 != 0: raise SyntaxError(f"un-matching ``` found in the block: {text}") while indexes: starting_index = indexes.pop(0) ending_index = indexes.pop(0) + 3 sub_string = text[starting_index:ending_index] new_substring = correct_spacing(sub_string) new_dict = { "type": block["type"], "text": new_substring, "info": block["info"], } code_blocks.insert(code_blocks.index(block), new_dict) code_blocks.remove(block) def extract_code_blocks(filepath, filter_=None): """Extract code blocks from .md files.""" content = Path(filepath).read_text(encoding="utf-8") markdown_parser = mistune.create_markdown(renderer=mistune.AstRenderer()) blocks = markdown_parser(content) flat_blocks = flatten_blocks(blocks) actual_type_filter = partial(type_filter, filter_) code_blocks = list(filter(block_code_filter, flat_blocks)) for code_block in code_blocks: extract_inner_code_blocks(code_block, code_blocks) for block in code_blocks: if block["text"].startswith("``` "): type_ = block["text"][4 : block["text"].find("\n")] block["text"] = block["text"].strip()[block["text"].find("\n") + 1 :] if block["text"].endswith("```"): block["text"] = block["text"].strip()[:-3] block["info"] = f"{type_}" bash_code_blocks = filter(actual_type_filter, code_blocks) return list(b["text"] for b in bash_code_blocks) def extract_python_code(filepath): """Removes the license part from the scripts""" python_str = "" with open(filepath, "r") as python_file: read_python_file = python_file.readlines() for i in range(21, len(read_python_file)): python_str += read_python_file[i] return python_str def compile_and_exec(code: str, locals_dict: Dict = None) -> Dict: """ Compile and exec the code. :param code: the code to execute. :param locals_dict: the dictionary of local variables. :return: the dictionary of locals. """ locals_dict = {} if locals_dict is None else locals_dict try: code_obj = compile(code, "fakemodule", "exec") exec(code_obj, locals_dict) # nosec except Exception: pytest.fail( "The execution of the following code:\n{}\nfailed with error:\n{}".format( code, traceback.format_exc() ) ) return locals_dict def compare_enum_classes(expected_enum_class, actual_enum_class): """Compare enum classes.""" try: # do some pre-processing expected_pairs = sorted(map(lambda x: (x.name, x.value), expected_enum_class)) actual_pairs = sorted(map(lambda x: (x.name, x.value), actual_enum_class)) assert expected_pairs == actual_pairs, "{} != {}".format( expected_pairs, actual_pairs ) except AssertionError: pytest.fail( "Actual enum {} is different from the actual one {}".format( expected_enum_class, actual_enum_class ) ) class BaseTestMarkdownDocs: """Base test class for testing Markdown documents.""" DOC_PATH: Path blocks: List[Dict] flat_blocks: List[Dict] python_blocks: List[Dict] @classmethod def setup_class(cls): """Set up the test.""" markdown_parser = mistune.create_markdown(renderer=mistune.AstRenderer()) cls.doc_path = cls.DOC_PATH cls.doc_content = cls.doc_path.read_text() cls.blocks = markdown_parser(cls.doc_content) cls.flat_blocks = flatten_blocks(cls.blocks) cls.code_blocks = list(filter(block_code_filter, cls.flat_blocks)) cls.python_blocks = list(filter(cls._python_selector, cls.flat_blocks)) @classmethod def _python_selector(cls, block: Dict) -> bool: return block["type"] == MISTUNE_BLOCK_CODE_ID and ( block["info"].strip() == "python" if block["info"] else False ) class BasePythonMarkdownDocs(BaseTestMarkdownDocs): """Test Markdown documentation by running Python snippets in sequence.""" @classmethod def setup_class(cls): """ Set up class. It sets the initial value of locals and globals. """ super().setup_class() cls.locals = {} cls.globals = {} def _assert(self, locals_, *mocks): """Do assertions after Python code execution.""" def test_python_blocks(self, *mocks): """Run Python code block in sequence.""" python_blocks = self.python_blocks globals_, locals_ = self.globals, self.locals for python_block in python_blocks: python_code = python_block["text"] exec(python_code, globals_, locals_) # nosec self._assert(locals_, *mocks) ================================================ FILE: tests/test_docs/test_agent_vs_aea/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the agent-vs-aea modules.""" ================================================ FILE: tests/test_docs/test_agent_vs_aea/agent_code_block.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This scripts contains code from agent-vs-aea.md file.""" import os import time from threading import Thread from typing import List from aea.agent import Agent from aea.configurations.base import ConnectionConfig from aea.connections.base import Connection from aea.helpers.file_io import write_with_lock from aea.identity.base import Identity from aea.mail.base import Envelope from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.protocols.default.message import DefaultMessage INPUT_FILE = "input_file" OUTPUT_FILE = "output_file" class MyAgent(Agent): """A simple agent.""" def __init__(self, identity: Identity, connections: List[Connection]): """Initialise the agent.""" super().__init__(identity, connections) def setup(self): """Setup the agent.""" def act(self): """Act implementation.""" print("Act called for tick {}".format(self.tick)) def handle_envelope(self, envelope: Envelope) -> None: """ Handle envelope. :param envelope: the envelope received :return: None """ print("React called for tick {}".format(self.tick)) if ( envelope is not None and envelope.protocol_specification_id == DefaultMessage.protocol_specification_id ): sender = envelope.sender receiver = envelope.to envelope.to = sender envelope.sender = receiver envelope.message = DefaultMessage.serializer.decode(envelope.message_bytes) envelope.message.sender = receiver envelope.message.to = sender print( "Received envelope from {} with protocol_specification_id={}".format( sender, envelope.protocol_specification_id ) ) self.outbox.put(envelope) def teardown(self): """Teardown the agent.""" def run(): """Run demo.""" # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # Create an addresses identity: identity = Identity( name="my_agent", address="some_address", public_key="public_key" ) # Set up the stub connection configuration = ConnectionConfig( input_file_path=INPUT_FILE, output_file_path=OUTPUT_FILE, connection_id=StubConnection.connection_id, ) stub_connection = StubConnection( configuration=configuration, data_dir=".", identity=identity ) # Create our Agent my_agent = MyAgent(identity, [stub_connection]) # Set the agent running in a different thread try: t = Thread(target=my_agent.start) t.start() # Wait for everything to start up time.sleep(3) # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = b"my_agent,other_agent,fetchai/default:1.0.0,\x12\r\x08\x01*\t*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) # Wait for the envelope to get processed time.sleep(2) # Read the output envelope generated by the agent with open(OUTPUT_FILE, "rb") as f: print("output message: " + f.readline().decode("utf-8")) finally: # Shut down the agent my_agent.stop() t.join() if __name__ == "__main__": run() ================================================ FILE: tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the agent-vs-aea.md file.""" import os from pathlib import Path import pytest from aea.test_tools.test_cases import AEATestCaseManyFlaky from tests.conftest import CUR_PATH, MAX_FLAKY_RERUNS, ROOT_DIR from tests.test_docs.helper import extract_code_blocks, extract_python_code from tests.test_docs.test_agent_vs_aea.agent_code_block import run MD_FILE = "docs/agent-vs-aea.md" PY_FILE = "test_docs/test_agent_vs_aea/agent_code_block.py" class TestFiles: """Test consistency of the files.""" @classmethod def setup_class(cls): """Setup the test class.""" doc_path = os.path.join(ROOT_DIR, MD_FILE) cls.code_blocks = extract_code_blocks(filepath=doc_path, filter_="python") test_code_path = os.path.join(CUR_PATH, PY_FILE) cls.python_file = extract_python_code(test_code_path) def test_read_md_file(self): """Test the last code block, that is the full listing of the demo from the Markdown.""" assert ( self.code_blocks[-1] == self.python_file ), "Files must be exactly the same." def test_code_blocks_exist(self): """Test that all the code-blocks exist in the python file.""" for blocks in self.code_blocks: assert ( blocks in self.python_file ), "Code-block doesn't exist in the python file." class TestAgentVsAEA(AEATestCaseManyFlaky): """This class contains the tests for the code-blocks in the agent-vs-aea.md file.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS ) # TODO: check why test_run_agent raises permission error on file on windows platform! def test_run_agent(self): """Run the agent from the file.""" run() assert os.path.exists(Path(self.t, "input_file")) message_text = b"other_agent,my_agent,fetchai/default:1.0.0,\x12\r\x08\x01*\t*\x07\n\x05hello," path = os.path.join(self.t, "output_file") with open(path, "rb") as file: msg = file.read() assert msg == message_text, "The messages must be identical." ================================================ FILE: tests/test_docs/test_bash_yaml/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the demos folder.""" ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the bash code-blocks in the demos.""" ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-aggregation-demo.md ================================================ ``` bash agent_name="agg$i" aea fetch fetchai/simple_aggregator:0.5.5 --alias $agent_name cd $agent_name aea install aea build ``` ``` bash agent_name="agg$i" aea create agent_name cd agent_name aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/http_server:0.23.6 aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/prometheus:0.9.6 aea add skill fetchai/advanced_data_request:0.7.6 aea add skill fetchai/simple_aggregation:0.3.6 aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea install aea build ``` ``` bash aea config set --type int vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.decimals 0 ``` ``` bash aea config set --type bool vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.use_http_server false ``` ``` bash aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "fetchai", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt"}]' ``` ``` bash aea config set vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url $COIN_URL aea config set vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs '[{"name": "price", "json_path": '"\"$JSON_PATH\""'}]' ``` ``` bash aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.quantity_name price aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.aggregation_function mean ``` ``` bash SERVICE_ID=my_btc_aggregation_service aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.service_id $SERVICE_ID aea config set vendor.fetchai.skills.simple_aggregation.models.strategy.args.search_query.search_value $SERVICE_ID ``` ``` bash aea generate-key fetchai aea add-key fetchai aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash MULTIADDR=$(cd ../agg0 && aea get-multiaddress fetchai --connection) aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:'$((11000+i))'", "entry_peers": ["/dns4/127.0.0.1/tcp/9000/p2p/'"$MULTIADDR\""'], "local_uri": "127.0.0.1:'$((9000+i))'", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:'$((9000+i))'" }' aea config set vendor.fetchai.connections.prometheus.config.port $((20000+i)) aea config set vendor.fetchai.connections.http_server.config.port $((8000+i)) ``` ``` bash aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/simple_oracle:0.16.5 ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id fetchai aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function update_oracle_value ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.oracle_value_name price_mean ``` ``` bash aea run ``` ``` bash info: [agg_i] found agents... ... info: [agg_i] Fetching data from... ... info: [agg_i] Observation: {'price': {'value':... ... info: [agg_i] sending observation to peer... ... info: [agg_i] received observation from sender... ... info: [agg_i] Observations:... ... info: [agg_i] Aggregation (mean):... ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md ================================================ ``` bash pip install aries-cloudagent ``` ``` bash ./manage build ./manage start --logs ``` ``` bash aca-py start --help ``` ``` bash aca-py start --admin 127.0.0.1 8021 --admin-insecure-mode --inbound-transport http 0.0.0.0 8020 --outbound-transport http --webhook-url http://127.0.0.1:8022/webhooks ``` ``` bash aca-py start --admin 127.0.0.1 8031 --admin-insecure-mode --inbound-transport http 0.0.0.0 8030 --outbound-transp http --webhook-url http://127.0.0.1:8032/webhooks ``` ``` bash aea fetch fetchai/aries_alice:0.32.5 cd aries_alice ``` ``` bash aea create aries_alice cd aries_alice aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/webhook:0.20.6 aea add skill fetchai/aries_alice:0.26.6 ``` ``` bash aea config set vendor.fetchai.skills.aries_alice.models.strategy.args.admin_host 127.0.0.1 ``` ``` bash aea config set --type int vendor.fetchai.skills.aries_alice.models.strategy.args.admin_port 8031 ``` ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8032 ``` ``` bash aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11000", "entry_peers": [], "local_uri": "127.0.0.1:7000", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:7000" }' ``` ``` bash aea install aea build ``` ``` bash aea run ``` ``` bash aea fetch fetchai/aries_faber:0.32.5 cd aries_faber ``` ``` bash aea create aries_faber cd aries_faber aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/webhook:0.20.6 aea add skill fetchai/aries_faber:0.24.5 ``` ``` bash aea config set vendor.fetchai.skills.aries_faber.models.strategy.args.admin_host 127.0.0.1 ``` ``` bash aea config set --type int vendor.fetchai.skills.aries_faber.models.strategy.args.admin_port 8021 ``` ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8022 ``` ``` bash aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:7001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:7001" }' ``` ``` bash aea install aea build ``` ``` bash aea run ``` ``` bash aea delete aries_faber aea delete aries_alice ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md ================================================ ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` ``` bash aea fetch fetchai/car_detector:0.32.5 cd car_detector aea install aea build ``` ``` bash aea create car_detector cd car_detector aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/carpark_detection:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea fetch fetchai/car_data_buyer:0.33.5 cd car_data_buyer aea install aea build ``` ``` bash aea create car_data_buyer cd car_data_buyer aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/carpark_client:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea add-key fetchai fetchai_private_key.txt --connection ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea run ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete car_detector aea delete car_data_buyer ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: - SOME_ADDRESS local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md ================================================ ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` ```bash pip install aea-ledger-fetchai ``` ``` bash aea fetch fetchai/weather_station:0.32.5 cd weather_station aea install aea build ``` ``` bash aea config set vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx False --type bool ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea run ``` ``` bash python weather_client.py ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-config.md ================================================ ``` yaml PACKAGE_REGEX: "[a-zA-Z_][a-zA-Z0-9_]*" AUTHOR_REGEX: "[a-zA-Z_][a-zA-Z0-9_]*" PUBLIC_ID_REGEX: "^[a-zA-Z0-9_]*/[a-zA-Z_][a-zA-Z0-9_]*:(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" LEDGER_ID_REGEX: "^[^\\d\\W]\\w*\\Z" ``` ``` yaml agent_name: my_agent # Name of the AEA project (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the project's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the AEA project (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") description: A demo project # Description of the AEA project license: Apache-2.0 # License of the AEA project aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: {} # Fingerprint of AEA project components. fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: # The list of connection public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX) - fetchai/stub:0.21.3 contracts: [] # The list of contract public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: # The list of protocol public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). - fetchai/default:1.1.7 skills: # The list of skill public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). - fetchai/error:0.18.6 default_connection: fetchai/p2p_libp2p:0.27.5 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). default_ledger: fetchai # The default ledger identifier the AEA project uses (must satisfy LEDGER_ID_REGEX) required_ledgers: [fetchai] # the list of identifiers of ledgers that the AEA project requires key pairs for (each item must satisfy LEDGER_ID_REGEX) default_routing: {} # The default routing scheme applied to envelopes sent by the AEA, it maps from protocol public ids to connection public ids (both keys and values must satisfy PUBLIC_ID_REGEX) connection_private_key_paths: # The private key paths the AEA project uses for its connections (keys must satisfy LEDGER_ID_REGEX, values must be file paths) fetchai: fetchai_private_key.txt private_key_paths: # The private key paths the AEA project uses (keys must satisfy LEDGER_ID_REGEX, values must be file paths) fetchai: fetchai_private_key.txt logging_config: # The logging configurations the AEA project uses disable_existing_loggers: false version: 1 dependencies: {} # The python dependencies the AEA relies on (e.g. plugins). They will be installed when `aea install` is run. ``` ``` yaml period: 0.05 # The period to call agent's act execution_timeout: 0 # The execution time limit on each call to `react` and `act` (0 disables the feature) timeout: 0.05 # The sleep time on each AEA loop spin (only relevant for the `sync` mode) max_reactions: 20 # The maximum number of envelopes processed per call to `react` (only relevant for the `sync` mode) skill_exception_policy: propagate # The exception policy applied to skills (must be one of "propagate", "just_log", or "stop_and_exit") connection_exception_policy: propagate # The exception policy applied to connections (must be one of "propagate", "just_log", or "stop_and_exit") loop_mode: async # The agent loop mode (must be one of "sync" or "async") runtime_mode: threaded # The runtime mode (must be one of "threaded" or "async") and determines how agent loop and multiplexer are run error_handler: None # The error handler to be used. decision_maker_handler: None # The decision maker handler to be used. storage_uri: None # The URI to the storage. data_dir: None # The path to the directory for local files. Defaults to current working directory. ``` ``` yaml public_id: some_author/some_package:0.1.0 # The public id of the connection (must satisfy PUBLIC_ID_REGEX). type: connection # for connections, this must be "connection". config: ... # a dictionary to overwrite the `config` field (see below) ``` ``` yaml public_id: some_author/some_package:0.1.0 # The public id of the connection (must satisfy PUBLIC_ID_REGEX). type: skill # for skills, this must be "skill". behaviours: # override configurations for behaviours behaviour_1: # override configurations for "behaviour_1" args: # arguments for a specific behaviour (see below) foo: bar handlers: # override configurations for handlers handler_1: # override configurations for "handler_1" args: # arguments for a specific handler (see below) foo: bar models: # override configurations for models model_1: # override configurations for "model_1" args: # arguments for a specific model (see below) foo: bar ``` ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: connection # The type of the package; for connections, it must be "connection" description: A scaffold connection # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: QmZvYZ5ECcWwqiNGh8qNTg735wu51HqaLxTSifUxkQ4KGj connection.py: QmagwVgaPgfeXqVTgcpFESA4DYsteSbojz94SLtmnHNAze fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: [] # The list of connection public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: [] # The list of protocol public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). class_name: MyScaffoldConnection # The class name of the class implementing the connection interface. config: # A dictionary containing the kwargs for the connection instantiation. foo: bar excluded_protocols: [] # The list of protocol public ids the package does not permit (each public id must satisfy PUBLIC_ID_REGEX). restricted_to_protocols: [] # The list of protocol public ids the package is limited to (each public id must satisfy PUBLIC_ID_REGEX). dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. is_abstract: false # An optional boolean that if `true` makes the connection ``` ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: contract # The type of the package; for contracts, it must be "contract" description: A scaffold contract # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: QmPBwWhEg3wcH1q9612srZYAYdANVdWLDFWKs7TviZmVj6 contract.py: QmXvjkD7ZVEJDJspEz5YApe5bRUxvZHNi8vfyeVHPyQD5G fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. class_name: MyScaffoldContract # The class name of the class implementing the contract interface. contract_interface_paths: {} # The paths to the contract interfaces (one for each ledger identifier). config: # A dictionary containing the kwargs for the contract instantiation. foo: bar dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. ``` ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: protocol # The type of the package; for protocols, it must be "protocol" description: A scaffold protocol # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: Qmay9PmfeHqqVa3rdgiJYJnzZzTStboQEfpwXDpcgJMHTJ message.py: QmdvAdYSHNdZyUMrK3ue7quHAuSNwgZZSHqxYXyvh8Nie4 serialization.py: QmVUzwaSMErJgNFYQZkzsDhuuT2Ht4EdbGJ443usHmPxVv fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. ``` ``` yaml name: scaffold # Name of the package (must satisfy PACKAGE_REGEX) author: fetchai # Author handle of the package's author (must satisfy AUTHOR_REGEX) version: 0.1.0 # Version of the package (a semantic version number, see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string") type: skill # The type of the package; for skills, it must be "skill" description: A scaffold skill # Description of the package license: Apache-2.0 # License of the package aea_version: '>=1.0.0, <2.0.0' # AEA framework version(s) compatible with the AEA project (a version number that matches PEP 440 version schemes, or a comma-separated list of PEP 440 version specifiers, see https://www.python.org/dev/peps/pep-0440/#version-specifiers) fingerprint: # Fingerprint of package components. __init__.py: QmNkZAetyctaZCUf6ACxP5onGWsSxu2hjSNoFmJ3ta6Lta behaviours.py: QmYa1rczhGTtMJBgCd1QR9uZhhkf45orm7TnGTE5Eizjpy handlers.py: QmZYyTENRr6ecnxx1FeBdgjLiBhFLVn9mqarzUtFQmNUFn my_model.py: QmPaZ6G37Juk63mJj88nParaEp71XyURts8AmmX1axs24V fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. contracts: [] # The list of contract public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: [] # The list of protocol public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). skills: [] # The list of skill public ids the package depends on (each public id must satisfy PUBLIC_ID_REGEX). is_abstract: false # An optional boolean that if `true` makes the skill abstract, i.e. not instantiated by the framework but importable from other skills. Defaults to `false`. behaviours: # The dictionary describing the behaviours immplemented in the package (including their configuration) scaffold: # Name of the behaviour under which it is made available on the skill context. args: # Keyword arguments provided to the skill component on instantiation. foo: bar class_name: MyScaffoldBehaviour # The class name of the class implementing the behaviour interface. handlers: # The dictionary describing the handlers immplemented in the package (including their configuration) scaffold: # Name of the handler under which it is made available on the skill args: # Keyword arguments provided to the skill component on instantiation. foo: bar class_name: MyScaffoldHandler # The class name of the class implementing the handler interface. models: # The dictionary describing the models immplemented in the package (including their configuration) scaffold: # Name of the model under which it is made available on the skill args: # Keyword arguments provided to the skill component on instantiation. foo: bar class_name: MyModel # The class name of the class implementing the model interface. dependencies: {} # The python dependencies the package relies on. They will be installed when `aea install` is run. ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-connection.md ================================================ ``` bash aea scaffold connection my_new_connection ``` ``` yaml connections: [] protocols: [] class_name: MyScaffoldConnection config: foo: bar excluded_protocols: [] restricted_to_protocols: [] dependencies: {} is_abstract: false cert_requests: [] ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-contract.md ================================================ ``` bash aea scaffold contract my_new_contract ``` ``` yaml contract_interface_paths: ethereum: build/my_contract.json ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-decision-maker.md ================================================ ``` yaml decision_maker_handler: config: {} dotted_path: "aea.decision_maker.gop:DecisionMakerHandler" file_path: null ``` ``` bash aea scaffold decision-maker-handler ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-deployment.md ================================================ ``` bash svn export https://github.com/fetchai/agents-aea/branches/main/deploy-image cd deploy-image ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md ================================================ ``` bash aea fetch fetchai/erc1155_deployer:0.34.5 cd erc1155_deployer aea install aea build ``` ``` bash aea create erc1155_deployer cd erc1155_deployer aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/erc1155_deploy:0.31.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-cosmos": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` ``` bash aea config set agent.default_ledger ethereum ``` ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea fetch fetchai/erc1155_client:0.34.5 cd erc1155_client aea install aea build ``` ``` bash aea create erc1155_client cd erc1155_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/erc1155_client:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-cosmos": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` ``` bash aea config set agent.default_ledger ethereum ``` ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash docker run -p 8545:8545 trufflesuite/ganache-cli:latest --verbose --gasPrice=0 --gasLimit=0x1fffffffffffff --account="$(cat erc1155_deployer/ethereum_private_key.txt),1000000000000000000000" --account="$(cat erc1155_client/ethereum_private_key.txt),1000000000000000000000" ``` ``` bash aea get-wealth ethereum ``` ``` bash aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum ``` ``` bash aea run ``` ``` bash registering service on SOEF. ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete erc1155_deployer aea delete erc1155_client ``` ``` yaml default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 ``` ``` yaml default_routing: fetchai/contract_api:1.1.7: fetchai/ledger:0.21.5 fetchai/ledger_api:1.1.7: fetchai/ledger:0.21.5 fetchai/oef_search:1.1.7: fetchai/soef:0.27.6 ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: - SOME_ADDRESS local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md ================================================ ``` bash sudo nano 99-hidraw-permissions.rules ``` ``` bash KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev" ``` ``` bash aea fetch fetchai/generic_seller:0.29.5 cd generic_seller aea eject skill fetchai/generic_seller:0.28.6 cd .. ``` ``` bash aea fetch fetchai/generic_buyer:0.30.5 cd generic_buyer aea eject skill fetchai/generic_buyer:0.27.6 cd .. ``` ``` bash aea init --reset --author fetchai ``` ``` bash aea create my_generic_seller cd my_generic_seller aea install ``` ``` bash aea scaffold skill generic_seller ``` ``` bash aea fingerprint skill fetchai/generic_seller:0.1.0 ``` ``` bash aea create my_generic_buyer cd my_generic_buyer aea install ``` ``` bash aea scaffold skill generic_buyer ``` ``` bash aea fingerprint skill fetchai/generic_buyer:0.1.0 ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` ``` bash aea generate-wealth fetchai --sync ``` ``` bash aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add protocol fetchai/fipa:1.1.7 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea run ``` ``` bash aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add protocol fetchai/fipa:1.1.7 aea add protocol fetchai/signing:1.1.7 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete my_generic_seller aea delete my_generic_buyer ``` ``` yaml name: generic_seller author: fetchai version: 0.1.0 type: skill description: The weather station skill implements the functionality to sell weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmPb5kHYZyhUN87EKmuahyGqDGgqVdGPyfC1KpGC3xfmcP __init__.py: QmTSEedzQySy2nzRCY3F66CBSX52f8s3pWHZTejX4hKC9h behaviours.py: QmS9sPCv2yBnhWsmHeaCptpApMtYZipbR39TXixeGK64Ks dialogues.py: QmdTW8q1xQ7ajFVsWmuV62ypoT5J2b6Hkyz52LFaWuMEtd handlers.py: QmQnQhSaHPUYaJut8bMe2LHEqiZqokMSVfCthVaqrvPbdi strategy.py: QmYTUsfv64eRQDevCfMUDQPx2GCtiMLFdacN4sS1E4Fdfx fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 skills: [] behaviours: service_registration: args: services_interval: 20 class_name: GenericServiceRegistrationBehaviour handlers: fipa: args: {} class_name: GenericFipaHandler ledger_api: args: {} class_name: GenericLedgerApiHandler oef_search: args: {} class_name: GenericOefSearchHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: data_for_sale: generic: data has_data_source: false is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: generic_service service_id: generic_service unit_price: 10 class_name: GenericStrategy is_abstract: false dependencies: {} ``` ``` yaml name: generic_buyer author: fetchai version: 0.1.0 type: skill description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmTR91jm7WfJpmabisy74NR5mc35YXjDU1zQAUKZeHRw8L __init__.py: QmU5vrC8FipyjfS5biNa6qDWdp4aeH5h4YTtbFDmCg8Chj behaviours.py: QmNwvSjEz4kzM3gWtnKbZVFJc2Z85Nb748CWAK4C4Sa4nT dialogues.py: QmNen91qQDWy4bNBKrB3LabAP5iRf29B8iwYss4NB13iNU handlers.py: QmZfudXXbdiREiViuwPZDXoQQyXT2ySQHdF7psQsohZXQy strategy.py: QmcrwaEWvKHDCNti8QjRhB4utJBJn5L8GpD27Uy9zHwKhY fingerprint_ignore_patterns: [] connections: - fetchai/ledger:0.21.5 contracts: [] protocols: - fetchai/default:1.1.7 - fetchai/fipa:1.1.7 - fetchai/ledger_api:1.1.7 - fetchai/oef_search:1.1.7 - fetchai/signing:1.1.7 skills: [] behaviours: search: args: search_interval: 5 class_name: GenericSearchBehaviour transaction: args: max_processing: 420 transaction_interval: 2 class_name: GenericTransactionBehaviour handlers: fipa: args: {} class_name: GenericFipaHandler ledger_api: args: {} class_name: GenericLedgerApiHandler oef_search: args: {} class_name: GenericOefSearchHandler signing: args: {} class_name: GenericSigningHandler models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: is_ledger_tx: true location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_tx_fee: 3550000000000000 max_unit_price: 20 min_quantity: 1 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 service_id: generic_service stop_searching_on_result: true class_name: GenericStrategy is_abstract: false dependencies: {} ``` ``` yaml config: delegate_uri: 127.0.0.1:11001 entry_peers: ['SOME_ADDRESS'] local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md ================================================ ``` bash aea fetch fetchai/generic_seller:0.29.5 --alias my_seller_aea cd my_seller_aea aea install aea build ``` ``` bash aea create my_seller_aea cd my_seller_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/generic_seller:0.28.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea fetch fetchai/generic_buyer:0.30.5 --alias my_buyer_aea cd my_buyer_aea aea install aea build ``` ``` bash aea create my_buyer_aea cd my_buyer_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/generic_buyer:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea add-key fetchai fetchai_private_key.txt --connection ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash cd my_seller_aea aea config set vendor.fetchai.skills.generic_seller.is_abstract false --type bool ``` ``` bash cd my_buyer_aea aea config set vendor.fetchai.skills.generic_buyer.is_abstract false --type bool ``` ``` bash aea run ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete my_seller_aea aea delete my_buyer_aea ``` ``` yaml models: ... strategy: args: currency_id: FET data_for_sale: generic: data has_data_source: false is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: generic_service service_id: generic_service unit_price: 10 class_name: GenericStrategy ``` ``` yaml models: ... strategy: args: currency_id: FET is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_tx_fee: 3550000000000000 max_unit_price: 20 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 service_id: generic_service class_name: GenericStrategy ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: - SOME_ADDRESS local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-gym-example.md ================================================ ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/examples svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` ``` bash pip install numpy gym ``` ``` bash python examples/gym_ex/train.py ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md ================================================ ``` bash mkdir gym_skill_agent svn export https://github.com/fetchai/agents-aea.git/trunk/examples ``` ``` bash pip install numpy gym ``` ``` bash aea fetch fetchai/gym_aea:0.26.5 --alias my_gym_aea cd my_gym_aea aea install ``` ``` bash aea create my_gym_aea cd my_gym_aea ``` ``` bash aea add skill fetchai/gym:0.21.6 ``` ``` bash aea config set agent.default_connection fetchai/gym:0.20.6 ``` ``` bash aea install ``` ``` bash mkdir gyms cp -a ../examples/gym_ex/gyms/. gyms/ ``` ``` bash aea config set vendor.fetchai.connections.gym.config.env 'gyms.env.BanditNArmedRandom' ``` ``` bash aea generate-key fetchai aea add-key fetchai ``` ``` bash aea run ``` ``` bash cd .. aea delete my_gym_aea ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md ================================================ ``` bash aea create my_aea cd my_aea ``` ``` bash aea add connection fetchai/http_server:0.23.6 ``` ``` bash aea config set agent.default_connection fetchai/http_server:0.23.6 ``` ``` bash aea config set vendor.fetchai.connections.http_server.config.api_spec_path "../examples/http_ex/petstore.yaml" ``` ``` bash aea generate-key fetchai aea add-key fetchai ``` ``` bash aea install ``` ``` bash aea scaffold skill http_echo ``` ``` bash aea fingerprint skill fetchai/http_echo:0.21.6 ``` ``` bash aea config set vendor.fetchai.connections.http_server.config.target_skill_id "$(aea config get agent.author)/http_echo:0.1.0" ``` ``` bash aea run ``` ``` yaml handlers: http_handler: args: {} class_name: HttpHandler models: default_dialogues: args: {} class_name: DefaultDialogues http_dialogues: args: {} class_name: HttpDialogues ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-language-agnostic-definition.md ================================================ ``` proto syntax = "proto3"; package aea; message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; bytes message = 4; string uri = 5; } ``` ``` proto import "google/protobuf/struct.proto"; message DialogueMessage { int32 message_id = 1; string dialogue_starter_reference = 2; string dialogue_responder_reference = 3; int32 target = 4; bytes content = 5; } message Message { oneof message { google.protobuf.Struct body = 1; DialogueMessage dialogue_message = 2; } } ``` ``` proto syntax = "proto3"; package aea.fetchai.default; message DefaultMessage{ // Custom Types message ErrorCode{ enum ErrorCodeEnum { UNSUPPORTED_PROTOCOL = 0; DECODING_ERROR = 1; INVALID_MESSAGE = 2; UNSUPPORTED_SKILL = 3; INVALID_DIALOGUE = 4; } ErrorCodeEnum error_code = 1; } // Performatives and contents message Bytes_Performative{ bytes content = 1; } message Error_Performative{ ErrorCode error_code = 1; string error_msg = 2; map error_data = 3; } message End_Performative{} oneof performative{ Bytes_Performative bytes = 5; End_Performative end = 6; Error_Performative error = 7; } } ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-ledger-integration.md ================================================ ``` bash git clone https://github.com/fetchai/fetchd.git cd fetchd git checkout release/v0.8.4 make install ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-logging.md ================================================ ``` bash aea create my_aea cd my_aea ``` ``` yaml agent_name: my_aea author: fetchai version: 0.1.0 description: '' license: Apache-2.0 aea_version: 0.6.0 fingerprint: {} fingerprint_ignore_patterns: [] connections: - fetchai/stub:0.21.3 contracts: [] protocols: - fetchai/default:1.1.7 skills: - fetchai/error:0.18.6 default_connection: fetchai/stub:0.21.3 default_ledger: fetchai required_ledgers: - fetchai logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} ``` ``` yaml logging_config: version: 1 disable_existing_loggers: False formatters: standard: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: logfile: class: logging.FileHandler formatter: standard level: DEBUG filename: logconfig.log console: class: logging.StreamHandler formatter: standard level: DEBUG loggers: aea: handlers: - logfile - console level: DEBUG propagate: False ``` ``` yaml logging_config: version: 1 disable_existing_loggers: false formatters: standard: format: '%(asctime)s [%(levelname)s] %(name)s: %(message)s' handlers: http: class: logging.handlers.HTTPHandler formatter: standard level: INFO host: localhost:5000 url: /stream method: POST loggers: aea: handlers: - http level: INFO propagate: false ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md ================================================ ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` ``` bash aea fetch fetchai/ml_data_provider:0.32.5 cd ml_data_provider aea install aea build ``` ``` bash aea create ml_data_provider cd ml_data_provider aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/ml_data_provider:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea fetch fetchai/ml_model_trainer:0.33.5 cd ml_model_trainer aea install aea build ``` ``` bash aea create ml_model_trainer cd ml_model_trainer aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/ml_train:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea add-key fetchai fetchai_private_key.txt --connection ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea run ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete ml_data_provider aea delete ml_model_trainer ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: - SOME_ADDRESS local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-oracle-demo.md ================================================ ``` bash aea fetch fetchai/coin_price_oracle:0.17.6 cd coin_price_oracle aea install ``` ``` bash aea create coin_price_oracle cd coin_price_oracle aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/ledger:0.21.5 aea add connection fetchai/prometheus:0.9.6 aea add skill fetchai/advanced_data_request:0.7.6 aea add skill fetchai/simple_oracle:0.16.5 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/ledger:0.21.5 aea install ``` ``` bash aea config set --type str vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url "https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd" ``` ``` bash aea config set --type list vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs '[{"name": "price", "json_path": "fetch-ai.usd"}]' ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.oracle_value_name price ``` ``` bash aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5" }' ``` ``` bash aea config set agent.default_ledger fetchai ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id fetchai aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function update_oracle_value ``` ``` bash LEDGER_ID=fetchai ``` ``` bash LEDGER_ID=ethereum ``` ``` bash aea config set agent.default_ledger ethereum ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id ethereum aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function updateOracleValue ``` ``` bash aea generate-key $LEDGER_ID --add-key ``` ``` bash aea generate-wealth $LEDGER_ID ``` ``` bash aea fetch fetchai/coin_price_oracle_client:0.12.6 cd coin_price_oracle_client aea install ``` ``` bash aea create coin_price_oracle_client cd coin_price_oracle_client aea add connection fetchai/http_client:0.24.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/simple_oracle_client:0.13.5 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/ledger:0.21.5 aea install ``` ``` bash aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5" }' ``` ``` bash aea config set agent.default_ledger fetchai ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.ledger_id fetchai aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.query_function query_oracle_value ``` ``` bash aea config set agent.default_ledger ethereum ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.ledger_id ethereum aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.query_function queryOracleValue ``` ``` bash aea generate-key $LEDGER_ID --add-key ``` ``` bash aea generate-wealth $LEDGER_ID ``` ``` bash docker run -p 8545:8545 trufflesuite/ganache-cli:latest --verbose --gasPrice=0 --gasLimit=0x1fffffffffffff --account="$(cat coin_price_oracle/ethereum_private_key.txt),1000000000000000000000" --account="$(cat coin_price_oracle_client/ethereum_private_key.txt),1000000000000000000000" ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle.models.strategy.args.erc20_address $ERC20_ADDRESS ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.erc20_address $ERC20_ADDRESS ``` ``` bash aea run ``` ``` bash info: [coin_price_oracle] Oracle contract successfully deployed at address: ... ... info: [coin_price_oracle] Oracle role successfully granted! ... info: [coin_price_oracle] Oracle value successfully updated! ``` ``` bash aea config set vendor.fetchai.skills.simple_oracle_client.models.strategy.args.oracle_contract_address $ORACLE_ADDRESS ``` ``` bash Oracle contract successfully deployed at address: ORACLE_ADDRESS ``` ``` bash aea run ``` ``` bash info: [coin_price_oracle_client] Oracle client contract successfully deployed at address: ... ... info: [coin_price_oracle_client] Oracle value successfully requested! ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md ================================================ ``` bash aea fetch fetchai/thermometer_aea:0.30.5 --alias my_thermometer_aea cd my_thermometer_aea aea install aea build ``` ``` bash aea create my_thermometer_aea cd my_thermometer_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea fetch fetchai/thermometer_client:0.32.5 --alias my_thermometer_client cd my_thermometer_client aea install aea build ``` ``` bash aea create my_thermometer_client cd my_thermometer_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer_client:0.26.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea install aea build ``` ``` bash aea eject skill fetchai/thermometer:0.27.6 ``` ``` bash aea fingerprint skill {YOUR_AUTHOR_HANDLE}/thermometer:0.1.0 ``` ``` bash aea run ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete my_thermometer_aea aea delete my_thermometer_client ``` ``` yaml models: ... strategy: args: currency_id: FET data_for_sale: temperature: 26 has_data_source: false is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: thermometer_data service_id: thermometer_data unit_price: 10 class_name: Strategy dependencies: SQLAlchemy: {} ``` ``` yaml models: ... strategy: args: currency_id: FET is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_tx_fee: 3550000000000000 max_unit_price: 20 search_query: constraint_type: == search_key: seller_service search_value: thermometer_data search_radius: 5.0 service_id: thermometer_data class_name: Strategy ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: - SOME_ADDRESS local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md ================================================ ``` bash pip install aea-ledger-fetchai ``` ``` bash aea create my_genesis_aea cd my_genesis_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection aea issue-certificates ``` ``` bash aea run --connections fetchai/p2p_libp2p:0.27.5 ``` ``` bash aea create my_other_aea cd my_other_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection aea issue-certificates ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run --connections fetchai/p2p_libp2p:0.27.5 ``` ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/packages/fetchai/connections/p2p_libp2p cd p2p_libp2p go build chmod +x libp2p_node ``` ``` bash docker build -t acn_node_standalone -f scripts/acn/Dockerfile . ``` ``` bash python3 run_acn_node_standalone.py libp2p_node --config-from-env ``` ``` bash python3 run_acn_node_standalone.py libp2p_node --config-from-file ``` ``` bash docker run -v :/acn/acn_config -it acn_node_standalone --config-from-file /acn/acn_config ``` ``` bash python3 run_acn_node_standalone.py libp2p_node --key-file \ --uri --uri-external \ --uri-delegate \ --entry-peers-maddrs ... ``` ``` bash docker run -v :/acn/key.txt -it acn_node_standalone --key-file /acn/key.txt \ --uri --uri-external \ --uri-delegate \ --entry-peers-maddrs ... ``` ``` yaml /dns4/acn.fetch.ai/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx /dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: null entry_peers: [/dns4/acn.fetch.ai/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx,/dns4/acn.fetch.ai/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW] public_uri: null local_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md ================================================ ``` bash aea_name/ aea-config.yaml YAML configuration of the AEA fetchai_private_key.txt The private key file connections/ Directory containing all the connections developed as part of the given project. connection_1/ First connection ... ... connection_n/ nth connection contracts/ Directory containing all the contracts developed as part of the given project. connection_1/ First connection ... ... connection_n/ nth connection protocols/ Directory containing all the protocols developed as part of the given project. protocol_1/ First protocol ... ... protocol_m/ mth protocol skills/ Directory containing all the skills developed as part of the given project. skill_1/ First skill ... ... skill_k/ kth skill vendor/ Directory containing all the added resources from the registry, sorted by author. author_1/ Directory containing all the resources added from author_1 connections/ Directory containing all the added connections from author_1 ... ... protocols/ Directory containing all the added protocols from author_1 ... ... skills/ Directory containing all the added skills from author_1 ... ... ``` ``` yaml connections: - fetchai/stub:0.21.3 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-performance-benchmark.md ================================================ ``` bash Usage: cpu_burn.py [OPTIONS] [ARGS]... Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop :return: None ARGS is function arguments in format: `run_time,sleep` default ARGS is `10,0.0001` Options: --timeout FLOAT Executor timeout in seconds [default: 10.0] --period FLOAT Period for measurement [default: 0.1] -N, --num-executions INTEGER Number of runs for each case [default: 1] -P, --plot INTEGER X axis parameter idx --help Show this message and exit. ``` ``` bash Test execution timeout: 10.0 Test execution measure period: 0.1 Tested function name: cpu_burn Tested function description: Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop :return: None Tested function argument names: ['run_time', 'sleep'] Tested function argument default values: [10, 0.0001] == Report created 2020-04-27 15:14:56.076549 == Arguments are `[10, 0.0001]` Number of runs: 1 Number of time terminated: 0 Time passed (seconds): 10.031443119049072 ± 0 cpu min (%): 0.0 ± 0 cpu max (%): 10.0 ± 0 cpu mean (%): 3.4 ± 0 mem min (kb): 53.98828125 ± 0 mem max (kb): 53.98828125 ± 0 mem mean (kb): 53.98828125 ± 0 ``` ``` bash Test execution timeout: 10.0 Test execution measure period: 0.1 Tested function name: cpu_burn Tested function description: Do nothing, just burn cpu to check cpu load changed on sleep. :param benchmark: benchmark special parameter to communicate with executor :param run_time: time limit to run this function :param sleep: time to sleep in loop :return: None Tested function argument names: ['run_time', 'sleep'] Tested function argument default values: [10, 0.0001] == Report created 2020-04-27 15:38:17.849535 == Arguments are `(3, 1e-05)` Number of runs: 5 Number of time terminated: 0 Time passed (seconds): 3.0087939262390138 ± 0.0001147521277690166 cpu min (%): 0.0 ± 0.0 cpu max (%): 11.0 ± 2.23606797749979 cpu mean (%): 6.2 ± 0.18257418583505522 mem min (kb): 54.0265625 ± 0.11180339887498948 mem max (kb): 54.0265625 ± 0.11180339887498948 mem mean (kb): 54.0265625 ± 0.11180339887498948 == Report created 2020-04-27 15:38:32.947308 == Arguments are `(3, 0.001)` Number of runs: 5 Number of time terminated: 0 Time passed (seconds): 3.014109659194946 ± 0.0004416575764579524 cpu min (%): 0.0 ± 0.0 cpu max (%): 8.0 ± 2.7386127875258306 cpu mean (%): 1.9986666666666666 ± 0.002981423969999689 mem min (kb): 53.9890625 ± 0.10431954926750306 mem max (kb): 53.9890625 ± 0.10431954926750306 mem mean (kb): 53.9890625 ± 0.10431954926750306 == Report created 2020-04-27 15:38:48.067511 == Arguments are `(3, 0.01)` Number of runs: 5 Number of time terminated: 0 Time passed (seconds): 3.0181806087493896 ± 0.0022409499756841883 cpu min (%): 0.0 ± 0.0 cpu max (%): 1.0 ± 2.23606797749979 cpu mean (%): 0.06666666666666667 ± 0.14907119849998599 mem min (kb): 53.9078125 ± 0.11487297672320501 mem max (kb): 53.9078125 ± 0.11487297672320501 mem mean (kb): 53.9078125 ± 0.11487297672320501 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-por.md ================================================ ``` yaml cert_requests: - identifier: acn ledger_id: fetchai not_after: '2023-01-01' not_before: '2022-01-01' public_key: fetchai message_format: '{public_key}' save_path: .certs/conn_cert.txt ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-protocol-generator.md ================================================ ``` bash aea generate protocol ``` ``` bash aea generate protocol --l ``` ``` bash aea create my_aea cd my_aea ``` ``` bash aea generate protocol ../examples/protocol_specification_ex/sample.yaml ``` ``` yaml --- name: two_party_negotiation author: fetchai version: 0.1.0 description: An example of a protocol specification that describes a protocol for bilateral negotiation. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' speech_acts: cfp: query: ct:Query propose: price: pt:float proposal: pt:dict[pt:str, pt:str] conditions: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:str], pt:set[pt:str]]] resources: pt:list[pt:bytes] accept: {} decline: {} ... --- ct:Query: | bytes query_bytes = 1; ... --- initiation: [cfp] reply: cfp: [propose, decline] propose: [propose, accept, decline] accept: [] decline: [] termination: [accept, decline] roles: {buyer, seller} end_states: [agreement_reached, agreement_unreached] keep_terminal_state_dialogues: true ... ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md ================================================ ``` bash python3 --version ``` ``` bash sudo apt-get install python3.7-dev ``` ``` bash curl https://raw.githubusercontent.com/fetchai/agents-aea/main/scripts/install.sh --output install.sh chmod +x install.sh ./install.sh ``` ```bash docker pull fetchai/aea-user:latest ``` ```bash docker run -it -v $(pwd):/agents --workdir=/agents fetchai/aea-user:latest ``` ```bash docker run -it -v %cd%:/agents --workdir=/agents fetchai/aea-user:latest ``` ``` bash mkdir my_aea_projects/ cd my_aea_projects/ ``` ``` bash which pipenv ``` ``` bash touch Pipfile && pipenv --python 3.7 && pipenv shell ``` ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/examples svn export https://github.com/fetchai/agents-aea.git/trunk/scripts svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` ``` bash pip install aea[all] ``` ``` bash sudo apt-get install python3.7-dev ``` ``` bash aea init ``` ``` bash aea register ``` ``` bash Do you have a Registry account? [y/N]: n Create a new account on the Registry now: Username: fetchai Email: hello@fetch.ai Password: Please make sure that passwords are equal. Confirm password: _ _____ _ / \ | ____| / \ / _ \ | _| / _ \ / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ v1.2.5 AEA configurations successfully initialized: {'author': 'fetchai'} ``` ``` bash aea fetch fetchai/my_first_aea:0.28.5 cd my_first_aea ``` ``` bash aea create my_first_aea cd my_first_aea ``` ``` bash aea add connection fetchai/stub:0.21.3 ``` ``` bash aea add skill fetchai/echo:0.20.6 ``` ``` bash TO,SENDER,PROTOCOL_ID,ENCODED_MESSAGE, ``` ``` bash recipient_aea,sender_aea,fetchai/default:1.0.0,\x08\x01\x12\x011*\x07\n\x05hello, ``` ``` bash aea install ``` ``` bash aea generate-key fetchai aea add-key fetchai ``` ``` bash aea run ``` ``` bash _ _____ _ / \ | ____| / \ / _ \ | _| / _ \ / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ v1.1.1 Starting AEA 'my_first_aea' in 'async' mode ... info: Echo Handler: setup method called. info: Echo Behaviour: setup method called. info: [my_first_aea]: Start processing messages... info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ... ``` ``` bash cd my_first_aea aea interact ``` ``` bash info: Echo Behaviour: act method called. info: Echo Handler: message=Message(dialogue_reference=('1', '') message_id=1 target=0 performative=bytes content=b'hello'), sender=my_first_aea_interact info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ``` ``` bash echo 'my_first_aea,sender_aea,fetchai/default:1.0.0,\x12\x10\x08\x01\x12\x011*\t*\x07\n\x05hello,' >> input_file ``` ``` bash info: Echo Behaviour: act method called. Echo Handler: message=Message(sender=sender_aea,to=my_first_aea,content=b'hello',dialogue_reference=('1', ''),message_id=1,performative=bytes,target=0), sender=sender_aea info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ``` ``` bash info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ^C my_first_aea interrupted! my_first_aea stopping ... info: Echo Handler: teardown method called. info: Echo Behaviour: teardown method called. ``` ``` bash aea interact ``` ``` bash pytest test.py ``` ``` bash aea delete my_first_aea ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-raspberry-set-up.md ================================================ ``` bash sudo apt update -y sudo apt-get update sudo apt-get dist-upgrade ``` ``` bash sudo apt install cmake golang -y ``` ``` bash sudo apt install gfortran libatlas-base-dev libopenblas-dev -y ``` ``` bash sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024 sudo /sbin/mkswap /var/swap.1 sudo chmod 600 /var/swap.1 sudo /sbin/swapon /var/swap.1 ``` ``` bash pip install numpy --upgrade pip install scikit-image ``` ``` bash sudo swapoff /var/swap.1 sudo rm /var/swap.1 ``` ``` bash export PATH="$HOME/.local/bin:$PATH" ``` ``` bash pip install aea[all] ``` ``` bash aea --version ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-scaffolding.md ================================================ ``` bash aea create my_aea --author "fetchai" cd my_aea ``` ``` bash aea scaffold skill my_skill ``` ``` bash aea scaffold protocol my_protocol ``` ``` bash aea scaffold contract my_contract ``` ``` bash aea scaffold connection my_connection ``` ``` bash aea fingerprint [package_name] [public_id] ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md ================================================ ``` bash aea create my_aea && cd my_aea aea scaffold skill my_search ``` ``` bash aea fingerprint skill fetchai/my_search:0.1.0 ``` ``` bash aea add protocol fetchai/oef_search:1.1.7 ``` ``` bash aea add connection fetchai/soef:0.27.6 aea add connection fetchai/p2p_libp2p:0.27.5 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` ``` bash aea fetch fetchai/simple_service_registration:0.32.5 && cd simple_service_registration && aea install && aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea run ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAm1uJpFsqSgHStJdtTBPpDme1fo8uFEvvY182D2y89jQuj"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` yaml name: my_search author: fetchai version: 0.1.0 type: skill description: A simple search skill utilising the SOEF search node. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/oef_search:1.1.7 skills: [] behaviours: my_search_behaviour: args: location: latitude: 51.5194 longitude: 0.127 search_query: constraint_type: == search_key: seller_service search_value: generic_service search_radius: 5.0 tick_interval: 5 class_name: MySearchBehaviour handlers: my_search_handler: args: {} class_name: MySearchHandler models: oef_search_dialogues: args: {} class_name: OefSearchDialogues dependencies: aea-ledger-fetchai: version: <2.0.0,>=1.0.0 is_abstract: false ``` ``` yaml name: simple_service_registration author: fetchai version: 0.20.0 type: skill description: The simple service registration skills is a skill to register a service. license: Apache-2.0 aea_version: '>=1.0.0, <2.0.0' fingerprint: README.md: QmUgCcR7sDBQeeCBRKwDT7tPBTi3t4zSibyEqR3xdQUKmh __init__.py: QmZd48HmYDr7FMxNaVeGfWRvVtieEdEV78hd7h7roTceP2 behaviours.py: QmQHf6QL5aBtLJ34D2tdcbjJLbzom9gaA3HWgRn3rWyigM dialogues.py: QmTT9dvFhWt6qvxjwBfMFDTrgEtgWbvgANYafyRg2BXwcR handlers.py: QmZqPt8toGbJgTT6NZBLxjkusrQCZ8GmUEwcmqZ1sd7DpG strategy.py: QmVXfQpk4cjDw576H2ELE12tEiN5brPkwvffvcTeMbsugA fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: - fetchai/oef_search:1.1.7 skills: [] behaviours: service: args: max_soef_registration_retries: 5 services_interval: 30 class_name: ServiceRegistrationBehaviour handlers: oef_search: args: {} class_name: OefSearchHandler models: oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: classification: piece: classification value: seller location: latitude: 51.5194 longitude: 0.127 personality_data: piece: genus value: data service_data: key: seller_service value: generic_service class_name: Strategy dependencies: {} is_abstract: false ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-skill.md ================================================ ``` yaml name: echo authors: fetchai version: 0.1.0 license: Apache-2.0 behaviours: echo: class_name: EchoBehaviour args: tick_interval: 1.0 handlers: echo: class_name: EchoHandler args: foo: bar models: {} dependencies: {} protocols: - fetchai/default:1.1.7 ``` ``` bash aea scaffold error-handler ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md ================================================ ``` bash aea fetch fetchai/tac_controller_contract:0.32.5 cd tac_controller_contract aea install aea build ``` ``` bash aea create tac_controller_contract cd tac_controller_contract aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_control_contract:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set vendor.fetchai.connections.soef.config.chain_identifier fetchai_v2_misc aea config set --type bool vendor.fetchai.skills.tac_control.is_abstract true aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "fetchai", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` ``` bash aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_one cd tac_participant_one aea install aea build cd .. aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_two cd tac_participant_two aea install aea build ``` ``` bash aea create tac_participant_one aea create tac_participant_two ``` ``` bash cd tac_participant_one aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set vendor.fetchai.connections.soef.config.chain_identifier fetchai_v2_misc aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "fetchai", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` ``` bash cd tac_participant_two aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set vendor.fetchai.connections.soef.config.chain_identifier fetchai_v2_misc aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "fetchai", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea config get vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time '01 01 2020 00:01' ``` ``` bash aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time "$(date -d "5 minutes" +'%d %m %Y %H:%M')" ``` ``` bash aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11002", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" }' ``` ``` bash aea get-address fetchai ``` ``` bash aea get-wealth fetchai ``` ``` bash aea run ``` ``` bash aea launch tac_participant_one tac_participant_two ``` ``` bash aea delete tac_controller_contract aea delete tac_participant_one aea delete tac_participant_two ``` ``` bash aea fetch fetchai/tac_controller_contract:0.32.5 cd tac_controller_contract aea install aea build ``` ``` bash aea create tac_controller_contract cd tac_controller_contract aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_control_contract:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set --type bool vendor.fetchai.skills.tac_control.is_abstract true aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' aea install aea build ``` ``` bash aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_one cd tac_participant_one aea install aea build cd .. aea fetch fetchai/tac_participant_contract:0.22.5 --alias tac_participant_two cd tac_participant_two aea install aea build ``` ``` bash aea create tac_participant_one aea create tac_participant_two ``` ``` bash cd tac_participant_one aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "ethereum", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` ``` bash cd tac_participant_two aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}, "aea-ledger-ethereum": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '''[{"identifier": "acn", "ledger_id": "ethereum", "message_format": "'{public_key}'", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]''' aea install aea build ``` ```bash aea config set agent.default_ledger ethereum json=$(printf '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt"}]') aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests "$json" aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum ``` ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea config get vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time '01 01 2020 00:01' ``` ``` bash aea config set vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time "$(date -d "5 minutes" +'%d %m %Y %H:%M')" ``` ```bash aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.27.5 -u public_uri ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11002", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" }' ``` ``` bash docker run -p 8545:8545 trufflesuite/ganache-cli:latest --verbose --gasPrice=0 --gasLimit=0x1fffffffffffff --account="$(cat tac_controller_contract/ethereum_private_key.txt),1000000000000000000000" --account="$(cat tac_participant_one/ethereum_private_key.txt),1000000000000000000000" --account="$(cat tac_participant_two/ethereum_private_key.txt),1000000000000000000000" ``` ``` bash aea get-wealth ethereum ``` ``` bash aea run ``` ``` bash aea launch tac_participant_one tac_participant_two ``` ``` bash aea delete tac_controller_contract aea delete tac_participant_one aea delete tac_participant_two ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md ================================================ ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` ``` bash { "delegate_uri": "127.0.0.1:11002", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" } ``` ``` bash aea fetch fetchai/tac_controller:0.30.5 cd tac_controller aea install aea build ``` ``` bash aea create tac_controller cd tac_controller aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_control:0.25.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea fetch fetchai/tac_participant:0.32.5 --alias tac_participant_one cd tac_participant_one aea install aea build cd .. aea fetch fetchai/tac_participant:0.32.5 --alias tac_participant_two cd tac_participant_two aea build ``` ``` bash aea create tac_participant_one aea create tac_participant_two ``` ``` bash cd tac_participant_one aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea install aea build ``` ``` bash cd tac_participant_two aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/tac_participation:0.25.6 aea add skill fetchai/tac_negotiation:0.29.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea config set --type dict agent.decision_maker_handler \ '{ "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": null }' aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea config get vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time aea config set vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time '01 01 2020 00:01' ``` ``` bash aea config set vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time "$(date -d "2 minutes" +'%d %m %Y %H:%M')" ``` ``` bash aea run ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11002", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002" }' ``` ``` bash aea run ``` ``` bash aea launch tac_participant_one tac_participant_two ``` ``` bash aea delete tac_controller aea delete tac_participant_one aea delete tac_participant_two ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: ['SOME_ADDRESS'] local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11002 entry_peers: ['SOME_ADDRESS'] local_uri: 127.0.0.1:9002 log_file: libp2p_node.log public_uri: 127.0.0.1:9002 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-tac.md ================================================ ``` bash git clone git@github.com:fetchai/agents-tac.git --recursive && cd agents-tac ``` ``` bash which pipenv ``` ``` bash pipenv --python 3.7 && pipenv shell ``` ``` bash pipenv install ``` ``` bash python setup.py install ``` ``` bash python scripts/launch.py ``` ``` bash git clone git@github.com:fetchai/agents-tac.git --recursive && cd agents-tac pipenv --python 3.7 && pipenv shell python setup.py install cd sandbox && docker-compose build docker-compose up ``` ``` bash pipenv shell python templates/v1/basic.py --name my_agent --dashboard ``` ``` bash docker stop $(docker ps -q) ``` ``` bash # mac docker ps -q | xargs docker stop ; docker system prune -a ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md ================================================ ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` ``` bash aea fetch fetchai/thermometer_aea:0.30.5 --alias my_thermometer_aea cd my_thermometer_aea aea install aea build ``` ``` bash aea create my_thermometer_aea cd my_thermometer_aea aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer:0.27.6 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` ``` bash aea fetch fetchai/thermometer_client:0.32.5 --alias my_thermometer_client cd my_thermometer_client aea install aea build ``` ``` bash aea create my_thermometer_client cd my_thermometer_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/thermometer_client:0.26.6 aea install aea build aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea add-key fetchai fetchai_private_key.txt --connection ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea run ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete my_thermometer_aea aea delete my_thermometer_client ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: - SOME_ADDRESS local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-wealth.md ================================================ ``` bash pip install aea-ledger-fetchai ``` ``` bash pip install aea-ledger-ethereum ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` ``` bash aea get-address fetchai ``` ``` bash aea get-address ethereum ``` ``` bash aea get-wealth fetchai ``` ``` bash aea get-wealth ethereum ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea generate-wealth ethereum ``` ================================================ FILE: tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md ================================================ ``` bash { "delegate_uri": "127.0.0.1:11001", "entry_peers": ["REPLACE_WITH_MULTI_ADDRESS_HERE"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" } ``` ``` bash aea fetch fetchai/weather_station:0.32.5 --alias my_weather_station cd my_weather_station aea install aea build ``` ``` bash aea create my_weather_station cd my_weather_station aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/weather_station:0.27.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea fetch fetchai/weather_client:0.33.5 --alias my_weather_client cd my_weather_client aea install aea build ``` ``` bash aea create my_weather_client cd my_weather_client aea add connection fetchai/p2p_libp2p:0.27.5 aea add connection fetchai/soef:0.27.6 aea add connection fetchai/ledger:0.21.5 aea add skill fetchai/weather_client:0.26.6 aea config set --type dict agent.dependencies \ '{ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"} }' aea config set agent.default_connection fetchai/p2p_libp2p:0.27.5 aea config set --type dict agent.default_routing \ '{ "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6" }' aea install aea build ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt aea add-key fetchai fetchai_private_key.txt --connection ``` ``` bash aea generate-wealth fetchai ``` ``` bash aea generate-key fetchai fetchai_connection_private_key.txt aea add-key fetchai fetchai_connection_private_key.txt --connection ``` ``` bash aea issue-certificates ``` ``` bash aea run ``` ``` bash aea config set --type dict vendor.fetchai.connections.p2p_libp2p.config \ '{ "delegate_uri": "127.0.0.1:11001", "entry_peers": ["SOME_ADDRESS"], "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001" }' ``` ``` bash aea run ``` ``` bash cd .. aea delete my_weather_station aea delete my_weather_client ``` ``` yaml --- public_id: fetchai/p2p_libp2p:0.27.5 type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: - SOME_ADDRESS local_uri: 127.0.0.1:9001 log_file: libp2p_node.log public_uri: 127.0.0.1:9001 ``` ================================================ FILE: tests/test_docs/test_bash_yaml/test_demo_docs.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the multiplexer-standalone.md file.""" import logging import os from pathlib import Path from tests.conftest import ROOT_DIR from tests.test_docs.helper import extract_code_blocks logger = logging.getLogger(__name__) class TestDemoDocs: """This class contains the tests for the bash/yaml-blocks in *.md file.""" BASH_DIR_PATH = Path(ROOT_DIR, "tests", "test_docs", "test_bash_yaml", "md_files") def _test_blocks(self, filename: str, filter_: str): """Test code blocks of a certain type determined by 'filter_' param.""" bash_file = Path(self.BASH_DIR_PATH, filename).read_text(encoding="utf-8") md_path = os.path.join(ROOT_DIR, "docs", filename.replace("bash-", "")) bash_code_blocks = extract_code_blocks(filepath=md_path, filter_=filter_) for blocks in bash_code_blocks: assert blocks in bash_file, "[{}]: FAILED. Code must be identical".format( filename ) logger.info( f"[{filename}]: PASSED. Tested {len(bash_code_blocks)} '{filter_}' blocks." ) def test_code_blocks_exist(self): """Test that all the code-blocks exist in the python file.""" logger.info(os.listdir(self.BASH_DIR_PATH)) for file in os.listdir(self.BASH_DIR_PATH): if not file.endswith(".md"): continue # in the future, we might add other filters like "python" self._test_blocks(file, "bash") self._test_blocks(file, "yaml") ================================================ FILE: tests/test_docs/test_build_aea_programmatically/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the 'build-aea-programmatically' modules.""" ================================================ FILE: tests/test_docs/test_build_aea_programmatically/programmatic_aea.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This scripts contains code from agent-vs-aea.md file.""" import os import time from threading import Thread from aea_ledger_fetchai import FetchAICrypto from aea.aea_builder import AEABuilder from aea.configurations.base import SkillConfig from aea.crypto.helpers import PRIVATE_KEY_PATH_SCHEMA, create_private_key from aea.helpers.file_io import write_with_lock from aea.skills.base import Skill ROOT_DIR = "./" INPUT_FILE = "input_file" OUTPUT_FILE = "output_file" FETCHAI_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(FetchAICrypto.identifier) def run(): """Run demo.""" # Create a private key create_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added builder = AEABuilder() builder.set_name("my_aea") builder.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) # Add the stub connection (assuming it is present in the local directory 'packages') builder.add_connection("./packages/fetchai/connections/stub") # Add the echo skill (assuming it is present in the local directory 'packages') builder.add_skill("./packages/fetchai/skills/echo") # create skill and handler manually from aea.protocols.base import Message from aea.skills.base import Handler from packages.fetchai.protocols.default.message import DefaultMessage class DummyHandler(Handler): """Dummy handler to handle messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id def setup(self) -> None: """Noop setup.""" def teardown(self) -> None: """Noop teardown.""" def handle(self, message: Message) -> None: """Handle incoming message.""" self.context.logger.info("You got a message: {}".format(str(message))) config = SkillConfig(name="test_skill", author="fetchai") skill = Skill(configuration=config) dummy_handler = DummyHandler( name="dummy_handler", skill_context=skill.skill_context ) skill.handlers.update({dummy_handler.name: dummy_handler}) builder.add_component_instance(skill) # Create our AEA my_aea = builder.build() # Set the AEA running in a different thread try: t = Thread(target=my_aea.start) t.start() # Wait for everything to start up time.sleep(4) # Create a message inside an envelope and get the stub connection to pass it on to the echo skill message_text = b"my_aea,other_agent,fetchai/default:1.0.0,\x12\x10\x08\x01\x12\x011*\t*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) print(b"input message: " + message_text) # Wait for the envelope to get processed time.sleep(4) # Read the output envelope generated by the echo skill with open(OUTPUT_FILE, "rb") as f: print(b"output message: " + f.readline()) finally: # Shut down the AEA my_aea.stop() t.join() t = None if __name__ == "__main__": run() ================================================ FILE: tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the build-aea-programmatically.md file.""" import os from pathlib import Path from aea.configurations.constants import DEFAULT_PRIVATE_KEY_FILE from aea.test_tools.test_cases import BaseAEATestCase from tests.conftest import CUR_PATH, ROOT_DIR from tests.test_docs.helper import extract_code_blocks, extract_python_code from tests.test_docs.test_build_aea_programmatically.programmatic_aea import run MD_FILE = "docs/build-aea-programmatically.md" PY_FILE = "test_docs/test_build_aea_programmatically/programmatic_aea.py" class TestProgrammaticAEA(BaseAEATestCase): """This class contains the tests for the code-blocks in the build-aea-programmatically.md file.""" @classmethod def setup_class(cls): """Setup the test class.""" super().setup_class() doc_path = os.path.join(ROOT_DIR, MD_FILE) cls.code_blocks = extract_code_blocks(filepath=doc_path, filter_="python") test_code_path = os.path.join(CUR_PATH, PY_FILE) cls.python_file = extract_python_code(test_code_path) def test_read_md_file(self): """Read the code blocks. Last block should be the whole code.""" assert ( self.code_blocks[-1] == self.python_file ), "Files must be exactly the same." def test_run_agent(self): """Run the agent from the file.""" run() assert os.path.exists(Path(self.t, "input_file")) assert os.path.exists(Path(self.t, "output_file")) assert os.path.exists(Path(self.t, DEFAULT_PRIVATE_KEY_FILE)) message_text_1 = b"other_agent,my_aea,fetchai/default:1.0.0," message_text_2 = b"hello," path = os.path.join(self.t, "output_file") msg = Path(path).read_bytes() assert message_text_1 in msg assert message_text_2 in msg def test_code_blocks_exist(self): """Test that all the code-blocks exist in the python file.""" for block in self.code_blocks: assert ( block in self.python_file ), "Code-block doesn't exist in the python file." ================================================ FILE: tests/test_docs/test_cli_commands.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of cli-commands.md file.""" import pprint import re from pathlib import Path from aea.cli import cli from tests.conftest import ROOT_DIR from tests.test_docs.helper import BaseTestMarkdownDocs IGNORE_MATCHES = ["`-v DEBUG run`", "`config set [path] [--type TYPE]`"] class TestCliCommands(BaseTestMarkdownDocs): """Test cli-commands.md documentation.""" DOC_PATH = Path(ROOT_DIR, "docs", "cli-commands.md") def test_cli_commands(self): """Test CLI commands.""" commands_raw = re.compile(r"\| `.*` +\|").findall(self.doc_content) commands_raw = [ re.compile(r"`([A-Za-z0-9\-_]+) ?.*`").search(s) for s in commands_raw ] commands_raw = list( filter(lambda x: x.group(0) not in IGNORE_MATCHES, commands_raw) ) actual_commands = list(map(lambda match: match.group(1), commands_raw)) actual_commands_set = set(actual_commands) expected_commands = set(cli.commands.keys()) # test no duplicates assert len(actual_commands) == len( actual_commands_set ), "Found duplicate commands in the documentation." # test that there is no missing command missing = expected_commands.difference(actual_commands) assert ( len(missing) == 0 ), f"Missing the following commands: {pprint.pformat(missing)}" # test that there are no more commands more = actual_commands_set.difference(expected_commands) assert len(more) == 0, f"There are unknown commands: {pprint.pformat(missing)}" # test that they are in the same order. actual = actual_commands expected = sorted(expected_commands) assert actual == expected, "Commands are not in alphabetical order." ================================================ FILE: tests/test_docs/test_cli_vs_programmatic_aeas/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the 'clis-vs-programmatic-aeas' modules.""" ================================================ FILE: tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This scripts contains code from cli-vs-programmatic-aeas.md file.""" import logging import os import sys from typing import cast from aea_ledger_fetchai import FetchAICrypto from aea.aea import AEA from aea.aea_builder import AEABuilder from aea.configurations.base import ConnectionConfig from aea.crypto.helpers import ( PRIVATE_KEY_PATH_SCHEMA, create_private_key, make_certificate, ) from aea.crypto.wallet import Wallet from aea.helpers.base import CertRequest from aea.identity.base import Identity from aea.protocols.base import Protocol from aea.registries.resources import Resources from aea.skills.base import Skill import packages.fetchai.connections.p2p_libp2p.connection from packages.fetchai.connections.ledger.connection import LedgerConnection from packages.fetchai.connections.p2p_libp2p.connection import P2PLibp2pConnection from packages.fetchai.connections.soef.connection import SOEFConnection from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.weather_client.strategy import Strategy API_KEY = "TwiCIriSl0mLahw17pyqoA" SOEF_ADDR = "s-oef.fetch.ai" SOEF_PORT = 443 ENTRY_PEER_ADDRESS = ( "/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmLBCAqHL8SuFosyDhAKYsLKXBZBWXBsB9oFw2qU4Kckun" ) FETCHAI_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(FetchAICrypto.identifier) FETCHAI_PRIVATE_KEY_FILE_CONNECTION = PRIVATE_KEY_PATH_SCHEMA.format( "fetchai_connection" ) ROOT_DIR = os.getcwd() logger = logging.getLogger("aea") logging.basicConfig(stream=sys.stdout, level=logging.INFO) def run(): """Run demo.""" # Create a private key create_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) create_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION) # Set up the wallet, identity and (empty) resources wallet = Wallet( private_key_paths={FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE}, connection_private_key_paths={ FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_CONNECTION }, ) identity = Identity( "my_aea", address=wallet.addresses.get(FetchAICrypto.identifier), public_key=wallet.public_keys.get(FetchAICrypto.identifier), ) resources = Resources() data_dir = os.getcwd() # specify the default routing for some protocols default_routing = { LedgerApiMessage.protocol_id: LedgerConnection.connection_id, OefSearchMessage.protocol_id: SOEFConnection.connection_id, } default_connection = P2PLibp2pConnection.connection_id state_update_protocol = Protocol.from_dir( os.path.join(os.getcwd(), "packages", "fetchai", "protocols", "state_update") ) resources.add_protocol(state_update_protocol) # Add the default protocol (which is part of the AEA distribution) default_protocol = Protocol.from_dir( os.path.join(os.getcwd(), "packages", "fetchai", "protocols", "default") ) resources.add_protocol(default_protocol) # Add the signing protocol (which is part of the AEA distribution) signing_protocol = Protocol.from_dir( os.path.join(os.getcwd(), "packages", "fetchai", "protocols", "signing") ) resources.add_protocol(signing_protocol) # Add the ledger_api protocol ledger_api_protocol = Protocol.from_dir( os.path.join( os.getcwd(), "packages", "fetchai", "protocols", "ledger_api", ) ) resources.add_protocol(ledger_api_protocol) # Add the oef_search protocol oef_protocol = Protocol.from_dir( os.path.join( os.getcwd(), "packages", "fetchai", "protocols", "oef_search", ) ) resources.add_protocol(oef_protocol) # Add the fipa protocol fipa_protocol = Protocol.from_dir( os.path.join( os.getcwd(), "packages", "fetchai", "protocols", "fipa", ) ) resources.add_protocol(fipa_protocol) # Add the LedgerAPI connection configuration = ConnectionConfig(connection_id=LedgerConnection.connection_id) ledger_api_connection = LedgerConnection( configuration=configuration, data_dir=data_dir, identity=identity ) resources.add_connection(ledger_api_connection) # Add the P2P connection cert_path = ".certs/conn_cert.txt" cert_request = CertRequest( identifier="acn", ledger_id=FetchAICrypto.identifier, not_after="2022-01-01", not_before="2021-01-01", public_key="fetchai", message_format="{public_key}", save_path=cert_path, ) public_key = wallet.connection_cryptos.public_keys.get(FetchAICrypto.identifier) message = cert_request.get_message(public_key) make_certificate( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE, message, cert_path ) configuration = ConnectionConfig( connection_id=P2PLibp2pConnection.connection_id, delegate_uri="127.0.0.1:11001", entry_peers=[ENTRY_PEER_ADDRESS], local_uri="127.0.0.1:9001", log_file="libp2p_node.log", public_uri="127.0.0.1:9001", build_directory=os.getcwd(), build_entrypoint="check_dependencies.py", cert_requests=[cert_request], ) configuration.directory = os.path.dirname( packages.fetchai.connections.p2p_libp2p.connection.__file__ ) AEABuilder.run_build_for_component_configuration(configuration) p2p_connection = P2PLibp2pConnection( configuration=configuration, data_dir=data_dir, identity=identity, crypto_store=wallet.connection_cryptos, ) resources.add_connection(p2p_connection) # Add the SOEF connection configuration = ConnectionConfig( api_key=API_KEY, soef_addr=SOEF_ADDR, soef_port=SOEF_PORT, restricted_to_protocols={OefSearchMessage.protocol_id}, connection_id=SOEFConnection.connection_id, ) soef_connection = SOEFConnection( configuration=configuration, data_dir=data_dir, identity=identity ) resources.add_connection(soef_connection) # create the AEA my_aea = AEA( identity, wallet, resources, data_dir, default_connection=default_connection, default_routing=default_routing, ) # Add the error and weather_client skills error_skill = Skill.from_dir( os.path.join(ROOT_DIR, "packages", "fetchai", "skills", "error"), agent_context=my_aea.context, ) weather_skill = Skill.from_dir( os.path.join(ROOT_DIR, "packages", "fetchai", "skills", "weather_client"), agent_context=my_aea.context, ) strategy = cast(Strategy, weather_skill.models.get("strategy")) strategy._is_ledger_tx = False for skill in [error_skill, weather_skill]: resources.add_skill(skill) # Run the AEA try: logger.info("STARTING AEA NOW!") my_aea.start() except KeyboardInterrupt: logger.info("STOPPING AEA NOW!") my_aea.stop() if __name__ == "__main__": run() ================================================ FILE: tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the build-aea-programmatically.md file.""" import os import shutil from pathlib import Path from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( CUR_PATH, FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, ROOT_DIR, wait_for_localhost_ports_to_close, ) from tests.test_docs.helper import extract_code_blocks, extract_python_code MD_FILE = "docs/cli-vs-programmatic-aeas.md" PY_FILE = "test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py" DEST = "programmatic_aea.py" def test_read_md_file(): """Compare the extracted code with the python file.""" doc_path = os.path.join(ROOT_DIR, MD_FILE) code_blocks = extract_code_blocks(filepath=doc_path, filter_="python") test_code_path = os.path.join(CUR_PATH, PY_FILE) python_file = extract_python_code(test_code_path) assert code_blocks[-1] == python_file, "Files must be exactly the same." class TestCliVsProgrammaticAEA(AEATestCaseManyFlaky): """This class contains the tests for the code-blocks in the build-aea-programmatically.md file.""" capture_log: bool = True @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_INTEGRATION) @pytest.mark.integration def test_cli_programmatic_communication(self): """Test the communication of the two agents.""" weather_station = "weather_station" self.fetch_agent("fetchai/weather_station:0.32.5", weather_station) self.set_agent_context(weather_station) self.set_config( "vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx", False, "bool", ) self.run_install() # add non-funded key self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } setting_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) weather_station_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_station output.".format(missing_strings) src_file_path = os.path.join(ROOT_DIR, "tests", PY_FILE) dst_file_path = os.path.join(ROOT_DIR, self.t, DEST) shutil.copyfile(src_file_path, dst_file_path) self._inject_location(location, dst_file_path) weather_client_process = self.start_subprocess(DEST, cwd=self.t) check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_client_process, check_strings, timeout=30, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_client output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=120, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_station output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( weather_client_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_client output.".format(missing_strings) self.terminate_agents(weather_client_process, weather_station_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) def _inject_location(self, location, dst_file_path): """Inject location into the weather client strategy.""" file = Path(dst_file_path) lines = file.read_text().splitlines() target_line = " strategy._is_ledger_tx = False" line_insertion_position = lines.index(target_line) + 1 lines.insert( line_insertion_position, " from packages.fetchai.skills.generic_buyer.strategy import Location", ) lines.insert( line_insertion_position + 1, f" strategy._agent_location = Location(latitude={location['latitude']}, longitude={location['longitude']})", ) file.write_text("\n".join(lines)) ================================================ FILE: tests/test_docs/test_data_models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of data-models.md file.""" from pathlib import Path from aea.helpers.search.models import Attribute, DataModel, Description from tests.conftest import ROOT_DIR from tests.test_docs.helper import BasePythonMarkdownDocs class TestDataModel(BasePythonMarkdownDocs): """Test the data models code snippets.""" DOC_PATH = Path(ROOT_DIR, "docs", "defining-data-models.md") def _assert(self, locals_, *_mocks): attribute = locals_["attr_title"] assert isinstance(attribute, Attribute) data_model = locals_["book_model"] assert isinstance(data_model, DataModel) description = locals_["It"] assert isinstance(description, Description) ================================================ FILE: tests/test_docs/test_decision_maker_transaction/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the decision-maker-transaction.md file.""" ================================================ FILE: tests/test_docs/test_decision_maker_transaction/decision_maker_transaction.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the decision-maker-transaction.md file.""" import logging import time from threading import Thread from typing import Optional, cast from aea_ledger_fetchai import FetchAICrypto from aea.aea_builder import AEABuilder from aea.configurations.base import PublicId, SkillConfig from aea.crypto.helpers import create_private_key from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet from aea.helpers.transaction.base import RawTransaction, Terms from aea.identity.base import Identity from aea.protocols.base import Address, Message from aea.protocols.dialogue.base import Dialogue from aea.skills.base import Handler, Model, Skill, SkillContext from packages.fetchai.protocols.signing.dialogues import SigningDialogue from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage from tests.conftest import get_wealth_if_needed logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) FETCHAI_PRIVATE_KEY_FILE_1 = "fetchai_private_key_1.txt" FETCHAI_PRIVATE_KEY_FILE_2 = "fetchai_private_key_2.txt" def run(): """Run demo.""" # Create a private key create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_1 ) # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added builder = AEABuilder() builder.set_name("my_aea") builder.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_1) # Create our AEA my_aea = builder.build() # add a simple skill with handler skill_context = SkillContext(my_aea.context) skill_config = SkillConfig(name="simple_skill", author="fetchai", version="0.1.0") signing_handler = SigningHandler( skill_context=skill_context, name="signing_handler" ) signing_dialogues_model = SigningDialogues( skill_context=skill_context, name="signing_dialogues", self_address=str(skill_config.public_id), ) simple_skill = Skill( skill_config, skill_context, handlers={signing_handler.name: signing_handler}, models={signing_dialogues_model.name: signing_dialogues_model}, ) my_aea.resources.add_skill(simple_skill) # create a second identity create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_2 ) counterparty_wallet = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_2}) get_wealth_if_needed(counterparty_wallet.addresses["fetchai"]) counterparty_identity = Identity( name="counterparty_aea", addresses=counterparty_wallet.addresses, public_keys=counterparty_wallet.public_keys, default_address_key=FetchAICrypto.identifier, ) # create signing message for decision maker to sign terms = Terms( ledger_id=FetchAICrypto.identifier, sender_address=my_aea.identity.address, counterparty_address=counterparty_identity.address, amount_by_currency_id={"FET": -1}, quantities_by_good_id={"some_service": 1}, nonce="some_nonce", fee_by_currency_id={"FET": 0}, ) get_wealth_if_needed(terms.sender_address) signing_dialogues = cast(SigningDialogues, skill_context.signing_dialogues) stub_transaction = LedgerApis.get_transfer_transaction( terms.ledger_id, terms.sender_address, terms.counterparty_address, terms.sender_payable_amount, terms.sender_fee, terms.nonce, ) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), raw_transaction=RawTransaction(FetchAICrypto.identifier, stub_transaction), terms=terms, ) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.create_with_message("decision_maker", signing_msg), ) assert signing_dialogue is not None my_aea.context.decision_maker_message_queue.put_nowait(signing_msg) # Set the AEA running in a different thread try: logger.info("STARTING AEA NOW!") t = Thread(target=my_aea.start) t.start() # Let it run long enough to interact with the decision maker time.sleep(1) finally: # Shut down the AEA logger.info("STOPPING AEA NOW!") my_aea.stop() t.join() class SigningDialogues(Model, BaseSigningDialogues): """Signing dialogues model.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ Model.__init__(self, **kwargs) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return SigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class SigningHandler(Handler): """Implement the signing handler.""" SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[PublicId] def setup(self) -> None: """Implement the setup for the handler.""" def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ signing_msg = cast(SigningMessage, message) # recover dialogue signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_dialogue = cast( Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) if signing_dialogue is None: self._handle_unidentified_dialogue(signing_msg) return # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: self._handle_signed_transaction(signing_msg, signing_dialogue) elif signing_msg.performative is SigningMessage.Performative.ERROR: self._handle_error(signing_msg, signing_dialogue) else: self._handle_invalid(signing_msg, signing_dialogue) def teardown(self) -> None: """ Implement the handler teardown. :return: None """ def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: """ Handle an unidentified dialogue. :param msg: the message """ self.context.logger.info( "received invalid signing message={}, unidentified dialogue.".format( signing_msg ) ) def _handle_signed_transaction( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle a signing message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.info("transaction signing was successful.") logger.info(signing_msg.signed_transaction) def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.info( "transaction signing was not successful. Error_code={} in dialogue={}".format( signing_msg.error_code, signing_dialogue ) ) def _handle_invalid( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue ) -> None: """ Handle an oef search message. :param signing_msg: the signing message :param signing_dialogue: the dialogue :return: None """ self.context.logger.warning( "cannot handle signing message of performative={} in dialogue={}.".format( signing_msg.performative, signing_dialogue ) ) if __name__ == "__main__": run() ================================================ FILE: tests/test_docs/test_decision_maker_transaction/test_decision_maker_transaction.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the standalone-transaction.md file.""" import logging import os from unittest.mock import patch import pytest from aea.test_tools.test_cases import BaseAEATestCase from ..helper import extract_code_blocks, extract_python_code from .decision_maker_transaction import logger, run from tests.conftest import CUR_PATH, MAX_FLAKY_RERUNS, ROOT_DIR MD_FILE = "docs/decision-maker-transaction.md" PY_FILE = "test_docs/test_decision_maker_transaction/decision_maker_transaction.py" test_logger = logging.getLogger(__name__) class TestDecisionMakerTransaction(BaseAEATestCase): """This class contains the tests for the code-blocks in the agent-vs-aea.md file.""" @classmethod def _patch_logger(cls): cls.patch_logger_info = patch.object(logger, "info") cls.mocked_logger_info = cls.patch_logger_info.__enter__() @classmethod def _unpatch_logger(cls): cls.mocked_logger_info.__exit__() @classmethod def setup_class(cls): """Setup the test class.""" super().setup_class() cls._patch_logger() doc_path = os.path.join(ROOT_DIR, MD_FILE) cls.code_blocks = extract_code_blocks(filepath=doc_path, filter_="python") test_code_path = os.path.join(CUR_PATH, PY_FILE) cls.python_file = extract_python_code(test_code_path) def test_read_md_file(self): """Test the last code block, that is the full listing of the demo from the Markdown.""" assert ( self.code_blocks[-1] == self.python_file ), "Files must be exactly the same." def test_code_blocks_exist(self): """Test that all the code-blocks exist in the python file.""" for blocks in self.code_blocks: assert ( blocks in self.python_file ), "Code-block doesn't exist in the python file." @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_run_end_to_end(self): """Run the transaction from the file.""" try: run() except RuntimeError: test_logger.info("RuntimeError: Some transactions have failed") @classmethod def teardown_class(cls): """Teardown test.""" super().teardown_class() cls._unpatch_logger() ================================================ FILE: tests/test_docs/test_docs_http_connection_and_skill.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """ This module contain the test for the HTTP connection and skill documentation page. The testing strategy is to just check that the Python code reported in the documentation is contained in the http_echo skill. Moreover, test that the code is compilable/executable. """ from pathlib import Path from unittest import mock from tests.conftest import ROOT_DIR from tests.test_docs.helper import BaseTestMarkdownDocs class TestHttpConnectionAndSkill(BaseTestMarkdownDocs): """Test the skill testing code snippets.""" DOC_PATH = Path(ROOT_DIR, "docs", "http-connection-and-skill.md") HTTP_ECHO_PATH = Path(ROOT_DIR, "packages", "fetchai", "skills", "http_echo") @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() assert ( len(cls.python_blocks) == 3 ), "this test expected exactly 3 Python blocks." # read the content of 'handlers.py' and 'dialogues.py' of skill cls.http_echo_handlers_module = (cls.HTTP_ECHO_PATH / "handlers.py").read_text() cls.http_echo_dialogues = (cls.HTTP_ECHO_PATH / "dialogues.py").read_text() def test_handlers_code_snippet(self): """Test the 'handlers.py' code snippet.""" handlers_code_snippet = self.python_blocks[0]["text"] # the handlers code snippet contains the YOUR_USERNAME placeholder # to be replaced by the user. We need to replace it with 'fetchai' # in order to compare it with the skill 'fetchai/http_echo'. replacement = "from packages.YOUR_USERNAME.skills.http_echo.dialogues import" to_be_replaced = "from packages.fetchai.skills.http_echo.dialogues import" expected_handlers_code = self.http_echo_handlers_module.replace( to_be_replaced, replacement ) assert handlers_code_snippet in expected_handlers_code def test_dialogues_code_snippet(self): """Test the 'dialogues.py' code snippet.""" dialogues_code_snippet = self.python_blocks[1]["text"] expected_dialogues_code = self.http_echo_dialogues assert dialogues_code_snippet in expected_dialogues_code @mock.patch("requests.get") @mock.patch("requests.post") def test_requests_code_snippet(self, *_mocks): """Test the 'requests' code snippet.""" _globals, locals_ = {}, {} requests_code_snippet = self.python_blocks[2]["text"] exec(requests_code_snippet, _globals, locals_) # nosec assert "requests" in locals_ assert "response" in locals_ ================================================ FILE: tests/test_docs/test_docs_protocol.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the protocol.md file.""" from enum import Enum from pathlib import Path import mistune from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.oef_search.custom_types import OefErrorOperation from packages.fetchai.protocols.oef_search.message import OefSearchMessage from tests.conftest import ROOT_DIR from tests.test_docs.helper import compare_enum_classes, compile_and_exec class TestProtocolDocs: """Test the integrity of the code-blocks in skill.md""" @classmethod def setup_class(cls): """Test skill.md""" markdown_parser = mistune.create_markdown(renderer=mistune.AstRenderer()) skill_doc_file = Path(ROOT_DIR, "docs", "protocol.md") doc = markdown_parser(skill_doc_file.read_text()) # get only code blocks cls.code_blocks = list(filter(lambda x: x["type"] == "block_code", doc)) def test_custom_protocol(self): """Test the code in the 'Custom protocol' section.""" # this is the offset of code blocks for the section under testing offset = 0 locals_dict = {} compile_and_exec(self.code_blocks[offset]["text"], locals_dict=locals_dict) ActualPerformative = locals_dict["Performative"] compare_enum_classes(ActualPerformative, DefaultMessage.Performative) # load the example of default message of type BYTES compile_and_exec(self.code_blocks[offset + 1]["text"], locals_dict=locals_dict) # load the definition of the ErrorCode enumeration compile_and_exec(self.code_blocks[offset + 2]["text"], locals_dict=locals_dict) ExpectedErrorCode = locals_dict["ErrorCode"] compare_enum_classes(ExpectedErrorCode, DefaultMessage.ErrorCode) # load the example of default message of type ERROR _ = compile_and_exec( self.code_blocks[offset + 3]["text"], locals_dict=locals_dict ) def test_oef_search_protocol(self): """Test the fetchai/oef_search:1.1.7 protocol documentation.""" # this is the offset of code blocks for the section under testing offset = 5 # define a data model and a description locals_dict = {"Enum": Enum} compile_and_exec(self.code_blocks[offset]["text"], locals_dict=locals_dict) ActualPerformative = locals_dict["Performative"] compare_enum_classes(OefSearchMessage.Performative, ActualPerformative) compile_and_exec(self.code_blocks[offset + 1]["text"], locals_dict=locals_dict) # mind the indexes: +3 before +2 compile_and_exec(self.code_blocks[offset + 3]["text"], locals_dict=locals_dict) compile_and_exec(self.code_blocks[offset + 2]["text"], locals_dict=locals_dict) # test the construction of OEF Search Messages does not contain trivial errors. locals_dict["OefSearchMessage"] = OefSearchMessage compile_and_exec(self.code_blocks[offset + 4]["text"], locals_dict=locals_dict) compile_and_exec(self.code_blocks[offset + 5]["text"], locals_dict=locals_dict) compile_and_exec(self.code_blocks[offset + 6]["text"], locals_dict=locals_dict) compile_and_exec(self.code_blocks[offset + 7]["text"], locals_dict=locals_dict) compile_and_exec(self.code_blocks[offset + 8]["text"], locals_dict=locals_dict) compile_and_exec(self.code_blocks[offset + 9]["text"], locals_dict=locals_dict) # this is just to test that something has actually run assert locals_dict["query_data"] == { "search_term": "country", "search_value": "UK", "constraint_type": "==", } # test the definition of OefErrorOperation compile_and_exec(self.code_blocks[offset + 10]["text"], locals_dict=locals_dict) ActualOefErrorOperation = locals_dict["OefErrorOperation"] ExpectedOefErrorOperation = OefErrorOperation compare_enum_classes(ExpectedOefErrorOperation, ActualOefErrorOperation) def test_fipa_protocol(self): """Test the fetchai/fipa:1.1.7 documentation.""" offset = 15 locals_dict = {"Enum": Enum} compile_and_exec(self.code_blocks[offset]["text"], locals_dict=locals_dict) ActualFipaPerformative = locals_dict["Performative"] ExpectedFipaPerformative = FipaMessage.Performative compare_enum_classes(ExpectedFipaPerformative, ActualFipaPerformative) ================================================ FILE: tests/test_docs/test_docs_skill.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the skill.md file.""" from pathlib import Path import mistune from aea.skills.behaviours import OneShotBehaviour from aea.skills.tasks import Task from tests.conftest import ROOT_DIR from tests.test_docs.helper import compile_and_exec class TestSkillDocs: """Test the integrity of the code-blocks in skill.md""" @classmethod def setup_class(cls): """Test skill.md""" markdown_parser = mistune.create_markdown(renderer=mistune.AstRenderer()) skill_doc_file = Path(ROOT_DIR, "docs", "skill.md") doc = markdown_parser(skill_doc_file.read_text()) # get only code blocks cls.code_blocks = list(filter(lambda x: x["type"] == "block_code", doc)) def test_context(self): """Test the code in context.""" block = self.code_blocks[0] expected = "self.context.outbox.put_message(message=reply)" assert block["text"].strip() == expected assert block["info"].strip() == "python" # TODO add tests for new_handlers queue def test_hello_world_behaviour(self): """Test the code in the 'behaviours.py' section.""" # here, we test the definition of a custom class offset = 2 block = self.code_blocks[offset] text = block["text"] # check that the code can be executed code_obj = compile(text, "fakemodule", "exec") locals_dict = {} exec(code_obj, globals(), locals_dict) # nosec # some consistency check on the behaviour class. HelloWorldBehaviour = locals_dict["HelloWorldBehaviour"] assert issubclass(HelloWorldBehaviour, OneShotBehaviour) # here, we test the code example for adding the new custom behaviour to the list # of new behaviours block = self.code_blocks[offset + 1] text = block["text"] assert ( text.strip() == 'self.context.new_behaviours.put(HelloWorldBehaviour(name="hello_world", skill_context=self.context))' ) block = self.code_blocks[offset + 2] assert ( block["text"] == "def hello():\n" ' print("Hello, World!")\n' "\n" 'self.context.new_behaviours.put(OneShotBehaviour(act=hello, name="hello_world", skill_context=self.context))\n' ) def test_task(self): """Test the code blocks of the 'tasks.py' section.""" # test code of task definition offset = 5 block = self.code_blocks[offset] locals_dict = compile_and_exec(block["text"]) nth_prime_number = locals_dict["nth_prime_number"] assert nth_prime_number(1) == 2 assert nth_prime_number(2) == 3 assert nth_prime_number(3) == 5 assert nth_prime_number(4) == 7 LongTask = locals_dict["LongTask"] assert issubclass(LongTask, Task) LongTask() ================================================ FILE: tests/test_docs/test_generic_step_by_step_guide/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in thermometer-skills-step-by-step.md file.""" ================================================ FILE: tests/test_docs/test_generic_step_by_step_guide/test_generic_step_by_step_guide.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in thermometer-skills-step-by-step.md file.""" import logging import os from pathlib import Path from tests.conftest import ROOT_DIR from tests.test_docs.helper import extract_code_blocks logger = logging.getLogger(__name__) class TestDemoDocs: """This class contains the tests for the python-blocks in thermometer-skills-step-by-step.md file.""" @classmethod def setup_class(cls): """Setup the test class.""" md_path = os.path.join(ROOT_DIR, "docs", "generic-skills-step-by-step.md") code_blocks = extract_code_blocks(filepath=md_path, filter_="python") cls.generic_seller = code_blocks[0:11] cls.generic_buyer = code_blocks[11 : len(code_blocks)] def test_generic_seller_skill_behaviour(self): """Test behaviours.py of generic_seller skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_seller", "behaviours.py" ) with open(path, "r") as file: python_code = file.read() assert self.generic_seller[0] in python_code, "Code is not identical." def test_generic_seller_skill_handler(self): """Test handlers.py of generic_seller skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_seller", "handlers.py" ) with open(path, "r") as file: python_code = file.read() for code_block in self.generic_seller[1:8]: assert code_block in python_code, "Code is not identical." def test_generic_seller_skill_strategy(self): """Test strategy.py of generic_seller skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_seller", "strategy.py" ) with open(path, "r") as file: python_code = file.read() for code_block in self.generic_seller[8:10]: assert code_block in python_code, "Code is not identical." def test_generic_seller_skill_dialogues(self): """Test dialogues.py of generic_seller skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_seller", "dialogues.py" ) with open(path, "r") as file: python_code = file.read() assert self.generic_seller[10] in python_code, "Code is not identical." def test_generic_buyer_skill_behaviour(self): """Test that the code blocks exist in the generic_buyer skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer", "behaviours.py", ) with open(path, "r") as file: python_code = file.read() assert self.generic_buyer[0] in python_code, "Code is not identical." def test_generic_buyer_skill_handler(self): """Test handlers.py of generic_buyer skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer", "handlers.py", ) with open(path, "r") as file: python_code = file.read() for code_block in self.generic_buyer[1:9]: assert code_block in python_code, "Code is not identical." def test_generic_buyer_skill_strategy(self): """Test strategy.py correctness of generic_buyer skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer", "strategy.py", ) with open(path, "r") as file: python_code = file.read() for code_block in self.generic_buyer[9:13]: assert code_block in python_code, "Code is not identical." def test_generic_buyer_skill_dialogues(self): """Test dialogues.py of generic_buyer skill.""" path = Path( ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer", "dialogues.py", ) with open(path, "r") as file: python_code = file.read() assert self.generic_buyer[13] in python_code, "Code is not identical." ================================================ FILE: tests/test_docs/test_generic_storage.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of generic-storage.md file.""" import inspect import re from pathlib import Path from aea.helpers.storage.generic_storage import SyncCollection from aea.skills.behaviours import TickerBehaviour from tests.conftest import ROOT_DIR from tests.test_docs.helper import BaseTestMarkdownDocs class TestGenericStorage(BaseTestMarkdownDocs): """Test generic-storage.md documentation.""" DOC_PATH = Path(ROOT_DIR, "docs", "generic-storage.md") @classmethod def setup_class(cls): """ Set up test. We test only the signatures and the docstrings of 'SyncCollection' methods. Hence, we need to remove the return statement. """ super().setup_class() cls.content = inspect.getsource(SyncCollection) cls.content = re.sub("\n +return.*", "", cls.content) def test_storage_abstract_methods(self): """ Test storage abstract methods. Note: the block index of the abstract methods is 0. Please check generic-storage.md """ block_index = 1 assert self.code_blocks[block_index]["text"] in self.content def test_test_behaviour(self): """Test that the 'TestBehaviour' code is compilable.""" block_index = 2 code = self.code_blocks[block_index]["text"] exec(code, {}, dict(TickerBehaviour=TickerBehaviour)) # nosec ================================================ FILE: tests/test_docs/test_language_agnostic_definition.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of language-agnostic-definition.md file.""" from pathlib import Path from typing import Dict from aea import AEA_DIR from aea.configurations.data_types import PublicId from tests.conftest import ROOT_DIR from tests.test_docs.helper import BaseTestMarkdownDocs MAIL_BASE_PROTO = Path(AEA_DIR) / "mail" / "base.proto" DEFAULT_MESSAGE_PROTO = ( Path(ROOT_DIR) / "packages" / "fetchai" / "protocols" / "default" / "default.proto" ) class TestLanguageAgnosticDocs(BaseTestMarkdownDocs): """Test the integrity of the language agnostic definitions in docs.""" DOC_PATH = Path(ROOT_DIR, "docs", "language-agnostic-definition.md") @classmethod def _proto_snippet_selector(cls, block: Dict) -> bool: return block["type"] == "block_code" and block["info"].strip() == "proto" @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() cls.code_blocks = list(filter(cls._proto_snippet_selector, cls.flat_blocks)) cls.actual_mail_base_file_content = MAIL_BASE_PROTO.read_text() cls.actual_default_message_file_content = DEFAULT_MESSAGE_PROTO.read_text() def test_envelope_code_snippet(self): """ Test the Envelope protobuf code snippet. It requires treating the preamble lines separately, because aea/mail/base.proto doesn't follow the same order of language-agnostic-definition.md file. """ block = self.code_blocks[0] assert block["info"].strip() == "proto" lines = block["text"].splitlines() first_part, second_part = "\n".join(lines[:3]), "\n".join(lines[3:]) assert first_part in self.actual_mail_base_file_content assert second_part in self.actual_mail_base_file_content def test_dialogue_message_code_snippet(self): """ Test the DialogueMessage protobuf code snippet. It requires treating the preamble lines separately, because aea/mail/base.proto doesn't follow the same order of language-agnostic-definition.md file. """ block = self.code_blocks[1] assert block["info"].strip() == "proto" assert block["text"] in self.actual_mail_base_file_content def test_public_id_regular_expression(self): """Test public id regular expression is the same.""" expected_regex = PublicId.PUBLIC_ID_REGEX assert expected_regex in self.doc_content def test_default_message_code_snippet(self): """Test DefaultMessage protobuf code snippet.""" block = self.code_blocks[2] assert block["info"].strip() == "proto" assert block["text"] in self.actual_default_message_file_content, Exception( [block["text"], self.actual_default_message_file_content] ) ================================================ FILE: tests/test_docs/test_ledger_integration.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of ledger-integration.md file.""" from importlib import import_module from pathlib import Path from unittest import mock from unittest.mock import MagicMock from aea.crypto.registries import ( crypto_registry, faucet_apis_registry, ledger_apis_registry, ) from tests.conftest import ROOT_DIR from tests.test_docs.helper import BasePythonMarkdownDocs def _import_module_mock(arg): """ Mock importlib.import_module only if argument is a dummy one: 'some.dotted.path'. This choice is tight to the code examples in 'ledger-integration.md'. It helps to tests the cases in which the import path is not a fake one. """ if arg.startswith("some.dotted.path"): return MagicMock() return import_module(arg) # we mock only if the import is from dummy import path like "some.dotted.path". @mock.patch("importlib.import_module", side_effect=_import_module_mock) @mock.patch("setuptools.setup") class TestLedgerIntegration(BasePythonMarkdownDocs): """Test the ledger integration code snippets.""" DOC_PATH = Path(ROOT_DIR, "docs", "ledger-integration.md") def _assert_isinstance(self, locals_key, cls_or_str, locals_): """Assert that the member of 'locals' is an instance of a class.""" assert locals_key in locals_ obj = locals_[locals_key] if type(cls_or_str) == type: assert isinstance(obj, cls_or_str) else: assert obj.__class__.__name__ == cls_or_str def _assert(self, locals_, *mocks): """Assert code outputs.""" self._assert_isinstance("fetchai_crypto", "FetchAICrypto", locals_) self._assert_isinstance("fetchai_ledger_api", "FetchAIApi", locals_) self._assert_isinstance("fetchai_faucet_api", "FetchAIFaucetApi", locals_) self._assert_isinstance("my_ledger_crypto", MagicMock, locals_) self._assert_isinstance("my_ledger_api", MagicMock, locals_) self._assert_isinstance("my_faucet_api", MagicMock, locals_) @classmethod def teardown_class(cls): """Tear down the test.""" crypto_registry.specs.pop("my_ledger_id") ledger_apis_registry.specs.pop("my_ledger_id") faucet_apis_registry.specs.pop("my_ledger_id") ================================================ FILE: tests/test_docs/test_multiagent_manager.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of multi-agent-manager.md file.""" import os import shutil import tempfile from importlib import import_module from pathlib import Path from unittest.mock import MagicMock import pytest from tests.conftest import MAX_FLAKY_RERUNS, PACKAGES_DIR, ROOT_DIR from tests.test_docs.helper import BasePythonMarkdownDocs def _import_module_mock(arg): """ Mock importlib.import_module only if argument is a dummy one: 'some.dotted.path'. This choice is tight to the code examples in 'ledger-integration.md'. It helps to tests the cases in which the import path is not a fake one. """ if arg.startswith("some.dotted.path"): return MagicMock() return import_module(arg) @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) # flaky on Windows class TestMultiAgentManager(BasePythonMarkdownDocs): """Test the ledger integration code snippets.""" DOC_PATH = Path(ROOT_DIR, "docs", "multi-agent-manager.md") @classmethod def setup_class(cls): """Set up the class.""" super().setup_class() cls.old_cwd = os.getcwd() cls.temp_dir = tempfile.mkdtemp() shutil.copytree(PACKAGES_DIR, Path(cls.temp_dir, "packages")) os.chdir(cls.temp_dir) @classmethod def teardown_class(cls): """Teardown class.""" os.chdir(cls.old_cwd) shutil.rmtree(cls.temp_dir) ================================================ FILE: tests/test_docs/test_multiplexer_standalone/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the multiplexer-standalone.md file.""" ================================================ FILE: tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the full script from the multiplexer-standalone.md file.""" import os import time from copy import copy from threading import Thread from typing import Optional from aea.configurations.base import ConnectionConfig from aea.helpers.file_io import write_with_lock from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.stub.connection import StubConnection from packages.fetchai.protocols.default.message import DefaultMessage INPUT_FILE = "input.txt" OUTPUT_FILE = "output.txt" def run(): """Run demo.""" # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): os.remove(INPUT_FILE) if os.path.isfile(OUTPUT_FILE): os.remove(OUTPUT_FILE) # create the connection and multiplexer objects configuration = ConnectionConfig( input_file=INPUT_FILE, output_file=OUTPUT_FILE, connection_id=StubConnection.connection_id, ) stub_connection = StubConnection( configuration=configuration, data_dir=".", identity=Identity("some_agent", "some_address", "some_public_key"), ) multiplexer = Multiplexer([stub_connection], protocols=[DefaultMessage]) try: # Set the multiplexer running in a different thread t = Thread(target=multiplexer.connect) t.start() # Wait for everything to start up for _ in range(20): if multiplexer.is_connected: break time.sleep(1) else: raise Exception("Not connected") # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( "multiplexer,some_agent,fetchai/default:1.0.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: write_with_lock(f, message_text) # Wait for the envelope to get processed for _ in range(20): if not multiplexer.in_queue.empty(): break time.sleep(1) else: raise Exception("No message!") # get the envelope envelope = multiplexer.get() # type: Optional[Envelope] assert envelope is not None # Inspect its contents print( "Envelope received by Multiplexer: sender={}, to={}, protocol_specification_id={}, message={}".format( envelope.sender, envelope.to, envelope.protocol_specification_id, envelope.message, ) ) # Create a mirrored response envelope response_envelope = copy(envelope) response_envelope.to = envelope.sender response_envelope.sender = envelope.to # Send the envelope back multiplexer.put(response_envelope) # Read the output envelope generated by the multiplexer with open(OUTPUT_FILE, "r") as f: print("Envelope received from Multiplexer: " + f.readline()) finally: # Shut down the multiplexer multiplexer.disconnect() t.join() if __name__ == "__main__": run() ================================================ FILE: tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the multiplexer-standalone.md file.""" import os from pathlib import Path from aea.test_tools.test_cases import BaseAEATestCase from tests.conftest import CUR_PATH, ROOT_DIR from tests.test_docs.helper import extract_code_blocks, extract_python_code from tests.test_docs.test_multiplexer_standalone.multiplexer_standalone import run MD_FILE = "docs/multiplexer-standalone.md" PY_FILE = "test_docs/test_multiplexer_standalone/multiplexer_standalone.py" class TestMultiplexerStandAlone(BaseAEATestCase): """This class contains the tests for the code-blocks in the build-aea-programmatically.md file.""" @classmethod def setup_class(cls): """Setup the test class.""" super().setup_class() doc_path = os.path.join(ROOT_DIR, MD_FILE) cls.code_blocks = extract_code_blocks(filepath=doc_path, filter_="python") test_code_path = os.path.join(CUR_PATH, PY_FILE) cls.python_file = extract_python_code(test_code_path) def test_read_md_file(self): """Read the code blocks. Last block should be the whole code.""" assert ( self.code_blocks[-1] == self.python_file ), "Files must be exactly the same." def test_run_agent(self): """Run the agent from the file.""" run() assert os.path.exists(Path(self.t, "input.txt")) assert os.path.exists(Path(self.t, "output.txt")) message_text = ( "some_agent,multiplexer,fetchai/default:1.0.0,\x08\x01*\x07\n\x05hello," ) path = os.path.join(str(self.t), "output.txt") with open(path, "r", encoding="utf-8") as file: msg = file.read() assert msg == message_text, "The messages must be identical." def test_code_blocks_exist(self): """Test that all the code-blocks exist in the python file.""" for blocks in self.code_blocks: assert ( blocks in self.python_file ), "Code-block doesn't exist in the python file." ================================================ FILE: tests/test_docs/test_orm_integration/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains the tests for the orm-integration.md guide.""" ================================================ FILE: tests/test_docs/test_orm_integration/orm_seller_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the strategy class.""" import json import random # nosec import time from typing import Dict import sqlalchemy as db from packages.fetchai.skills.generic_seller.strategy import GenericStrategy class Strategy(GenericStrategy): """This class defines a strategy for the agent.""" def __init__(self, **kwargs) -> None: """ Initialize the strategy of the agent. :param register_as: determines whether the agent registers as seller, buyer or both :param search_for: determines whether the agent searches for sellers, buyers or both :return: None """ self._db_engine = db.create_engine("sqlite:///genericdb.db") self._tbl = self.create_database_and_table() self.insert_data() super().__init__(**kwargs) def collect_from_data_source(self) -> Dict[str, str]: """Implement the logic to collect data.""" connection = self._db_engine.connect() query = db.select([self._tbl]) result_proxy = connection.execute(query) data_points = result_proxy.fetchall() return {"data": json.dumps(list(map(tuple, data_points)))} def create_database_and_table(self): """Creates a database and a table to store the data if not exists.""" metadata = db.MetaData() tbl = db.Table( "data", metadata, db.Column("timestamp", db.Integer()), db.Column("temprature", db.String(255), nullable=False), ) metadata.create_all(self._db_engine) return tbl def insert_data(self): """Insert data in the database.""" connection = self._db_engine.connect() for _ in range(10): query = db.insert(self._tbl).values( # nosec timestamp=time.time(), temprature=str(random.randrange(10, 25)) ) connection.execute(query) ================================================ FILE: tests/test_docs/test_orm_integration/test_orm_integration.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the orm-integration.md guide.""" from pathlib import Path from random import uniform import mistune import pytest import yaml from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, ROOT_DIR, wait_for_localhost_ports_to_close, ) seller_strategy_replacement = """models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues strategy: args: currency_id: FET data_for_sale: temperature: 26 has_data_source: false is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 service_data: key: seller_service value: thermometer_data service_id: thermometer_data unit_price: 10 class_name: Strategy dependencies: SQLAlchemy: {}""" buyer_strategy_replacement = """models: default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues ledger_api_dialogues: args: {} class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues signing_dialogues: args: {} class_name: SigningDialogues strategy: args: currency_id: FET is_ledger_tx: true ledger_id: fetchai location: latitude: 51.5194 longitude: 0.127 max_negotiations: 1 max_tx_fee: 3550000000000000 max_unit_price: 20 search_query: constraint_type: == search_key: seller_service search_value: thermometer_data search_radius: 5.0 service_id: thermometer_data class_name: Strategy""" ORM_SELLER_STRATEGY_PATH = Path( ROOT_DIR, "tests", "test_docs", "test_orm_integration", "orm_seller_strategy.py" ) @pytest.mark.integration class TestOrmIntegrationDocs(AEATestCaseManyFlaky): """This class contains the tests for the orm-integration.md guide.""" @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_INTEGRATION) def test_orm_integration_docs_example(self): """Run the weather skills sequence.""" seller_aea_name = "my_thermometer_aea" buyer_aea_name = "my_thermometer_client" self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # Setup seller self.set_agent_context(seller_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/thermometer:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) # ejecting changes author and version! self.eject_item("skill", "fetchai/thermometer:0.27.6") seller_skill_config_replacement = yaml.safe_load(seller_strategy_replacement) self.nested_set_config( "skills.thermometer.models.strategy.args", seller_skill_config_replacement["models"]["strategy"]["args"], ) self.nested_set_config( "skills.thermometer.dependencies", seller_skill_config_replacement["dependencies"], ) # Replace the seller strategy seller_stategy_path = Path( seller_aea_name, "skills", "thermometer", "strategy.py", ) self.replace_file_content(ORM_SELLER_STRATEGY_PATH, seller_stategy_path) self.fingerprint_item( "skill", "{}/thermometer:0.1.0".format(self.author), ) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = "skills.thermometer.models.strategy.args.location" self.nested_set_config(setting_path, location) # Setup Buyer self.set_agent_context(buyer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/thermometer_client:0.26.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) buyer_skill_config_replacement = yaml.safe_load(buyer_strategy_replacement) self.nested_set_config( "vendor.fetchai.skills.thermometer_client.models.strategy.args", buyer_skill_config_replacement["models"]["strategy"]["args"], ) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # Fire the sub-processes and the threads. self.set_agent_context(seller_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) seller_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_aea output.".format(missing_strings) self.set_agent_context(buyer_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) buyer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=30, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_client_aea output.".format( missing_strings ) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "checking whether transaction=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=120, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "requesting transfer transaction from ledger api for message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) self.terminate_agents(seller_aea_process, buyer_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) def test_strategy_consistency(): """Test that the seller strategy specified in the documentation is the same we use in the tests.""" markdown_parser = mistune.create_markdown(renderer=mistune.AstRenderer()) skill_doc_file = Path(ROOT_DIR, "docs", "orm-integration.md") doc = markdown_parser(skill_doc_file.read_text()) # get only code blocks code_blocks = list(filter(lambda x: x["type"] == "block_code", doc)) python_code_blocks = list( filter( lambda x: x["info"] is not None and x["info"].strip() == "python", code_blocks, ) ) strategy_file_content = ORM_SELLER_STRATEGY_PATH.read_text() for python_code_block in python_code_blocks: if not python_code_block["text"] in strategy_file_content: pytest.fail( "Code block not present in strategy file:\n{}".format( python_code_block["text"] ) ) ================================================ FILE: tests/test_docs/test_query_language.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of query-language.md file.""" from pathlib import Path from unittest import mock from unittest.mock import MagicMock from tests.conftest import ROOT_DIR from tests.test_docs.helper import BasePythonMarkdownDocs @mock.patch("aea.helpers.search.models.Query.check_validity") class TestQueryLanguage(BasePythonMarkdownDocs): """Test the data models code snippets.""" DOC_PATH = Path(ROOT_DIR, "docs", "query-language.md") @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() cls.locals["book_model"] = MagicMock() ================================================ FILE: tests/test_docs/test_quickstart.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of quickstart.md file.""" from pathlib import Path import pytest from packages.fetchai.protocols.default.message import DefaultMessage from tests.conftest import ROOT_DIR from tests.test_docs.helper import BasePythonMarkdownDocs, extract_code_blocks class TestQuickstartTest(BasePythonMarkdownDocs): """Test the quickstart test.""" DOC_PATH = Path(ROOT_DIR, "docs", "quickstart.md") @pytest.mark.skip def test_correct_echo_string(): """Test the echo string in the quickstart is using the correct protocol specification id.""" file_path = Path(ROOT_DIR, "docs", "quickstart.md") bash_code_blocks = extract_code_blocks(filepath=file_path, filter_="bash") echo_bloc = bash_code_blocks[24] default_protocol_spec_id = echo_bloc.split(",")[2] assert ( str(DefaultMessage.protocol_specification_id) == default_protocol_spec_id ), "Spec ids not matching!" ================================================ FILE: tests/test_docs/test_simple_oef_usage.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of simple-oef-usage.md file.""" from pathlib import Path from aea.helpers.search.models import Description from packages.fetchai.protocols.oef_search import OefSearchMessage from tests.conftest import ROOT_DIR from tests.test_docs.helper import BasePythonMarkdownDocs class TestSimpleOefUsage(BasePythonMarkdownDocs): """Test the simple OEF usage code snippets.""" DOC_PATH = Path(ROOT_DIR, "docs", "simple-oef-usage.md") def _assert(self, locals_, *mocks): """Assert executed code.""" assert isinstance(locals_["message"], OefSearchMessage) assert isinstance(locals_["service_description"], Description) ================================================ FILE: tests/test_docs/test_skill_guide/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the skill-guide.md file.""" ================================================ FILE: tests/test_docs/test_skill_guide/test_skill_guide.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the skill-guide.md file.""" import filecmp import os from pathlib import Path from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea import AEA_DIR from aea.configurations.base import DEFAULT_VERSION from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( AUTHOR, FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, ROOT_DIR, wait_for_localhost_ports_to_close, ) from tests.test_docs.helper import extract_code_blocks MD_FILE = "docs/skill-guide.md" @pytest.mark.integration class TestBuildSkill(AEATestCaseManyFlaky): """This class contains the tests for the code-blocks in the skill-guide.md file.""" capture_log = True @classmethod def setup_class(cls): """Setup the test class.""" super().setup_class() cls.doc_path = os.path.join(ROOT_DIR, MD_FILE) cls.code_blocks = extract_code_blocks(filepath=cls.doc_path, filter_="python") @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_INTEGRATION) def test_update_skill_and_run(self): """Test that the resource folder contains scaffold handlers.py module.""" assert self.code_blocks != [], "File must not be empty." self.initialize_aea(AUTHOR) # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } simple_service_registration_aea = "simple_service_registration" self.fetch_agent( "fetchai/simple_service_registration:0.32.5", simple_service_registration_aea, ) self.set_agent_context(simple_service_registration_aea) # add non-funded key self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) default_routing = { "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # replace location setting_path = "vendor.fetchai.skills.simple_service_registration.models.strategy.args.location" self.nested_set_config(setting_path, location) search_aea = "search_aea" self.create_agents(search_aea) self.set_agent_context(search_aea) skill_name = "my_search" skill_id = AUTHOR + "/" + skill_name + ":" + DEFAULT_VERSION self.scaffold_item("skill", skill_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) # manually change the files: path = Path(self.t, search_aea, "skills", skill_name, "behaviours.py") original = Path(AEA_DIR, "skills", "scaffold", "behaviours.py") assert filecmp.cmp(path, original) with open(path, "w") as file: file.write(self.code_blocks[0]) # block one is behaviour path = Path(self.t, search_aea, "skills", skill_name, "handlers.py") original = Path(AEA_DIR, "skills", "scaffold", "handlers.py") assert filecmp.cmp(path, original) with open(path, "w") as file: file.write(self.code_blocks[1]) # block two is handler path = Path(self.t, search_aea, "skills", skill_name, "my_model.py") original = Path(AEA_DIR, "skills", "scaffold", "my_model.py") assert filecmp.cmp(path, original) with open(path, "w") as file: file.write(self.code_blocks[2]) # block three is dialogues path_new = Path(self.t, search_aea, "skills", skill_name, "dialogues.py") os.rename(path, path_new) path = Path(self.t, search_aea, "skills", skill_name, "skill.yaml") yaml_code_block = extract_code_blocks(self.doc_path, filter_="yaml") with open(path, "w") as file: file.write(yaml_code_block[0]) # block one is yaml path = Path(self.t, search_aea, "skills", skill_name, "__init__.py") original = Path(AEA_DIR, "skills", "scaffold", "__init__.py") assert not filecmp.cmp(path, original) # the public id gets updated with open(path, "w") as file: file.write(self.code_blocks[3]) # block four is init # update fingerprint self.fingerprint_item("skill", skill_id) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = "skills.{}.behaviours.my_search_behaviour.args.location".format( skill_name ) self.nested_set_config(setting_path, location) # run agents self.set_agent_context(simple_service_registration_aea) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) simple_service_registration_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( simple_service_registration_aea_process, check_strings, timeout=30, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in simple_service_registration_aea output.".format( missing_strings ) self.set_agent_context(search_aea) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) search_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( search_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in search_aea output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", ) missing_strings = self.missing_from_output( simple_service_registration_aea_process, check_strings, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in simple_service_registration_aea output.".format( missing_strings ) check_strings = ( "sending search request to OEF search node, search_count=", "number of search requests sent=", "found number of agents=1, received search count=", ) missing_strings = self.missing_from_output( search_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in search_aea output.".format(missing_strings) self.terminate_agents( simple_service_registration_aea_process, search_aea_process ) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_docs/test_skill_testing.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the content of skill-testing.md file.""" from pathlib import Path from unittest import mock from unittest.mock import MagicMock from tests.conftest import ROOT_DIR from tests.test_docs.helper import BasePythonMarkdownDocs @mock.patch("unittest.mock.MagicMock.assert_any_call") class TestSkillTesting(BasePythonMarkdownDocs): """Test the skill testing code snippets.""" DOC_PATH = Path(ROOT_DIR, "docs", "skill-testing.md") @classmethod def setup_class(cls): """Set up the test.""" super().setup_class() # without this, it fails at the first block # with: "NameError: name 'Path' is not defined" cls.globals.update(globals()) cls.locals.update(dict(cls=MagicMock(), self=MagicMock())) ================================================ FILE: tests/test_docs/test_standalone_transaction/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the standalone-transaction.md file.""" ================================================ FILE: tests/test_docs/test_standalone_transaction/standalone_transaction.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the last code-block from the standalone-transaction.md file.""" import logging from aea_ledger_fetchai import FetchAICrypto from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet logger = logging.getLogger("aea") logging.basicConfig(level=logging.INFO) FETCHAI_PRIVATE_KEY_FILE_1 = "fetchai_private_key_1.txt" FETCHAI_PRIVATE_KEY_FILE_2 = "fetchai_private_key_2.txt" def run(): """Run demo.""" # Create a private keys create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_1 ) create_private_key( FetchAICrypto.identifier, private_key_file=FETCHAI_PRIVATE_KEY_FILE_2 ) # Set up the wallets wallet_1 = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_1}) wallet_2 = Wallet({FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE_2}) # Generate some wealth try_generate_testnet_wealth( FetchAICrypto.identifier, wallet_1.addresses[FetchAICrypto.identifier] ) logger.info( "Sending amount to {}".format(wallet_2.addresses.get(FetchAICrypto.identifier)) ) # Create the transaction and send it to the ledger. tx_nonce = LedgerApis.generate_tx_nonce( FetchAICrypto.identifier, wallet_2.addresses.get(FetchAICrypto.identifier), wallet_1.addresses.get(FetchAICrypto.identifier), ) transaction = LedgerApis.get_transfer_transaction( identifier=FetchAICrypto.identifier, sender_address=wallet_1.addresses.get(FetchAICrypto.identifier), destination_address=wallet_2.addresses.get(FetchAICrypto.identifier), amount=1, tx_fee=1, tx_nonce=tx_nonce, ) signed_transaction = wallet_1.sign_transaction( FetchAICrypto.identifier, transaction ) transaction_digest = LedgerApis.send_signed_transaction( FetchAICrypto.identifier, signed_transaction ) logger.info("Transaction complete.") logger.info("The transaction digest is {}".format(transaction_digest)) if __name__ == "__main__": run() ================================================ FILE: tests/test_docs/test_standalone_transaction/test_standalone_transaction.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the code-blocks in the standalone-transaction.md file.""" import logging import os from unittest.mock import patch import pytest from aea.test_tools.test_cases import BaseAEATestCase from tests.conftest import CUR_PATH, MAX_FLAKY_RERUNS_INTEGRATION, ROOT_DIR from tests.test_docs.helper import extract_code_blocks, extract_python_code from tests.test_docs.test_standalone_transaction.standalone_transaction import ( logger, run, ) MD_FILE = "docs/standalone-transaction.md" PY_FILE = "test_docs/test_standalone_transaction/standalone_transaction.py" test_logger = logging.getLogger(__name__) class TestStandaloneTransaction(BaseAEATestCase): """This class contains the tests for the code-blocks in the agent-vs-aea.md file.""" @classmethod def _patch_logger(cls): cls.patch_logger_info = patch.object(logger, "info") cls.mocked_logger_info = cls.patch_logger_info.__enter__() @classmethod def _unpatch_logger(cls): cls.mocked_logger_info.__exit__() @classmethod def setup_class(cls): """Setup the test class.""" super().setup_class() cls._patch_logger() doc_path = os.path.join(ROOT_DIR, MD_FILE) cls.code_blocks = extract_code_blocks(filepath=doc_path, filter_="python") test_code_path = os.path.join(CUR_PATH, PY_FILE) cls.python_file = extract_python_code(test_code_path) def test_read_md_file(self): """Test the last code block, that is the full listing of the demo from the Markdown.""" assert ( self.code_blocks[-1] == self.python_file ), "Files must be exactly the same." @pytest.mark.integration(reruns=MAX_FLAKY_RERUNS_INTEGRATION) def test_run_end_to_end(self): """Run the transaction from the file.""" try: run() self.mocked_logger_info.assert_any_call("Transaction complete.") except RuntimeError: test_logger.info("RuntimeError: Some transactions have failed") def test_code_blocks_exist(self): """Test that all the code-blocks exist in the python file.""" for blocks in self.code_blocks: assert ( blocks in self.python_file ), "Code-block doesn't exist in the python file." ================================================ FILE: tests/test_examples/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the examples dir.""" ================================================ FILE: tests/test_examples/test_gym_ex.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the gym example.""" import os import shutil import sys import tempfile from pathlib import Path from tests.common.pexpect_popen import PexpectWrapper from tests.conftest import ROOT_DIR, env_path_separator DIRECTORIES = ["packages", "examples"] class TestGymExt: """Test the gym example.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.old_cwd = Path(os.getcwd()) cls.t = Path(tempfile.mkdtemp()) for directory in DIRECTORIES: dir_path = Path(directory) tmp_dir = cls.t / dir_path src_dir = cls.old_cwd / Path(ROOT_DIR, directory) shutil.copytree(str(src_dir), str(tmp_dir)) os.chdir(Path(cls.t)) def test_gym_ex(self): """Run the gym ex sequence.""" try: env = os.environ.copy() env[ "PYTHONPATH" ] = f"{self.t}{env_path_separator()}{env.get('PYTHONPATH', '')}" process = PexpectWrapper( # nosec [ sys.executable, str(Path("examples/gym_ex/train.py").resolve()), "--nb-steps", "50", ], env=env, maxread=1, encoding="utf-8", logfile=sys.stdout, ) process.expect(["Step 50/50"], timeout=10) process.wait_to_complete(5) assert process.returncode == 0, "Test failed" finally: process.terminate() process.wait_to_complete(5) @classmethod def teardown_class(cls): """Teardown the test.""" try: os.chdir(Path(cls.old_cwd)) shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_examples/test_http_client_connection_to_aries_cloud_agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """End-to-end test for AEA connecting using HTTP Client connection, to an Aries Cloud Agent.""" import asyncio import logging import os import shutil import subprocess # nosec import time from threading import Thread from typing import Optional from unittest.mock import MagicMock import pytest import yaml from aea import AEA_DIR from aea.aea import AEA from aea.configurations.base import ( ConnectionConfig, ProtocolConfig, PublicId, SkillConfig, ) from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE from aea.crypto.wallet import Wallet from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Message, Protocol from aea.registries.resources import Resources from aea.skills.base import Handler, Skill, SkillContext from packages.fetchai.connections.http_client.connection import HTTPClientConnection from packages.fetchai.protocols.http.message import HttpMessage logger = logging.getLogger(__name__) @pytest.mark.asyncio class TestAEAToACA: """End-to-end test for an AEA connecting to an ACA via the http client connection.""" @classmethod def setup_class(cls): """Initialise the class.""" cls.aca_admin_address = "127.0.0.1" cls.aca_admin_port = 8020 cls.aea_address = "some string" cls.aea_public_key = "some public_key" cls.aea_identity = Identity( "identity", address=cls.aea_address, public_key=cls.aea_public_key ) cls.cwd = os.getcwd() # check Aries Cloud Agents (ACA) is installed res = shutil.which("aca-py") if res is None: pytest.skip( "Please install Aries Cloud Agents first! See the following link: https://github.com/hyperledger/aries-cloudagent-python" ) # run an ACA # command: aca-py start --admin 127.0.0.1 8020 --admin-insecure-mode --inbound-transport http 0.0.0.0 8000 --outbound-transport http cls.process = subprocess.Popen( # nosec [ "aca-py", "start", "--admin", cls.aca_admin_address, str(cls.aca_admin_port), "--admin-insecure-mode", "--inbound-transport", "http", "0.0.0.0", "8000", "--outbound-transport", "http", ] ) time.sleep(4.0) @pytest.mark.asyncio async def test_connecting_to_aca(self): """Test connecting to aca.""" configuration = ConnectionConfig( host=self.aca_admin_address, port=self.aca_admin_port, connection_id=HTTPClientConnection.connection_id, ) http_client_connection = HTTPClientConnection( configuration=configuration, data_dir=MagicMock(), identity=self.aea_identity, ) http_client_connection.loop = asyncio.get_event_loop() # Request messages request_http_message = HttpMessage( dialogue_reference=("1", ""), target=0, message_id=1, performative=HttpMessage.Performative.REQUEST, method="GET", url="http://{}:{}/status".format( self.aca_admin_address, self.aca_admin_port ), headers="", version="", body=b"", ) request_http_message.to = "ACA" request_envelope = Envelope( to="ACA", sender="AEA", message=request_http_message, ) try: # connect to ACA await http_client_connection.connect() assert http_client_connection.is_connected is True # send request to ACA await http_client_connection.send(envelope=request_envelope) # receive response from ACA response_envelop = await http_client_connection.receive() # check the response assert response_envelop.to == self.aea_address assert response_envelop.sender == "HTTP Server" assert response_envelop.protocol_id == HttpMessage.protocol_id decoded_response_message = response_envelop.message assert ( decoded_response_message.performative == HttpMessage.Performative.RESPONSE ) assert decoded_response_message.version == "" assert decoded_response_message.status_code == 200 assert decoded_response_message.status_text == "OK" assert decoded_response_message.headers is not None assert decoded_response_message.version is not None finally: # disconnect from ACA await http_client_connection.disconnect() assert http_client_connection.is_connected is False @pytest.mark.asyncio async def test_end_to_end_aea_aca(self): """Test the end to end aea aca interaction.""" # AEA components wallet = Wallet({DEFAULT_LEDGER: DEFAULT_PRIVATE_KEY_FILE}) identity = Identity( name="my_aea_1", address=wallet.addresses.get(DEFAULT_LEDGER), public_key=wallet.public_keys.get(DEFAULT_LEDGER), default_address_key=DEFAULT_LEDGER, ) data_dir = MagicMock() configuration = ConnectionConfig( host=self.aca_admin_address, port=self.aca_admin_port, connection_id=HTTPClientConnection.connection_id, ) http_client_connection = HTTPClientConnection( configuration=configuration, data_dir=MagicMock(), identity=identity, ) resources = Resources() resources.add_connection(http_client_connection) # create AEA aea = AEA(identity, wallet, resources, data_dir) # Add http protocol to AEA resources http_protocol_configuration = ProtocolConfig.from_json( yaml.safe_load( open( os.path.join( self.cwd, "packages", "fetchai", "protocols", "http", "protocol.yaml", ) ) ) ) http_protocol = Protocol(http_protocol_configuration, HttpMessage.serializer()) resources.add_protocol(http_protocol) # Request message & envelope request_http_message = HttpMessage( dialogue_reference=("", ""), target=0, message_id=1, performative=HttpMessage.Performative.REQUEST, method="GET", url="http://{}:{}/status".format( self.aca_admin_address, self.aca_admin_port ), headers="", version="", body=b"", ) request_http_message.to = "ACA" request_envelope = Envelope( to="ACA", sender="AEA", message=request_http_message, ) # add a simple skill with handler skill_context = SkillContext(aea.context) skill_config = SkillConfig( name="simple_skill", author="fetchai", version="0.1.0" ) aea_handler = AEAHandler(skill_context=skill_context, name="aea_handler") simple_skill = Skill( skill_config, skill_context, handlers={aea_handler.name: aea_handler} ) resources.add_skill(simple_skill) # add error skill to AEA error_skill = Skill.from_dir( os.path.join(AEA_DIR, "skills", "error"), agent_context=aea.context ) resources.add_skill(error_skill) # start AEA thread t_aea = Thread(target=aea.start) try: t_aea.start() time.sleep(1.0) aea.outbox.put(request_envelope) time.sleep(5.0) assert ( aea_handler.handled_message.performative == HttpMessage.Performative.RESPONSE ) assert aea_handler.handled_message.version == "" assert aea_handler.handled_message.status_code == 200 assert aea_handler.handled_message.status_text == "OK" assert aea_handler.handled_message.headers is not None assert aea_handler.handled_message.version is not None finally: aea.stop() t_aea.join() @classmethod def teardown_class(cls): """Teardown the aca.""" # terminate the ACA cls.process.terminate() class AEAHandler(Handler): """The handler for the AEA.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id # type: Optional[PublicId] def __init__(self, **kwargs): """Initialize the handler.""" super().__init__(**kwargs) self.kwargs = kwargs self.handled_message = None def setup(self) -> None: """Implement the setup for the handler.""" pass def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ self.handled_message = message def teardown(self) -> None: """ Implement the handler teardown. :return: None """ ================================================ FILE: tests/test_packages/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages dir.""" ================================================ FILE: tests/test_packages/test_connections/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/connections dir.""" ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the Libp2p-based DHT connection.""" ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains AEA cli tests for P2PLibp2p connection.""" import json import os from aea_ledger_ethereum.ethereum import EthereumCrypto as Ethereum from aea_ledger_fetchai import FetchAICrypto as FetchAI from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.connections.p2p_libp2p.connection import ( PUBLIC_ID as P2P_CONNECTION_PUBLIC_ID, ) from tests.conftest import libp2p_log_on_failure DEFAULT_PORT = 10234 DEFAULT_DELEGATE_PORT = 11234 DEFAULT_NET_SIZE = 4 LIBP2P_LAUNCH_TIMEOUT = 20 # may downloads up to ~66Mb class TestP2PLibp2pConnectionAEARunningDefaultConfigNode(AEATestCaseEmpty): """Test AEA with p2p_libp2p connection is correctly run""" @classmethod def setup_class(cls): """Set the test up""" super(TestP2PLibp2pConnectionAEARunningDefaultConfigNode, cls).setup_class() cls.conn_key_file = os.path.join(os.path.abspath(os.getcwd()), "./conn_key.txt") cls.log_files = [] @libp2p_log_on_failure def test_agent(self): """Test with aea.""" self.generate_private_key() self.add_private_key() self.generate_private_key(private_key_file=self.conn_key_file) self.add_private_key(private_key_filepath=self.conn_key_file, connection=True) self.add_item("connection", str(P2P_CONNECTION_PUBLIC_ID)) self.run_cli_command("build", cwd=self._get_cwd()) self.set_config("agent.default_connection", str(P2P_CONNECTION_PUBLIC_ID)) # for logging config_path = "vendor.fetchai.connections.p2p_libp2p.config" log_file = "libp2p_node_{}.log".format(self.agent_name) log_file = os.path.join(os.path.abspath(os.getcwd()), log_file) self.set_config("{}.log_file".format(config_path), log_file) TestP2PLibp2pConnectionAEARunningDefaultConfigNode.log_files.append(log_file) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) process = self.run_agent() is_running = self.is_running(process, timeout=LIBP2P_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" check_strings = "Peer running in " missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) self.terminate_agents(process) assert self.is_successfully_terminated( process ), "AEA wasn't successfully terminated." @classmethod def teardown_class(cls): """Tear down the test""" cls.terminate_agents() super(TestP2PLibp2pConnectionAEARunningDefaultConfigNode, cls).teardown_class() class TestP2PLibp2pConnectionAEARunningEthereumConfigNode(AEATestCaseEmpty): """Test AEA with p2p_libp2p connection is correctly run""" @classmethod def setup_class(cls): """Set the test up""" super(TestP2PLibp2pConnectionAEARunningEthereumConfigNode, cls).setup_class() cls.conn_key_file = os.path.join(os.path.abspath(os.getcwd()), "./conn_key.txt") cls.log_files = [] @libp2p_log_on_failure def test_agent(self): """Test with aea.""" key_path = "ethereum_private_key.txt" self.generate_private_key( ledger_api_id=Ethereum.identifier, private_key_file=key_path ) self.add_private_key( ledger_api_id=Ethereum.identifier, private_key_filepath=key_path ) self.generate_private_key(private_key_file=self.conn_key_file) self.add_private_key(private_key_filepath=self.conn_key_file, connection=True) self.add_item("connection", str(P2P_CONNECTION_PUBLIC_ID)) self.run_cli_command("build", cwd=self._get_cwd()) self.set_config("agent.default_ledger", Ethereum.identifier) self.nested_set_config( "agent.required_ledgers", [FetchAI.identifier, Ethereum.identifier] ) self.set_config("agent.default_connection", str(P2P_CONNECTION_PUBLIC_ID)) # for logging config_path = "vendor.fetchai.connections.p2p_libp2p.config" log_file = "libp2p_node_{}.log".format(self.agent_name) log_file = os.path.join(os.path.abspath(os.getcwd()), log_file) self.set_config("{}.log_file".format(config_path), log_file) self.log_files.append(log_file) setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": Ethereum.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": "fetchai", "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") self.run_cli_command("issue-certificates", cwd=self._get_cwd()) process = self.run_agent() is_running = self.is_running(process, timeout=LIBP2P_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" check_strings = "Peer running in " missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) self.terminate_agents(process) assert self.is_successfully_terminated( process ), "AEA wasn't successfully terminated." @classmethod def teardown_class(cls): """Tear down the test""" cls.terminate_agents() super(TestP2PLibp2pConnectionAEARunningEthereumConfigNode, cls).teardown_class() class TestP2PLibp2pConnectionAEARunningFullNode(AEATestCaseEmpty): """Test AEA with p2p_libp2p connection is correctly run""" capture_log = True @classmethod def setup_class(cls): """Set the test up""" super(TestP2PLibp2pConnectionAEARunningFullNode, cls).setup_class() cls.conn_key_file = os.path.join(os.path.abspath(os.getcwd()), "./conn_key.txt") cls.log_files = [] @libp2p_log_on_failure def test_agent(self): """Test with aea.""" self.generate_private_key() self.add_private_key() self.generate_private_key(private_key_file=self.conn_key_file) self.add_private_key(private_key_filepath=self.conn_key_file, connection=True) self.add_item("connection", str(P2P_CONNECTION_PUBLIC_ID)) self.run_cli_command("build", cwd=self._get_cwd()) # setup a full node: with public uri, relay service, and delegate service config_path = "vendor.fetchai.connections.p2p_libp2p.config" self.set_config( "{}.local_uri".format(config_path), "127.0.0.1:{}".format(DEFAULT_PORT) ) self.set_config( "{}.public_uri".format(config_path), "127.0.0.1:{}".format(DEFAULT_PORT) ) self.set_config( "{}.delegate_uri".format(config_path), "127.0.0.1:{}".format(DEFAULT_DELEGATE_PORT), ) # for logging log_file = "libp2p_node_{}.log".format(self.agent_name) log_file = os.path.join(os.path.abspath(os.getcwd()), log_file) self.set_config("{}.log_file".format(config_path), log_file) TestP2PLibp2pConnectionAEARunningFullNode.log_files.append(log_file) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) process = self.run_agent() is_running = self.is_running(process, timeout=LIBP2P_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" check_strings = "Peer running in " missing_strings = self.missing_from_output(process, check_strings, timeout=30) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) self.terminate_agents(process) assert self.is_successfully_terminated( process ), "AEA wasn't successfully terminated." @classmethod def teardown_class(cls): """Tear down the test""" cls.terminate_agents() super(TestP2PLibp2pConnectionAEARunningFullNode, cls).teardown_class() ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_build.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Test P2PLibp2p connection build.""" import os import tempfile from io import StringIO from unittest import mock import pytest from aea.exceptions import AEAException from packages.fetchai.connections.p2p_libp2p import check_dependencies from packages.fetchai.connections.p2p_libp2p.check_dependencies import ( MINIMUM_GCC_VERSION, MINIMUM_GO_VERSION, build_node, check_versions, version_to_string, ) from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_NODE_MODULE_NAME def test_check_versions(): """Test check_versions - positive case.""" with mock.patch("sys.stdout", new_callable=StringIO) as mock_stdout: check_versions() stdout = mock_stdout.getvalue() assert f"check 'go'>={version_to_string(MINIMUM_GO_VERSION)}, found " in stdout assert f"check 'gcc'>={version_to_string(MINIMUM_GCC_VERSION)}, found " in stdout def test_check_versions_negative_binary_not_found(): """Test check_versions - negative case, binary not found.""" with mock.patch("shutil.which", return_value=None): with pytest.raises( AEAException, match="'go' is required by the libp2p connection, but it is not installed, or it is not accessible from the system path.", ): check_versions() def test_check_versions_negative_version_too_low(): """Test check_versions - negative case, version too low.""" with mock.patch.object( check_dependencies, "get_version", return_value=(0, 0, 0), ): with pytest.raises( AEAException, match=f"The installed version of 'go' is too low: expected at least {version_to_string(MINIMUM_GO_VERSION)}; found 0.0.0.", ): check_versions() def test_check_versions_negative_cannot_parse_version(): """Test the check_versions - negative case, cannot parse version.""" with mock.patch("sys.stdout", new_callable=StringIO) as mock_stdout: with mock.patch("subprocess.check_output", return_value=b""): check_versions() stdout = mock_stdout.getvalue() assert ( "Warning: cannot parse 'go' version from command: ['go', 'version']." in stdout ) assert ( "Warning: cannot parse 'gcc' version from command: ['gcc', '--version']." in stdout ) def test_build_node(): """Test build node function.""" with tempfile.TemporaryDirectory() as build_dir: build_node(build_dir) assert os.path.exists(os.path.join(build_dir, LIBP2P_NODE_MODULE_NAME)) ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_communication.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for P2PLibp2p connection.""" import asyncio import os import shutil import tempfile from unittest import mock from unittest.mock import Mock, call import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.crypto.registries import make_crypto from aea.mail.base import Empty, Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.p2p_libp2p.connection import NodeClient, Uri from packages.fetchai.protocols.default import DefaultSerializer from packages.fetchai.protocols.default.message import DefaultMessage from tests.conftest import ( _make_libp2p_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 DEFAULT_NET_SIZE = 4 MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) @pytest.mark.asyncio class TestP2PLibp2pConnectionConnectDisconnect: """Test that connection is established and torn down correctly""" def setup(self): """Set the test up""" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() os.chdir(self.t) @pytest.mark.asyncio async def test_p2plibp2pconnection_connect_disconnect_default(self): """Test connect then disconnect.""" temp_dir = os.path.join(self.t, "temp_dir") os.mkdir(temp_dir) connection = _make_libp2p_connection(data_dir=temp_dir) assert connection.is_connected is False try: await connection.connect() assert connection.is_connected is True except Exception as e: await connection.disconnect() raise e await connection.disconnect() assert connection.is_connected is False @pytest.mark.asyncio async def test_p2plibp2pconnection_connect_disconnect_ethereum(self): """Test connect then disconnect.""" temp_dir = os.path.join(self.t, "temp_dir") os.mkdir(temp_dir) crypto = make_crypto(EthereumCrypto.identifier) connection = _make_libp2p_connection(data_dir=temp_dir, agent_key=crypto) assert connection.is_connected is False try: await connection.connect() assert connection.is_connected is True except Exception as e: await connection.disconnect() raise e await connection.disconnect() assert connection.is_connected is False def teardown(self): """Tear down the test""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestP2PLibp2pConnectionEchoEnvelope: """Test that connection will route envelope to destination""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] aea_ledger_fetchai = make_crypto(FetchAICrypto.identifier) aea_ledger_ethereum = make_crypto(EthereumCrypto.identifier) try: temp_dir_1 = os.path.join(cls.t, "temp_dir_1") os.mkdir(temp_dir_1) cls.connection1 = _make_libp2p_connection( data_dir=temp_dir_1, agent_key=aea_ledger_fetchai, port=DEFAULT_PORT + 1 ) cls.multiplexer1 = Multiplexer( [cls.connection1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection1.node.log_file) cls.multiplexer1.connect() cls.multiplexers.append(cls.multiplexer1) genesis_peer = cls.connection1.node.multiaddrs[0] temp_dir_2 = os.path.join(cls.t, "temp_dir_2") os.mkdir(temp_dir_2) cls.connection2 = _make_libp2p_connection( data_dir=temp_dir_2, port=DEFAULT_PORT + 2, entry_peers=[genesis_peer], agent_key=aea_ledger_ethereum, ) cls.multiplexer2 = Multiplexer( [cls.connection2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection2.node.log_file) cls.multiplexer2.connect() cls.multiplexers.append(cls.multiplexer2) except Exception as e: cls.teardown_class() raise e def test_connection_is_established(self): """Test connection established.""" assert self.connection1.is_connected is True assert self.connection2.is_connected is True def test_envelope_routed(self): """Test envelope routed.""" addr_1 = self.connection1.node.address addr_2 = self.connection2.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) self.multiplexer1.put(envelope) delivered_envelope = self.multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg def test_envelope_echoed_back(self): """Test envelope echoed back.""" addr_1 = self.connection1.node.address addr_2 = self.connection2.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) self.multiplexer1.put(original_envelope) delivered_envelope = self.multiplexer2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_2 self.multiplexer2.put(delivered_envelope) echoed_envelope = self.multiplexer1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message != original_envelope.message assert original_envelope.message_bytes == delivered_envelope.message_bytes @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestP2PLibp2pConnectionRouting: """Test that libp2p node will reliably route envelopes in a local network""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] try: port_genesis = DEFAULT_PORT + 10 temp_dir_gen = os.path.join(cls.t, "temp_dir_gen") os.mkdir(temp_dir_gen) cls.connection_genesis = _make_libp2p_connection( data_dir=temp_dir_gen, port=port_genesis ) cls.multiplexer_genesis = Multiplexer( [cls.connection_genesis], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_genesis.node.log_file) cls.multiplexer_genesis.connect() cls.multiplexers.append(cls.multiplexer_genesis) genesis_peer = cls.connection_genesis.node.multiaddrs[0] cls.connections = [cls.connection_genesis] port = port_genesis for i in range(DEFAULT_NET_SIZE): port += 1 temp_dir = os.path.join(cls.t, f"temp_dir_{i}") os.mkdir(temp_dir) conn = _make_libp2p_connection( data_dir=temp_dir, port=port, entry_peers=[genesis_peer] ) mux = Multiplexer([conn], protocols=[MockDefaultMessageProtocol]) cls.connections.append(conn) cls.log_files.append(conn.node.log_file) mux.connect() cls.multiplexers.append(mux) except Exception as e: cls.teardown_class() raise e def test_connection_is_established(self): """Test connection established.""" assert self.connection_genesis.is_connected is True for conn in self.connections: assert conn.is_connected is True def test_star_routing_connectivity(self): """Test star routing connectivity.""" addrs = [conn.node.address for conn in self.connections] for source in range(len(self.multiplexers)): for destination in range(len(self.multiplexers)): if destination == source: continue msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addrs[destination], sender=addrs[source], message=msg, ) self.multiplexers[source].put(envelope) delivered_envelope = self.multiplexers[destination].get( block=True, timeout=10 ) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestP2PLibp2pConnectionEchoEnvelopeRelayOneDHTNode: """Test that connection will route envelope to destination using the same relay node""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] try: temp_dir_rel = os.path.join(cls.t, "temp_dir_rel") os.mkdir(temp_dir_rel) cls.relay = _make_libp2p_connection( data_dir=temp_dir_rel, port=DEFAULT_PORT + 1 ) cls.multiplexer = Multiplexer([cls.relay]) cls.log_files.append(cls.relay.node.log_file) cls.multiplexer.connect() cls.multiplexers.append(cls.multiplexer) relay_peer = cls.relay.node.multiaddrs[0] temp_dir_1 = os.path.join(cls.t, "temp_dir_1") os.mkdir(temp_dir_1) cls.connection1 = _make_libp2p_connection( data_dir=temp_dir_1, port=DEFAULT_PORT + 2, relay=False, entry_peers=[relay_peer], ) cls.multiplexer1 = Multiplexer( [cls.connection1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection1.node.log_file) cls.multiplexer1.connect() cls.multiplexers.append(cls.multiplexer1) temp_dir_2 = os.path.join(cls.t, "temp_dir_2") os.mkdir(temp_dir_2) cls.connection2 = _make_libp2p_connection( data_dir=temp_dir_2, port=DEFAULT_PORT + 3, entry_peers=[relay_peer] ) cls.multiplexer2 = Multiplexer( [cls.connection2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection2.node.log_file) cls.multiplexer2.connect() cls.multiplexers.append(cls.multiplexer2) except Exception as e: cls.teardown_class() raise e def test_connection_is_established(self): """Test connection established.""" assert self.relay.is_connected is True assert self.connection1.is_connected is True assert self.connection2.is_connected is True def test_envelope_routed(self): """Test envelope routed.""" addr_1 = self.connection1.node.address addr_2 = self.connection2.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) self.multiplexer1.put(envelope) delivered_envelope = self.multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg def test_envelope_echoed_back(self): """Test envelope echoed back.""" addr_1 = self.connection1.node.address addr_2 = self.connection2.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) self.multiplexer1.put(original_envelope) delivered_envelope = self.multiplexer2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_2 self.multiplexer2.put(delivered_envelope) echoed_envelope = self.multiplexer1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message != original_envelope.message assert original_envelope.message_bytes == delivered_envelope.message_bytes @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestP2PLibp2pConnectionRoutingRelayTwoDHTNodes: """Test that libp2p DHT network will reliably route envelopes from relay/non-relay to relay/non-relay nodes""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] try: temp_dir_rel_1 = os.path.join(cls.t, "temp_dir_rel_1") os.mkdir(temp_dir_rel_1) port_relay_1 = DEFAULT_PORT + 10 cls.connection_relay_1 = _make_libp2p_connection( data_dir=temp_dir_rel_1, port=port_relay_1 ) cls.multiplexer_relay_1 = Multiplexer( [cls.connection_relay_1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_relay_1.node.log_file) cls.multiplexer_relay_1.connect() cls.multiplexers.append(cls.multiplexer_relay_1) relay_peer_1 = cls.connection_relay_1.node.multiaddrs[0] temp_dir_rel_2 = os.path.join(cls.t, "temp_dir_rel_2") os.mkdir(temp_dir_rel_2) port_relay_2 = DEFAULT_PORT + 100 cls.connection_relay_2 = _make_libp2p_connection( data_dir=temp_dir_rel_2, port=port_relay_2, entry_peers=[relay_peer_1] ) cls.multiplexer_relay_2 = Multiplexer( [cls.connection_relay_2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_relay_2.node.log_file) cls.multiplexer_relay_2.connect() cls.multiplexers.append(cls.multiplexer_relay_2) relay_peer_2 = cls.connection_relay_2.node.multiaddrs[0] cls.connections = [cls.connection_relay_1, cls.connection_relay_2] port = port_relay_1 for i in range(int(DEFAULT_NET_SIZE / 2) + 1): temp_dir = os.path.join(cls.t, f"temp_dir_conn_{i}_1") os.mkdir(temp_dir) port += 1 conn = _make_libp2p_connection( data_dir=temp_dir, port=port, relay=False, entry_peers=[relay_peer_1], ) mux = Multiplexer([conn]) cls.connections.append(conn) cls.log_files.append(conn.node.log_file) mux.connect() cls.multiplexers.append(mux) port = port_relay_2 for i in range(int(DEFAULT_NET_SIZE / 2) + 1): temp_dir = os.path.join(cls.t, f"temp_dir_conn_{i}_2") os.mkdir(temp_dir) port += 1 conn = _make_libp2p_connection( data_dir=temp_dir, port=port, relay=False, entry_peers=[relay_peer_2], ) mux = Multiplexer([conn]) cls.connections.append(conn) cls.log_files.append(conn.node.log_file) mux.connect() cls.multiplexers.append(mux) except Exception as e: cls.teardown_class() raise e def test_connection_is_established(self): """Test connection established.""" assert self.connection_relay_1.is_connected is True assert self.connection_relay_2.is_connected is True for conn in self.connections: assert conn.is_connected is True def test_star_routing_connectivity(self): """Test star routing connectivity.""" addrs = [conn.node.address for conn in self.connections] for source in range(len(self.multiplexers)): for destination in range(len(self.multiplexers)): if destination == source: continue msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addrs[destination], sender=addrs[source], message=msg, ) self.multiplexers[source].put(envelope) delivered_envelope = self.multiplexers[destination].get( block=True, timeout=10 ) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.sender = delivered_envelope.sender msg.to = delivered_envelope.to assert envelope.message == msg @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass def test_libp2pconnection_uri(): """Test uri.""" uri = Uri(host="127.0.0.1") uri = Uri(host="127.0.0.1", port=10000) assert uri.host == "127.0.0.1" and uri.port == 10000 @pytest.mark.asyncio class TestP2PLibp2pNodeRestart: """Test node restart.""" def setup(self): """Set the test up""" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() os.chdir(self.t) @pytest.mark.asyncio async def test_node_restart(self): """Test node restart works.""" temp_dir = os.path.join(self.t, "temp_dir") os.mkdir(temp_dir) connection = _make_libp2p_connection(data_dir=temp_dir) try: await connection.node.start() pipe = connection.node.pipe assert pipe is not None await connection.node.restart() new_pipe = connection.node.pipe assert new_pipe is not None assert new_pipe is not pipe finally: await connection.node.stop() def teardown(self): """Tear down the test""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class BaseTestP2PLibp2p: """Base test class for p2p libp2p tests with two peers.""" def _make_envelope( self, sender_address: str, receiver_address: str, message_id: int = 1, target: int = 0, ): """Make an envelope for testing purposes.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=message_id, target=target, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=receiver_address, sender=sender_address, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) return envelope @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] aea_ledger_fetchai = make_crypto(FetchAICrypto.identifier) aea_ledger_ethereum = make_crypto(EthereumCrypto.identifier) try: temp_dir_1 = os.path.join(cls.t, "temp_dir_1") os.mkdir(temp_dir_1) cls.connection1 = _make_libp2p_connection( data_dir=temp_dir_1, agent_key=aea_ledger_fetchai, port=DEFAULT_PORT + 1 ) cls.multiplexer1 = Multiplexer( [cls.connection1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection1.node.log_file) cls.multiplexer1.connect() cls.multiplexers.append(cls.multiplexer1) genesis_peer = cls.connection1.node.multiaddrs[0] temp_dir_2 = os.path.join(cls.t, "temp_dir_2") os.mkdir(temp_dir_2) cls.connection2 = _make_libp2p_connection( data_dir=temp_dir_2, port=DEFAULT_PORT + 2, entry_peers=[genesis_peer], agent_key=aea_ledger_ethereum, ) cls.multiplexer2 = Multiplexer( [cls.connection2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection2.node.log_file) cls.multiplexer2.connect() cls.multiplexers.append(cls.multiplexer2) except Exception as e: cls.teardown_class() raise e @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestP2PLibp2PSendEnvelope(BaseTestP2PLibp2p): """Test that connection will send envelope with error, and that reconnection fixes it.""" def test_connection_is_established(self): """Test connection established.""" assert self.connection1.is_connected is True assert self.connection2.is_connected is True def test_envelope_routed(self): """Test envelope routed.""" addr_1 = self.connection2.node.address addr_2 = self.connection1.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) # make the send to fail # note: we don't mock the genesis peer. with mock.patch.object( self.connection2.logger, "exception" ) as _mock_logger, mock.patch.object( self.connection2.node.pipe, "write", side_effect=Exception("some error") ): self.multiplexer2.put(envelope) delivered_envelope = self.multiplexer1.get(block=True, timeout=20) _mock_logger.assert_called_with( "Failed to send after pipe reconnect. Exception: some error. Try recover connection to node and send again." ) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg @libp2p_log_on_failure_all class TestP2PLibp2PReceiveEnvelope(BaseTestP2PLibp2p): """Test that connection will receive envelope with error, and that reconnection fixes it.""" def test_envelope_routed(self): """Test envelope routed.""" addr_1 = self.connection1.node.address addr_2 = self.connection2.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) # make the receive to fail with mock.patch.object( self.connection2.logger, "exception" ) as _mock_logger, mock.patch.object( self.connection2.node.pipe, "read", side_effect=Exception("some error") ): self.multiplexer1.put(envelope) delivered_envelope = self.multiplexer2.get(block=True, timeout=20) _mock_logger.assert_has_calls( [ call( "Failed to read. Exception: some error. Try reconnect to node and read again." ) ] ) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg @libp2p_log_on_failure_all class TestLibp2pEnvelopeOrder(BaseTestP2PLibp2p): """ Test message ordering. Test that the order of envelope is the guaranteed to be the same when communicating between two peers. """ NB_ENVELOPES = 1000 def test_burst_order(self): """Test order of envelope burst is guaranteed on receiving end.""" addr_1 = self.connection1.address addr_2 = self.connection2.address sent_envelopes = [ self._make_envelope(addr_1, addr_2, i, i - 1) for i in range(1, self.NB_ENVELOPES + 1) ] for envelope in sent_envelopes: self.multiplexer1.put(envelope) received_envelopes = [] for _ in range(1, self.NB_ENVELOPES + 1): envelope = self.multiplexer2.get(block=True, timeout=20) received_envelopes.append(envelope) # test no new message is "created" with pytest.raises(Empty): self.multiplexer2.get(block=True, timeout=1) assert len(sent_envelopes) == len( received_envelopes ), f"expected number of envelopes {len(sent_envelopes)}, got {len(received_envelopes)}" for expected, actual in zip(sent_envelopes, received_envelopes): assert expected.message == actual.message, ( "message content differ; probably a wrong message " "ordering on the receiving end" ) @pytest.mark.asyncio async def test_nodeclient_pipe_connect(): """Test pipe.connect called on NodeClient.connect.""" f = asyncio.Future() f.set_result(None) pipe = Mock() pipe.connect.return_value = f node_client = NodeClient(pipe, Mock()) await node_client.connect() pipe.connect.assert_called_once() ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains Negative tests for Libp2p connection.""" import asyncio import os import platform import shutil import subprocess # nosec import sys import tempfile from asyncio.futures import Future from unittest.mock import Mock, patch import pytest from aea.configurations.base import ConnectionConfig from aea.crypto.registries import make_crypto from aea.identity.base import Identity from aea.multiplexer import Multiplexer from packages.fetchai.connections.p2p_libp2p.connection import ( LIBP2P_NODE_MODULE_NAME, Libp2pNode, P2PLibp2pConnection, _golang_module_run, _ip_all_private_or_all_public, ) from packages.fetchai.protocols.acn.message import AcnMessage from tests.conftest import DEFAULT_LEDGER, _make_libp2p_connection DEFAULT_PORT = 10234 DEFAULT_NET_SIZE = 4 class TestP2PLibp2pConnectionFailureGolangRun: """Test that golang run fails if wrong path or timeout""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) temp_dir = os.path.join(cls.t, "temp_dir") os.mkdir(temp_dir) cls.connection = _make_libp2p_connection(data_dir=temp_dir) cls.wrong_path = tempfile.mkdtemp() def test_wrong_path(self): """Test the wrong path.""" log_file_desc = open("log", "a", 1) with pytest.raises(Exception): _golang_module_run( self.wrong_path, LIBP2P_NODE_MODULE_NAME, [], log_file_desc ) def test_timeout(self): """Test the timeout.""" self.connection.node._connection_timeout = 0 muxer = Multiplexer([self.connection]) with pytest.raises(Exception): muxer.connect() muxer.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) shutil.rmtree(cls.wrong_path) except (OSError, IOError): pass class TestP2PLibp2pConnectionFailureNodeDisconnect: """Test that connection handles node disconnecting properly""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) temp_dir = os.path.join(cls.t, "temp_dir") os.mkdir(temp_dir) cls.connection = _make_libp2p_connection(data_dir=temp_dir) def test_node_disconnect(self): """Test node disconnect.""" muxer = Multiplexer([self.connection]) muxer.connect() self.connection.node.proc.terminate() self.connection.node.proc.wait() muxer.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestP2PLibp2pConnectionFailureSetupNewConnection: """Test that connection constructor ensures proper configuration""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) crypto = make_crypto(DEFAULT_LEDGER) cls.identity = Identity( "identity", address=crypto.address, public_key=crypto.public_key ) cls.host = "localhost" cls.port = "10000" cls.key_file = os.path.join(cls.t, "keyfile") crypto.dump(cls.key_file) def test_entry_peers_when_no_public_uri_provided(self): """Test entry peers when no public uri provided.""" configuration = ConnectionConfig( libp2p_key_file=None, local_uri="{}:{}".format(self.host, self.port), delegate_uri="{}:{}".format(self.host, self.port), entry_peers=None, log_file=None, connection_id=P2PLibp2pConnection.connection_id, build_directory=self.t, ) with pytest.raises(ValueError): P2PLibp2pConnection( configuration=configuration, data_dir=self.t, identity=self.identity ) def test_local_uri_provided_when_public_uri_provided(self): """Test local uri provided when public uri provided.""" configuration = ConnectionConfig( node_key_file=self.key_file, public_uri="{}:{}".format(self.host, self.port), entry_peers=None, log_file=None, connection_id=P2PLibp2pConnection.connection_id, build_directory=self.t, ) with pytest.raises(ValueError): P2PLibp2pConnection( configuration=configuration, data_dir=self.t, identity=self.identity ) @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass def test_libp2pconnection_mixed_ip_address(): """Test correct public uri ip and entry peers ips configuration.""" assert _ip_all_private_or_all_public([]) is True assert _ip_all_private_or_all_public(["127.0.0.1", "127.0.0.1"]) is True assert _ip_all_private_or_all_public(["localhost", "127.0.0.1"]) is True assert _ip_all_private_or_all_public(["10.0.0.1", "127.0.0.1"]) is False assert _ip_all_private_or_all_public(["fetch.ai", "127.0.0.1"]) is False assert _ip_all_private_or_all_public(["104.26.2.97", "127.0.0.1"]) is False assert _ip_all_private_or_all_public(["fetch.ai", "acn.fetch.ai"]) is True def test_libp2pconnection_node_config_registration_delay(): """Test node registration delay configuration""" host = "localhost" port = "10000" with tempfile.TemporaryDirectory() as data_dir: _make_libp2p_connection( port=port, host=host, data_dir=data_dir, build_directory=data_dir ) with tempfile.TemporaryDirectory() as data_dir: with pytest.raises(ValueError): _make_libp2p_connection( port=port, host=host, data_dir=data_dir, peer_registration_delay="must_be_float", build_directory=data_dir, ) def test_build_dir_not_set(): """Test build dir not set.""" host = "localhost" port = "10000" with tempfile.TemporaryDirectory() as data_dir: con = _make_libp2p_connection( port=port, host=host, data_dir=data_dir, build_directory=data_dir ) con.configuration.build_directory = None with pytest.raises( ValueError, match="Connection Configuration build directory is not set!" ): P2PLibp2pConnection( configuration=con.configuration, data_dir=data_dir, identity=con._identity, crypto_store=con.crypto_store, ) @pytest.mark.asyncio async def test_reconnect_on_write_failed(): """Test node restart on write fail.""" host = "localhost" port = "10000" with patch( "packages.fetchai.connections.p2p_libp2p.connection.P2PLibp2pConnection._check_node_built", return_value="./", ), patch("tests.conftest.build_node"), tempfile.TemporaryDirectory() as data_dir: con = _make_libp2p_connection( port=port, host=host, data_dir=data_dir, build_directory=data_dir ) node = Libp2pNode(Mock(), Mock(), "tmp", "tmp") con.node = node node.pipe = Mock() node.pipe.write = Mock(side_effect=Exception("expected")) con._node_client = node.get_client() f = Future() f.set_result(None) with patch.object( con, "_restart_node", return_value=f ) as restart_mock, patch.object( con, "_ensure_valid_envelope_for_external_comms" ), patch.object( con._node_client, "make_acn_envelope_message", return_value=b"some_data" ), pytest.raises( Exception, match="expected" ): await con._send_envelope_with_node_client(Mock()) assert node.pipe.write.call_count == 2 restart_mock.assert_called_once() @pytest.mark.asyncio async def test_reconnect_on_write_failed_reconnect_pipe(): """Test node restart on write fail.""" host = "localhost" port = "10000" with patch( "packages.fetchai.connections.p2p_libp2p.connection.P2PLibp2pConnection._check_node_built", return_value="./", ), patch("tests.conftest.build_node"), tempfile.TemporaryDirectory() as data_dir: con = _make_libp2p_connection( port=port, host=host, data_dir=data_dir, build_directory=data_dir ) node = Libp2pNode(Mock(), Mock(), "tmp", "tmp") f = Future() f.set_result(None) con.node = node node.pipe = Mock() node.pipe.connect = Mock(return_value=f) node.pipe.write = Mock(side_effect=[Exception("expected"), f]) node.pipe.close = Mock(return_value=f) con._node_client = node.get_client() status_ok = Mock() status_ok.code = int(AcnMessage.StatusBody.StatusCode.SUCCESS) status_ok_future = Future() status_ok_future.set_result(status_ok) with patch.object(con, "_ensure_valid_envelope_for_external_comms"), patch.object( node, "is_proccess_running", return_value=True ), patch.object( con._node_client, "make_acn_envelope_message", return_value=b"some_data" ), patch.object( con._node_client, "wait_for_status", lambda: status_ok_future ): await con._send_envelope_with_node_client(Mock()) assert node.pipe.write.call_count == 2 assert node.pipe.connect.call_count == 1 @pytest.mark.asyncio async def test_reconnect_on_read_failed(): """Test node restart on read fail.""" host = "localhost" port = "10000" with patch( "packages.fetchai.connections.p2p_libp2p.connection.P2PLibp2pConnection._check_node_built", return_value="./", ), patch("tests.conftest.build_node"), tempfile.TemporaryDirectory() as data_dir: con = _make_libp2p_connection( port=port, host=host, data_dir=data_dir, build_directory=data_dir ) node = Libp2pNode(Mock(), Mock(), "tmp", "tmp") con.node = node node.pipe = Mock() node.pipe.read = Mock(side_effect=Exception("expected")) con._node_client = node.get_client() f = Future() f.set_result(None) with patch.object( con, "_restart_node", return_value=f ) as restart_mock, pytest.raises(Exception, match="expected"): await con._read_envelope_from_node() assert node.pipe.read.call_count == 2 restart_mock.assert_called_once() assert node.pipe.read.call_count == 2 restart_mock.assert_called_once() @pytest.mark.asyncio async def test_max_restarts(): """Test node max restarts exception.""" node = Libp2pNode(Mock(), Mock(), "tmp", "tmp", max_restarts=0) with pytest.raises(ValueError, match="Max restarts attempts reached:"): await node.restart() @pytest.mark.asyncio async def test_node_stopped_callback(): """Test node stopped callback called.""" if not ( platform.system() != "Windows" and sys.version_info.major == 3 and sys.version_info.minor >= 8 ): pytest.skip( "Not supported on this platform. Unix and python >= 3.8 supported only" ) host = "127.0.0.1" port = "10000" with tempfile.TemporaryDirectory() as data_dir: con = _make_libp2p_connection( port=port, host=host, data_dir=data_dir, build_directory=data_dir ) con.node.logger.error = Mock() await con.node.start() subprocess.Popen.terminate(con.node.proc) await asyncio.sleep(2) con.node.logger.error.assert_called_once() await con.node.stop() with tempfile.TemporaryDirectory() as data_dir: con = _make_libp2p_connection( port=port, host=host, data_dir=data_dir, build_directory=data_dir ) con.node.logger.error = Mock() await con.node.start() await con.node.stop() await asyncio.sleep(2) con.node.logger.error.assert_not_called() @pytest.mark.asyncio async def test_send_acn_confirm_failed(): """Test nodeclient send fails on confirmation from other point .""" node = Libp2pNode(Mock(), Mock(), "tmp", "tmp") f = Future() f.set_result(None) node.pipe = Mock() node.pipe.connect = Mock(return_value=f) node.pipe.write = Mock(return_value=f) node_client = node.get_client() status = Mock() status.code = int(AcnMessage.StatusBody.StatusCode.ERROR_GENERIC) status_future = Future() status_future.set_result(status) with patch.object( node_client, "make_acn_envelope_message", return_value=b"some_data" ), patch.object( node_client, "wait_for_status", lambda: status_future ), pytest.raises( Exception, match=r"failed to send envelope. got error confirmation" ): await node_client.send_envelope(Mock()) @pytest.mark.asyncio async def test_send_acn_confirm_timeout(): """Test nodeclient send fails on timeout.""" node = Libp2pNode(Mock(), Mock(), "tmp", "tmp") f = Future() f.set_result(None) node.pipe = Mock() node.pipe.connect = Mock(return_value=f) node.pipe.write = Mock(return_value=f) node_client = node.get_client() node_client.ACN_ACK_TIMEOUT = 0.5 with patch.object( node_client, "make_acn_envelope_message", return_value=b"some_data" ), patch("asyncio.wait_for", side_effect=asyncio.TimeoutError()), pytest.raises( Exception, match=r"acn status await timeout!" ): await node_client.send_envelope(Mock()) @pytest.mark.asyncio async def test_acn_decode_error_on_read(): """Test nodeclient send fails on read.""" node = Libp2pNode(Mock(), Mock(), "tmp", "tmp") f = Future() f.set_result(b"some_data") node.pipe = Mock() node.pipe.connect = Mock(return_value=f) node_client = node.get_client() node_client.ACN_ACK_TIMEOUT = 0.5 with patch.object(node_client, "_read", lambda: f), patch.object( node_client, "write_acn_status_error", return_value=f ) as mocked_write_acn_status_error, pytest.raises( Exception, match=r"Error parsing acn message:" ): await node_client.read_envelope() mocked_write_acn_status_error.assert_called_once() @pytest.mark.asyncio async def test_write_acn_error(): """Test nodeclient write acn error.""" node = Libp2pNode(Mock(), Mock(), "tmp", "tmp") f = Future() f.set_result(b"some_data") node.pipe = Mock() node.pipe.connect = Mock(return_value=f) node_client = node.get_client() with patch.object(node_client, "_write", return_value=f) as write_mock: await node_client.write_acn_status_error("some error") write_mock.assert_called_once() ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_fault_tolerance.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains resilience and fault tolerance tests for P2PLibp2p connection.""" import os import shutil import tempfile import time import pytest from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.registries import make_crypto from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.p2p_libp2p.check_dependencies import build_node from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.default.serialization import DefaultSerializer from tests.common.utils import wait_for_condition from tests.conftest import ( MAX_FLAKY_RERUNS_INTEGRATION, _make_libp2p_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_INTEGRATION) class BaseTestLibp2pRelay: """Base test class for libp2p connection relay.""" @libp2p_log_on_failure def setup(self): """Set the test up""" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() os.chdir(self.t) build_node(self.t) self.log_files = [] self.multiplexers = [] def change_state_and_wait( self, multiplexer: Multiplexer, expected_is_connected: bool = False, timeout: int = 10, ) -> None: """ Change state of a multiplexer (either connect or disconnect) and wait. :param multiplexer: the multiplexer to connect/disconnect. :param expected_is_connected: whether it should be connected or disconnected. :param timeout: the maximum number seconds to wait. :return: None """ wait_for_condition( lambda: multiplexer.is_connected == expected_is_connected, timeout=timeout ) def teardown(self): """Tear down the test""" for mux in self.multiplexers: mux.disconnect() os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestLibp2pConnectionRelayNodeRestartIncomingEnvelopes(BaseTestLibp2pRelay): """Test that connection will reliably receive envelopes after its relay node restarted""" @libp2p_log_on_failure def setup(self): """Set the test up""" super().setup() temp_dir_gen = os.path.join(self.t, "temp_dir_gen") os.mkdir(temp_dir_gen) self.genesis = _make_libp2p_connection( data_dir=temp_dir_gen, port=DEFAULT_PORT + 1, build_directory=self.t ) self.multiplexer_genesis = Multiplexer( [self.genesis], protocols=[DefaultMessage] ) self.multiplexer_genesis.connect() self.log_files.append(self.genesis.node.log_file) self.multiplexers.append(self.multiplexer_genesis) genesis_peer = self.genesis.node.multiaddrs[0] file = "node_key" make_crypto(DEFAULT_LEDGER).dump(file) self.relay_key_path = file temp_dir_rel = os.path.join(self.t, "temp_dir_rel") os.mkdir(temp_dir_rel) self.relay = _make_libp2p_connection( data_dir=temp_dir_rel, port=DEFAULT_PORT + 2, entry_peers=[genesis_peer], node_key_file=self.relay_key_path, build_directory=self.t, ) self.multiplexer_relay = Multiplexer([self.relay], protocols=[DefaultMessage]) self.multiplexer_relay.connect() self.log_files.append(self.relay.node.log_file) self.multiplexers.append(self.multiplexer_relay) relay_peer = self.relay.node.multiaddrs[0] temp_dir_1 = os.path.join(self.t, "temp_dir_1") os.mkdir(temp_dir_1) self.connection = _make_libp2p_connection( data_dir=temp_dir_1, port=DEFAULT_PORT + 3, relay=False, entry_peers=[relay_peer], build_directory=self.t, ) self.multiplexer = Multiplexer([self.connection], protocols=[DefaultMessage]) self.multiplexer.connect() self.log_files.append(self.connection.node.log_file) self.multiplexers.append(self.multiplexer) temp_dir_2 = os.path.join(self.t, "temp_dir_2") os.mkdir(temp_dir_2) self.connection2 = _make_libp2p_connection( data_dir=temp_dir_2, port=DEFAULT_PORT + 4, relay=False, entry_peers=[relay_peer], build_directory=self.t, ) self.multiplexer2 = Multiplexer([self.connection2], protocols=[DefaultMessage]) self.multiplexer2.connect() self.log_files.append(self.connection2.node.log_file) self.multiplexers.append(self.multiplexer2) def test_connection_is_established(self): """Test connection established.""" assert self.relay.is_connected is True assert self.connection.is_connected is True assert self.connection2.is_connected is True def test_envelope_routed_from_peer_after_relay_restart(self): """Test envelope routed from third peer after relay restart.""" addr_1 = self.genesis.address addr_2 = self.connection.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_genesis.put(envelope) delivered_envelope = self.multiplexer.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes self.multiplexer_relay.disconnect() self.change_state_and_wait(self.multiplexer_relay, expected_is_connected=False) # currently, multiplexer cannot be restarted self.multiplexer_relay = Multiplexer([self.relay], protocols=[DefaultMessage]) self.multiplexer_relay.connect() self.change_state_and_wait(self.multiplexer_relay, expected_is_connected=True) self.multiplexers.append(self.multiplexer_relay) msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"helloAfterRestart", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) time.sleep(10) self.multiplexer_genesis.put(envelope) delivered_envelope = self.multiplexer.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes def test_envelope_routed_from_client_after_relay_restart(self): """Test envelope routed from third relay client after relay restart.""" addr_1 = self.connection.address addr_2 = self.connection2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_1, sender=addr_2, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer2.put(envelope) delivered_envelope = self.multiplexer.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes self.multiplexer_relay.disconnect() self.change_state_and_wait(self.multiplexer_relay, expected_is_connected=False) # currently, multiplexer cannot be restarted self.multiplexer_relay = Multiplexer([self.relay], protocols=[DefaultMessage]) self.multiplexer_relay.connect() self.change_state_and_wait(self.multiplexer_relay, expected_is_connected=True) self.multiplexers.append(self.multiplexer_relay) msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"helloAfterRestart", ) envelope = Envelope( to=addr_1, sender=addr_2, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) time.sleep(10) self.multiplexer2.put(envelope) delivered_envelope = self.multiplexer.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes @libp2p_log_on_failure_all class TestLibp2pConnectionRelayNodeRestartOutgoingEnvelopes(BaseTestLibp2pRelay): """Test that connection will reliably route envelope to destination in case of relay node restart within timeout""" @libp2p_log_on_failure def setup(self): """Set the test up""" super().setup() temp_dir_gen = os.path.join(self.t, "temp_dir_gen") os.mkdir(temp_dir_gen) self.genesis = _make_libp2p_connection( data_dir=temp_dir_gen, port=DEFAULT_PORT + 1, build_directory=self.t ) self.multiplexer_genesis = Multiplexer( [self.genesis], protocols=[DefaultMessage] ) self.multiplexer_genesis.connect() self.log_files.append(self.genesis.node.log_file) self.multiplexers.append(self.multiplexer_genesis) genesis_peer = self.genesis.node.multiaddrs[0] file = "node_key" make_crypto(DEFAULT_LEDGER).dump(file) self.relay_key_path = file temp_dir_rel = os.path.join(self.t, "temp_dir_rel") os.mkdir(temp_dir_rel) self.relay = _make_libp2p_connection( data_dir=temp_dir_rel, port=DEFAULT_PORT + 2, entry_peers=[genesis_peer], node_key_file=self.relay_key_path, build_directory=self.t, ) self.multiplexer_relay = Multiplexer([self.relay], protocols=[DefaultMessage]) self.multiplexer_relay.connect() self.log_files.append(self.relay.node.log_file) self.multiplexers.append(self.multiplexer_relay) relay_peer = self.relay.node.multiaddrs[0] temp_dir_1 = os.path.join(self.t, "temp_dir_1") os.mkdir(temp_dir_1) self.connection = _make_libp2p_connection( data_dir=temp_dir_1, port=DEFAULT_PORT + 3, relay=False, entry_peers=[relay_peer], build_directory=self.t, ) self.multiplexer = Multiplexer([self.connection], protocols=[DefaultMessage]) self.multiplexer.connect() self.log_files.append(self.connection.node.log_file) self.multiplexers.append(self.multiplexer) def test_connection_is_established(self): """Test connection established.""" assert self.relay.is_connected is True assert self.connection.is_connected is True def test_envelope_routed_after_relay_restart(self): """Test envelope routed after relay restart.""" addr_1 = self.connection.address addr_2 = self.genesis.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer.put(envelope) delivered_envelope = self.multiplexer_genesis.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes self.multiplexer_relay.disconnect() self.change_state_and_wait(self.multiplexer_relay, expected_is_connected=False) msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"helloAfterRestart", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) time.sleep(10) self.multiplexer.put(envelope) # currently, multiplexer cannot be restarted self.multiplexer_relay = Multiplexer([self.relay], protocols=[DefaultMessage]) self.multiplexer_relay.connect() self.change_state_and_wait(self.multiplexer_relay, expected_is_connected=True) self.multiplexers.append(self.multiplexer_relay) delivered_envelope = self.multiplexer_genesis.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes @libp2p_log_on_failure_all class TestLibp2pConnectionAgentMobility(BaseTestLibp2pRelay): """Test that connection will correctly route envelope to destination that changed its peer""" @libp2p_log_on_failure def setup(self): """Set the test up""" super().setup() temp_dir_gen = os.path.join(self.t, "temp_dir_gen") os.mkdir(temp_dir_gen) self.genesis = _make_libp2p_connection(data_dir=temp_dir_gen, port=DEFAULT_PORT) self.multiplexer_genesis = Multiplexer( [self.genesis], protocols=[DefaultMessage] ) self.log_files.append(self.genesis.node.log_file) self.multiplexer_genesis.connect() self.multiplexers.append(self.multiplexer_genesis) genesis_peer = self.genesis.node.multiaddrs[0] temp_dir_1 = os.path.join(self.t, "temp_dir_1") os.mkdir(temp_dir_1) self.connection1 = _make_libp2p_connection( data_dir=temp_dir_1, port=DEFAULT_PORT + 1, entry_peers=[genesis_peer] ) self.multiplexer1 = Multiplexer([self.connection1], protocols=[DefaultMessage]) self.log_files.append(self.connection1.node.log_file) self.multiplexer1.connect() self.multiplexers.append(self.multiplexer1) self.connection_key = make_crypto(DEFAULT_LEDGER) temp_dir_2 = os.path.join(self.t, "temp_dir_2") os.mkdir(temp_dir_2) self.connection2 = _make_libp2p_connection( data_dir=temp_dir_2, port=DEFAULT_PORT + 2, entry_peers=[genesis_peer], agent_key=self.connection_key, ) self.multiplexer2 = Multiplexer([self.connection2], protocols=[DefaultMessage]) self.log_files.append(self.connection2.node.log_file) self.multiplexer2.connect() self.multiplexers.append(self.multiplexer2) def test_connection_is_established(self): """Test connection established.""" assert self.connection1.is_connected is True assert self.connection2.is_connected is True def test_envelope_routed_after_peer_changed(self): """Test envelope routed after peer changed.""" addr_1 = self.connection1.address addr_2 = self.connection2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer1.put(envelope) delivered_envelope = self.multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes self.multiplexer2.disconnect() self.change_state_and_wait(self.multiplexer2, expected_is_connected=False) # currently, multiplexer cannot be restarted self.multiplexer2 = Multiplexer([self.connection2], protocols=[DefaultMessage]) self.multiplexer2.connect() self.change_state_and_wait(self.multiplexer2, expected_is_connected=True) self.multiplexers.append(self.multiplexer2) msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"helloAfterChangingPeer", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=msg.protocol_specification_id, message=msg.encode(), ) time.sleep(10) self.multiplexer1.put(envelope) delivered_envelope = self.multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message_bytes == envelope.message_bytes ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_integration.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for P2PLibp2p connection.""" import itertools import os import shutil import tempfile from copy import copy from unittest.mock import Mock from aea.helpers.acn.uri import Uri from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.protocols.default.message import DefaultMessage from tests.conftest import ( _make_libp2p_client_connection, _make_libp2p_connection, _make_libp2p_mailbox_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 DEFAULT_NET_SIZE = 4 MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) @libp2p_log_on_failure_all class TestP2PLibp2pConnectionIntegrationTest: """Test mix of relay/delegate agents and client connections work together""" BASE_PORT_NUM: int = DEFAULT_PORT @classmethod def get_port(cls) -> int: """Get next port to use for libp2p.""" cls.BASE_PORT_NUM += 1 return cls.BASE_PORT_NUM @classmethod def make_connection(cls, name, **kwargs): """Make a p2p connection.""" if name in cls.multiplexers_dict: raise ValueError(f"Connection with name `{name}` already added") temp_dir = os.path.join(cls.t, name) os.mkdir(temp_dir) conn_options = copy(kwargs) conn_options["port"] = conn_options.get("port", cls.get_port()) conn_options["data_dir"] = conn_options.get("data_dir", temp_dir) conn = _make_libp2p_connection(**conn_options) multiplexer = Multiplexer([conn], protocols=[MockDefaultMessageProtocol]) cls.log_files.append(conn.node.log_file) multiplexer.connect() cls.multiplexers_dict[name] = multiplexer cls.connections_dict[name] = conn return conn @classmethod def make_client_connection(cls, name, **kwargs): """Make a p2p client connection.""" if name in cls.multiplexers_dict: raise ValueError(f"Connection with name `{name}` already added") temp_dir = os.path.join(cls.t, name) os.mkdir(temp_dir) conn_options = copy(kwargs) conn_options["data_dir"] = conn_options.get("data_dir", temp_dir) conn = _make_libp2p_client_connection(**conn_options) multiplexer = Multiplexer([conn], protocols=[MockDefaultMessageProtocol]) multiplexer.connect() cls.multiplexers_dict[name] = multiplexer cls.connections_dict[name] = conn return conn @classmethod def make_mailbox_connection(cls, name, **kwargs): """Make a p2p mailbox connection.""" if name in cls.multiplexers_dict: raise ValueError(f"Connection with name `{name}` already added") temp_dir = os.path.join(cls.t, name) os.mkdir(temp_dir) conn_options = copy(kwargs) conn_options["data_dir"] = conn_options.get("data_dir", temp_dir) conn = _make_libp2p_mailbox_connection(**conn_options) multiplexer = Multiplexer([conn], protocols=[MockDefaultMessageProtocol]) multiplexer.connect() cls.multiplexers_dict[name] = multiplexer cls.connections_dict[name] = conn return conn @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers_dict = {} cls.connections_dict = {} cls.multiplexers = [] try: cls.main_relay = cls.make_connection("main_relay", relay=True) main_relay = cls.main_relay.node.multiaddrs[0] cls.relay_2 = cls.make_connection( "relay_2", entry_peers=[main_relay], relay=True ) relay_peer_2 = cls.relay_2.node.multiaddrs[0] cls.delegate_1 = cls.make_connection( "delegate_1", entry_peers=[main_relay], relay=True, delegate=True, delegate_port=cls.get_port(), mailbox_port=cls.get_port(), mailbox=True, ) cls.delegate_2 = cls.make_connection( "delegate_2", entry_peers=[relay_peer_2], relay=True, delegate=True, delegate_port=cls.get_port(), mailbox_port=cls.get_port(), mailbox=True, ) cls.agent_connection_1 = cls.make_connection( "agent_connection_1", entry_peers=[main_relay], relay=False, delegate=False, ) cls.agent_connection_2 = cls.make_connection( "agent_connection_2", entry_peers=[relay_peer_2], relay=False, delegate=False, ) cls.client_connection_1 = cls.make_client_connection( "client_1", peer_public_key=cls.delegate_1.node.pub, **cls.get_delegate_host_port(cls.delegate_1.node.delegate_uri), ) cls.client_connection_2 = cls.make_client_connection( "client_2", peer_public_key=cls.delegate_2.node.pub, **cls.get_delegate_host_port(cls.delegate_2.node.delegate_uri), ) cls.mailbox_connection_1 = cls.make_mailbox_connection( "mailbox_1", peer_public_key=cls.delegate_1.node.pub, **cls.get_delegate_host_port(Uri(cls.delegate_1.node.mailbox_uri)), ) cls.mailbox_connection_2 = cls.make_mailbox_connection( "mailbox_2", peer_public_key=cls.delegate_2.node.pub, **cls.get_delegate_host_port(Uri(cls.delegate_2.node.mailbox_uri)), ) except Exception: cls.teardown_class() raise @classmethod def get_delegate_host_port(cls, delegate_uri: Uri) -> dict: """Get delegate/mailbox server config dict.""" return {"node_port": delegate_uri.port, "node_host": delegate_uri.host} def test_connection_is_established(self): """Test connection established.""" for conn in self.connections_dict.values(): assert conn.is_connected is True def send_message(self, from_name: str, to_name: str) -> None: """Send message from one connection to another and check it's delivered.""" from_addr = self.connections_dict[from_name].address # type: ignore to_addr = self.connections_dict[to_name].address # type: ignore from_multiplexer = self.multiplexers_dict[from_name] # type: ignore to_multiplexer = self.multiplexers_dict[to_name] # type: ignore msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=to_addr, sender=from_addr, message=msg, ) from_multiplexer.put(envelope) delivered_envelope = to_multiplexer.get(block=True, timeout=10) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) # type: ignore msg.sender = delivered_envelope.sender msg.to = delivered_envelope.to assert envelope.message == msg def test_send_and_receive(self): """Test envelope send/received by every pair of connection.""" for from_name, to_name in itertools.permutations( [ "client_1", "client_2", "agent_connection_1", "agent_connection_2", "mailbox_1", "mailbox_2", ], 2, ): self.send_message(from_name, to_name) @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() for mux in cls.multiplexers_dict.values(): mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains integration tests for P2PLibp2p connection.""" import os import shutil import tempfile import pytest from aea.helpers.base import CertRequest from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from aea.test_tools.test_cases import AEATestCaseMany from packages.fetchai.connections.p2p_libp2p.connection import ( PUBLIC_ID as P2P_CONNECTION_PUBLIC_ID, ) from packages.fetchai.connections.p2p_libp2p_client.connection import ( PUBLIC_ID as P2P_CLIENT_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.default.message import DefaultMessage from tests.conftest import ( PUBLIC_DHT_DELEGATE_URI_1, PUBLIC_DHT_DELEGATE_URI_2, PUBLIC_DHT_P2P_MADDR_1, PUBLIC_DHT_P2P_MADDR_2, PUBLIC_DHT_P2P_PUBLIC_KEY_1, PUBLIC_DHT_P2P_PUBLIC_KEY_2, PUBLIC_STAGING_DHT_DELEGATE_URI_1, PUBLIC_STAGING_DHT_DELEGATE_URI_2, PUBLIC_STAGING_DHT_P2P_MADDR_1, PUBLIC_STAGING_DHT_P2P_MADDR_2, PUBLIC_STAGING_DHT_P2P_PUBLIC_KEY_1, PUBLIC_STAGING_DHT_P2P_PUBLIC_KEY_2, _make_libp2p_client_connection, _make_libp2p_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 PUBLIC_DHT_MADDRS = [PUBLIC_DHT_P2P_MADDR_1, PUBLIC_DHT_P2P_MADDR_2] PUBLIC_DHT_DELEGATE_URIS = [PUBLIC_DHT_DELEGATE_URI_1, PUBLIC_DHT_DELEGATE_URI_2] PUBLIC_DHT_PUBLIC_KEYS = [PUBLIC_DHT_P2P_PUBLIC_KEY_1, PUBLIC_DHT_P2P_PUBLIC_KEY_2] PUBLIC_STAGING_DHT_MADDRS = [ PUBLIC_STAGING_DHT_P2P_MADDR_1, PUBLIC_STAGING_DHT_P2P_MADDR_2, ] PUBLIC_STAGING_DHT_DELEGATE_URIS = [ PUBLIC_STAGING_DHT_DELEGATE_URI_1, PUBLIC_STAGING_DHT_DELEGATE_URI_2, ] PUBLIC_STAGING_DHT_PUBLIC_KEYS = [ PUBLIC_STAGING_DHT_P2P_PUBLIC_KEY_1, PUBLIC_STAGING_DHT_P2P_PUBLIC_KEY_2, ] AEA_DEFAULT_LAUNCH_TIMEOUT = 20 AEA_LIBP2P_LAUNCH_TIMEOUT = 20 @pytest.fixture def maddrs(request): """Fixture for multi addresses.""" return request.param @pytest.fixture def delegate_uris_public_keys(request): """Fixture for delegate uris and public keys.""" return request.param @pytest.mark.integration @libp2p_log_on_failure_all class TestLibp2pConnectionPublicDHTRelay: """Test that public DHT's relay service is working properly""" def setup(self): """Set the test up""" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() os.chdir(self.t) self.log_files = [] @pytest.mark.parametrize( "maddrs", [PUBLIC_DHT_MADDRS, PUBLIC_STAGING_DHT_MADDRS], indirect=True ) def test_connectivity(self, maddrs): """Test connectivity.""" for i, maddr in enumerate(maddrs): temp_dir = os.path.join(self.t, f"dir_{i}") os.mkdir(temp_dir) connection = _make_libp2p_connection( port=DEFAULT_PORT + 1, relay=False, entry_peers=[maddr], data_dir=temp_dir, ) multiplexer = Multiplexer([connection]) self.log_files.append(connection.node.log_file) multiplexer.connect() try: assert ( connection.is_connected is True ), "Couldn't connect to public node {}".format(maddr) except Exception: raise finally: multiplexer.disconnect() @pytest.mark.parametrize( "maddrs", [PUBLIC_DHT_MADDRS, PUBLIC_STAGING_DHT_MADDRS], indirect=True ) def test_communication_direct(self, maddrs): """Test communication direct.""" for i, maddr in enumerate(maddrs): multiplexers = [] try: temp_dir_1 = os.path.join(self.t, f"dir_{i}_1") os.mkdir(temp_dir_1) connection1 = _make_libp2p_connection( port=DEFAULT_PORT + 1, relay=False, entry_peers=[maddr], data_dir=temp_dir_1, ) multiplexer1 = Multiplexer([connection1]) self.log_files.append(connection1.node.log_file) multiplexer1.connect() multiplexers.append(multiplexer1) temp_dir_2 = os.path.join(self.t, f"dir_{i}_2") os.mkdir(temp_dir_2) connection2 = _make_libp2p_connection( port=DEFAULT_PORT + 2, relay=False, entry_peers=[maddr], data_dir=temp_dir_2, ) multiplexer2 = Multiplexer([connection2]) self.log_files.append(connection2.node.log_file) multiplexer2.connect() multiplexers.append(multiplexer2) addr_1 = connection1.node.address addr_2 = connection2.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) multiplexer1.put(envelope) delivered_envelope = multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg except Exception: raise finally: for mux in multiplexers: mux.disconnect() @pytest.mark.parametrize( "maddrs", [PUBLIC_DHT_MADDRS, PUBLIC_STAGING_DHT_MADDRS], indirect=True ) def test_communication_indirect(self, maddrs): """Test communication indirect.""" assert len(maddrs) > 1, "Test requires at least 2 public dht node" for i in range(len(maddrs)): multiplexers = [] try: temp_dir_1 = os.path.join(self.t, f"dir_{i}__") os.mkdir(temp_dir_1) connection1 = _make_libp2p_connection( port=DEFAULT_PORT + 1, relay=False, entry_peers=[maddrs[i]], data_dir=temp_dir_1, ) multiplexer1 = Multiplexer([connection1]) self.log_files.append(connection1.node.log_file) multiplexer1.connect() multiplexers.append(multiplexer1) addr_1 = connection1.node.address for j in range(len(maddrs)): if j == i: continue temp_dir_2 = os.path.join(self.t, f"dir_{i}_{j}") os.mkdir(temp_dir_2) connection2 = _make_libp2p_connection( port=DEFAULT_PORT + 2, relay=False, entry_peers=[maddrs[j]], data_dir=temp_dir_2, ) multiplexer2 = Multiplexer([connection2]) self.log_files.append(connection2.node.log_file) multiplexer2.connect() multiplexers.append(multiplexer2) addr_2 = connection2.node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) multiplexer1.put(envelope) delivered_envelope = multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg multiplexer2.disconnect() del multiplexers[-1] except Exception: raise finally: for mux in multiplexers: mux.disconnect() def teardown(self): """Tear down the test""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass @pytest.mark.integration class TestLibp2pConnectionPublicDHTDelegate: """Test that public DHT's delegate service is working properly""" def setup(self): """Set the test up""" self.cwd = os.getcwd() self.t = tempfile.mkdtemp() os.chdir(self.t) @pytest.mark.parametrize( "delegate_uris_public_keys", [ (PUBLIC_DHT_DELEGATE_URIS, PUBLIC_DHT_PUBLIC_KEYS), (PUBLIC_STAGING_DHT_DELEGATE_URIS, PUBLIC_STAGING_DHT_PUBLIC_KEYS), ], indirect=True, ) def test_connectivity(self, delegate_uris_public_keys): """Test connectivity.""" delegate_uris, public_keys = delegate_uris_public_keys for i in range(len(delegate_uris)): uri = delegate_uris[i] peer_public_key = public_keys[i] temp_dir = os.path.join(self.t, f"dir_{i}") os.mkdir(temp_dir) connection = _make_libp2p_client_connection( peer_public_key=peer_public_key, uri=uri, data_dir=temp_dir ) multiplexer = Multiplexer([connection]) try: multiplexer.connect() assert ( connection.is_connected is True ), "Couldn't connect to public node {}".format(uri) except Exception: raise finally: multiplexer.disconnect() @pytest.mark.parametrize( "delegate_uris_public_keys", [ (PUBLIC_DHT_DELEGATE_URIS, PUBLIC_DHT_PUBLIC_KEYS), (PUBLIC_STAGING_DHT_DELEGATE_URIS, PUBLIC_STAGING_DHT_PUBLIC_KEYS), ], indirect=True, ) def test_communication_direct(self, delegate_uris_public_keys): """Test communication direct (i.e. both clients registered to same peer).""" delegate_uris, public_keys = delegate_uris_public_keys for i in range(len(delegate_uris)): uri = delegate_uris[i] peer_public_key = public_keys[i] multiplexers = [] try: temp_dir_1 = os.path.join(self.t, f"dir_{i}_1") os.mkdir(temp_dir_1) connection1 = _make_libp2p_client_connection( peer_public_key=peer_public_key, uri=uri, data_dir=temp_dir_1 ) multiplexer1 = Multiplexer([connection1]) multiplexer1.connect() multiplexers.append(multiplexer1) temp_dir_2 = os.path.join(self.t, f"dir_{i}_2") os.mkdir(temp_dir_2) connection2 = _make_libp2p_client_connection( peer_public_key=peer_public_key, uri=uri, data_dir=temp_dir_2 ) multiplexer2 = Multiplexer([connection2]) multiplexer2.connect() multiplexers.append(multiplexer2) addr_1 = connection1.address addr_2 = connection2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) multiplexer1.put(envelope) delivered_envelope = multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg except Exception: raise finally: for mux in multiplexers: mux.disconnect() @pytest.mark.parametrize( "delegate_uris_public_keys", [ (PUBLIC_DHT_DELEGATE_URIS, PUBLIC_DHT_PUBLIC_KEYS), (PUBLIC_STAGING_DHT_DELEGATE_URIS, PUBLIC_STAGING_DHT_PUBLIC_KEYS), ], indirect=True, ) def test_communication_indirect(self, delegate_uris_public_keys): """Test communication indirect (i.e. clients registered to different peers).""" delegate_uris, public_keys = delegate_uris_public_keys assert len(delegate_uris) > 1, "Test requires at least 2 public dht node" for i in range(len(delegate_uris)): multiplexers = [] try: temp_dir_1 = os.path.join(self.t, f"dir_{i}__") os.mkdir(temp_dir_1) connection1 = _make_libp2p_client_connection( peer_public_key=public_keys[i], uri=delegate_uris[i], data_dir=temp_dir_1, ) multiplexer1 = Multiplexer([connection1]) multiplexer1.connect() multiplexers.append(multiplexer1) addr_1 = connection1.address for j in range(len(delegate_uris)): if j == i: continue temp_dir_2 = os.path.join(self.t, f"dir_{i}_{j}") os.mkdir(temp_dir_2) connection2 = _make_libp2p_client_connection( peer_public_key=public_keys[j], uri=delegate_uris[j], data_dir=temp_dir_2, ) multiplexer2 = Multiplexer([connection2]) multiplexer2.connect() multiplexers.append(multiplexer2) addr_2 = connection2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, message=msg, ) multiplexer1.put(envelope) delivered_envelope = multiplexer2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message != envelope.message msg = DefaultMessage.serializer.decode(delivered_envelope.message) msg.to = delivered_envelope.to msg.sender = delivered_envelope.sender assert envelope.message == msg multiplexer2.disconnect() del multiplexers[-1] except Exception: raise finally: for mux in multiplexers: mux.disconnect() def teardown(self): """Tear down the test""" os.chdir(self.cwd) try: shutil.rmtree(self.t) except (OSError, IOError): pass @pytest.mark.integration class TestLibp2pConnectionPublicDHTRelayAEACli(AEATestCaseMany): """Test that public DHT's relay service is working properly, using aea cli""" @libp2p_log_on_failure @pytest.mark.parametrize( "maddrs", [PUBLIC_DHT_MADDRS, PUBLIC_STAGING_DHT_MADDRS], indirect=True ) def test_connectivity(self, maddrs): """Test connectivity.""" self.log_files = [] self.agent_name = "some" self.create_agents(self.agent_name) self.set_agent_context(self.agent_name) self.conn_key_file = os.path.join( os.path.abspath(os.getcwd()), "./conn_key.txt" ) self.generate_private_key() self.add_private_key() self.generate_private_key(private_key_file=self.conn_key_file) self.add_private_key(private_key_filepath=self.conn_key_file, connection=True) self.add_item("connection", str(P2P_CONNECTION_PUBLIC_ID)) self.run_cli_command("build", cwd=self._get_cwd()) self.set_config("agent.default_connection", str(P2P_CONNECTION_PUBLIC_ID)) # for logging log_file = "libp2p_node_{}.log".format(self.agent_name) log_file = os.path.join(os.path.abspath(os.getcwd()), log_file) config_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config( config_path, { "local_uri": "127.0.0.1:{}".format(DEFAULT_PORT), "entry_peers": maddrs, "log_file": log_file, }, ) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) self.log_files = [log_file] process = self.run_agent() is_running = self.is_running(process, timeout=AEA_LIBP2P_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" check_strings = "Peer running in " missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) self.terminate_agents(process) assert self.is_successfully_terminated( process ), "AEA wasn't successfully terminated." def teardown(self): """Clean up after test case run.""" self.unset_agent_context() self.run_cli_command("delete", self.agent_name) @pytest.mark.integration class TestLibp2pConnectionPublicDHTDelegateAEACli(AEATestCaseMany): """Test that public DHT's delegate service is working properly, using aea cli""" @pytest.mark.parametrize( "delegate_uris_public_keys", [ (PUBLIC_DHT_DELEGATE_URIS, PUBLIC_DHT_PUBLIC_KEYS), (PUBLIC_STAGING_DHT_DELEGATE_URIS, PUBLIC_STAGING_DHT_PUBLIC_KEYS), ], indirect=True, ) def test_connectivity(self, delegate_uris_public_keys): """Test connectivity.""" delegate_uris, public_keys = delegate_uris_public_keys self.agent_name = "some" self.create_agents(self.agent_name) self.set_agent_context(self.agent_name) self.generate_private_key() self.add_private_key() self.add_item("connection", str(P2P_CLIENT_CONNECTION_PUBLIC_ID)) config_path = "vendor.fetchai.connections.p2p_libp2p_client.config" self.nested_set_config( config_path, {"nodes": [{"uri": "{}".format(uri)} for uri in delegate_uris]}, ) conn_path = "vendor.fetchai.connections.p2p_libp2p_client" self.nested_set_config( conn_path + ".config", { "nodes": [ {"uri": uri, "public_key": public_keys[i]} for i, uri in enumerate(delegate_uris) ] }, ) # generate certificates for connection self.nested_set_config( conn_path + ".cert_requests", [ CertRequest( identifier="acn", ledger_id="fetchai", not_after="2022-01-01", not_before="2021-01-01", public_key=public_key, message_format="{public_key}", save_path=f"./cli_test_cert_{public_key}.txt", ) for public_key in public_keys ], ) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) process = self.run_agent() is_running = self.is_running(process, timeout=AEA_DEFAULT_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" self.terminate_agents(process) assert self.is_successfully_terminated( process ), "AEA wasn't successfully terminated." def teardown(self): """Clean up after test case run.""" self.unset_agent_context() self.run_cli_command("delete", self.agent_name) ================================================ FILE: tests/test_packages/test_connections/test_p2p_libp2p/test_slow_queue.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for P2PLibp2p connection.""" import os import shutil import tempfile from unittest.mock import Mock import pytest from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.protocols.default.message import DefaultMessage from tests.common.utils import wait_for_condition from tests.conftest import ( _make_libp2p_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) @libp2p_log_on_failure_all class TestSlowQueue: """Test that libp2p node uses slow queue in case of long DHT lookups.""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] try: port_genesis = DEFAULT_PORT + 10 temp_dir_gen = os.path.join(cls.t, "temp_dir_gen") os.mkdir(temp_dir_gen) cls.bad_address = _make_libp2p_connection( data_dir=temp_dir_gen, port=port_genesis ).node.address cls.connection_genesis = _make_libp2p_connection( data_dir=temp_dir_gen, port=port_genesis ) cls.multiplexer_genesis = Multiplexer( [cls.connection_genesis], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_genesis.node.log_file) cls.multiplexer_genesis.connect() cls.multiplexers.append(cls.multiplexer_genesis) genesis_peer = cls.connection_genesis.node.multiaddrs[0] cls.connections = [cls.connection_genesis] temp_dir = os.path.join(cls.t, "temp_dir_100") os.mkdir(temp_dir) cls.conn = _make_libp2p_connection( data_dir=temp_dir, port=port_genesis + 100, entry_peers=[genesis_peer] ) port = port_genesis for i in range(2): port += 1 temp_dir = os.path.join(cls.t, f"temp_dir_{i}") os.mkdir(temp_dir) conn = _make_libp2p_connection( data_dir=temp_dir, port=port, entry_peers=[genesis_peer] ) mux = Multiplexer([conn], protocols=[MockDefaultMessageProtocol]) cls.connections.append(conn) cls.log_files.append(conn.node.log_file) mux.connect() cls.multiplexers.append(mux) for conn in cls.connections: assert conn.is_connected is True except Exception as e: cls.teardown_class() raise e @pytest.mark.asyncio async def test_slow_queue(self): """Test slow queue.""" con2 = self.connections[-1] await self.conn.connect() def _make_envelope(addr): msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr, sender=self.conn.node.address, message=msg, ) return envelope try: for _ in range(50): for addr in [con2.node.address, self.bad_address]: await self.conn._node_client.send_envelope(_make_envelope(addr)) for _ in range(2): for addr in [self.bad_address, con2.node.address]: await self.conn._node_client.send_envelope(_make_envelope(addr)) def _check(): with open(self.conn.node.log_file) as f: return "while sending slow envelope:" in f.read() wait_for_condition(_check, timeout=30, period=0.1) finally: await self.conn.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_packages/test_connections/test_prometheus/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the prometheus connection implementation.""" ================================================ FILE: tests/test_packages/test_connections/test_prometheus/test_prometheus.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the prometheus connection module.""" import asyncio from typing import cast from unittest.mock import MagicMock, Mock import pytest from aea.common import Address from aea.configurations.base import ConnectionConfig, PublicId from aea.exceptions import AEAEnforceError from aea.identity.base import Identity from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.prometheus.connection import ( ConnectionStates, PrometheusConnection, ) from packages.fetchai.protocols.prometheus.dialogues import PrometheusDialogue from packages.fetchai.protocols.prometheus.dialogues import ( PrometheusDialogues as BasePrometheusDialogues, ) from packages.fetchai.protocols.prometheus.message import PrometheusMessage class PrometheusDialogues(BasePrometheusDialogues): """The dialogues class keeps track of all prometheus dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return PrometheusDialogue.Role.AGENT BasePrometheusDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class TestPrometheusConnection: """Test the packages/connection/prometheus/connection.py.""" def setup(self): """Initialise the class.""" self.metrics = {} configuration = ConnectionConfig( connection_id=PrometheusConnection.connection_id, port=9090, ) self.some_skill = "some/skill:0.1.0" self.agent_address = "my_address" self.agent_public_key = "my_public_key" self.protocol_specification_id = PublicId.from_str("fetchai/prometheus:1.1.7") identity = Identity( "name", address=self.agent_address, public_key=self.agent_public_key ) self.prometheus_con = PrometheusConnection( identity=identity, configuration=configuration, data_dir=MagicMock() ) self.loop = asyncio.get_event_loop() self.prometheus_address = str(PrometheusConnection.connection_id) self.dialogues = PrometheusDialogues(self.some_skill) async def send_add_metric(self, title: str, metric_type: str) -> None: """Send an add_metric message.""" msg, sending_dialogue = self.dialogues.create( counterparty=self.prometheus_address, performative=PrometheusMessage.Performative.ADD_METRIC, title=title, type=metric_type, description="a gauge", labels={}, ) assert sending_dialogue is not None envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self.prometheus_con.send(envelope) async def send_update_metric(self, title: str, update_func: str) -> None: """Send an update_metric message.""" msg, sending_dialogue = self.dialogues.create( counterparty=self.prometheus_address, performative=PrometheusMessage.Performative.UPDATE_METRIC, title=title, callable=update_func, value=1.0, labels={}, ) assert sending_dialogue is not None assert sending_dialogue.last_message is not None envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self.prometheus_con.send(envelope) def teardown(self): """Clean up after tests.""" self.loop.run_until_complete(self.prometheus_con.disconnect()) @pytest.mark.asyncio async def test_connection(self): """Test connect.""" assert ( self.prometheus_con.state == ConnectionStates.disconnected ), "should not be connected yet" await self.prometheus_con.connect() assert ( self.prometheus_con.state == ConnectionStates.connected ), "should be connected" # test add metric (correct) await self.send_add_metric("some_metric", "Gauge") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 200 assert msg.message == "New Gauge successfully added: some_metric." # test add metric (already exists) await self.send_add_metric("some_metric", "Gauge") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 409 assert msg.message == "Metric already exists." # test add metric (wrong type) await self.send_add_metric("cool_metric", "CoolBar") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 404 assert msg.message == "CoolBar is not a recognized prometheus metric." # test update metric (inc: correct) await self.send_update_metric("some_metric", "inc") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 200 assert msg.message == "Metric some_metric successfully updated." # test update metric (set: correct) await self.send_update_metric("some_metric", "set") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 200 assert msg.message == "Metric some_metric successfully updated." # test update metric (doesn't exist) await self.send_update_metric("cool_metric", "inc") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 404 assert msg.message == "Metric cool_metric not found." # test update metric (bad update function: not found in attr) await self.send_update_metric("some_metric", "go") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 400 assert msg.message == "Update function go not found for metric some_metric." # test update metric (bad update function: found in getattr, not a method) await self.send_update_metric("some_metric", "name") envelope = await self.prometheus_con.receive() msg = cast(PrometheusMessage, envelope.message) assert msg.performative == PrometheusMessage.Performative.RESPONSE assert msg.code == 400 assert ( msg.message == "Failed to update metric some_metric: name is not a valid update function." ) # Test that invalid message is rejected. with pytest.raises(AEAEnforceError): envelope = Envelope( to="some_address", sender="me", message=Mock(spec=Message), ) await self.prometheus_con.channel.send(envelope) # Test that envelope without dialogue produces warning. msg = PrometheusMessage( PrometheusMessage.Performative.RESPONSE, code=0, message="" ) envelope = Envelope( to=self.prometheus_address, sender=self.some_skill, message=msg, ) await self.prometheus_con.channel.send(envelope) # Test that envelope with invalid protocol_specification_id raises error. with pytest.raises(ValueError): msg, _ = self.dialogues.create( counterparty=self.prometheus_address, performative=PrometheusMessage.Performative.UPDATE_METRIC, title="", callable="", value=1.0, labels={}, ) envelope = Envelope( to=self.prometheus_address, sender=self.some_skill, message=msg, ) envelope._protocol_specification_id = "bad_id" await self.prometheus_con.channel.send(envelope) @pytest.mark.asyncio async def test_disconnect(self): """Test disconnect.""" await self.prometheus_con.disconnect() assert ( self.prometheus_con.state == ConnectionStates.disconnected ), "should be disconnected" ================================================ FILE: tests/test_packages/test_connections/test_webhook/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the webhook connection implementation.""" ================================================ FILE: tests/test_packages/test_connections/test_webhook/test_webhook.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for the webhook connection and channel.""" import asyncio import json import logging from traceback import print_exc from typing import cast from unittest.mock import MagicMock, patch import aiohttp import pytest from aiohttp.client_reqrep import ClientResponse from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue from packages.fetchai.connections.webhook.connection import WebhookConnection from packages.fetchai.protocols.http.dialogues import HttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage from tests.common.mocks import RegexComparator from tests.conftest import get_host, get_unused_tcp_port logger = logging.getLogger(__name__) class HttpDialogues(BaseHttpDialogues): """The dialogues class keeps track of all http dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> Dialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return HttpDialogue.Role.CLIENT BaseHttpDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) @pytest.mark.asyncio class TestWebhookConnection: """Tests the webhook connection's 'connect' functionality.""" def setup(self): """Initialise the class.""" self.host = get_host() self.port = get_unused_tcp_port() self.target_skill_id = "some_author/some_skill:0.1.0" self.identity = Identity( "identity", address="some string", public_key="some public_key" ) self.path = "/webhooks/topic/{topic}/" self.loop = asyncio.get_event_loop() configuration = ConnectionConfig( webhook_address=self.host, webhook_port=self.port, webhook_url_path=self.path, target_skill_id=self.target_skill_id, connection_id=WebhookConnection.connection_id, ) self.webhook_connection = WebhookConnection( configuration=configuration, data_dir=MagicMock(), identity=self.identity, ) self.skill_dialogues = HttpDialogues(self.target_skill_id) async def test_initialization(self): """Test the initialisation of the class.""" assert self.webhook_connection.address == self.identity.address @pytest.mark.asyncio async def test_connection(self): """Test the connect functionality of the webhook connection.""" await self.webhook_connection.connect() assert self.webhook_connection.is_connected is True @pytest.mark.asyncio async def test_disconnect(self): """Test the disconnect functionality of the webhook connection.""" await self.webhook_connection.connect() assert self.webhook_connection.is_connected is True await self.webhook_connection.disconnect() assert self.webhook_connection.is_connected is False def teardown(self): """Close connection after testing.""" try: self.loop.run_until_complete(self.webhook_connection.disconnect()) except Exception: print_exc() raise @pytest.mark.asyncio async def test_receive_post_ok(self): """Test the connect functionality of the webhook connection.""" await self.webhook_connection.connect() assert self.webhook_connection.is_connected is True payload = {"hello": "world"} call_task = self.loop.create_task(self.call_webhook("test_topic", json=payload)) envelope = await asyncio.wait_for(self.webhook_connection.receive(), timeout=10) assert envelope message = cast(HttpMessage, envelope.message) dialogue = self.skill_dialogues.update(message) assert dialogue is not None assert message.method.upper() == "POST" assert message.body.decode("utf-8") == json.dumps(payload) await call_task @pytest.mark.asyncio async def test_send(self): """Test the connect functionality of the webhook connection.""" await self.webhook_connection.connect() assert self.webhook_connection.is_connected is True http_message = HttpMessage( dialogue_reference=("", ""), target=0, message_id=1, performative=HttpMessage.Performative.REQUEST, method="get", url="/", headers="", body="", version="", ) envelope = Envelope( to="addr", sender="my_id", message=http_message, ) with patch.object(self.webhook_connection.logger, "warning") as mock_logger: await self.webhook_connection.send(envelope) await asyncio.sleep(0.01) mock_logger.assert_any_call( RegexComparator( "Dropping envelope=.* as sending via the webhook is not possible!" ) ) async def call_webhook(self, topic: str, **kwargs) -> ClientResponse: """ Make a http request to a webhook. :param topic: topic to use :params **kwargs: data or json for payload :return: http response """ path = self.path.format(topic=topic) method = kwargs.get("method", "post") url = f"http://{self.host}:{self.port}{path}" try: async with aiohttp.ClientSession() as session: async with session.request(method, url, **kwargs) as resp: await resp.read() return resp except Exception: print_exc() raise ================================================ FILE: tests/test_packages/test_contracts/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/contracts dir.""" ================================================ FILE: tests/test_packages/test_protocols/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/protocols dir.""" ================================================ FILE: tests/test_packages/test_protocols/test_aggregation.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the aggregation protocol package.""" import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.aggregation.dialogues import ( AggregationDialogue, AggregationDialogues, ) from packages.fetchai.protocols.aggregation.message import AggregationMessage from packages.fetchai.protocols.aggregation.message import ( _default_logger as aggregation_message_logger, ) from tests.conftest import ROOT_DIR sys.path.append(ROOT_DIR) def test_observation_serialization(): """Test the serialization for 'observation' speech-act works.""" msg = AggregationMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=AggregationMessage.Performative.OBSERVATION, value=0, time="some_time", source="some_source", signature="some_signature", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = AggregationMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_aggregation_serialization(): """Test the serialization for 'aggregation' speech-act works.""" msg = AggregationMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=AggregationMessage.Performative.AGGREGATION, value=0, time="some_time", contributors=("address1", "address2"), signature="some_multisignature", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = AggregationMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert ( str(AggregationMessage.Performative.OBSERVATION) == "observation" ), "The str value must be observation" assert ( str(AggregationMessage.Performative.AGGREGATION) == "aggregation" ), "The str value must be aggregation" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = AggregationMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=AggregationMessage.Performative.AGGREGATION, value=0, time="some_time", contributors=("address1", "address2"), signature="some_multisignature", ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( AggregationMessage.Performative, "__eq__", return_value=False ): AggregationMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = AggregationMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=AggregationMessage.Performative.AGGREGATION, value=0, time="some_time", contributors=("address1", "address2"), signature="some_multisignature", ) encoded_msg = AggregationMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( AggregationMessage.Performative, "__eq__", return_value=False ): AggregationMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.aggregation.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the message is incorrect.""" with mock.patch.object(aggregation_message_logger, "error") as mock_logger: AggregationMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=AggregationMessage.Performative.AGGREGATION, ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests aggregation dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.peer_addr = "peer address" cls.agent_dialogues = AgentDialogues(cls.agent_addr) cls.server_dialogues = PeerDialogues(cls.peer_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.peer_addr, dialogue_reference=(str(0), ""), role=AggregationDialogue.Role.AGENT, ) assert isinstance(result, AggregationDialogue) assert result.role == AggregationDialogue.Role.AGENT, "The role must be Agent." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.peer_addr, dialogue_reference=(str(0), ""), role=AggregationDialogue.Role.AGENT, ) assert isinstance(result, AggregationDialogue) assert result.role == AggregationDialogue.Role.AGENT, "The role must be Agent." class AgentDialogue(AggregationDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[AggregationMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ AggregationDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AgentDialogues(AggregationDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return AggregationDialogue.Role.AGENT AggregationDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AgentDialogue, ) class PeerDialogue(AggregationDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[AggregationMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ AggregationDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class PeerDialogues(AggregationDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return AggregationDialogue.Role.AGENT AggregationDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=PeerDialogue, ) ================================================ FILE: tests/test_packages/test_protocols/test_ml_trade.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the ml_trade protocol package.""" import logging import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.ml_trade.dialogues import ( MlTradeDialogue, MlTradeDialogues, ) from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.ml_trade.message import ( _default_logger as ml_trade_message_logger, ) from tests.conftest import ROOT_DIR logger = logging.getLogger(__name__) sys.path.append(ROOT_DIR) def test_cfp_serialization(): """Test the serialization for 'cfp' speech-act works.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_terms_serialization(): """Test the serialization for 'terms' speech-act works.""" msg = MlTradeMessage( message_id=2, target=1, performative=MlTradeMessage.Performative.TERMS, terms=Description({"foo1": 1, "bar1": 2}), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_accept_serialization(): """Test the serialization for 'accept' speech-act works.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.ACCEPT, terms=Description({"foo1": 1, "bar1": 2}), tx_digest="some_tx_digest", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_data_serialization(): """Test the serialization for 'data' speech-act works.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.DATA, terms=Description({"foo1": 1, "bar1": 2}), payload=b"some_payload", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert str(MlTradeMessage.Performative.CFP) == "cfp", "The str value must be cfp" assert ( str(MlTradeMessage.Performative.TERMS) == "terms" ), "The str value must be terms" assert ( str(MlTradeMessage.Performative.ACCEPT) == "accept" ), "The str value must be accept" assert str(MlTradeMessage.Performative.DATA) == "data", "The str value must be data" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( MlTradeMessage.Performative, "__eq__", return_value=False ): MlTradeMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) encoded_msg = MlTradeMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( MlTradeMessage.Performative, "__eq__", return_value=False ): MlTradeMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.ml_trade.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_fipa_incorrect_message(mocked_enforce): """Test that we raise an exception when the fipa message is incorrect.""" with mock.patch.object(ml_trade_message_logger, "error") as mock_logger: MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests ml_trade dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.buyer_addr = "buyer address" cls.seller_addr = "seller address" cls.buyer_dialogues = BuyerDialogues(cls.buyer_addr) cls.seller_dialogues = SellerDialogues(cls.seller_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.buyer_dialogues._create_self_initiated( dialogue_opponent_addr=self.seller_addr, dialogue_reference=(str(0), ""), role=MlTradeDialogue.Role.SELLER, ) assert isinstance(result, MlTradeDialogue) assert result.role == MlTradeDialogue.Role.SELLER, "The role must be seller." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.buyer_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.seller_addr, dialogue_reference=(str(0), ""), role=MlTradeDialogue.Role.BUYER, ) assert isinstance(result, MlTradeDialogue) assert result.role == MlTradeDialogue.Role.BUYER, "The role must be buyer." class BuyerDialogue(MlTradeDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[MlTradeMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ MlTradeDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class BuyerDialogues(MlTradeDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return MlTradeDialogue.Role.BUYER MlTradeDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=BuyerDialogue, ) class SellerDialogue(MlTradeDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[MlTradeMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ MlTradeDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class SellerDialogues(MlTradeDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return MlTradeDialogue.Role.SELLER MlTradeDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=SellerDialogue, ) ================================================ FILE: tests/test_packages/test_protocols/test_prometheus.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the prometheus protocol package.""" import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.prometheus.dialogues import ( PrometheusDialogue, PrometheusDialogues, ) from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.protocols.prometheus.message import ( _default_logger as prometheus_message_logger, ) from tests.conftest import ROOT_DIR sys.path.append(ROOT_DIR) def test_add_metric_serialization(): """Test the serialization for 'add_metric' speech-act works.""" msg = PrometheusMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=PrometheusMessage.Performative.ADD_METRIC, type="some_type", title="some_title", description="some_description", labels={"label_key": "label_value"}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = PrometheusMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_update_metric_serialization(): """Test the serialization for 'update_metric' speech-act works.""" msg = PrometheusMessage( message_id=2, dialogue_reference=(str(0), ""), target=1, performative=PrometheusMessage.Performative.UPDATE_METRIC, title="some_title", callable="some_update_function", value=1.0, labels={"label_key": "label_value"}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = PrometheusMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_response_serialization(): """Test the serialization for 'response' speech-act works.""" msg = PrometheusMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=PrometheusMessage.Performative.RESPONSE, code=0, message="some_message", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = PrometheusMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert ( str(PrometheusMessage.Performative.ADD_METRIC) == "add_metric" ), "The str value must be add_metric" assert ( str(PrometheusMessage.Performative.UPDATE_METRIC) == "update_metric" ), "The str value must be update_metric" assert ( str(PrometheusMessage.Performative.RESPONSE) == "response" ), "The str value must be response" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = PrometheusMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=PrometheusMessage.Performative.RESPONSE, code=0, message="some_message", ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( PrometheusMessage.Performative, "__eq__", return_value=False ): PrometheusMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = PrometheusMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=PrometheusMessage.Performative.RESPONSE, code=0, message="some_message", ) encoded_msg = PrometheusMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( PrometheusMessage.Performative, "__eq__", return_value=False ): PrometheusMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.prometheus.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the message is incorrect.""" with mock.patch.object(prometheus_message_logger, "error") as mock_logger: PrometheusMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=PrometheusMessage.Performative.RESPONSE, code=0, message="some_message", ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests prometheus dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.server_addr = "server address" cls.agent_dialogues = AgentDialogues(cls.agent_addr) cls.server_dialogues = ServerDialogues(cls.server_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.server_addr, dialogue_reference=(str(0), ""), role=PrometheusDialogue.Role.AGENT, ) assert isinstance(result, PrometheusDialogue) assert result.role == PrometheusDialogue.Role.AGENT, "The role must be Agent." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.server_addr, dialogue_reference=(str(0), ""), role=PrometheusDialogue.Role.AGENT, ) assert isinstance(result, PrometheusDialogue) assert result.role == PrometheusDialogue.Role.AGENT, "The role must be Agent." class AgentDialogue(PrometheusDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[PrometheusMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ PrometheusDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AgentDialogues(PrometheusDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return PrometheusDialogue.Role.AGENT PrometheusDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AgentDialogue, ) class ServerDialogue(PrometheusDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[PrometheusMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ PrometheusDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class ServerDialogues(PrometheusDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return PrometheusDialogue.Role.SERVER PrometheusDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=ServerDialogue, ) ================================================ FILE: tests/test_packages/test_skills/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills dir.""" ================================================ FILE: tests/test_packages/test_skills/test_advanced_data_request/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/advanced_data_request dir.""" ================================================ FILE: tests/test_packages/test_skills/test_advanced_data_request/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the advanced data request skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.skills.advanced_data_request.behaviours import ( AdvancedDataRequestBehaviour, ) from packages.fetchai.skills.advanced_data_request.models import ( AdvancedDataRequestModel, ) from tests.conftest import ROOT_DIR class TestSkillBehaviour(BaseSkillTestCase): """Test behaviours of advanced data request.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "advanced_data_request" ) @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.advanced_data_request_behaviour = cast( AdvancedDataRequestBehaviour, cls._skill.skill_context.behaviours.advanced_data_request_behaviour, ) cls.advanced_data_request_model = cast( AdvancedDataRequestModel, cls.advanced_data_request_behaviour.context.advanced_data_request_model, ) cls.advanced_data_request_model.url = "some_url" def test_send_http_request_message(self): """Test the send_http_request_message method of the advanced_data_request behaviour.""" self.advanced_data_request_behaviour.send_http_request_message() self.assert_quantity_in_outbox(1) msg = cast(HttpMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, url="some_url", ) assert has_attributes, error_str def test_add_prometheus_metric(self): """Test the add_prometheus_metric method of the advanced_data_request behaviour.""" self.advanced_data_request_behaviour.add_prometheus_metric( "some_metric", "Gauge", "some_description", {"label_key": "label_value"} ) self.assert_quantity_in_outbox(1) msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.ADD_METRIC, type="Gauge", title="some_metric", description="some_description", labels={"label_key": "label_value"}, ) assert has_attributes, error_str def test_update_prometheus_metric(self): """Test the update_prometheus_metric method of the advanced_data_request behaviour.""" self.advanced_data_request_behaviour.update_prometheus_metric( "some_metric", "set", 0.0, {"label_key": "label_value"} ) self.assert_quantity_in_outbox(1) msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.UPDATE_METRIC, callable="set", title="some_metric", value=0.0, labels={"label_key": "label_value"}, ) assert has_attributes, error_str def test_setup(self): """Test that the setup method puts two messages (prometheus metrics) in the outbox by default.""" self.advanced_data_request_behaviour.setup() self.assert_quantity_in_outbox(2) msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.ADD_METRIC, type="Gauge", title="num_retrievals", ) assert has_attributes, error_str msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.ADD_METRIC, type="Gauge", title="num_requests", ) assert has_attributes, error_str def test_act(self): """Test that the act method of the advanced_data_request behaviour puts one message (http request) in the outbox.""" self.advanced_data_request_behaviour.act() self.assert_quantity_in_outbox(1) msg = cast(HttpMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, url=self.advanced_data_request_model.url, ) assert has_attributes, error_str def test_teardown(self): """Test that the teardown method of the advanced_data_request behaviour leaves no messages in the outbox.""" assert self.advanced_data_request_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_advanced_data_request/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the simple_data_request skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.connections.http_server.connection import ( PUBLIC_ID as HTTP_SERVER_ID, ) from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.skills.advanced_data_request.dialogues import ( HttpDialogues, PrometheusDialogues, ) from packages.fetchai.skills.advanced_data_request.handlers import ( HttpHandler, PrometheusHandler, ) from packages.fetchai.skills.advanced_data_request.models import ( AdvancedDataRequestModel, ) from tests.conftest import ROOT_DIR class TestHttpHandler(BaseSkillTestCase): """Test http handler of advanced_data_request skill.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "advanced_data_request" ) is_agent_to_agent_messages = False @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.http_handler = cast(HttpHandler, cls._skill.skill_context.handlers.http) cls.logger = cls._skill.skill_context.logger cls.advanced_data_request_model = cast( AdvancedDataRequestModel, cls._skill.skill_context.advanced_data_request_model, ) cls.advanced_data_request_model.url = "http://some-url" cls.advanced_data_request_model.outputs = [ {"name": "output1", "json_path": "in1.in2"}, {"name": "output2", "json_path": "id"}, ] cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) cls.list_of_messages = ( DialogueMessage( HttpMessage.Performative.REQUEST, { "method": "get", "url": "some_url", "headers": "", "version": "", "body": b"", }, ), ) def test_setup(self): """Test the setup method of the http handler.""" assert self.http_handler.setup() is None self.assert_quantity_in_outbox(0) def test_setup_with_http_server(self): """Test the setup method of the http handler.""" self.advanced_data_request_model.use_http_server = True assert self.http_handler.setup() is None assert self.http_handler._http_server_id == HTTP_SERVER_ID self.assert_quantity_in_outbox(0) def test_handle_response(self): """Test the _handle_response method of the http handler to a valid response.""" # setup http_dialogue = self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, version="", status_code=200, status_text="", headers="", body=b'{"in1": {"in2": 1.0}, "id": "XXX"}', ) # handle message self.http_handler.handle(incoming_message) # check that data was correctly entered into shared state observation = { "output1": {"value": 100000, "decimals": 5}, "output2": {"value": "XXX"}, } assert ( self.http_handler.context.shared_state["output1"] == observation["output1"] ) assert ( self.http_handler.context.shared_state["output2"] == observation["output2"] ) # check that outbox contains update_prometheus metric message self.assert_quantity_in_outbox(1) def test_handle__handle_unidentified_dialogue(self): """Test handling an unidentified dialogoue""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=HttpMessage, dialogue_reference=incorrect_dialogue_reference, performative=HttpMessage.Performative.RESPONSE, version="", status_code=200, status_text="", headers="", body=b"{}", ) # operation with patch.object(self.http_handler.context.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(0) def test_handle_response_invalid_body(self): """Test the _handle_response method of the http handler to an unexpected response.""" # setup http_dialogue = self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, version="", status_code=200, status_text="", headers="", body=b"{}", ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) assert "output1" not in self.http_handler.context.shared_state # after mock_logger.assert_any_call( logging.WARNING, "No valid output for output1 found in response.", ) def test_handle_response_missing_output(self): """Test the _handle_response method of the http handler to a response with a missing output.""" # setup http_dialogue = self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, version="", status_code=200, status_text="", headers="", body=b'{"in1": {}, "id": "XXX"}', ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) assert self.http_handler.context.shared_state["output2"] == {"value": "XXX"} # after mock_logger.assert_any_call( logging.WARNING, "No valid output for output1 found in response.", ) mock_logger.assert_any_call( logging.INFO, "Observation: {'output2': {'value': 'XXX'}}", ) def test_handle_response_bad_response_code(self): """Test the _handle_response method of the http handler to a response with a code that is not 200.""" # setup http_dialogue = self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, version="", status_code=999, status_text="", headers="", body=b'{"in1": {"in2": 1.0}, "id": "XXX"}', ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) assert "output1" not in self.http_handler.context.shared_state # after mock_logger.assert_any_call( logging.INFO, "got unexpected http message: code = 999", ) def test_handle_request_get(self): """Test the _handle_request method of the http handler with 'get' method.""" incoming_message = self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method="get", url="some_url", headers="", version="", body=b"", ) self.http_handler._http_server_id = "some_id" # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received http request with method=get, url=some_url and body=b''", ) # check that outbox contains the http response prometheus metric update messages self.assert_quantity_in_outbox(2) def test_handle_request_post(self): """Test the _handle_request method of the http handler with 'post' method.""" incoming_message = self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method="post", url="some_url", headers="", version="", body=b"", ) self.http_handler._http_server_id = "some_id" # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) mock_logger.assert_any_call( logging.INFO, "method 'post' is not supported.", ) # check that outbox is empty self.assert_quantity_in_outbox(0) def test_handle_request_no_http_server(self): """Test the _handle_request method of the http handler when http server is disabled.""" incoming_message = self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method="get", url="some_url", headers="", version="", body=b"", ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received http request with method=get, url=some_url and body=b''", ) mock_logger.assert_any_call( logging.INFO, "http server is not enabled.", ) # check that outbox is empty self.assert_quantity_in_outbox(0) def test_teardown(self): """Test the teardown method of the http handler.""" assert self.http_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestPrometheusHandler(BaseSkillTestCase): """Test prometheus handler of advanced_data_request skill.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "advanced_data_request" ) is_agent_to_agent_messages = False @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.prometheus_handler = cast( PrometheusHandler, cls._skill.skill_context.handlers.prometheus ) cls.logger = cls._skill.skill_context.logger cls.advanced_data_request_model = cast( AdvancedDataRequestModel, cls._skill.skill_context.advanced_data_request_model, ) cls.prometheus_dialogues = cast( PrometheusDialogues, cls._skill.skill_context.prometheus_dialogues ) cls.list_of_messages = ( DialogueMessage( PrometheusMessage.Performative.ADD_METRIC, { "type": "Gauge", "title": "some_title", "description": "some_description", "labels": {}, }, ), ) def test_setup(self): """Test the setup method of the prometheus handler.""" assert self.prometheus_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_response(self): """Test the _handle_response method of the prometheus handler to a valid response.""" # setup prometheus_dialogue = self.prepare_skill_dialogue( dialogues=self.prometheus_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=prometheus_dialogue, performative=PrometheusMessage.Performative.RESPONSE, code=200, message="some_message", ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.prometheus_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.DEBUG, "Prometheus response (200): some_message" ) def test_handle_unidentified_dialogue(self): """Test the _handle_response method of the prometheus handler to an unidentified dialogue.""" # setup incoming_message = self.build_incoming_message( message_type=PrometheusMessage, performative=PrometheusMessage.Performative.RESPONSE, code=200, message="some_message", ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.prometheus_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid message={incoming_message}, unidentified dialogue.", ) def test_teardown(self): """Test the teardown method of the prometheus handler.""" assert self.prometheus_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_aries_alice/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/aries_alice dir.""" ================================================ FILE: tests/test_packages/test_skills/test_aries_alice/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module sets up test environment for aries_alice skill.""" import json from pathlib import Path from typing import cast from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Location, Query, ) from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_alice.behaviours import AliceBehaviour from packages.fetchai.skills.aries_alice.dialogues import ( DefaultDialogues, HttpDialogues, OefSearchDialogues, ) from packages.fetchai.skills.aries_alice.handlers import ( DefaultHandler, HttpHandler, OefSearchHandler, ) from packages.fetchai.skills.aries_alice.strategy import Strategy from tests.conftest import ROOT_DIR class AriesAliceTestCase(BaseSkillTestCase): """Sets the aries_alice class up for testing.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "aries_alice") @classmethod def setup(cls): """Setup the test class.""" cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.service_data = {"key": "seller_service", "value": "some_value"} cls.personality_data = {"piece": "genus", "value": "some_personality"} cls.classification = {"piece": "classification", "value": "some_classification"} cls.admin_host = "127.0.0.1" cls.admin_port = 8067 config_overrides = { "models": { "strategy": { "args": { "location": cls.location, "service_data": cls.service_data, "personality_data": cls.personality_data, "classification": cls.classification, "admin_host": cls.admin_host, "admin_port": cls.admin_port, } } }, } super().setup(config_overrides=config_overrides) # behaviours cls.alice_behaviour = cast( AliceBehaviour, cls._skill.skill_context.behaviours.alice, ) # dialogues cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) # handlers cls.default_handler = cast( DefaultHandler, cls._skill.skill_context.handlers.default ) cls.http_handler = cast(HttpHandler, cls._skill.skill_context.handlers.http) cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) # models cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger # mocked objects cls.mocked_method = "SOME_METHOD" cls.mocked_url = "www.some-url.com" cls.mocked_version = "some_version" cls.mocked_headers = "some_headers" cls.body_dict = {"some_key": "some_value"} cls.body_str = "some_body" cls.mocked_body_bytes = json.dumps(cls.body_str).encode("utf-8") cls.mocked_query = Query( [Constraint("some_attribute_name", ConstraintType("==", "some_value"))], DataModel( "some_data_model_name", [ Attribute( "some_attribute_name", str, False, "Some attribute descriptions.", ) ], ), ) cls.mocked_proposal = Description( { "contract_address": "some_contract_address", "token_id": "123456", "trade_nonce": "876438756348568", "from_supply": "543", "to_supply": "432", "value": "67", } ) cls.mocked_registration_description = Description({"foo1": 1, "bar1": 2}) cls.registration_message = OefSearchMessage( dialogue_reference=("", ""), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=cls.mocked_registration_description, ) cls.registration_message.sender = str(cls._skill.skill_context.skill_id) cls.registration_message.to = cls._skill.skill_context.search_service_address # list of messages cls.list_of_http_messages = ( DialogueMessage( HttpMessage.Performative.REQUEST, { "method": cls.mocked_method, "url": cls.mocked_url, "headers": cls.mocked_headers, "version": cls.mocked_version, "body": cls.mocked_body_bytes, }, is_incoming=False, ), ) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) ================================================ FILE: tests/test_packages/test_skills/test_aries_alice/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the aries_alice skill.""" import json import logging from unittest.mock import patch from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_alice.behaviours import HTTP_CLIENT_PUBLIC_ID from tests.test_packages.test_skills.test_aries_alice.intermediate_class import ( AriesAliceTestCase, ) class TestAliceBehaviour(AriesAliceTestCase): """Test alice behaviour of aries_alice.""" def test_init(self): """Test the __init__ method of the alice behaviour.""" assert self.alice_behaviour.failed_registration_msg is None assert self.alice_behaviour._nb_retries == 0 def test_send_http_request_message(self): """Test the send_http_request_message method of the alice behaviour.""" # operation self.alice_behaviour.send_http_request_message( self.mocked_method, self.mocked_url, self.body_dict ) # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, to=str(HTTP_CLIENT_PUBLIC_ID), sender=str(self.skill.skill_context.skill_id), method=self.mocked_method, url=self.mocked_url, headers="Content-Type: application/json", version="", body=json.dumps(self.body_dict).encode("utf-8"), ) assert has_attributes, error_str def test_perform_agents_search(self) -> None: """Perform agents search to query proofs from.""" self.strategy.is_searching = True # type: ignore with patch.object(self.alice_behaviour.context.logger, "log") as mock_logger: # type: ignore self.alice_behaviour.perform_agents_search() # type: ignore mock_logger.assert_any_call(logging.INFO, "Searching for agents on SOEF...") def test_setup(self): """Test the setup method of the alice behaviour.""" # operation with patch.object( self.strategy, "get_location_description", return_value=self.mocked_registration_description, ) as mock_desc: with patch.object(self.logger, "log") as mock_logger: self.alice_behaviour.setup() # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"My address is: {self.skill.skill_context.agent_address}" ) mock_desc.assert_called_once() # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") def test_act_i(self): """Test the act method of the alice behaviour where failed_registration_msg is NOT None.""" # setup self.alice_behaviour.failed_registration_msg = self.registration_message with patch.object(self.logger, "log") as mock_logger: self.alice_behaviour.act() # after self.assert_quantity_in_outbox(1) # _retry_failed_registration has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=type(self.registration_message), performative=self.registration_message.performative, to=self.registration_message.to, sender=str(self.skill.skill_context.skill_id), service_description=self.registration_message.service_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Retrying registration on SOEF. Retry {self.alice_behaviour._nb_retries} out of {self.alice_behaviour._max_soef_registration_retries}.", ) assert self.alice_behaviour.failed_registration_msg is None def test_act_ii(self): """Test the act method of the alice behaviour where failed_registration_msg is NOT None and max retries is reached.""" # setup self.alice_behaviour.failed_registration_msg = self.registration_message self.alice_behaviour._max_soef_registration_retries = 2 self.alice_behaviour._nb_retries = 2 self.alice_behaviour.act() # after self.assert_quantity_in_outbox(0) assert self.skill.skill_context.is_active is False def test_register_service(self): """Test the register_service method of the alice behaviour.""" # operation with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.alice_behaviour.register_service() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's service on the SOEF." ) def test_register_genus(self): """Test the register_genus method of the alice behaviour.""" # operation with patch.object( self.strategy, "get_register_personality_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.alice_behaviour.register_genus() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality genus on the SOEF." ) def test_register_classification(self): """Test the register_classification method of the alice behaviour.""" # operation with patch.object( self.strategy, "get_register_classification_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.alice_behaviour.register_classification() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality classification on the SOEF." ) def test_teardown(self): """Test the teardown method of the alice behaviour.""" # operation with patch.object( self.strategy, "get_unregister_service_description", return_value=self.mocked_registration_description, ): with patch.object( self.strategy, "get_location_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.alice_behaviour.teardown() # after self.assert_quantity_in_outbox(2) # _unregister_service has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering service from SOEF.") # _unregister_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering agent from SOEF.") ================================================ FILE: tests/test_packages/test_skills/test_aries_alice/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the aries_alice skill.""" from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_alice.dialogues import ( DefaultDialogue, HttpDialogue, OefSearchDialogue, ) from tests.test_packages.test_skills.test_aries_alice.intermediate_class import ( AriesAliceTestCase, ) class TestDialogues(AriesAliceTestCase): """Test dialogue classes of aries_alice.""" def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_http_dialogues(self): """Test the HttpDialogues class.""" _, dialogue = self.http_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, version=self.mocked_version, headers=self.mocked_headers, body=self.mocked_body_bytes, ) assert dialogue.role == HttpDialogue.Role.CLIENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.mocked_query, ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_aries_alice/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the aries_alice skill.""" import json import logging from typing import cast from unittest.mock import ANY, patch from aea.protocols.dialogue.base import Dialogues from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_alice.dialogues import ( HttpDialogue, OefSearchDialogue, ) from packages.fetchai.skills.aries_alice.handlers import ADMIN_COMMAND_RECEIVE_INVITE from tests.test_packages.test_skills.test_aries_alice.intermediate_class import ( AriesAliceTestCase, ) class TestDefaultHandler(AriesAliceTestCase): """Test default handler of aries_alice.""" def test_setup(self): """Test the setup method of the default handler.""" assert self.default_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_i(self): """Test the handle method of the default handler where @type is in content.""" # setup content = {"@type": "some", "@id": "conn_id"} content_bytes = json.dumps(content).encode("utf-8") incoming_message = cast( DefaultMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), content=content_bytes, ), ) # operation with patch.object( self.alice_behaviour, "send_http_request_message" ) as mock_send: with patch.object(self.logger, "log") as mock_logger: self.default_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"Received message content:{content}", ) mock_send.assert_any_call( method="POST", url=self.strategy.admin_url + ADMIN_COMMAND_RECEIVE_INVITE + "?auto_accept=true", content=content, ) def test_handle_ii(self): """Test the handle method of the default handler where http_dialogue is None.""" # setup incoming_message = cast( DefaultMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, dialogue_reference=("", ""), error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="some_error_msg", error_data={"some_key": b"some_bytes"}, ), ) # operation with patch.object( self.alice_behaviour, "send_http_request_message" ) as mock_send: with patch.object(self.logger, "log") as mock_logger: self.default_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, "alice -> default_handler -> handle(): something went wrong when adding the incoming default message to the dialogue.", ) mock_send.assert_not_called() def test_teardown(self): """Test the teardown method of the default handler.""" assert self.default_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestHttpHandler(AriesAliceTestCase): """Test http handler of aries_alice.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the http_handler handler.""" assert self.http_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the handle method of the http handler where incoming message is invalid.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, dialogue_reference=incorrect_dialogue_reference, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, headers=self.mocked_headers, version=self.mocked_version, body=self.mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, "alice -> http_handler -> handle() -> REQUEST: something went wrong when adding the incoming HTTP webhook request message to the dialogue.", ) def test_handle_request_connection_invitation(self): """Test the handle method of the http handler where performative is REQUEST.""" # setup invitation_msg_id = "some" connection_id = "someconid" addr = "some addr" self.http_handler.connected = {} self.strategy.aea_addresses = [addr] self.strategy.invitations = {invitation_msg_id: addr} name = "bob" body = { "invitation_msg_id": invitation_msg_id, "connection_id": connection_id, "state": "active", "their_label": name, } mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger, patch.object( self.alice_behaviour, "send_http_request_message" ) as send_http_request_message_mock: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"Connected to {name}") assert connection_id in self.http_handler.connected send_http_request_message_mock.assert_any_call( method="POST", url=self.strategy.admin_url + "/present-proof/send-request", content=ANY, ) def test_handle_request_credentials_proof_request(self): """Test the handle method of the http handler where performative is REQUEST.""" # setup self.http_handler.presentation_requests = [] presentation_exchange_id = "some" body = { "role": "prover", "presentation_request_dict": {}, "state": "request_received", "presentation_exchange_id": presentation_exchange_id, } mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger, patch.object( self.alice_behaviour, "send_http_request_message" ) as send_http_request_message_mock: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "Got credentials proof request") send_http_request_message_mock.assert_any_call( method="GET", url=self.strategy.admin_url + f"/present-proof/records/{presentation_exchange_id}/credentials", ) def test_handle_request_get_credentials_proof(self): """Test the handle method of the http handler where performative is REQUEST.""" # setup self.http_handler.presentation_requests = [] connection_id = "some" addr = "some_addr" name = "bob" self.http_handler.addr_names = {addr: name} self.http_handler.connected = {connection_id: addr} self.strategy.aea_addresses = [addr] presentation_exchange_id = "some" body = { "role": "verifier", "presentation_request_dict": {}, "state": "presentation_received", "presentation_exchange_id": presentation_exchange_id, "connection_id": connection_id, } mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"Got credentials proof from {name}") def test_handle_request_get_credentials_issued(self): """Test the handle method of the http handler where performative is REQUEST.""" # setup connection_id = "some" self.strategy.is_searching = False cred_def_id = "some_cred_def_id" body = { "credential_proposal_dict": {"credential_proposal": ""}, "state": "credential_acked", "raw_credential": {"cred_def_id": cred_def_id}, "connection_id": connection_id, } mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger, patch.object( self.alice_behaviour, "perform_agents_search" ) as mock_perform_agents_search: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"Got crendetials from faber: schema:{cred_def_id} {body['credential_proposal_dict']['credential_proposal']}", ) assert self.strategy.is_searching is True assert self.http_handler.cred_def_id == cred_def_id mock_perform_agents_search.assert_called_once() def test_handle_response_i(self): """Test the handle method of the http handler where performative is RESPONSE and content has Error.""" # setup http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"Error": "something"} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, "Something went wrong after I sent the administrative command of 'invitation receive'", ) def test_handle_response_ii(self): """Test the handle method of the http handler where performative is RESPONSE and content does NOT have Error.""" # setup addr = "some addr" self.strategy.aea_addresses = [addr] connection_id = 2342 invitation = {"some_key": "some_value"} http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"connection_id": connection_id, "invitation": invitation} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"Received http response message content:{body}" ) assert connection_id in self.http_handler.connections_sent mock_logger.assert_any_call( logging.INFO, f"Sent invitation to {addr}. Waiting for the invitation from agent some addr to finalise the connection...", ) def test_teardown(self): """Test the teardown method of the http handler.""" assert self.http_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(AriesAliceTestCase): """Test oef_search handler of aries_alice.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_location[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_search_dialogue}.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.alice_behaviour, "register_service", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH set_service_key data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_service[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.alice_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.alice_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iv(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, and its service are successfully registered on the SOEF.", ) def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef success targets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_proposal, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={incoming_message.performative} in dialogue={self.oef_search_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_aries_alice/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the aries_alice skill.""" from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import Description, Location from tests.test_packages.test_skills.test_aries_alice.intermediate_class import ( AriesAliceTestCase, ) class TestStrategy(AriesAliceTestCase): """Test Strategy of aries_alice.""" def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.admin_host == self.admin_host assert self.strategy.admin_port == self.admin_port assert self.strategy.admin_url == f"http://{self.admin_host}:{self.admin_port}" def test_get_location_description(self): """Test the get_location_description method of the Strategy class.""" description = self.strategy.get_location_description() assert type(description) == Description assert description.data_model is AGENT_LOCATION_MODEL assert description.values.get("location", "") == Location( latitude=self.location["latitude"], longitude=self.location["longitude"] ) def test_get_register_service_description(self): """Test the get_register_service_description method of the Strategy class.""" description = self.strategy.get_register_service_description() assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert description.values.get("key", "") == self.service_data["key"] assert description.values.get("value", "") == self.service_data["value"] def test_get_register_personality_description(self): """Test the get_register_personality_description method of the Strategy class.""" description = self.strategy.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == self.personality_data["piece"] assert description.values.get("value", "") == self.personality_data["value"] def test_get_register_classification_description(self): """Test the get_register_classification_description method of the Strategy class.""" description = self.strategy.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == self.classification["piece"] assert description.values.get("value", "") == self.classification["value"] def test_get_unregister_service_description(self): """Test the get_unregister_service_description method of the Strategy class.""" description = self.strategy.get_unregister_service_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == self.service_data["key"] ================================================ FILE: tests/test_packages/test_skills/test_aries_faber/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/aries_faber dir.""" ================================================ FILE: tests/test_packages/test_skills/test_aries_faber/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module sets up test environment for aries_faber skill.""" import json from pathlib import Path from typing import cast from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Query, ) from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_faber.behaviours import FaberBehaviour from packages.fetchai.skills.aries_faber.dialogues import ( DefaultDialogues, HttpDialogues, OefSearchDialogues, ) from packages.fetchai.skills.aries_faber.handlers import HttpHandler, OefSearchHandler from packages.fetchai.skills.aries_faber.strategy import Strategy from tests.conftest import ROOT_DIR class AriesFaberTestCase(BaseSkillTestCase): """Sets the aries_faber class up for testing.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "aries_faber") @classmethod def setup(cls): """Setup the test class.""" cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.search_query = { "search_key": "intro_service", "search_value": "intro_alice", "constraint_type": "==", } cls.search_radius = 5.0 cls.admin_host = "127.0.0.1" cls.admin_port = 8021 cls.ledger_url = "http://127.0.0.1:9000" config_overrides = { "models": { "strategy": { "args": { "location": cls.location, "search_query": cls.search_query, "search_radius": cls.search_radius, "admin_host": cls.admin_host, "admin_port": cls.admin_port, "ledger_url": cls.ledger_url, } } }, } super().setup(config_overrides=config_overrides) # behaviours cls.faber_behaviour = cast( FaberBehaviour, cls._skill.skill_context.behaviours.faber, ) # dialogues cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) # handlers cls.http_handler = cast(HttpHandler, cls._skill.skill_context.handlers.http) cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) # models cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger # mocked objects cls.mocked_method = "SOME_METHOD" cls.mocked_url = "www.some-url.com" cls.mocked_version = "some_version" cls.mocked_headers = "some_headers" cls.body_dict = {"some_key": "some_value"} cls.body_str = "some_body" cls.body_bytes = b"some_body" cls.mocked_body_bytes = json.dumps(cls.body_str).encode("utf-8") cls.mocked_query = Query( [Constraint("some_attribute_name", ConstraintType("==", "some_value"))], DataModel( "some_data_model_name", [ Attribute( "some_attribute_name", str, False, "Some attribute descriptions.", ) ], ), ) cls.mocked_proposal = Description( { "contract_address": "some_contract_address", "token_id": "123456", "trade_nonce": "876438756348568", "from_supply": "543", "to_supply": "432", "value": "67", } ) # list of messages cls.list_of_http_messages = ( DialogueMessage( HttpMessage.Performative.REQUEST, { "method": cls.mocked_method, "url": cls.mocked_url, "headers": cls.mocked_headers, "version": cls.mocked_version, "body": cls.mocked_body_bytes, }, is_incoming=False, ), ) cls.list_of_oef_search_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": cls.mocked_query}, ), ) ================================================ FILE: tests/test_packages/test_skills/test_aries_faber/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the aries_faber skill.""" import json import logging from unittest.mock import patch from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_faber.behaviours import HTTP_CLIENT_PUBLIC_ID from tests.test_packages.test_skills.test_aries_faber.intermediate_class import ( AriesFaberTestCase, ) class TestFaberBehaviour(AriesFaberTestCase): """Test registration behaviour of aries_faber.""" def test_send_http_request_message(self): """Test the send_http_request_message method of the faber behaviour.""" # operation self.faber_behaviour.send_http_request_message( self.mocked_method, self.mocked_url, self.body_dict ) # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, to=str(HTTP_CLIENT_PUBLIC_ID), sender=str(self.skill.skill_context.skill_id), method=self.mocked_method, url=self.mocked_url, headers="Content-Type: application/json", version="", body=json.dumps(self.body_dict).encode("utf-8"), ) assert has_attributes, error_str def test_setup(self): """Test the setup method of the faber behaviour.""" # operation self.faber_behaviour.setup() # after self.assert_quantity_in_outbox(0) assert self.strategy.is_searching is True def test_act_is_searching(self): """Test the act method of the faber behaviour where is_searching is True.""" # setup self.strategy._is_searching = True # operation with patch.object( self.strategy, "get_location_and_service_query", return_value=self.mocked_query, ): with patch.object(self.logger, "log") as mock_logger: self.faber_behaviour.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=str(self.skill.public_id), query=self.mocked_query, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "Searching for Alice on SOEF...", ) def test_teardown(self): """Test the teardown method of the service_registration behaviour.""" self.faber_behaviour.teardown() self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_aries_faber/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the aries_faber skill.""" from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_faber.dialogues import ( DefaultDialogue, HttpDialogue, OefSearchDialogue, ) from tests.test_packages.test_skills.test_aries_faber.intermediate_class import ( AriesFaberTestCase, ) class TestDialogues(AriesFaberTestCase): """Test dialogue classes of aries_faber.""" def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=self.body_bytes, ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_http_dialogues(self): """Test the HttpDialogues class.""" _, dialogue = self.http_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, version=self.mocked_version, headers=self.mocked_headers, body=self.mocked_body_bytes, ) assert dialogue.role == HttpDialogue.Role.CLIENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.mocked_query, ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_aries_faber/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the aries_faber skill.""" import json import logging from typing import cast from unittest.mock import ANY, patch import pytest from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.aries_faber.dialogues import ( HttpDialogue, OefSearchDialogue, ) from packages.fetchai.skills.aries_faber.handlers import SUPPORT_REVOCATION from packages.fetchai.skills.aries_faber.strategy import ( ADMIN_COMMAND_CREATE_INVITATION, ADMIN_COMMAND_CREDDEF, ADMIN_COMMAND_REGISTGER_PUBLIC_DID, ADMIN_COMMAND_SCEHMAS, ADMIN_COMMAND_STATUS, FABER_ACA_IDENTITY, LEDGER_COMMAND_REGISTER_DID, ) from tests.test_packages.test_skills.test_aries_faber.intermediate_class import ( AriesFaberTestCase, ) class TestHttpHandler(AriesFaberTestCase): """Test http handler of aries_faber.""" is_agent_to_agent_messages = False def test__init__i(self): """Test the __init__ method of the http_request behaviour.""" assert self.http_handler.faber_identity == FABER_ACA_IDENTITY assert self.http_handler.did is None assert self.http_handler._schema_id is None assert self.http_handler.credential_definition_id is None assert self.http_handler.connections_sent == {} assert self.http_handler.connections_set == {} assert self.http_handler.counterparts_names == {} def test_setup(self): """Test the setup method of the http_handler handler.""" assert self.http_handler.setup() is None self.assert_quantity_in_outbox(0) def test_properties(self): """Test the properties of the http_handler handler.""" self.http_handler._schema_id = None with pytest.raises(ValueError, match="schema_id not set"): assert self.http_handler.schema_id is None self.http_handler._schema_id = "some_schema_id" assert self.http_handler.schema_id == "some_schema_id" def test_handle_unidentified_dialogue(self): """Test the handle method of the http handler where incoming message is invalid.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, dialogue_reference=incorrect_dialogue_reference, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, headers=self.mocked_headers, version=self.mocked_version, body=self.mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, "something went wrong when adding the incoming HTTP message to the dialogue.", ) def test_handle_request(self): """Test the handle method of the http handler where performative is REQUEST.""" # setup con_id = 123 agent_addr = "agent_addr" agent_name = "alice" self.http_handler.connections_sent = {con_id: agent_addr} self.http_handler.is_connected_to_Faber = False body = {"connection_id": con_id, "state": "active", "their_label": agent_name} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method=self.mocked_method, url=self.mocked_url, headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger, patch.object( self.faber_behaviour, "send_http_request_message" ) as mock_http_req: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"Received webhook message content:{str(body)}" ) mock_logger.assert_any_call( logging.INFO, f"Connected to {agent_name}({agent_addr})" ) mock_http_req.assert_any_call( method="POST", url=self.strategy.admin_url + "/issue-credential/send", content=ANY, ) assert self.http_handler.counterparts_names[agent_addr] == agent_name assert self.http_handler.connections_set[con_id] == agent_addr def test_handle_response_1(self): """Test the handle method of the http handler where performative is RESPONSE and content has version.""" # setup data = { "alias": self.http_handler.faber_identity, "seed": self.strategy.seed, "role": "TRUST_ANCHOR", } http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"version": "some_version"} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object( self.faber_behaviour, "send_http_request_message" ) as mock_http_req: with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"Received message: {str(body)}") mock_logger.assert_any_call( logging.INFO, f"Registering Faber_ACA with seed {str(self.strategy.seed)}", ) mock_http_req.assert_any_call( method="POST", url=self.strategy.ledger_url + LEDGER_COMMAND_REGISTER_DID, content=data, ) def test_handle_response_2(self): """Test the handle method of the http handler where performative is RESPONSE and content has did.""" # setup did = "some_did" http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"did": did} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object( self.faber_behaviour, "send_http_request_message" ) as mock_http_req: with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"Received message: {str(body)}") mock_logger.assert_any_call(logging.INFO, f"Received DID: {did}") mock_http_req.assert_any_call( method="POST", url=self.strategy.admin_url + ADMIN_COMMAND_REGISTGER_PUBLIC_DID + f"?did={did}", content="", ) assert self.http_handler.did == did def test_handle_response_3(self): """Test the handle method of the http handler where performative is RESPONSE and content has did.""" # setup schema_body = { "schema_name": "degree schema", "schema_version": "0.0.1", "attributes": ["average", "date", "degree", "name"], } http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"result": {"posture": 123}} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object( self.faber_behaviour, "send_http_request_message" ) as mock_http_req: with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"Received message: {str(body)}") mock_logger.assert_any_call( logging.INFO, f"Registering schema {str(schema_body)}" ) mock_http_req.assert_any_call( method="POST", url=self.strategy.admin_url + ADMIN_COMMAND_SCEHMAS, content=schema_body, ) def test_handle_response_4(self): """Test the handle method of the http handler where performative is RESPONSE and content has schema_id.""" # setup schema_id = "some_schema_id" credential_definition_body = { "schema_id": schema_id, "support_revocation": SUPPORT_REVOCATION, } http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"schema_id": schema_id} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object( self.faber_behaviour, "send_http_request_message" ) as mock_http_req: with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"Received message: {str(body)}") assert self.http_handler.schema_id == schema_id mock_http_req.assert_any_call( method="POST", url=self.strategy.admin_url + ADMIN_COMMAND_CREDDEF, content=credential_definition_body, ) def test_handle_response_5(self): """Test the handle method of the http handler where performative is RESPONSE and content has credential_definition_id.""" # setup credential_definition_id = "some_credential_definition_id" self.strategy.aea_addresses = ["some"] http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"credential_definition_id": credential_definition_id} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object( self.faber_behaviour, "send_http_request_message" ) as mock_http_req: with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"Received message: {str(body)}") assert self.http_handler.credential_definition_id == credential_definition_id mock_http_req.assert_any_call( method="POST", url=self.strategy.admin_url + ADMIN_COMMAND_CREATE_INVITATION ) def test_handle_response_6(self): """Test the handle method of the http handler where performative is RESPONSE and content has connection_id.""" # setup connection_id = 2342 addr = "someaddr" self.strategy.aea_addresses = [addr] invitation = {"some_key": "some_value"} http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = {"connection_id": connection_id, "invitation": invitation} mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call(logging.INFO, f"Received message: {str(body)}") assert connection_id in self.http_handler.connections_sent mock_logger.assert_any_call(logging.INFO, f"connection: {str(body)}") mock_logger.assert_any_call(logging.INFO, f"connection id: {connection_id}") mock_logger.assert_any_call(logging.INFO, f"invitation: {str(invitation)}") mock_logger.assert_any_call( logging.INFO, f"Sent invitation to {addr}. Waiting for the invitation from agent someaddr to finalise the connection...", ) # _send_default_message message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, to=addr, sender=self.skill.skill_context.agent_address, content=json.dumps(invitation).encode("utf-8"), ) assert has_attributes, error_str def test_handle_response_7(self): """Test the handle method of the http handler where performative is RESPONSE and credentials issued.""" # setup connection_id = 2342 addr = "someaddr" name = "bob" self.strategy.aea_addresses = [addr] self.http_handler.connections_set[connection_id] = addr self.http_handler.counterparts_names[addr] = name http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) body = { "credential_proposal_dict": {}, "connection_id": connection_id, "credential_offer_dict": { "credential_preview": { "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/1.0/credential-preview", "attributes": [ {"name": "name", "value": "bob"}, {"name": "date", "value": "2022-01-01"}, {"name": "degree", "value": "History"}, {"name": "average", "value": "4"}, ], } }, } mocked_body_bytes = json.dumps(body).encode("utf-8") incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, status_code=200, status_text="some_status_code", headers=self.mocked_headers, version=self.mocked_version, body=mocked_body_bytes, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) mock_logger.assert_any_call(logging.INFO, f"Received message: {str(body)}") mock_logger.assert_any_call( logging.INFO, f"Credential issued for {name}({addr}): {body['credential_offer_dict']['credential_preview']}", ) def test_teardown(self): """Test the teardown method of the http handler.""" assert self.http_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(AriesFaberTestCase): """Test oef_search handler of aries_faber.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_oef_search_messages[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_search_dialogue}.", ) def test_handle_search_i(self): """Test the _handle_search method of the oef_search handler where the number of agents found is NOT 0.""" # setup alice_address = "alice" agents = (alice_address, "bob") oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_oef_search_messages[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( { "agent_1": {"key_1": "value_1", "key_2": "value_2"}, "agent_2": {"key_3": "value_3", "key_4": "value_4"}, } ), ), ) # operation with patch.object( self.faber_behaviour, "send_http_request_message" ) as mock_http_req: with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found agents {', '.join(agents)}, stopping search.", ) assert self.strategy.is_searching is False assert self.strategy.aea_addresses == list(agents) mock_http_req.assert_any_call( "GET", self.strategy.admin_url + ADMIN_COMMAND_STATUS ) def test_handle_search_ii(self): """Test the _handle_search method of the oef_search handler where the number of agents found is 0.""" # setup agents = tuple() oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_oef_search_messages[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo({}), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "Waiting for more agents.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_proposal, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={incoming_message.performative} in dialogue={self.oef_search_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_aries_faber/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the aries_faber skill.""" import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.models import Constraint, ConstraintType, Query from tests.test_packages.test_skills.test_aries_faber.intermediate_class import ( AriesFaberTestCase, ) class TestStrategy(AriesFaberTestCase): """Test Strategy of aries_faber.""" def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.admin_host == self.admin_host assert self.strategy.admin_port == self.admin_port assert self.strategy.ledger_url == self.ledger_url assert self.strategy.admin_url == f"http://{self.admin_host}:{self.admin_port}" assert self.strategy.is_searching is False assert self.strategy.aea_addresses == [] self.strategy.aea_addresses = ["some"] assert self.strategy.aea_addresses == ["some"] with pytest.raises(AEAEnforceError, match="Can only set bool on is_searching!"): self.strategy.is_searching = "some_value" self.strategy.is_searching = True assert self.strategy.is_searching is True def test_get_location_and_service_query(self): """Test the get_location_and_service_query method of the Strategy class.""" query = self.strategy.get_location_and_service_query() assert type(query) == Query assert len(query.constraints) == 2 assert query.model is None location_constraint = Constraint( "location", ConstraintType( "distance", (self.strategy._agent_location, self.search_radius) ), ) assert query.constraints[0] == location_constraint service_key_constraint = Constraint( self.search_query["search_key"], ConstraintType( self.search_query["constraint_type"], self.search_query["search_value"], ), ) assert query.constraints[1] == service_key_constraint ================================================ FILE: tests/test_packages/test_skills/test_carpark_detection/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/carpark_detection dir.""" ================================================ FILE: tests/test_packages/test_skills/test_carpark_detection/test_database.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the RegistrationDB class of the carpark detection skill.""" import os import sqlite3 from pathlib import Path from unittest.mock import Mock, patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.carpark_detection.database import DetectionDatabase from tests.conftest import ROOT_DIR class TestDetectionDatabase(BaseSkillTestCase): """Test DetectionDatabase of carpark detection.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "carpark_detection") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.temp_dir = os.path.join(os.path.dirname(__file__), "temp_files_placeholder") cls.create_if_not_present = True cls.logger = None cls.db = DetectionDatabase( temp_dir=cls.temp_dir, create_if_not_present=cls.create_if_not_present, logger=cls.logger, ) def test__init__i(self): """Test the __init__ method of the DetectionDatabase class.""" # operation with patch.object(self.db, "initialise_backend") as mock_initialise_back: self.db.__init__(self.temp_dir, self.create_if_not_present, self.logger) mock_initialise_back.assert_called_once() def test_is_db_exits_i(self): """Test the is_db_exits method of the DetectionDatabase class where db exists.""" # operation with patch("os.path.isfile", return_value=True) as mock_is_file: with patch.object( self.db, "get_system_status", return_value="Exists" ) as mock_status: exists = self.db.is_db_exits() # after mock_is_file.assert_any_call(self.db.database_path) mock_status.assert_any_call("db", False) assert exists is True def test_is_db_exits_ii(self): """Test the is_db_exits method of the DetectionDatabase class where db file does NOT exist.""" # operation with patch("os.path.isfile", return_value=False) as mock_is_file: exists = self.db.is_db_exits() # after mock_is_file.assert_any_call(self.db.database_path) assert exists is False def test_is_db_exits_iii(self): """Test the is_db_exits method of the DetectionDatabase class where db status does NOT contain Exists.""" # operation with patch("os.path.isfile", return_value=True) as mock_is_file: with patch.object( self.db, "get_system_status", return_value="Something" ) as mock_status: exists = self.db.is_db_exits() # after mock_is_file.assert_any_call(self.db.database_path) mock_status.assert_any_call("db", False) assert exists is False def test_initialise_backend_i(self): """Test the initialise_backend method of the DetectionDatabase class where db NOT exists.""" # operation with patch.object(self.db, "ensure_dirs_exist") as mock_ensure: with patch.object(self.db, "execute_single_sql") as mock_sql: with patch.object( self.db, "is_db_exits", return_value=False ) as mock_exists: with patch.object(self.db, "set_system_status") as mock_status: self.db.initialise_backend() # after mock_ensure.assert_called_once() mock_sql.assert_called() assert mock_sql.call_count == 6 mock_exists.assert_called_once() mock_status.assert_called() assert mock_status.call_count == 3 def test_initialise_backend_ii(self): """Test the initialise_backend method of the DetectionDatabase class where db exists.""" # operation with patch.object(self.db, "ensure_dirs_exist") as mock_ensure: with patch.object(self.db, "execute_single_sql") as mock_sql: with patch.object( self.db, "is_db_exits", return_value=True ) as mock_exists: with patch.object(self.db, "set_system_status") as mock_status: self.db.initialise_backend() # after mock_ensure.assert_called_once() mock_sql.assert_called() assert mock_sql.call_count == 6 mock_exists.assert_called_once() mock_status.assert_called() assert mock_status.call_count == 1 def test_get_lat_lon_i(self): """Test the get_lat_lon method of the DetectionDatabase class where lat and lon are UNKNOWN.""" # setup lat = 1.2 lon = 3 expected_lon = 3.0 # operation with patch.object( self.db, "get_system_status", side_effect=[lat, lon] ) as mock_status: actual_lat, actual_lon = self.db.get_lat_lon() # after mock_status.assert_called() assert mock_status.call_count == 2 assert actual_lat == lat assert actual_lon == expected_lon def test_get_lat_lon_ii(self): """Test the get_lat_lon method of the DetectionDatabase class where lat and lon are NOT UNKNOWN.""" # setup lat = "UNKNOWN" lon = "UNKNOWN" # operation with patch.object( self.db, "get_system_status", side_effect=[lat, lon] ) as mock_status: actual_lat, actual_lon = self.db.get_lat_lon() # after mock_status.assert_called() assert mock_status.call_count == 2 assert actual_lat is None assert actual_lon is None def test_set_system_status(self): """Test the set_system_status method of the DetectionDatabase class.""" # setup system_name = "some_system_name" status = "some_status" # operation with patch.object(self.db, "execute_single_sql") as mock_sql: self.db.set_system_status(system_name, status) # after mock_sql.assert_any_call( "INSERT OR REPLACE INTO status_table(system_name, status) values(?, ?)", (system_name, status), ) def test_get_system_status_i(self): """Test the get_system_status method of the DetectionDatabase class where result is NOT empty.""" # setup system_name = "some_system_name" print_exceptions = True expected_result = 1 mocked_result = [[expected_result, 2], [3, 4]] # operation with patch.object( self.db, "execute_single_sql", return_value=mocked_result ) as mock_sql: actual_result = self.db.get_system_status(system_name, print_exceptions) # after mock_sql.assert_any_call( "SELECT status FROM status_table WHERE system_name=?", (system_name,), print_exceptions, ) assert actual_result == expected_result def test_get_system_status_ii(self): """Test the get_system_status method of the DetectionDatabase class where result IS empty.""" # setup system_name = "some_system_name" print_exceptions = True mocked_result = [] # operation with patch.object( self.db, "execute_single_sql", return_value=mocked_result ) as mock_sql: actual_result = self.db.get_system_status(system_name, print_exceptions) # after mock_sql.assert_any_call( "SELECT status FROM status_table WHERE system_name=?", (system_name,), print_exceptions, ) assert actual_result == "UNKNOWN" def test_execute_single_sql_i(self): """Test the execute_single_sql method of the DBCommunication class where NO exception is thrown.""" # setup command = "some_command" variables = (1, "2", 4.3) print_exceptions = True result = [1, 2, 3, 4, 5] mocked_conn = Mock(wrap=sqlite3.Connection) mocked_cursor = Mock(wraps=sqlite3.Cursor) # operation with patch("sqlite3.connect", return_value=mocked_conn) as mock_conn: with patch.object( mocked_conn, "cursor", return_value=mocked_cursor ) as mock_curs: with patch.object(mocked_cursor, "execute") as mock_exe: with patch.object( mocked_cursor, "fetchall", return_value=result ) as mock_fetchall: with patch.object(mocked_conn, "commit") as mock_commit: with patch.object(mocked_conn, "close") as mock_con_close: actual_result = self.db.execute_single_sql( command, variables, print_exceptions ) # after mock_conn.assert_called_once() mock_curs.assert_called_once() mock_exe.assert_any_call( command, variables, ) mock_fetchall.assert_called_once() mock_commit.assert_called_once() mock_con_close.assert_called_once() assert actual_result == result def test_execute_single_sql_ii(self): """Test the execute_single_sql method of the DBCommunication class where an exception IS thrown.""" # setup command = "some_command" variables = (1, "2", 4.3) print_exceptions = True result = [1, 2, 3, 4, 5] exception_message = "some_exception_message" mocked_conn = Mock(wrap=sqlite3.Connection) mocked_cursor = Mock(wraps=sqlite3.Cursor) # operation with patch("sqlite3.connect", return_value=mocked_conn) as mock_conn: with patch.object( mocked_conn, "cursor", return_value=mocked_cursor ) as mock_curs: with patch.object(mocked_cursor, "execute") as mock_exe: with patch.object( mocked_cursor, "fetchall", return_value=result ) as mock_fetchall: with patch.object( mocked_conn, "commit", side_effect=ValueError(exception_message), ) as mock_commit: with patch.object(mocked_conn, "close") as mock_con_close: with patch.object( self.db.logger, "warning" ) as mock_logger: actual_result = self.db.execute_single_sql( command, variables, print_exceptions ) # after mock_conn.assert_called_once() mock_curs.assert_called_once() mock_exe.assert_any_call( command, variables, ) mock_fetchall.assert_called_once() mock_commit.assert_called_once() mock_logger.assert_any_call( f"Exception in database: {exception_message}", ) mock_con_close.assert_called_once() assert actual_result == result def test_get_latest_detection_data_i(self): """Test the get_latest_detection_data method of the DBCommunication class where result is NOT None.""" # setup max_num_rows = 2 command = """SELECT * FROM images ORDER BY epoch DESC LIMIT ?""" result = [[1, 2, 3, 4, 5, 6, 7, 8]] expected_result = [ { "epoch": result[0][0], "raw_image_path": result[0][1], "processed_image_path": result[0][2], "total_count": result[0][3], "moving_count": result[0][4], "free_spaces": result[0][5], "lat": result[0][6], "lon": result[0][7], } ] # operation with patch.object( self.db, "execute_single_sql", return_value=result ) as mock_exe: actual_result = self.db.get_latest_detection_data(max_num_rows) # after mock_exe.assert_any_call( command, (max_num_rows,), ) assert actual_result == expected_result def test_get_latest_detection_data_ii(self): """Test the get_latest_detection_data method of the DBCommunication class where result IS None.""" # setup max_num_rows = 2 command = """SELECT * FROM images ORDER BY epoch DESC LIMIT ?""" result = None # operation with patch.object( self.db, "execute_single_sql", return_value=result ) as mock_exe: actual_result = self.db.get_latest_detection_data(max_num_rows) # after mock_exe.assert_any_call( command, (max_num_rows,), ) assert actual_result is None def test_ensure_dirs_exist(self): """Test the ensure_dirs_exist method of the DetectionDatabase class where db exists.""" # operation with patch("os.path.isdir", return_value=False) as mock_is_dir: with patch("os.mkdir") as mock_mkdir: self.db.ensure_dirs_exist() # after mock_is_dir.assert_any_call(self.db.temp_dir) mock_is_dir.assert_any_call(self.db.raw_image_dir) mock_is_dir.assert_any_call(self.db.processed_image_dir) mock_mkdir.assert_any_call(self.db.temp_dir) mock_mkdir.assert_any_call(self.db.raw_image_dir) mock_mkdir.assert_any_call(self.db.processed_image_dir) ================================================ FILE: tests/test_packages/test_skills/test_carpark_detection/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the carpark detection skill.""" from pathlib import Path from unittest.mock import patch import pytest from aea.exceptions import AEAEnforceError from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.carpark_detection.strategy import ( DEFAULT_DB_IS_REL_TO_CWD, DEFAULT_DB_REL_DIR, Strategy, ) from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of carpark detection.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "carpark_detection") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.db_is_rel_to_cwd = DEFAULT_DB_IS_REL_TO_CWD cls.db_rel_dir = DEFAULT_DB_REL_DIR cls.strategy = Strategy( db_is_rel_to_cwd=cls.db_is_rel_to_cwd, db_rel_dir=cls.db_rel_dir, name="strategy", skill_context=cls._skill.skill_context, ) def test__init__i(self): """Test the __init__ method of the Strategy class where db_dir does NOT exist.""" # operation with patch("os.path.isdir", return_value=False): with pytest.raises(ValueError, match="Database directory does not exist!"): Strategy( db_is_rel_to_cwd=self.db_is_rel_to_cwd, db_rel_dir=self.db_rel_dir, name="strategy", skill_context=self.skill.skill_context, ) def test__init__ii(self): """Test the __init__ method of the Strategy class where db_is_rel_to_cwd is True.""" # operation with patch("os.path.isdir", return_value=True): with patch.object(self.strategy, "_update_service_data") as mock_update: self.strategy.__init__( db_is_rel_to_cwd=True, db_rel_dir=self.db_rel_dir, name="strategy", skill_context=self.skill.skill_context, ) mock_update.assert_called_once() def test_update_service_data(self): """Test the _update_service_data method of the Strategy class.""" lat = 2 lon = 3 # operation with patch.object( self.strategy.db, "is_db_exits", return_value=True ) as mock_db_exists: with patch.object( self.strategy.db, "get_latest_detection_data", return_value=[1, 2] ) as mock_latest_detection: with patch.object( self.strategy.db, "get_lat_lon", return_value=(lat, lon) ) as mock_lat_lon: self.strategy._update_service_data() # after mock_db_exists.assert_called_once() mock_latest_detection.assert_any_call(1) mock_lat_lon.assert_called_once() assert self.strategy._service_data == { "latitude": lat, "longitude": lon, } def test_collect_from_data_source_i(self): """Test the collect_from_data_source method of the Strategy class where len(data)>0.""" # setup free_spaces = 24 data = [ { "epoch": "some_epoch", "raw_image_path": "some_raw_image_path", "processed_image_path": "some_processed_image_path", "total_count": "some_total_count", "moving_count": "some_moving_count", "free_spaces": free_spaces, "lat": "some_lat", "lon": "some_lon", }, { "epoch": "some_other_epoch", "raw_image_path": "some_other_raw_image_path", "processed_image_path": "some_other_processed_image_path", "total_count": "some_other_total_count", "moving_count": "some_other_moving_count", "free_spaces": 10, "lat": "some_other_lat", "lon": "some_other_lon", }, { "epoch": "some_yet_another_epoch", "raw_image_path": "some_yet_another_raw_image_path", "processed_image_path": "some_yet_another_processed_image_path", "total_count": "some_yet_another_total_count", "moving_count": "some_yet_another_moving_count", "free_spaces": 2, "lat": "some_yet_another_lat", "lon": "some_yet_another_lon", }, ] expected_result = {"free_spaces": str(free_spaces)} # operation with patch.object( self.strategy.db, "get_latest_detection_data", return_value=data ) as mock_get_data: result = self.strategy.collect_from_data_source() # after mock_get_data.assert_any_call(1) assert result == expected_result def test_collect_from_data_source_ii(self): """Test the collect_from_data_source method of the Strategy class where len(data)==0.""" # setup data = [] # operation with patch.object( self.strategy.db, "get_latest_detection_data", return_value=data ) as mock_get_data: with pytest.raises(AEAEnforceError, match="Did not find any data."): self.strategy.collect_from_data_source() # after mock_get_data.assert_any_call(1) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw1/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/confirmation_aw1 dir.""" ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw1/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the confirmation aw1 skill.""" import logging from pathlib import Path from typing import Tuple, cast from unittest.mock import patch import pytest from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.skills.confirmation_aw1.behaviours import ( LEDGER_API_ADDRESS, TransactionBehaviour, ) from packages.fetchai.skills.confirmation_aw1.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, RegisterDialogue, RegisterDialogues, ) from packages.fetchai.skills.confirmation_aw1.strategy import Strategy from tests.conftest import ROOT_DIR FETCHAI = "fetchai" class TestTransactionBehaviour(BaseSkillTestCase): """Test transaction behaviour of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.transaction_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.register_dialogues = cast( RegisterDialogues, cls._skill.skill_context.register_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.list_of_registration_messages = ( DialogueMessage( RegisterMessage.Performative.REGISTER, {"info": {"some_key": "some_value"}}, ), ) @staticmethod def _check_start_processing_effects(self_, register_dialogue, mock_logger) -> None: """Perform checks related to running _start_processing.""" mock_logger.assert_any_call( logging.INFO, f"Processing transaction, {len(self_.transaction_behaviour.waiting)} transactions remaining", ) message = self_.get_message_from_outbox() has_attributes, error_str = self_.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self_.skill.skill_context.skill_id), terms=register_dialogue.terms, ) assert has_attributes, error_str ledger_api_dialogue = cast( LedgerApiDialogue, self_.ledger_api_dialogues.get_dialogue(message) ) assert ledger_api_dialogue.associated_register_dialogue == register_dialogue assert self_.transaction_behaviour.processing_time == 0.0 assert self_.transaction_behaviour.processing == ledger_api_dialogue mock_logger.assert_any_call( logging.INFO, f"requesting transfer transaction from ledger api for message={message}...", ) @staticmethod def _setup_register_ledger_api_dialogues( self_, ) -> Tuple[LedgerApiDialogue, RegisterDialogue]: """Setup register and ledger_api dialogues for some of the following tests.""" register_dialogue = cast( RegisterDialogue, self_.prepare_skill_dialogue( dialogues=self_.register_dialogues, messages=self_.list_of_registration_messages, is_agent_to_agent_messages=True, ), ) register_dialogue.terms = Terms( ledger_id="some_ledger_id", sender_address="some_sender_address", counterparty_address="some_counterparty", amount_by_currency_id={"1": -10}, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce="some_none", fee_by_currency_id={"1": 100}, ) ledger_api_dialogue = cast( LedgerApiDialogue, self_.prepare_skill_dialogue( dialogues=self_.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": "some_ledger_id", "address": "some_address"}, ), ), ), ) ledger_api_dialogue.associated_register_dialogue = register_dialogue return ledger_api_dialogue, register_dialogue def test_setup(self): """Test the setup method of the transaction behaviour.""" assert self.transaction_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act_i(self): """Test the act method of the transaction behaviour where processing IS None and len(self.waiting) is NOT 0.""" # setup _, register_dialogue = self._setup_register_ledger_api_dialogues(self) processing_time = 5.0 max_processing = 120 self.transaction_behaviour.processing = None self.transaction_behaviour.max_processing = max_processing self.transaction_behaviour.processing_time = processing_time self.transaction_behaviour.waiting = [register_dialogue] # before assert self.transaction_behaviour.processing_time == processing_time assert self.transaction_behaviour.processing is None # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(1) # _start_processing self._check_start_processing_effects(self, register_dialogue, mock_logger) def test_act_ii(self): """Test the act method of the transaction behaviour where processing is NOT None and processing_time < max_processing.""" # setup processing_time = 5.0 self.transaction_behaviour.processing = "some_dialogue" self.transaction_behaviour.max_processing = 120 self.transaction_behaviour.processing_time = processing_time # operation self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(0) assert ( self.transaction_behaviour.processing_time == processing_time + self.transaction_behaviour.tick_interval ) def test_act_iii(self): """Test the act method of the transaction behaviour where processing is NOT None and processing_time > max_processing.""" # setup ( ledger_api_dialogue, register_dialogue, ) = self._setup_register_ledger_api_dialogues(self) processing_time = 121.0 self.transaction_behaviour.processing = ledger_api_dialogue self.transaction_behaviour.max_processing = 120 self.transaction_behaviour.processing_time = processing_time # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(1) # _timeout_processing assert ledger_api_dialogue.dialogue_label in self.transaction_behaviour.timedout # below is overridden in _start_processing # assert register_dialogue in self.transaction_behaviour.waiting assert self.transaction_behaviour.processing_time == 0.0 # below is overridden in _start_processing # assert self.transaction_behaviour.processing is None # _start_processing self._check_start_processing_effects(self, register_dialogue, mock_logger) def test_timeout_processing(self): """Test the _timeout_processing method of the transaction behaviour where self.processing IS None.""" # setup self.transaction_behaviour.processing_time = None # operation self.transaction_behaviour._timeout_processing() # after self.assert_quantity_in_outbox(0) def test_act_iv(self): """Test the act method of the transaction behaviour where len(waiting) == 0.""" # setup self.transaction_behaviour.processing = None self.transaction_behaviour.waiting = [] # operation self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_failed_processing(self): """Test the failed_processing method of the transaction behaviour.""" # setup ( ledger_api_dialogue, register_dialogue, ) = self._setup_register_ledger_api_dialogues(self) self.transaction_behaviour.timedout.add(ledger_api_dialogue.dialogue_label) # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.failed_processing(ledger_api_dialogue) # after self.assert_quantity_in_outbox(0) # finish_processing assert self.transaction_behaviour.timedout == set() mock_logger.assert_any_call( logging.DEBUG, f"Timeout dialogue in transaction processing: {ledger_api_dialogue}", ) # failed_processing assert register_dialogue in self.transaction_behaviour.waiting def test_finish_processing_i(self): """Test the finish_processing method of the transaction behaviour where self.processing == ledger_api_dialogue.""" # setup ( ledger_api_dialogue, register_dialogue, ) = self._setup_register_ledger_api_dialogues(self) self.transaction_behaviour.processing = ledger_api_dialogue # operation self.transaction_behaviour.failed_processing(ledger_api_dialogue) # after assert self.transaction_behaviour.processing_time == 0.0 assert self.transaction_behaviour.processing is None def test_finish_processing_ii(self): """Test the finish_processing method of the transaction behaviour where ledger_api_dialogue's dialogue_label is NOT in self.timedout.""" # setup ( ledger_api_dialogue, register_dialogue, ) = self._setup_register_ledger_api_dialogues(self) # operation with pytest.raises(ValueError) as err: self.transaction_behaviour.finish_processing(ledger_api_dialogue) # after assert ( err.value.args[0] == f"Non-matching dialogues in transaction behaviour: {self.transaction_behaviour.processing} and {ledger_api_dialogue}" ) def test_teardown(self): """Test the teardown method of the transaction behaviour.""" assert self.transaction_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw1/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the generic buyer skill.""" from pathlib import Path from typing import cast import pytest from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.confirmation_aw1.dialogues import ( ContractApiDialogue, ContractApiDialogues, DefaultDialogue, DefaultDialogues, LedgerApiDialogue, LedgerApiDialogues, RegisterDialogue, RegisterDialogues, SigningDialogue, SigningDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue classes of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.register_dialogues = cast( RegisterDialogues, cls._skill.skill_context.register_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_register_dialogue(self): """Test the RegisterDialogue class.""" register_dialogue = RegisterDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=RegisterDialogue.Role.AGENT, ) # terms with pytest.raises(AEAEnforceError, match="Terms not set!"): assert register_dialogue.terms terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) register_dialogue.terms = terms with pytest.raises(AEAEnforceError, match="Terms already set!"): register_dialogue.terms = terms assert register_dialogue.terms == terms def test_register_dialogues(self): """Test the RegisterDialogues class.""" _, dialogue = self.register_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=RegisterMessage.Performative.REGISTER, info={"some_key": "some_value"}, ) assert dialogue.role == RegisterDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_contract_api_dialogue(self): """Test the ContractApiDialogue class.""" contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_register_dialogue with pytest.raises(ValueError, match="Associated register dialogue not set!"): assert contract_api_dialogue.associated_register_dialogue register_dialogue = RegisterDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=RegisterDialogue.Role.AGENT, ) contract_api_dialogue.associated_register_dialogue = register_dialogue with pytest.raises( AEAEnforceError, match="Associated register dialogue already set!" ): contract_api_dialogue.associated_register_dialogue = register_dialogue assert contract_api_dialogue.associated_register_dialogue == register_dialogue # terms with pytest.raises(ValueError, match="Terms not set!"): assert contract_api_dialogue.terms terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) contract_api_dialogue.terms = terms with pytest.raises(AEAEnforceError, match="Terms already set!"): contract_api_dialogue.terms = terms assert contract_api_dialogue.terms == terms def test_contract_api_dialogues(self): """Test the ContractApiDialogues class.""" _, dialogue = self.contract_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id="some_ledger_id", contract_id="some_contract_id", callable="some_callable", kwargs=Kwargs({"some_key": "some_value"}), ) assert dialogue.role == ContractApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_ledger_api_dialogue(self): """Test the LedgerApiDialogue class.""" ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=LedgerApiDialogue.Role.AGENT, ) # associated_register_dialogue with pytest.raises(AEAEnforceError, match="RegisterDialogue not set!"): assert ledger_api_dialogue.associated_register_dialogue register_dialogue = RegisterDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=RegisterDialogue.Role.AGENT, ) ledger_api_dialogue.associated_register_dialogue = register_dialogue with pytest.raises(AEAEnforceError, match="RegisterDialogue already set!"): ledger_api_dialogue.associated_register_dialogue = register_dialogue assert ledger_api_dialogue.associated_register_dialogue == register_dialogue def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_signing_dialogue(self): """Test the SigningDialogue class.""" signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=SigningDialogue.Role.SKILL, ) # associated_ledger_api_dialogue with pytest.raises(AEAEnforceError, match="LedgerApiDialogue not set!"): assert signing_dialogue.associated_ledger_api_dialogue ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=LedgerApiDialogue.Role.AGENT, ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue with pytest.raises(AEAEnforceError, match="LedgerApiDialogue already set!"): signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue assert signing_dialogue.associated_ledger_api_dialogue == ledger_api_dialogue def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms="some_terms", raw_transaction="some_raw_transaction", ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw1/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the confirmation aw1 skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import PropertyMock, patch import pytest from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import ( RawTransaction, SignedTransaction, Terms, TransactionDigest, TransactionReceipt, ) from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.custom_types import Kwargs, State from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.confirmation_aw1.behaviours import TransactionBehaviour from packages.fetchai.skills.confirmation_aw1.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogue, LedgerApiDialogues, RegisterDialogue, RegisterDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.confirmation_aw1.handlers import ( AW1RegistrationHandler, ContractApiHandler, LEDGER_API_ADDRESS, LedgerApiHandler, SigningHandler, ) from packages.fetchai.skills.confirmation_aw1.strategy import Strategy from tests.conftest import ROOT_DIR class TestAW1RegistrationHandler(BaseSkillTestCase): """Test registration handler of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.register_handler = cast( AW1RegistrationHandler, cls._skill.skill_context.handlers.registration ) cls.logger = cls._skill.skill_context.logger cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.tx_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.register_dialogues = cast( RegisterDialogues, cls._skill.skill_context.register_dialogues ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.info = {"ethereum_address": "some_ethereum_address"} cls.kwargs = {"address": "some_ethereum_address"} cls.terms = Terms( ledger_id="some_ledger_id", sender_address="some_sender_address", counterparty_address="some_counterparty", amount_by_currency_id={"1": -10}, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce="some_none", fee_by_currency_id={"1": 100}, ) cls.list_of_registration_messages = ( DialogueMessage(RegisterMessage.Performative.REGISTER, {"info": cls.info}), ) def test_setup(self): """Test the setup method of the fipa handler.""" assert self.register_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the register handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=RegisterMessage, dialogue_reference=incorrect_dialogue_reference, performative=RegisterMessage.Performative.REGISTER, info=self.info, ) # operation with patch.object(self.register_handler.context.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid register_msg message={incoming_message}, unidentified dialogue.", ) def test_handle_register_is_valid_i(self): """Test the _handle_register method of the register handler where is_valid is True and NOT in developer_only_mode.""" # setup incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=RegisterMessage, performative=RegisterMessage.Performative.REGISTER, info=self.info, ), ) # operation with patch.object( self.strategy, "valid_registration", return_value=(True, 0, "all good!"), ) as mock_valid: with patch.object( self.strategy, "lock_registration_temporarily" ) as mock_lock: with patch.object( self.strategy, "get_kwargs", return_value=self.kwargs ) as mock_kwargs: with patch.object( self.strategy, "get_terms", return_value=self.terms ) as mock_terms: with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_valid.called_once() mock_lock.called_once() mock_kwargs.called_once() mock_terms.called_once() mock_logger.assert_any_call( logging.INFO, f"valid registration={incoming_message.info}. Verifying if tokens staked.", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_STATE, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), contract_id=self.strategy.contract_id, contract_address=self.strategy.contract_address, callable=self.strategy.contract_callable, kwargs=ContractApiMessage.Kwargs(self.kwargs), ) assert has_attributes, error_str contract_api_dialogue = cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ) register_dialogue = cast( RegisterDialogue, self.register_dialogues.get_dialogue(incoming_message) ) assert contract_api_dialogue.terms == self.terms assert contract_api_dialogue.associated_register_dialogue == register_dialogue def test_handle_register_is_valid_ii(self): """Test the _handle_register method of the register handler where is_valid is True and IN developer_only_mode.""" # setup self.strategy.developer_handle_only = True incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=RegisterMessage, performative=RegisterMessage.Performative.REGISTER, info=self.info, ), ) # operation with patch.object( self.strategy, "valid_registration", return_value=(True, 0, "all good!"), ) as mock_valid: with patch.object( self.strategy, "lock_registration_temporarily" ) as mock_lock: with patch.object( self.strategy, "get_terms", return_value=self.terms ) as mock_terms: with patch.object( self.strategy, "finalize_registration" ) as mock_finalize: with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_valid.called_once() mock_lock.called_once() mock_terms.called_once() mock_logger.assert_any_call( logging.INFO, f"valid registration={incoming_message.info}. Verifying if tokens staked.", ) mock_finalize.assert_called_once() register_dialogue = cast( RegisterDialogue, self.register_dialogues.get_dialogue(incoming_message) ) assert register_dialogue.terms == self.terms assert register_dialogue in self.tx_behaviour.waiting def test_handle_register_is_not_valid(self): """Test the _handle_register method of the register handler where is_valid is False.""" # setup incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=RegisterMessage, performative=RegisterMessage.Performative.REGISTER, info=self.info, ), ) error_code = 1 error_msg = "already registered!" # operation with patch.object( self.strategy, "valid_registration", return_value=(False, error_code, error_msg), ) as mock_valid: with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_valid.called_once() mock_logger.assert_any_call( logging.INFO, f"invalid registration={incoming_message.info}. Rejecting.", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=RegisterMessage, performative=RegisterMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=error_code, error_msg=error_msg, info={}, ) assert has_attributes, error_str def test_handle_invalid(self): """Test the _handle_invalid method of the fipa handler.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], ), ) incoming_message = cast( RegisterMessage, self.build_incoming_message_for_skill_dialogue( dialogue=register_dialogue, performative=RegisterMessage.Performative.SUCCESS, info=self.info, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle register_msg message of performative={incoming_message.performative} in dialogue={register_dialogue}.", ) def test_teardown(self): """Test the teardown method of the fipa handler.""" assert self.register_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestContractApiHandler(BaseSkillTestCase): """Test contract_api handler of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.contract_api_handler = cast( ContractApiHandler, cls._skill.skill_context.handlers.contract_api ) cls.tx_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls.contract_api_handler.context.logger cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.register_dialogues = cast( RegisterDialogues, cls._skill.skill_context.register_dialogues ) cls.ledger_id = "some_ledger_id" cls.contract_id = "some_contract_id" cls.contract_address = "some_contract_address," cls.callable = "some_callable" cls.kwargs = Kwargs({"some_key": "some_value"}) cls.state = State("some_ledger_id", {"some_key": "some_value"}) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_STATE, { "ledger_id": cls.ledger_id, "contract_id": cls.contract_id, "contract_address": cls.contract_address, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.info = {"ethereum_address": "some_ethereum_address"} cls.terms = Terms( ledger_id="some_ledger_id", sender_address="some_sender_address", counterparty_address="some_counterparty", amount_by_currency_id={"1": -10}, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce="some_none", fee_by_currency_id={"1": 100}, ) cls.list_of_registration_messages = ( DialogueMessage( RegisterMessage.Performative.REGISTER, {"info": cls.info}, is_incoming=True, ), ) def test_setup(self): """Test the setup method of the contract_api handler.""" assert self.contract_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the contract_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=ContractApiMessage.Performative.STATE, state=self.state, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid contract_api message={incoming_message}, unidentified dialogue.", ) def test_handle_state_staked( self, ): """Test the _handle_state method of the contract_api handler where has_staked is True.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.associated_register_dialogue = register_dialogue contract_api_dialogue.terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.STATE, state=self.state, ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(self.strategy, "has_staked", return_value=True): with patch.object( self.strategy, "finalize_registration" ) as mock_finalize: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received state message={incoming_message}" ) mock_logger.assert_any_call( logging.INFO, "Has staked! Requesting funds release." ) mock_finalize.assert_called_once() assert register_dialogue.terms == contract_api_dialogue.terms assert register_dialogue in self.tx_behaviour.waiting def test_handle_state_not_staked( self, ): """Test the _handle_state method of the contract_api handler where has_staked is False.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.associated_register_dialogue = register_dialogue contract_api_dialogue.terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.STATE, state=self.state, ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(self.strategy, "has_staked", return_value=False): with patch.object(self.strategy, "unlock_registration") as mock_unlock: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received state message={incoming_message}" ) mock_unlock.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"invalid registration={cast(RegisterMessage, register_dialogue.last_incoming_message).info}. Rejecting.", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=RegisterMessage, performative=RegisterMessage.Performative.ERROR, to=register_dialogue.dialogue_label.dialogue_opponent_addr, sender=self.skill.skill_context.agent_address, error_code=1, error_msg="No funds staked!", info={}, ) assert has_attributes, error_str def test_handle_state_register_msg_is_none( self, ): """Test the _handle_state method of the contract_api handler where register_msg is None.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) register_dialogue._incoming_messages = [] contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.associated_register_dialogue = register_dialogue contract_api_dialogue.terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.STATE, state=self.state, ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises(ValueError, match="Could not retrieve fipa message"): self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received state message={incoming_message}" ) def test_handle_error_i(self): """Test the _handle_error method of the contract_api handler.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.associated_register_dialogue = register_dialogue contract_api_dialogue.terms = self.terms incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.strategy, "unlock_registration") as mock_unlock: with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) mock_unlock.assert_called_once() def test_handle_error_ii(self): """Test the _handle_error method of the contract_api handler where register_dialogue's last incoming message is None.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) register_dialogue._incoming_messages = [] contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.associated_register_dialogue = register_dialogue contract_api_dialogue.terms = self.terms incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises(ValueError, match="Could not retrieve fipa message"): self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the contract_api handler.""" # setup invalid_performative = ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=self.ledger_id, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle contract_api message of performative={invalid_performative} in dialogue={self.contract_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the contract_api handler.""" assert self.contract_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(BaseSkillTestCase): """Test signing handler of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.tx_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.register_dialogues = cast( RegisterDialogue, cls._skill.skill_context.register_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.info = {"ethereum_address": "some_ethereum_address"} cls.list_of_registration_messages = ( DialogueMessage( RegisterMessage.Performative.REGISTER, {"info": cls.info}, is_incoming=True, ), ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": cls.terms, "raw_transaction": SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), }, ), ) cls.list_of_ledger_api_messages = ( DialogueMessage(LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {}), DialogueMessage(LedgerApiMessage.Performative.RAW_TRANSACTION, {}), DialogueMessage(LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {}), DialogueMessage(LedgerApiMessage.Performative.TRANSACTION_DIGEST, {}), ) def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_transaction_last_ledger_api_message_is_none( self, ): """Test the _handle_signed_transaction method of the signing handler.""" # setup signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], ), ) ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:2], ), ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue signing_dialogue.associated_ledger_api_dialogue._incoming_messages = [] incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SigningMessage.SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ), ) # operation with pytest.raises( ValueError, match="Could not retrieve last message in ledger api dialogue" ): with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") def test_handle_signed_transaction_last_ledger_api_message_is_not_none( self, ): """Test the _handle_signed_transaction method of the signing handler where the last ledger_api message is not None.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:2], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SigningMessage.SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), signed_transaction=incoming_message.signed_transaction, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "sending transaction to ledger.") def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], counterparty=COUNTERPARTY_AGENT_ADDRESS, is_agent_to_agent_messages=True, ), ) ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:4], counterparty=LEDGER_API_ADDRESS, ), ) ledger_api_dialogue.associated_register_dialogue = register_dialogue signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.tx_behaviour, "failed_processing"): with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) # finish_processing assert self.tx_behaviour.processing_time == 0.0 assert self.tx_behaviour.processing is None def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.terms, raw_transaction=SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestGenericLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.transaction_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.register_dialogues = cast( RegisterDialogue, cls._skill.skill_context.register_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.info = {"ethereum_address": "some_ethereum_address"} cls.list_of_registration_messages = ( DialogueMessage( RegisterMessage.Performative.REGISTER, {"info": cls.info}, is_incoming=True, ), ) cls.raw_transaction = RawTransaction( "some_ledger_id", {"some_key": "some_value"} ) cls.signed_transaction = SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ) cls.transaction_digest = TransactionDigest("some_ledger_id", "some_body") cls.transaction_receipt = TransactionReceipt( "some_ledger_id", {"receipt_key": "receipt_value"}, {"transaction_key": "transaction_value"}, ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {"terms": cls.terms} ), DialogueMessage( LedgerApiMessage.Performative.RAW_TRANSACTION, {"raw_transaction": cls.raw_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {"signed_transaction": cls.signed_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_DIGEST, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_RECEIPT, {"transaction_receipt": cls.transaction_receipt}, ), ) cls.list_of_aws_aeas = ["awx_aea_1", "awx_aea_2"] cls.developer_handle = "some_developer_handle" def _check_send_confirmation_details_to_awx_aeas(self, aea, mock_logger): mock_logger.assert_any_call( logging.INFO, f"informing awx_aeas={self.list_of_aws_aeas} of registration success of confirmed aea={aea} of developer={self.developer_handle}.", ) for awx_awa in self.list_of_aws_aeas: message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, to=awx_awa, sender=self.skill.skill_context.agent_address, content=f"{aea}_{self.developer_handle}".encode("utf-8"), ) assert has_attributes, error_str def test_setup(self): """Test the setup method of the ledger_api handler.""" # setup list_of_aea = ["aea_1", "aea_2"] # operation with patch.object( type(self.strategy), "all_registered_aeas", new_callable=PropertyMock, return_value=list_of_aea, ): with patch.object( type(self.strategy), "awx_aeas", new_callable=PropertyMock, return_value=self.list_of_aws_aeas, ): with patch.object( self.strategy, "get_developer_handle", return_value=self.developer_handle, ): with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.setup() # after self.assert_quantity_in_outbox(len(self.list_of_aws_aeas) * len(list_of_aea)) for aea in list_of_aea: self._check_send_confirmation_details_to_awx_aeas(aea, mock_logger) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.ERROR, code=1, ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_raw_transaction(self): """Test the _handle_raw_transaction method of the ledger_api handler.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) register_dialogue.terms = self.terms ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], counterparty=LEDGER_API_ADDRESS, ), ) ledger_api_dialogue.associated_register_dialogue = register_dialogue incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.RAW_TRANSACTION, raw_transaction=self.raw_transaction, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after self.assert_quantity_in_decision_making_queue(1) mock_logger.assert_any_call( logging.INFO, f"received raw transaction={incoming_message}" ) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_TRANSACTION, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=self.terms, ) assert has_attributes, error_str signing_dialogue = cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ) assert signing_dialogue.associated_ledger_api_dialogue == ledger_api_dialogue mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) def test_handle_transaction_digest(self): """Test the _handle_transaction_digest method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:3], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=self.transaction_digest, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully submitted. Transaction digest={incoming_message.transaction_digest}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, to=incoming_message.sender, sender=str(self.skill.skill_context.skill_id), transaction_digest=self.transaction_digest, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "checking transaction is settled.", ) def test_handle_transaction_receipt_i(self): """Test the _handle_transaction_receipt method of the ledger_api handler.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) register_dialogue.terms = self.terms ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) ledger_api_dialogue.associated_register_dialogue = register_dialogue last_outgoing_message = cast( LedgerApiMessage, ledger_api_dialogue.last_outgoing_message ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( self.transaction_behaviour, "finish_processing" ) as mocked_finish: with patch.object( LedgerApis, "is_transaction_settled", return_value=True ) as mocked_settled: with patch.object( type(self.strategy), "awx_aeas", new_callable=PropertyMock, return_value=self.list_of_aws_aeas, ): with patch.object( self.strategy, "get_developer_handle", return_value=self.developer_handle, ): with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mocked_settled.assert_called_once() mocked_finish.assert_any_call(ledger_api_dialogue) self.assert_quantity_in_outbox(3) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=RegisterMessage, performative=RegisterMessage.Performative.SUCCESS, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, info={"transaction_digest": last_outgoing_message.transaction_digest.body}, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"informing counterparty={message.to} of registration success.", ) # _send_confirmation_details_to_awx_aeas self._check_send_confirmation_details_to_awx_aeas(message.to, mock_logger) def test_handle_transaction_receipt_ii(self): """Test the _handle_transaction_receipt method of the ledger_api handler where last register msg is None.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) register_dialogue.terms = self.terms register_dialogue._incoming_messages = [] ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) ledger_api_dialogue.associated_register_dialogue = register_dialogue incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( self.transaction_behaviour, "finish_processing" ) as mocked_finish: with patch.object( LedgerApis, "is_transaction_settled", return_value=True ) as mocked_settled: with pytest.raises( ValueError, match="Could not retrieve last register message" ): self.ledger_api_handler.handle(incoming_message) # after mocked_settled.assert_called_once() mocked_finish.assert_any_call(ledger_api_dialogue) def test_handle_transaction_receipt_iii(self): """Test the _handle_transaction_receipt method of the ledger_api handler where tx is NOT settled.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_registration_messages[:1], is_agent_to_agent_messages=True, ), ) register_dialogue.terms = self.terms ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) ledger_api_dialogue.associated_register_dialogue = register_dialogue incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( self.transaction_behaviour, "failed_processing" ) as mocked_failed: with patch.object( LedgerApis, "is_transaction_settled", return_value=False ) as mocked_settled: with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mocked_settled.assert_called_once() mocked_failed.assert_any_call(ledger_api_dialogue) self.assert_quantity_in_outbox(0) assert self.transaction_behaviour.processing is None assert self.transaction_behaviour.processing_time == 0.0 mock_logger.assert_any_call( logging.INFO, f"transaction_receipt={self.transaction_receipt} not settled or not valid, aborting", ) def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) # operation with patch.object( self.transaction_behaviour, "failed_processing" ) as mocked_failed: with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mocked_failed.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id="some_ledger_id", address="some_address", to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw1/test_registration_db.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the RegistrationDB class of the confirmation aw1 skill.""" from pathlib import Path from unittest.mock import patch import pytest from aea.test_tools.test_skill import BaseSkillTestCase from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test RegistrationDB of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.custom_path = None cls.db = cls._skill.skill_context.registration_db cls.address = "some_address" def test__initialise_backend(self): """Test the _initialise_backend method of the RegistrationDB class.""" # operation with patch("os.path.isfile", return_value=False) as mock_is_file: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db._initialise_backend() # after mock_is_file.assert_called_once() mock_exe.assert_any_call( "CREATE TABLE IF NOT EXISTS registered_table (address TEXT, ethereum_address TEXT, " "ethereum_signature TEXT, fetchai_signature TEXT, " "developer_handle TEXT, tweet TEXT)" ) def test_set_registered(self): """Test the set_registered method of the RegistrationDB class.""" # setup ethereum_address = "some_ethereum_address" ethereum_signature = "some_ethereum_signature" fetchai_signature = "some_fetchai_signature" developer_handle = "some_developer_handle" tweet = "some_tweet" # operation with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_registered( self.address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet, ) # after mock_exe.assert_any_call( "INSERT OR REPLACE INTO registered_table(address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet) values(?, ?, ?, ?, ?, ?)", ( self.address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet, ), ) def test_set_registered_developer_only(self): """Test the set_registered_developer_only method of the RegistrationDB class.""" # setup developer_handle = "some_developer_handle" # operation with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_registered_developer_only( self.address, developer_handle, ) # after mock_exe.assert_any_call( "INSERT OR REPLACE INTO registered_table(address, developer_handle) values(?, ?)", ( self.address, developer_handle, ), ) def test_is_registered_i(self): """Test the is_registered method of the RegistrationDB class where result is NOT empty.""" # setup result = [["1"], ["2", "3"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: is_registered = self.db.is_registered(self.address) # after mock_exe.assert_any_call( "SELECT * FROM registered_table WHERE address=?", (self.address,) ) assert is_registered def test_is_registered_ii(self): """Test the is_registered method of the RegistrationDB class where result is empty.""" # setup result = [] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: is_registered = self.db.is_registered(self.address) # after mock_exe.assert_any_call( "SELECT * FROM registered_table WHERE address=?", (self.address,) ) assert not is_registered def test_get_developer_handle_i(self): """Test the get_developer_handle method of the RegistrationDB class where there is 1 developer handle in the result.""" # setup developer_handle = "developer_handle_1" result = [[developer_handle], ["developer_handle_1"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: actual_developer_handle = self.db.get_developer_handle(self.address) # after mock_exe.assert_any_call( "SELECT developer_handle FROM registered_table WHERE address=?", (self.address,), ) assert actual_developer_handle == developer_handle def test_get_developer_handle_ii(self): """Test the get_developer_handle method of the RegistrationDB class where there is more than 1 developer handle in the result.""" # setup developer_handle = "developer_handle_1" another_developer_handle = "developer_handle_2" result = [[developer_handle, another_developer_handle], ["developer_handle_1"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: with pytest.raises( ValueError, match=f"More than one developer_handle found for address={self.address}.", ): self.db.get_developer_handle(self.address) # after mock_exe.assert_any_call( "SELECT developer_handle FROM registered_table WHERE address=?", (self.address,), ) def test_get_all_registered(self): """Test the get_all_registered method of the RegistrationDB class.""" # setup result = [["1", "2", "3"], ["4", "5", "6"]] expected_registered = ["1", "4"] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: actual_registered = self.db.get_all_registered() # after mock_exe.assert_any_call("SELECT address FROM registered_table", ()) assert expected_registered == actual_registered ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw1/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the confirmation aw1 skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import Terms from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.confirmation_aw1.registration_db import RegistrationDB from packages.fetchai.skills.confirmation_aw1.strategy import ( DEVELOPER_ONLY_REQUIRED_KEYS, PUBLIC_ID, REQUIRED_KEYS, Strategy, ) from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of confirmation aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.token_denomination = "atestfet" # nosec cls.token_dispense_amount = 100000 cls.fetchai_staking_contract_address = ( "0x351bac612b50e87b46e4b10a282f632d41397de2" ) cls.override_staking_check = False cls.awx_aeas = [] cls.strategy = Strategy( token_denomination=cls.token_denomination, token_dispense_amount=cls.token_dispense_amount, fetchai_staking_contract_address=cls.fetchai_staking_contract_address, override_staking_check=cls.override_staking_check, awx_aeas=cls.awx_aeas, name="strategy", skill_context=cls._skill.skill_context, ) cls.address = "some_address" cls.info = { "ethereum_address": "some_value", "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", "tweet": "some_tweet", } cls.logger = cls._skill.skill_context.logger cls.db = cast(RegistrationDB, cls._skill.skill_context.registration_db) def test__init__(self): """Test the __init__ of Strategy class.""" assert self.strategy._is_ready_to_register is False assert self.strategy._is_registered is False assert self.strategy.is_registration_pending is False assert self.strategy.signature_of_ethereum_address is None assert self.strategy._ledger_id == self.skill.skill_context.default_ledger_id assert self.strategy._max_tx_fee == 100 assert self.strategy._contract_ledger_id == "ethereum" assert self.strategy._contract_callable == "get_stake" assert self.strategy._contract_id == str(PUBLIC_ID) assert self.strategy._in_process_registrations == {} def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.contract_id == self.strategy._contract_id assert self.strategy.contract_address == self.fetchai_staking_contract_address assert self.strategy.contract_ledger_id == self.strategy._contract_ledger_id assert self.strategy.contract_callable == self.strategy._contract_callable assert self.strategy.awx_aeas == self.awx_aeas assert self.strategy.all_registered_aeas == [] def test_lock_registration_temporarily(self): """Test the lock_registration_temporarily method of the Strategy class.""" # before assert self.address not in self.strategy._in_process_registrations # operation self.strategy.lock_registration_temporarily(self.address, self.info) # after assert self.strategy._in_process_registrations[self.address] == self.info def test_finalize_registration_i(self): """Test the finalize_registration method of the Strategy class where NOT developer_only_mode.""" # setup self.strategy.developer_handle_only = False self.strategy.lock_registration_temporarily(self.address, self.info) # operation with patch.object(self.db, "set_registered") as mock_set: with patch.object(self.logger, "log") as mock_logger: self.strategy.finalize_registration(self.address) # after assert self.address not in self.strategy._in_process_registrations mock_logger.assert_any_call( logging.INFO, f"finalizing registration for address={self.address}, info={self.info}", ) mock_set.assert_any_call( address=self.address, ethereum_address=self.info["ethereum_address"], ethereum_signature=self.info["signature_of_ethereum_address"], fetchai_signature=self.info["signature_of_fetchai_address"], developer_handle=self.info["developer_handle"], tweet=self.info.get("tweet", ""), ) def test_finalize_registration_ii(self): """Test the finalize_registration method of the Strategy class where IS developer_only_mode.""" # setup self.strategy.developer_handle_only = True self.strategy.lock_registration_temporarily(self.address, self.info) # operation with patch.object(self.db, "set_registered_developer_only") as mock_set: with patch.object(self.logger, "log") as mock_logger: self.strategy.finalize_registration(self.address) # after assert self.address not in self.strategy._in_process_registrations mock_logger.assert_any_call( logging.INFO, f"finalizing registration for address={self.address}, info={self.info}", ) mock_set.assert_any_call( address=self.address, developer_handle=self.info["developer_handle"], ) def test_unlock_registration(self): """Test the unlock_registration method of the Strategy class.""" # setup self.strategy.lock_registration_temporarily(self.address, self.info) # before assert self.address in self.strategy._in_process_registrations # operation with patch.object(self.logger, "log") as mock_logger: self.strategy.unlock_registration(self.address) # after assert self.address not in self.strategy._in_process_registrations mock_logger.assert_any_call( logging.INFO, f"registration info did not pass staking checks = {self.info}", ) def test_get_developer_handle(self): """Test the get_developer_handle method of the Strategy class.""" # operation with patch.object(self.db, "get_developer_handle") as mock_get: self.strategy.get_developer_handle(self.address) # after mock_get.assert_any_call(self.address) def test_valid_registration_succeeds(self): """Test the valid_registration method of the Strategy class which succeeds.""" # setup registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": self.address, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", } # operation with patch.object( self.strategy, "_valid_signature", return_value=True ) as mock_valid: with patch.object(self.db, "is_registered", return_value=False) as mock_is: is_valid, code, msg = self.strategy.valid_registration( registration_info, self.address ) # after mock_is.assert_called_once() mock_valid.assert_called() assert is_valid assert code == 0 assert msg == "all good!" def test_valid_registration_fails_i(self): """Test the valid_registration method of the Strategy class which fails because some key is missing.""" # setup incorrect_registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": self.address, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", } # operation is_valid, code, msg = self.strategy.valid_registration( incorrect_registration_info, self.address ) # after assert not is_valid assert code == 1 assert msg == f"missing keys in registration info, required: {REQUIRED_KEYS}!" def test_valid_registration_fails_ii(self): """Test the valid_registration method of the Strategy class which fails because addresses do not match.""" # setup different_addres = "some_other_address" incorrect_registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": different_addres, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", } # operation is_valid, code, msg = self.strategy.valid_registration( incorrect_registration_info, self.address ) # after assert not is_valid assert code == 1 assert msg == "fetchai address of agent and registration info do not match!" def test_valid_registration_fails_iii(self): """Test the valid_registration method of the Strategy class which fails because _valid_signature returns False for first call.""" # setup incorrect_registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": self.address, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", } # operation with patch.object( self.strategy, "_valid_signature", return_value=False ) as mock_valid: is_valid, code, msg = self.strategy.valid_registration( incorrect_registration_info, self.address ) # after mock_valid.assert_called_once() assert not is_valid assert code == 1 assert msg == "fetchai address and signature do not match!" def test_valid_registration_fails_iv(self): """Test the valid_registration method of the Strategy class which fails because _valid_signature returns False for second call.""" # setup incorrect_registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": self.address, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", } # operation with patch.object( self.strategy, "_valid_signature", side_effect=[True, False] ) as mock_valid: is_valid, code, msg = self.strategy.valid_registration( incorrect_registration_info, self.address ) # after mock_valid.assert_called() assert not is_valid assert code == 1 assert msg == "ethereum address and signature do not match!" def test_valid_registration_fails_v(self): """Test the valid_registration method of the Strategy class which fails because no developer_handle was provided.""" # setup incorrect_registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": self.address, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "", } # operation is_valid, code, msg = self.strategy.valid_registration( incorrect_registration_info, self.address ) # after assert not is_valid assert code == 1 assert msg == "missing developer_handle!" def test_valid_registration_fails_vi(self): """Test the valid_registration method of the Strategy class which fails because agent registration is in progress.""" # setup registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": self.address, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", } self.strategy.lock_registration_temporarily(self.address, self.info) # operation is_valid, code, msg = self.strategy.valid_registration( registration_info, self.address ) # after assert not is_valid assert code == 1 assert msg == "registration in process for this address!" def test_valid_registration_fails_vii(self): """Test the valid_registration method of the Strategy class which fails because agent already registered.""" # setup registration_info = { "ethereum_address": "some_ethereum_address", "fetchai_address": self.address, "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", } # operation with patch.object(self.db, "is_registered", return_value=True) as mock_is: is_valid, code, msg = self.strategy.valid_registration( registration_info, self.address ) # after mock_is.assert_called_once() assert not is_valid assert code == 1 assert msg == "already registered!" def test_valid_registration_fails_developer_only_mode_i(self): """Test the valid_registration method of the Strategy class in developer_only_mode which fails because some key is missing.""" # setup self.strategy.developer_handle_only = True incorrect_registration_info = { "fetchai_address": self.address, } # operation is_valid, code, msg = self.strategy.valid_registration( incorrect_registration_info, self.address ) # after assert not is_valid assert code == 1 assert ( msg == f"missing keys in registration info, required: {DEVELOPER_ONLY_REQUIRED_KEYS}!" ) def test_valid_registration_fails_developer_only_mode_ii(self): """Test the valid_registration method of the Strategy class which fails because addresses do not match.""" # setup self.strategy.developer_handle_only = True different_addres = "some_other_address" incorrect_registration_info = { "fetchai_address": different_addres, "developer_handle": "some_developer_handle", } # operation is_valid, code, msg = self.strategy.valid_registration( incorrect_registration_info, self.address ) # after assert not is_valid assert code == 1 assert msg == "fetchai address of agent and registration info do not match!" def test__valid_signature_i(self): """Test the _valid_signature method of the Strategy class where result is True.""" # setup expected_signer = "some_expected_signer" signature = "some_signature" message_str = "some_message_str" ledger_id = "some_ledger_id" # operation with patch.object( LedgerApis, "recover_message", return_value=(expected_signer,) ) as mock_recover: is_valid = self.strategy._valid_signature( expected_signer, signature, message_str, ledger_id ) # after mock_recover.assert_called_once() assert is_valid def test__valid_signature_ii(self): """Test the _valid_signature method of the Strategy class where result is False.""" # setup expected_signer = "some_expected_signer" signature = "some_signature" message_str = "some_message_str" ledger_id = "some_ledger_id" # operation with patch.object( LedgerApis, "recover_message", return_value=("some_other_signer",) ) as mock_recover: is_valid = self.strategy._valid_signature( expected_signer, signature, message_str, ledger_id ) # after mock_recover.assert_called_once() assert not is_valid def test__valid_signature_iii(self): """Test the _valid_signature method of the Strategy class where an exception is raised.""" # setup expected_signer = "some_expected_signer" signature = "some_signature" message_str = "some_message_str" ledger_id = "some_ledger_id" exception_message = "some_exception_message" # operation with patch.object( LedgerApis, "recover_message", side_effect=Exception(exception_message) ) as mock_recover: with patch.object(self.logger, "log") as mock_logger: is_valid = self.strategy._valid_signature( expected_signer, signature, message_str, ledger_id ) # after mock_recover.assert_called_once() mock_logger.assert_any_call( logging.WARNING, f"Signing exception: {exception_message}", ) assert not is_valid def test_get_terms(self): """Test the get_terms method of the Strategy class.""" # setup counterparty = "some_counterparty" expected_terms = Terms( ledger_id=self.strategy._ledger_id, sender_address=self.skill.skill_context.agent_address, counterparty_address=counterparty, amount_by_currency_id={ self.token_denomination: -self.token_dispense_amount }, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce="some", fee_by_currency_id={self.token_denomination: self.strategy._max_tx_fee}, ) # operation actual_terms = self.strategy.get_terms(counterparty) # after assert actual_terms == expected_terms def test_get_kwargs(self): """Test the get_kwargs method of the Strategy class.""" # setup expected_kwargs = {"address": self.info["ethereum_address"]} # operation actual_kwargs = self.strategy.get_kwargs(self.info) # after assert actual_kwargs == expected_kwargs def test_has_staked_i(self): """Test the get_kwargs method of the Strategy class where _override_staking_check is False and stake value is greater than 0.""" # setup state = {"stake": "100"} # operation has_staked = self.strategy.has_staked(state) # after assert has_staked is True def test_has_staked_ii(self): """Test the get_kwargs method of the Strategy class where _override_staking_check is False and stake value is 0.""" # setup state = {"stake": "0"} # operation has_staked = self.strategy.has_staked(state) # after assert has_staked is False def test_has_staked_iii(self): """Test the get_kwargs method of the Strategy class where _override_staking_check is True.""" # setup self.strategy._override_staking_check = True state = {"stake": "100"} # operation has_staked = self.strategy.has_staked(state) # after assert has_staked is True ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw2/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/confirmation_aw2 dir.""" ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw2/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the simple_data_request skill.""" from pathlib import Path from aea.test_tools.test_skill import BaseSkillTestCase from tests.conftest import ROOT_DIR class ConfirmationAW2TestCase(BaseSkillTestCase): """Sets the confirmation_aw2 class up for testing (overrides the necessary config values so tests can be done on confirmation_aw2 skill).""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw2") @classmethod def setup(cls): """Setup the test class.""" cls.aw1_aea = "some_aw1_aea" config_overrides = {"models": {"strategy": {"args": {"aw1_aea": cls.aw1_aea}}}} super().setup(config_overrides=config_overrides) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw2/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the confirmation aw2 skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.crypto.ledger_apis import LedgerApis from aea.protocols.dialogue.base import DialogueMessage from packages.fetchai.protocols.default.dialogues import DefaultDialogue from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.skills.confirmation_aw2.dialogues import DefaultDialogues from packages.fetchai.skills.confirmation_aw2.handlers import DefaultHandler from packages.fetchai.skills.confirmation_aw2.strategy import Strategy from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw2.intermediate_class import ( ConfirmationAW2TestCase, ) class TestDefaultHandler(ConfirmationAW2TestCase): """Test default handler of confirmation aw2.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw2") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_handler = cast( DefaultHandler, cls._skill.skill_context.handlers.default_handler ) cls.logger = cls._skill.skill_context.logger cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.list_of_default_messages = ( DialogueMessage( DefaultMessage.Performative.BYTES, {"content": b"some_content"} ), ) cls.confirmed_aea = b"ConfirmedAEA" cls.developer_handle = b"DeveloperHandle" def test_setup(self): """Test the setup method of the default handler.""" assert self.default_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the register handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=DefaultMessage, dialogue_reference=incorrect_dialogue_reference, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) # operation with patch.object(self.logger, "log") as mock_logger: self.default_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid default message={incoming_message}, unidentified dialogue.", ) def test_handle_bytes_i(self): """Test the _handle_bytes method of the default handler where the sender IS aw1_aea.""" # setup incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=self.confirmed_aea + b"_" + self.developer_handle, sender=self.aw1_aea, ), ) # operation with patch.object(LedgerApis, "is_valid_address", return_value=True): with patch.object(self.logger, "log") as mock_logger: with patch.object( self.strategy, "register_counterparty" ) as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() mock_logger.assert_any_call( logging.INFO, f"adding confirmed_aea={self.confirmed_aea.decode('utf-8')} with developer_handle={self.developer_handle.decode('utf-8')} to db.", ) def test_handle_bytes_ii(self): """Test the _handle_bytes method of the default handler where the content is undecodable.""" # setup incorrect_content = "some_incorrect_content" incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=incorrect_content, sender=self.aw1_aea, ), ) # operation with patch.object(LedgerApis, "is_valid_address", return_value=True): with patch.object(self.logger, "log") as mock_logger: with patch.object( self.strategy, "register_counterparty" ) as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() mock_logger.assert_any_call( logging.WARNING, "received invalid developer_handle=." ) def test_handle_bytes_iii(self): """Test the _handle_bytes method of the default handler where is_valid_address is False.""" # setup incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=self.confirmed_aea + b"_" + self.developer_handle, sender=self.aw1_aea, ), ) # operation with patch.object(LedgerApis, "is_valid_address", return_value=False): with patch.object(self.logger, "log") as mock_logger: with patch.object( self.strategy, "register_counterparty" ) as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() default_dialogue = cast( DefaultDialogue, self.default_dialogues.get_dialogue(incoming_message) ) mock_logger.assert_any_call( logging.WARNING, f"received invalid address={self.confirmed_aea.decode('utf-8')} in dialogue={default_dialogue}.", ) def test_handle_bytes_iv(self): """Test the _handle_bytes method of the default handler where the sender is NOT aw1_aea.""" # setup incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=self.confirmed_aea + b"_" + self.developer_handle, sender="some_other_aea", ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(self.strategy, "register_counterparty") as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() default_dialogue = cast( DefaultDialogue, self.default_dialogues.get_dialogue(incoming_message) ) mock_logger.assert_any_call( logging.WARNING, f"cannot handle default message of performative={incoming_message.performative} in dialogue={default_dialogue}. Invalid sender={incoming_message.sender}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the default handler.""" # setup default_dialogue = cast( DefaultDialogue, self.prepare_skill_dialogue( dialogues=self.default_dialogues, messages=self.list_of_default_messages[:1], ), ) incoming_message = cast( RegisterMessage, self.build_incoming_message_for_skill_dialogue( dialogue=default_dialogue, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.DECODING_ERROR, error_msg="some_error_message", error_data={"some_key": b"some_value"}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.default_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle default message of performative={incoming_message.performative} in dialogue={default_dialogue}.", ) def test_teardown(self): """Test the teardown method of the default handler.""" assert self.default_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw2/test_registration_db.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the RegistrationDB class of the confirmation aw2 skill.""" import datetime import json import logging from pathlib import Path from unittest.mock import Mock, patch from packages.fetchai.skills.confirmation_aw2.registration_db import RegistrationDB from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw2.intermediate_class import ( ConfirmationAW2TestCase, ) class TestStrategy(ConfirmationAW2TestCase): """Test RegistrationDB of confirmation aw2.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw2") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.custom_path = None cls.db = RegistrationDB( custom_path=cls.custom_path, name="strategy", skill_context=cls._skill.skill_context, ) cls.address = "some_address" cls.logger = cls._skill.skill_context.logger cls.timestamp = datetime.datetime.now() cls.data = {"some_key_1": "some_value_1", "some_key_2": "some_value_2"} cls.developer_handle = "developer_handle" cls.first_trade = "2020-12-22 18:30:00.000000" cls.second_trade = "second_trade" cls.first_info = "first_info" cls.second_info = "second_info" def test__initialise_backend(self): """Test the _initialise_backend method of the RegistrationDB class.""" # operation with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db._initialise_backend() # after mock_exe.assert_any_call( "CREATE TABLE IF NOT EXISTS registered_table (address TEXT, ethereum_address TEXT, " "ethereum_signature TEXT, fetchai_signature TEXT, " "developer_handle TEXT, tweet TEXT)" ) mock_exe.assert_any_call( "CREATE TABLE IF NOT EXISTS trade_table (address TEXT PRIMARY KEY, first_trade timestamp, " "second_trade timestamp, first_info TEXT, second_info TEXT)" ) def test_set_trade_i(self): """Test the set_trade method of the RegistrationDB class where record IS None.""" # operation with patch.object( self.db, "get_trade_table", return_value=None ) as mock_get_trade_table: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_trade( self.address, self.timestamp, self.data, ) # after mock_get_trade_table.assert_called_once() mock_exe.assert_any_call( "INSERT INTO trade_table(address, first_trade, second_trade, first_info, second_info) values(?, ?, ?, ?, ?)", (self.address, self.timestamp, None, json.dumps(self.data), None), ) def test_set_trade_ii(self): """Test the set_trade method of the RegistrationDB class where record is NOT None.""" # setup record = ( self.address, self.first_trade, None, self.first_info, self.second_info, ) # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade_table: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_trade( self.address, self.timestamp, self.data, ) # after mock_get_trade_table.assert_called_once() mock_exe.assert_any_call( "INSERT or REPLACE into trade_table(address, first_trade, second_trade, first_info, second_info) values(?, ?, ?, ?, ?)", ( self.address, self.first_trade, self.timestamp, self.first_info, json.dumps(self.data), ), ) def test_set_trade_iii(self): """Test the set_trade method of the RegistrationDB class where record is NOT None and first_trade is None.""" # setup record = ( self.address, None, None, self.first_info, self.second_info, ) # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade_table: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_trade( self.address, self.timestamp, self.data, ) # after mock_get_trade_table.assert_called_once() mock_exe.assert_not_called() def test_get_trade_table(self): """Test the get_trade_table method of the RegistrationDB class.""" # setup trade_table = ("something_1", "something_2") result = [trade_table, ("something_3",)] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: actual_trade_table = self.db.get_trade_table(self.address) # after mock_exe.assert_any_call( "SELECT * FROM trade_table where address=?", (self.address,) ) assert actual_trade_table == trade_table def test_set_registered_i(self): """Test the set_registered method of the RegistrationDB class where is_registeredis is False.""" # operation with patch.object( self.db, "is_registered", return_value=False ) as mock_is_registered: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_registered( self.address, self.developer_handle, ) # after mock_is_registered.assert_called_once() mock_exe.assert_any_call( "INSERT OR REPLACE INTO registered_table(address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet) values(?, ?, ?, ?, ?, ?)", (self.address, "", "", "", self.developer_handle, ""), ) def test_set_registered_ii(self): """Test the set_registered method of the RegistrationDB class where is_registeredis is True.""" # operation with patch.object( self.db, "is_registered", return_value=True ) as mock_is_registered: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_registered( self.address, self.developer_handle, ) # after mock_is_registered.assert_called_once() mock_exe.assert_not_called() def test_is_registered_i(self): """Test the is_registered method of the RegistrationDB class where result is NOT empty.""" # setup result = [["1"], ["2", "3"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: is_registered = self.db.is_registered(self.address) # after mock_exe.assert_any_call( "SELECT * FROM registered_table WHERE address=?", (self.address,) ) assert is_registered def test_is_registered_ii(self): """Test the is_registered method of the RegistrationDB class where result is empty.""" # setup result = [] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: is_registered = self.db.is_registered(self.address) # after mock_exe.assert_any_call( "SELECT * FROM registered_table WHERE address=?", (self.address,) ) assert not is_registered def test_is_allowed_to_trade_i(self): """Test the is_allowed_to_trade method of the RegistrationDB class where record IS None.""" # setup mininum_hours_between_txs = 1 record = None # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: with patch.object(self.db, "_execute_single_sql") as mock_exe: is_registered = self.db.is_allowed_to_trade( self.address, mininum_hours_between_txs ) # after mock_get_trade.assert_called_once() mock_exe.assert_not_called() assert is_registered def test_is_allowed_to_trade_ii(self): """Test the is_allowed_to_trade method of the RegistrationDB class where first_trade and second_trade are NOT present.""" # setup minimum_hours_between_txs = 1 record = (self.address, None, None, self.first_info, self.second_info) # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: is_registered = self.db.is_allowed_to_trade( self.address, minimum_hours_between_txs ) # after mock_get_trade.assert_called_once() assert is_registered def test_is_allowed_to_trade_iii(self): """Test the is_allowed_to_trade method of the RegistrationDB class where is_allowed_to_trade_ is True.""" # setup minimum_hours_between_txs = 1 record = ( self.address, self.first_trade, None, self.first_info, self.second_info, ) mocked_now_greater_than_minimum = "2020-12-22 20:30:00.000000" datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = datetime.datetime.strptime( mocked_now_greater_than_minimum, "%Y-%m-%d %H:%M:%S.%f" ) # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: is_registered = self.db.is_allowed_to_trade( self.address, minimum_hours_between_txs ) # after mock_get_trade.assert_called_once() assert is_registered def test_is_allowed_to_trade_iv(self): """Test the is_allowed_to_trade method of the RegistrationDB class where is_allowed_to_trade_ is False.""" # setup minimum_hours_between_txs = 1 record = ( self.address, self.first_trade, None, self.first_info, self.second_info, ) mocked_now_less_than_minimum = "2020-12-22 18:31:00.000000" # operation datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = datetime.datetime.strptime( mocked_now_less_than_minimum, "%Y-%m-%d %H:%M:%S.%f" ) with patch("datetime.datetime", new=datetime_mock): with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: with patch.object(self.logger, "log") as mock_logger: is_registered = self.db.is_allowed_to_trade( self.address, minimum_hours_between_txs ) # after mock_get_trade.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"Invalid attempt for counterparty={self.address}, not enough time since last trade!", ) assert not is_registered def test_is_allowed_to_trade_v(self): """Test the is_allowed_to_trade method of the RegistrationDB class where second_trade IS present.""" # setup minimum_hours_between_txs = 1 record = ( self.address, self.first_trade, self.second_trade, self.first_info, self.second_info, ) mocked_now = "2020-12-22 18:31:00.000000" # operation datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = datetime.datetime.strptime( mocked_now, "%Y-%m-%d %H:%M:%S.%f" ) with patch("datetime.datetime", new=datetime_mock): with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: with patch.object(self.logger, "log") as mock_logger: is_registered = self.db.is_allowed_to_trade( self.address, minimum_hours_between_txs ) # after mock_get_trade.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"Invalid attempt for counterparty={self.address}, already completed 2 trades!", ) assert not is_registered def test_has_completed_two_trades_i(self): """Test the has_completed_two_trades method of the RegistrationDB class where first and second trade are present.""" # setup record = ( self.address, self.first_trade, self.second_trade, self.first_info, self.second_info, ) # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: has_completed = self.db.has_completed_two_trades(self.address) # after mock_get_trade.assert_called_once() assert has_completed def test_has_completed_two_trades_ii(self): """Test the has_completed_two_trades method of the RegistrationDB class where first and second trade are NOT present.""" # setup record = (self.address, None, None, self.first_info, self.second_info) # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: has_completed = self.db.has_completed_two_trades(self.address) # after mock_get_trade.assert_called_once() assert not has_completed def test_has_completed_two_trades_iii(self): """Test the has_completed_two_trades method of the RegistrationDB class where first trade is NOT and second trade IS present.""" # setup record = ( self.address, None, self.second_trade, self.first_info, self.second_info, ) # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: has_completed = self.db.has_completed_two_trades(self.address) # after mock_get_trade.assert_called_once() assert not has_completed def test_has_completed_two_trades_iv(self): """Test the has_completed_two_trades method of the RegistrationDB class where first trade IS and second trade is NOT present.""" # setup record = ( self.address, self.first_trade, None, self.first_info, self.second_info, ) # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: has_completed = self.db.has_completed_two_trades(self.address) # after mock_get_trade.assert_called_once() assert not has_completed def test_has_completed_two_trades_v(self): """Test the has_completed_two_trades method of the RegistrationDB class where record IS None.""" # setup record = None # operation with patch.object( self.db, "get_trade_table", return_value=record ) as mock_get_trade: has_completed = self.db.has_completed_two_trades(self.address) # after mock_get_trade.assert_called_once() assert not has_completed def test_completed_two_trades(self): """Test the completed_two_trades method of the RegistrationDB class.""" # setup row_1 = ( "address_1", "ethereum_address_1", "something2_1", "something3_1", "developer_handle_1", ) row_2 = ( "address_2", "ethereum_address_2", "something2_2", "something3_2", "developer_handle_2", ) row_3 = ( "address_3", "ethereum_address_3", "something2_3", "something3_3", "developer_handle_3", ) result = [row_1, row_2, row_3] has_completed = [True, False, True] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: with patch.object( self.db, "has_completed_two_trades", side_effect=has_completed ): actual_completed = self.db.completed_two_trades() # after mock_exe.assert_any_call("SELECT * FROM registered_table", ()) assert actual_completed == [ ("address_1", "ethereum_address_1", "developer_handle_1"), ("address_3", "ethereum_address_3", "developer_handle_3"), ] ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw2/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the confirmation aw2 skill.""" import datetime import logging from pathlib import Path from typing import cast from unittest.mock import Mock, patch import pytest from packages.fetchai.skills.confirmation_aw2.registration_db import RegistrationDB from packages.fetchai.skills.confirmation_aw2.strategy import Strategy from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw2.intermediate_class import ( ConfirmationAW2TestCase, ) class TestStrategy(ConfirmationAW2TestCase): """Test Strategy of confirmation aw2.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw2") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.minimum_hours_between_txs = 4 cls.minimum_minutes_since_last_attempt = 2 cls.strategy = Strategy( aw1_aea="some_aw1_aea", mininum_hours_between_txs=cls.minimum_hours_between_txs, minimum_minutes_since_last_attempt=cls.minimum_minutes_since_last_attempt, name="strategy", skill_context=cls._skill.skill_context, ) cls.address = "some_address" cls.info = { "ethereum_address": "some_value", "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", "tweet": "some_tweet", } cls.logger = cls._skill.skill_context.logger cls.db = cast(RegistrationDB, cls._skill.skill_context.registration_db) cls.counterparty = "couterparty_1" def test__init__i(self): """Test the __init__ of Strategy class.""" assert self.strategy.aw1_aea == self.aw1_aea assert self.strategy.minimum_hours_between_txs == self.minimum_hours_between_txs assert ( self.strategy.minimum_minutes_since_last_attempt == self.minimum_minutes_since_last_attempt ) def test__init__ii(self): """Test the __init__ of Strategy class where aw1_aea is None.""" with pytest.raises(ValueError, match="aw1_aea must be provided!"): Strategy( aw1_aea=None, mininum_hours_between_txs=self.minimum_hours_between_txs, minimum_minutes_since_last_attempt=self.minimum_minutes_since_last_attempt, name="strategy", skill_context=self.skill.skill_context, ) def test_get_acceptable_counterparties(self): """Test the get_acceptable_counterparties method of the Strategy class.""" # setup couterparties = ("couterparty_1", "couterparty_2", "couterparty_3") is_valid_counterparty = [True, False, True] # operation with patch.object( self.strategy, "is_valid_counterparty", side_effect=is_valid_counterparty ): actual_acceptable_counterparties = ( self.strategy.get_acceptable_counterparties(couterparties) ) # after assert actual_acceptable_counterparties == ("couterparty_1", "couterparty_3") def test_is_enough_time_since_last_attempt_i(self): """Test the is_enough_time_since_last_attempt method of the Strategy class where now IS greater than last attempt + min minutes.""" # setup counterparty_last_attempt_time_str = "2020-12-22 20:30:00.000000" counterparty_last_attempt_time = datetime.datetime.strptime( counterparty_last_attempt_time_str, "%Y-%m-%d %H:%M:%S.%f" ) mocked_now_greater_than_last_plus_minimum = "2020-12-22 20:33:00.000000" datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = datetime.datetime.strptime( mocked_now_greater_than_last_plus_minimum, "%Y-%m-%d %H:%M:%S.%f" ) self.strategy.last_attempt = {self.counterparty: counterparty_last_attempt_time} # operation with patch("datetime.datetime", new=datetime_mock): is_enough_time = self.strategy.is_enough_time_since_last_attempt( self.counterparty ) # after assert is_enough_time is True def test_is_enough_time_since_last_attempt_ii(self): """Test the is_enough_time_since_last_attempt method of the Strategy class where now is NOT greater than last attempt + min minutes.""" # setup counterparty_last_attempt_time_str = "2020-12-22 20:30:00.000000" counterparty_last_attempt_time = datetime.datetime.strptime( counterparty_last_attempt_time_str, "%Y-%m-%d %H:%M:%S.%f" ) mocked_now_less_than_last_plus_minimum = "2020-12-22 20:31:00.000000" datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = datetime.datetime.strptime( mocked_now_less_than_last_plus_minimum, "%Y-%m-%d %H:%M:%S.%f" ) self.strategy.last_attempt = {self.counterparty: counterparty_last_attempt_time} # operation with patch("datetime.datetime", new=datetime_mock): is_enough_time = self.strategy.is_enough_time_since_last_attempt( self.counterparty ) # after assert is_enough_time is False def test_is_enough_time_since_last_attempt_iii(self): """Test the is_enough_time_since_last_attempt method of the Strategy class where now counterparty is NOT in last_attempt.""" # setup self.strategy.last_attempt = {} # operation is_enough_time = self.strategy.is_enough_time_since_last_attempt( self.counterparty ) # after assert is_enough_time is True def test_is_valid_counterparty_i(self): """Test the is_valid_counterparty method of the Strategy class where is_registered is False.""" # operation with patch.object(self.db, "is_registered", return_value=False): with patch.object(self.logger, "log") as mock_logger: is_valid = self.strategy.is_valid_counterparty(self.counterparty) # after mock_logger.assert_any_call( logging.INFO, f"Invalid counterparty={self.counterparty}, not registered!", ) assert is_valid is False def test_is_valid_counterparty_ii(self): """Test the is_valid_counterparty method of the Strategy class where is_enough_time_since_last_attempt is False.""" # operation with patch.object(self.db, "is_registered", return_value=True): with patch.object( self.strategy, "is_enough_time_since_last_attempt", return_value=False ): with patch.object(self.logger, "log") as mock_logger: is_valid = self.strategy.is_valid_counterparty(self.counterparty) # after mock_logger.assert_any_call( logging.DEBUG, f"Not enough time since last attempt for counterparty={self.counterparty}!", ) assert is_valid is False def test_is_valid_counterparty_iii(self): """Test the is_valid_counterparty method of the Strategy class where is_allowed_to_trade is False.""" # operation with patch.object(self.db, "is_registered", return_value=True): with patch.object( self.strategy, "is_enough_time_since_last_attempt", return_value=True ): with patch.object(self.db, "is_allowed_to_trade", return_value=False): is_valid = self.strategy.is_valid_counterparty(self.counterparty) # after assert is_valid is False def test_is_valid_counterparty_iv(self): """Test the is_valid_counterparty method of the Strategy class where it succeeds.""" # operation with patch.object(self.db, "is_registered", return_value=True): with patch.object( self.strategy, "is_enough_time_since_last_attempt", return_value=True ): with patch.object(self.db, "is_allowed_to_trade", return_value=True): is_valid = self.strategy.is_valid_counterparty(self.counterparty) # after assert is_valid is True def test_successful_trade_with_counterparty(self): """Test the successful_trade_with_counterparty method of the Strategy class.""" # setup data = {"some_key_1": "some_value_1", "some_key_2": "some_value_2"} mocked_now_str = "2020-12-22 20:33:00.000000" mock_now = datetime.datetime.strptime(mocked_now_str, "%Y-%m-%d %H:%M:%S.%f") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mock_now # operation with patch.object(self.db, "set_trade") as mock_set_trade: with patch("datetime.datetime", new=datetime_mock): with patch.object(self.logger, "log") as mock_logger: self.strategy.successful_trade_with_counterparty( self.counterparty, data ) # after mock_set_trade.assert_any_call(self.counterparty, mock_now, data) mock_logger.assert_any_call( logging.INFO, f"Successful trade with={self.counterparty}. Data acquired={data}!", ) def test_register_counterparty(self): """Test the register_counterparty method of the Strategy class.""" # setup developer_handle = "some_developer_handle" # operation with patch.object(self.db, "set_registered") as mock_set_registered: self.strategy.register_counterparty(self.counterparty, developer_handle) # after mock_set_registered.assert_any_call(self.counterparty, developer_handle) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw3/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/confirmation_aw3 dir.""" ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw3/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the simple_data_request skill.""" from pathlib import Path from aea.test_tools.test_skill import BaseSkillTestCase from tests.conftest import ROOT_DIR class ConfirmationAW3TestCase(BaseSkillTestCase): """Sets the confirmation_aw3 class up for testing (overrides the necessary config values so tests can be done on confirmation_aw3 skill).""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw3") @classmethod def setup(cls): """Setup the test class.""" cls.aw1_aea = "some_aw1_aea" cls.leaderboard_url = "some_leaderboard_url" cls.leaderboard_token = "some_leaderboard_token" # nosec config_overrides = { "models": { "strategy": { "args": { "aw1_aea": cls.aw1_aea, "leaderboard_url": cls.leaderboard_url, "leaderboard_token": cls.leaderboard_token, } } } } super().setup(config_overrides=config_overrides) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw3/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the confirmation aw3 skill.""" from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Query, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.confirmation_aw3.behaviours import SearchBehaviour from packages.fetchai.skills.confirmation_aw3.strategy import Strategy from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw3.intermediate_class import ( ConfirmationAW3TestCase, ) class TestSearchBehaviour(ConfirmationAW3TestCase): """Test search behaviour of confirmation aw3.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw3") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.search_behaviour = cast( SearchBehaviour, cls._skill.skill_context.behaviours.search ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) def test_act(self): """Test the act method of the transaction behaviour.""" # setup self.strategy.is_searching = True mock_query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( "some_name", [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) # operation with patch.object(self.strategy, "update_search_query_params") as mock_update: with patch.object( self.strategy, "get_location_and_service_query", return_value=mock_query ): self.search_behaviour.act() # after self.assert_quantity_in_outbox(1) mock_update.assert_called_once() has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), query=mock_query, ) assert has_attributes, error_str ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw3/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the generic buyer skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.confirmation_aw3.dialogues import ( HttpDialogue, HttpDialogues, ) from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw3.intermediate_class import ( ConfirmationAW3TestCase, ) class TestDialogues(ConfirmationAW3TestCase): """Test dialogue classes of confirmation aw3.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw3") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) def test_http_dialogues(self): """Test the HttpDialogues class.""" _, dialogue = self.http_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=HttpMessage.Performative.REQUEST, method="some_method", url="some_url", version="some_version", headers="some_headers", body=b"some_body", ) assert dialogue.role == HttpDialogue.Role.CLIENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw3/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the confirmation aw3 skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.crypto.ledger_apis import LedgerApis from aea.protocols.dialogue.base import DialogueMessage from packages.fetchai.protocols.default.dialogues import DefaultDialogue from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.confirmation_aw3.dialogues import ( DefaultDialogues, HttpDialogue, HttpDialogues, ) from packages.fetchai.skills.confirmation_aw3.handlers import ( DefaultHandler, HttpHandler, ) from packages.fetchai.skills.confirmation_aw3.strategy import Strategy from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw3.intermediate_class import ( ConfirmationAW3TestCase, ) class TestDefaultHandler(ConfirmationAW3TestCase): """Test default handler of confirmation aw3.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw3") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_handler = cast( DefaultHandler, cls._skill.skill_context.handlers.default_handler ) cls.logger = cls._skill.skill_context.logger cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.list_of_default_messages = ( DialogueMessage( DefaultMessage.Performative.BYTES, {"content": b"some_content"} ), ) cls.confirmed_aea = b"ConfirmedAEA" cls.developer_handle = b"DeveloperHandle" def test_setup(self): """Test the setup method of the default handler.""" assert self.default_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the register handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=DefaultMessage, dialogue_reference=incorrect_dialogue_reference, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) # operation with patch.object(self.logger, "log") as mock_logger: self.default_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid default message={incoming_message}, unidentified dialogue.", ) def test_handle_bytes_i(self): """Test the _handle_bytes method of the default handler where the sender IS aw1_aea.""" # setup incoming_message = cast( DefaultMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=self.confirmed_aea + b"_" + self.developer_handle, sender=self.aw1_aea, ), ) # operation with patch.object(LedgerApis, "is_valid_address", return_value=True): with patch.object(self.logger, "log") as mock_logger: with patch.object( self.strategy, "register_counterparty" ) as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() mock_logger.assert_any_call( logging.INFO, f"adding confirmed_aea={self.confirmed_aea.decode('utf-8')} with developer_handle={self.developer_handle.decode('utf-8')} to db.", ) def test_handle_bytes_ii(self): """Test the _handle_bytes method of the default handler where the content is undecodable.""" # setup incorrect_content = "some_incorrect_content" incoming_message = cast( DefaultMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=incorrect_content, sender=self.aw1_aea, ), ) # operation with patch.object(LedgerApis, "is_valid_address", return_value=True): with patch.object(self.logger, "log") as mock_logger: with patch.object( self.strategy, "register_counterparty" ) as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() mock_logger.assert_any_call( logging.WARNING, "received invalid developer_handle=." ) def test_handle_bytes_iii(self): """Test the _handle_bytes method of the default handler where is_valid_address is False.""" # setup incoming_message = cast( DefaultMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=self.confirmed_aea + b"_" + self.developer_handle, sender=self.aw1_aea, ), ) # operation with patch.object(LedgerApis, "is_valid_address", return_value=False): with patch.object(self.logger, "log") as mock_logger: with patch.object( self.strategy, "register_counterparty" ) as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() default_dialogue = cast( DefaultDialogue, self.default_dialogues.get_dialogue(incoming_message) ) mock_logger.assert_any_call( logging.WARNING, f"received invalid address={self.confirmed_aea.decode('utf-8')} in dialogue={default_dialogue}.", ) def test_handle_bytes_iv(self): """Test the _handle_bytes method of the default handler where the sender is NOT aw1_aea.""" # setup incoming_message = cast( DefaultMessage, self.build_incoming_message( message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, content=self.confirmed_aea + b"_" + self.developer_handle, sender="some_other_aea", ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(self.strategy, "register_counterparty") as mock_register: self.default_handler.handle(incoming_message) # after mock_register.called_once() default_dialogue = cast( DefaultDialogue, self.default_dialogues.get_dialogue(incoming_message) ) mock_logger.assert_any_call( logging.WARNING, f"cannot handle default message of performative={incoming_message.performative} in dialogue={default_dialogue}. Invalid sender={incoming_message.sender}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the default handler.""" # setup default_dialogue = cast( DefaultDialogue, self.prepare_skill_dialogue( dialogues=self.default_dialogues, messages=self.list_of_default_messages[:1], ), ) incoming_message = cast( DefaultMessage, self.build_incoming_message_for_skill_dialogue( dialogue=default_dialogue, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.DECODING_ERROR, error_msg="some_error_message", error_data={"some_key": b"some_value"}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.default_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle default message of performative={incoming_message.performative} in dialogue={default_dialogue}.", ) def test_teardown(self): """Test the teardown method of the default handler.""" assert self.default_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestHttpHandler(ConfirmationAW3TestCase): """Test http handler of confirmation aw3.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw3") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.http_handler = cast( HttpHandler, cls._skill.skill_context.handlers.http_handler ) cls.logger = cls._skill.skill_context.logger cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) cls.method = "some_method" cls.url = "some_url" cls.version = "some_version" cls.headers = "some_headers" cls.body = b"some_body" cls.list_of_http_messages = ( DialogueMessage( HttpMessage.Performative.REQUEST, { "method": cls.method, "url": cls.url, "version": cls.version, "headers": cls.headers, "body": cls.body, }, ), ) cls.confirmed_aea = b"ConfirmedAEA" cls.developer_handle = b"DeveloperHandle" def test_setup(self): """Test the setup method of the http handler.""" assert self.http_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the register handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=HttpMessage, dialogue_reference=incorrect_dialogue_reference, performative=HttpMessage.Performative.RESPONSE, version=self.version, status_code=200, status_text="some_status_text", headers=self.headers, body=self.body, ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid http message={incoming_message}, unidentified dialogue.", ) def test__handle_response(self): """Test the _handle_bytes method of the http handler where the sender IS aw1_aea.""" # setup status_code = 200 status_text = "some_status_text" http_dialogue = cast( HttpDialogue, self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_http_messages[:1], ), ) incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, version=self.version, status_code=status_code, status_text=status_text, headers=self.headers, body=self.body, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received http response with status_code={}, status_text={} and body={!r} in dialogue={}".format( status_code, status_text, self.body, http_dialogue ), ) def test_handle_invalid(self): """Test the _handle_invalid method of the http handler.""" # setup incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method=self.method, url=self.url, version=self.version, headers=self.headers, body=self.body, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after http_dialogue = cast( HttpDialogue, self.http_dialogues.get_dialogue(incoming_message) ) mock_logger.assert_any_call( logging.WARNING, f"cannot handle http message of performative={incoming_message.performative} in dialogue={http_dialogue}.", ) def test_teardown(self): """Test the teardown method of the http handler.""" assert self.http_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw3/test_registration_db.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the RegistrationDB class of the confirmation aw3 skill.""" import datetime import json from pathlib import Path from unittest.mock import patch import pytest from packages.fetchai.skills.confirmation_aw3.registration_db import RegistrationDB from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw3.intermediate_class import ( ConfirmationAW3TestCase, ) class TestStrategy(ConfirmationAW3TestCase): """Test RegistrationDB of confirmation aw3.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw3") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.custom_path = None cls.db = RegistrationDB( custom_path=cls.custom_path, name="strategy", skill_context=cls._skill.skill_context, ) cls.address = "some_address" cls.timestamp = datetime.datetime.now() cls.data = {"some_key_1": "some_value_1", "some_key_2": "some_value_2"} cls.developer_handle = "developer_handle" def test_set_trade(self): """Test the set_trade method of the RegistrationDB class.""" # operation with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_trade( self.address, self.timestamp, self.data, ) # after mock_exe.assert_any_call( "INSERT INTO trades_table(address, created_at, data) values(?, ?, ?)", (self.address, self.timestamp, json.dumps(self.data)), ) def test_get_trade_count(self): """Test the get_trade_count method of the RegistrationDB class.""" # setup result = [["1", "2"], ["3", "4"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: actual_result = self.db.get_trade_count( self.address, ) # after mock_exe.assert_any_call( "SELECT COUNT(*) FROM trades_table where address=?", (self.address,), ) assert actual_result == 1 def test_get_developer_handle_i(self): """Test the get_developer_handle method of the RegistrationDB class which succeeds.""" # setup result = [["developer_handle"], ["something_1", "something_2"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: actual_result = self.db.get_developer_handle( self.address, ) # after mock_exe.assert_any_call( "SELECT developer_handle FROM registered_table where address=?", (self.address,), ) assert actual_result == "developer_handle" def test_get_developer_handle_ii(self): """Test the get_developer_handle method of the RegistrationDB class where the length of result[0] != 1.""" # setup result = [["developer_handle", "something_1"], ["something_2", "something_3"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: with pytest.raises( ValueError, match=f"More than one developer_handle found for address={self.address}.", ): self.db.get_developer_handle(self.address) # after mock_exe.assert_any_call( "SELECT developer_handle FROM registered_table where address=?", (self.address,), ) def test_get_addresses_i(self): """Test the get_addresses method of the RegistrationDB class where there are more than 0 addresses.""" # setup result = [["address_1", "something_1"], ["address_2", "something_2"]] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: actual_addresses = self.db.get_addresses( self.developer_handle, ) # after mock_exe.assert_any_call( "SELECT address FROM registered_table where developer_handle=?", (self.developer_handle,), ) assert actual_addresses == ["address_1", "address_2"] def test_get_addresses_ii(self): """Test the get_addresses method of the RegistrationDB class where there are 0 addresses.""" # setup result = [] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: with pytest.raises( ValueError, match=f"Should find at least one address for developer_handle={self.developer_handle}.", ): self.db.get_addresses( self.developer_handle, ) # after mock_exe.assert_any_call( "SELECT address FROM registered_table where developer_handle=?", (self.developer_handle,), ) def test_get_handle_and_trades(self): """Test the get_handle_and_trades method of the RegistrationDB class.""" # setup result = ["address_1", "address_2"] trade_counts = [2, 5] # operation with patch.object( self.db, "get_developer_handle", return_value=self.developer_handle ) as mock_developer_handle: with patch.object( self.db, "get_addresses", return_value=result ) as mock_addresses: with patch.object( self.db, "get_trade_count", side_effect=trade_counts ) as mock_trade_counts: actual_addresses = self.db.get_handle_and_trades( self.address, ) # after mock_developer_handle.assert_any_call(self.address) mock_addresses.assert_any_call(self.developer_handle) mock_trade_counts.assert_called() assert actual_addresses == (self.developer_handle, sum(trade_counts)) def test_get_all_addresses_and_handles(self): """Test the get_all_addresses_and_handles method of the RegistrationDB class.""" # setup result = [("address_1", "something_1"), ("address_2", "something_2")] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: actual_result = self.db.get_all_addresses_and_handles() # after mock_exe.assert_any_call( "SELECT address, developer_handle FROM registered_table", () ) assert actual_result == result def test_get_leaderboard_i(self): """Test the get_leaderboard method of the RegistrationDB class where none of the number of trades are 0.""" # setup addresses_and_handlers = [ ("address_1", "handler_1"), ("address_2", "handler_2"), ] trade_counts = [2, 5] expected_result = [("address_2", "handler_2", 5), ("address_1", "handler_1", 2)] # operation with patch.object( self.db, "get_all_addresses_and_handles", return_value=addresses_and_handlers, ) as mock_get_addresses: with patch.object( self.db, "get_trade_count", side_effect=trade_counts ) as mock_trade_counts: actual_result = self.db.get_leaderboard() # after mock_get_addresses.assert_called_once() mock_trade_counts.assert_called() assert actual_result == expected_result def test_get_leaderboard_ii(self): """Test the get_leaderboard method of the RegistrationDB class where some number of trades are 0.""" # setup addresses_and_handlers = [ ("address_1", "handler_1"), ("address_2", "handler_2"), ] trade_counts = [2, 0] expected_result = [("address_1", "handler_1", 2)] # operation with patch.object( self.db, "get_all_addresses_and_handles", return_value=addresses_and_handlers, ) as mock_get_addresses: with patch.object( self.db, "get_trade_count", side_effect=trade_counts ) as mock_trade_counts: actual_result = self.db.get_leaderboard() # after mock_get_addresses.assert_called_once() mock_trade_counts.assert_called() assert actual_result == expected_result def test_set_registered_i(self): """Test the set_registered method of the RegistrationDB class where is_registered is False.""" # operation with patch.object( self.db, "is_registered", return_value=False ) as mock_registered: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_registered(self.address, self.developer_handle) # after mock_registered.assert_any_call(self.address) mock_exe.assert_any_call( "INSERT OR REPLACE INTO registered_table(address, ethereum_address, ethereum_signature, fetchai_signature, developer_handle, tweet) values(?, ?, ?, ?, ?, ?)", (self.address, "", "", "", self.developer_handle, ""), ) def test_set_registered_ii(self): """Test the set_registered method of the RegistrationDB class where is_registered is True.""" # operation with patch.object( self.db, "is_registered", return_value=True ) as mock_registered: with patch.object(self.db, "_execute_single_sql") as mock_exe: self.db.set_registered(self.address, self.developer_handle) # after mock_registered.assert_any_call(self.address) mock_exe.assert_not_called() def test_is_registered_i(self): """Test the is_registered method of the RegistrationDB class where result's length is more than 0.""" # setup result = [("address_1", "something_1"), ("address_2", "something_2")] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: is_registered = self.db.is_registered(self.address) # after mock_exe.assert_any_call( "SELECT * FROM registered_table WHERE address=?", (self.address,), ) assert is_registered is True def test_is_registered_ii(self): """Test the is_registered method of the RegistrationDB class where result's length is 0.""" # setup result = [] # operation with patch.object( self.db, "_execute_single_sql", return_value=result ) as mock_exe: is_registered = self.db.is_registered(self.address) # after mock_exe.assert_any_call( "SELECT * FROM registered_table WHERE address=?", (self.address,), ) assert is_registered is False ================================================ FILE: tests/test_packages/test_skills/test_confirmation_aw3/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the confirmation aw3 skill.""" import datetime import json import logging from pathlib import Path from typing import cast from unittest.mock import Mock, patch import pytest from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.confirmation_aw3.registration_db import RegistrationDB from packages.fetchai.skills.confirmation_aw3.strategy import ( HTTP_CLIENT_PUBLIC_ID, Strategy, ) from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_confirmation_aw3.intermediate_class import ( ConfirmationAW3TestCase, ) class TestStrategy(ConfirmationAW3TestCase): """Test Strategy of confirmation aw3.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "confirmation_aw3") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.location_name = "berlin" cls.locations = { cls.location_name: {"latitude": 52.52, "longitude": 13.405}, } cls.search_query_type = "weather" cls.search_queries = { cls.search_query_type: { "constraint_type": "==", "search_key": "seller_service", "search_value": "weather_data", }, } cls.leaderboard_url = "some_url" cls.leaderboard_token = "some_token" # nosec cls.strategy = Strategy( aw1_aea="some_aw1_aea", locations=cls.locations, search_queries=cls.search_queries, leaderboard_url=cls.leaderboard_url, leaderboard_token=cls.leaderboard_token, name="strategy", skill_context=cls._skill.skill_context, ) cls.address = "some_address" cls.info = { "ethereum_address": "some_value", "signature_of_ethereum_address": "some_signature_of_ethereum_address", "signature_of_fetchai_address": "some_signature_of_fetchai_address", "developer_handle": "some_developer_handle", "tweet": "some_tweet", } cls.logger = cls._skill.skill_context.logger cls.db = cast(RegistrationDB, cls._skill.skill_context.registration_db) cls.counterparty = "couterparty_1" def test__init__i(self): """Test the __init__ of Strategy class.""" assert self.strategy.aw1_aea == self.aw1_aea assert self.strategy._locations == self.locations assert self.strategy._search_queries == self.search_queries assert self.strategy.leaderboard_url == f"{self.leaderboard_url}/insert" assert self.strategy.leaderboard_token == self.leaderboard_token def test__init__ii(self): """Test the __init__ of Strategy class where aw1_aea is None.""" with pytest.raises(ValueError, match="aw1_aea must be provided!"): Strategy( aw1_aea=None, locations=self.locations, search_queries=self.search_queries, leaderboard_url=self.leaderboard_url, leaderboard_token=self.leaderboard_token, name="strategy", skill_context=self.skill.skill_context, ) def test__init__iii(self): """Test the __init__ of Strategy class where length of locations is 0.""" with pytest.raises(ValueError, match="locations must have at least one entry"): Strategy( aw1_aea="some_aw1_aea", locations={}, search_queries=self.search_queries, leaderboard_url=self.leaderboard_url, leaderboard_token=self.leaderboard_token, name="strategy", skill_context=self.skill.skill_context, ) def test__init__iv(self): """Test the __init__ of Strategy class where length of search_queries is 0.""" with pytest.raises( ValueError, match="search_queries must have at least one entry" ): Strategy( aw1_aea="some_aw1_aea", locations=self.locations, search_queries={}, leaderboard_url=self.leaderboard_url, leaderboard_token=self.leaderboard_token, name="strategy", skill_context=self.skill.skill_context, ) def test__init__v(self): """Test the __init__ of Strategy class where leaderboard_url is None.""" with pytest.raises(ValueError, match="No leader board url provided!"): Strategy( aw1_aea="some_aw1_aea", locations=self.locations, search_queries=self.search_queries, leaderboard_url=None, leaderboard_token=self.leaderboard_token, name="strategy", skill_context=self.skill.skill_context, ) def test__init__vi(self): """Test the __init__ of Strategy class where leaderboard_token is None.""" with pytest.raises(ValueError, match="No leader board token provided!"): Strategy( aw1_aea="some_aw1_aea", locations=self.locations, search_queries=self.search_queries, leaderboard_url=self.leaderboard_url, leaderboard_token=None, name="strategy", skill_context=self.skill.skill_context, ) def test_get_acceptable_counterparties(self): """Test the get_acceptable_counterparties method of the Strategy class.""" # setup couterparties = ("couterparty_1", "couterparty_2", "couterparty_3") is_valid_counterparty = [True, False, True] # operation with patch.object( self.strategy, "is_valid_counterparty", side_effect=is_valid_counterparty ): actual_acceptable_counterparties = ( self.strategy.get_acceptable_counterparties(couterparties) ) # after assert actual_acceptable_counterparties == ("couterparty_1", "couterparty_3") def test_is_valid_counterparty_i(self): """Test the is_valid_counterparty method of the Strategy class where is_registered is False.""" # operation with patch.object( self.db, "is_registered", return_value=False ) as mock_is_regostered: with patch.object(self.logger, "log") as mock_logger: is_valid = self.strategy.is_valid_counterparty(self.counterparty) # after mock_is_regostered.assert_any_call(self.counterparty) mock_logger.assert_any_call( logging.INFO, f"Invalid counterparty={self.counterparty}, not registered!", ) assert is_valid is False def test_is_valid_counterparty_ii(self): """Test the is_valid_counterparty method of the Strategy class where is_registered is True.""" # operation with patch.object( self.db, "is_registered", return_value=True ) as mock_is_regostered: is_valid = self.strategy.is_valid_counterparty(self.counterparty) # after mock_is_regostered.assert_any_call(self.counterparty) assert is_valid is True def test_successful_trade_with_counterparty(self): """Test the successful_trade_with_counterparty method of the Strategy class.""" # setup data = {"some_key_1": "some_value_1", "some_key_2": "some_value_2"} developer_handle = "some_developer_handle" nb_trades = 5 mocked_now_str = "2020-12-22 20:33:00.000000" mock_now = datetime.datetime.strptime(mocked_now_str, "%Y-%m-%d %H:%M:%S.%f") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mock_now # operation with patch.object(self.db, "set_trade") as mock_set_trade: with patch("datetime.datetime", new=datetime_mock): with patch.object(self.logger, "log") as mock_logger: with patch.object( self.db, "get_handle_and_trades", return_value=(developer_handle, nb_trades), ) as mock_handle: self.strategy.successful_trade_with_counterparty( self.counterparty, data ) # after mock_set_trade.assert_any_call(self.counterparty, mock_now, data) mock_handle.assert_any_call(self.counterparty) mock_logger.assert_any_call( logging.INFO, f"Successful trade with={self.counterparty}.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, to=str(HTTP_CLIENT_PUBLIC_ID), sender=str(self.skill.skill_context.skill_id), method="POST", url=self.strategy.leaderboard_url, headers="Content-Type: application/json; charset=utf-8", version="", body=json.dumps( { "name": developer_handle, "points": nb_trades, "token": self.leaderboard_token, } ).encode("utf-8"), ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Notifying leaderboard: developer_handle={developer_handle}, nb_trades={nb_trades}.", ) def test_register_counterparty(self): """Test the register_counterparty method of the Strategy class.""" # setup developer_handle = "some_developer_handle" # operation with patch.object(self.db, "set_registered") as mock_set_registered: self.strategy.register_counterparty(self.counterparty, developer_handle) # after mock_set_registered.assert_any_call(self.counterparty, developer_handle) def test_update_search_query_params(self): """Test the update_search_query_params method of the Strategy class.""" # operation with patch.object(self.logger, "log") as mock_logger: self.strategy.update_search_query_params() # after mock_logger.assert_any_call( logging.INFO, f"New search_type={self.search_query_type} and location={self.location_name}.", ) ================================================ FILE: tests/test_packages/test_skills/test_erc1155_deploy/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/erc1155_deploy dir.""" ================================================ FILE: tests/test_packages/test_skills/test_erc1155_deploy/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module sets up test environment for erc1155_deploy skill.""" from pathlib import Path from typing import cast from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Location, Query, ) from aea.helpers.transaction.base import ( RawMessage, RawTransaction, SignedTransaction, Terms, TransactionDigest, TransactionReceipt, ) from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_deploy.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.erc1155_deploy.dialogues import ( ContractApiDialogues, DefaultDialogues, FipaDialogues, LedgerApiDialogues, OefSearchDialogues, SigningDialogues, ) from packages.fetchai.skills.erc1155_deploy.handlers import ( ContractApiHandler, FipaHandler, LedgerApiHandler, OefSearchHandler, SigningHandler, ) from packages.fetchai.skills.erc1155_deploy.strategy import Strategy from tests.conftest import ROOT_DIR class ERC1155DeployTestCase(BaseSkillTestCase): """Sets the erc1155_deploy class up for testing.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "erc1155_deploy") @classmethod def setup(cls): """Setup the test class.""" cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.mint_quantities = [100, 100, 100, 100, 100, 100, 100, 100, 100, 100] cls.service_data = {"key": "seller_service", "value": "some_value"} cls.personality_data = {"piece": "genus", "value": "some_personality"} cls.classification = {"piece": "classification", "value": "some_classification"} cls.from_supply = 756 cls.to_supply = 12 cls.value = 87 cls.token_type = 2 config_overrides = { "models": { "strategy": { "args": { "location": cls.location, "mint_quantities": cls.mint_quantities, "service_data": cls.service_data, "personality_data": cls.personality_data, "classification": cls.classification, "from_supply": cls.from_supply, "to_supply": cls.to_supply, "value": cls.value, "token_type": cls.token_type, } } }, } super().setup(config_overrides=config_overrides) # behaviours cls.registration_behaviour = cast( ServiceRegistrationBehaviour, cls._skill.skill_context.behaviours.service_registration, ) # dialogues cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) # handlers cls.fipa_handler = cast(FipaHandler, cls._skill.skill_context.handlers.fipa) cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.contract_api_handler = cast( ContractApiHandler, cls._skill.skill_context.handlers.contract_api ) cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) # models cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger # mocked objects cls.ledger_id = "some_ledger_id" cls.contract_id = "some_contract_id" cls.contract_address = "some_contract_address" cls.callable = "some_callable" cls.body_dict = {"some_key": "some_value"} cls.body_str = "some_body" cls.body_bytes = b"some_body" cls.kwargs = Kwargs(cls.body_dict) cls.address = "some_address" cls.mocked_terms = Terms( cls.ledger_id, cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.mocked_query = Query( [Constraint("some_attribute_name", ConstraintType("==", "some_value"))], DataModel( "some_data_model_name", [ Attribute( "some_attribute_name", str, False, "Some attribute descriptions.", ) ], ), ) cls.mocked_proposal = Description( { "contract_address": "some_contract_address", "token_id": "123456", "trade_nonce": "876438756348568", "from_supply": "543", "to_supply": "432", "value": "67", } ) cls.mocked_registration_description = Description({"foo1": 1, "bar1": 2}) cls.mocked_raw_tx = RawTransaction(cls.ledger_id, cls.body_dict) cls.mocked_raw_msg = RawMessage(cls.ledger_id, cls.body_bytes) cls.mocked_tx_digest = TransactionDigest(cls.ledger_id, cls.body_str) cls.mocked_signed_tx = SignedTransaction(cls.ledger_id, cls.body_dict) cls.mocked_tx_receipt = TransactionReceipt( cls.ledger_id, {"receipt_key": "receipt_value", "contractAddress": cls.contract_address}, {"transaction_key": "transaction_value"}, ) cls.registration_message = OefSearchMessage( dialogue_reference=("", ""), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=cls.mocked_registration_description, ) cls.registration_message.sender = str(cls._skill.skill_context.skill_id) cls.registration_message.to = cls._skill.skill_context.search_service_address # list of messages cls.list_of_fipa_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": cls.mocked_query}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.mocked_proposal} ), ) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_RAW_TRANSACTION, { "ledger_id": cls.ledger_id, "contract_id": cls.contract_id, "contract_address": cls.contract_address, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, {"terms": cls.mocked_terms, "raw_transaction": cls.mocked_raw_tx}, ), ) cls.list_of_ledger_api_balance_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": cls.ledger_id, "address": "some_address"}, ), ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {"terms": cls.mocked_terms}, ), DialogueMessage( LedgerApiMessage.Performative.RAW_TRANSACTION, {"raw_transaction": cls.mocked_raw_tx}, ), DialogueMessage( LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {"signed_transaction": cls.mocked_signed_tx}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_DIGEST, {"transaction_digest": cls.mocked_tx_digest}, ), DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, {"transaction_digest": cls.mocked_tx_digest}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_RECEIPT, {"transaction_receipt": cls.mocked_tx_receipt}, ), ) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) ================================================ FILE: tests/test_packages/test_skills/test_erc1155_deploy/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the erc1155_deploy skill.""" import logging from typing import cast from unittest.mock import patch from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.erc1155_deploy.behaviours import LEDGER_API_ADDRESS from packages.fetchai.skills.erc1155_deploy.dialogues import ContractApiDialogue from tests.test_packages.test_skills.test_erc1155_deploy.intermediate_class import ( ERC1155DeployTestCase, ) class TestServiceRegistrationBehaviour(ERC1155DeployTestCase): """Test registration behaviour of erc1155_deploy.""" def test_init(self): """Test the __init__ method of the registration behaviour.""" assert self.registration_behaviour.is_registered is False assert self.registration_behaviour.registration_in_progress is False assert self.registration_behaviour.failed_registration_msg is None assert self.registration_behaviour._nb_retries == 0 def test_setup(self): """Test the setup method of the registration behaviour.""" # setup self.strategy._is_contract_deployed = False # before assert self.strategy.is_behaviour_active is True # operation with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.setup() # after self.assert_quantity_in_outbox(2) # _request_balance has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.strategy.ledger_id, address=cast( str, self.skill.skill_context.agent_addresses.get(self.strategy.ledger_id), ), ) assert has_attributes, error_str # _request_contract_deploy_transaction assert self.strategy.is_behaviour_active is False has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.strategy.ledger_id, contract_id=self.strategy.contract_id, callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.skill.skill_context.agent_address, "gas": self.strategy.gas, } ), ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "requesting contract deployment transaction..." ) def test_act_i(self): """Test the act method of the registration behaviour where failed_registration_msg is NOT None.""" # setup self.registration_behaviour.failed_registration_msg = self.registration_message with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.act() # after self.assert_quantity_in_outbox(1) # _retry_failed_registration has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=type(self.registration_message), performative=self.registration_message.performative, to=self.registration_message.to, sender=str(self.skill.skill_context.skill_id), service_description=self.registration_message.service_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Retrying registration on SOEF. Retry {self.registration_behaviour._nb_retries} out of {self.registration_behaviour._max_soef_registration_retries}.", ) assert self.registration_behaviour.failed_registration_msg is None def test_act_ii(self): """Test the act method of the registration behaviour where failed_registration_msg is NOT None and max retries is reached.""" # setup self.registration_behaviour.failed_registration_msg = self.registration_message self.registration_behaviour._max_soef_registration_retries = 2 self.registration_behaviour._nb_retries = 2 self.registration_behaviour.act() # after self.assert_quantity_in_outbox(0) assert self.skill.skill_context.is_active is False def test_act_iii(self): """Test the act method of the registration behaviour where is_behaviour_active IS False.""" # setup self.strategy.is_behaviour_active = False # operation self.registration_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_act_iv(self): """Test the act method of the registration behaviour where is_contract_deployed IS True and is_tokens_created IS False.""" # setup self.strategy.is_contract_deployed = True self.strategy._is_tokens_created = False self.strategy._contract_address = self.contract_address # operation with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.act() # after self.assert_quantity_in_outbox(1) assert self.strategy.is_behaviour_active is False # _request_token_create_transaction message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.strategy.ledger_id, contract_id=self.strategy.contract_id, contract_address=self.strategy.contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.skill.skill_context.agent_address, "token_ids": self.strategy.token_ids, "gas": self.strategy.gas, } ), ) assert has_attributes, error_str contract_api_dialogue = cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ) assert contract_api_dialogue.terms == self.strategy.get_create_token_terms() mock_logger.assert_any_call( logging.INFO, "requesting create batch transaction..." ) def test_act_v(self): """Test the act method of the registration behaviour where is_contract_deployed IS True, is_tokens_created IS True and is_tokens_minted is False.""" # setup self.strategy.is_contract_deployed = True self.strategy._is_tokens_created = True self.strategy._is_tokens_minted = False self.strategy._contract_address = self.contract_address # operation with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.act() # after self.assert_quantity_in_outbox(1) assert self.strategy.is_behaviour_active is False # _request_token_mint_transaction message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.strategy.ledger_id, contract_id=self.strategy.contract_id, contract_address=self.strategy.contract_address, callable="get_mint_batch_transaction", kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.skill.skill_context.agent_address, "recipient_address": self.skill.skill_context.agent_address, "token_ids": self.strategy.token_ids, "mint_quantities": self.strategy.mint_quantities, "gas": self.strategy.gas, } ), ) assert has_attributes, error_str contract_api_dialogue = cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ) assert contract_api_dialogue.terms == self.strategy.get_mint_token_terms() mock_logger.assert_any_call( logging.INFO, "requesting mint batch transaction..." ) def test_act_vi(self): """Test the act method of the registration behaviour where is_contract_deployed IS True, is_tokens_created IS True and is_tokens_minted is True and is_registered IS False.""" # setup self.strategy.is_contract_deployed = True self.strategy._is_tokens_created = True self.strategy._is_tokens_minted = True self.strategy._contract_address = self.contract_address # before assert self.registration_behaviour.registration_in_progress is False # operation with patch.object( self.strategy, "get_location_description", return_value=self.mocked_registration_description, ) as mock_desc: with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.act() # after self.assert_quantity_in_outbox(1) assert self.registration_behaviour.registration_in_progress is True # _register_agent mock_desc.assert_called_once() has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") def test_register_service(self): """Test the register_service method of the registration behaviour.""" # operation with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.register_service() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's service on the SOEF." ) def test_register_genus(self): """Test the register_genus method of the registration behaviour.""" # operation with patch.object( self.strategy, "get_register_personality_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.register_genus() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality genus on the SOEF." ) def test_register_classification(self): """Test the register_classification method of the registration behaviour.""" # operation with patch.object( self.strategy, "get_register_classification_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.register_classification() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality classification on the SOEF." ) def test_teardown(self): """Test the teardown method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_unregister_service_description", return_value=self.mocked_registration_description, ): with patch.object( self.strategy, "get_location_description", return_value=self.mocked_registration_description, ): with patch.object(self.logger, "log") as mock_logger: self.registration_behaviour.teardown() # after self.assert_quantity_in_outbox(2) # _unregister_service has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering service from SOEF.") # _unregister_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_registration_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering agent from SOEF.") ================================================ FILE: tests/test_packages/test_skills/test_erc1155_deploy/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the erc1155_deploy skill.""" import pytest from aea.exceptions import AEAEnforceError from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_deploy.dialogues import ( ContractApiDialogue, DefaultDialogue, FipaDialogue, LedgerApiDialogue, OefSearchDialogue, SigningDialogue, ) from tests.test_packages.test_skills.test_erc1155_deploy.intermediate_class import ( ERC1155DeployTestCase, ) class TestDialogues(ERC1155DeployTestCase): """Test dialogue classes of erc1155_deploy.""" def test_contract_api_dialogue(self): """Test the ContractApiDialogue class.""" contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # terms with pytest.raises(ValueError, match="Terms not set!"): assert contract_api_dialogue.terms contract_api_dialogue.terms = self.mocked_terms with pytest.raises(AEAEnforceError, match="Terms already set!"): contract_api_dialogue.terms = self.mocked_terms assert contract_api_dialogue.terms == self.mocked_terms def test_contract_api_dialogues(self): """Test the ContractApiDialogues class.""" _, dialogue = self.contract_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=self.ledger_id, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ) assert dialogue.role == ContractApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_fipa_dialogue(self): """Test the FipaDialogue class.""" fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=FipaDialogue.Role.BUYER, ) # proposal with pytest.raises(ValueError, match="Proposal not set!"): assert fipa_dialogue.proposal fipa_dialogue.proposal = self.mocked_registration_description with pytest.raises(AEAEnforceError, match="Proposal already set!"): fipa_dialogue.proposal = self.mocked_registration_description assert fipa_dialogue.proposal == self.mocked_registration_description def test_fipa_dialogues(self): """Test the FipaDialogues class.""" _, dialogue = self.fipa_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=FipaMessage.Performative.CFP, query=self.mocked_query, ) assert dialogue.role == FipaDialogue.Role.SELLER assert dialogue.self_address == self.skill.skill_context.agent_address def test_ledger_api_dialogue(self): """Test the LedgerApiDialogue class.""" ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_signing_dialogue with pytest.raises(ValueError, match="Associated signing dialogue not set!"): assert ledger_api_dialogue.associated_signing_dialogue signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=SigningDialogue.Role.SKILL, ) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue with pytest.raises( AEAEnforceError, match="Associated signing dialogue already set!" ): ledger_api_dialogue.associated_signing_dialogue = signing_dialogue assert ledger_api_dialogue.associated_signing_dialogue == signing_dialogue def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=self.ledger_id, address=self.address, ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.mocked_query, ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_signing_dialogue(self): """Test the SigningDialogue class.""" signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_contract_api_dialogue with pytest.raises( ValueError, match="Associated contract api dialogue not set!" ): assert signing_dialogue.associated_contract_api_dialogue contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue with pytest.raises( AEAEnforceError, match="Associated contract api dialogue already set!" ): signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue assert ( signing_dialogue.associated_contract_api_dialogue == contract_api_dialogue ) def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms=self.mocked_terms, raw_transaction=self.mocked_raw_tx, ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_erc1155_deploy/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the erc1155_deploy skill.""" import logging from typing import cast from unittest.mock import patch from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import State from aea.protocols.dialogue.base import Dialogues from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_deploy.dialogues import ( ContractApiDialogue, FipaDialogue, LedgerApiDialogue, OefSearchDialogue, SigningDialogue, ) from packages.fetchai.skills.erc1155_deploy.handlers import LEDGER_API_ADDRESS from tests.test_packages.test_skills.test_erc1155_deploy.intermediate_class import ( ERC1155DeployTestCase, ) class TestFipaHandler(ERC1155DeployTestCase): """Test fipa handler of erc1155_deploy.""" def test_setup(self): """Test the setup method of the fipa handler.""" assert self.fipa_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the fipa handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( FipaMessage, self.build_incoming_message( message_type=FipaMessage, dialogue_reference=incorrect_dialogue_reference, performative=FipaMessage.Performative.ACCEPT, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"unidentified dialogue for message={incoming_message}.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_cfp_i(self): """Test the _handle_cfp method of the fipa handler where is_tokens_minted is True.""" # setup self.strategy._is_tokens_minted = True incoming_message = cast( FipaMessage, self.build_incoming_message( message_type=FipaMessage, performative=FipaMessage.Performative.CFP, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), query=self.mocked_query, ), ) # operation with patch.object( self.strategy, "get_proposal", return_value=self.mocked_proposal ) as mock_prop: with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received CFP from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_prop.assert_called_once() self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.PROPOSE, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, proposal=self.mocked_proposal, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending PROPOSE to agent={COUNTERPARTY_AGENT_ADDRESS[-5:]}: proposal={self.mocked_proposal.values}", ) def test_handle_cfp_ii(self): """Test the _handle_cfp method of the fipa handler where is_tokens_minted is False.""" # setup self.strategy._is_tokens_minted = False incoming_message = cast( FipaMessage, self.build_incoming_message( message_type=FipaMessage, performative=FipaMessage.Performative.CFP, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), query=self.mocked_query, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received CFP from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.INFO, "Contract items not minted yet. Try again later.", ) self.assert_quantity_in_outbox(0) def test_handle_accept_w_inform_i(self): """Test the _handle_accept_w_inform method of the fipa handler where tx_signature is NOT None.""" # setup tx_signature = "some_tx_signature" self.strategy.contract_address = self.contract_address fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:2], ), ) fipa_dialogue.proposal = self.mocked_proposal incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT_W_INFORM, info={"tx_signature": tx_signature}, ), ) # operation with patch.object( self.strategy, "get_single_swap_terms", return_value=self.mocked_terms ) as mock_swap: with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ACCEPT_W_INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}: tx_signature={tx_signature}", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.strategy.ledger_id, contract_id=self.strategy.contract_id, contract_address=self.strategy.contract_address, callable="get_atomic_swap_single_transaction", kwargs=ContractApiMessage.Kwargs( { "from_address": self.skill.skill_context.agent_address, "to_address": incoming_message.sender, "token_id": int(fipa_dialogue.proposal.values["token_id"]), "from_supply": int(fipa_dialogue.proposal.values["from_supply"]), "to_supply": int(fipa_dialogue.proposal.values["to_supply"]), "value": int(fipa_dialogue.proposal.values["value"]), "trade_nonce": int(fipa_dialogue.proposal.values["trade_nonce"]), "signature": tx_signature, } ), ) assert has_attributes, error_str mock_swap.assert_called_once() contract_api_dialogue = cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ) assert contract_api_dialogue.terms == self.mocked_terms mock_logger.assert_any_call( logging.INFO, "requesting single atomic swap transaction...", ) def test_handle_accept_w_inform_ii(self): """Test the _handle_accept_w_inform method of the fipa handler where tx_signature is NOT None.""" # setup tx_signature = "some_tx_signature" fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:2], ), ) fipa_dialogue.proposal = self.mocked_proposal incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT_W_INFORM, info={"something": tx_signature}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ACCEPT_W_INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]} with no signature.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the fipa handler.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:2], ), ) incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle fipa message of performative={incoming_message.performative} in dialogue={fipa_dialogue}.", ) def test_teardown(self): """Test the teardown method of the fipa handler.""" assert self.fipa_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestLedgerApiHandler(ERC1155DeployTestCase): """Test ledger_api handler of erc1155_deploy.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( LedgerApiMessage, self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=self.ledger_id, balance=10, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_balance(self): """Test the _handle_balance method of the ledger_api handler.""" # setup balance = 10 ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_balance_messages[:1], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=self.ledger_id, balance=balance, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {self.ledger_id} ledger={incoming_message.balance}.", ) def test_handle_transaction_digest(self): """Test the _handle_transaction_digest method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:3], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=self.mocked_tx_digest, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully submitted. Transaction digest={incoming_message.transaction_digest}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, to=incoming_message.sender, sender=str(self.skill.skill_context.skill_id), transaction_digest=self.mocked_tx_digest, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "requesting transaction receipt.", ) def test_handle_transaction_receipt_i(self): """Test the _handle_transaction_receipt method of the ledger_api handler where the transaction is NOT settled.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.mocked_tx_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=False): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, f"transaction failed. Transaction receipt={incoming_message.transaction_receipt}", ) def test_handle_transaction_receipt_ii(self): """Test the _handle_transaction_receipt method of the ledger_api handler where is_contract_deployed is False.""" # setup self.strategy._is_contract_deployed = False ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.mocked_tx_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={self.mocked_tx_receipt}", ) assert self.strategy.contract_address == self.contract_address assert self.strategy.is_contract_deployed is True assert self.strategy.is_behaviour_active is True def test_handle_transaction_receipt_iii(self): """Test the _handle_transaction_receipt method of the ledger_api handler where is_tokens_created is False.""" # setup self.strategy._is_contract_deployed = True self.strategy._is_tokens_created = False ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.mocked_tx_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={self.mocked_tx_receipt}", ) assert self.strategy.is_tokens_created is True assert self.strategy.is_behaviour_active is True def test_handle_transaction_receipt_iv(self): """Test the _handle_transaction_receipt method of the ledger_api handler where is_tokens_minted is False.""" # setup self.strategy._is_contract_deployed = True self.strategy._is_tokens_created = True self.strategy._is_tokens_minted = False ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.mocked_tx_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={self.mocked_tx_receipt}", ) assert self.strategy.is_tokens_minted is True assert self.strategy.is_behaviour_active is True def test_handle_transaction_receipt_v(self): """Test the _handle_transaction_receipt method of the ledger_api handler where is_tokens_minted is True.""" # setup self.strategy._is_contract_deployed = True self.strategy._is_tokens_created = True self.strategy._is_tokens_minted = True ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.mocked_tx_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={self.mocked_tx_receipt}", ) assert self.skill.skill_context.is_active is False mock_logger.assert_any_call(logging.INFO, "demo finished!") def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_balance_messages[:1], ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = cast( LedgerApiMessage, self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=self.ledger_id, address=self.address, to=str(self.skill.skill_context.skill_id), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestContractApiHandler(ERC1155DeployTestCase): """Test contract_api handler of erc1155_deploy.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the contract_api handler.""" assert self.contract_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( ContractApiMessage, self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=ContractApiMessage.Performative.STATE, state=State(self.ledger_id, self.body_dict), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid contract_api message={incoming_message}, unidentified dialogue.", ) def test_handle_raw_transaction(self): """Test the _handle_raw_transaction method of the signing handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.terms = self.mocked_terms incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=self.mocked_raw_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw transaction={incoming_message}" ) self.assert_quantity_in_decision_making_queue(1) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_TRANSACTION, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), raw_transaction=self.mocked_raw_tx, terms=contract_api_dialogue.terms, ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_contract_api_dialogue == contract_api_dialogue ) mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received contract_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION incoming_message = cast( ContractApiMessage, self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=self.ledger_id, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle contract_api message of performative={invalid_performative} in dialogue={self.contract_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the contract_api handler.""" assert self.contract_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(ERC1155DeployTestCase): """Test signing handler of erc1155_deploy.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( SigningMessage, self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_transaction( self, ): """Test the _handle_signed_transaction method of the signing handler.""" # setup signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.mocked_signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), signed_transaction=self.mocked_signed_tx, ) assert has_attributes, error_str ledger_api_dialogue = cast( LedgerApiDialogue, self.ledger_api_dialogues.get_dialogue(message) ) assert ledger_api_dialogue.associated_signing_dialogue == signing_dialogue mock_logger.assert_any_call(logging.INFO, "sending transaction to ledger.") def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.mocked_terms, raw_transaction=SigningMessage.RawTransaction( self.ledger_id, {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(ERC1155DeployTestCase): """Test oef_search handler of erc1155_deploy.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_location[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_search_dialogue}.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.registration_behaviour, "register_service", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH set_service_key data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_service[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.registration_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.registration_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iv(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) assert self.registration_behaviour.is_registered is True assert self.registration_behaviour.registration_in_progress is False mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, and its service are successfully registered on the SOEF.", ) def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef success targets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_proposal, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={incoming_message.performative} in dialogue={self.oef_search_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_erc1155_deploy/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the erc1155_deploy skill.""" import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, ) from aea.helpers.search.models import Description, Location from aea.helpers.transaction.base import Terms from packages.fetchai.contracts.erc1155.contract import ERC1155Contract from packages.fetchai.skills.erc1155_deploy.strategy import ( SIMPLE_SERVICE_MODEL, Strategy, ) from tests.test_packages.test_skills.test_erc1155_deploy.intermediate_class import ( ERC1155DeployTestCase, ) class TestStrategy(ERC1155DeployTestCase): """Test Strategy of erc1155_deploy.""" def test__init__(self): """Test the properties of Strategy class.""" assert Strategy( location=self.location, mint_quantities=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], service_data=self.service_data, personality_data=self.personality_data, classification=self.classification, from_supply=self.from_supply, to_supply=self.to_supply, value=self.value, token_type=1, name="strategy", skill_context=self.skill.skill_context, ) def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.ledger_id == self.skill.skill_context.default_ledger_id assert self.strategy.contract_id == str(ERC1155Contract.contract_id) assert self.strategy.mint_quantities == self.mint_quantities assert self.strategy.token_ids == self.strategy._token_ids self.strategy._token_ids = None with pytest.raises(ValueError, match="Token ids not set."): assert self.strategy.token_ids with pytest.raises(ValueError, match="Contract address not set!"): assert self.strategy.contract_address self.strategy.contract_address = self.contract_address with pytest.raises(AEAEnforceError, match="Contract address already set!"): self.strategy.contract_address = self.contract_address assert self.strategy.contract_address == self.contract_address assert self.strategy.is_contract_deployed is False self.strategy.is_contract_deployed = True assert self.strategy.is_contract_deployed is True with pytest.raises(AEAEnforceError, match="Only allowed to switch to true."): self.strategy.is_contract_deployed = False assert self.strategy.is_tokens_created is False self.strategy.is_tokens_created = True assert self.strategy.is_tokens_created is True with pytest.raises(AEAEnforceError, match="Only allowed to switch to true."): self.strategy.is_tokens_created = False assert self.strategy.is_tokens_minted is False self.strategy.is_tokens_minted = True assert self.strategy.is_tokens_minted is True with pytest.raises(AEAEnforceError, match="Only allowed to switch to true."): self.strategy.is_tokens_minted = False assert self.strategy.gas == self.strategy._gas def test_get_location_description(self): """Test the get_location_description method of the Strategy class.""" description = self.strategy.get_location_description() assert type(description) == Description assert description.data_model is AGENT_LOCATION_MODEL assert description.values.get("location", "") == Location( latitude=self.location["latitude"], longitude=self.location["longitude"] ) def test_get_register_service_description(self): """Test the get_register_service_description method of the Strategy class.""" description = self.strategy.get_register_service_description() assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert description.values.get("key", "") == self.service_data["key"] assert description.values.get("value", "") == self.service_data["value"] def test_get_register_personality_description(self): """Test the get_register_personality_description method of the Strategy class.""" description = self.strategy.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == self.personality_data["piece"] assert description.values.get("value", "") == self.personality_data["value"] def test_get_register_classification_description(self): """Test the get_register_classification_description method of the Strategy class.""" description = self.strategy.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == self.classification["piece"] assert description.values.get("value", "") == self.classification["value"] def test_get_service_description(self): """Test the get_service_description method of the Strategy class.""" description = self.strategy.get_service_description() assert type(description) == Description assert description.data_model is SIMPLE_SERVICE_MODEL assert ( description.values.get("seller_service", "") == self.service_data["value"] ) def test_get_unregister_service_description(self): """Test the get_unregister_service_description method of the Strategy class.""" description = self.strategy.get_unregister_service_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == self.service_data["key"] def test_get_deploy_terms(self): """Test the get_deploy_terms of Strategy.""" assert self.strategy.get_deploy_terms() == Terms( ledger_id=self.ledger_id, sender_address=self.skill.skill_context.agent_address, counterparty_address=self.skill.skill_context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) def test_get_create_token_terms(self): """Test the get_create_token_terms of Parameters.""" assert self.strategy.get_create_token_terms() == Terms( ledger_id=self.ledger_id, sender_address=self.skill.skill_context.agent_address, counterparty_address=self.skill.skill_context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) def test_get_mint_token_terms(self): """Test the get_mint_token_terms of Strategy.""" assert self.strategy.get_mint_token_terms() == Terms( ledger_id=self.ledger_id, sender_address=self.skill.skill_context.agent_address, counterparty_address=self.skill.skill_context.agent_address, amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ) def test_get_proposal(self): """Test the get_proposal of Strategy.""" # setup self.strategy._contract_address = self.contract_address first_id = 8768 self.strategy._token_ids = [first_id, 234, 879643] # operation actual_proposal = self.strategy.get_proposal() # after assert all( keys in actual_proposal.values for keys in [ "contract_address", "token_id", "trade_nonce", "from_supply", "to_supply", ] ) assert ( actual_proposal.values.get("contract_address", "") == self.contract_address ) assert actual_proposal.values.get("token_id", "") == str(first_id) assert isinstance(actual_proposal.values.get("trade_nonce", ""), str) assert actual_proposal.values.get("from_supply", "") == str(self.from_supply) assert actual_proposal.values.get("to_supply", "") == str(self.to_supply) assert actual_proposal.values.get("value", "") == str(self.value) def test_get_single_swap_terms(self): """Test the get_single_swap_terms of Strategy.""" assert self.strategy.get_single_swap_terms( self.mocked_proposal, "some_address" ) == Terms( ledger_id=self.ledger_id, sender_address=self.skill.skill_context.agent_address, counterparty_address="some_address", amount_by_currency_id={ str(self.mocked_proposal.values["token_id"]): int( self.mocked_proposal.values["from_supply"] ) - int(self.mocked_proposal.values["to_supply"]) }, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce=str(self.mocked_proposal.values["trade_nonce"]), fee_by_currency_id={}, ) ================================================ FILE: tests/test_packages/test_skills/test_fetch_block/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/fetch_block dir.""" ================================================ FILE: tests/test_packages/test_skills/test_fetch_block/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the fetch block skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.ledger_api.custom_types import Kwargs from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.skills.fetch_block.behaviours import FetchBlockBehaviour from tests.conftest import ROOT_DIR LEDGER_ID = "fetchai" class TestSkillBehaviour(BaseSkillTestCase): """Test behaviours of fetch block.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "fetch_block") @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.fetch_block_behaviour = cast( FetchBlockBehaviour, cls._skill.skill_context.behaviours.fetch_block_behaviour, ) def test__get_block(self): """Test that the _get_block function sends the right message to the ledger_api.""" self.fetch_block_behaviour._get_block() self.assert_quantity_in_outbox(1) msg = cast(LedgerApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_STATE, ledger_id=LEDGER_ID, callable="blocks", args=("latest",), kwargs=Kwargs({}), ) assert has_attributes, error_str def test_setup(self): """Test that the setup method puts no messages in the outbox by default.""" self.fetch_block_behaviour.setup() self.assert_quantity_in_outbox(0) def test_act(self): """Test that the act method of the fetch_block behaviour puts the correct message in the outbox.""" self.fetch_block_behaviour.act() self.assert_quantity_in_outbox(1) msg = cast(LedgerApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_STATE, ledger_id=LEDGER_ID, callable="blocks", args=("latest",), kwargs=Kwargs({}), ) assert has_attributes, error_str def test_teardown(self): """Test that the teardown method of the fetch_block behaviour leaves no messages in the outbox.""" assert self.fetch_block_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_fetch_block/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the simple_data_request skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.ledger_api.custom_types import State from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.skills.fetch_block.behaviours import FetchBlockBehaviour from packages.fetchai.skills.fetch_block.dialogues import LedgerApiDialogues from packages.fetchai.skills.fetch_block.handlers import LedgerApiHandler from tests.conftest import ROOT_DIR LEDGER_ID = "fetchai" class TestLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of fetch_block skill.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "fetch_block") is_agent_to_agent_messages = False @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup(**kwargs) cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.logger = cls._skill.skill_context.logger cls.fetch_block_behaviour = cast( FetchBlockBehaviour, cls._skill.skill_context.behaviours.fetch_block_behaviour, ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_STATE, { "ledger_id": LEDGER_ID, "callable": "blocks", "args": ("latest",), "kwargs": LedgerApiMessage.Kwargs({}), }, ), DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": LEDGER_ID, "address": "some_eth_address"}, ), ) def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle__handle_unidentified_dialogue(self): """Test handling an unidentified dialogoue""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.GET_STATE, ledger_id=LEDGER_ID, callable="blocks", args=("latest",), kwargs=LedgerApiMessage.Kwargs({}), ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(0) def test__handle_state(self): """Test handling a state""" # setup test_block_data = { "block_id": {"hash": "00000000"}, "block": { "header": {"height": "1", "entropy": {"group_signature": "SIGNATURE"}} }, } dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.STATE, ledger_id=LEDGER_ID, state=State(LEDGER_ID, test_block_data), ) # handle message with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # check that data was correctly entered into shared state assert self.ledger_api_handler.context.shared_state["observation"] == { "block": test_block_data } # after mock_logger.assert_any_call( logging.INFO, "Retrieved latest block: " + str({"block_height": 1}), ) self.assert_quantity_in_outbox(0) def test__handle_invalid(self): """Test handling an invalid performative""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[1:] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=LEDGER_ID, balance=0, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={incoming_message.performative} in dialogue={dialogue}.", ) self.assert_quantity_in_outbox(0) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_hello_world/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for fetchai/hellow_world skill.""" ================================================ FILE: tests/test_packages/test_skills/test_hello_world/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """Tests for hello_world skill's behaviour.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.hello_world.behaviours import HelloWorld from tests.conftest import ROOT_DIR class TestHelloWorld(BaseSkillTestCase): """Test HelloWorld behaviour of hello world.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "hello_world") is_agent_to_agent_messages = False def setup_method(self): """Set up the test environment.""" self.message = "Hello Something Custom!" config_overrides = { "behaviours": {"hello_world": {"args": {"message": self.message}}} } super().setup(config_overrides=config_overrides) self.hello_world_behaviour = cast( HelloWorld, self._skill.skill_context.behaviours.hello_world ) self.logger = self._skill.skill_context.logger def test_act(self): """Test the act method of the hello_world behaviour.""" # operation with patch.object(self.logger, "log") as mock_logger: assert self.hello_world_behaviour.act() is None # after mock_logger.assert_any_call(logging.INFO, self.message) ================================================ FILE: tests/test_packages/test_skills/test_http_echo/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/http_echo dir.""" ================================================ FILE: tests/test_packages/test_skills/test_http_echo/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the http_echo skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.http_echo.dialogues import ( DefaultDialogue, DefaultDialogues, HttpDialogue, HttpDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue class of http_echo.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "http_echo") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_http_dialogues(self): """Test the HttpDialogues class.""" _, dialogue = self.http_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=HttpMessage.Performative.REQUEST, method="some_method", url="some_url", version="some_version", headers="some_headers", body=b"some_body", ) assert dialogue.role == HttpDialogue.Role.SERVER assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_http_echo/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler class of the http_echo skill.""" import json import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.http_echo.dialogues import HttpDialogues from packages.fetchai.skills.http_echo.handlers import HttpHandler from tests.conftest import ROOT_DIR class TestHttpHandler(BaseSkillTestCase): """Test HttpHandler of http_echo.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "http_echo") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.http_handler = cast( HttpHandler, cls._skill.skill_context.handlers.http_handler ) cls.logger = cls._skill.skill_context.logger cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) cls.get_method = "get" cls.post_method = "post" cls.url = "some_url" cls.version = "some_version" cls.headers = "some_headers" cls.body = b"some_body" cls.sender = "fetchai/some_skill:0.1.0" cls.skill_id = str(cls._skill.skill_context.skill_id) cls.status_code = 100 cls.status_text = "some_status_text" cls.content = b"some_content" cls.list_of_messages = ( DialogueMessage( HttpMessage.Performative.REQUEST, { "method": cls.get_method, "url": cls.url, "version": cls.version, "headers": cls.headers, "body": cls.body, }, ), ) def test_setup(self): """Test the setup method of the http_echo handler.""" assert self.http_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the http_echo handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=HttpMessage, dialogue_reference=incorrect_dialogue_reference, performative=HttpMessage.Performative.REQUEST, to=self.skill_id, method=self.get_method, url=self.url, version=self.version, headers=self.headers, body=self.body, ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received invalid http message={incoming_message}, unidentified dialogue.", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"http_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_request_get(self): """Test the _handle_request method of the http_echo handler where method is get.""" # setup incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, to=self.skill_id, sender=self.sender, method=self.get_method, url=self.url, version=self.version, headers=self.headers, body=self.body, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, "received http request with method={}, url={} and body={!r}".format( incoming_message.method, incoming_message.url, incoming_message.body ), ) # _handle_get message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=HttpMessage, performative=HttpMessage.Performative.RESPONSE, to=incoming_message.sender, sender=incoming_message.to, version=incoming_message.version, status_code=200, status_text="Success", headers=incoming_message.headers, body=json.dumps({"tom": {"type": "cat", "age": 10}}).encode("utf-8"), ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"responding with: {message}", ) def test_handle_request_post(self): """Test the _handle_request method of the http_echo handler where method is post.""" # setup incoming_message = cast( HttpMessage, self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, to=self.skill_id, sender=self.sender, method=self.post_method, url=self.url, version=self.version, headers=self.headers, body=self.body, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, "received http request with method={}, url={} and body={!r}".format( incoming_message.method, incoming_message.url, incoming_message.body ), ) # _handle_post message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=HttpMessage, performative=HttpMessage.Performative.RESPONSE, to=incoming_message.sender, sender=incoming_message.to, version=incoming_message.version, status_code=200, status_text="Success", headers=incoming_message.headers, body=self.body, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"responding with: {message}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the http_echo handler.""" # setup http_dialogue = self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( HttpMessage, self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, version=self.version, status_code=self.status_code, status_text=self.status_text, headers=self.headers, body=self.body, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle http message of performative={incoming_message.performative} in dialogue={http_dialogue}.", ) def test_teardown(self): """Test the teardown method of the http_echo handler.""" assert self.http_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_ml_data_provider/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/ml_data_provider dir.""" ================================================ FILE: tests/test_packages/test_skills/test_ml_data_provider/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the ml_data_provider skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.ml_data_provider.dialogues import ( DefaultDialogue, DefaultDialogues, LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, MlTradeDialogues, OefSearchDialogue, OefSearchDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue classes of ml_data_provider.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_data_provider") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.ml_dialogues = cast( MlTradeDialogues, cls._skill.skill_context.ml_trade_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_ml_dialogues(self): """Test the MlTradeDialogues class.""" _, dialogue = self.ml_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=MlTradeMessage.Performative.CFP, query="some_query", ) assert dialogue.role == MlTradeDialogue.Role.SELLER assert dialogue.self_address == self.skill.skill_context.agent_address def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query="some_query", ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_ml_data_provider/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the ml_data_provider skill.""" import logging import uuid from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import Attribute, DataModel, Description, Location from aea.helpers.transaction.base import TransactionDigest, TransactionReceipt from aea.protocols.dialogue.base import DialogueMessage, Dialogues from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.ml_data_provider.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.ml_data_provider.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, MlTradeDialogues, OefSearchDialogues, ) from packages.fetchai.skills.ml_data_provider.handlers import ( LedgerApiHandler, MlTradeHandler, OefSearchHandler, ) from packages.fetchai.skills.ml_data_provider.strategy import Strategy from tests.conftest import ROOT_DIR class TestMlTradeHandler(BaseSkillTestCase): """Test ml handler of ml_data_provider.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_data_provider") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ml_handler = cast( MlTradeHandler, cls._skill.skill_context.handlers.ml_trade ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.ml_dialogues = cast( MlTradeDialogues, cls._skill.skill_context.ml_trade_dialogues ) cls.logger = cls._skill.skill_context.logger cls.batch_size = 32 cls.price_per_data_batch = 10 cls.seller_tx_fee = 0 cls.buyer_tx_fee = 0 cls.currency_id = "FET" cls.ledger_id = "FET" cls.service_id = "data_service" cls.terms = Description( { "batch_size": cls.batch_size, "price": cls.price_per_data_batch, "seller_tx_fee": cls.seller_tx_fee, "buyer_tx_fee": cls.buyer_tx_fee, "currency_id": cls.currency_id, "ledger_id": cls.ledger_id, "address": cls._skill.skill_context.agent_address, "service_id": cls.service_id, "nonce": uuid.uuid4().hex, } ) cls.list_of_messages = ( DialogueMessage(MlTradeMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage(MlTradeMessage.Performative.TERMS, {"terms": cls.terms}), DialogueMessage( MlTradeMessage.Performative.ACCEPT, {"terms": cls.terms, "tx_digest": "some_tx_digest"}, ), ) def test_setup(self): """Test the setup method of the ml handler.""" assert self.ml_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ml handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=MlTradeMessage, dialogue_reference=incorrect_dialogue_reference, performative=MlTradeMessage.Performative.ACCEPT, terms=self.terms, tx_digest="some_tx_digest", ) # operation with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ml_trade message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"ml_trade_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_cfp_is_matching_supply(self): """Test the _handle_cfp method of the ml handler where is_matching_supply is True.""" # setup incoming_message = self.build_incoming_message( message_type=MlTradeMessage, performative=MlTradeMessage.Performative.CFP, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), query="some_query", ) # operation with patch.object(self.strategy, "is_matching_supply", return_value=True): with patch.object(self.strategy, "generate_terms", return_value=self.terms): with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"got a Call for Terms from {COUNTERPARTY_AGENT_ADDRESS[-5:]}.", ) mock_logger.assert_any_call( logging.INFO, f"sending to the address={COUNTERPARTY_AGENT_ADDRESS[-5:]} a Terms message: {self.terms.values}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=MlTradeMessage, performative=MlTradeMessage.Performative.TERMS, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, terms=self.terms, ) assert has_attributes, error_str def test_handle_cfp_not_is_matching_supply(self): """Test the _handle_cfp method of the ml handler where is_matching_supply is False.""" # setup incoming_message = self.build_incoming_message( message_type=MlTradeMessage, performative=MlTradeMessage.Performative.CFP, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), query="some_query", ) # operation with patch.object(self.strategy, "is_matching_supply", return_value=False): with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"got a Call for Terms from {COUNTERPARTY_AGENT_ADDRESS[-5:]}.", ) mock_logger.assert_any_call(logging.INFO, "query does not match supply.") def test_handle_accept_i(self): """Test the _handle_accept method of the ml handler.""" # setup mocked_tx_digest = "some_tx_digest" terms = self.strategy.generate_terms() expected_data = self.strategy.sample_data(terms.values["batch_size"]) ml_dialogue = cast( MlTradeDialogue, self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:2], ), ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.ACCEPT, terms=terms, tx_digest=mocked_tx_digest, ) # operation with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"got an Accept from {COUNTERPARTY_AGENT_ADDRESS[-5:]}: {terms.values}", ) mock_logger.assert_any_call( logging.INFO, f"sending to address={COUNTERPARTY_AGENT_ADDRESS[-5:]} a Data message: shape={expected_data[0].shape}", ) self.assert_quantity_in_outbox(1) message = cast(MlTradeMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=MlTradeMessage, performative=MlTradeMessage.Performative.DATA, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, terms=terms, ) assert has_attributes, error_str assert type(message.payload) == bytes def test_handle_accept_ii(self): """Test the _handle_accept method of the ml handler where terms is NOT valid.""" # setup mocked_tx_digest = "some_tx_digest" ml_dialogue = cast( MlTradeDialogue, self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:2], ), ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.ACCEPT, terms=self.terms, tx_digest=mocked_tx_digest, ) # operation with patch.object(self.strategy, "is_valid_terms", return_value=False): with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"got an Accept from {COUNTERPARTY_AGENT_ADDRESS[-5:]}: {self.terms.values}", ) mock_logger.assert_any_call(logging.INFO, "terms are not valid.") self.assert_quantity_in_outbox(0) def test_handle_invalid(self): """Test the _handle_invalid method of the ml handler.""" # setup ml_dialogue = self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.TERMS, terms=self.terms, ) # operation with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ml_trade message of performative={incoming_message.performative} in dialogue={ml_dialogue}.", ) def test_teardown(self): """Test the teardown method of the ml handler.""" assert self.ml_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of ml_data_provider.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_data_provider") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.ml_dialogues = cast( MlTradeDialogues, cls._skill.skill_context.ml_trade_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.logger = cls._skill.skill_context.logger cls.ledger_id = "FET" cls.transaction_digest = TransactionDigest("some_ledger_id", "some_body") cls.transaction_receipt = TransactionReceipt( "some_ledger_id", {"some_key": "some_value"}, {"some_key": "some_value"} ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_RECEIPT, {"transaction_receipt": cls.transaction_receipt}, ), ) def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_balance(self): """Test the _handle_balance method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": "some_ledger_id", "address": "some_address"}, ), ), ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=self.ledger_id, balance=10, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {self.ledger_id} ledger={incoming_message.balance}.", ) def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id="some_ledger_id", address="some_address", to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(BaseSkillTestCase): """Test oef search handler of ml_data_provider.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_data_provider") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.service_registration_behaviour = cast( ServiceRegistrationBehaviour, cls._skill.skill_context.behaviours.service_registration, ) cls.list_of_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} ), ) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) cls.unregister_description = Description( {"key": "seller_service"}, data_model=DataModel("remove", [Attribute("key", str, True)]), ) cls.list_of_messages_unregister = ( DialogueMessage( OefSearchMessage.Performative.UNREGISTER_SERVICE, {"service_description": cls.unregister_description}, is_incoming=False, ), ) def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_service", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH set_service_key data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_service[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iv(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, and its service are successfully registered on the SOEF.", ) def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef successtargets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) def test_handle_error_i(self): """Test the _handle_error method of the oef_search handler where the oef error targets register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert ( self.service_registration_behaviour.failed_registration_msg == oef_dialogue.get_message_by_id(incoming_message.target) ) def test_handle_error_ii(self): """Test the _handle_error method of the oef_search handler where the oef error does NOT target register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_unregister[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert self.service_registration_behaviour.failed_registration_msg is None def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=("1", ""), performative=invalid_performative, service_description="some_service_description", ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_ml_data_provider/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the ml_data_provider skill.""" import json from pathlib import Path from unittest.mock import PropertyMock, patch import numpy as np from aea.configurations.constants import DEFAULT_LEDGER from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.ml_data_provider.strategy import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, DEFAULT_BATCH_SIZE, DEFAULT_BUYER_TX_FEE, DEFAULT_CLASSIFICATION, DEFAULT_LOCATION, DEFAULT_PERSONALITY_DATA, DEFAULT_PRICE_PER_DATA_BATCH, DEFAULT_SELLER_TX_FEE, DEFAULT_SERVICE_DATA, DEFAULT_SERVICE_ID, SIMPLE_DATA_MODEL, Strategy, ) from tests.conftest import ROOT_DIR class TestGenericStrategy(BaseSkillTestCase): """Test Strategy of ml_data_provider.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_data_provider") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.batch_size = DEFAULT_BATCH_SIZE cls.price_per_data_batch = DEFAULT_PRICE_PER_DATA_BATCH cls.seller_tx_fee = DEFAULT_SELLER_TX_FEE cls.buyer_tx_fee = DEFAULT_BUYER_TX_FEE cls.currency_id = "some_currency_id" cls.ledger_id = DEFAULT_LEDGER cls.is_ledger_tx = True cls.service_id = DEFAULT_SERVICE_ID cls.location = DEFAULT_LOCATION cls.personality_data = DEFAULT_PERSONALITY_DATA cls.classification = DEFAULT_CLASSIFICATION cls.service_data = DEFAULT_SERVICE_DATA cls.strategy = Strategy( batch_size=cls.batch_size, price_per_data_batch=cls.price_per_data_batch, seller_tx_fee=cls.seller_tx_fee, buyer_tx_fee=cls.buyer_tx_fee, ledger_id=cls.ledger_id, is_ledger_tx=cls.is_ledger_tx, currency_id=cls.currency_id, service_id=cls.service_id, location=cls.location, personality_data=cls.personality_data, classification=cls.classification, service_data=cls.service_data, name="strategy", skill_context=cls._skill.skill_context, ) def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.ledger_id == self.ledger_id assert self.strategy.is_ledger_tx == self.is_ledger_tx def test_get_location_description(self): """Test the get_location_description method of the Strategy class.""" description = self.strategy.get_location_description() assert type(description) == Description assert description.data_model is AGENT_LOCATION_MODEL assert description.values.get("location", "") == Location( latitude=self.location["latitude"], longitude=self.location["longitude"] ) def test_get_register_personality_description(self): """Test the get_register_personality_description method of the Strategy class.""" description = self.strategy.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "genus" assert description.values.get("value", "") == "data" def test_get_register_classification_description(self): """Test the get_register_classification_description method of the Strategy class.""" description = self.strategy.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "classification" assert description.values.get("value", "") == "seller" def test_get_register_service_description(self): """Test the get_register_service_description method of the Strategy class.""" description = self.strategy.get_register_service_description() assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert description.values.get("key", "") == "dataset_id" assert description.values.get("value", "") == "fmnist" def test_get_service_description(self): """Test the get_service_description method of the Strategy class.""" description = self.strategy.get_service_description() assert type(description) == Description assert description.data_model is SIMPLE_DATA_MODEL assert description.values.get("dataset_id", "") == "fmnist" def test_get_unregister_service_description(self): """Test the get_unregister_service_description method of the GenericStrategy class.""" description = self.strategy.get_unregister_service_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == "dataset_id" def test_sample_data(self): """Test the sample_data method of the Strategy class.""" data = self.strategy.sample_data(32) assert type(data) == tuple assert len(data) == 2 assert type(data[0]) == np.ndarray assert type(data[0]) == np.ndarray def test_encode_sample_data(self): """Test the encode_sample_data method of the Strategy class.""" # setup data = self.strategy.sample_data(32) # operation encoded_data = self.strategy.encode_sample_data(data) # after assert type(encoded_data) == bytes # decode it and check identical decoded_arrays = json.loads(encoded_data) numpy_data_0 = np.asarray(decoded_arrays["data_0"]) numpy_data_1 = np.asarray(decoded_arrays["data_1"]) assert (numpy_data_0 == data[0]).all() assert (numpy_data_1 == data[1]).all() def test_is_matching_supply(self): """Test the is_matching_supply method of the Strategy class.""" acceptable_constraint = Constraint("dataset_id", ConstraintType("==", "fmnist")) matching_query = Query([acceptable_constraint]) is_matching_supply = self.strategy.is_matching_supply(matching_query) assert is_matching_supply unacceptable_constraint = Constraint( "dataset_id", ConstraintType("==", "some_other_service") ) unmatching_query = Query([unacceptable_constraint]) is_matching_supply = self.strategy.is_matching_supply(unmatching_query) assert not is_matching_supply def test_generate_terms(self): """Test the generate_terms method of the Strategy class.""" # setup mocked_nonce = "some_nonce" expected_proposal = Description( { "batch_size": self.batch_size, "price": self.price_per_data_batch, "seller_tx_fee": self.seller_tx_fee, "buyer_tx_fee": self.buyer_tx_fee, "currency_id": self.currency_id, "ledger_id": self.ledger_id, "address": self.skill.skill_context.agent_address, "service_id": self.service_id, "nonce": mocked_nonce, } ) # operation with patch( "uuid.UUID.hex", new_callable=PropertyMock, return_value=mocked_nonce ) as mocked_uuid: proposal = self.strategy.generate_terms() # after mocked_uuid.assert_called_once() assert proposal == expected_proposal def test_is_valid_terms_i(self): """Test the is_valid_terms method of the Strategy class where terms is VALID.""" # setup valid_proposal = Description( { "batch_size": self.batch_size, "price": self.price_per_data_batch, "seller_tx_fee": self.seller_tx_fee, "buyer_tx_fee": self.buyer_tx_fee, "currency_id": self.currency_id, "ledger_id": self.ledger_id, "address": self.skill.skill_context.agent_address, "service_id": self.service_id, "nonce": "some_nonce", } ) # operation is_valid = self.strategy.is_valid_terms(valid_proposal) # after assert is_valid is True def test_is_valid_terms_ii(self): """Test the is_valid_terms method of the Strategy class where terms is INVALID.""" # setup invalid_batch_size = 0 valid_proposal = Description( { "batch_size": invalid_batch_size, "price": self.price_per_data_batch, "seller_tx_fee": self.seller_tx_fee, "buyer_tx_fee": self.buyer_tx_fee, "currency_id": self.currency_id, "ledger_id": self.ledger_id, "address": self.skill.skill_context.agent_address, "service_id": self.service_id, "nonce": "some_nonce", } ) # operation is_valid = self.strategy.is_valid_terms(valid_proposal) # after assert is_valid is False ================================================ FILE: tests/test_packages/test_skills/test_ml_train/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/ml_train dir.""" ================================================ FILE: tests/test_packages/test_skills/test_ml_train/helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the ml_train skill.""" from typing import Tuple import numpy as np def produce_data(batch_size) -> Tuple: """Produce the data.""" from tensorflow import keras # pylint: disable=import-outside-toplevel ((train_x, train_y), _) = keras.datasets.fashion_mnist.load_data() idx = np.arange(train_x.shape[0]) mask = np.zeros_like(idx, dtype=bool) selected = np.random.choice(idx, batch_size, replace=False) mask[selected] = True x_sample = train_x[mask] y_sample = train_y[mask] return x_sample, y_sample ================================================ FILE: tests/test_packages/test_skills/test_ml_train/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the ml_train skill.""" import logging import uuid from multiprocessing.pool import ApplyResult from pathlib import Path from typing import Tuple, cast from unittest.mock import Mock, patch import pytest from aea.helpers.search.models import Description from aea.protocols.dialogue.base import DialogueMessage from aea.skills.tasks import TaskManager from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.skills.ml_train.behaviours import ( GenericSearchBehaviour, LEDGER_API_ADDRESS, SearchBehaviour, TransactionBehaviour, ) from packages.fetchai.skills.ml_train.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, MlTradeDialogues, ) from packages.fetchai.skills.ml_train.strategy import Strategy from tests.conftest import ROOT_DIR class TestSearchBehaviour(BaseSkillTestCase): """Test Search behaviour of ml_train.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_train") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.search_behaviour = cast( SearchBehaviour, cls._skill.skill_context.behaviours.search ) cls.tx_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.task_manager = cast(TaskManager, cls._skill.skill_context.task_manager) cls.logger = cls._skill.skill_context.logger cls._skill.skill_context.task_manager.start() def test_act_no_task(self): """Test the act method of the search behaviour where current_task_id is None.""" # setup self.strategy._current_task_id = None # operation with patch.object(GenericSearchBehaviour, "act") as mock_generic_act: self.search_behaviour.act() # after mock_generic_act.assert_called_once() def test_act_task_not_ready(self): """Test the act method of the search behaviour where task isn't ready.""" # setup self.strategy._current_task_id = 1 mock_task_result = Mock(wraps=ApplyResult) mock_task_result.ready.return_value = False # operation with patch.object( self.task_manager, "get_task_result", return_value=mock_task_result ): self.search_behaviour.act() # after self.assert_quantity_in_outbox(0) assert self.strategy._current_task_id == 1 def test_act_task_not_successful(self): """Test the act method of the search behaviour where task isn't successful.""" # setup self.strategy._current_task_id = 1 mock_task_result = Mock(wraps=ApplyResult) mock_task_result.ready.return_value = True mock_task_result.successful.return_value = False # operation with patch.object( self.task_manager, "get_task_result", return_value=mock_task_result ): self.search_behaviour.act() # after self.assert_quantity_in_outbox(0) assert self.strategy._current_task_id == 1 def test_act_task_ready_and_successful(self): """Test the act method of the search behaviour where task is ready and successful.""" # setup self.strategy._current_task_id = 1 mocked_weights = "some_weights" mock_task_result = Mock(wraps=ApplyResult) mock_task_result.ready.return_value = True mock_task_result.successful.return_value = True mock_task_result.get.return_value = mocked_weights # operation with patch.object( self.task_manager, "get_task_result", return_value=mock_task_result ): with patch.object(GenericSearchBehaviour, "act") as mock_generic_act: self.search_behaviour.act() # after assert self.strategy._current_task_id is None assert self.strategy._weights == mocked_weights mock_generic_act.assert_called_once() def test_act_data_exists(self): """Test the act method of the search behaviour where no task is running and there is strategy.data is not empty.""" # setup self.strategy._current_task_id = None mocked_data = ([], []) self.strategy.data = [mocked_data] mocked_task_id = 1 # operation with patch.object( self.task_manager, "enqueue_task", return_value=mocked_task_id ) as mocked_enqueue_task: self.search_behaviour.act() # after assert self.strategy.data == [] mocked_enqueue_task.assert_called_once() assert self.strategy._current_task_id == mocked_task_id @classmethod def teardown(cls): """Tears down the test class.""" cls._skill.skill_context.task_manager.stop() class TestTransactionBehaviour(BaseSkillTestCase): """Test transaction behaviour of ml_train buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_train") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.transaction_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.ml_dialogues = cast( MlTradeDialogues, cls._skill.skill_context.ml_trade_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.batch_size = 32 cls.price_per_data_batch = 10 cls.seller_tx_fee = 0 cls.buyer_tx_fee = 0 cls.currency_id = "FET" cls.ledger_id = "FET" cls.service_id = "data_service" cls.terms = Description( { "batch_size": cls.batch_size, "price": cls.price_per_data_batch, "seller_tx_fee": cls.seller_tx_fee, "buyer_tx_fee": cls.buyer_tx_fee, "currency_id": cls.currency_id, "ledger_id": cls.ledger_id, "address": cls._skill.skill_context.agent_address, "service_id": cls.service_id, "nonce": uuid.uuid4().hex, } ) cls.list_of_messages = ( DialogueMessage(MlTradeMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage(MlTradeMessage.Performative.TERMS, {"terms": cls.terms}), DialogueMessage( MlTradeMessage.Performative.ACCEPT, {"terms": cls.terms, "tx_digest": "some_tx_digest"}, ), ) @staticmethod def _check_start_processing_effects(self_, ml_dialogue, mock_logger) -> None: """Perform checks related to running _start_processing.""" # _start_processing mock_logger.assert_any_call( logging.INFO, f"Processing transaction, {len(self_.transaction_behaviour.waiting)} transactions remaining", ) message = self_.get_message_from_outbox() has_attributes, error_str = self_.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self_.skill.public_id), terms=ml_dialogue.terms, ) assert has_attributes, error_str ledger_api_dialogue = cast( LedgerApiDialogue, self_.ledger_api_dialogues.get_dialogue(message) ) assert ledger_api_dialogue.associated_ml_trade_dialogue == ml_dialogue assert self_.transaction_behaviour.processing_time == 0.0 assert self_.transaction_behaviour.processing == ledger_api_dialogue mock_logger.assert_any_call( logging.INFO, f"requesting transfer transaction from ledger api for message={message}...", ) @staticmethod def _setup_ml_ledger_api_dialogues( self_, ) -> Tuple[LedgerApiDialogue, MlTradeDialogue]: """Setup ml_trade and ledger_api dialogues for some of the following tests.""" ml_dialogue = cast( MlTradeDialogue, self_.prepare_skill_dialogue( dialogues=self_.ml_dialogues, messages=self_.list_of_messages, ), ) ml_dialogue.terms = self_.terms ledger_api_dialogue = cast( LedgerApiDialogue, self_.prepare_skill_dialogue( dialogues=self_.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": "some_ledger_id", "address": "some_address"}, ), ), ), ) ledger_api_dialogue.associated_ml_trade_dialogue = ml_dialogue return ledger_api_dialogue, ml_dialogue def test_setup(self): """Test the setup method of the transaction behaviour.""" assert self.transaction_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act_i(self): """Test the act method of the transaction behaviour where processing IS None and len(self.waiting) is NOT 0.""" # setup _, ml_dialogue = self._setup_ml_ledger_api_dialogues(self) processing_time = 5.0 max_processing = 120 self.transaction_behaviour.processing = None self.transaction_behaviour.max_processing = max_processing self.transaction_behaviour.processing_time = processing_time self.transaction_behaviour.waiting = [ml_dialogue] # before assert self.transaction_behaviour.processing_time == processing_time assert self.transaction_behaviour.processing is None # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(1) # _start_processing self._check_start_processing_effects(self, ml_dialogue, mock_logger) def test_act_ii(self): """Test the act method of the transaction behaviour where processing is NOT None and processing_time < max_processing.""" # setup processing_time = 5.0 self.transaction_behaviour.processing = "some_dialogue" self.transaction_behaviour.max_processing = 120 self.transaction_behaviour.processing_time = processing_time # operation self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(0) assert ( self.transaction_behaviour.processing_time == processing_time + self.transaction_behaviour.tick_interval ) def test_act_iii(self): """Test the act method of the transaction behaviour where processing is NOT None and processing_time > max_processing.""" # setup ledger_api_dialogue, ml_dialogue = self._setup_ml_ledger_api_dialogues(self) processing_time = 121.0 self.transaction_behaviour.processing = ledger_api_dialogue self.transaction_behaviour.max_processing = 120 self.transaction_behaviour.processing_time = processing_time # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(1) # _timeout_processing assert ledger_api_dialogue.dialogue_label in self.transaction_behaviour.timedout # below is overridden in _start_processing # assert ml_dialogue in self.transaction_behaviour.waiting assert self.transaction_behaviour.processing_time == 0.0 # below is overridden in _start_processing # assert self.transaction_behaviour.processing is None # _start_processing self._check_start_processing_effects(self, ml_dialogue, mock_logger) def test_timeout_processing(self): """Test the _timeout_processing method of the transaction behaviour where self.processing IS None.""" # setup self.transaction_behaviour.processing_time = None # operation self.transaction_behaviour._timeout_processing() # after self.assert_quantity_in_outbox(0) def test_act_iv(self): """Test the act method of the transaction behaviour where len(waiting) == 0.""" # setup self.transaction_behaviour.processing = None self.transaction_behaviour.waiting = [] # operation self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_failed_processing(self): """Test the failed_processing method of the transaction behaviour.""" # setup ledger_api_dialogue, ml_dialogue = self._setup_ml_ledger_api_dialogues(self) self.transaction_behaviour.timedout.add(ledger_api_dialogue.dialogue_label) # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.failed_processing(ledger_api_dialogue) # after self.assert_quantity_in_outbox(0) # finish_processing assert self.transaction_behaviour.timedout == set() mock_logger.assert_any_call( logging.DEBUG, f"Timeout dialogue in transaction processing: {ledger_api_dialogue}", ) # failed_processing assert ml_dialogue in self.transaction_behaviour.waiting def test_finish_processing_i(self): """Test the finish_processing method of the transaction behaviour where self.processing == ledger_api_dialogue.""" # setup ledger_api_dialogue, ml_dialogue = self._setup_ml_ledger_api_dialogues(self) self.transaction_behaviour.processing = ledger_api_dialogue # operation self.transaction_behaviour.failed_processing(ledger_api_dialogue) # after assert self.transaction_behaviour.processing_time == 0.0 assert self.transaction_behaviour.processing is None def test_finish_processing_ii(self): """Test the finish_processing method of the transaction behaviour where ledger_api_dialogue's dialogue_label is NOT in self.timedout.""" # setup ledger_api_dialogue, ml_dialogue = self._setup_ml_ledger_api_dialogues(self) # operation with pytest.raises(ValueError) as err: self.transaction_behaviour.finish_processing(ledger_api_dialogue) # after assert ( err.value.args[0] == f"Non-matching dialogues in transaction behaviour: {self.transaction_behaviour.processing} and {ledger_api_dialogue}" ) def test_teardown(self): """Test the teardown method of the transaction behaviour.""" assert self.transaction_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_ml_train/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the ml_train skill.""" from pathlib import Path from typing import cast import pytest from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.ml_train.dialogues import ( DefaultDialogue, DefaultDialogues, LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, MlTradeDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue classes of ml_train.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_train") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.ml_dialogues = cast( MlTradeDialogues, cls._skill.skill_context.ml_trade_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_ml_dialogue(self): """Test the MlTradeDialogue class.""" ml_dialogue = MlTradeDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=MlTradeDialogue.Role.BUYER, ) # terms with pytest.raises(AEAEnforceError, match="Terms not set!"): assert ml_dialogue.terms terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) ml_dialogue.terms = terms with pytest.raises(AEAEnforceError, match="Terms already set!"): ml_dialogue.terms = terms assert ml_dialogue.terms == terms def test_ml_dialogues(self): """Test the MlTradeDialogues class.""" _, dialogue = self.ml_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=MlTradeMessage.Performative.CFP, query="some_query", ) assert dialogue.role == MlTradeDialogue.Role.BUYER assert dialogue.self_address == self.skill.skill_context.agent_address def test_ledger_api_dialogue(self): """Test the LedgerApiDialogue class.""" ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=LedgerApiDialogue.Role.AGENT, ) # associated_ml_trade_dialogue with pytest.raises(AEAEnforceError, match="MlTradeDialogue not set!"): assert ledger_api_dialogue.associated_ml_trade_dialogue ml_dialogue = MlTradeDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=MlTradeDialogue.Role.BUYER, ) ledger_api_dialogue.associated_ml_trade_dialogue = ml_dialogue with pytest.raises(AEAEnforceError, match="MlTradeDialogue already set!"): ledger_api_dialogue.associated_ml_trade_dialogue = ml_dialogue assert ledger_api_dialogue.associated_ml_trade_dialogue == ml_dialogue def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.public_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query="some_query", ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.public_id) def test_signing_dialogue(self): """Test the SigningDialogue class.""" signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=SigningDialogue.Role.SKILL, ) # associated_ledger_api_dialogue with pytest.raises(AEAEnforceError, match="LedgerApiDialogue not set!"): assert signing_dialogue.associated_ledger_api_dialogue ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=LedgerApiDialogue.Role.AGENT, ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue with pytest.raises(AEAEnforceError, match="LedgerApiDialogue already set!"): signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue assert signing_dialogue.associated_ledger_api_dialogue == ledger_api_dialogue def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms="some_terms", raw_transaction="some_raw_transaction", ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.public_id) ================================================ FILE: tests/test_packages/test_skills/test_ml_train/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the ml_train skill.""" import json import logging import uuid from pathlib import Path from typing import cast from unittest.mock import patch import numpy as np import pytest from aea.crypto.ledger_apis import LedgerApis from aea.helpers.search.models import Description from aea.helpers.transaction.base import ( RawTransaction, SignedTransaction, Terms, TransactionDigest, TransactionReceipt, ) from aea.protocols.dialogue.base import DialogueMessage from aea.skills.tasks import TaskManager from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.ml_data_provider.strategy import ( Strategy as DataProviderStrategy, ) from packages.fetchai.skills.ml_train.behaviours import TransactionBehaviour from packages.fetchai.skills.ml_train.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, MlTradeDialogue, MlTradeDialogues, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.ml_train.handlers import ( DUMMY_DIGEST, LEDGER_API_ADDRESS, LedgerApiHandler, MlTradeHandler, OEFSearchHandler, SigningHandler, ) from packages.fetchai.skills.ml_train.strategy import Strategy from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_ml_train.helpers import produce_data class TestMlTradeHandler(BaseSkillTestCase): """Test ml_trade handler of ml_train.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_train") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ml_handler = cast( MlTradeHandler, cls._skill.skill_context.handlers.ml_trade ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.ml_dialogues = cast( MlTradeDialogues, cls._skill.skill_context.ml_trade_dialogues ) cls.tx_behaviour = cast( TransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.ledger_api_dialogues = cast( MlTradeDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.task_manager = cast(TaskManager, cls._skill.skill_context.task_manager) cls.logger = cls._skill.skill_context.logger cls.batch_size = 32 cls.price_per_data_batch = 10 cls.seller_tx_fee = 0 cls.buyer_tx_fee = 0 cls.currency_id = "FET" cls.ledger_id = "FET" cls.service_id = "data_service" cls.terms = Description( { "batch_size": cls.batch_size, "price": cls.price_per_data_batch, "seller_tx_fee": cls.seller_tx_fee, "buyer_tx_fee": cls.buyer_tx_fee, "currency_id": cls.currency_id, "ledger_id": cls.ledger_id, "address": cls._skill.skill_context.agent_address, "service_id": cls.service_id, "nonce": uuid.uuid4().hex, } ) cls.list_of_messages = ( DialogueMessage(MlTradeMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage(MlTradeMessage.Performative.TERMS, {"terms": cls.terms}), DialogueMessage( MlTradeMessage.Performative.ACCEPT, {"terms": cls.terms, "tx_digest": "some_tx_digest"}, ), ) def test_setup(self): """Test the setup method of the ml_trade handler.""" assert self.ml_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ml_trade handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=MlTradeMessage, dialogue_reference=incorrect_dialogue_reference, performative=MlTradeMessage.Performative.ACCEPT, terms=self.terms, tx_digest="some_tx_digest", ) # operation with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ml_trade message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"ml_trade_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_terms_not_affordable_nor_acceptable(self): """Test the _handle_propose method of the ml_trade handler where terms is not affordable nor acceptable.""" # setup ml_dialogue = self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.TERMS, terms=self.terms, ) # operation with patch.object( self.strategy, "is_acceptable_terms", return_value=False, ) as mocked_acceptable: with patch.object( self.strategy, "is_affordable_terms", return_value=False, ) as mocked_affordable: with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after incoming_message = cast(MlTradeMessage, incoming_message) mock_logger.assert_any_call( logging.INFO, f"received terms message from {incoming_message.sender[-5:]}: terms={incoming_message.terms.values}", ) mocked_acceptable.assert_called_once() mocked_affordable.assert_called_once() mock_logger.assert_any_call( logging.INFO, "rejecting, terms are not acceptable and/or affordable", ) self.assert_quantity_in_outbox(0) def test_handle_terms_is_affordable_and_acceptable_not_ledger(self): """Test the _handle_terms method of the ml_train handler where terms is affordable and acceptable and is NOT ledger_tx.""" # setup self.strategy._is_ledger_tx = False ml_dialogue = self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.TERMS, terms=self.terms, ) # operation with patch.object( self.strategy, "is_acceptable_terms", return_value=True, ) as mocked_acceptable: with patch.object( self.strategy, "is_affordable_terms", return_value=True, ) as mocked_affordable: with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after incoming_message = cast(MlTradeMessage, incoming_message) mock_logger.assert_any_call( logging.INFO, f"received terms message from {incoming_message.sender[-5:]}: terms={incoming_message.terms.values}", ) mocked_acceptable.assert_called_once() mocked_affordable.assert_called_once() self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=MlTradeMessage, performative=MlTradeMessage.Performative.ACCEPT, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, tx_digest=DUMMY_DIGEST, terms=self.terms, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "sending dummy transaction digest ...", ) def test_handle_terms_is_affordable_and_acceptable_is_ledger(self): """Test the _handle_terms method of the ml_train handler where terms is affordable and acceptable and IS ledger_tx.""" # setup self.strategy._is_ledger_tx = True ml_dialogue = cast( MlTradeDialogue, self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:1], ), ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.TERMS, terms=self.terms, ) mocked_terms_from_proposal = "some_terms" # operation with patch.object( self.strategy, "is_acceptable_terms", return_value=True, ) as mocked_acceptable: with patch.object( self.strategy, "is_affordable_terms", return_value=True, ) as mocked_affordable: with patch.object( self.strategy, "terms_from_proposal", return_value=mocked_terms_from_proposal, ) as mocked_terms_from: with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after incoming_message = cast(MlTradeMessage, incoming_message) mock_logger.assert_any_call( logging.INFO, f"received terms message from {incoming_message.sender[-5:]}: terms={incoming_message.terms.values}", ) mocked_acceptable.assert_called_once() mocked_affordable.assert_called_once() mocked_terms_from.assert_called_with(self.terms) assert ml_dialogue.terms == mocked_terms_from_proposal assert ml_dialogue in self.tx_behaviour.waiting def test_handle_data_with_data(self): """Test the _handle_data method of the ml_trade handler where data is NOT None.""" # setup data = produce_data(self.batch_size) payload = DataProviderStrategy.encode_sample_data(data) ml_dialogue = cast( MlTradeDialogue, self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:3], ), ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.DATA, terms=self.terms, payload=payload, ) # operation with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received data message from {COUNTERPARTY_AGENT_ADDRESS[-5:]}: data shape={data[0].shape}, terms={self.terms.values}", ) assert len(self.strategy.data[0]) == len(data) assert np.array_equal(self.strategy.data[0][0], data[0]) is True assert np.array_equal(self.strategy.data[0][1], data[1]) is True assert self.strategy.is_searching is True def test_handle_data_without_data(self): """Test the _handle_data method of the ml_trade handler where data IS None.""" # setup data = None payload = json.dumps(data).encode("utf-8") ml_dialogue = cast( MlTradeDialogue, self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:3], ), ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.DATA, terms=self.terms, payload=payload, ) # operation with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received data message with no data from {COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ml_trade handler.""" # setup ml_dialogue = self.prepare_skill_dialogue( dialogues=self.ml_dialogues, messages=self.list_of_messages[:2], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ml_dialogue, performative=MlTradeMessage.Performative.ACCEPT, terms=self.terms, tx_digest="some_tx_digest", ) # operation with patch.object(self.logger, "log") as mock_logger: self.ml_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ml_trade message of performative={incoming_message.performative} in dialogue={ml_dialogue}.", ) def test_teardown(self): """Test the teardown method of the ml handler.""" assert self.ml_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(BaseSkillTestCase): """Test oef search handler of ml_train.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "ml_train") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OEFSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.logger = cls._skill.skill_context.logger cls.list_of_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} ), ) def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) def test_handle_search_zero_agents(self): """Test the _handle_search method of the oef_search handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=tuple(), agents_info=OefSearchMessage.AgentsInfo({}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found no agents in dialogue={oef_dialogue}, continue searching.", ) def test_handle_search_i(self): """Test the _handle_search method of the oef_search handler where len(agent) Tuple: """Prodice the data.""" import tensorflow as tf # pylint: disable=import-outside-toplevel ((train_x, train_y), _) = tf.keras.datasets.fashion_mnist.load_data() idx = np.arange(train_x.shape[0]) mask = np.zeros_like(idx, dtype=bool) selected = np.random.choice(idx, batch_size, replace=False) mask[selected] = True x_sample = train_x[mask] y_sample = train_y[mask] return x_sample, y_sample def test_setup(self): """Test the setup method of the MLTrainTask class.""" # operation with patch.object(self.logger, "info") as mock_logger: self.task.setup() # after mock_logger.assert_any_call("ML Train task: setup method called.") def test_make_model_i(self): """Test the make_model method of the MLTrainTask class where weights is None.""" # setup import tensorflow as tf # pylint: disable=import-outside-toplevel # operation with patch("tensorflow.keras.Sequential.set_weights") as mock_set_weights: model = self.task.make_model() # after assert isinstance(model, tf.keras.Sequential) mock_set_weights.assert_not_called() def test_make_model_ii(self): """Test the make_model method of the MLTrainTask class where weights is NOT None.""" # setup import tensorflow as tf # pylint: disable=import-outside-toplevel # before self.task.weights = [] # operation with patch("tensorflow.keras.Sequential.set_weights") as mock_set_weights: model = self.task.make_model() # after assert isinstance(model, tf.keras.Sequential) mock_set_weights.assert_any_call(self.task.weights) def test_execute(self): """Test the execute method of the MLTrainTask class.""" # before mocked_new_weights = ["new_weights"] mocked_loss = "0.1" mocked_acc = "0.8" # operation with patch.object(self.logger, "info") as mock_logger: with patch("tensorflow.keras.Sequential.fit") as mock_fit: with patch( "tensorflow.keras.Sequential.get_weights", return_value=mocked_new_weights, ) as mock_get_weights: with patch( "tensorflow.keras.Sequential.evaluate", return_value=(mocked_loss, mocked_acc), ) as mock_evaluate: actual_new_weights = self.task.execute() # after mock_fit.assert_called_with( self.task.train_x, self.task.train_y, epochs=self.task.epochs_per_batch ) mock_get_weights.assert_called_once() mock_evaluate.assert_called_with( self.task.train_x, self.task.train_y, verbose=2 ) mock_logger.assert_any_call( f"Start training with {self.task.train_x.shape[0]} rows" ) mock_logger.assert_any_call(f"Loss: {mocked_loss}, Acc: {mocked_acc}") assert actual_new_weights == mocked_new_weights def test_teardown(self): """Test the teardown method of the MLTrainTask class.""" # operation with patch.object(self.logger, "info") as mock_logger: self.task.teardown() # after mock_logger.assert_any_call("ML Train task: teardown method called.") ================================================ FILE: tests/test_packages/test_skills/test_registration_aw1/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/registration_aw1 dir.""" ================================================ FILE: tests/test_packages/test_skills/test_registration_aw1/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the tac negotiation skill.""" from pathlib import Path from typing import cast from unittest.mock import patch from aea.crypto.ledger_apis import LedgerApis from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.registration_aw1.behaviours import AW1RegistrationBehaviour from packages.fetchai.skills.registration_aw1.dialogues import ( RegisterDialogues, SigningDialogues, ) from packages.fetchai.skills.registration_aw1.handlers import ( AW1RegistrationHandler, SigningHandler, ) from packages.fetchai.skills.registration_aw1.strategy import Strategy from tests.conftest import ROOT_DIR class RegiatrationAW1TestCase(BaseSkillTestCase): """Sets the registration_aw1 class up for testing (overrides the necessary config values so tests can be done on registration_aw1 skill).""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "registration_aw1") @classmethod def setup(cls): """Setup the test class.""" cls.developer_handle = "some_developer_handle" cls.ethereum_address = "some_ethereum_address" cls.signature_of_fetchai_address = "some_signature_of_fetchai_address" cls.shared_storage_key = "some_shared_storage_key" cls.tweet = "some_tweet" cls.aw1_registration_aea = "aw1_registration_aea_1" cls.aw1_registration_aeas = {cls.aw1_registration_aea} cls.whitelist = [cls.aw1_registration_aea] config_overrides = { "models": { "strategy": { "args": { "developer_handle": cls.developer_handle, "ethereum_address": cls.ethereum_address, "signature_of_fetchai_address": cls.signature_of_fetchai_address, "shared_storage_key": cls.shared_storage_key, "whitelist": cls.whitelist, "tweet": cls.tweet, } } }, } with patch.object(LedgerApis, "is_valid_address", return_value=True): super().setup( config_overrides=config_overrides, shared_state={cls.shared_storage_key: None}, ) cls.register_behaviour = cast( AW1RegistrationBehaviour, cls._skill.skill_context.behaviours.registration, ) cls.register_handler = cast( AW1RegistrationHandler, cls._skill.skill_context.handlers.registration ) cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.register_dialogues = cast( RegisterDialogues, cls._skill.skill_context.register_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger ================================================ FILE: tests/test_packages/test_skills/test_registration_aw1/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the registration_aw1 skill.""" import logging from pathlib import Path from unittest.mock import patch from aea.helpers.transaction.base import RawMessage, Terms from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_registration_aw1.intermediate_class import ( RegiatrationAW1TestCase, ) class TestAW1Registration(RegiatrationAW1TestCase): """Test registration behaviour of registration_aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "registration_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() def test_setup_i(self): """Test the setup method of the registration behaviour NOT developer_handle_mode and announce_termination_key is None.""" # setup self.strategy.announce_termination_key = None self.strategy.developer_handle_mode = False # operation with patch.object(self.logger, "log") as mock_logger: self.register_behaviour.setup() # after self.assert_quantity_in_decision_making_queue(1) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_MESSAGE, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), raw_message=RawMessage( self.strategy.ledger_id, self.strategy.ethereum_address.encode("utf-8") ), terms=Terms( ledger_id=self.strategy.ledger_id, sender_address="", counterparty_address="", amount_by_currency_id={}, quantities_by_good_id={}, nonce="", ), ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "sending signing_msg to decision maker..." ) def test_setup_ii(self): """Test the setup method of the registration behaviour IN developer_handle_mode and announce_termination_key is NOT None.""" # setup key = "some_key" self.strategy.announce_termination_key = key self.strategy.developer_handle_only = True # operation self.register_behaviour.setup() # after self.assert_quantity_in_decision_making_queue(0) assert self.skill.skill_context.shared_state[key] is False def test_act_i(self): """Test the act method of the registration behaviour where is_ready_to_register is False.""" # setup self.strategy.is_ready_to_register = False # operation self.register_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_act_ii(self): """Test the act method of the registration behaviour where aw1_registration_aeas is None.""" # setup self.strategy.is_ready_to_register = True # operation self.register_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_act_iii(self): """Test the act method of the registration behaviour where is_registered is True.""" # setup self.strategy.is_ready_to_register = True self.skill.skill_context.shared_state[ self.shared_storage_key ] = self.aw1_registration_aeas self.strategy.is_registered = True # operation self.register_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_act_iv(self): """Test the act method of the registration behaviour where is_registration_pending is True.""" # setup self.strategy.is_ready_to_register = True self.skill.skill_context.shared_state[ self.shared_storage_key ] = self.aw1_registration_aeas self.strategy.is_registered = False self.strategy.is_registration_pending = True # operation self.register_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_act_v(self): """Test the act method of the registration behaviour where _register_for_aw1 is called.""" # setup self.strategy.is_ready_to_register = True self.skill.skill_context.shared_state[ self.shared_storage_key ] = self.aw1_registration_aeas self.strategy.is_registered = False self.strategy.is_registration_pending = False # operation with patch.object(self.logger, "log") as mock_logger: self.register_behaviour.act() # after self.assert_quantity_in_outbox(len(self.aw1_registration_aeas)) assert self.strategy.is_registration_pending is True # _register_for_aw1 info = self.strategy.registration_info message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=RegisterMessage, performative=RegisterMessage.Performative.REGISTER, to=self.aw1_registration_aea, sender=self.skill.skill_context.agent_address, info=info, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending registration info: {info}", ) def test_act_vi(self): """Test the act method of the registration behaviour where aw1 agent is NOT in the whitelist.""" # setup self.strategy.is_ready_to_register = True self.skill.skill_context.shared_state[ self.shared_storage_key ] = self.aw1_registration_aeas self.strategy.is_registered = False self.strategy.is_registration_pending = False self.strategy._whitelist = [] # operation with patch.object(self.logger, "log") as mock_logger: self.register_behaviour.act() # after self.assert_quantity_in_outbox(0) assert self.strategy.is_registration_pending is True mock_logger.assert_any_call( logging.INFO, f"agent={self.aw1_registration_aea} not in whitelist={self.strategy._whitelist}", ) def test_teardown(self): """Test the teardown method of the registration behaviour.""" assert self.register_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_registration_aw1/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the registration_aw1 skill.""" from pathlib import Path from aea.helpers.transaction.base import RawTransaction, Terms from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.registration_aw1.dialogues import ( RegisterDialogue, SigningDialogue, ) from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_registration_aw1.intermediate_class import ( RegiatrationAW1TestCase, ) class TestDialogues(RegiatrationAW1TestCase): """Test dialogue classes of registration_aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "registration_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() def test_register_dialogues(self): """Test the FipaDialogues class.""" _, dialogue = self.register_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=RegisterMessage.Performative.REGISTER, info={"some_key": "some_value"}, ) assert dialogue.role == RegisterDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms=Terms( "some_ledger_id", "some_sender_address", "some_counterparty_address", dict(), dict(), "some_nonce", ), raw_transaction=RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_registration_aw1/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the registration_aw1 skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueMessage from packages.fetchai.protocols.register.message import RegisterMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.registration_aw1.dialogues import ( RegisterDialogue, SigningDialogue, ) from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_registration_aw1.intermediate_class import ( RegiatrationAW1TestCase, ) class TestAW1RegistrationHandler(RegiatrationAW1TestCase): """Test registration handler of registration_aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "registration_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.signature_of_ethereum_address = "some_signature_of_ethereum_address" cls.info = { "ethereum_address": cls.ethereum_address, "fetchai_address": cls._skill.skill_context.agent_address, "signature_of_ethereum_address": cls.signature_of_ethereum_address, "signature_of_fetchai_address": cls.signature_of_fetchai_address, "developer_handle": cls.developer_handle, "tweet": cls.tweet, } cls.list_of_messages = ( DialogueMessage(RegisterMessage.Performative.REGISTER, {"info": cls.info}), ) def test_setup(self): """Test the setup method of the registration_aw1 handler.""" assert self.register_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the registration_aw1 handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=RegisterMessage, dialogue_reference=incorrect_dialogue_reference, performative=RegisterMessage.Performative.REGISTER, info={"some_key": "some_value"}, ) # operation with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid register_msg message={incoming_message}, unidentified dialogue.", ) def test_handle_success_i(self): """Test the _handle_success method of the registration_aw1 handler where announce_termination_key IS None.""" # setup self.strategy.announce_termination_key = None register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_messages, is_agent_to_agent_messages=True, ), ) incoming_message = cast( RegisterMessage, self.build_incoming_message_for_skill_dialogue( dialogue=register_dialogue, performative=RegisterMessage.Performative.SUCCESS, info={"transaction_digest": "some_transaction_digest"}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.DEBUG, f"received register_msg success message={incoming_message} in dialogue={register_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, f"received register message success, info={incoming_message.info}. Stop me now!", ) assert self.strategy.is_registered is True assert self.strategy.is_registration_pending is False assert self.strategy.is_ready_to_register is False def test_handle_success_ii(self): """Test the _handle_success method of the registration_aw1 handler where announce_termination_key is NOT None.""" # setup key = "some_key" self.strategy.announce_termination_key = key register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_messages, is_agent_to_agent_messages=True, ), ) incoming_message = cast( RegisterMessage, self.build_incoming_message_for_skill_dialogue( dialogue=register_dialogue, performative=RegisterMessage.Performative.SUCCESS, info={"transaction_digest": "some_transaction_digest"}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.DEBUG, f"received register_msg success message={incoming_message} in dialogue={register_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, f"received register message success, info={incoming_message.info}. Stop me now!", ) assert self.strategy.is_registered is True assert self.strategy.is_registration_pending is False assert self.strategy.is_ready_to_register is False assert self.skill.skill_context.shared_state[key] is True def test_handle_error(self): """Test the _handle_error method of the registration_aw1 handler.""" # setup register_dialogue = cast( RegisterDialogue, self.prepare_skill_dialogue( dialogues=self.register_dialogues, messages=self.list_of_messages, is_agent_to_agent_messages=True, ), ) incoming_message = cast( RegisterMessage, self.build_incoming_message_for_skill_dialogue( dialogue=register_dialogue, performative=RegisterMessage.Performative.ERROR, error_code=1, error_msg="some_error_msg", info={"some_key": "some_value"}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.DEBUG, f"received register_msg error message={incoming_message} in dialogue={register_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, f"received register message error, error_msg={incoming_message.error_msg}. Stop me now!", ) assert self.strategy.is_registration_pending is False assert self.strategy.is_ready_to_register is False def test_handle_invalid(self): """Test the _handle_invalid method of the registration_aw1 handler.""" # setup incoming_message = cast( RegisterMessage, self.build_incoming_message( message_type=RegisterMessage, performative=RegisterMessage.Performative.REGISTER, info=self.info, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.register_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) register_dialogue = self.register_dialogues.get_dialogue(incoming_message) mock_logger.assert_any_call( logging.WARNING, f"cannot handle register_msg message of performative={incoming_message.performative} in dialogue={register_dialogue}.", ) def test_teardown(self): """Test the teardown method of the registration_aw1 handler.""" assert self.register_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(RegiatrationAW1TestCase): """Test signing handler of registration_aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "registration_aw1") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_id = "some_ledger_id" cls.body_bytes = b"some_body" cls.body_str = "some_body" cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_signing_msg_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_MESSAGE, { "terms": cls.terms, "raw_message": SigningMessage.RawMessage( cls.ledger_id, cls.body_bytes ), }, ), ) def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_message(self): """Test the _handle_signed_message method of the signing handler.""" # setup signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_msg_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_MESSAGE, signed_message=SigningMessage.SignedMessage( self.ledger_id, self.body_str ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.DEBUG, f"received signing message from decision maker, message={incoming_message} in dialogue={signing_dialogue}", ) mock_logger.assert_any_call( logging.INFO, f"received signing message from decision maker, signature={incoming_message.signed_message.body} stored!", ) assert self.strategy.signature_of_ethereum_address == self.body_str assert self.strategy.is_ready_to_register is True def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_msg_messages[:1], counterparty=signing_counterparty, ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.terms, raw_transaction=SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={incoming_message.performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_registration_aw1/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the registration_aw1 skill.""" from pathlib import Path from unittest.mock import patch from aea.crypto.ledger_apis import LedgerApis from packages.fetchai.skills.registration_aw1.strategy import Strategy from tests.conftest import ROOT_DIR from tests.test_packages.test_skills.test_registration_aw1.intermediate_class import ( RegiatrationAW1TestCase, ) class TestStrategy(RegiatrationAW1TestCase): """Test Strategy of registration_aw1.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "registration_aw1") @classmethod def setup(cls): """Setup the test class.""" super().setup() def test__init__i(self): """Test __init__ where developer_handle_only is False.""" announce_termination_key = True developer_handle_only = False with patch.object(LedgerApis, "is_valid_address", return_value=True): strategy = Strategy( developer_handle=self.developer_handle, ethereum_address=self.ethereum_address, signature_of_fetchai_address=self.signature_of_fetchai_address, shared_storage_key=self.shared_storage_key, whitelist=self.whitelist, tweet=self.tweet, announce_termination_key=announce_termination_key, developer_handle_only=developer_handle_only, name=Strategy, skill_context=self.skill.skill_context, ) assert strategy._developer_handle == self.developer_handle assert strategy._whitelist == self.whitelist assert strategy._shared_storage_key == self.shared_storage_key assert strategy.announce_termination_key == announce_termination_key assert strategy.developer_handle_only == developer_handle_only assert strategy._ethereum_address == self.ethereum_address assert ( strategy._signature_of_fetchai_address == self.signature_of_fetchai_address ) assert strategy._tweet == self.tweet assert strategy._is_ready_to_register is False assert strategy._is_registered is False assert strategy.is_registration_pending is False assert strategy.signature_of_ethereum_address is None assert strategy._ledger_id == self.skill.skill_context.default_ledger_id def test__init__ii(self): """Test __init__ where developer_handle_only is True.""" announce_termination_key = False developer_handle_only = True strategy = Strategy( developer_handle=self.developer_handle, ethereum_address=self.ethereum_address, signature_of_fetchai_address=self.signature_of_fetchai_address, shared_storage_key=self.shared_storage_key, whitelist=self.whitelist, tweet=self.tweet, announce_termination_key=announce_termination_key, developer_handle_only=developer_handle_only, name=Strategy, skill_context=self.skill.skill_context, ) assert strategy._developer_handle == self.developer_handle assert strategy._whitelist == self.whitelist assert strategy._shared_storage_key == self.shared_storage_key assert strategy.announce_termination_key == announce_termination_key assert strategy.developer_handle_only == developer_handle_only assert strategy._is_ready_to_register is True assert strategy._ethereum_address == "some_dummy_address" assert strategy._signature_of_fetchai_address is None assert strategy._tweet is None assert strategy._is_registered is False assert strategy.is_registration_pending is False assert strategy.signature_of_ethereum_address is None assert strategy._ledger_id == self.skill.skill_context.default_ledger_id def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.shared_storage_key == self.shared_storage_key assert self.strategy.whitelist == self.whitelist assert self.strategy.ethereum_address == self.ethereum_address assert self.strategy.ledger_id == self.skill.skill_context.default_ledger_id assert self.strategy.is_registration_pending is False assert self.strategy.signature_of_ethereum_address is None assert self.strategy.is_ready_to_register is False self.strategy.is_ready_to_register = True assert self.strategy.is_ready_to_register is True assert self.strategy.is_registered is False self.strategy.is_registered = True assert self.strategy.is_registered is True info_i = { "ethereum_address": self.ethereum_address, "fetchai_address": self.skill.skill_context.agent_address, "signature_of_ethereum_address": self.strategy.signature_of_ethereum_address, "signature_of_fetchai_address": self.signature_of_fetchai_address, "developer_handle": self.developer_handle, "tweet": self.tweet, } assert self.strategy.registration_info == info_i self.strategy._tweet = "PUT_THE_LINK_TO_YOUR_TWEET_HERE" info_ii = { "ethereum_address": self.ethereum_address, "fetchai_address": self.skill.skill_context.agent_address, "signature_of_ethereum_address": self.strategy.signature_of_ethereum_address, "signature_of_fetchai_address": self.signature_of_fetchai_address, "developer_handle": self.developer_handle, } assert self.strategy.registration_info == info_ii info_iii = { "fetchai_address": self.skill.skill_context.agent_address, "developer_handle": self.developer_handle, } self.strategy.developer_handle_only = True assert self.strategy.registration_info == info_iii ================================================ FILE: tests/test_packages/test_skills/test_simple_aggregation/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_aggregation dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_aggregation/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the advanced data request skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea_ledger_fetchai import FetchAICrypto from aea.helpers.search.models import Description from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.aggregation.message import AggregationMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_aggregation.behaviours import ( AggregationBehaviour, DEFAULT_SIGNATURE, DEFAULT_SOURCE, SearchBehaviour, ) from packages.fetchai.skills.simple_aggregation.strategy import AggregationStrategy from tests.conftest import ROOT_DIR DATA_REQUEST_OBS = {"some_quantity": {"value": 100, "decimals": 0}} OBSERVATION = { "value": 100, "time": "some_time", "signature": DEFAULT_SIGNATURE, "source": DEFAULT_SOURCE, } LEDGER_ID = FetchAICrypto.identifier PEERS = ("peer1", "peer2") class TestAggregationBehaviour(BaseSkillTestCase): """Test aggregation behaviour of simple aggregation skill.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_aggregation" ) @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.aggregation_behaviour = cast( AggregationBehaviour, cls._skill.skill_context.behaviours.aggregation, ) cls.aggregation_strategy = cast( AggregationStrategy, cls.aggregation_behaviour.context.strategy, ) cls.aggregation_strategy.url = "some_url" cls.aggregation_strategy._quantity_name = "some_quantity" cls.agent_address = cls.aggregation_behaviour.context.agent_addresses[LEDGER_ID] def test_act(self): """Test the act method of the aggregation behaviour.""" with patch.object( self.aggregation_behaviour.context.logger, "log" ) as mock_logger: self.aggregation_behaviour.act() mock_logger.assert_any_call( logging.INFO, "No observation to send", ) self.assert_quantity_in_outbox(0) # mock an observation self.aggregation_behaviour.context.shared_state[ "some_quantity" ] = DATA_REQUEST_OBS["some_quantity"] self.aggregation_strategy.add_peers(PEERS) assert all([peer in self.aggregation_strategy.peers for peer in PEERS]) with patch.object( self.aggregation_behaviour.context.logger, "log" ) as mock_logger: self.aggregation_behaviour.act() mock_logger.assert_any_call( logging.INFO, f"sending observation to peer={PEERS[0]}", ) mock_logger.assert_any_call( logging.INFO, f"sending observation to peer={PEERS[1]}", ) obs = self.aggregation_strategy.observation assert obs["value"] == OBSERVATION["value"] # test broadcast_observation self.assert_quantity_in_outbox(2) msg = cast(AggregationMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=AggregationMessage, performative=AggregationMessage.Performative.OBSERVATION, value=100, source=DEFAULT_SOURCE, signature=DEFAULT_SIGNATURE, ) assert has_attributes, error_str class TestSearchBehaviour(BaseSkillTestCase): """Test search behaviour of simple aggregation skill.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_aggregation" ) @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.search_behaviour = cast( SearchBehaviour, cls._skill.skill_context.behaviours.search, ) cls.aggregation_strategy = cast( AggregationStrategy, cls.search_behaviour.context.strategy, ) cls.aggregation_strategy.url = "some_url" cls.aggregation_strategy._quantity_name = "some_quantity" cls.agent_address = cls.search_behaviour.context.agent_addresses[LEDGER_ID] cls.logger = cls._skill.skill_context.logger cls.registration_message = OefSearchMessage( dialogue_reference=("", ""), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description="some_service_description", ) cls.registration_message.sender = str(cls._skill.skill_context.skill_id) cls.registration_message.to = cls._skill.skill_context.search_service_address cls.mocked_description = Description({"foo1": 1, "bar1": 2}) def test_setup(self): """Test the setup method of the search behaviour.""" with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.setup() mock_logger.assert_any_call( logging.INFO, "registering agent on SOEF.", ) description = self.aggregation_strategy.get_location_description() # test SOEF messages self.assert_quantity_in_outbox(1) msg = cast(OefSearchMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) assert has_attributes, error_str def test_act_i(self): """Test the act method of the search behaviour where failed_registration_msg IS None.""" query = self.aggregation_strategy.get_location_and_service_query() self.search_behaviour.act() # test SOEF search query self.assert_quantity_in_outbox(1) msg = cast(OefSearchMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) assert has_attributes, error_str def test_act_ii(self): """Test the act method of the service_registration behaviour where failed_registration_msg is NOT None.""" # setup self.search_behaviour.failed_registration_msg = self.registration_message query = self.aggregation_strategy.get_location_and_service_query() # operation with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.act() # after self.assert_quantity_in_outbox(2) # _retry_failed_registration has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=type(self.registration_message), performative=self.registration_message.performative, to=self.registration_message.to, sender=str(self.skill.skill_context.skill_id), service_description=self.registration_message.service_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Retrying registration on SOEF. Retry {self.search_behaviour._nb_retries} out of {self.search_behaviour._max_soef_registration_retries}.", ) assert self.search_behaviour.failed_registration_msg is None # act msg = cast(OefSearchMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) assert has_attributes, error_str def test_act_iii(self): """Test the act method of the service_registration behaviour where failed_registration_msg is NOT None and max retries is reached.""" # setup self.search_behaviour.failed_registration_msg = self.registration_message self.search_behaviour._max_soef_registration_retries = 2 self.search_behaviour._nb_retries = 2 query = self.aggregation_strategy.get_location_and_service_query() # operation self.search_behaviour.act() # after self.assert_quantity_in_outbox(1) msg = cast(OefSearchMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) assert has_attributes, error_str assert self.skill.skill_context.is_active is False def test_register_service(self): """Test the register_service method of the service_registration behaviour.""" # operation with patch.object( self.aggregation_strategy, "get_register_service_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.register_service() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's service on the SOEF." ) def test_register_genus(self): """Test the register_genus method of the service_registration behaviour.""" # operation with patch.object( self.aggregation_strategy, "get_register_personality_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.register_genus() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality genus on the SOEF." ) def test_register_classification(self): """Test the register_classification method of the service_registration behaviour.""" # operation with patch.object( self.aggregation_strategy, "get_register_classification_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.register_classification() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality classification on the SOEF." ) def test_teardown(self): """Test the teardown method of the search behaviour.""" with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.teardown() mock_logger.assert_any_call( logging.INFO, "unregistering agent from SOEF.", ) mock_logger.assert_any_call( logging.INFO, "unregistering service from SOEF.", ) descriptions = [ self.aggregation_strategy.get_unregister_service_description(), self.aggregation_strategy.get_location_description(), ] # test SOEF messages self.assert_quantity_in_outbox(len(descriptions)) for description in descriptions: msg = cast(OefSearchMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) assert has_attributes, error_str ================================================ FILE: tests/test_packages/test_skills/test_simple_aggregation/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the simple_data_request skill.""" import logging import statistics from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import Attribute, DataModel, Description, Location from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.aggregation.message import AggregationMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_aggregation.behaviours import ( DEFAULT_SIGNATURE, DEFAULT_SOURCE, SearchBehaviour, ) from packages.fetchai.skills.simple_aggregation.dialogues import ( AggregationDialogues, OefSearchDialogues, ) from packages.fetchai.skills.simple_aggregation.handlers import ( AggregationHandler, OefSearchHandler, get_observation_from_message, ) from packages.fetchai.skills.simple_aggregation.strategy import AggregationStrategy from tests.conftest import ROOT_DIR class TestAggregationHandler(BaseSkillTestCase): """Test aggregation handler of simple_aggregation skill.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_aggregation" ) is_agent_to_agent_messages = False @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.aggregation_handler = cast( AggregationHandler, cls._skill.skill_context.handlers.aggregation ) cls.logger = cls._skill.skill_context.logger cls.aggregation_strategy = cast( AggregationStrategy, cls._skill.skill_context.strategy, ) cls.aggregation_dialogues = cast( AggregationDialogues, cls._skill.skill_context.aggregation_dialogues ) def test_setup(self): """Test the setup method of the aggregation handler.""" assert self.aggregation_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_observation(self): """Test the _handle_observation method of the aggregation handler to a valid observation.""" self.aggregation_strategy._quantity_name = "some_quantity" sender = "some_sender_address" sender_value = 100 my_value = 50 values = [float(my_value), float(sender_value)] aggregate = getattr(statistics, self.aggregation_strategy._aggregation_function) incoming_message = self.build_incoming_message( message_type=AggregationMessage, performative=AggregationMessage.Performative.OBSERVATION, sender=sender, to=self._skill.skill_context.agent_address, value=sender_value, time="some_time", source=DEFAULT_SOURCE, signature=DEFAULT_SIGNATURE, ) self.aggregation_strategy.add_peers((sender,)) assert sender in self.aggregation_strategy._peers self.aggregation_strategy.make_observation(my_value, "some_time") # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.aggregation_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received observation from sender={sender[-5:]}" ) expected_aggregation = {"value": aggregate(values), "decimals": 0} aggregated_key = ( "some_quantity" + "_" + self.aggregation_strategy._aggregation_function ) obs = get_observation_from_message(incoming_message) assert len(self.aggregation_strategy._observations) == 2 assert self.aggregation_strategy._observations[sender] == obs assert self.aggregation_strategy._aggregation == aggregate(values) assert ( self.aggregation_handler.context.shared_state[aggregated_key] == expected_aggregation ) mock_logger.assert_any_call(logging.INFO, f"Observations: {values}") mock_logger.assert_any_call( logging.INFO, f"Aggregation ({self.aggregation_strategy._aggregation_function}): {self.aggregation_strategy._aggregation}", ) def test_handle_aggregation(self): """Test the _handle_aggregation method of the aggregation handler to a valid aggregation.""" sender = "some_sender_address" sender_value = 75 incoming_message = self.build_incoming_message( message_type=AggregationMessage, performative=AggregationMessage.Performative.AGGREGATION, sender=sender, to=self._skill.skill_context.agent_address, value=sender_value, time="some_time", contributors=(sender, "some_other_peer"), signature=DEFAULT_SIGNATURE, ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.aggregation_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received aggregation from sender={sender[-5:]}" ) def test_handle_unidentified_dialogue(self): """Test the _handle_response method of the aggregation handler to an unidentified dialogue.""" # setup incoming_message = self.build_incoming_message( message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.aggregation_strategy.get_location_and_service_query(), sender="some_sender", to=self._skill.skill_context.agent_address, ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.aggregation_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid aggregation message={incoming_message}, unidentified dialogue.", ) def test_teardown(self): """Test the teardown method of the aggregation handler.""" assert self.aggregation_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(BaseSkillTestCase): """Test oef search handler of simple aggregation skill.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_aggregation" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.strategy = cast( AggregationStrategy, cls._skill.skill_context.strategy, ) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.service_registration_behaviour = cast( SearchBehaviour, cls._skill.skill_context.behaviours.search, ) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) cls.unregister_description = Description( {"key": "seller_service"}, data_model=DataModel("remove", [Attribute("key", str, True)]), ) cls.list_of_messages_unregister = ( DialogueMessage( OefSearchMessage.Performative.UNREGISTER_SERVICE, {"service_description": cls.unregister_description}, is_incoming=False, ), ) cls.list_of_messages_search = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": cls.strategy.get_location_and_service_query()}, is_incoming=False, ), ) def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.strategy.get_location_and_service_query(), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_service", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH set_service_key data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_service[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iv(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, and its service are successfully registered on the SOEF.", ) def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef successtargets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) def test_handle_error_i(self): """Test the _handle_error method of the oef_search handler where the oef error targets register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert ( self.service_registration_behaviour.failed_registration_msg == oef_dialogue.get_message_by_id(incoming_message.target) ) def test_handle_error_ii(self): """Test the _handle_error method of the oef_search handler where the oef error does NOT target register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_unregister[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert self.service_registration_behaviour.failed_registration_msg is None def test_handle_search_no_agents(self): """Test the _handle_search method of the oef_search handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_search[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=tuple(), agents_info=OefSearchMessage.AgentsInfo({}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found no agents in dialogue={oef_dialogue}, continue searching.", ) def test_handle_search_found_agents(self): """Test the _handle_search method of the oef_search handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_search[:1], ) agents = ("agnt1", "agnt2") incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( {"agent_1": {"key_1": "value_1"}, "agent_2": {"key_2": "value_2"}} ), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"found agents={list(agents)}.") assert len(self.strategy._peers) == 2 for agent in agents: assert agent in self.strategy._peers def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=("1", ""), performative=invalid_performative, service_description=self.strategy.get_unregister_service_description(), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_aggregation/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the simple_aggregation skill.""" from pathlib import Path from typing import cast from aea.helpers.search.models import Description from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.simple_aggregation.strategy import ( AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, AggregationStrategy, ) from tests.conftest import ROOT_DIR class TestAggregationStrategy(BaseSkillTestCase): """Test AggregationStrategy of simple_aggregation.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_aggregation" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.strategy = cast(AggregationStrategy, cls._skill.skill_context.strategy) def test_get_register_service_description(self): """Test the get_register_service_description method of the GenericStrategy class.""" description = self.strategy.get_register_service_description() assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert description.values.get("key", "") == "service" assert description.values.get("value", "") == "generic_aggregation_service" def test_get_register_personality_description(self): """Test the get_register_personality_description method of the GenericStrategy class.""" description = self.strategy.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "genus" assert description.values.get("value", "") == "data" def test_get_register_classification_description(self): """Test the get_register_classification_description method of the GenericStrategy class.""" description = self.strategy.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "classification" assert description.values.get("value", "") == "agent" def test_get_unregister_service_description(self): """Test the get_unregister_service_description method of the GenericStrategy class.""" description = self.strategy.get_unregister_service_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == "service" ================================================ FILE: tests/test_packages/test_skills/test_simple_buyer/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_buyer dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_data_request/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_data_request dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_data_request/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the simple_data_request skill.""" from pathlib import Path from aea.test_tools.test_skill import BaseSkillTestCase from tests.conftest import ROOT_DIR class SimpleDataRequestTestCase(BaseSkillTestCase): """Sets the simple_data_request class up for testing (overrides the necessary config values so tests can be done on simple_data_request skill).""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_data_request" ) @classmethod def setup(cls): """Setup the test class.""" cls.mocked_method = "some_method" cls.mocked_url = "some_url" cls.mocked_shared_state_key = "some_name_for_data" config_overrides = { "behaviours": { "http_request": { "args": {"method": cls.mocked_method, "url": cls.mocked_url} } }, "handlers": { "http": {"args": {"shared_state_key": cls.mocked_shared_state_key}} }, } super().setup(config_overrides=config_overrides) ================================================ FILE: tests/test_packages/test_skills/test_simple_data_request/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the simple_data_request skill.""" import json from typing import cast import pytest from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.simple_data_request.behaviours import ( HTTP_CLIENT_PUBLIC_ID, HttpRequestBehaviour, ) from tests.test_packages.test_skills.test_simple_data_request.intermediate_class import ( SimpleDataRequestTestCase, ) class TestHttpRequestBehaviour(SimpleDataRequestTestCase): """Test http_request behaviour of http_request.""" is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.http_request_behaviour = cast( HttpRequestBehaviour, cls._skill.skill_context.behaviours.http_request ) def test__init__i(self): """Test the __init__ method of the http_request behaviour.""" assert self.http_request_behaviour.url == "some_url" assert self.http_request_behaviour.method == "some_method" assert self.http_request_behaviour.body == "" def test__init__ii(self): """Test the __init__ method of the http_request behaviour where ValueError is raise.""" with pytest.raises(ValueError, match="Url, method and body must be provided."): self.http_request_behaviour.__init__( url=None, method="some_method", body="some_body" ) def test_setup(self): """Test the setup method of the http_request behaviour.""" assert self.http_request_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act_i(self): """Test the act method of the http_request behaviour where lookup_termination_key IS None.""" # setup self.http_request_behaviour.lookup_termination_key = None # operation self.http_request_behaviour.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, to=str(HTTP_CLIENT_PUBLIC_ID), sender=str(self.skill.skill_context.skill_id), method=self.mocked_method, url=self.mocked_url, headers="", version="", body=json.dumps(self.http_request_behaviour.body).encode("utf-8"), ) assert has_attributes, error_str def test_act_ii(self): """Test the act method of the http_request behaviour where lookup_termination_key is NOT None and lookup_termination_key is False.""" # setup key = "some_key" self.http_request_behaviour.lookup_termination_key = key self.skill.skill_context.shared_state[key] = False # operation self.http_request_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_act_iii(self): """Test the act method of the http_request behaviour where lookup_termination_key is NOT None and lookup_termination_key is True.""" # setup key = "some_key" self.http_request_behaviour.lookup_termination_key = key self.skill.skill_context.shared_state[key] = True # operation self.http_request_behaviour.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, to=str(HTTP_CLIENT_PUBLIC_ID), sender=str(self.skill.skill_context.skill_id), method=self.mocked_method, url=self.mocked_url, headers="", version="", body=json.dumps(self.http_request_behaviour.body).encode("utf-8"), ) assert has_attributes, error_str def test_teardown(self): """Test the teardown method of the http_request behaviour.""" assert self.http_request_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_data_request/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the simple_data_request skill.""" from typing import cast from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.simple_data_request.dialogues import ( HttpDialogue, HttpDialogues, ) from tests.test_packages.test_skills.test_simple_data_request.intermediate_class import ( SimpleDataRequestTestCase, ) class TestDialogues(SimpleDataRequestTestCase): """Test dialogue class of simple_data_request.""" @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) def test_http_dialogues(self): """Test the HttpDialogues class.""" _, dialogue = self.http_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=HttpMessage.Performative.REQUEST, method="some_method", url="some_url", version="some_version", headers="some_headers", body=b"some_body", ) assert dialogue.role == HttpDialogue.Role.CLIENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_simple_data_request/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the simple_data_request skill.""" import logging from typing import cast from unittest.mock import patch import pytest from aea.protocols.dialogue.base import DialogueMessage from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.skills.simple_data_request.dialogues import HttpDialogues from packages.fetchai.skills.simple_data_request.handlers import HttpHandler from tests.test_packages.test_skills.test_simple_data_request.intermediate_class import ( SimpleDataRequestTestCase, ) class TestHttpHandler(SimpleDataRequestTestCase): """Test http handler of simple_data_request.""" is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.http_handler = cast(HttpHandler, cls._skill.skill_context.handlers.http) cls.logger = cls._skill.skill_context.logger cls.http_dialogues = cast( HttpDialogues, cls._skill.skill_context.http_dialogues ) cls.data = b"some_body" cls.list_of_messages = ( DialogueMessage( HttpMessage.Performative.REQUEST, { "method": "some_method", "url": "some_url", "headers": "some_headers", "version": "some_version", "body": b"some_body", }, ), ) def test__init__(self): """Test the __init__ method of the http handler where ValueError is raise.""" with pytest.raises(ValueError, match="No shared_state_key provided!"): self.http_handler.__init__(shared_state_key=None) def test_setup(self): """Test the setup method of the http handler.""" assert self.http_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the http handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=HttpMessage, dialogue_reference=incorrect_dialogue_reference, performative=HttpMessage.Performative.RESPONSE, method="some_method", url="some_url", headers="some_headers", version="some_version", body=b"some_body", ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid http message={incoming_message}, unidentified dialogue.", ) def test_handle_response(self): """Test the _handle_response method of the http handler.""" # setup http_dialogue = self.prepare_skill_dialogue( dialogues=self.http_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=http_dialogue, performative=HttpMessage.Performative.RESPONSE, method="some_method", url="some_url", headers="some_headers", version="some_version", body=self.data, ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.DEBUG, f"received http response={incoming_message} in dialogue={http_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "updating shared_state with received data=b'some_body'!", ) assert ( self.skill.skill_context._agent_context.shared_state[ self.mocked_shared_state_key ] == self.data ) def test_handle_invalid(self): """Test the _handle_invalid method of the http handler.""" # setup incoming_message = self.build_incoming_message( message_type=HttpMessage, performative=HttpMessage.Performative.REQUEST, method="some_method", url="some_url", headers="some_headers", version="some_version", body=self.data, ) # operation with patch.object(self.logger, "log") as mock_logger: self.http_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle http message of performative={incoming_message.performative} in dialogue={self.http_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the http handler.""" assert self.http_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_oracle/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_oracle dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_oracle/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the simple oracle skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.contracts.oracle.contract import PUBLIC_ID as CONTRACT_PUBLIC_ID from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.skills.simple_oracle.behaviours import SimpleOracleBehaviour from packages.fetchai.skills.simple_oracle.dialogues import PrometheusDialogues from packages.fetchai.skills.simple_oracle.strategy import Strategy from tests.conftest import ROOT_DIR ETHEREUM_LEDGER_ID = "ethereum" FETCHAI_LEDGER_ID = "fetchai" AGENT_ADDRESS = "some_eth_address" DEFAULT_ADDRESS = "0x0000000000000000000000000000000000000000" class TestSkillBehaviour(BaseSkillTestCase): """Test behaviours of simple oracle.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle") @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.simple_oracle_behaviour = cast( SimpleOracleBehaviour, cls._skill.skill_context.behaviours.simple_oracle_behaviour, ) def test_setup(self): """Test the setup method of the simple_oracle behaviour.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_behaviour.setup() self.assert_quantity_in_outbox(3) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", ) assert has_attributes, error_str msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.ADD_METRIC, type="Gauge", title="oracle_account_balance_ETH", description="Balance of oracle contract (ETH)", ) assert has_attributes, error_str msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.ADD_METRIC, type="Gauge", title="num_oracle_updates", description="Number of updates published to oracle contract", ) assert has_attributes, error_str def test_setup_with_contract_set(self): """Test the setup method of the simple_oracle behaviour for existing contract.""" prometheus_dialogues = cast( PrometheusDialogues, self.simple_oracle_behaviour.context.prometheus_dialogues, ) prometheus_dialogues.enabled = False strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.contract_address = DEFAULT_ADDRESS strategy.is_contract_deployed = True with patch.object( self.simple_oracle_behaviour.context.logger, "log" ) as mock_logger: self.simple_oracle_behaviour.setup() mock_logger.assert_any_call( logging.INFO, "Fetch oracle contract address already added", ) self.assert_quantity_in_outbox(0) def test_setup_with_contract_set_and_oracle_role_granted(self): """Test the setup method of the simple_oracle behaviour for existing contract and oracle role.""" prometheus_dialogues = cast( PrometheusDialogues, self.simple_oracle_behaviour.context.prometheus_dialogues, ) prometheus_dialogues.enabled = False strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.contract_address = DEFAULT_ADDRESS strategy.is_contract_deployed = True strategy.is_oracle_role_granted = True assert strategy.erc20_address == DEFAULT_ADDRESS with patch.object( self.simple_oracle_behaviour.context.logger, "log" ) as mock_logger: self.simple_oracle_behaviour.setup() mock_logger.assert_any_call( logging.INFO, "Oracle role already granted", ) self.assert_quantity_in_outbox(0) def test_act_pre_deploy(self): """Test the act method of the simple_oracle behaviour before contract is deployed.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_behaviour.context.agent_addresses[ ETHEREUM_LEDGER_ID ] = "AGENT_ADDRESS" self.simple_oracle_behaviour.act() self.assert_quantity_in_outbox(1) msg = cast(LedgerApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ETHEREUM_LEDGER_ID, address=cast( str, self.simple_oracle_behaviour.context.agent_addresses.get( ETHEREUM_LEDGER_ID ), ), ) assert has_attributes, error_str def test_act_grant_role(self): """Test the act method of the simple_oracle behaviour before role is granted.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.contract_address = DEFAULT_ADDRESS strategy._ledger_id = ETHEREUM_LEDGER_ID strategy.is_contract_deployed = True self.simple_oracle_behaviour.context.agent_addresses[ ETHEREUM_LEDGER_ID ] = "AGENT_ADDRESS" self.simple_oracle_behaviour.act() self.assert_quantity_in_outbox(2) msg = cast(LedgerApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ETHEREUM_LEDGER_ID, address=cast( str, self.simple_oracle_behaviour.context.agent_addresses.get( ETHEREUM_LEDGER_ID ), ), ) assert has_attributes, error_str msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, contract_id=str(CONTRACT_PUBLIC_ID), contract_address=strategy.contract_address, callable="get_grant_role_transaction", ) assert has_attributes, error_str def test_act_update(self): """Test the act method of the simple_oracle behaviour for normal updating.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.contract_address = DEFAULT_ADDRESS strategy._ledger_id = ETHEREUM_LEDGER_ID strategy.is_contract_deployed = True strategy.is_oracle_role_granted = True strategy._oracle_value_name = "oracle_value" self.simple_oracle_behaviour.context.shared_state["oracle_value"] = { "some_key": "some_value" } self.simple_oracle_behaviour.context.agent_addresses[ ETHEREUM_LEDGER_ID ] = "AGENT_ADDRESS" self.simple_oracle_behaviour.act() self.assert_quantity_in_outbox(2) msg = cast(LedgerApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ETHEREUM_LEDGER_ID, address=cast( str, self.simple_oracle_behaviour.context.agent_addresses.get( ETHEREUM_LEDGER_ID ), ), ) assert has_attributes, error_str msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, contract_id=str(CONTRACT_PUBLIC_ID), contract_address=strategy.contract_address, callable="get_update_transaction", ) assert has_attributes, error_str def test_act_no_oracle_value(self): """Test the act method of the simple_oracle behaviour when no oracle value is present.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.contract_address = DEFAULT_ADDRESS strategy._ledger_id = ETHEREUM_LEDGER_ID strategy.is_contract_deployed = True strategy.is_oracle_role_granted = True self.simple_oracle_behaviour.context.agent_addresses[ ETHEREUM_LEDGER_ID ] = "AGENT_ADDRESS" self.simple_oracle_behaviour.act() self.assert_quantity_in_outbox(1) msg = cast(LedgerApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ETHEREUM_LEDGER_ID, address=cast( str, self.simple_oracle_behaviour.context.agent_addresses.get( ETHEREUM_LEDGER_ID ), ), ) assert has_attributes, error_str def test__request_contract_deploy_transaction(self): """Test that the _request_contract_deploy_transaction function sends the right message to the contract_api for ethereum ledger.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_behaviour._request_contract_deploy_transaction() self.assert_quantity_in_outbox(1) kwargs = strategy.get_deploy_kwargs() assert "ERC20Address" in kwargs.body assert "initialFee" in kwargs.body msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=kwargs, ) assert has_attributes, error_str def test__request_contract_store_transaction(self): """Test that the _request_contract_deploy_transaction function sends the right message to the contract_api for fetchai ledger.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy._ledger_id = FETCHAI_LEDGER_ID self.simple_oracle_behaviour._request_contract_deploy_transaction() self.assert_quantity_in_outbox(1) kwargs = strategy.get_deploy_kwargs() assert "ERC20Address" not in kwargs.body assert "initialFee" not in kwargs.body msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=kwargs, ) assert has_attributes, error_str def test__request_grant_role_transaction(self): """Test that the _request_grant_role_transaction function sends the right message to the contract_api.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.contract_address = DEFAULT_ADDRESS strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_behaviour._request_grant_role_transaction() self.assert_quantity_in_outbox(1) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, contract_id=str(CONTRACT_PUBLIC_ID), contract_address=strategy.contract_address, callable="get_grant_role_transaction", ) assert has_attributes, error_str def test__request_update_transaction(self): """Test that the _request_update_transaction function sends the right message to the contract_api.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.contract_address = DEFAULT_ADDRESS strategy._ledger_id = ETHEREUM_LEDGER_ID update_args = {"some": "args"} self.simple_oracle_behaviour._request_update_transaction(update_args) self.assert_quantity_in_outbox(1) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, contract_id=str(CONTRACT_PUBLIC_ID), contract_address=strategy.contract_address, callable="get_update_transaction", ) assert has_attributes, error_str def test__get_balance(self): """Test that the _get_balance function sends the right message to the ledger_api.""" strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_behaviour.context.agent_addresses[ ETHEREUM_LEDGER_ID ] = "AGENT_ADDRESS" self.simple_oracle_behaviour._get_balance() self.assert_quantity_in_outbox(1) msg = cast(LedgerApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ETHEREUM_LEDGER_ID, address=cast( str, self.simple_oracle_behaviour.context.agent_addresses.get( ETHEREUM_LEDGER_ID ), ), ) assert has_attributes, error_str def test_add_prometheus_metric(self): """Test the send_http_request_message method of the simple_oracle behaviour.""" self.simple_oracle_behaviour.add_prometheus_metric( "some_metric", "Gauge", "some_description", {"label_key": "label_value"} ) self.assert_quantity_in_outbox(1) msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.ADD_METRIC, type="Gauge", title="some_metric", description="some_description", labels={"label_key": "label_value"}, ) assert has_attributes, error_str def test_update_prometheus_metric(self): """Test the test_update_prometheus_metric method of the simple_oracle behaviour.""" self.simple_oracle_behaviour.update_prometheus_metric( "some_metric", "set", 0.0, {"label_key": "label_value"} ) self.assert_quantity_in_outbox(1) msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.UPDATE_METRIC, callable="set", title="some_metric", value=0.0, labels={"label_key": "label_value"}, ) assert has_attributes, error_str def test_teardown(self): """Test that the teardown method of the simple_oracle behaviour leaves no messages in the outbox.""" assert self.simple_oracle_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_oracle/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the simple_data_request skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.contracts.oracle.contract import PUBLIC_ID as CONTRACT_PUBLIC_ID from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.custom_types import ( Kwargs as ContractApiKwargs, ) from packages.fetchai.protocols.contract_api.custom_types import State from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.custom_types import ( SignedTransaction, TransactionDigest, TransactionReceipt, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.prometheus.message import PrometheusMessage from packages.fetchai.protocols.signing.custom_types import RawTransaction from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.simple_oracle.behaviours import SimpleOracleBehaviour from packages.fetchai.skills.simple_oracle.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogue, LedgerApiDialogues, PrometheusDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.simple_oracle.handlers import ( ContractApiHandler, LedgerApiHandler, PrometheusHandler, SigningHandler, ) from packages.fetchai.skills.simple_oracle.strategy import Strategy from tests.conftest import ROOT_DIR ETHEREUM_LEDGER_ID = "ethereum" FETCHAI_LEDGER_ID = "fetchai" DEFAULT_TX = {"some_tx_key": "some_tx_value"} DEFAULT_TERMS = [ ETHEREUM_LEDGER_ID, "sender_address", "counterparty_address", {}, {}, "some_nonce", ] FETCHAI_DEPLOY_RECEIPT = { "logs": [ { "events": [ { "attributes": [ {"key": "code_id", "value": "8888"}, {"key": "_contract_address", "value": "some_contract_address"}, ] } ] } ] } class TestLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of simple_oracle skill.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle") is_agent_to_agent_messages = False @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup(**kwargs) cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.logger = cls._skill.skill_context.logger cls.simple_oracle_behaviour = cast( SimpleOracleBehaviour, cls._skill.skill_context.behaviours.simple_oracle_behaviour, ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": ETHEREUM_LEDGER_ID, "address": "some_eth_address"}, ), DialogueMessage( LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, { "signed_transaction": SignedTransaction( ETHEREUM_LEDGER_ID, DEFAULT_TX ) }, ), DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, { "transaction_digest": TransactionDigest( ETHEREUM_LEDGER_ID, "some_digest" ) }, ), DialogueMessage( LedgerApiMessage.Performative.GET_STATE, { "ledger_id": ETHEREUM_LEDGER_ID, "callable": "some_callable", "args": (), "kwargs": LedgerApiMessage.Kwargs({}), }, ), ) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": ETHEREUM_LEDGER_ID, "contract_id": "some_contract_id", "callable": "some_callable", "kwargs": ContractApiKwargs({"some_key": "some_value"}), }, ), DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": "fetchai", "contract_id": "some_contract_id", "callable": "some_callable", "kwargs": ContractApiKwargs({"some_key": "some_value"}), }, ), ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": Terms(*DEFAULT_TERMS), "raw_transaction": RawTransaction(ETHEREUM_LEDGER_ID, DEFAULT_TX), }, ), ) def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle__handle_unidentified_dialogue(self): """Test handling an unidentified dialogoue""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ETHEREUM_LEDGER_ID, address="some_eth_address", ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(0) def test__handle_balance(self): """Test handling a balance""" # setup balance = 0 dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=ETHEREUM_LEDGER_ID, balance=balance, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"Balance on {ETHEREUM_LEDGER_ID} ledger={balance}.", ) self.assert_quantity_in_outbox(1) def test__handle_transaction_digest(self): """Test handling a transaction digest""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[1:2] ) digest = "some_digest" transaction_digest = TransactionDigest(ETHEREUM_LEDGER_ID, digest) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=transaction_digest, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully submitted. Transaction digest=TransactionDigest: ledger_id={ETHEREUM_LEDGER_ID}, body={digest}", ) self.assert_quantity_in_outbox(1) def test__handle_transaction_receipt_failed(self): """Test handling a transaction receipt""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) receipt = {"status": 0} transaction_receipt = TransactionReceipt( ETHEREUM_LEDGER_ID, receipt, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, f"transaction failed. Transaction receipt={transaction_receipt}", ) self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_deploy(self): """Test handling a deploy transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[:1] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) terms = Terms(*DEFAULT_TERMS, label="deploy") contract_api_dialogue.terms = terms signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue transaction_receipt = TransactionReceipt( FETCHAI_LEDGER_ID, FETCHAI_DEPLOY_RECEIPT, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle contract successfully deployed at address: some_contract_address", ) assert ( self.simple_oracle_behaviour.context.strategy.is_contract_deployed ), "Contract deployment status not set" self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_store(self): """Test handling a store contract code transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[1:2] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy._ledger_id = "fetchai" contract_api_dialogue.terms = strategy.get_deploy_terms() assert contract_api_dialogue.terms.kwargs["label"] == "store" signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue transaction_receipt = TransactionReceipt( FETCHAI_LEDGER_ID, FETCHAI_DEPLOY_RECEIPT, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) self.assert_quantity_in_outbox(1) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id="fetchai", contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( { "label": "OracleContract", "init_msg": {"fee": str(strategy.initial_fee_deploy)}, "gas": strategy.gas_limit_instantiate, "tx_fee": strategy.gas_price * strategy.gas_limit_instantiate, "amount": 1, "code_id": 8888, "deployer_address": "test_agent_address", } ), ) assert has_attributes, error_str def test__handle_transaction_receipt_init(self): """Test handling a store contract code transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[1:2] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy._ledger_id = "fetchai" contract_api_dialogue.terms = strategy.get_deploy_terms( is_init_transaction=True ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue receipt = {"status": 1, "contractAddress": "some_contract_address"} transaction_receipt = TransactionReceipt( ETHEREUM_LEDGER_ID, receipt, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object( LedgerApis, "get_contract_address", return_value="some_contract_address" ): with patch.object( self.ledger_api_handler.context.logger, "log" ) as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle contract successfully deployed at address: some_contract_address", ) assert ( self.simple_oracle_behaviour.context.strategy.is_contract_deployed ), "Contract deployment status not set" self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_grant_role(self): """Test handling a grant_role transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[:1] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) terms = Terms(*DEFAULT_TERMS, label="grant_role") strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.is_contract_deployed = True contract_api_dialogue.terms = terms signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue receipt = {"status": 1, "contractAddress": "some_contract_address"} transaction_receipt = TransactionReceipt( ETHEREUM_LEDGER_ID, receipt, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle role successfully granted!", ) assert ( self.simple_oracle_behaviour.context.strategy.is_oracle_role_granted ), "Oracle role status not set" self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_update(self): """Test handling an update transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[:1] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) terms = Terms(*DEFAULT_TERMS, label="update") strategy = cast(Strategy, self.simple_oracle_behaviour.context.strategy) strategy.is_contract_deployed = True strategy.is_oracle_role_granted = True contract_api_dialogue.terms = terms signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue receipt = {"status": 1, "contractAddress": "some_contract_address"} transaction_receipt = TransactionReceipt( ETHEREUM_LEDGER_ID, receipt, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle value successfully updated!", ) self.assert_quantity_in_outbox(1) msg = cast(PrometheusMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=PrometheusMessage, performative=PrometheusMessage.Performative.UPDATE_METRIC, callable="inc", title="num_oracle_updates", value=1.0, labels={}, ) assert has_attributes, error_str def test__handle_error(self): """Test handling an error message""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={dialogue}.", ) self.assert_quantity_in_outbox(0) def test__handle_invalid(self): """Test handling an invalid performative""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[3:4] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.STATE, ledger_id=ETHEREUM_LEDGER_ID, state=LedgerApiMessage.State(ETHEREUM_LEDGER_ID, {}), ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={incoming_message.performative} in dialogue={dialogue}.", ) self.assert_quantity_in_outbox(0) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestContractApiHandler(BaseSkillTestCase): """Test contract_api handler of simple oracle.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.contract_api_handler = cast( ContractApiHandler, cls._skill.skill_context.handlers.contract_api ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls.contract_api_handler.context.logger cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.contract_id = "some_contract_id" cls.contract_address = "some_contract_address," cls.callable = "some_callable" cls.kwargs = Kwargs({"some_key": "some_value"}) cls.state = State("some_ledger_id", {"some_key": "some_value"}) cls.terms = Terms( ledger_id="some_ledger_id", sender_address="some_sender_address", counterparty_address="some_counterparty", amount_by_currency_id={"1": -10}, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce="some_none", fee_by_currency_id={"1": 100}, ) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": ETHEREUM_LEDGER_ID, "contract_id": cls.contract_id, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.info = {"ethereum_address": "some_ethereum_address"} def test_setup(self): """Test the setup method of the contract_api handler.""" assert self.contract_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the contract_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=ContractApiMessage.Performative.STATE, state=self.state, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid contract_api message={incoming_message}, unidentified dialogue.", ) def test_handle_raw_transaction(self): """Test the _handle_raw_transaction method of the contract_api handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=ContractApiMessage.RawTransaction(ETHEREUM_LEDGER_ID, {}), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw transaction={incoming_message}", ) mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) self.assert_quantity_in_decision_making_queue(1) def test_handle_error(self): """Test the _handle_error method of the contract_api handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the contract_api handler.""" # setup invalid_performative = ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=ETHEREUM_LEDGER_ID, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle contract_api message of performative={invalid_performative} in dialogue={self.contract_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the contract_api handler.""" assert self.contract_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(BaseSkillTestCase): """Test signing handler of simple oracle.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.logger = cls.signing_handler.context.logger cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": cls.terms, "raw_transaction": SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), }, ), ) def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_transaction( self, ): """Test the _handle_signed_transaction method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SigningMessage.SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=str(LEDGER_CONNECTION_PUBLIC_ID), sender=str(self.skill.skill_context.skill_id), signed_transaction=incoming_message.signed_transaction, ) assert has_attributes, error_str assert ( cast( LedgerApiDialogue, self.ledger_api_dialogues.get_dialogue(message) ).associated_signing_dialogue == signing_dialogue ) mock_logger.assert_any_call(logging.INFO, "sending transaction to ledger.") def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.terms, raw_transaction=SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestPrometheusHandler(BaseSkillTestCase): """Test prometheus handler of simple_oracle skill.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle") is_agent_to_agent_messages = False @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.prometheus_handler = cast( PrometheusHandler, cls._skill.skill_context.handlers.prometheus ) cls.logger = cls._skill.skill_context.logger cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.prometheus_dialogues = cast( PrometheusDialogues, cls._skill.skill_context.prometheus_dialogues ) cls.list_of_messages = ( DialogueMessage( PrometheusMessage.Performative.ADD_METRIC, { "type": "Gauge", "title": "some_title", "description": "some_description", "labels": {}, }, ), ) def test_setup(self): """Test the setup method of the prometheus handler.""" assert self.prometheus_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_response(self): """Test the _handle_response method of the prometheus handler to a valid response.""" # setup prometheus_dialogue = self.prepare_skill_dialogue( dialogues=self.prometheus_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=prometheus_dialogue, performative=PrometheusMessage.Performative.RESPONSE, code=200, message="some_message", ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.prometheus_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.DEBUG, "Prometheus response (200): some_message" ) def test_handle_unidentified_dialogue(self): """Test the _handle_response method of the prometheus handler to an unidentified dialogue.""" # setup incoming_message = self.build_incoming_message( message_type=PrometheusMessage, performative=PrometheusMessage.Performative.RESPONSE, code=200, message="some_message", ) # handle message with logging with patch.object(self.logger, "log") as mock_logger: self.prometheus_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid message={incoming_message}, unidentified dialogue.", ) def test_teardown(self): """Test the teardown method of the prometheus handler.""" assert self.prometheus_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_oracle_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_oracle_client dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_oracle_client/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the simple oracle client skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.contracts.fet_erc20.contract import PUBLIC_ID as ERC20_PUBLIC_ID from packages.fetchai.contracts.oracle_client.contract import ( PUBLIC_ID as CLIENT_CONTRACT_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.skills.simple_oracle_client.behaviours import ( SimpleOracleClientBehaviour, ) from packages.fetchai.skills.simple_oracle_client.strategy import Strategy from tests.conftest import ROOT_DIR DEFAULT_ADDRESS = "0x0000000000000000000000000000000000000000" ETHEREUM_LEDGER_ID = "ethereum" FETCHAI_LEDGER_ID = "fetchai" class TestSkillBehaviour(BaseSkillTestCase): """Test behaviours of simple oracle client.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle_client" ) @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup() cls.simple_oracle_client_behaviour = cast( SimpleOracleClientBehaviour, cls._skill.skill_context.behaviours.simple_oracle_client_behaviour, ) def test_setup(self): """Test the setup method of the simple_oracle_client behaviour.""" strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.oracle_contract_address = DEFAULT_ADDRESS strategy.erc20_address = DEFAULT_ADDRESS strategy.is_oracle_contract_set = True strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_client_behaviour.setup() self.assert_quantity_in_outbox(1) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, contract_id=str(CLIENT_CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", ) assert has_attributes, error_str def test_setup_with_contract_set(self): """Test the setup method of the simple_oracle_client behaviour for existing contract.""" strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.client_contract_address = DEFAULT_ADDRESS strategy.oracle_contract_address = DEFAULT_ADDRESS strategy.erc20_address = DEFAULT_ADDRESS strategy.is_client_contract_deployed = True strategy.is_oracle_contract_set = True strategy._ledger_id = ETHEREUM_LEDGER_ID with patch.object( self.simple_oracle_client_behaviour.context.logger, "log" ) as mock_logger: self.simple_oracle_client_behaviour.setup() mock_logger.assert_any_call( logging.INFO, "Fetch oracle client contract address already added", ) self.assert_quantity_in_outbox(0) def test_act_pre_deploy(self): """Test the act method of the simple_oracle_client behaviour before contract is deployed.""" with patch.object( self.simple_oracle_client_behaviour.context.logger, "log" ) as mock_logger: self.simple_oracle_client_behaviour.act() mock_logger.assert_any_call( logging.INFO, "Oracle client contract not yet deployed", ) self.assert_quantity_in_outbox(0) def test_act_approve_transactions(self): """Test the act method of the simple_oracle_client behaviour before transactions are approved.""" strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.client_contract_address = DEFAULT_ADDRESS strategy.oracle_contract_address = DEFAULT_ADDRESS strategy.erc20_address = DEFAULT_ADDRESS strategy.is_client_contract_deployed = True strategy.is_oracle_contract_set = True strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_client_behaviour.act() self.assert_quantity_in_outbox(1) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, contract_id=str(ERC20_PUBLIC_ID), contract_address=strategy.client_contract_address, callable="get_approve_transaction", ) assert has_attributes, error_str def test_act_query(self): """Test the act method of the simple_oracle_client behaviour for normal querying.""" strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.client_contract_address = DEFAULT_ADDRESS strategy.oracle_contract_address = DEFAULT_ADDRESS strategy.erc20_address = DEFAULT_ADDRESS strategy.is_client_contract_deployed = True strategy.is_oracle_transaction_approved = True strategy.is_oracle_contract_set = True strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_client_behaviour.act() self.assert_quantity_in_outbox(1) assert strategy.is_oracle_contract_set msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, contract_id=str(CLIENT_CONTRACT_PUBLIC_ID), contract_address=strategy.client_contract_address, callable="get_query_transaction", ) assert has_attributes, error_str def test__request_contract_deploy_transaction(self): """Test that the _request_contract_deploy_transaction function sends the right message to the contract_api for ethereum ledger.""" strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.oracle_contract_address = "some_address" strategy._ledger_id = ETHEREUM_LEDGER_ID self.simple_oracle_client_behaviour._request_contract_deploy_transaction() self.assert_quantity_in_outbox(1) kwargs = strategy.get_deploy_kwargs() assert "fetchOracleContractAddress" in kwargs.body msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, contract_id=str(CLIENT_CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=kwargs, ) assert has_attributes, error_str def test__request_contract_store_transaction(self): """Test that the _request_contract_deploy_transaction function sends the right message to the contract_api for fetchai ledger.""" strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.oracle_contract_address = "some_address" strategy._ledger_id = FETCHAI_LEDGER_ID self.simple_oracle_client_behaviour._request_contract_deploy_transaction() self.assert_quantity_in_outbox(1) kwargs = strategy.get_deploy_kwargs() assert "fetchOracleContractAddress" not in kwargs.body msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, contract_id=str(CLIENT_CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=kwargs, ) assert has_attributes, error_str def test_teardown(self): """Test that the teardown method of the simple_oracle_client behaviour leaves no messages in the outbox.""" assert self.simple_oracle_client_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_oracle_client/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the simple_data_request skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.connections.ledger.base import ( CONNECTION_ID as LEDGER_CONNECTION_PUBLIC_ID, ) from packages.fetchai.contracts.oracle_client.contract import ( PUBLIC_ID as CONTRACT_PUBLIC_ID, ) from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.custom_types import ( Kwargs as ContractApiKwargs, ) from packages.fetchai.protocols.contract_api.custom_types import State from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.custom_types import ( SignedTransaction, TransactionDigest, TransactionReceipt, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.signing.custom_types import RawTransaction from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.simple_oracle_client.behaviours import ( SimpleOracleClientBehaviour, ) from packages.fetchai.skills.simple_oracle_client.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogue, LedgerApiDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.simple_oracle_client.handlers import ( ContractApiHandler, LedgerApiHandler, SigningHandler, ) from packages.fetchai.skills.simple_oracle_client.strategy import Strategy from tests.conftest import ROOT_DIR ETHEREUM_LEDGER_ID = "ethereum" FETCHAI_LEDGER_ID = "fetchai" DEFAULT_TX = {"some_tx_key": "some_tx_value"} DEFAULT_TERMS = [ ETHEREUM_LEDGER_ID, "sender_address", "counterparty_address", {}, {}, "some_nonce", ] FETCHAI_DEPLOY_RECEIPT = { "logs": [ { "events": [ { "attributes": [ {"key": "code_id", "value": "8888"}, {"key": "_contract_address", "value": "some_contract_address"}, ] } ] } ] } class TestLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of simple_oracle_client skill.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle_client" ) is_agent_to_agent_messages = False @classmethod def setup(cls, **kwargs): """Setup the test class.""" super().setup(**kwargs) cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.logger = cls._skill.skill_context.logger cls.simple_oracle_client_behaviour = cast( SimpleOracleClientBehaviour, cls._skill.skill_context.behaviours.simple_oracle_client_behaviour, ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": ETHEREUM_LEDGER_ID, "address": "some_eth_address"}, ), DialogueMessage( LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, { "signed_transaction": SignedTransaction( ETHEREUM_LEDGER_ID, DEFAULT_TX ) }, ), DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, { "transaction_digest": TransactionDigest( ETHEREUM_LEDGER_ID, "some_digest" ) }, ), DialogueMessage( LedgerApiMessage.Performative.GET_STATE, { "ledger_id": ETHEREUM_LEDGER_ID, "callable": "some_callable", "args": (), "kwargs": LedgerApiMessage.Kwargs({}), }, ), ) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": ETHEREUM_LEDGER_ID, "contract_id": "some_contract_id", "callable": "some_callable", "kwargs": ContractApiKwargs({"some_key": "some_value"}), }, ), DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": FETCHAI_LEDGER_ID, "contract_id": "some_contract_id", "callable": "some_callable", "kwargs": ContractApiKwargs({"some_key": "some_value"}), }, ), ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": Terms(*DEFAULT_TERMS), "raw_transaction": RawTransaction(ETHEREUM_LEDGER_ID, DEFAULT_TX), }, ), ) def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle__handle_unidentified_dialogue(self): """Test handling an unidentified dialogoue""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=ETHEREUM_LEDGER_ID, address="some_eth_address", ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(0) def test__handle_balance(self): """Test handling a balance""" # setup balance = 0 dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=ETHEREUM_LEDGER_ID, balance=balance, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {ETHEREUM_LEDGER_ID} ledger={balance}.", ) self.assert_quantity_in_outbox(0) def test__handle_transaction_digest(self): """Test handling a transaction digest""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[1:2] ) digest = "some_digest" transaction_digest = TransactionDigest(ETHEREUM_LEDGER_ID, digest) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=transaction_digest, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully submitted. Transaction digest=TransactionDigest: ledger_id={ETHEREUM_LEDGER_ID}, body={digest}", ) self.assert_quantity_in_outbox(1) def test__handle_transaction_receipt_failed(self): """Test handling a transaction receipt""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) receipt = {"status": 0} transaction_receipt = TransactionReceipt( ETHEREUM_LEDGER_ID, receipt, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, f"transaction failed. Transaction receipt={transaction_receipt}", ) self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_deploy(self): """Test handling a deploy transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[:1] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) ledger_id = ETHEREUM_LEDGER_ID strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy._ledger_id = ledger_id contract_api_dialogue.terms = strategy.get_deploy_terms() signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue receipt = {"status": 1, "contractAddress": "some_contract_address"} transaction_receipt = TransactionReceipt(ledger_id, receipt, DEFAULT_TX) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: with patch( "aea.crypto.ledger_apis.LedgerApis.get_contract_address", return_value="some_contract_address", ): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle client contract successfully deployed at address: some_contract_address", ) assert ( self.simple_oracle_client_behaviour.context.strategy.is_client_contract_deployed ), "Contract deployment status not set" self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_store(self): """Test handling a store contract code transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[1:2] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy._ledger_id = "fetchai" strategy.oracle_contract_address = "some_oracle_address" contract_api_dialogue.terms = strategy.get_deploy_terms() assert contract_api_dialogue.terms.kwargs["label"] == "store" signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue transaction_receipt = TransactionReceipt( FETCHAI_LEDGER_ID, FETCHAI_DEPLOY_RECEIPT, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) self.assert_quantity_in_outbox(1) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id="fetchai", contract_id=str(CONTRACT_PUBLIC_ID), callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( { "label": "OracleContract", "init_msg": { "oracle_contract_address": strategy.oracle_contract_address }, "gas": strategy.gas_limit_instantiate, "tx_fee": strategy.gas_price * strategy.gas_limit_instantiate, "amount": 0, "code_id": 8888, "deployer_address": "test_agent_address", } ), ) assert has_attributes, error_str def test__handle_transaction_receipt_init(self): """Test handling a store contract code transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[1:2] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) ledger_id = FETCHAI_LEDGER_ID strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy._ledger_id = ledger_id contract_api_dialogue.terms = strategy.get_deploy_terms( is_init_transaction=True ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue transaction_receipt = TransactionReceipt( ledger_id, FETCHAI_DEPLOY_RECEIPT, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle client contract successfully deployed at address: some_contract_address", ) assert ( self.simple_oracle_client_behaviour.context.strategy.is_client_contract_deployed ), "Contract deployment status not set" self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_approve(self): """Test handling an approve transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[:1] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) terms = Terms(*DEFAULT_TERMS, label="approve") strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.is_client_contract_deployed = True contract_api_dialogue.terms = terms signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue receipt = {"status": 1, "contractAddress": "some_contract_address"} transaction_receipt = TransactionReceipt( ETHEREUM_LEDGER_ID, receipt, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle client transactions approved!", ) assert ( self.simple_oracle_client_behaviour.context.strategy.is_oracle_transaction_approved ), "Contract deployment status not set" self.assert_quantity_in_outbox(0) def test__handle_transaction_receipt_query(self): """Test handling a query transaction receipt""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) contract_api_dialogue = self.prepare_skill_dialogue( self.contract_api_dialogues, self.list_of_contract_api_messages[:1] ) signing_dialogue = self.prepare_skill_dialogue( self.signing_dialogues, self.list_of_signing_messages[:1] ) terms = Terms(*DEFAULT_TERMS, label="query") strategy = cast(Strategy, self.simple_oracle_client_behaviour.context.strategy) strategy.is_client_contract_deployed = True strategy.is_oracle_role_granted = True contract_api_dialogue.terms = terms signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue receipt = {"status": 1, "contractAddress": "some_contract_address"} transaction_receipt = TransactionReceipt( ETHEREUM_LEDGER_ID, receipt, DEFAULT_TX ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=transaction_receipt, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={transaction_receipt}", ) mock_logger.assert_any_call( logging.INFO, "Oracle value successfully requested!", ) self.assert_quantity_in_outbox(0) def test__handle_error(self): """Test handling an error message""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[2:3] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={dialogue}.", ) self.assert_quantity_in_outbox(0) def test__handle_invalid(self): """Test handling an invalid performative""" # setup dialogue = self.prepare_skill_dialogue( self.ledger_api_dialogues, self.list_of_ledger_api_messages[3:4] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=LedgerApiMessage.Performative.STATE, ledger_id=ETHEREUM_LEDGER_ID, state=LedgerApiMessage.State(ETHEREUM_LEDGER_ID, {}), ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={incoming_message.performative} in dialogue={dialogue}.", ) self.assert_quantity_in_outbox(0) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestContractApiHandler(BaseSkillTestCase): """Test contract_api handler of simple oracle client.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle_client" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.contract_api_handler = cast( ContractApiHandler, cls._skill.skill_context.handlers.contract_api ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls.contract_api_handler.context.logger cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.contract_id = "some_contract_id" cls.contract_address = "some_contract_address," cls.callable = "some_callable" cls.kwargs = Kwargs({"some_key": "some_value"}) cls.state = State("some_ledger_id", {"some_key": "some_value"}) cls.terms = Terms( ledger_id="some_ledger_id", sender_address="some_sender_address", counterparty_address="some_counterparty", amount_by_currency_id={"1": -10}, quantities_by_good_id={}, is_sender_payable_tx_fee=True, nonce="some_none", fee_by_currency_id={"1": 100}, ) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": ETHEREUM_LEDGER_ID, "contract_id": cls.contract_id, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.info = {"ethereum_address": "some_ethereum_address"} def test_setup(self): """Test the setup method of the contract_api handler.""" assert self.contract_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the contract_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=ContractApiMessage.Performative.STATE, state=self.state, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid contract_api message={incoming_message}, unidentified dialogue.", ) def test_handle_raw_transaction(self): """Test the _handle_raw_transaction method of the contract_api handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=ContractApiMessage.RawTransaction(ETHEREUM_LEDGER_ID, {}), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw transaction={incoming_message}", ) mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) self.assert_quantity_in_decision_making_queue(1) def test_handle_error(self): """Test the _handle_error method of the contract_api handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the contract_api handler.""" # setup invalid_performative = ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=ETHEREUM_LEDGER_ID, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle contract_api message of performative={invalid_performative} in dialogue={self.contract_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the contract_api handler.""" assert self.contract_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(BaseSkillTestCase): """Test signing handler of simple oracle client.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_oracle_client" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.logger = cls.signing_handler.context.logger cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": cls.terms, "raw_transaction": SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), }, ), ) def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_transaction( self, ): """Test the _handle_signed_transaction method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SigningMessage.SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=str(LEDGER_CONNECTION_PUBLIC_ID), sender=str(self.skill.skill_context.skill_id), signed_transaction=incoming_message.signed_transaction, ) assert has_attributes, error_str assert ( cast( LedgerApiDialogue, self.ledger_api_dialogues.get_dialogue(message) ).associated_signing_dialogue == signing_dialogue ) mock_logger.assert_any_call(logging.INFO, "sending transaction to ledger.") def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.terms, raw_transaction=SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_seller/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_seller dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_seller/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the simple seller skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch import pytest from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.simple_seller.strategy import Strategy from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of simple seller.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "simple_seller") @classmethod def setup(cls): """Setup the test class.""" cls.mocked_name_of_data = "some_name_for_data" config_overrides = { "models": { "strategy": {"args": {"shared_state_key": cls.mocked_name_of_data}} } } super().setup(config_overrides=config_overrides) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.mocked_data_1 = ( b'[{"type_1": "data_1", "type_2": "data_2", "type_3": "data_3"}]' ) cls.mocked_data_2 = ( b'{"type_1": "data_1", "type_2": "data_2", "type_3": "data_3"}' ) cls.mocked_data_3 = b"some_non_jason_data" def test__init__(self): """Test the __init__ method of the Strategy class.""" with pytest.raises(ValueError, match="No shared_state_key provided!"): Strategy(shared_state_key=None) def test_collect_from_data_source_i(self): """Test the collect_from_data_source method of the Strategy class where the data is NOT a dictionary.""" self.skill.skill_context._agent_context._shared_state[ self.mocked_name_of_data ] = self.mocked_data_1 expected_formatted_data = { "data": '[{"type_1": "data_1", "type_2": "data_2", "type_3": "data_3"}]' } actual_data = self.strategy.collect_from_data_source() assert actual_data == expected_formatted_data def test_collect_from_data_source_ii(self): """Test the collect_from_data_source method of the Strategy class where the data IS a dictionary.""" self.skill.skill_context._agent_context._shared_state[ self.mocked_name_of_data ] = self.mocked_data_2 expected_formatted_data = { "type_1": "data_1", "type_2": "data_2", "type_3": "data_3", } actual_data = self.strategy.collect_from_data_source() assert actual_data == expected_formatted_data def test_format_data_exception(self): """Test the _format_data method of the Strategy class where JSONDecodeError raises.""" with patch.object(self.skill.skill_context.logger, "log") as mock_logger: self.strategy._format_data(self.mocked_data_3) mock_logger.assert_any_call( logging.WARNING, f"error when loading json: {'Expecting value: line 1 column 1 (char 0)'}", ) ================================================ FILE: tests/test_packages/test_skills/test_simple_service_registration/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_service_registration dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_service_registration/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the simple_service_registration skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import Description from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_registration.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.simple_service_registration.strategy import Strategy from tests.conftest import ROOT_DIR class TestServiceRegistrationBehaviour(BaseSkillTestCase): """Test service behaviour of simple_service_registration.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_registration" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.service_behaviour = cast( ServiceRegistrationBehaviour, cls._skill.skill_context.behaviours.service ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.mocked_description_1 = Description({"foo1": 1, "bar1": 2}) cls.mocked_description_2 = Description({"foo2": 1, "bar2": 2}) cls.registration_message = OefSearchMessage( dialogue_reference=("", ""), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description="some_service_description", ) cls.registration_message.sender = str(cls._skill.skill_context.skill_id) cls.registration_message.to = cls._skill.skill_context.search_service_address cls.mocked_description = Description({"foo1": 1, "bar1": 2}) def test_setup(self): """Test the setup method of the service behaviour.""" # operation with patch.object( self.strategy, "get_location_description", return_value=self.mocked_description_1, ): with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_description_2, ): with patch.object(self.logger, "log") as mock_logger: self.service_behaviour.setup() # after self.assert_quantity_in_outbox(1) # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description_1, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") def test_act_i(self): """Test the act method of the service behaviour where failed_registration_msg IS None.""" # setup self.service_behaviour.failed_registration_msg = None # operation assert self.service_behaviour.act() is None # after self.assert_quantity_in_outbox(0) def test_act_ii(self): """Test the act method of the service behaviour where failed_registration_msg is NOT None.""" # setup self.service_behaviour.failed_registration_msg = self.registration_message # operation with patch.object(self.logger, "log") as mock_logger: self.service_behaviour.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=type(self.registration_message), performative=self.registration_message.performative, to=self.registration_message.to, sender=str(self.skill.skill_context.skill_id), service_description=self.registration_message.service_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Retrying registration on SOEF. Retry {self.service_behaviour._nb_retries} out of {self.service_behaviour._max_soef_registration_retries}.", ) assert self.service_behaviour.failed_registration_msg is None def test_act_iii(self): """Test the act method of the service behaviour where failed_registration_msg is NOT None and max retries is reached.""" # setup self.service_behaviour.failed_registration_msg = self.registration_message self.service_behaviour._max_soef_registration_retries = 2 self.service_behaviour._nb_retries = 2 # operation self.service_behaviour.act() # after self.assert_quantity_in_outbox(0) assert self.skill.skill_context.is_active is False def test_register_service(self): """Test the register_service method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.service_behaviour.register_service() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's service on the SOEF." ) def test_register_genus(self): """Test the register_genus method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_personality_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.service_behaviour.register_genus() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality genus on the SOEF." ) def test_register_classification(self): """Test the register_classification method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_classification_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.service_behaviour.register_classification() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality classification on the SOEF." ) def test_teardown(self): """Test the teardown method of the service behaviour.""" # operation with patch.object( self.strategy, "get_location_description", return_value=self.mocked_description_1, ): with patch.object( self.strategy, "get_unregister_service_description", return_value=self.mocked_description_2, ): with patch.object(self.logger, "log") as mock_logger: self.service_behaviour.teardown() # after self.assert_quantity_in_outbox(2) # _unregister_service has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description_2, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering service from SOEF.") # _unregister_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description_1, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering agent from SOEF.") ================================================ FILE: tests/test_packages/test_skills/test_simple_service_registration/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the simple_service_registration skill.""" from pathlib import Path from typing import cast from aea.helpers.search.models import Description from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue class of simple_service_registration.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_registration" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.mocked_description = Description({"foo1": 1, "bar1": 2}) def test_oef_search_dialogues(self): """Test the OefSearchDialogue class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=self.skill.skill_context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_description, ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_simple_service_registration/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the simple_service_registration skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import Attribute, DataModel, Description, Location from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_registration.behaviours import ( ServiceRegistrationBehaviour, ) from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogues, ) from packages.fetchai.skills.simple_service_registration.handlers import ( OefSearchHandler, ) from tests.conftest import ROOT_DIR class TestOefSearchHandler(BaseSkillTestCase): """Test oef_search handler of simple_service_registration.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_registration" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.service_registration_behaviour = cast( ServiceRegistrationBehaviour, cls._skill.skill_context.behaviours.service, ) cls.logger = cls._skill.skill_context.logger cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.data = b"some_body" cls.mocked_description = Description({"foo1": 1, "bar1": 2}) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) cls.unregister_description = Description( {"key": "seller_service"}, data_model=DataModel("remove", [Attribute("key", str, True)]), ) cls.list_of_messages_unregister = ( DialogueMessage( OefSearchMessage.Performative.UNREGISTER_SERVICE, {"service_description": cls.unregister_description}, is_incoming=False, ), ) def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_service", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH set_service_key data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_service[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iv(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, and its service are successfully registered on the SOEF.", ) def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef successtargets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) def test_handle_error_i(self): """Test the _handle_error method of the oef_search handler where the oef error targets register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert ( self.service_registration_behaviour.failed_registration_msg == oef_dialogue.get_message_by_id(incoming_message.target) ) def test_handle_error_ii(self): """Test the _handle_error method of the oef_search handler where the oef error does NOT target register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_unregister[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert self.service_registration_behaviour.failed_registration_msg is None def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup incoming_message = self.build_incoming_message( message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_description, ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={incoming_message.performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_service_registration/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the simple_service_registration skill.""" from pathlib import Path import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.models import Description, Location from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.simple_service_registration.strategy import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, Strategy, ) from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of simple_service_registration.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_registration" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.service_data = {"key": "seller_service", "value": "generic_service"} cls.strategy = Strategy( location=cls.location, service_data=cls.service_data, name="strategy", skill_context=cls._skill.skill_context, ) def test__init__(self): """Test the __init__ method of the Strategy class.""" assert self.strategy._remove_service_data == {"key": self.service_data["key"]} def test__init__exception(self): """Test the __init__ method of the Strategy class where exception is raise.""" incorrect_service_data_1 = {"key": "seller_service"} incorrect_service_data_2 = { "incorrect_key": "seller_service", "value": "generic_service", } incorrect_service_data_3 = { "key": "seller_service", "incorrect_key": "generic_service", } with pytest.raises( AEAEnforceError, match="service_data must contain keys `key` and `value`" ): Strategy( location=self.location, service_data=incorrect_service_data_1, ) with pytest.raises( AEAEnforceError, match="service_data must contain keys `key` and `value`" ): Strategy( location=self.location, service_data=incorrect_service_data_2, ) with pytest.raises( AEAEnforceError, match="service_data must contain keys `key` and `value`" ): Strategy( location=self.location, service_data=incorrect_service_data_3, ) def test_get_location_description(self): """Test the get_location_description method of the Strategy class.""" description = self.strategy.get_location_description() assert type(description) == Description assert description.data_model is AGENT_LOCATION_MODEL assert description.values.get("location", "") == Location( latitude=self.location["latitude"], longitude=self.location["longitude"] ) def test_get_register_service_description(self): """Test the get_register_service_description method of the Strategy class.""" description = self.strategy.get_register_service_description() assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert description.values.get("key", "") == self.service_data["key"] assert description.values.get("value", "") == self.service_data["value"] def test_get_register_personality_description(self): """Test the get_register_personality_description method of the GenericStrategy class.""" description = self.strategy.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "genus" assert description.values.get("value", "") == "data" def test_get_register_classification_description(self): """Test the get_register_classification_description method of the GenericStrategy class.""" description = self.strategy.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "classification" assert description.values.get("value", "") == "seller" def test_get_unregister_service_description(self): """Test the get_unregister_service_description method of the Strategy class.""" description = self.strategy.get_unregister_service_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == self.service_data["key"] ================================================ FILE: tests/test_packages/test_skills/test_simple_service_search/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/simple_service_search dir.""" ================================================ FILE: tests/test_packages/test_skills/test_simple_service_search/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour class of the simple_service_search skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Query, ) from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_search.behaviours import ( ServiceSearchBehaviour, ) from packages.fetchai.skills.simple_service_search.strategy import Strategy from tests.conftest import ROOT_DIR class TestServiceSearchBehaviour(BaseSkillTestCase): """Test service_search behaviour of simple_service_search.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_search" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.search_behaviour = cast( ServiceSearchBehaviour, cls._skill.skill_context.behaviours.service_search ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( "some_name", [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) def test_setup(self): """Test the setup method of the service_search behaviour.""" assert self.search_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act(self): """Test the act method of the service_search behaviour.""" # operation with patch.object(self.strategy, "get_query", return_value=self.query): with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), query=self.query, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "sending search request to OEF search node" ) def test_teardown(self): """Test the teardown method of the service_search behaviour.""" assert self.search_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_service_search/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the simple_service_search skill.""" from pathlib import Path from typing import cast from aea.helpers.search.models import Description from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_search.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue class of simple_service_search.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_search" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.mocked_description = Description({"foo1": 1, "bar1": 2}) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=self.skill.skill_context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_description, ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_simple_service_search/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler class of the simple_service_search skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Query, ) from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.simple_service_search.dialogues import OefSearchDialogues from packages.fetchai.skills.simple_service_search.handlers import OefSearchHandler from packages.fetchai.skills.simple_service_search.strategy import Strategy from tests.conftest import ROOT_DIR class TestOefSearchHandler(BaseSkillTestCase): """Test oef_search handler of simple_service_search.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_search" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.data = b"some_body" cls.query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( "some_name", [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) cls.mocked_description = Description({"foo1": 1, "bar1": 2}) cls.list_of_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": cls.query} ), ) def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup oef_search_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_search_dialogue}.", ) def test_handle_search_i(self): """Test the _handle_search method of the oef_search handler where the number of agents found is 0.""" # setup agents = ("agent_1", "agent_2") oef_search_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( { "agent_1": {"key_1": "value_1", "key_2": "value_2"}, "agent_2": {"key_3": "value_3", "key_4": "value_4"}, } ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found number of agents={len(agents)}, search_response={incoming_message}", ) assert self.skill.skill_context._agent_context.shared_state[ self.strategy.shared_storage_key ] == set(agents) def test_handle_search_ii(self): """Test the _handle_search method of the oef_search handler where the number of agents found is 0.""" # setup agents = tuple() oef_search_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( { "agent_1": {"key_1": "value_1", "key_2": "value_2"}, "agent_2": {"key_3": "value_3", "key_4": "value_4"}, } ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"no agents found, search_response={incoming_message}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup incoming_message = self.build_incoming_message( message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_description, ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={incoming_message.performative} in dialogue={self.oef_search_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_simple_service_search/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the simple_service_search skill.""" from pathlib import Path from aea.helpers.search.models import Constraint, ConstraintType, Query from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.simple_service_search.strategy import Strategy from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of simple_service_search.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "simple_service_search" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.mocked_name_of_data = "some_name_for_data" cls.mocked_data_1 = ( b'[{"type_1": "data_1", "type_2": "data_2", "type_3": "data_3"}]' ) cls.mocked_data_2 = ( b'{"type_1": "data_1", "type_2": "data_2", "type_3": "data_3"}' ) cls.mocked_data_3 = b"some_non_jason_data" cls.search_query = { "search_key": "seller_service", "search_value": "generic_service", "constraint_type": "==", } cls.search_location = {"longitude": 0.1270, "latitude": 51.5194} cls.search_radius = 5.0 cls.shared_state_key = "agents_found" cls.strategy = Strategy( search_query=cls.search_query, search_location=cls.search_location, search_radius=cls.search_radius, shared_storage_key=cls.shared_state_key, name="strategy", skill_context=cls._skill.skill_context, ) def test_simple_properties(self): """Test simple properties of the Strategy class.""" assert self.strategy.shared_storage_key == self.shared_state_key def test_get_query(self): """Test the get_query method of the Strategy class.""" query = self.strategy.get_query() assert type(query) == Query assert len(query.constraints) == 2 assert query.model is None location_constraint = Constraint( "location", ConstraintType( "distance", (self.strategy._agent_location, self.search_radius) ), ) assert query.constraints[0] == location_constraint service_key_constraint = Constraint( self.search_query["search_key"], ConstraintType( self.search_query["constraint_type"], self.search_query["search_value"], ), ) assert query.constraints[1] == service_key_constraint ================================================ FILE: tests/test_packages/test_skills/test_tac_control/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/tac_control dir.""" ================================================ FILE: tests/test_packages/test_skills/test_tac_control/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the tac control skill.""" import datetime import logging from pathlib import Path from typing import cast from unittest.mock import Mock, PropertyMock, patch import pytest from aea.helpers.search.models import Description from aea.mail.base import Address from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_control.behaviours import TacBehaviour from packages.fetchai.skills.tac_control.dialogues import TacDialogues from packages.fetchai.skills.tac_control.game import Game, Phase from packages.fetchai.skills.tac_control.parameters import Parameters from tests.conftest import ROOT_DIR class TestSkillBehaviour(BaseSkillTestCase): """Test tac behaviour of tac_control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.tac_behaviour = cast(TacBehaviour, cls._skill.skill_context.behaviours.tac) cls.game = cast(Game, cls._skill.skill_context.game) cls.parameters = cast(Parameters, cls._skill.skill_context.parameters) cls.tac_dialogues = cast(TacDialogues, cls._skill.skill_context.tac_dialogues) cls.mocked_reg_time = cls._time("00:02") cls.mocked_start_time = cls._time("00:04") cls.mocked_end_time = cls._time("00:06") cls.parameters._registration_start_time = cls.mocked_reg_time cls.parameters._start_time = cls.mocked_start_time cls.parameters._end_time = cls.mocked_end_time cls.mocked_description = Description({"foo1": 1, "bar1": 2}) cls.agent_1_address = "agent_address_1" cls.agent_1_name = "agent_name_1" cls.agent_2_address = "agent_address_2" cls.agent_2_name = "agent_name_2" cls.registration_message = OefSearchMessage( dialogue_reference=("", ""), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description="some_service_description", ) cls.registration_message.sender = str(cls._skill.skill_context.skill_id) cls.registration_message.to = cls._skill.skill_context.search_service_address def test_init(self): """Test the __init__ method of the tac behaviour.""" assert self.tac_behaviour._registered_description is None def test_setup(self): """Test the setup method of the tac behaviour.""" # operation with patch.object( self.game, "get_location_description", return_value=self.mocked_description ): with patch.object(self.tac_behaviour.context.logger, "log") as mock_logger: self.tac_behaviour.setup() # after self.assert_quantity_in_outbox(1) # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") @staticmethod def _time(time: str): date_time = "01 01 2020 " + time return datetime.datetime.strptime(date_time, "%d %m %Y %H:%M") def test_act_i(self): """Test the act method of the tac behaviour where phase is pre_game and reg_start_time < now < start_time.""" # setup self.game._phase = Phase.PRE_GAME self.game.is_registered_agent = True mocked_now_time = self._time("00:03") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_register_tac_description", return_value=self.mocked_description, ): with patch.object( self.tac_behaviour.context.logger, "log" ) as mock_logger: self.tac_behaviour.act() # after # act assert self.game.phase == Phase.GAME_REGISTRATION # _register_tac self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering TAC data model on SOEF.") # act mock_logger.assert_any_call( logging.INFO, f"TAC open for registration until: {self.mocked_start_time}" ) def test_act_ii(self): """Test the act method of the tac behaviour where phase is game_registration and start_time < now < end_time and nb_agent < min_nb_agents.""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now_time = self._time("00:05") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.parameters._min_nb_agents = 2 self.game._registration.register_agent( COUNTERPARTY_AGENT_ADDRESS, self.agent_1_name ) self.prepare_skill_dialogue( self.tac_dialogues, ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": "some_agent_name"}, True, ), ), ) # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object( self.tac_behaviour.context.logger, "log" ) as mock_logger: self.tac_behaviour.act() # after self.assert_quantity_in_outbox(2) # _cancel_tac mock_logger.assert_any_call( logging.INFO, "notifying agents that TAC is cancelled." ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.CANCELLED, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, ) assert has_attributes, error_str # phase is POST_GAME assert self.game.phase == Phase.POST_GAME # _unregister_tac has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "unregistering TAC data model from SOEF." ) def test_cancel_tac_not_1_dialogue(self): """Test the _cancel_tac method of the tac behaviour where number of dialogues for an agent is 0.""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now_time = self._time("00:05") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.parameters._min_nb_agents = 2 self.game._registration.register_agent( COUNTERPARTY_AGENT_ADDRESS, self.agent_1_name ) # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object(self.tac_behaviour.context.logger, "log"): with pytest.raises( ValueError, match="Error when retrieving dialogue." ): self.tac_behaviour.act() def test_cancel_tac_empty_dialogue(self): """Test the _cancel_tac method of the tac behaviour where the dialogue is empty.""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now_time = self._time("00:05") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.parameters._min_nb_agents = 2 self.game._registration.register_agent( self.skill.skill_context.agent_address, self.agent_1_name ) dialogue = self.prepare_skill_dialogue( self.tac_dialogues, ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": "some_agent_name"}, True, ), ), ) dialogue._incoming_messages = [] # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object(self.tac_behaviour.context.logger, "log"): with pytest.raises( ValueError, match="Error when retrieving dialogue." ): self.tac_behaviour.act() def _assert_tac_message_and_logging_output( self, tac_message: TacMessage, participant_address: Address, mocked_logger, ): has_attributes, error_str = self.message_has_attributes( actual_message=tac_message, message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, to=participant_address, sender=self.skill.skill_context.agent_address, ) assert has_attributes, error_str mocked_logger.assert_any_call( logging.DEBUG, f"sending game data to '{participant_address}': {str(tac_message)}", ) def test_act_iii(self): """Test the act method of the tac behaviour where phase is game_registration and start_time < now < end_time and nb_agent >= min_nb_agents""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now_time = self._time("00:05") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) mocked_holdings_summary = "some_holdings_summary" mocked_equilibrium_summary = "some_equilibrium_summary" self.prepare_skill_dialogue( self.tac_dialogues, ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": self.agent_1_name}, True, ), ), self.agent_1_address, ) self.prepare_skill_dialogue( self.tac_dialogues, ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": self.agent_2_name}, True, ), ), self.agent_2_address, ) # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object( type(self.game), "holdings_summary", new_callable=PropertyMock, return_value=mocked_holdings_summary, ): with patch.object( type(self.game), "equilibrium_summary", new_callable=PropertyMock, return_value=mocked_equilibrium_summary, ): with patch.object( self.tac_behaviour.context.logger, "log" ) as mock_logger: self.tac_behaviour.act() # after self.assert_quantity_in_outbox(3) # _start_tac mock_logger.assert_any_call( logging.INFO, f"started competition:\n{mocked_holdings_summary}" ) mock_logger.assert_any_call( logging.INFO, f"computed equilibrium:\n{mocked_equilibrium_summary}" ) tac_message_1_in_outbox = cast(TacMessage, self.get_message_from_outbox()) self._assert_tac_message_and_logging_output( tac_message_1_in_outbox, self.agent_1_address, mock_logger ) tac_message_2_in_outbox = cast(TacMessage, self.get_message_from_outbox()) self._assert_tac_message_and_logging_output( tac_message_2_in_outbox, self.agent_2_address, mock_logger ) # phase is POST_GAME assert self.game.phase == Phase.GAME # _unregister_tac has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str assert self.tac_behaviour._registered_description is None mock_logger.assert_any_call( logging.INFO, "unregistering TAC data model from SOEF." ) def test_register_genus(self): """Test the register_genus method of the service_registration behaviour.""" # operation with patch.object( self.game, "get_register_personality_description", return_value=self.mocked_description, ): with patch.object(self.tac_behaviour.context.logger, "log") as mock_logger: self.tac_behaviour.register_genus() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality genus on the SOEF." ) def test_register_classification(self): """Test the register_classification method of the service_registration behaviour.""" # operation with patch.object( self.game, "get_register_classification_description", return_value=self.mocked_description, ): with patch.object(self.tac_behaviour.context.logger, "log") as mock_logger: self.tac_behaviour.register_classification() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality classification on the SOEF." ) def test_start_tac_not_1_dialogue(self): """Test the _start_tac method of the tac behaviour where number of dialogues for an agent is 0.""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now_time = self._time("00:05") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object(self.tac_behaviour.context.logger, "log"): with pytest.raises( ValueError, match="Error when retrieving dialogue." ): self.tac_behaviour.act() def test_start_tac_empty_dialogue(self): """Test the _start_tac method of the tac behaviour where a dialogue is empty.""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now_time = self._time("00:05") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) dialogue_1 = self.prepare_skill_dialogue( self.tac_dialogues, ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": self.agent_1_name}, True, ), ), self.agent_1_address, ) dialogue_1._incoming_messages = [] # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object(self.tac_behaviour.context.logger, "log"): with pytest.raises( ValueError, match="Error when retrieving last message." ): self.tac_behaviour.act() def test_act_iv(self): """Test the act method of the tac behaviour where phase is GAME and end_time < now.""" # setup self.game._phase = Phase.GAME mocked_now_time = self._time("00:07") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) mocked_holdings_summary = "some_holdings_summary" mocked_equilibrium_summary = "some_equilibrium_summary" self.prepare_skill_dialogue( self.tac_dialogues, ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": self.agent_1_name}, True, ), ), self.agent_1_address, ) self.prepare_skill_dialogue( self.tac_dialogues, ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": self.agent_2_name}, True, ), ), self.agent_2_address, ) # operation with patch("datetime.datetime", new=datetime_mock): with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object( type(self.game), "holdings_summary", new_callable=PropertyMock, return_value=mocked_holdings_summary, ): with patch.object( type(self.game), "equilibrium_summary", new_callable=PropertyMock, return_value=mocked_equilibrium_summary, ): with patch.object( self.tac_behaviour.context.logger, "log" ) as mock_logger: self.tac_behaviour.act() # after self.assert_quantity_in_outbox(2) # _cancel_tac mock_logger.assert_any_call( logging.INFO, "notifying agents that TAC is cancelled." ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.CANCELLED, to=self.agent_1_address, sender=self.skill.skill_context.agent_address, ) assert has_attributes, error_str has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.CANCELLED, to=self.agent_2_address, sender=self.skill.skill_context.agent_address, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"finished competition:\n{mocked_holdings_summary}" ) mock_logger.assert_any_call( logging.INFO, f"computed equilibrium:\n{mocked_equilibrium_summary}" ) # phase is POST_GAME assert self.game.phase == Phase.POST_GAME assert self.skill.skill_context.is_active is False def test_act_v(self): """Test the act method of the tac behaviour where failed_registration_msg is NOT None.""" # setup self.tac_behaviour.failed_registration_msg = self.registration_message with patch.object(self.tac_behaviour.context.logger, "log") as mock_logger: self.tac_behaviour.act() # after self.assert_quantity_in_outbox(1) # _retry_failed_registration has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=type(self.registration_message), performative=self.registration_message.performative, to=self.registration_message.to, sender=str(self.skill.skill_context.skill_id), service_description=self.registration_message.service_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Retrying registration on SOEF. Retry {self.tac_behaviour._nb_retries} out of {self.tac_behaviour._max_soef_registration_retries}.", ) assert self.tac_behaviour.failed_registration_msg is None def test_act_vi(self): """Test the act method of the tac behaviour where failed_registration_msg is NOT None and max retries is reached.""" # setup self.tac_behaviour.failed_registration_msg = self.registration_message self.tac_behaviour._max_soef_registration_retries = 2 self.tac_behaviour._nb_retries = 2 self.tac_behaviour.act() # after self.assert_quantity_in_outbox(0) assert self.skill.skill_context.is_active is False def test_teardown(self): """Test the teardown method of the service_registration behaviour.""" # setup mocked_location_description = Description({"foo1": 1, "bar1": 2}) # operation with patch.object( self.game, "get_unregister_tac_description", return_value=self.mocked_description, ): with patch.object( self.game, "get_location_description", return_value=mocked_location_description, ): with patch.object( self.tac_behaviour.context.logger, "log" ) as mock_logger: self.tac_behaviour.teardown() # after self.assert_quantity_in_outbox(2) # _unregister_tac has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "unregistering TAC data model from SOEF." ) # _unregister_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=mocked_location_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering agent from SOEF.") ================================================ FILE: tests/test_packages/test_skills/test_tac_control/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the tac control skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_control.dialogues import ( DefaultDialogue, DefaultDialogues, OefSearchDialogue, OefSearchDialogues, TacDialogue, TacDialogues, ) from tests.conftest import ROOT_DIR class TestTacDialogues(BaseSkillTestCase): """Test dialogue classes of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.tac_dialogues = cast(TacDialogues, cls._skill.skill_context.tac_dialogues) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query="some_query", ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_tac_dialogues(self): """Test the TacDialogues class.""" _, dialogue = self.tac_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=TacMessage.Performative.REGISTER, agent_name="some_agent_name", ) assert dialogue.role == TacDialogue.Role.CONTROLLER assert dialogue.self_address == self.skill.skill_context.agent_address ================================================ FILE: tests/test_packages/test_skills/test_tac_control/test_game.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the models of the tac control skill.""" import datetime import logging import pprint from pathlib import Path from unittest.mock import Mock, patch import pytest from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import AEAEnforceError from aea.helpers.preference_representations.base import ( linear_utility, logarithmic_utility, ) from aea.helpers.search.models import Description, Location from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_control.game import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, AgentState, Configuration, Game, Initialization, Phase, Registration, Transaction, Transactions, ) from packages.fetchai.skills.tac_control.parameters import Parameters from tests.conftest import ROOT_DIR class TestConfiguration: """Test Configuration class of tac control.""" @classmethod def setup(cls): """Setup the test class.""" cls.version_id = "some_version_id" cls.tx_fee = 1 cls.agent_addr_to_name = { "agent_address_1": "agent_name_1", "agent_address_2": "agent_name_2", } cls.currency_id_to_name = {"1": "currency_1"} cls.good_id_to_name = {"3": "good_1", "4": "good_2"} cls.configuration = Configuration( cls.version_id, cls.tx_fee, cls.agent_addr_to_name, cls.currency_id_to_name, cls.good_id_to_name, ) def test_simple_properties(self): """Test the properties of Game class.""" assert self.configuration.version_id == self.version_id assert self.configuration.fee_by_currency_id == {"1": 1} assert self.configuration.agent_addr_to_name == self.agent_addr_to_name assert self.configuration.currency_id_to_name == self.currency_id_to_name assert self.configuration.good_id_to_name == self.good_id_to_name assert self.configuration.has_contract_address is False with pytest.raises(AEAEnforceError, match="Contract_address not set yet!"): assert self.configuration.contract_address self.configuration.contract_address = "some_contract_address" assert self.configuration.has_contract_address is True assert self.configuration.contract_address == "some_contract_address" with pytest.raises(AEAEnforceError, match="Contract_address already set!"): self.configuration.contract_address = "some_other_contract_address" def test_check_consistency_succeeds(self): """Test the _check_consistency of Configuration class which succeeds.""" self.configuration._check_consistency() def test_check_consistency_fails_i(self): """Test the _check_consistency of Configuration class which fails on version being None.""" self.configuration._version_id = None with pytest.raises(AEAEnforceError, match="A version id must be set."): assert self.configuration._check_consistency() def test_check_consistency_fails_ii(self): """Test the _check_consistency of Configuration class which fails because tx_fee < 0.""" self.configuration._tx_fee = -5 with pytest.raises(AEAEnforceError, match="Tx fee must be non-negative."): assert self.configuration._check_consistency() def test_check_consistency_fails_iii(self): """Test the _check_consistency of Configuration class which fails because number of agents is less than 2.""" self.configuration._agent_addr_to_name = {"agent_address_1": "agent_name_1"} with pytest.raises(AEAEnforceError, match="Must have at least two agents."): assert self.configuration._check_consistency() def test_check_consistency_fails_iv(self): """Test the _check_consistency of Configuration class which fails because number of goods is less than 2.""" self.configuration._good_id_to_name = {"3": "good_1"} with pytest.raises(AEAEnforceError, match="Must have at least two goods."): assert self.configuration._check_consistency() def test_check_consistency_fails_v(self): """Test the _check_consistency of Configuration class which fails because number of currencies is not 1.""" self.configuration._currency_id_to_name = {"1": "currency_1", "2": "currency_2"} with pytest.raises(AEAEnforceError, match="Must have exactly one currency."): assert self.configuration._check_consistency() def test_check_consistency_fails_vi(self): """Test the _check_consistency of Configuration class which fails because same id for good and currency.""" self.configuration._good_id_to_name = {"1": "good_1", "2": "good_2"} self.configuration._currency_id_to_name = {"1": "currency_1"} with pytest.raises( AEAEnforceError, match="Currency id and good ids cannot overlap." ): assert self.configuration._check_consistency() class TestInitialization: """Test Initialization class of tac control.""" @classmethod def setup(cls): """Setup the test class.""" cls.agent_addr_to_currency_endowments = { "agent_address_1": {"1": 10}, "agent_address_2": {"1": 5}, } cls.agent_addr_to_exchange_params = { "agent_address_1": {"1": 1.0}, "agent_address_2": {"1": 1.5}, } cls.agent_addr_to_good_endowments = { "agent_address_1": {"2": 5, "3": 7}, "agent_address_2": {"2": 4, "3": 6}, } cls.agent_addr_to_utility_params = { "agent_address_1": {"2": 1.0, "3": 1.1}, "agent_address_2": {"2": 1.3, "3": 1.5}, } cls.good_id_to_eq_prices = {"2": 1.7, "3": 1.3} cls.agent_addr_to_eq_good_holdings = { "agent_address_1": {"2": 1.2, "3": 1.1}, "agent_address_2": {"2": 1.1, "3": 1.4}, } cls.agent_addr_to_eq_currency_holdings = { "agent_address_1": {"1": 1.1}, "agent_address_2": {"1": 1.2}, } cls.initialization = Initialization( cls.agent_addr_to_currency_endowments, cls.agent_addr_to_exchange_params, cls.agent_addr_to_good_endowments, cls.agent_addr_to_utility_params, cls.good_id_to_eq_prices, cls.agent_addr_to_eq_good_holdings, cls.agent_addr_to_eq_currency_holdings, ) def test_simple_properties(self): """Test the properties of Game class.""" assert ( self.initialization.agent_addr_to_currency_endowments == self.agent_addr_to_currency_endowments ) assert ( self.initialization.agent_addr_to_exchange_params == self.agent_addr_to_exchange_params ) assert ( self.initialization.agent_addr_to_good_endowments == self.agent_addr_to_good_endowments ) assert ( self.initialization.agent_addr_to_utility_params == self.agent_addr_to_utility_params ) assert self.initialization.good_id_to_eq_prices == self.good_id_to_eq_prices assert ( self.initialization.agent_addr_to_eq_good_holdings == self.agent_addr_to_eq_good_holdings ) assert ( self.initialization.agent_addr_to_eq_currency_holdings == self.agent_addr_to_eq_currency_holdings ) def test_check_consistency_succeeds(self): """Test the _check_consistency of Configuration class which succeeds.""" self.initialization._check_consistency() def test_check_consistency_fails_i(self): """Test the _check_consistency of Configuration class which fails because currency endowments are negative.""" self.initialization._agent_addr_to_currency_endowments = { "agent_address_1": {"1": -1}, "agent_address_2": {"1": 5}, } with pytest.raises( AEAEnforceError, match="Currency endowments must be non-negative." ): assert self.initialization._check_consistency() def test_check_consistency_fails_ii(self): """Test the _check_consistency of Configuration class which fails because ExchangeParams are not strictly positive.""" self.initialization._agent_addr_to_exchange_params = { "agent_address_1": {"1": 0.0}, "agent_address_2": {"1": -1.2}, } with pytest.raises( AEAEnforceError, match="ExchangeParams must be strictly positive." ): assert self.initialization._check_consistency() def test_check_consistency_fails_iii(self): """Test the _check_consistency of Configuration class which fails because Good endowments are not strictly positive.""" self.initialization._agent_addr_to_good_endowments = { "agent_address_1": {"2": 0, "3": -1}, "agent_address_2": {"2": -7, "3": 0}, } with pytest.raises( AEAEnforceError, match="Good endowments must be strictly positive." ): assert self.initialization._check_consistency() def test_check_consistency_fails_iv(self): """Test the _check_consistency of Configuration class which fails because UtilityParams are not strictly positive.""" self.initialization._agent_addr_to_utility_params = { "agent_address_1": {"2": 0, "3": -7}, "agent_address_2": {"2": -4, "3": 0}, } with pytest.raises( AEAEnforceError, match="UtilityParams must be strictly positive." ): assert self.initialization._check_consistency() def test_check_consistency_fails_v(self): """Test the _check_consistency of Configuration class which fails because lengths of endowments are not the same.""" self.initialization._agent_addr_to_currency_endowments = { "agent_address_1": {"1": 10}, } with pytest.raises( AEAEnforceError, match="Length of endowments must be the same." ): assert self.initialization._check_consistency() def test_check_consistency_fails_vi(self): """Test the _check_consistency of Configuration class which fails because lengths of params are not the same.""" self.initialization._agent_addr_to_exchange_params = { "agent_address_1": {"1": 1.0}, } with pytest.raises(AEAEnforceError, match="Length of params must be the same."): assert self.initialization._check_consistency() def test_check_consistency_fails_vii(self): """Test the _check_consistency of Configuration class which fails because length of eq_prices and elements of eq_good_holdings are not the same.""" self.initialization._good_id_to_eq_prices = {"2": 1.7} with pytest.raises( AEAEnforceError, match="Length of eq_prices and an element of eq_good_holdings must be the same.", ): assert self.initialization._check_consistency() def test_check_consistency_fails_viii(self): """Test the _check_consistency of Configuration class which fails because length of eq_good_holdings and eq_currency_holdings are not the same.""" self.initialization._agent_addr_to_eq_currency_holdings = { "agent_address_1": {"1": 1.1}, } with pytest.raises( AEAEnforceError, match="Length of eq_good_holdings and eq_currency_holdings must be the same.", ): assert self.initialization._check_consistency() def test_check_consistency_fails_ix(self): """Test the _check_consistency of Configuration class which fails because exchange_params and currency_endowments have different number of rows.""" self.initialization._agent_addr_to_currency_endowments = { "agent_address_1": {"1": 10, "2": 11}, "agent_address_2": {"1": 5}, } with pytest.raises( AEAEnforceError, match="Dimensions for exchange_params and currency_endowments rows must be the same.", ): assert self.initialization._check_consistency() def test_check_consistency_fails_x(self): """Test the _check_consistency of Configuration class which fails because utility_params and rows have different number of rows.""" self.initialization._agent_addr_to_good_endowments = { "agent_address_1": {"2": 5, "3": 7, "4": 8}, "agent_address_2": {"2": 4, "3": 6}, } with pytest.raises( AEAEnforceError, match="Dimensions for utility_params and good_endowments rows must be the same.", ): assert self.initialization._check_consistency() class TestTransaction: """Test Initialization class of tac control.""" @classmethod def setup(cls): """Setup the test class.""" cls.ledger_id = "ethereum" cls.sender_address = "some_sender_address" cls.counterparty_address = "some_counterparty_address" cls.amount_by_currency_id = {"1": 10} cls.quantities_by_good_id = {"2": 5, "5": 10} cls.is_sender_payable_tx_fee = True cls.nonce = "some_nonce" cls.fee_by_currency_id = {"1": 1} cls.sender_signature = "some_sender_signature" cls.counterparty_signature = "some_counterparty_signature" cls.transaction = Transaction( cls.ledger_id, cls.sender_address, cls.counterparty_address, cls.amount_by_currency_id, cls.quantities_by_good_id, cls.is_sender_payable_tx_fee, cls.nonce, cls.fee_by_currency_id, cls.sender_signature, cls.counterparty_signature, ) def test_simple_properties(self): """Test the properties of Game class.""" assert self.transaction.sender_signature == self.sender_signature assert self.transaction.counterparty_signature == self.counterparty_signature def test_has_matching_signatures_succeeds(self): """Test the has_matching_signatures method of Transaction class where the two addresses appear in the hash.""" with patch.object( LedgerApis, "recover_message", return_value=(self.sender_address, self.counterparty_address), ): assert self.transaction.has_matching_signatures() is True def test_has_matching_signatures_fails_sender_not_in_hash(self): """Test the has_matching_signatures method of Transaction class where the sender address does not appear in the hash.""" with patch.object( LedgerApis, "recover_message", return_value=(self.counterparty_address,) ): assert self.transaction.has_matching_signatures() is False def test_has_matching_signatures_fails_counterparty_not_in_hash(self): """Test the has_matching_signatures method of Transaction class where the counterparty addresses does not appear in the hash.""" with patch.object( LedgerApis, "recover_message", return_value=(self.sender_address,) ): assert self.transaction.has_matching_signatures() is False def test_has_matching_signatures_fails_sender_and_counterparty_not_in_hash(self): """Test the has_matching_signatures method of Transaction class where the sender and counterparty addresses do not appear in the hash.""" with patch.object(LedgerApis, "recover_message", return_value=tuple()): assert self.transaction.has_matching_signatures() is False def test_from_message(self): """Test the from_message method of Transaction class.""" ledger_id = "some_ledger_id" sender_address = "some_sender_address" counterparty_address = "some_counterparty_address" amount_by_currency_id = {"FET": 10} fee_by_currency_id = {"FET": 2} quantities_by_good_id = {"G1": -1} nonce = "some_nonce" sender_signature = "some_signature" counterparty_signature = "some_other_signature" tx_id = Transaction.get_hash( ledger_id, sender_address=sender_address, counterparty_address=counterparty_address, good_ids=["G1"], sender_supplied_quantities=[0], counterparty_supplied_quantities=[1], sender_payable_amount=0, counterparty_payable_amount=10, nonce=nonce, ) tx = Transaction.from_message( TacMessage( performative=TacMessage.Performative.TRANSACTION, transaction_id=tx_id, ledger_id=ledger_id, sender_address=sender_address, counterparty_address=counterparty_address, amount_by_currency_id=amount_by_currency_id, fee_by_currency_id=fee_by_currency_id, quantities_by_good_id=quantities_by_good_id, nonce=nonce, sender_signature=sender_signature, counterparty_signature=counterparty_signature, ) ) assert tx.ledger_id == ledger_id assert tx.sender_address == sender_address assert tx.counterparty_address == counterparty_address assert tx.amount_by_currency_id == amount_by_currency_id assert tx.fee_by_currency_id == fee_by_currency_id assert tx.quantities_by_good_id == quantities_by_good_id assert tx.nonce == nonce assert tx.sender_signature == sender_signature assert tx.counterparty_signature == counterparty_signature def test__eq__(self): """Test the __eq__ method of Transaction class.""" equal_transaction = Transaction( self.ledger_id, self.sender_address, self.counterparty_address, self.amount_by_currency_id, self.quantities_by_good_id, self.is_sender_payable_tx_fee, self.nonce, self.fee_by_currency_id, self.sender_signature, self.counterparty_signature, ) assert self.transaction.__eq__(equal_transaction) is True not_equal_transaction = Transaction( self.ledger_id, "some_different_sender_address", self.counterparty_address, self.amount_by_currency_id, self.quantities_by_good_id, self.is_sender_payable_tx_fee, self.nonce, self.fee_by_currency_id, self.sender_signature, self.counterparty_signature, ) assert self.transaction.__eq__(not_equal_transaction) is False class TestAgentState: """Test AgentState class of tac control.""" @classmethod def setup(cls): """Setup the test class.""" cls.agent_address = "sender_address" cls.amount_by_currency_id = {"1": 10} cls.quantities_by_good_id = {"2": 1, "3": 2} cls.exchange_params_by_currency_id = {"1": 1.0} cls.utility_params_by_good_id = {"2": 1.0, "3": 1.5} cls.agent_state = AgentState( cls.agent_address, cls.amount_by_currency_id, cls.exchange_params_by_currency_id, cls.quantities_by_good_id, cls.utility_params_by_good_id, ) cls.ledger_id = "ethereum" cls.sender_address = cls.agent_address cls.counterparty_address = "some_counterparty_address" cls.tx_amount_by_currency_id = {"1": 10} cls.tx_quantities_by_good_id = {"2": -1, "3": -2} cls.is_sender_payable_tx_fee = True cls.nonce = "some_nonce" cls.fee_by_currency_id = {"1": 1} cls.sender_signature = "some_sender_signature" cls.counterparty_signature = "some_counterparty_signature" cls.transaction_1 = Transaction( cls.ledger_id, cls.sender_address, cls.counterparty_address, cls.tx_amount_by_currency_id, cls.tx_quantities_by_good_id, cls.is_sender_payable_tx_fee, cls.nonce, cls.fee_by_currency_id, cls.sender_signature, cls.counterparty_signature, ) cls.amount_by_currency_id_2 = {"1": -9} cls.quantities_by_good_id_2 = {"2": 1, "3": 2} cls.transaction_2 = Transaction( cls.ledger_id, cls.sender_address, cls.counterparty_address, cls.amount_by_currency_id_2, cls.quantities_by_good_id_2, cls.is_sender_payable_tx_fee, cls.nonce, cls.fee_by_currency_id, cls.sender_signature, cls.counterparty_signature, ) def test_simple_properties(self): """Test the properties of AgentState class.""" assert self.agent_state.agent_address == self.agent_address assert self.agent_state.amount_by_currency_id == self.amount_by_currency_id assert ( self.agent_state.exchange_params_by_currency_id == self.exchange_params_by_currency_id ) assert self.agent_state.quantities_by_good_id == self.quantities_by_good_id assert ( self.agent_state.utility_params_by_good_id == self.utility_params_by_good_id ) def test_get_score(self): """Test the get_score of AgentState class.""" assert self.agent_state.get_score() == logarithmic_utility( self.utility_params_by_good_id, self.quantities_by_good_id ) + linear_utility( self.exchange_params_by_currency_id, self.amount_by_currency_id ) def test_is_consistent_transaction_succeeds(self): """Test the is_consistent_transaction of AgentState class where it returns True.""" assert self.agent_state.is_consistent_transaction(self.transaction_1) is True def test_is_consistent_transaction_fails_i(self): """Test the is_consistent_transaction of AgentState class where it fails because agent address is not sender/counterparty.""" self.transaction_1._sender_address = "some_sender_address" assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_is_consistent_transaction_fails_ii(self): """Test the is_consistent_transaction of AgentState class where it fails because tx is not single currency.""" self.transaction_1._amount_by_currency_id = {"1": 10, "2": 20} assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_is_consistent_transaction_fails_iii(self): """Test the is_consistent_transaction of AgentState class where it fails because there is no exchange of wealth.""" self.transaction_1._amount_by_currency_id = {"1": 0} self.transaction_1._quantities_by_good_id = {"2": 0, "3": 0} assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_is_consistent_transaction_fails_iv(self): """Test the is_consistent_transaction of AgentState class where it fails because sender does not have enough funds.""" self.transaction_1._amount_by_currency_id = {"1": -11} self.transaction_1._quantities_by_good_id = {"2": 1, "3": 0} assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_is_consistent_transaction_succeeds_iv(self): """Test the is_consistent_transaction of AgentState class where it succeeds and sender does have enough funds.""" self.transaction_1._amount_by_currency_id = {"1": -9} self.transaction_1._quantities_by_good_id = {"2": 1, "3": 0} assert self.agent_state.is_consistent_transaction(self.transaction_1) is True def test_is_consistent_transaction_fails_v(self): """Test the is_consistent_transaction of AgentState class where it fails because counterparty does not have enough goods.""" self.transaction_1._counterparty_address = self.agent_address self.transaction_1._sender_address = "some_sender_address" self.transaction_1._amount_by_currency_id = {"1": -10} self.transaction_1._quantities_by_good_id = {"2": 2, "3": 2} assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_is_consistent_transaction_succeeds_v(self): """Test the is_consistent_transaction of AgentState class where it succeeds and counterparty does have enough goods.""" self.transaction_1._counterparty_address = self.agent_address self.transaction_1._sender_address = "some_sender_address" self.transaction_1._amount_by_currency_id = {"1": -10} self.transaction_1._quantities_by_good_id = {"2": 1, "3": 2} assert self.agent_state.is_consistent_transaction(self.transaction_1) is True def test_is_consistent_transaction_fails_vi(self): """Test the is_consistent_transaction of AgentState class where it fails because sender does not have enough goods.""" self.transaction_1._amount_by_currency_id = {"1": 10} self.transaction_1._quantities_by_good_id = {"2": -2, "3": -2} assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_is_consistent_transaction_succeeds_vi(self): """Test the is_consistent_transaction of AgentState class where it succeeds and sender does have enough goods.""" self.transaction_1._amount_by_currency_id = {"1": 10} self.transaction_1._quantities_by_good_id = {"2": -1, "3": -2} assert self.agent_state.is_consistent_transaction(self.transaction_1) is True def test_is_consistent_transaction_fails_vii(self): """Test the is_consistent_transaction of AgentState class where it fails because counterparty does not have enough funds.""" self.transaction_1._counterparty_address = self.agent_address self.transaction_1._sender_address = "some_sender_address" self.transaction_1._amount_by_currency_id = {"1": 11} self.transaction_1._quantities_by_good_id = {"2": -1, "3": -2} assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_is_consistent_transaction_succeeds_vii(self): """Test the is_consistent_transaction of AgentState class where it succeeds and counterparty does have enough funds.""" self.transaction_1._counterparty_address = self.agent_address self.transaction_1._sender_address = "some_sender_address" self.transaction_1._amount_by_currency_id = {"1": 9} self.transaction_1._quantities_by_good_id = {"2": -1, "3": -2} assert self.agent_state.is_consistent_transaction(self.transaction_1) is True def test_is_consistent_transaction_fails_viii(self): """Test the is_consistent_transaction of AgentState class where it fails because inconsistent values.""" self.transaction_1._amount_by_currency_id = {"1": -11} self.transaction_1._quantities_by_good_id = {"2": -1, "3": -2} assert self.agent_state.is_consistent_transaction(self.transaction_1) is False def test_apply(self): """Test the apply of AgentState class.""" new_agent_state = self.agent_state.apply( [self.transaction_1, self.transaction_2] ) assert new_agent_state.amount_by_currency_id == {"1": 11} assert new_agent_state.quantities_by_good_id == {"2": 1, "3": 2} def test_update_sender_i(self): """Test the update of AgentState class where agent is tx sender.""" self.agent_state.update(self.transaction_1) assert self.agent_state.amount_by_currency_id == {"1": 20} assert self.agent_state.quantities_by_good_id == {"2": 0, "3": 0} def test_update_sender_ii(self): """Test the update of AgentState class where agent is tx sender.""" self.agent_state.update(self.transaction_2) assert self.agent_state.amount_by_currency_id == {"1": 1} assert self.agent_state.quantities_by_good_id == {"2": 2, "3": 4} def test_update_counterparty_i(self): """Test the update of AgentState class where agent is tx counterparty.""" # setup self.transaction_1._sender_address = "some_sender_address" self.transaction_1._counterparty_address = self.agent_address # operation self.agent_state.update(self.transaction_1) # after assert self.agent_state.amount_by_currency_id == {"1": 0} assert self.agent_state.quantities_by_good_id == {"2": 2, "3": 4} def test_update_counterparty_ii(self): """Test the update of AgentState class where agent is tx counterparty.""" # setup self.transaction_2._sender_address = "some_sender_address" self.transaction_2._counterparty_address = self.agent_address # operation self.agent_state.update(self.transaction_2) # after assert self.agent_state.amount_by_currency_id == {"1": 19} assert self.agent_state.quantities_by_good_id == {"2": 0, "3": 0} def test__copy__(self): """Test the __copy__ of AgentState class.""" new_agent_state = self.agent_state.__copy__() assert new_agent_state == self.agent_state def test__str__(self): """Test the __str__ of AgentState class.""" agent_state_str = self.agent_state.__str__() assert agent_state_str == "AgentState{}".format( pprint.pformat( { "agent_address": self.agent_state.agent_address, "amount_by_currency_id": self.agent_state.amount_by_currency_id, "exchange_params_by_currency_id": self.agent_state.exchange_params_by_currency_id, "quantities_by_good_id": self.agent_state.quantities_by_good_id, "utility_params_by_good_id": self.agent_state.utility_params_by_good_id, } ) ) def test__eq__(self): """Test the __eq__ of AgentState class.""" another_agent_state = AgentState( self.agent_address, self.amount_by_currency_id, self.exchange_params_by_currency_id, self.quantities_by_good_id, self.utility_params_by_good_id, ) assert self.agent_state.__eq__(another_agent_state) is True class TestTransactions: """Test Initialization class of tac control.""" @classmethod def setup(cls): """Setup the test class.""" cls.transactions = Transactions() def test_simple_properties(self): """Test the properties of Game class.""" assert self.transactions.confirmed == {} assert self.transactions.confirmed_per_agent == {} def test_add(self): """Test the add of Transactions class which succeeds.""" ledger_id = "ethereum" sender_address = "some_agent_address" counterparty_address = "some_counterparty_address" tx_amount_by_currency_id = {"1": 10} tx_quantities_by_good_id = {"2": -1, "3": -2} is_sender_payable_tx_fee = True nonce = "some_nonce" fee_by_currency_id = {"1": 1} sender_signature = "some_sender_signature" counterparty_signature = "some_counterparty_signature" transaction = Transaction( ledger_id, sender_address, counterparty_address, tx_amount_by_currency_id, tx_quantities_by_good_id, is_sender_payable_tx_fee, nonce, fee_by_currency_id, sender_signature, counterparty_signature, ) mocked_now = datetime.datetime.strptime("01 01 2020 00:01", "%d %m %Y %H:%M") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now with patch("datetime.datetime", new=datetime_mock): self.transactions.add(transaction) assert self.transactions.confirmed[mocked_now] == transaction assert ( self.transactions.confirmed_per_agent[sender_address][mocked_now] == transaction ) assert ( self.transactions.confirmed_per_agent[counterparty_address][mocked_now] == transaction ) class TestRegistration: """Test Registration class of tac control.""" @classmethod def setup(cls): """Setup the test class.""" cls.registration = Registration() def test_simple_properties(self): """Test the properties of Game class.""" assert self.registration.agent_addr_to_name == {} assert self.registration.nb_agents == 0 def test_register_agent(self): """Test the register_agent of Registration class which succeeds.""" agent_addr = "some_agent_address" agent_name = "some_agent_name" self.registration.register_agent(agent_addr, agent_name) assert self.registration.agent_addr_to_name == {agent_addr: agent_name} assert self.registration.nb_agents == 1 def test_unregister_agent(self): """Test the unregister_agent of Registration class which succeeds.""" agent_addr = "some_agent_address" agent_name = "some_agent_name" self.registration.register_agent(agent_addr, agent_name) assert self.registration.agent_addr_to_name == {agent_addr: agent_name} assert self.registration.nb_agents == 1 self.registration.unregister_agent(agent_addr) assert self.registration.agent_addr_to_name == {} assert self.registration.nb_agents == 0 class TestGame(BaseSkillTestCase): """Test Game class of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.amount_by_currency_id = {"FET": 10} cls.exchange_params_by_currency_id = {"FET": 1.0} cls.quantities_by_good_id = {"G1": 1, "G2": 2} cls.utility_params_by_good_id = {"G1": 1.0, "G2": 1.5} cls.game = Game(name="Game", skill_context=cls._skill.skill_context) cls._skill.skill_context.parameters = Parameters( ledger_id="", contract_address=None, good_ids=[], currency_ids=[], min_nb_agents=2, money_endowment=200, nb_goods=9, nb_currencies=1, tx_fee=1, base_good_endowment=2, lower_bound_factor=1, upper_bound_factor=1, registration_start_time="01 01 2020 00:01", registration_timeout=60, item_setup_timeout=60, competition_timeout=300, inactivity_timeout=30, whitelist=[], location={"longitude": 0.1270, "latitude": 51.5194}, service_data={"key": "tac", "value": "v1"}, name="parameters", skill_context=cls._skill.skill_context, ) cls.game._conf = "stub" def test_simple_properties(self): """Test the properties of Game class.""" self.game._conf = None # phase assert self.game.phase == Phase.PRE_GAME with patch.object(self.game.context.logger, "log") as mock_logger: self.game.phase = Phase.GAME mock_logger.assert_any_call(logging.DEBUG, f"Game phase set to: {Phase.GAME}") assert self.game.phase == Phase.GAME # registration assert self.game.registration.nb_agents == 0 assert self.game.registration.agent_addr_to_name == {} # conf with pytest.raises( AEAEnforceError, match="Call create before calling configuration." ): assert self.game.conf conf = Configuration( "some_version_id", 1, {"ag_1_add": "ag_1", "ag_2_add": "ag_2"}, {"FET": "fetch"}, {"G_1": "good_1", "G_2": "good_2"}, ) self.game._conf = conf assert self.game.conf == conf # initialization with pytest.raises( AEAEnforceError, match="Call create before calling initialization." ): assert self.game.initialization init = Initialization({}, {}, {}, {}, {}, {}, {}) self.game._initialization = init assert self.game.initialization == init # initial_agent_states with pytest.raises( AEAEnforceError, match="Call create before calling initial_agent_states." ): assert self.game.initial_agent_states ias = {} self.game._initial_agent_states = ias assert self.game.initial_agent_states == ias # current_agent_states with pytest.raises( AEAEnforceError, match="Call create before calling current_agent_states." ): assert self.game.current_agent_states cas = {} self.game._current_agent_states = cas assert self.game.current_agent_states == cas # transactions tx = Transactions() self.game._transactions = tx assert self.game.transactions == tx # is_allowed_to_mint assert self.game.is_allowed_to_mint is True self.game.is_allowed_to_mint = False assert self.game.is_allowed_to_mint is False def test_create_succeeds(self): """Test the create method of the Game class which succeeds.""" self.game.phase = Phase.PRE_GAME with patch.object(self.game, "_generate") as mock_generate: self.game.create() assert self.game.phase == Phase.GAME_SETUP mock_generate.assert_called_once() def test_create_fails(self): """Test the create method of the Game class which fails because phase is Game.""" self.game.phase = Phase.GAME with pytest.raises(AEAEnforceError, match="A game phase is already active."): with patch.object(self.game, "_generate"): self.game.create() assert self.game.phase == Phase.GAME def test_get_next_agent_state_for_minting(self): """Test the get_next_agent_state_for_minting method of the Game class.""" agent_state = AgentState( "some_address_1", self.amount_by_currency_id, self.exchange_params_by_currency_id, self.quantities_by_good_id, self.utility_params_by_good_id, ) self.game._initial_agent_states = {"ag1": agent_state} self.game._already_minted_agents = [] actual_agent_state = self.game.get_next_agent_state_for_minting() assert actual_agent_state == agent_state self.game._already_minted_agents = ["ag1"] actual_agent_state = self.game.get_next_agent_state_for_minting() assert actual_agent_state is None def test_create_generate(self): """Test the _generate method of the Game class.""" # before assert self.game._conf == "stub" assert self.game._initialization is None assert self.game._initial_agent_states is None assert self.game._current_agent_states is None agent_addr_1 = "some_agent_address_1" agent_name_1 = "some_agent_name_1" agent_addr_2 = "some_agent_address_2" agent_name_2 = "some_agent_name_2" self.game.registration.register_agent(agent_addr_1, agent_name_1) self.game.registration.register_agent(agent_addr_2, agent_name_2) # operation self.game._generate() # after assert self.game._conf != "stub" assert self.game.initialization is not None assert self.game._initial_agent_states is not None assert self.game._current_agent_states is not None def test_holdings_summary(self): """Test the holdings_summary method of the Game class.""" # before agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" self.game._conf = Configuration( "some_version_id", 1, {agent_address_1: "agent_name_1", agent_address_2: "agent_name_2"}, {"1": "currency_1"}, {"2": "good_1", "3": "good_2"}, ) agent_state_1 = AgentState( agent_address_1, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) agent_state_2 = AgentState( agent_address_2, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) self.game._current_agent_states = { agent_address_1: agent_state_1, agent_address_2: agent_state_2, } expected_holding_summary = ( "\nCurrent good & money allocation & score: \n" "- agent_name_1:\n" " good_1: 1\n" " good_2: 2\n" " currency_1: 10\n" " score: 21.55\n" "- agent_name_2:\n" " good_1: 1\n" " good_2: 2\n" " currency_1: 10\n" " score: 21.55\n\n" ) # operation holding_summary = self.game.holdings_summary # after assert holding_summary == expected_holding_summary def test_equilibrium_summary(self): """Test the equilibrium_summary method of the Game class.""" # before agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" self.game._conf = Configuration( "some_version_id", 1, {agent_address_1: "agent_name_1", agent_address_2: "agent_name_2"}, {"1": "currency_1"}, {"2": "good_1", "3": "good_2"}, ) self.game._initialization = Initialization( {agent_address_1: {"1": 10}, agent_address_2: {"1": 5}}, {agent_address_1: {"1": 1.0}, agent_address_2: {"1": 1.5}}, {agent_address_1: {"2": 5, "3": 7}, agent_address_2: {"2": 4, "3": 6}}, { agent_address_1: {"2": 1.0, "3": 1.1}, agent_address_2: {"2": 1.3, "3": 1.5}, }, {"2": 1.7, "3": 1.3}, { agent_address_1: {"2": 1.2, "3": 1.1}, agent_address_2: {"2": 1.1, "3": 1.4}, }, {agent_address_1: {"1": 1.1}, agent_address_2: {"1": 1.2}}, ) expected_equilibrium_summary = ( "\nEquilibrium prices: \n" "good_1 1.7\n" "good_2 1.3\n\n" "Equilibrium good allocation: \n" "- agent_name_1:\n" " good_1: 1.2\n" " good_2: 1.1\n" "- agent_name_2:\n" " good_1: 1.1\n" " good_2: 1.4\n\n" "Equilibrium money allocation: \n" "- agent_name_1:\n" " currency_1: 1.1\n" "- agent_name_2:\n" " currency_1: 1.2\n\n" ) # operation equilibrium_summary = self.game.equilibrium_summary # after assert equilibrium_summary == expected_equilibrium_summary def test_is_transaction_valid_succeeds(self): """Test the is_transaction_valid method of the Game class which succeeds.""" # before agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" tx = Transaction( "ethereum", agent_address_1, agent_address_2, {"1": 10}, {"2": 5, "5": 10}, True, "some_nonce", {"1": 1}, "some_sender_signature", "some_counterparty_signature", ) agent_state_1 = AgentState( agent_address_1, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) agent_state_2 = AgentState( agent_address_2, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) self.game._current_agent_states = { agent_address_1: agent_state_1, agent_address_2: agent_state_2, } # operation with patch.object(Transaction, "has_matching_signatures", return_value=True): with patch.object( AgentState, "is_consistent_transaction", return_value=True ): assert self.game.is_transaction_valid(tx) is True def test_is_transaction_valid_fails_not_matching_signatures(self): """Test the is_transaction_valid method of the Game class which fails because the signatures do no match.""" # before agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" tx = Transaction( "ethereum", agent_address_1, agent_address_2, {"1": 10}, {"2": 5, "5": 10}, True, "some_nonce", {"1": 1}, "some_sender_signature", "some_counterparty_signature", ) agent_state_1 = AgentState( agent_address_1, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) agent_state_2 = AgentState( agent_address_2, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) self.game._current_agent_states = { agent_address_1: agent_state_1, agent_address_2: agent_state_2, } # operation with patch.object(Transaction, "has_matching_signatures", return_value=False): with patch.object( AgentState, "is_consistent_transaction", return_value=True ): assert self.game.is_transaction_valid(tx) is False def test_is_transaction_valid_fails_tx_inconsistent(self): """Test the is_transaction_valid method of the Game class which fails because transactions are inconsistent.""" # before agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" tx = Transaction( "ethereum", agent_address_1, agent_address_2, {"1": 10}, {"2": 5, "5": 10}, True, "some_nonce", {"1": 1}, "some_sender_signature", "some_counterparty_signature", ) agent_state_1 = AgentState( agent_address_1, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) agent_state_2 = AgentState( agent_address_2, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) self.game._current_agent_states = { agent_address_1: agent_state_1, agent_address_2: agent_state_2, } # operation with patch.object(Transaction, "has_matching_signatures", return_value=True): with patch.object( AgentState, "is_consistent_transaction", return_value=False ): assert self.game.is_transaction_valid(tx) is False def test_settle_transaction_succeeds(self): """Test the settle_transaction method of the Game class which succeeds.""" # setup agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" tx = Transaction( "ethereum", agent_address_1, agent_address_2, {"1": 10}, {"2": -1, "3": 0}, True, "some_nonce", {"1": 1}, "some_sender_signature", "some_counterparty_signature", ) agent_state_1 = AgentState( agent_address_1, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) agent_state_2 = AgentState( agent_address_2, {"1": 10}, {"1": 1.0}, {"2": 1, "3": 2}, {"2": 1.0, "3": 1.5}, ) self.game._current_agent_states = { agent_address_1: agent_state_1, agent_address_2: agent_state_2, } expected_agent_state_1 = AgentState( agent_address_1, {"1": 20}, {"1": 1.0}, {"2": 0, "3": 2}, {"2": 1.0, "3": 1.5}, ) expected_agent_state_2 = AgentState( agent_address_2, {"1": 0}, {"1": 1.0}, {"2": 2, "3": 2}, {"2": 1.0, "3": 1.5}, ) # before assert self.game._current_agent_states[agent_address_1] == agent_state_1 assert self.game._current_agent_states[agent_address_2] == agent_state_2 # operation with patch.object(Transaction, "has_matching_signatures", return_value=True): self.game.settle_transaction(tx) # after assert ( self.game._current_agent_states[agent_address_1] == expected_agent_state_1 ) assert ( self.game._current_agent_states[agent_address_2] == expected_agent_state_2 ) def test_settle_transaction_fails_current_agent_states_is_none(self): """Test the settle_transaction method of the Game class which fails because current_agent_states is None.""" # before agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" tx = Transaction( "ethereum", agent_address_1, agent_address_2, {"1": 10}, {"2": 5, "5": 10}, True, "some_nonce", {"1": 1}, "some_sender_signature", "some_counterparty_signature", ) # operation with pytest.raises( AEAEnforceError, match="Call create before calling current_agent_states." ): assert self.game.settle_transaction(tx) def test_settle_transaction_fails_tx_invalid(self): """Test the settle_transaction method of the Game class which fails because transaction is invalid.""" # before agent_address_1 = "agent_address_1" agent_address_2 = "agent_address_2" tx = Transaction( "ethereum", agent_address_1, agent_address_2, {"1": 10}, {"2": 5, "5": 10}, True, "some_nonce", {"1": 1}, "some_sender_signature", "some_counterparty_signature", ) self.game._current_agent_states = "some_current_agent_states" # operation with patch.object(self.game, "is_transaction_valid", return_value=False): with pytest.raises(AEAEnforceError, match="Transaction is not valid."): assert self.game.settle_transaction(tx) def test_get_location_description(self): """Test the get_location_description method of the Game class.""" description = self.game.get_location_description() assert type(description) == Description assert description.data_model is AGENT_LOCATION_MODEL assert description.values.get("location", "") == Location( longitude=0.1270, latitude=51.5194 ) def test_get_register_tac_description(self): """Test the get_register_tac_description method of the Game class.""" description = self.game.get_register_tac_description() assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert description.values.get("key", "") == "tac" assert description.values.get("value", "") == "v1" def test_get_register_personality_description(self): """Test the get_register_personality_description method of the GenericStrategy class.""" description = self.game.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "genus" assert description.values.get("value", "") == "service" def test_get_register_classification_description(self): """Test the get_register_classification_description method of the GenericStrategy class.""" description = self.game.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "classification" assert description.values.get("value", "") == "tac.controller" def test_get_unregister_tac_description(self): """Test the get_unregister_tac_description method of the Game class.""" description = self.game.get_unregister_tac_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == "tac" ================================================ FILE: tests/test_packages/test_skills/test_tac_control/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the tac control skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import PropertyMock, patch import pytest from aea.helpers.search.models import Attribute, DataModel, Description, Location from aea.protocols.dialogue.base import DialogueMessage, Dialogues from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_control.behaviours import TacBehaviour from packages.fetchai.skills.tac_control.dialogues import ( OefSearchDialogues, TacDialogues, ) from packages.fetchai.skills.tac_control.game import ( Configuration, Game, Phase, Transaction, ) from packages.fetchai.skills.tac_control.handlers import OefSearchHandler, TacHandler from packages.fetchai.skills.tac_control.parameters import Parameters from tests.conftest import ROOT_DIR class TestTacHandler(BaseSkillTestCase): """Test tac handler of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.tac_handler = cast(TacHandler, cls._skill.skill_context.handlers.tac) cls.tac_dialogues = cast(TacDialogues, cls._skill.skill_context.tac_dialogues) cls.game = cast(Game, cls._skill.skill_context.game) cls.parameters = cast(Parameters, cls._skill.skill_context.parameters) cls.agent_name = "some_agent_name" cls.list_of_messages = ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": cls.agent_name}, True ), DialogueMessage( TacMessage.Performative.GAME_DATA, { "amount_by_currency_id": {"FET": 1}, "exchange_params_by_currency_id": {"FET": 1.0}, "quantities_by_good_id": {"G1": 10}, "utility_params_by_good_id": {"G1": 1.0}, "fee_by_currency_id": {"FET": 1}, "agent_addr_to_name": {COUNTERPARTY_AGENT_ADDRESS: "some_name"}, "currency_id_to_name": {"FET": "FETCH"}, "good_id_to_name": {"G1": "Good_1"}, "version_id": "v1", }, ), ) def test_setup(self): """Test the setup method of the fipa handler.""" assert self.tac_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the fipa handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=TacMessage, dialogue_reference=incorrect_dialogue_reference, performative=TacMessage.Performative.CANCELLED, ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid tac message={incoming_message}, unidentified dialogue (reference={incoming_message.dialogue_reference}).", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"tac_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_on_register_not_pre_reg_phase(self): """Test the _on_register method of the tac handler where phase is NOT pre_registration.""" # setup self.game._phase = Phase.PRE_GAME incoming_message = self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.REGISTER, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), agent_name=self.agent_name, ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"received registration outside of game registration phase: '{incoming_message}'", ) def test_on_register_agent_not_in_whitelist(self): """Test the _on_register method of the tac handler where the agent is NOT in the whitelist.""" # setup self.game._phase = Phase.GAME_REGISTRATION self.parameters._whitelist = ["some_other_agent", "yet_another_agent"] incoming_message = self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.REGISTER, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), agent_name=self.agent_name, ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"agent name not in whitelist: '{self.agent_name}'" ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, error_code=TacMessage.ErrorCode.AGENT_NAME_NOT_IN_WHITELIST, ) assert has_attributes, error_str def test_on_register_agent_address_already_exists(self): """Test the _on_register method of the tac handler where the agent address of the sender is already registered.""" # setup self.game._phase = Phase.GAME_REGISTRATION self.game._registration.register_agent( COUNTERPARTY_AGENT_ADDRESS, self.agent_name ) incoming_message = self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.REGISTER, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), agent_name="some_name", ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"agent already registered: '{self.agent_name}'" ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, error_code=TacMessage.ErrorCode.AGENT_ADDR_ALREADY_REGISTERED, ) assert has_attributes, error_str def test_on_register_agent_name_already_exists(self): """Test the _on_register method of the tac handler where the agent name of the sender is already registered.""" # setup self.game._phase = Phase.GAME_REGISTRATION self.game._registration.register_agent("some_address", self.agent_name) incoming_message = self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.REGISTER, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), agent_name=self.agent_name, ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"agent with this name already registered: '{self.agent_name}'", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, error_code=TacMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED, ) assert has_attributes, error_str def test_on_register(self): """Test the _on_register method of the tac handler, the successful case.""" # setup self.game._phase = Phase.GAME_REGISTRATION incoming_message = self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.REGISTER, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), agent_name=self.agent_name, ) # before assert self.game.registration.nb_agents == 0 # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"agent '{incoming_message.sender}' registered as '{self.agent_name}'", ) assert self.game.registration.nb_agents == 1 def test_on_unregister_not_pre_reg_phase(self): """Test the _on_unregister method of the tac handler where phase is NOT pre_registration.""" # setup self.game._phase = Phase.PRE_GAME dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=TacMessage.Performative.UNREGISTER, ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"received unregister outside of game registration phase: '{incoming_message}'", ) def test_on_unregister_agent_address_not_registered(self): """Test the _on_unregister method of the tac handler where the agent address of the sender is not registered.""" # setup self.game._phase = Phase.GAME_REGISTRATION dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=TacMessage.Performative.UNREGISTER, ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"agent not registered: '{COUNTERPARTY_AGENT_ADDRESS}'" ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, error_code=TacMessage.ErrorCode.AGENT_NOT_REGISTERED, ) assert has_attributes, error_str def test_on_unregister(self): """Test the _on_unregister method of the tac handler: successful.""" # setup self.game._phase = Phase.GAME_REGISTRATION self.game._registration.register_agent( COUNTERPARTY_AGENT_ADDRESS, self.agent_name ) self.game._registration.register_agent("address_2", "name_2") self.game._conf = Configuration( "v1", 1, self.game.registration.agent_addr_to_name, {"key_1": "v_1"}, {"k_1": "v_1", "k_2": "v_2"}, ) dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=TacMessage.Performative.UNREGISTER, ) # before assert self.game.registration.nb_agents == 2 # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.DEBUG, f"agent unregistered: '{self.agent_name}'" ) assert self.game.registration.nb_agents == 1 def test_on_transaction(self): """Test the _on_transaction method of the tac handler where phase is NOT GAME.""" # setup self.game._phase = Phase.PRE_GAME dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:2] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, performative=TacMessage.Performative.TRANSACTION, transaction_id="some_id", ledger_id="some_ledger", sender_address=COUNTERPARTY_AGENT_ADDRESS, counterparty_address=self.skill.skill_context.agent_address, amount_by_currency_id={"FET": 1}, fee_by_currency_id={"FET": 2}, quantities_by_good_id={"G1": 1}, nonce="some_nonce", sender_signature="some_signature", counterparty_signature="some_other_signature", ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"received transaction outside of game phase: '{incoming_message}'", ) def test_on_transaction_valid(self): """Test the _on_transaction method of the tac handler where the transaction is valid.""" # setup self.game._phase = Phase.GAME tac_participant_sender = COUNTERPARTY_AGENT_ADDRESS tac_participant_counterparty = "counterparties_counterparty" counterparty_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:2], tac_participant_counterparty ) self_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:2], ) ledger_id = "some_ledger" good_ids = ["G1"] nonce = "some_nonce" amount_by_currency_id = {"FET": 1} quantities_by_good_id = {"G1": 1} tx_id = Transaction.get_hash( ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, good_ids=good_ids, sender_supplied_quantities=[1], counterparty_supplied_quantities=[0], sender_payable_amount=0, counterparty_payable_amount=1, nonce=nonce, ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=self_dialogue, performative=TacMessage.Performative.TRANSACTION, transaction_id=tx_id, ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, amount_by_currency_id=amount_by_currency_id, fee_by_currency_id={"FET": 2}, quantities_by_good_id=quantities_by_good_id, nonce=nonce, sender_signature="some_signature", counterparty_signature="some_other_signature", ), ) tx = Transaction.from_message(incoming_message) mocked_holdings_summary = "some_holdings_summary" # operation with patch.object( type(self.game), "holdings_summary", new_callable=PropertyMock, return_value=mocked_holdings_summary, ): with patch.object(self.game, "is_transaction_valid", return_value=True): with patch.object(self.game, "settle_transaction"): with patch.object( self.tac_handler.context.logger, "log" ) as mock_logger: self.tac_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(2) # _on_transaction mock_logger.assert_any_call(logging.DEBUG, f"handling transaction: {tx}") # _handle_valid_transaction mock_logger.assert_any_call( logging.INFO, f"handling valid transaction: {tx_id[-10:]}" ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, to=tac_participant_sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, transaction_id=tx_id, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, ) assert has_attributes, error_str has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, to=tac_participant_counterparty, sender=self.skill.skill_context.agent_address, # in this case message_id is negative so previous negative id is id + 1 target=counterparty_dialogue.last_message.message_id + 1, transaction_id=tx.counterparty_hash, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"transaction '{tx_id[-10:]}' between '{self.skill.skill_context.agent_address}' and '{self.skill.skill_context.agent_address}' settled successfully.", ) mock_logger.assert_any_call( logging.INFO, f"total number of transactions settled: {len(self.game.transactions.confirmed)}", ) mock_logger.assert_any_call( logging.INFO, f"current state:\n{mocked_holdings_summary}" ) def test_handle_valid_transaction_recovered_tac_dialogue_not_1(self): """Test the _handle_valid_transaction method of the tac handler where the number of recivered tac dialogues is 0.""" # setup self.game._phase = Phase.GAME tac_participant_sender = COUNTERPARTY_AGENT_ADDRESS tac_participant_counterparty = "counterparties_counterparty" self_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:2], ) ledger_id = "some_ledger" good_ids = ["G1"] nonce = "some_nonce" amount_by_currency_id = {"FET": 1} quantities_by_good_id = {"G1": 1} tx_id = Transaction.get_hash( ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, good_ids=good_ids, sender_supplied_quantities=[1], counterparty_supplied_quantities=[0], sender_payable_amount=0, counterparty_payable_amount=1, nonce=nonce, ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=self_dialogue, performative=TacMessage.Performative.TRANSACTION, transaction_id=tx_id, ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, amount_by_currency_id=amount_by_currency_id, fee_by_currency_id={"FET": 2}, quantities_by_good_id=quantities_by_good_id, nonce=nonce, sender_signature="some_signature", counterparty_signature="some_other_signature", ), ) tx = Transaction.from_message(incoming_message) mocked_holdings_summary = "some_holdings_summary" # operation with patch.object( type(self.game), "holdings_summary", new_callable=PropertyMock, return_value=mocked_holdings_summary, ): with patch.object(self.game, "is_transaction_valid", return_value=True): with patch.object(self.game, "settle_transaction"): with patch.object( self.tac_handler.context.logger, "log" ) as mock_logger: with pytest.raises( ValueError, match="Error when retrieving dialogue." ): self.tac_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) # _on_transaction mock_logger.assert_any_call(logging.DEBUG, f"handling transaction: {tx}") # _handle_valid_transaction mock_logger.assert_any_call( logging.INFO, f"handling valid transaction: {tx_id[-10:]}" ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, to=tac_participant_sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, transaction_id=tx_id, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, ) assert has_attributes, error_str def test_handle_valid_transaction_no_last_message(self): """Test the _handle_valid_transaction method of the tac handler where the recovered dialogue is empty.""" # setup self.game._phase = Phase.GAME tac_participant_sender = COUNTERPARTY_AGENT_ADDRESS tac_participant_counterparty = "counterparties_counterparty" counterparty_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:2], tac_participant_counterparty ) self_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:2], ) counterparty_dialogue._incoming_messages = [] counterparty_dialogue._outgoing_messages = [] ledger_id = "some_ledger" good_ids = ["G1"] nonce = "some_nonce" amount_by_currency_id = {"FET": 1} quantities_by_good_id = {"G1": 1} tx_id = Transaction.get_hash( ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, good_ids=good_ids, sender_supplied_quantities=[1], counterparty_supplied_quantities=[0], sender_payable_amount=0, counterparty_payable_amount=1, nonce=nonce, ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=self_dialogue, performative=TacMessage.Performative.TRANSACTION, transaction_id=tx_id, ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, amount_by_currency_id=amount_by_currency_id, fee_by_currency_id={"FET": 2}, quantities_by_good_id=quantities_by_good_id, nonce=nonce, sender_signature="some_signature", counterparty_signature="some_other_signature", ), ) tx = Transaction.from_message(incoming_message) mocked_holdings_summary = "some_holdings_summary" # operation with patch.object( type(self.game), "holdings_summary", new_callable=PropertyMock, return_value=mocked_holdings_summary, ): with patch.object(self.game, "is_transaction_valid", return_value=True): with patch.object(self.game, "settle_transaction"): with patch.object( self.tac_handler.context.logger, "log" ) as mock_logger: with pytest.raises( ValueError, match="Error when retrieving last message." ): self.tac_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) # _on_transaction mock_logger.assert_any_call(logging.DEBUG, f"handling transaction: {tx}") # _handle_valid_transaction mock_logger.assert_any_call( logging.INFO, f"handling valid transaction: {tx_id[-10:]}" ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, to=tac_participant_sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, transaction_id=tx_id, amount_by_currency_id=amount_by_currency_id, quantities_by_good_id=quantities_by_good_id, ) assert has_attributes, error_str def test_on_transaction_invalid(self): """Test the _on_transaction method of the tac handler where the transaction is invalid.""" # setup self.game._phase = Phase.GAME tac_participant_sender = COUNTERPARTY_AGENT_ADDRESS tac_participant_counterparty = "counterparties_counterparty" self_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:2], ) ledger_id = "some_ledger" good_ids = ["G1"] nonce = "some_nonce" amount_by_currency_id = {"FET": 1} quantities_by_good_id = {"G1": 1} tx_id = Transaction.get_hash( ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, good_ids=good_ids, sender_supplied_quantities=[1], counterparty_supplied_quantities=[0], sender_payable_amount=0, counterparty_payable_amount=1, nonce=nonce, ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=self_dialogue, performative=TacMessage.Performative.TRANSACTION, transaction_id=tx_id, ledger_id=ledger_id, sender_address=tac_participant_sender, counterparty_address=tac_participant_counterparty, amount_by_currency_id=amount_by_currency_id, fee_by_currency_id={"FET": 2}, quantities_by_good_id=quantities_by_good_id, nonce=nonce, sender_signature="some_signature", counterparty_signature="some_other_signature", ), ) tx = Transaction.from_message(incoming_message) # operation with patch.object(self.game, "is_transaction_valid", return_value=False): with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) # _on_transaction mock_logger.assert_any_call(logging.DEBUG, f"handling transaction: {tx}") # _handle_invalid_transaction mock_logger.assert_any_call( logging.INFO, f"handling invalid transaction: {tx_id}, tac_msg={incoming_message}", ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, to=tac_participant_sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, error_code=TacMessage.ErrorCode.TRANSACTION_NOT_VALID, info={"transaction_id": tx_id}, ) assert has_attributes, error_str def test_handle_invalid(self): """Test the _handle_invalid method of the fipa handler.""" # setup tac_dialogue = self.prepare_skill_dialogue( dialogues=self.tac_dialogues, messages=self.list_of_messages[:2], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=tac_dialogue, performative=TacMessage.Performative.CANCELLED, ) # operation with patch.object(self.tac_handler.context.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle tac message of performative={incoming_message.performative} in dialogue={tac_dialogue}.", ) def test_teardown(self): """Test the teardown method of the fipa handler.""" assert self.tac_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(BaseSkillTestCase): """Test oef search handler of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef ) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.service_registration_behaviour = cast( TacBehaviour, cls._skill.skill_context.behaviours.tac, ) cls.game = cast(Game, cls._skill.skill_context.game) cls.list_of_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} ), ) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) cls.unregister_description = Description( {"key": "seller_service"}, data_model=DataModel("remove", [Attribute("key", str, True)]), ) cls.list_of_messages_unregister = ( DialogueMessage( OefSearchMessage.Performative.UNREGISTER_SERVICE, {"service_description": cls.unregister_description}, is_incoming=False, ), ) def test_setup(self): """Test the setup method of the oef handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.game.is_registered_agent is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() assert self.game.is_registered_agent is False def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.game.is_registered_agent is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() assert self.game.is_registered_agent is False def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.game.is_registered_agent is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, is successfully registered on the SOEF.", ) assert self.game.is_registered_agent is True def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef successtargets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.game.is_registered_agent is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) assert self.game.is_registered_agent is False def test_handle_error_i(self): """Test the _handle_error method of the oef_search handler where the oef error targets register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert ( self.service_registration_behaviour.failed_registration_msg == oef_dialogue.get_message_by_id(incoming_message.target) ) def test_handle_error_ii(self): """Test the _handle_error method of the oef_search handler where the oef error does NOT target register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_unregister[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert self.service_registration_behaviour.failed_registration_msg is None def test_handle_invalid(self): """Test the _handle_invalid method of the oef handler.""" # setup invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=("1", ""), performative=invalid_performative, service_description="some_service_description", ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_tac_control/test_helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the helpers module of the tac control skill.""" from pathlib import Path from unittest.mock import patch import pytest from aea.exceptions import AEAEnforceError from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.tac_control.helpers import ( ERC1155Contract, _sample_good_instances, determine_scaling_factor, generate_currency_endowments, generate_currency_id_to_name, generate_currency_ids, generate_equilibrium_prices_and_holdings, generate_exchange_params, generate_good_endowments, generate_good_id_to_name, generate_good_ids, generate_utility_params, ) from tests.conftest import ROOT_DIR class TestHelpers(BaseSkillTestCase): """Test Helper module methods of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") @classmethod def setup(cls): """Setup the test class.""" super().setup() def test_generate_good_ids_succeeds(self): """Test the generate_good_ids of Helpers module.""" expected_list = [1, 2, 3, 4, 5] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): good_ids = generate_good_ids(5, 2) assert good_ids == expected_list def test_generate_good_ids_fails(self): """Test the generate_good_ids of Helpers module which fails because the generate_token_ids generates wrong good ids.""" expected_list = [1, 2, 3, 4, 5, 6] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): with pytest.raises( AEAEnforceError, match="Length of good ids and number of goods must match.", ): assert generate_good_ids(5, 2) def test_generate_currency_ids_succeeds(self): """Test the generate_good_ids of Helpers module which succeeds.""" expected_list = [1, 2, 3, 4, 5] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): currency_ids = generate_currency_ids(5, 2) assert currency_ids == expected_list def test_generate_currency_ids_fails(self): """Test the generate_good_ids of Helpers module which fails because generate_token_ids generates wrong currency ids.""" expected_list = [1, 2, 3, 4, 5, 6] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): with pytest.raises( AEAEnforceError, match="Length of currency ids and number of currencies must match.", ): assert generate_currency_ids(5, 2) def test_generate_currency_id_to_name(self): """Test the generate_currency_id_to_name of Helpers module.""" expected_currency_id_to_name = { "1": "FT_1", "3": "FT_3", "5": "FT_5", "7": "FT_7", "9": "FT_9", } currency_id_to_name = generate_currency_id_to_name(5, [1, 3, 5, 7, 9]) assert currency_id_to_name == expected_currency_id_to_name def test_generate_currency_id_to_name_invalid_lengths(self): """Test the generate_currency_id_to_name of Helpers module where the lengths do not match.""" # phase with pytest.raises( AEAEnforceError, match="Length of currency_ids does not match nb_currencies.", ): assert generate_currency_id_to_name(nb_currencies=1, currency_ids=[1, 2]) def test_generate_good_id_to_name(self): """Test the generate_good_id_to_name of Helpers module.""" expected_good_id_to_name = { "1": "FT_1", "3": "FT_3", "5": "FT_5", "7": "FT_7", "9": "FT_9", } good_id_to_name = generate_good_id_to_name(5, [1, 3, 5, 7, 9]) assert good_id_to_name == expected_good_id_to_name def test_generate_good_id_to_name_invalid_lengths(self): """Test the generate_good_id_to_name of Helpers module.""" # phase with pytest.raises( AEAEnforceError, match="Length of good_ids does not match nb_goods." ): assert generate_good_id_to_name(nb_goods=1, good_ids=[1, 2]) def test_determine_scaling_factor(self): """Test the determine_scaling_factor of Helpers module.""" money_endowment = 53730411 scaling_factor = determine_scaling_factor(money_endowment) assert scaling_factor == 10000000.0 def test_generate_good_endowments(self): """Test the generate_good_endowments of Helpers module.""" endowments = generate_good_endowments( ["ag_1_add", "ag_2_add"], ["good_id_1", "good_id_2"], 2, 1, 1 ) assert "good_id_1" in endowments["ag_1_add"] assert "good_id_2" in endowments["ag_1_add"] assert "good_id_1" in endowments["ag_2_add"] assert "good_id_2" in endowments["ag_2_add"] def test_generate_utility_params(self): """Test the generate_utility_params of Helpers module.""" utility_function_params = generate_utility_params( ["ag_1_add", "ag_2_add"], ["good_id_1", "good_id_2"], 1000.0 ) assert "good_id_1" in utility_function_params["ag_1_add"].keys() assert "good_id_2" in utility_function_params["ag_1_add"].keys() assert "good_id_1" in utility_function_params["ag_2_add"].keys() assert "good_id_2" in utility_function_params["ag_2_add"].keys() def test_sample_good_instances(self): """Test the _sample_good_instances of Helpers module.""" nb_instances = _sample_good_instances(2, ["good_id_1", "good_id_2"], 2, 1, 1) assert type(nb_instances["good_id_1"]) == int assert type(nb_instances["good_id_2"]) == int def test_generate_currency_endowments(self): """Test the generate_currency_endowments of Helpers module.""" currency_endowments = generate_currency_endowments( ["ag_1_add", "ag_2_add"], ["currency_id_1", "currency_id_2"], 10 ) assert "currency_id_1" in currency_endowments["ag_1_add"].keys() assert "currency_id_2" in currency_endowments["ag_2_add"].keys() assert currency_endowments["ag_1_add"]["currency_id_1"] == 10 assert currency_endowments["ag_1_add"]["currency_id_2"] == 10 assert currency_endowments["ag_2_add"]["currency_id_1"] == 10 assert currency_endowments["ag_2_add"]["currency_id_2"] == 10 def test_generate_exchange_params(self): """Test the generate_exchange_params of Helpers module.""" currency_endowments = generate_exchange_params( ["ag_1_add", "ag_2_add"], ["currency_id_1", "currency_id_2"] ) assert "currency_id_1" in currency_endowments["ag_1_add"].keys() assert "currency_id_2" in currency_endowments["ag_2_add"].keys() assert currency_endowments["ag_1_add"]["currency_id_1"] == 1.0 assert currency_endowments["ag_1_add"]["currency_id_2"] == 1.0 assert currency_endowments["ag_2_add"]["currency_id_1"] == 1.0 assert currency_endowments["ag_2_add"]["currency_id_2"] == 1.0 def test_generate_equilibrium_prices_and_holdings(self): """Test the generate_equilibrium_prices_and_holdings of Helpers module.""" ( eq_prices_dict, eq_good_holdings_dict, eq_currency_holdings_dict, ) = generate_equilibrium_prices_and_holdings( {"ag_1": {"good_1": 1}}, {"ag_1": {"good_1": 1.0}}, {"ag_1": {"currency_1": 10}}, {"ag_1": {"currency_1": 1.0}}, 2.0, ) assert len(eq_prices_dict) == 1 assert type(eq_prices_dict["good_1"]) == float assert len(eq_good_holdings_dict) == 1 assert len(eq_good_holdings_dict["ag_1"]) == 1 assert type(eq_good_holdings_dict["ag_1"]["good_1"]) == float assert len(eq_currency_holdings_dict) == 1 assert type(eq_currency_holdings_dict["ag_1"]["currency_1"]) == float ================================================ FILE: tests/test_packages/test_skills/test_tac_control/test_parameters.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the parameters module of the tac control skill.""" import datetime import logging from pathlib import Path from unittest.mock import Mock, patch import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.models import Location from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.tac_control.parameters import Parameters from tests.conftest import ROOT_DIR class TestParameters(BaseSkillTestCase): """Test Parameters module of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.kwargs = { "ledger_id": "some_ledger_id", "contract_address": None, "good_ids": [], "currency_ids": [], "min_nb_agents": 2, "money_endowment": 200, "nb_goods": 9, "nb_currencies": 1, "tx_fee": 1, "base_good_endowment": 2, "lower_bound_factor": 1, "upper_bound_factor": 1, "registration_start_time": "01 01 2020 00:01", "registration_timeout": 60, "item_setup_timeout": 60, "competition_timeout": 300, "inactivity_timeout": 30, "whitelist": [], "location": {"longitude": 0.1270, "latitude": 51.5194}, "service_data": {"key": "tac", "value": "v1"}, "name": "parameters", "skill_context": cls._skill.skill_context, } cls.parameters = Parameters(**cls.kwargs) @staticmethod def _time(date_time: str): return datetime.datetime.strptime(date_time, "%d %m %Y %H:%M") def test_init_now_after_start(self): """Test the init of Parameters where now is after the registration_start_time.""" mocked_start_time = self._time("01 01 2020 00:01") mocked_now_time = self._time("01 01 2020 00:02") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time with patch("datetime.datetime", new=datetime_mock): with patch.object(self.parameters.context.logger, "log") as mocked_logger: Parameters(**self.kwargs) mocked_logger.assert_any_call( logging.WARNING, f"TAC registration start time {mocked_start_time} is in the past! Deregistering skill.", ) assert self.skill.skill_context.is_active is False def test_init_now_before_start(self): """Test the init of Parameters where now is before the registration_start_time.""" mocked_start_time_str = "01 01 2020 00:03" mocked_start_time = self._time(mocked_start_time_str) mocked_now_time = self._time("01 01 2020 00:02") datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time self.kwargs["registration_start_time"] = mocked_start_time_str with patch("datetime.datetime", new=datetime_mock): with patch.object(self.parameters.context.logger, "log") as mocked_logger: par = Parameters(**self.kwargs) mocked_logger.assert_any_call( logging.INFO, f"TAC registation start time: {mocked_start_time}, and registration end time: {par.registration_end_time}, and start time: " f"{par.start_time}, " f"and end time: {par.end_time}", ) def test_simple_properties(self): """Test the properties of Parameters class.""" # phase assert self.parameters.ledger_id == "some_ledger_id" self.parameters._contract_address = None with pytest.raises(AEAEnforceError, match="No contract address provided."): assert self.parameters.contract_address self.parameters.contract_address = "some_contract_address" assert self.parameters.contract_address == "some_contract_address" with pytest.raises(AEAEnforceError, match="Contract address already provided."): self.parameters.contract_address = "some_contract_address" assert self.parameters.contract_id == self.parameters._contract_id assert self.parameters.is_contract_deployed is True self.parameters._contract_address = None assert self.parameters.is_contract_deployed is False assert self.parameters.registration_end_time == datetime.datetime.strptime( "01 01 2020 00:02", "%d %m %Y %H:%M" ) assert self.parameters.inactivity_timeout == 30 assert self.parameters.agent_location == { "location": Location(latitude=51.5194, longitude=0.1270) } assert self.parameters.set_service_data == {"key": "tac", "value": "v1"} assert self.parameters.set_personality_data == { "piece": "genus", "value": "service", } assert self.parameters.set_classification == { "piece": "classification", "value": "tac.controller", } assert self.parameters.remove_service_data == {"key": "tac"} assert self.parameters.simple_service_data == {"tac": "v1"} def test_init_inconsistent(self): """Test the __init__ of the Parameters class where _check_consistency raises an exception.""" self.kwargs["contract_address"] = "some_contract_address" error_message = "If the contract address is set, then good ids and currency id must be provided and consistent." with pytest.raises(ValueError, match=error_message): assert Parameters( **self.kwargs, ) ================================================ FILE: tests/test_packages/test_skills/test_tac_control_contract/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/tac_control_contract dir.""" ================================================ FILE: tests/test_packages/test_skills/test_tac_control_contract/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the tac control contract skill.""" import datetime import logging from pathlib import Path from typing import cast from unittest.mock import Mock, patch from aea.helpers.search.models import Description from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.skills.tac_control_contract.behaviours import ( LEDGER_API_ADDRESS, TacBehaviour, ) from packages.fetchai.skills.tac_control_contract.dialogues import ( ContractApiDialogue, ContractApiDialogues, TacDialogues, ) from packages.fetchai.skills.tac_control_contract.game import ( AgentState, Configuration, Game, Phase, ) from packages.fetchai.skills.tac_control_contract.parameters import ( DEFAULT_CONTRACT_DEPLOY_FEE, DEFAULT_CONTRACT_EXECUTE_FEE, Parameters, ) from tests.conftest import ROOT_DIR class TestSkillBehaviour(BaseSkillTestCase): """Test tac behaviour of tac_control_contract.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "tac_control_contract" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.tac_behaviour = cast(TacBehaviour, cls._skill.skill_context.behaviours.tac) cls.game = cast(Game, cls._skill.skill_context.game) cls.parameters = cast(Parameters, cls._skill.skill_context.parameters) cls.tac_dialogues = cast(TacDialogues, cls._skill.skill_context.tac_dialogues) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.logger = cls.tac_behaviour.context.logger cls.mocked_reg_start_time = cls._time("00:02") cls.mocked_reg_end_time = cls._time("00:04") cls.mocked_start_time = cls._time("00:06") cls.mocked_end_time = cls._time("00:08") cls.parameters._registration_start_time = cls.mocked_reg_start_time cls.parameters._registration_end_time = cls.mocked_reg_end_time cls.parameters._start_time = cls.mocked_start_time cls.parameters._end_time = cls.mocked_end_time cls.mocked_description = Description({"foo1": 1, "bar1": 2}) cls.agent_1_address = "agent_address_1" cls.agent_1_name = "agent_name_1" cls.agent_2_address = "agent_address_2" cls.agent_2_name = "agent_name_2" cls.amount_by_currency_id = {"1": 10} cls.exchange_params_by_currency_id = {"1": 1.0} cls.quantities_by_good_id = {"2": 1, "3": 2} cls.utility_params_by_good_id = {"2": 1.0, "3": 1.5} cls.agent_1_state = AgentState( cls.agent_1_address, cls.amount_by_currency_id, cls.exchange_params_by_currency_id, cls.quantities_by_good_id, cls.utility_params_by_good_id, ) cls.agent_2_state = AgentState( cls.agent_2_address, cls.amount_by_currency_id, cls.exchange_params_by_currency_id, cls.quantities_by_good_id, cls.utility_params_by_good_id, ) def test_setup(self): """Test the setup method of the tac behaviour.""" # operation with patch.object(self.logger, "log") as mock_logger: self.tac_behaviour.setup() # after assert self.game.phase == Phase.CONTRACT_DEPLOYMENT_PROPOSAL self.assert_quantity_in_outbox(2) # first message is produced in superclass (from tac_control skill) which has its own unit tests self.drop_messages_from_outbox(1) # _request_contract_deploy_transaction message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.parameters.ledger_id, contract_id=self.parameters.contract_id, callable=ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.skill.skill_context.agent_address, "gas": 5000000, "tx_fee": DEFAULT_CONTRACT_DEPLOY_FEE, } ), ) assert has_attributes, error_str assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).terms == self.parameters.get_deploy_terms() ) assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).callable == ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) mock_logger.assert_any_call( logging.INFO, "requesting contract deployment transaction..." ) @staticmethod def _time(time: str): date_time = "01 01 2020 " + time return datetime.datetime.strptime(date_time, "%d %m %Y %H:%M") @staticmethod def _mock_time(time: str) -> Mock: mocked_now_time = TestSkillBehaviour._time(time) datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = mocked_now_time return datetime_mock def test_act_i(self): """Test the act method of the tac behaviour where phase is contract_deployed and reg_start_time < now < reg_end_time.""" # setup self.game._phase = Phase.CONTRACT_DEPLOYED mocked_now = self._mock_time("00:03") # operation with patch("datetime.datetime", new=mocked_now): with patch.object(self.tac_behaviour, "_register_tac") as mock_register_tac: with patch.object(self.logger, "log") as mock_logger: self.tac_behaviour.act() # after assert self.game.phase == Phase.GAME_REGISTRATION # _register_tac is a superclass method with its own unit test in test_tac_control mock_register_tac.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"TAC open for registration until: {self.mocked_reg_end_time}" ) def test_act_ii(self): """Test the act method of the tac behaviour where phase is game_registration and reg_end_time < now < start_time and nb_agent < min_nb_agents.""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now = self._mock_time("00:05") self.parameters._min_nb_agents = 2 self.game._registration.register_agent( COUNTERPARTY_AGENT_ADDRESS, self.agent_1_name ) # operation with patch("datetime.datetime", new=mocked_now): with patch.object(self.tac_behaviour, "_cancel_tac") as mock_cancel_tac: with patch.object( self.tac_behaviour, "_unregister_tac" ) as mock_unregister_tac: with patch.object(self.logger, "log") as mock_logger: self.tac_behaviour.act() # after mock_logger.assert_any_call(logging.INFO, "closing registration!") mock_logger.assert_any_call( logging.INFO, f"registered agents={1}, minimum agents required={self.parameters._min_nb_agents}", ) # _cancel_tac is a superclass method with its own unit test in test_tac_control mock_cancel_tac.assert_called_with(self.game) assert self.game.phase == Phase.POST_GAME # _unregister_tac is a superclass method with its own unit test in test_tac_control mock_unregister_tac.assert_called_once() assert self.skill.skill_context.is_active is False def test_act_iii(self): """Test the act method of the tac behaviour where phase is game_registration and reg_end_time < now < start_time and nb_agent >= min_nb_agents.""" # setup self.game._phase = Phase.GAME_REGISTRATION mocked_now = self._mock_time("00:05") self.parameters._contract_address = "some_contract_address" self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) # operation with patch("datetime.datetime", new=mocked_now): with patch.object( self.tac_behaviour, "_unregister_tac" ) as mock_unregister_tac: with patch.object(self.logger, "log") as mock_logger: self.tac_behaviour.act() # after mock_logger.assert_any_call(logging.INFO, "closing registration!") assert self.game.phase == Phase.GAME_SETUP assert self.game.conf.contract_address == self.parameters.contract_address # _unregister_tac is a superclass method with its own unit test in test_tac_control mock_unregister_tac.assert_called_once() def test_act_iv(self): """Test the act method of the tac behaviour where phase is GAME_SETUP and reg_end_time < now < start_time.""" # setup self.game._phase = Phase.GAME_SETUP mocked_now = self._mock_time("00:05") self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) self.game._conf = Configuration( "v1", 1, self.game.registration.agent_addr_to_name, {"1": "currency_name"}, {"2": "good_1", "3": "good_2"}, ) self.parameters._contract_address = "some_contract_address" # operation with patch("datetime.datetime", new=mocked_now): with patch.object(self.logger, "log") as mock_logger: self.tac_behaviour.act() # after assert self.game.phase == Phase.TOKENS_CREATION_PROPOSAL # _request_create_items_transaction self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.parameters.ledger_id, contract_id=self.parameters.contract_id, contract_address=self.parameters.contract_address, callable=ContractApiDialogue.Callable.GET_CREATE_BATCH_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.skill.skill_context.agent_address, "token_ids": [2, 3, 1], "gas": 5000000, "tx_fee": DEFAULT_CONTRACT_EXECUTE_FEE, } ), ) assert has_attributes, error_str assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).terms == self.parameters.get_create_token_terms() ) assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).callable == ContractApiDialogue.Callable.GET_CREATE_BATCH_TRANSACTION ) mock_logger.assert_any_call( logging.INFO, "requesting create items transaction..." ) def test_act_v(self): """Test the act method of the tac behaviour where phase is TOKENS_CREATED.""" # setup self.game._phase = Phase.TOKENS_CREATED self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) self.game._initial_agent_states = { self.agent_1_address: self.agent_1_state, self.agent_2_address: self.agent_2_state, } self.parameters._contract_address = "some_contract_address" # operation with patch.object(self.logger, "log") as mock_logger: self.tac_behaviour.act() # after assert self.game.phase == Phase.TOKENS_MINTING_PROPOSAL # _request_mint_items_transaction assert self.game.is_allowed_to_mint is False mock_logger.assert_any_call( logging.INFO, f"requesting mint_items transactions for agent={self.agent_1_name}.", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.parameters.ledger_id, contract_id=self.parameters.contract_id, contract_address=self.parameters.contract_address, callable=ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.skill.skill_context.agent_address, "recipient_address": self.agent_1_address, "token_ids": [2, 3, 1], "mint_quantities": [1, 2, 10], "gas": 5000000, "tx_fee": DEFAULT_CONTRACT_EXECUTE_FEE, } ), ) assert has_attributes, error_str assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).terms == self.parameters.get_mint_token_terms() ) assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).callable == ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION ) def test_request_mint_items_transaction_not_allowed_to_mint(self): """Test the _request_mint_items_transaction method of the tac behaviour where is_allowed_to_mint is False.""" # setup self.game._phase = Phase.TOKENS_CREATED self.game.is_allowed_to_mint = False # operation self.tac_behaviour.act() # after assert self.game.phase == Phase.TOKENS_MINTING_PROPOSAL # _request_mint_items_transaction self.assert_quantity_in_outbox(0) def test_request_mint_items_transaction_agent_state_is_none(self): """Test the _request_mint_items_transaction method of the tac behaviour where agent_state is None.""" # setup self.game._phase = Phase.TOKENS_CREATED self.game._initial_agent_states = {self.agent_1_address: None} # operation self.tac_behaviour.act() # after assert self.game.phase == Phase.TOKENS_MINTING_PROPOSAL # _request_mint_items_transaction assert self.game.is_allowed_to_mint is False self.assert_quantity_in_outbox(0) def test_act_vi(self): """Test the act method of the tac behaviour where phase is TOKENS_MINTING_PROPOSAL.""" # setup self.game._phase = Phase.TOKENS_MINTING_PROPOSAL self.parameters._min_nb_agents = 2 self.game._registration.register_agent(self.agent_1_address, self.agent_1_name) self.game._registration.register_agent(self.agent_2_address, self.agent_2_name) self.game._initial_agent_states = { self.agent_1_address: self.agent_1_state, self.agent_2_address: self.agent_2_state, } self.parameters._contract_address = "some_contract_address" # operation with patch.object(self.logger, "log") as mock_logger: self.tac_behaviour.act() # after assert self.game.phase == Phase.TOKENS_MINTING_PROPOSAL # _request_mint_items_transaction assert self.game.is_allowed_to_mint is False mock_logger.assert_any_call( logging.INFO, f"requesting mint_items transactions for agent={self.agent_1_name}.", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.parameters.ledger_id, contract_id=self.parameters.contract_id, contract_address=self.parameters.contract_address, callable=ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( { "deployer_address": self.skill.skill_context.agent_address, "recipient_address": self.agent_1_address, "token_ids": [2, 3, 1], "mint_quantities": [1, 2, 10], "gas": 5000000, "tx_fee": DEFAULT_CONTRACT_EXECUTE_FEE, } ), ) assert has_attributes, error_str assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).terms == self.parameters.get_mint_token_terms() ) assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).callable == ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION ) def test_act_vii(self): """Test the act method of the tac behaviour where phase is TOKENS_MINTED and start_time < now < end_time.""" # setup self.game._phase = Phase.TOKENS_MINTED mocked_now = self._mock_time("00:07") # operation with patch("datetime.datetime", new=mocked_now): with patch.object(self.tac_behaviour, "_start_tac") as mock_start_tac: with patch.object(self.logger, "log"): self.tac_behaviour.act() # after assert self.game.phase == Phase.GAME # _start_tac is a superclass method with its own unit test in test_tac_control mock_start_tac.assert_called_with(self.game) def test_act_viii(self): """Test the act method of the tac behaviour where phase is GAME and end_time < now.""" # setup self.game._phase = Phase.GAME mocked_now = self._mock_time("00:09") # operation with patch("datetime.datetime", new=mocked_now): with patch.object(self.tac_behaviour, "_cancel_tac") as mock_cancel_tac: with patch.object(self.logger, "log"): self.tac_behaviour.act() # after assert self.game.phase == Phase.POST_GAME # _cancel_tac is a superclass method with its own unit test in test_tac_control mock_cancel_tac.assert_called_with(self.game) assert self.skill.skill_context.is_active is False ================================================ FILE: tests/test_packages/test_skills/test_tac_control_contract/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the tac control contract skill.""" from pathlib import Path from typing import cast import pytest from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import RawTransaction, Terms from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_control_contract.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogue, LedgerApiDialogues, SigningDialogue, SigningDialogues, ) from tests.conftest import ROOT_DIR class TestTacDialogues(BaseSkillTestCase): """Test dialogue classes of tac control contract.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "tac_control_contract" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) def test_contract_api_dialogue(self): """Test the ContractApiDialogue class.""" contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # callable with pytest.raises(ValueError, match="Callable not set!"): assert contract_api_dialogue.callable callable = ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION contract_api_dialogue.callable = callable with pytest.raises(AEAEnforceError, match="Callable already set!"): contract_api_dialogue.callable = callable assert contract_api_dialogue.callable == callable # terms with pytest.raises(ValueError, match="Terms not set!"): assert contract_api_dialogue.terms terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) contract_api_dialogue.terms = terms with pytest.raises(AEAEnforceError, match="Terms already set!"): contract_api_dialogue.terms = terms assert contract_api_dialogue.terms == terms def test_contract_api_dialogues(self): """Test the ContractApiDialogues class.""" _, dialogue = self.contract_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id="some_ledger_id", contract_id="some_contract_id", callable="some_callable", kwargs=Kwargs({"some_key": "some_value"}), ) assert dialogue.role == ContractApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_ledger_api_dialogue(self): """Test the LedgerApiDialogue class.""" ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_signing_dialogue with pytest.raises(ValueError, match="Associated signing dialogue not set!"): assert ledger_api_dialogue.associated_signing_dialogue signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=SigningDialogue.Role.SKILL, ) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue with pytest.raises( AEAEnforceError, match="Associated signing dialogue already set!" ): ledger_api_dialogue.associated_signing_dialogue = signing_dialogue assert ledger_api_dialogue.associated_signing_dialogue == signing_dialogue def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_signing_dialogue(self): """Test the SigningDialogue class.""" signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_contract_api_dialogue with pytest.raises( ValueError, match="Associated contract api dialogue not set!" ): assert signing_dialogue.associated_contract_api_dialogue contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue with pytest.raises( AEAEnforceError, match="Associated contract api dialogue already set!" ): signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue assert ( signing_dialogue.associated_contract_api_dialogue == contract_api_dialogue ) def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms=Terms( "some_ledger_id", "some_sender_address", "some_counterparty_address", dict(), dict(), "some_nonce", ), raw_transaction=RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_tac_control_contract/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the tac control skill.""" import logging from pathlib import Path from typing import Dict, cast from unittest.mock import patch from aea_ledger_fetchai import FetchAIApi from aea.crypto.ledger_apis import LedgerApis from aea.helpers.transaction.base import ( RawTransaction, SignedTransaction, State, Terms, TransactionDigest, TransactionReceipt, ) from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_control_contract.dialogues import ( ContractApiDialogue, ContractApiDialogues, LedgerApiDialogue, LedgerApiDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.tac_control_contract.game import Game, Phase from packages.fetchai.skills.tac_control_contract.handlers import ( ContractApiHandler, LEDGER_API_ADDRESS, LedgerApiHandler, SigningHandler, ) from packages.fetchai.skills.tac_control_contract.parameters import ( DEFAULT_CONTRACT_DEPLOY_FEE, Parameters, ) from tests.conftest import ROOT_DIR class TestContractApiHandler(BaseSkillTestCase): """Test contract_api handler of tac control contract.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "tac_control_contract" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.contract_api_handler = cast( ContractApiHandler, cls._skill.skill_context.handlers.contract_api ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.logger = cls.contract_api_handler.context.logger cls.ledger_id = "some_ledger_id" cls.contract_id = "some_contract_id" cls.callable = "some_callable" cls.kwargs = Kwargs({"some_key": "some_value"}) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": cls.ledger_id, "contract_id": cls.contract_id, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) def test_setup(self): """Test the setup method of the contract_api handler.""" assert self.contract_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=ContractApiMessage.Performative.STATE, state=State("some_ledger_id", {"some_key": "some_value"}), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid contract_api message={incoming_message}, unidentified dialogue.", ) def test_handle_raw_transaction( self, ): """Test the _handle_signed_transaction method of the signing handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=ContractApiMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw transaction={incoming_message}" ) self.assert_quantity_in_decision_making_queue(1) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_TRANSACTION, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=contract_api_dialogue.terms, ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_contract_api_dialogue == contract_api_dialogue ) mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup contract_api_dialogue = self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ) incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received contract_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=self.ledger_id, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle contract_api message of performative={invalid_performative} in dialogue={self.contract_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the contract_api handler.""" assert self.contract_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(BaseSkillTestCase): """Test signing handler of tac control contract.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "tac_control_contract" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.logger = cls.signing_handler.context.logger cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": cls.terms, "raw_transaction": SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), }, ), ) def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_transaction( self, ): """Test the _handle_signed_transaction method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SigningMessage.SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), signed_transaction=incoming_message.signed_transaction, ) assert has_attributes, error_str assert ( cast( LedgerApiDialogue, self.ledger_api_dialogues.get_dialogue(message) ).associated_signing_dialogue == signing_dialogue ) mock_logger.assert_any_call(logging.INFO, "sending transaction to ledger.") def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.terms, raw_transaction=SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of tac control contract.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "tac_control_contract" ) is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.parameters = cast(Parameters, cls._skill.skill_context.parameters) cls.game = cast(Game, cls._skill.skill_context.game) cls.logger = cls.ledger_api_handler.context.logger cls.ledger_id = "some_ledger_id" cls.contract_id = "some_contract_id" cls.callable = "some_callable" cls.kwargs = Kwargs({"some_key": "some_value"}) cls.body = {"some_key": "some_value"} cls.body_str = "some_body" cls.contract_address = "some_contract_address" cls.raw_transaction = RawTransaction(cls.ledger_id, cls.body) cls.signed_transaction = SignedTransaction(cls.ledger_id, cls.body) cls.transaction_digest = TransactionDigest(cls.ledger_id, cls.body_str) cls.receipt = {"contractAddress": cls.contract_address} cls.code_id = 8888 cls.fetch_deploy_receipt = { "logs": [ { "events": [ { "attributes": [ {"key": "code_id", "value": cls.code_id}, { "key": "contract_address", "value": "some_contract_address", }, ] } ] } ] } cls.fetch_deploy_receipt_no_code_id = { "logs": [ { "events": [ { "attributes": [ {"key": "not_a_code_id", "value": "something"}, { "key": "contract_address", "value": "some_contract_address", }, ] } ] } ] } cls.fetch_init_receipt = {"status": 1, "contractAddress": cls.contract_address} cls.transaction_receipt = TransactionReceipt( cls.ledger_id, cls.receipt, {"transaction_key": "transaction_value"} ) cls.terms_dict = { "ledger_id": cls.ledger_id, "sender_address": cls._skill.skill_context.agent_address, "counterparty_address": "counterprty", "amount_by_currency_id": {"currency_id": 50}, "quantities_by_good_id": {"good_id": -10}, "nonce": "some_nonce", } cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": Terms(**cls.terms_dict), "raw_transaction": SigningMessage.RawTransaction( cls.ledger_id, cls.body ), }, ), ) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": cls.ledger_id, "contract_id": cls.contract_id, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {"terms": Terms(**cls.terms_dict)}, ), DialogueMessage( LedgerApiMessage.Performative.RAW_TRANSACTION, {"raw_transaction": cls.raw_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {"signed_transaction": cls.signed_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_DIGEST, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, {"transaction_digest": cls.transaction_digest}, ), ) @staticmethod def _terms(terms_dict: Dict, label: str) -> Terms: """ Provides Terms with the specified label. :param label: the label :return: Terms """ terms_dict["label"] = label return Terms(**terms_dict) @staticmethod def _transaction_receipt_builder(ledger_id: str, receipt) -> TransactionReceipt: """ Provides Terms with the specified label. :param ledger_id: the ledger_id :param receipt: the transaction receipt :return: Terms """ transaction_receipt = TransactionReceipt( ledger_id, receipt, {"transaction_key": "transaction_value"} ) return transaction_receipt def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.BALANCE, ledger_id="some_ledger_id", balance=10, ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_balance(self): """Test the _handle_balance method of the ledger_api handler.""" # setup balance = 10 ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": self.ledger_id, "address": "some_address"}, ), ), counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=self.ledger_id, balance=balance, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {self.ledger_id} ledger={incoming_message.balance}.", ) def test_handle_transaction_digest(self): """Test the _handle_transaction_digest method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:3], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=self.transaction_digest, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully submitted. Transaction digest={incoming_message.transaction_digest}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), transaction_digest=incoming_message.transaction_digest, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "requesting transaction receipt.") def test_handle_transaction_receipt_failed(self): """Test the _handle_transaction_receipt method of the ledger_api handler where the transaction is NOT settled.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=False): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, f"transaction failed. Transaction receipt={incoming_message.transaction_receipt}", ) def test_handle_transaction_receipt_callable_get_deploy_transaction_label_store( self, ): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is GET_DEPLOY_TRANSACTION and terms label is 'store'.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) contract_api_dialogue.terms = self._terms(self.terms_dict, "store") incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self._transaction_receipt_builder( FetchAIApi.identifier, self.fetch_deploy_receipt ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after # _request_init_transaction self.assert_quantity_in_outbox(1) msg = cast(ContractApiMessage, self.get_message_from_outbox()) has_attributes, error_str = self.message_has_attributes( actual_message=msg, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=self.parameters.ledger_id, contract_id=self.parameters.contract_id, callable=ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( { "label": "TACERC1155", "init_msg": {}, "gas": self.parameters.gas, "amount": 0, "code_id": self.code_id, "deployer_address": self.skill.skill_context.agent_address, "tx_fee": DEFAULT_CONTRACT_DEPLOY_FEE, } ), ) assert has_attributes, error_str assert contract_api_dialogue.terms == self.parameters.get_deploy_terms( is_init_transaction=True ) assert ( contract_api_dialogue.callable == ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) mock_logger.assert_any_call( logging.INFO, "requesting contract initialisation transaction...", ) def test_handle_transaction_receipt_callable_get_deploy_transaction_label_store_no_code_id( self, ): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is GET_DEPLOY_TRANSACTION and terms label is 'store' and no code_id.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) contract_api_dialogue.terms = self._terms(self.terms_dict, "store") incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self._transaction_receipt_builder( FetchAIApi.identifier, self.fetch_deploy_receipt_no_code_id ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after # _request_init_transaction self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, "Failed to initialise contract: code_id not found", ) def test_handle_transaction_receipt_callable_get_deploy_transaction_label_init( self, ): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is GET_DEPLOY_TRANSACTION and terms label is 'init'.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) contract_api_dialogue.terms = self._terms(self.terms_dict, "init") incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self._transaction_receipt_builder( FetchAIApi.identifier, self.fetch_init_receipt ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): with patch.object( LedgerApis, "get_contract_address", return_value=self.contract_address, ): self.ledger_api_handler.handle(incoming_message) # after assert self.parameters.contract_address == self.contract_address assert self.game.phase == Phase.CONTRACT_DEPLOYED mock_logger.assert_any_call(logging.INFO, "contract deployed.") def test_handle_transaction_receipt_callable_get_deploy_transaction_label_deploy( self, ): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is GET_DEPLOY_TRANSACTION and terms label is 'deploy'.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION ) contract_api_dialogue.terms = self._terms(self.terms_dict, "deploy") incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self._transaction_receipt_builder( FetchAIApi.identifier, self.fetch_init_receipt ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): with patch.object( LedgerApis, "get_contract_address", return_value=self.contract_address, ): self.ledger_api_handler.handle(incoming_message) # after assert self.parameters.contract_address == self.contract_address assert self.game.phase == Phase.CONTRACT_DEPLOYED mock_logger.assert_any_call(logging.INFO, "contract deployed.") def test_handle_transaction_receipt_callable_get_create_batch_transaction(self): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is GET_CREATE_BATCH_TRANSACTION.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_CREATE_BATCH_TRANSACTION ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after assert self.game.phase == Phase.TOKENS_CREATED mock_logger.assert_any_call(logging.INFO, "tokens created.") def test_handle_transaction_receipt_callable_get_mint_batch_transaction_i(self): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is GET_MINT_BATCH_TRANSACTION and all tokens are NOT minted.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) self.parameters.nb_completed_minting = 0 self.game._registration._agent_addr_to_name = { "some_address_1": "some_name_1", "some_address_2": "some_name_2", } # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "tokens minted.") assert self.parameters.nb_completed_minting == 1 assert self.game.is_allowed_to_mint is True assert self.game.phase != Phase.TOKENS_MINTED def test_handle_transaction_receipt_callable_get_mint_batch_transaction_ii(self): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is GET_MINT_BATCH_TRANSACTION and all tokens are minted.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = ( ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) self.parameters.nb_completed_minting = 0 self.game._registration._agent_addr_to_name = {"some_address": "some_name"} # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "tokens minted.") assert self.parameters.nb_completed_minting == 1 assert self.game.is_allowed_to_mint is True assert self.game.phase == Phase.TOKENS_MINTED mock_logger.assert_any_call(logging.INFO, "all tokens minted.") def test_handle_transaction_receipt_incorrect_callable(self): """Test the _handle_transaction_receipt method of the ledger_api handler where contract_api callable is incorrect.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:4], ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue ledger_api_dialogue.associated_signing_dialogue = signing_dialogue contract_api_dialogue.callable = "some_incorrect_callable" incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(LedgerApis, "is_transaction_settled", return_value=True): self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.ERROR, "unexpected transaction receipt!") def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id="some_ledger_id", address="some_address", to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_tac_control_contract/test_helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the helpers module of the tac control contract skill.""" from pathlib import Path from unittest.mock import patch import pytest from aea.exceptions import AEAEnforceError from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.tac_control_contract.helpers import ( ERC1155Contract, _sample_good_instances, determine_scaling_factor, generate_currency_endowments, generate_currency_id_to_name, generate_currency_ids, generate_equilibrium_prices_and_holdings, generate_exchange_params, generate_good_endowments, generate_good_id_to_name, generate_good_ids, generate_utility_params, ) from tests.conftest import ROOT_DIR class TestHelpers(BaseSkillTestCase): """Test Helper module methods of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_control") @classmethod def setup(cls): """Setup the test class.""" super().setup() def test_generate_good_ids_succeeds(self): """Test the generate_good_ids of Helpers module which succeeds.""" expected_list = [1, 2, 3, 4, 5] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): good_ids = generate_good_ids(5) assert good_ids == expected_list def test_generate_good_ids_fails(self): """Test the generate_good_ids of Helpers module which fails because generate_token_ids generates wrong good ids.""" expected_list = [1, 2, 3, 4, 5, 6] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): with pytest.raises( AEAEnforceError, match="Length of good ids and number of goods must match.", ): assert generate_good_ids(5) def test_generate_currency_ids_succeeds(self): """Test the generate_good_ids of Helpers module which succeeds.""" expected_list = [1, 2, 3, 4, 5] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): currency_ids = generate_currency_ids(5) assert currency_ids == expected_list def test_generate_currency_ids_fails(self): """Test the generate_good_ids of Helpers module which fails because generate_token_ids generates wrong currency ids.""" expected_list = [1, 2, 3, 4, 5, 6] with patch.object( ERC1155Contract, "generate_token_ids", return_value=expected_list ): with pytest.raises( AEAEnforceError, match="Length of currency ids and number of currencies must match.", ): assert generate_currency_ids(5) def test_generate_currency_id_to_name(self): """Test the generate_currency_id_to_name of Helpers module.""" expected_currency_id_to_name = { "1": "FT_1", "3": "FT_3", "5": "FT_5", "7": "FT_7", "9": "FT_9", } currency_id_to_name = generate_currency_id_to_name([1, 3, 5, 7, 9]) assert currency_id_to_name == expected_currency_id_to_name def test_generate_good_id_to_name(self): """Test the generate_good_id_to_name of Helpers module.""" expected_good_id_to_name = { "1": "FT_1", "3": "FT_3", "5": "FT_5", "7": "FT_7", "9": "FT_9", } good_id_to_name = generate_good_id_to_name([1, 3, 5, 7, 9]) assert good_id_to_name == expected_good_id_to_name def test_determine_scaling_factor(self): """Test the determine_scaling_factor of Helpers module.""" money_endowment = 53730411 scaling_factor = determine_scaling_factor(money_endowment) assert scaling_factor == 10000000.0 def test_generate_good_endowments(self): """Test the generate_good_endowments of Helpers module.""" endowments = generate_good_endowments( ["ag_1_add", "ag_2_add"], ["good_id_1", "good_id_2"], 2, 1, 1 ) assert "good_id_1" in endowments["ag_1_add"] assert "good_id_2" in endowments["ag_1_add"] assert "good_id_1" in endowments["ag_2_add"] assert "good_id_2" in endowments["ag_2_add"] def test_generate_utility_params(self): """Test the generate_utility_params of Helpers module.""" utility_function_params = generate_utility_params( ["ag_1_add", "ag_2_add"], ["good_id_1", "good_id_2"], 1000.0 ) assert "good_id_1" in utility_function_params["ag_1_add"].keys() assert "good_id_2" in utility_function_params["ag_1_add"].keys() assert "good_id_1" in utility_function_params["ag_2_add"].keys() assert "good_id_2" in utility_function_params["ag_2_add"].keys() def test_sample_good_instances(self): """Test the _sample_good_instances of Helpers module.""" nb_instances = _sample_good_instances(2, ["good_id_1", "good_id_2"], 2, 1, 1) assert type(nb_instances["good_id_1"]) == int assert type(nb_instances["good_id_2"]) == int def test_generate_currency_endowments(self): """Test the generate_currency_endowments of Helpers module.""" currency_endowments = generate_currency_endowments( ["ag_1_add", "ag_2_add"], ["currency_id_1", "currency_id_2"], 10 ) assert "currency_id_1" in currency_endowments["ag_1_add"].keys() assert "currency_id_2" in currency_endowments["ag_2_add"].keys() assert currency_endowments["ag_1_add"]["currency_id_1"] == 10 assert currency_endowments["ag_1_add"]["currency_id_2"] == 10 assert currency_endowments["ag_2_add"]["currency_id_1"] == 10 assert currency_endowments["ag_2_add"]["currency_id_2"] == 10 def test_generate_exchange_params(self): """Test the generate_exchange_params of Helpers module.""" currency_endowments = generate_exchange_params( ["ag_1_add", "ag_2_add"], ["currency_id_1", "currency_id_2"] ) assert "currency_id_1" in currency_endowments["ag_1_add"].keys() assert "currency_id_2" in currency_endowments["ag_2_add"].keys() assert currency_endowments["ag_1_add"]["currency_id_1"] == 1.0 assert currency_endowments["ag_1_add"]["currency_id_2"] == 1.0 assert currency_endowments["ag_2_add"]["currency_id_1"] == 1.0 assert currency_endowments["ag_2_add"]["currency_id_2"] == 1.0 def test_generate_equilibrium_prices_and_holdings(self): """Test the generate_equilibrium_prices_and_holdings of Helpers module.""" ( eq_prices_dict, eq_good_holdings_dict, eq_currency_holdings_dict, ) = generate_equilibrium_prices_and_holdings( {"ag_1": {"good_1": 1}}, {"ag_1": {"good_1": 1.0}}, {"ag_1": {"currency_1": 10}}, {"ag_1": {"currency_1": 1.0}}, 2.0, ) assert len(eq_prices_dict) == 1 assert type(eq_prices_dict["good_1"]) == float assert len(eq_good_holdings_dict) == 1 assert len(eq_good_holdings_dict["ag_1"]) == 1 assert type(eq_good_holdings_dict["ag_1"]["good_1"]) == float assert len(eq_currency_holdings_dict) == 1 assert type(eq_currency_holdings_dict["ag_1"]) == float ================================================ FILE: tests/test_packages/test_skills/test_tac_control_contract/test_parameters.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the parameters module of the tac control contract skill.""" from pathlib import Path from aea_ledger_ethereum import EthereumApi from aea_ledger_fetchai import FetchAIApi from aea.helpers.transaction.base import Terms from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.tac_control_contract.parameters import Parameters from tests.conftest import ROOT_DIR class TestParameters(BaseSkillTestCase): """Test Parameters module of tac control contract.""" path_to_skill = Path( ROOT_DIR, "packages", "fetchai", "skills", "tac_control_contract" ) @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.kwargs = { "ledger_id": "some_ledger_id", "contract_address": None, "good_ids": [], "currency_ids": [], "min_nb_agents": 2, "money_endowment": 200, "nb_goods": 9, "nb_currencies": 1, "tx_fee": 1, "base_good_endowment": 2, "lower_bound_factor": 1, "upper_bound_factor": 1, "registration_start_time": "01 01 2020 00:01", "registration_timeout": 60, "item_setup_timeout": 60, "competition_timeout": 300, "inactivity_timeout": 30, "whitelist": [], "location": {"longitude": 0.1270, "latitude": 51.5194}, "service_data": {"key": "tac", "value": "v1"}, "name": "parameters", "skill_context": cls._skill.skill_context, } cls.parameters = Parameters(**cls.kwargs) def test__init__(self): """Test the __init__ of Parameters.""" assert self.parameters.nb_completed_minting == 0 def test_get_deploy_terms(self): """Test the get_deploy_terms of Parameters.""" self.parameters._ledger_id = FetchAIApi.identifier assert self.parameters.get_deploy_terms() == Terms( FetchAIApi.identifier, self.skill.skill_context.agent_address, self.skill.skill_context.agent_address, {}, {}, "", label="store", ) self.parameters._ledger_id = FetchAIApi.identifier assert self.parameters.get_deploy_terms(True) == Terms( FetchAIApi.identifier, self.skill.skill_context.agent_address, self.skill.skill_context.agent_address, {}, {}, "", label="init", ) self.parameters._ledger_id = EthereumApi.identifier assert self.parameters.get_deploy_terms() == Terms( EthereumApi.identifier, self.skill.skill_context.agent_address, self.skill.skill_context.agent_address, {}, {}, "", label="deploy", ) def test_get_create_token_terms(self): """Test the get_create_token_terms of Parameters.""" assert self.parameters.get_create_token_terms() == Terms( "some_ledger_id", self.skill.skill_context.agent_address, self.skill.skill_context.agent_address, {}, {}, "", ) def test_get_mint_token_terms(self): """Test the get_mint_token_terms of Parameters.""" assert self.parameters.get_mint_token_terms() == Terms( "some_ledger_id", self.skill.skill_context.agent_address, self.skill.skill_context.agent_address, {}, {}, "", ) ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/tac_negotiation dir.""" ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the tac negotiation skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import PropertyMock, patch from aea.decision_maker.gop import GoalPursuitReadiness, OwnershipState, Preferences from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.tac_negotiation.behaviours import ( GoodsRegisterAndSearchBehaviour, TransactionCleanUpBehaviour, ) from packages.fetchai.skills.tac_negotiation.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.skills.tac_negotiation.strategy import Strategy from packages.fetchai.skills.tac_negotiation.transactions import Transactions from tests.conftest import ROOT_DIR class TestSkillBehaviour(BaseSkillTestCase): """Test tac behaviour of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" tac_dm_context_kwargs = { "goal_pursuit_readiness": GoalPursuitReadiness(), "ownership_state": OwnershipState(), "preferences": Preferences(), } super().setup(dm_context_kwargs=tac_dm_context_kwargs) cls.tac_negotiation = cast( GoodsRegisterAndSearchBehaviour, cls._skill.skill_context.behaviours.tac_negotiation, ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.goal_pursuit_readiness = ( cls._skill.skill_context.decision_maker_handler_context.goal_pursuit_readiness ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.mocked_description = Description({"foo1": 1, "bar1": 2}) cls.mocked_query = Query( [Constraint("tac_service", ConstraintType("==", "both"))] ) cls.sender = str(cls._skill.skill_context.skill_id) cls.registration_message = OefSearchMessage( dialogue_reference=("", ""), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description="some_service_description", ) cls.registration_message.sender = str(cls._skill.skill_context.skill_id) cls.registration_message.to = cls._skill.skill_context.search_service_address cls.tac_version_id = "some_tac_version_id" def test_init(self): """Test the __init__ method of the negotiation behaviour.""" assert self.tac_negotiation.is_registered is False assert self.tac_negotiation.failed_registration_msg is None assert self.tac_negotiation._nb_retries == 0 def test_setup(self): """Test the setup method of the negotiation behaviour.""" assert self.tac_negotiation.setup() is None self.assert_quantity_in_outbox(0) def test_act_i(self): """Test the act method of the negotiation behaviour where is_game_finished is True.""" # setup self.skill.skill_context._agent_context._shared_state = { "is_game_finished": True } # operation self.tac_negotiation.act() # after self.assert_quantity_in_outbox(0) assert self.skill.skill_context.is_active is False def test_act_ii(self): """Test the act method of the negotiation behaviour where goal_pursuit_readiness is not ready.""" # setup self.skill.skill_context._agent_context._shared_state = { "is_game_finished": False } self.goal_pursuit_readiness._status = GoalPursuitReadiness.Status.NOT_READY # operation self.tac_negotiation.act() # after self.assert_quantity_in_outbox(0) def test_act_iii(self): """Test the act method of the negotiation behaviour where tac_version_id is NOT in the shared state.""" # setup self.skill.skill_context._agent_context._shared_state = { "is_game_finished": False } self.goal_pursuit_readiness._status = GoalPursuitReadiness.Status.READY if "tac_version_id" in self.skill.skill_context._agent_context._shared_state: self.skill.skill_context._agent_context._shared_state.pop("tac_version_id") with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.act() # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.ERROR, "Cannot get the tac_version_id. Stopping!" ) def test_act_iv(self): """Test the act method of the negotiation behaviour where is_registered is False.""" # setup self.skill.skill_context._agent_context._shared_state = { "is_game_finished": False, "tac_version_id": self.tac_version_id, } self.goal_pursuit_readiness._status = GoalPursuitReadiness.Status.READY searching_for_types = [(True, "sellers"), (False, "buyers")] no_searches = len(searching_for_types) self.tac_negotiation.failed_registration_msg = None # operation with patch.object( self.strategy, "get_location_description", return_value=self.mocked_description, ): with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_description, ): with patch.object( self.strategy, "get_location_and_service_query", return_value=self.mocked_query, ): with patch.object( type(self.strategy), "searching_for_types", new_callable=PropertyMock, return_value=searching_for_types, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.act() # after self.assert_quantity_in_outbox(no_searches + 1) # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=self.sender, service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") # _search_services for search in searching_for_types: message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=self.sender, query=self.mocked_query, ) assert has_attributes, error_str assert ( cast( OefSearchDialogue, self.oef_search_dialogues.get_dialogue(message) ).is_seller_search == search[0] ) mock_logger.assert_any_call( logging.INFO, f"searching for {search[1]}, search_id={message.dialogue_reference}.", ) def test_act_v(self): """Test the act method of the negotiation behaviour where failed_registration_msg is NOT None.""" # setup self.skill.skill_context._agent_context._shared_state = { "is_game_finished": False, "tac_version_id": self.tac_version_id, } self.goal_pursuit_readiness._status = GoalPursuitReadiness.Status.READY searching_for_types = [(True, "sellers"), (False, "buyers")] no_searches = len(searching_for_types) self.tac_negotiation.failed_registration_msg = self.registration_message # operation with patch.object( self.strategy, "get_location_description", return_value=self.mocked_description, ): with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_description, ): with patch.object( self.strategy, "get_location_and_service_query", return_value=self.mocked_query, ): with patch.object( type(self.strategy), "searching_for_types", new_callable=PropertyMock, return_value=searching_for_types, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.act() # after self.assert_quantity_in_outbox(no_searches + 2) # _retry_failed_registration has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=type(self.registration_message), performative=self.registration_message.performative, to=self.registration_message.to, sender=str(self.skill.skill_context.skill_id), service_description=self.registration_message.service_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Retrying registration on SOEF. Retry {self.tac_negotiation._nb_retries} out of {self.tac_negotiation._max_soef_registration_retries}.", ) assert self.tac_negotiation.failed_registration_msg is None # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=self.sender, service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") # _search_services for search in searching_for_types: message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=self.sender, query=self.mocked_query, ) assert has_attributes, error_str assert ( cast( OefSearchDialogue, self.oef_search_dialogues.get_dialogue(message) ).is_seller_search == search[0] ) mock_logger.assert_any_call( logging.INFO, f"searching for {search[1]}, search_id={message.dialogue_reference}.", ) def test_act_vi(self): """Test the act method of the negotiation behaviour where failed_registration_msg is NOT None and max retries is reached.""" # setup self.skill.skill_context._agent_context._shared_state = { "is_game_finished": False, "tac_version_id": self.tac_version_id, } self.goal_pursuit_readiness._status = GoalPursuitReadiness.Status.READY searching_for_types = [(True, "sellers"), (False, "buyers")] no_searches = len(searching_for_types) self.tac_negotiation.failed_registration_msg = self.registration_message self.tac_negotiation._max_soef_registration_retries = 2 self.tac_negotiation._nb_retries = 2 # operation with patch.object( self.strategy, "get_location_description", return_value=self.mocked_description, ): with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_description, ): with patch.object( self.strategy, "get_location_and_service_query", return_value=self.mocked_query, ): with patch.object( type(self.strategy), "searching_for_types", new_callable=PropertyMock, return_value=searching_for_types, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.act() # after self.assert_quantity_in_outbox(no_searches + 1) assert self.skill.skill_context.is_active is False # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=self.sender, service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") # _search_services for search in searching_for_types: message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=self.sender, query=self.mocked_query, ) assert has_attributes, error_str assert ( cast( OefSearchDialogue, self.oef_search_dialogues.get_dialogue(message) ).is_seller_search == search[0] ) mock_logger.assert_any_call( logging.INFO, f"searching for {search[1]}, search_id={message.dialogue_reference}.", ) def test_act_vii(self): """Test the act method of the negotiation behaviour where is_registered is True.""" # setup self.skill.skill_context._agent_context._shared_state = { "is_game_finished": False, "tac_version_id": self.tac_version_id, } self.goal_pursuit_readiness._status = GoalPursuitReadiness.Status.READY self.tac_negotiation.is_registered = True searching_for_types = [(True, "sellers"), (False, "buyers")] no_searches = len(searching_for_types) # operation with patch.object( self.strategy, "get_location_and_service_query", return_value=self.mocked_query, ): with patch.object( type(self.strategy), "searching_for_types", new_callable=PropertyMock, return_value=searching_for_types, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.act() # after self.assert_quantity_in_outbox(no_searches) # _search_services for search in searching_for_types: message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=self.sender, query=self.mocked_query, ) assert has_attributes, error_str assert ( cast( OefSearchDialogue, self.oef_search_dialogues.get_dialogue(message) ).is_seller_search == search[0] ) mock_logger.assert_any_call( logging.INFO, f"searching for {search[1]}, search_id={message.dialogue_reference}.", ) def test_register_service(self): """Test the register_service method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.register_service() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"updating service directory as {self.strategy.registering_as}.", ) def test_register_genus(self): """Test the register_genus method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_personality_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.register_genus() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality genus on the SOEF." ) def test_register_classification(self): """Test the register_classification method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_classification_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.register_classification() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality classification on the SOEF." ) def test_teardown_i(self): """Test the teardown method of the negotiation behaviour.""" # setup self.tac_negotiation.is_registered = True # operation with patch.object( self.strategy, "get_unregister_service_description", return_value=self.mocked_description, ): with patch.object( self.strategy, "get_location_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.tac_negotiation.teardown() # after self.assert_quantity_in_outbox(2) # _unregister_service has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=self.sender, service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.DEBUG, f"unregistering from service directory as {self.strategy.registering_as}.", ) # _unregister_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=self.sender, service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering agent from SOEF.") # teardown assert self.tac_negotiation.is_registered is False def test_teardown_ii(self): """Test the teardown method of the negotiation behaviour where is_registered is False.""" # setup self.tac_negotiation.is_registered = False # operation assert self.tac_negotiation.teardown() is None # after self.assert_quantity_in_outbox(0) class TestTransactionCleanUpBehaviour(BaseSkillTestCase): """Test clean_up behaviour of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.clean_up_behaviour = cast( TransactionCleanUpBehaviour, cls._skill.skill_context.behaviours.clean_up ) cls.transactions = cast(Transactions, cls._skill.skill_context.transactions) def test_setup(self): """Test the setup method of the clean_up behaviour.""" assert self.clean_up_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act(self): """Test the act method of the clean_up behaviour.""" # operation with patch.object( self.transactions, "update_confirmed_transactions" ) as mocked_update: with patch.object( self.transactions, "cleanup_pending_transactions" ) as mocked_cleanup: self.clean_up_behaviour.act() # after self.assert_quantity_in_outbox(0) mocked_update.assert_called_once() mocked_cleanup.assert_called_once() def test_teardown(self): """Test the teardown method of the clean_up behaviour.""" assert self.clean_up_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the tac control contract skill.""" from pathlib import Path from typing import cast import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Query, ) from aea.helpers.transaction.base import RawTransaction, Terms from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.cosm_trade.message import CosmTradeMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_negotiation.dialogues import ( ContractApiDialogue, ContractApiDialogues, CosmTradeDialogue, CosmTradeDialogues, DefaultDialogue, DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.tac_negotiation.helpers import SUPPLY_DATAMODEL_NAME from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue classes of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.cosm_trade_dialogues = cast( CosmTradeDialogues, cls._skill.skill_context.cosm_trade_dialogues ) cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( SUPPLY_DATAMODEL_NAME, [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) def test_fipa_dialogue(self): """Test the FipaDialogue class.""" fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=FipaDialogue.Role.BUYER, ) # counterparty_signature with pytest.raises(ValueError, match="counterparty_signature not set!"): assert fipa_dialogue.counterparty_signature fipa_dialogue.counterparty_signature = "some_counterparty_signature" with pytest.raises( AEAEnforceError, match="counterparty_signature already set!" ): fipa_dialogue.counterparty_signature = "some_other_counterparty_signature" assert fipa_dialogue.counterparty_signature == "some_counterparty_signature" # proposal with pytest.raises(ValueError, match="Proposal not set!"): assert fipa_dialogue.proposal description = Description({"foo1": 1, "bar1": 2}) fipa_dialogue.proposal = description with pytest.raises(AEAEnforceError, match="Proposal already set!"): fipa_dialogue.proposal = description assert fipa_dialogue.proposal == description # terms with pytest.raises(ValueError, match="Terms not set!"): assert fipa_dialogue.terms terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) fipa_dialogue.terms = terms with pytest.raises(AEAEnforceError, match="Terms already set!"): fipa_dialogue.terms = terms assert fipa_dialogue.terms == terms def test_fipa_dialogues(self): """Test the FipaDialogues class.""" _, dialogue = self.fipa_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=FipaMessage.Performative.CFP, query=self.query, ) assert dialogue.role == FipaDialogue.Role.BUYER assert dialogue.self_address == self.skill.skill_context.agent_address def test_contract_api_dialogue(self): """Test the ContractApiDialogue class.""" contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_fipa_dialogue with pytest.raises(ValueError, match="associated_fipa_dialogue not set!"): assert contract_api_dialogue.associated_fipa_dialogue fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=FipaDialogue.Role.BUYER, ) contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue with pytest.raises( AEAEnforceError, match="associated_fipa_dialogue already set!" ): contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue assert contract_api_dialogue.associated_fipa_dialogue == fipa_dialogue def test_contract_api_dialogues(self): """Test the ContractApiDialogues class.""" _, dialogue = self.contract_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id="some_ledger_id", contract_id="some_contract_id", callable="some_callable", kwargs=Kwargs({"some_key": "some_value"}), ) assert dialogue.role == ContractApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_cosm_trade_dialogues(self): """Test the CosmTradeDialogues class.""" _, dialogue = self.cosm_trade_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=CosmTradeMessage.Performative.INFORM_PUBLIC_KEY, public_key="some_public_key", ) assert dialogue.role == CosmTradeDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_ledger_api_dialogue(self): """Test the LedgerApiDialogue class.""" ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_signing_dialogue with pytest.raises(ValueError, match="Associated signing dialogue not set!"): assert ledger_api_dialogue.associated_signing_dialogue signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=SigningDialogue.Role.SKILL, ) ledger_api_dialogue.associated_signing_dialogue = signing_dialogue with pytest.raises( AEAEnforceError, match="Associated signing dialogue already set!" ): ledger_api_dialogue.associated_signing_dialogue = signing_dialogue assert ledger_api_dialogue.associated_signing_dialogue == signing_dialogue def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_oef_search_dialogue(self): """Test the OefSearchDialogue class.""" oef_search_dialogue = OefSearchDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # is_seller_search with pytest.raises(ValueError, match="is_seller_search not set!"): assert oef_search_dialogue.is_seller_search oef_search_dialogue.is_seller_search = True with pytest.raises(AEAEnforceError, match="is_seller_search already set!"): oef_search_dialogue.is_seller_search = False assert oef_search_dialogue.is_seller_search is True def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.query, ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_signing_dialogue(self): """Test the SigningDialogue class.""" signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_fipa_dialogue with pytest.raises(ValueError, match="associated_fipa_dialogue not set!"): assert signing_dialogue.associated_fipa_dialogue fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=FipaDialogue.Role.BUYER, ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue with pytest.raises( AEAEnforceError, match="associated_fipa_dialogue already set!" ): signing_dialogue.associated_fipa_dialogue = fipa_dialogue assert signing_dialogue.associated_fipa_dialogue == fipa_dialogue # associated_cosm_trade_dialogue cosm_trade_dialogue = CosmTradeDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=CosmTradeDialogue.Role.AGENT, ) signing_dialogue.associated_cosm_trade_dialogue = cosm_trade_dialogue with pytest.raises( AEAEnforceError, match="associated_cosm_trade_dialogue already set!" ): signing_dialogue.associated_cosm_trade_dialogue = cosm_trade_dialogue assert signing_dialogue.associated_cosm_trade_dialogue == cosm_trade_dialogue def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms=Terms( "some_ledger_id", "some_sender_address", "some_counterparty_address", dict(), dict(), "some_nonce", ), raw_transaction=RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the tac negotiation skill.""" import logging from pathlib import Path from typing import Optional, cast from unittest.mock import PropertyMock, patch import pytest from aea_ledger_ethereum import EthereumApi from aea_ledger_fetchai import FetchAIApi from aea.crypto.ledger_apis import LedgerApis from aea.exceptions import AEAEnforceError from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Location, Query, ) from aea.helpers.transaction.base import ( RawMessage, RawTransaction, SignedTransaction, State, Terms, TransactionDigest, TransactionReceipt, ) from aea.protocols.dialogue.base import DialogueMessage, DialogueStats from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.cosm_trade.message import CosmTradeMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.tac_negotiation.behaviours import ( GoodsRegisterAndSearchBehaviour, ) from packages.fetchai.skills.tac_negotiation.dialogues import ( ContractApiDialogue, ContractApiDialogues, CosmTradeDialogue, CosmTradeDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.tac_negotiation.handlers import ( ContractApiHandler, CosmTradeHandler, FipaNegotiationHandler, LEDGER_API_ADDRESS, LedgerApiHandler, OefSearchHandler, SigningHandler, ) from packages.fetchai.skills.tac_negotiation.helpers import SUPPLY_DATAMODEL_NAME from packages.fetchai.skills.tac_negotiation.strategy import Strategy from packages.fetchai.skills.tac_negotiation.transactions import Transactions from tests.conftest import ROOT_DIR class TestFipaHandler(BaseSkillTestCase): """Test fipa handler of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.fipa_handler = cast( FipaNegotiationHandler, cls._skill.skill_context.handlers.fipa ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.transactions = cast(Transactions, cls._skill.skill_context.transactions) cls.logger = cls._skill.skill_context.logger cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.dialogue_stats = cls.fipa_dialogues.dialogue_stats cls.ledger_id = "some_ledger_id" cls.counterprty_address = COUNTERPARTY_AGENT_ADDRESS cls.amount_by_currency_id = {"1": 50} cls.quantities_by_good_id = {"2": -10} cls.nonce = "234543" cls.contract_id = "some_contract_id" cls.contract_address = "some_contract_address" cls.kwargs = {"some_key": "some_value"} cls.counterparty_signature = "some_counterparty_signature" cls.terms = Terms( cls.ledger_id, cls._skill.skill_context.agent_address, cls.counterprty_address, cls.amount_by_currency_id, cls.quantities_by_good_id, cls.nonce, ) cls.raw_message = RawMessage( ledger_id=cls.ledger_id, body=cls.terms.sender_hash.encode("utf-8") ) cls.cfp_query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( SUPPLY_DATAMODEL_NAME, [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) cls.proposal = Description( { "ledger_id": cls.ledger_id, "price": 100, "currency_id": "1", "fee": 1, "nonce": cls.nonce, } ) cls.list_of_messages_other_initiated = ( DialogueMessage( FipaMessage.Performative.CFP, {"query": cls.cfp_query}, True ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.proposal} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) cls.list_of_messages_self_initiated = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": cls.cfp_query}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.proposal} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) def test_setup(self): """Test the setup method of the fipa handler.""" assert self.fipa_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the fipa handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=FipaMessage, dialogue_reference=incorrect_dialogue_reference, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid fipa message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": incoming_message.encode()}, ) assert has_attributes, error_str def _assert_stat_state( self, dialogue_stats: DialogueStats, changed_agent: Optional[str] = None, changed_end_state: Optional[FipaDialogue.EndState] = None, ) -> None: """ Evaluates the state of dialogue stats. If 'changed_agent' and 'changed_end_state' are None, it asserts that the dialogue stats are 0 for all end_states. If they are not None, it checks that all end_states are 0, except for 'changed_end_state' in dialogues started by 'changed_agent' (i.e. 'self' or 'other'). :param changed_agent: can either by 'self' or 'other'. Dialogues started by this agent has a none-zero end_state. :param changed_end_state: the changed end_state. :return: """ if changed_agent is None and changed_end_state is None: unchanged_dict_1 = dialogue_stats.self_initiated unchanged_dict_2 = dialogue_stats.other_initiated for end_state_numbers in unchanged_dict_1.values(): assert end_state_numbers == 0 for end_state_numbers in unchanged_dict_2.values(): assert end_state_numbers == 0 elif changed_agent is not None and changed_end_state is not None: if changed_agent == "self": changed_dict = dialogue_stats.self_initiated unchanged_dict = dialogue_stats.other_initiated elif changed_agent == "other": changed_dict = dialogue_stats.other_initiated unchanged_dict = dialogue_stats.self_initiated else: raise SyntaxError( f"changed_agent can only be 'self' or 'other'. Found {changed_agent}." ) for end_state_numbers in unchanged_dict.values(): assert end_state_numbers == 0 for end_state, end_state_numbers in changed_dict.items(): if end_state == changed_end_state: assert end_state_numbers == 1 else: assert end_state_numbers == 0 else: raise SyntaxError( "changed_agent and changed_end_state should either both be None, or neither." ) def test_handle_cfp_i(self): """Test the _on_cfp method of the fipa handler where proposal_for_query is None.""" # setup mocked_proposal = None incoming_message = self.build_incoming_message( message_type=FipaMessage, performative=FipaMessage.Performative.CFP, query=self.cfp_query, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object( self.strategy, "get_proposal_for_query", return_value=mocked_proposal, ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.DECLINE, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, ) assert has_attributes, error_str self._assert_stat_state( self.dialogue_stats, "other", FipaDialogue.EndState.DECLINED_CFP ) def test_handle_cfp_ii(self): """Test the _on_cfp method of the fipa handler where proposal_for_query is NOT None.""" # setup incoming_message = self.build_incoming_message( message_type=FipaMessage, performative=FipaMessage.Performative.CFP, query=self.cfp_query, ) # operation with patch.object( self.strategy, "get_proposal_for_query", return_value=self.proposal ): with patch.object(self.strategy, "terms_from_proposal") as mock_terms: with patch.object( self.transactions, "add_pending_proposal" ) as mock_pending: with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.PROPOSE, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, proposal=self.proposal, ) assert has_attributes, error_str mock_terms.assert_called_once() mock_pending.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"sending {message.performative} to {message.to[-5:]} (as {self.fipa_dialogues.get_dialogue(message).role}), message={message}", ) def test_handle_propose_i(self): """Test the _handle_propose method of the fipa handler where the tx IS profitable.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.PROPOSE, proposal=self.proposal, ) # operation with patch.object( self.strategy, "is_profitable_transaction", return_value=True ): with patch.object(self.transactions, "add_locked_tx") as mock_lock: with patch.object( self.transactions, "add_pending_initial_acceptance" ) as mock_pending: with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.ACCEPT, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, ) assert has_attributes, error_str mock_lock.assert_called_once() mock_pending.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"sending {message.performative} to {message.to[-5:]} (as {self.fipa_dialogues.get_dialogue(message).role}), message={message}", ) def test_handle_propose_ii(self): """Test the _handle_propose method of the fipa handler where the tx is NOT profitable.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.PROPOSE, proposal=self.proposal, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object( self.strategy, "is_profitable_transaction", return_value=False ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.DECLINE, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, ) assert has_attributes, error_str self._assert_stat_state( self.dialogue_stats, "self", FipaDialogue.EndState.DECLINED_PROPOSE ) mock_logger.assert_any_call( logging.INFO, f"sending {message.performative} to {message.to[-5:]} (as {self.fipa_dialogues.get_dialogue(message).role}), message={message}", ) def test_handle_decline_decline_cfp(self): """Test the _handle_decline method of the fipa handler where the end state is decline_cfp.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) self._assert_stat_state( self.dialogue_stats, "self", FipaDialogue.EndState.DECLINED_CFP ) def test_handle_decline_decline_propose(self): """Test the _handle_decline method of the fipa handler where the end state is decline_propose.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_other_initiated[:2], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object(self.transactions, "pop_pending_proposal") as mock_pending: with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) self._assert_stat_state( self.dialogue_stats, "other", FipaDialogue.EndState.DECLINED_PROPOSE ) mock_pending.assert_called_once() def test_handle_decline_decline_accept(self): """Test the _handle_decline method of the fipa handler where the end state is decline_accept.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:3], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object( self.transactions, "pop_pending_initial_acceptance" ) as mock_pending: with patch.object(self.transactions, "pop_locked_tx") as mock_locked: with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) self._assert_stat_state( self.dialogue_stats, "self", FipaDialogue.EndState.DECLINED_ACCEPT ) mock_pending.assert_called_once() mock_locked.assert_called_once() def test_handle_accept_i(self): """Test the _on_accept method of the fipa handler where the tx IS profitable, is_contract_tx is True, ledger is Ethereum.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = EthereumApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_other_initiated[:2], is_agent_to_agent_messages=True, ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.transactions, "pop_pending_proposal") as mock_pending: with patch.object( self.strategy, "is_profitable_transaction", return_value=True ): with patch.object(self.transactions, "add_locked_tx") as mock_lock: with patch.object( type(self.strategy), "contract_id", new_callable=PropertyMock, return_value=self.contract_id, ): with patch.object( type(self.strategy), "contract_address", new_callable=PropertyMock, return_value=self.contract_address, ): with patch.object( self.strategy, "kwargs_from_terms", return_value=self.kwargs, ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_pending.assert_called_once() mock_lock.assert_called_once() message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=EthereumApi.identifier, contract_id=self.contract_id, contract_address=self.contract_address, callable="get_hash_batch", kwargs=ContractApiMessage.Kwargs(self.kwargs), ) assert has_attributes, error_str assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) mock_logger.assert_any_call( logging.INFO, f"requesting batch transaction hash, sending {message.performative} to {self.contract_id}, message={message}", ) def test_handle_accept_ii(self): """Test the _on_accept method of the fipa handler where the tx IS profitable and strategy's is_contract_tx is False.""" # setup self.strategy._is_contract_tx = False fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_other_initiated[:2], ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object( self.transactions, "pop_pending_proposal", return_value=self.terms ) as mock_pending: with patch.object( self.strategy, "is_profitable_transaction", return_value=True ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_decision_making_queue(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_pending.assert_called_once() message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_MESSAGE, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=self.terms, raw_message=self.raw_message, ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) mock_logger.assert_any_call( logging.INFO, f"requesting signature, sending {message.performative} to decision_maker, message={message}", ) def test_handle_accept_iii(self): """Test the _on_accept method of the fipa handler where the tx IS profitable, is_contract_tx is True, ledger is FetchAi.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_other_initiated[:2], is_agent_to_agent_messages=True, ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.transactions, "pop_pending_proposal") as mock_pending: with patch.object( self.strategy, "is_profitable_transaction", return_value=True ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_pending.assert_called_once() message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, info={ "public_key": self.skill.skill_context.public_keys[ self.strategy.ledger_id ] }, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending {message.performative.value} to {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={message}.", ) def test_handle_accept_iv(self): """Test the _on_accept method of the fipa handler where the tx IS profitable, is_contract_tx is True, ledger is FetchAi, public_key is None.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_other_initiated[:2], is_agent_to_agent_messages=True, ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.transactions, "pop_pending_proposal") as mock_pending: with patch.object( self.strategy, "is_profitable_transaction", return_value=True ): with patch.object( type(self.strategy), "contract_address", new_callable=PropertyMock, return_value=self.contract_address, ): with patch.object( type(self.skill.skill_context), "public_keys", new_callable=PropertyMock, return_value={"some_ledger": "some_public_key"}, ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_pending.assert_called_once() mock_logger.assert_any_call( logging.INFO, f"Agent has no public key for {self.strategy.ledger_id}.", ) def test_handle_accept_v(self): """Test the _on_accept method of the fipa handler where the tx IS profitable, is_contract_tx is True, ledger is not FetchAi nor Ethereum.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = self.ledger_id fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_other_initiated[:2], is_agent_to_agent_messages=True, ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.transactions, "pop_pending_proposal") as mock_pending: with patch.object( self.strategy, "is_profitable_transaction", return_value=True ): with patch.object(self.logger, "log") as mock_logger: with pytest.raises( AEAEnforceError, match=f"Unidentified ledger id: {self.ledger_id}", ): self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_pending.assert_called_once() def test_handle_accept_vi(self): """Test the _on_accept method of the fipa handler where the tx is NOT profitable.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_other_initiated[:2], ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object( self.transactions, "pop_pending_proposal", return_value=self.terms ) as mock_pending: with patch.object( self.strategy, "is_profitable_transaction", return_value=False ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_pending.assert_called_once() message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.DECLINE, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, ) assert has_attributes, error_str self._assert_stat_state( self.dialogue_stats, "other", FipaDialogue.EndState.DECLINED_ACCEPT ) mock_logger.assert_any_call( logging.INFO, f"sending {message.performative} to {message.to[-5:]} (as {self.fipa_dialogues.get_dialogue(message).role}), message={message}", ) def test_handle_match_accept_i(self): """Test the _handle_match_accept method of the fipa handler where is_contract_tx is True and ledger_id is Fetchai.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:3], ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={}, ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_logger.assert_any_call( logging.INFO, f"{incoming_message.performative} did not contain counterparty public_key!", ) def test_handle_match_accept_ii(self): """Test the _handle_match_accept method of the fipa handler where is_contract_tx is True and ledger_id is Fetchai.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier counterparty_public_key = "counterparty_public_key" fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:3], ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"public_key": counterparty_public_key}, ) # operation with patch.object( type(self.strategy), "contract_address", new_callable=PropertyMock, return_value=self.contract_address, ): with patch.object( type(self.strategy), "contract_id", new_callable=PropertyMock, return_value=self.contract_id, ): with patch.object( self.strategy, "kwargs_from_terms", return_value=self.kwargs ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=FetchAIApi.identifier, contract_id=self.contract_id, contract_address=self.contract_address, callable="get_atomic_swap_batch_transaction", kwargs=ContractApiMessage.Kwargs(self.kwargs), ) assert has_attributes, error_str assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) mock_logger.assert_any_call( logging.INFO, f"requesting batch atomic swap transaction, sending {message.performative} to {self.contract_id}, message={message}", ) def test_handle_match_accept_iii(self): """Test the _handle_match_accept method of the fipa handler where is_contract_tx is True and ledger_id is neither Fetchai nor Ethereum.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = self.ledger_id fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:3], ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={}, ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises( AEAEnforceError, match=f"Unidentified ledger id: {self.ledger_id}" ): self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) def test_handle_match_accept_iv(self): """Test the _handle_match_accept method of the fipa handler where is_contract_tx is True, ledger_id is Ethereum and counterparty signature is None.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = EthereumApi.identifier fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:3], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"signature": None}, ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_logger.assert_any_call( logging.INFO, f"{incoming_message.performative} did not contain counterparty signature!", ) def test_handle_match_accept_v(self): """Test the _handle_match_accept method of the fipa handler where is_contract_tx is True and counterparty signature is NOT None.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = EthereumApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:3], ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"signature": self.counterparty_signature}, ) # operation with patch.object( type(self.strategy), "contract_id", new_callable=PropertyMock, return_value=self.contract_id, ): with patch.object( type(self.strategy), "contract_address", new_callable=PropertyMock, return_value=self.contract_address, ): with patch.object( self.strategy, "kwargs_from_terms", return_value=self.kwargs ): with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) assert fipa_dialogue.counterparty_signature == self.counterparty_signature message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=EthereumApi.identifier, contract_id=self.contract_id, contract_address=self.contract_address, callable="get_atomic_swap_batch_transaction", kwargs=ContractApiMessage.Kwargs(self.kwargs), ) assert has_attributes, error_str assert ( cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) mock_logger.assert_any_call( logging.INFO, f"requesting batch atomic swap transaction, sending {message.performative} to {self.contract_id}, message={message}", ) def test_handle_match_accept_vi(self): """Test the _handle_match_accept method of the fipa handler where is_contract_tx is False and counterparty signature is NOT None.""" # setup self.strategy._is_contract_tx = False fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages_self_initiated[:3], ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"signature": self.counterparty_signature}, ) # operation with patch.object( self.transactions, "pop_pending_initial_acceptance", return_value=self.terms ) as mock_pending: with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after self.assert_quantity_in_decision_making_queue(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from {incoming_message.sender[-5:]} (as {self.fipa_dialogues.get_dialogue(incoming_message).role}), message={incoming_message}", ) mock_pending.assert_called_once() assert fipa_dialogue.counterparty_signature == self.counterparty_signature message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_MESSAGE, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=self.terms, raw_message=self.raw_message, ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) mock_logger.assert_any_call( logging.INFO, f"requesting signature, sending {message.performative} to decision_maker, message={message}", ) def test_teardown(self): """Test the teardown method of the fipa handler.""" assert self.fipa_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestCosmTradeHandler(BaseSkillTestCase): """Test cosm_trade handler of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.cosm_trade_handler = cast( CosmTradeHandler, cls._skill.skill_context.handlers.cosm_trade ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.cosm_trade_dialogues = cast( CosmTradeDialogues, cls._skill.skill_context.cosm_trade_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.dialogue_stats = cls.cosm_trade_dialogues.dialogue_stats cls.ledger_id = "some_ledger_id" cls.counterprty_address = COUNTERPARTY_AGENT_ADDRESS cls.amount_by_currency_id = {"1": 50} cls.quantities_by_good_id = {"2": -10} cls.nonce = "234543" cls.body = {"some_key": "some_value"} cls.fipa_dialogue_id = ("1", "1") cls.terms = Terms( cls.ledger_id, cls._skill.skill_context.agent_address, cls.counterprty_address, cls.amount_by_currency_id, cls.quantities_by_good_id, cls.nonce, ) cls.raw_message = RawMessage( ledger_id=cls.ledger_id, body=cls.terms.sender_hash.encode("utf-8") ) cls.signed_tx = SignedTransaction(cls.ledger_id, cls.body) cls.cfp_query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( SUPPLY_DATAMODEL_NAME, [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) cls.proposal = Description( { "ledger_id": cls.ledger_id, "price": 100, "currency_id": "1", "fee": 1, "nonce": cls.nonce, } ) cls.list_of_fipa_messages = ( DialogueMessage( FipaMessage.Performative.CFP, {"query": cls.cfp_query}, True ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.proposal} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) cls.list_of_cosm_trade_messages = ( DialogueMessage( CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, {"signed_transaction": cls.signed_tx, "fipa_dialogue_id": ("1", "")}, ), ) @staticmethod def _assert_stat_state( dialogue_stats: DialogueStats, changed_agent: Optional[str] = None, changed_end_state: Optional[CosmTradeDialogue.EndState] = None, ) -> None: """ Evaluates the state of dialogue stats. If 'changed_agent' and 'changed_end_state' are None, it asserts that the dialogue stats are 0 for all end_states. If they are not None, it checks that all end_states are 0, except for 'changed_end_state' in dialogues started by 'changed_agent' (i.e. 'self' or 'other'). :param changed_agent: can either by 'self' or 'other'. Dialogues started by this agent has a none-zero end_state. :param changed_end_state: the changed end_state. :return: """ if changed_agent is None and changed_end_state is None: unchanged_dict_1 = dialogue_stats.self_initiated unchanged_dict_2 = dialogue_stats.other_initiated for end_state_numbers in unchanged_dict_1.values(): assert end_state_numbers == 0 for end_state_numbers in unchanged_dict_2.values(): assert end_state_numbers == 0 elif changed_agent is not None and changed_end_state is not None: if changed_agent == "self": changed_dict = dialogue_stats.self_initiated unchanged_dict = dialogue_stats.other_initiated elif changed_agent == "other": changed_dict = dialogue_stats.other_initiated unchanged_dict = dialogue_stats.self_initiated else: raise SyntaxError( f"changed_agent can only be 'self' or 'other'. Found {changed_agent}." ) for end_state_numbers in unchanged_dict.values(): assert end_state_numbers == 0 for end_state, end_state_numbers in changed_dict.items(): if end_state == changed_end_state: assert end_state_numbers == 1 else: assert end_state_numbers == 0 else: raise SyntaxError( "changed_agent and changed_end_state should either both be None, or neither." ) def test_setup(self): """Test the setup method of the cosm_trade handler.""" assert self.cosm_trade_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the cosm_trade handler.""" # setup self.strategy._is_contract_tx = True incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=CosmTradeMessage, dialogue_reference=incorrect_dialogue_reference, performative=CosmTradeMessage.Performative.INFORM_PUBLIC_KEY, public_key="some_public_key", ) # operation with patch.object(self.logger, "log") as mock_logger: self.cosm_trade_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid cosm_trade message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"cosm_trade_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_signed_tx_i(self): """Test the _on_accept method of the cosm_trade handler.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], is_agent_to_agent_messages=True, ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message( message_type=CosmTradeMessage, performative=CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, signed_transaction=self.signed_tx, fipa_dialogue_id=fipa_dialogue.dialogue_label.dialogue_reference, ) raw_tx = RawTransaction( ledger_id=self.signed_tx.ledger_id, body=self.signed_tx.body, ) # operation with patch.object(self.logger, "log") as mock_logger: self.cosm_trade_handler.handle(incoming_message) # after self.assert_quantity_in_decision_making_queue(1) mock_logger.assert_any_call( logging.INFO, f"received inform_signed_tx with signed_tx={self.signed_tx}", ) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_TRANSACTION, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=self.terms, raw_transaction=raw_tx, ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) assert cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_cosm_trade_dialogue == self.cosm_trade_dialogues.get_dialogue( incoming_message ) mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) def test_handle_signed_tx_ii(self): """Test the _on_accept method of the cosm_trade handler where fipa_dialogue_id IS None.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], is_agent_to_agent_messages=True, ), ) fipa_dialogue._terms = self.terms incoming_message = self.build_incoming_message( message_type=CosmTradeMessage, performative=CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, signed_transaction=self.signed_tx, fipa_dialogue_id=None, ) # operation with patch.object(self.logger, "log") as mock_logger: self.cosm_trade_handler.handle(incoming_message) # after self.assert_quantity_in_decision_making_queue(0) mock_logger.assert_any_call( logging.INFO, f"received inform_signed_tx with signed_tx={self.signed_tx}", ) mock_logger.assert_any_call( logging.INFO, "inform_signed_tx must contain fipa dialogue reference.", ) def test_handle_error(self): """Test the _handle_decline method of the cosm_trade handler where the end state is Failed.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier cosm_trade_dialogue = self.prepare_skill_dialogue( dialogues=self.cosm_trade_dialogues, messages=self.list_of_cosm_trade_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=cosm_trade_dialogue, performative=CosmTradeMessage.Performative.ERROR, code=1, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object(self.logger, "log") as mock_logger: self.cosm_trade_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received cosm_trade_api error message={incoming_message} in dialogue={cosm_trade_dialogue}.", ) self._assert_stat_state( self.dialogue_stats, "self", CosmTradeDialogue.EndState.FAILED ) def test_handle_end(self): """Test the _handle_decline method of the cosm_trade handler where the end state is SUCCESS.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier cosm_trade_dialogue = self.prepare_skill_dialogue( dialogues=self.cosm_trade_dialogues, messages=self.list_of_cosm_trade_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=cosm_trade_dialogue, performative=CosmTradeMessage.Performative.END, ) # before self._assert_stat_state(self.dialogue_stats) # operation with patch.object(self.logger, "log") as mock_logger: self.cosm_trade_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received cosm_trade_api end message={incoming_message} in dialogue={cosm_trade_dialogue}.", ) self._assert_stat_state( self.dialogue_stats, "self", CosmTradeDialogue.EndState.SUCCESSFUL ) def test_teardown(self): """Test the teardown method of the cosm_trade handler.""" assert self.cosm_trade_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(BaseSkillTestCase): """Test signing handler of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls.signing_handler.context.logger cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.cosm_trade_dialogues = cast( CosmTradeDialogues, cls._skill.skill_context.cosm_trade_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.ledger_id = "some_ledger_id" cls.nonce = "some_nonce" cls.body = {"some_key": "some_value"} cls.body_bytes = b"some_body" cls.body_str = "some_body" cls.counterparty_signature = "some_counterparty_signature" cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_signing_msg_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_MESSAGE, { "terms": cls.terms, "raw_message": SigningMessage.RawMessage( cls.ledger_id, cls.body_bytes ), }, ), ) cls.list_of_signing_tx_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": cls.terms, "raw_transaction": SigningMessage.RawTransaction( cls.ledger_id, cls.body ), }, ), ) cls.cfp_query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( SUPPLY_DATAMODEL_NAME, [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) cls.proposal = Description( { "ledger_id": cls.ledger_id, "price": 100, "currency_id": "1", "fee": 1, "nonce": cls.nonce, } ) cls.list_of_other_initiated_fipa_messages = ( DialogueMessage( FipaMessage.Performative.CFP, {"query": cls.cfp_query}, True ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.proposal} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) cls.list_of_self_initiated_fipa_messages_ethereum = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": cls.cfp_query}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.proposal} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) cls.list_of_self_initiated_fipa_messages_fetchai = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": cls.cfp_query}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.proposal} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"public_key": "some_public_key"}}, ), ) cls.signed_tx = SignedTransaction(cls.ledger_id, cls.body) cls.list_of_cosm_trade_messages = ( DialogueMessage( CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, {"signed_transaction": cls.signed_tx, "fipa_dialogue_id": ("1", "")}, ), ) def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_message_i(self): """Test the _handle_signed_message method of the signing handler where last fipa message is ACCEPT.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_other_initiated_fipa_messages[:3], is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_msg_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_MESSAGE, signed_message=SigningMessage.SignedMessage( self.ledger_id, self.body_str ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, to=fipa_dialogue.dialogue_label.dialogue_opponent_addr, # (line below) match-accept is already added to fipa_dialogue, hence "-1" target=fipa_dialogue.last_incoming_message.message_id, sender=self.skill.skill_context.agent_address, info={"signature": incoming_message.signed_message.body}, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending {message.performative.value} to {message.to[-5:]} (as {fipa_dialogue.role}), message={message}.", ) def test_handle_signed_message_ii(self): """Test the _handle_signed_message method of the signing handler where last fipa message is MATCH_ACCEPT.""" # setup mocked_tx = { "terms": self.terms, "sender_signature": self.body_str, "counterparty_signature": self.counterparty_signature, } fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_ethereum[:4], is_agent_to_agent_messages=True, ), ) fipa_dialogue.counterparty_signature = self.counterparty_signature fipa_dialogue.terms = self.terms signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_msg_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_MESSAGE, signed_message=SigningMessage.SignedMessage( self.ledger_id, self.body_str ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) assert ( self.skill.skill_context.shared_state["transactions"][ fipa_dialogue.terms.sender_hash ] == mocked_tx ) mock_logger.assert_any_call( logging.INFO, f"sending transaction to controller, tx={mocked_tx}." ) def test_handle_signed_message_iii(self): """Test the _handle_signed_message method of the signing handler where last fipa message is neither ACCEPT nor MATCH_ACCEPT.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_ethereum[:3], is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_msg_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_MESSAGE, signed_message=SigningMessage.SignedMessage( self.ledger_id, self.body_str ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises( AEAEnforceError, match="last message should be of performative accept or match accept.", ): self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) def test_handle_signed_message_iv(self): """Test the _handle_signed_message method of the signing handler where last fipa message is None.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_ethereum[:1], is_agent_to_agent_messages=True, ), ) fipa_dialogue._incoming_messages = [] fipa_dialogue._outgoing_messages = [] signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_msg_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_MESSAGE, signed_message=SigningMessage.SignedMessage( self.ledger_id, self.body_str ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises(AEAEnforceError, match="last message not recovered."): self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) def test_handle_signed_transaction_v(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is False.""" # setup self.strategy._is_contract_tx = False signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) mock_logger.assert_any_call( logging.WARNING, "signed transaction handler only for contract case." ) def test_handle_signed_transaction_vi(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is True, associated cosm_trade dialogue is NOT None.""" # setup self.strategy._is_contract_tx = True cosm_trade_dialogue = cast( CosmTradeDialogue, self.prepare_skill_dialogue( dialogues=self.cosm_trade_dialogues, messages=self.list_of_cosm_trade_messages, is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_cosm_trade_dialogue = cosm_trade_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), signed_transaction=incoming_message.signed_transaction, ) assert has_attributes, error_str assert ( cast( LedgerApiDialogue, self.ledger_api_dialogues.get_dialogue(message) ).associated_signing_dialogue == signing_dialogue ) mock_logger.assert_any_call( logging.INFO, f"sending {message.performative} to ledger {self.strategy.ledger_id}, message={message}", ) def test_handle_signed_transaction_i(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is True and last fipa message is ACCEPT.""" # setup self.strategy._is_contract_tx = True fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_other_initiated_fipa_messages[:3], is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, to=fipa_dialogue.dialogue_label.dialogue_opponent_addr, # (line below) match-accept is already added to fipa_dialogue, hence "-1" sender=self.skill.skill_context.agent_address, info={"tx_signature": incoming_message.signed_transaction}, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending {message.performative.value} to {message.to[-5:]} (as {fipa_dialogue.role}), message={message}.", ) def test_handle_signed_transaction_ii(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is True and last fipa message is MATCH_ACCEPT and ledger is Ethereum.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = EthereumApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_ethereum[:4], is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), signed_transaction=incoming_message.signed_transaction, ) assert has_attributes, error_str assert ( cast( LedgerApiDialogue, self.ledger_api_dialogues.get_dialogue(message) ).associated_signing_dialogue == signing_dialogue ) mock_logger.assert_any_call( logging.INFO, f"sending {message.performative} to ledger {self.strategy.ledger_id}, message={message}", ) def test_handle_signed_transaction_vii(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is True and last fipa message is MATCH_ACCEPT and ledger is Fetchai.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = FetchAIApi.identifier fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_fetchai[:4], is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=CosmTradeMessage, performative=CosmTradeMessage.Performative.INFORM_SIGNED_TRANSACTION, to=fipa_dialogue.dialogue_label.dialogue_opponent_addr, sender=self.skill.skill_context.agent_address, signed_transaction=incoming_message.signed_transaction, fipa_dialogue_id=fipa_dialogue.dialogue_label.dialogue_reference, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending {message.performative.value} to {message.to[-5:]}, message={message}.", ) def test_handle_signed_transaction_viii(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is True and last fipa message is MATCH_ACCEPT and ledger is neither Ethereum nor Fetchai.""" # setup self.strategy._is_contract_tx = True self.strategy._ledger_id = self.ledger_id fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_fetchai[:4], is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises( AEAEnforceError, match=f"Unidentified ledger id: {self.ledger_id}", ): self.signing_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) def test_handle_signed_transaction_iii(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is True and last fipa message is neither ACCEPT nor MATCH_ACCEPT.""" # setup self.strategy._is_contract_tx = True fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_ethereum[:3], is_agent_to_agent_messages=True, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises( AEAEnforceError, match="last message should be of performative accept or match accept.", ): self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) def test_handle_signed_transaction_iv(self): """Test the _handle_signed_transaction method of the signing handler where is_contract_tx is True and last incoming fipa message is None.""" # setup self.strategy._is_contract_tx = True fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_self_initiated_fipa_messages_ethereum[:3], is_agent_to_agent_messages=True, ), ) fipa_dialogue._incoming_messages = [] fipa_dialogue._outgoing_messages = [] signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) signing_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=self.signed_tx, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises(AEAEnforceError, match="last message not recovered."): self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_tx_messages[:1], counterparty=signing_counterparty, ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.terms, raw_transaction=SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received {incoming_message.performative} from decision_maker, message={incoming_message}", ) mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.logger = cls.ledger_api_handler.context.logger cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.ledger_id = "some_ledger_id" cls.contract_id = "some_contract_id" cls.callable = "some_callable" cls.kwargs = Kwargs({"some_key": "some_value"}) cls.body = {"some_key": "some_value"} cls.body_str = "some_body" cls.contract_address = "some_contract_address" cls.raw_transaction = RawTransaction(cls.ledger_id, cls.body) cls.signed_transaction = SignedTransaction(cls.ledger_id, cls.body) cls.transaction_digest = TransactionDigest(cls.ledger_id, cls.body_str) cls.receipt = {"contractAddress": cls.contract_address} cls.transaction_receipt = TransactionReceipt( cls.ledger_id, cls.receipt, {"transaction_key": "transaction_value"} ) cls.address = "some_address" cls.terms = Terms( cls.ledger_id, cls._skill.skill_context.agent_address, "counterparty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {"terms": cls.terms} ), DialogueMessage( LedgerApiMessage.Performative.RAW_TRANSACTION, {"raw_transaction": cls.raw_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {"signed_transaction": cls.signed_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_DIGEST, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, {"transaction_digest": cls.transaction_digest}, ), ) def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.BALANCE, ledger_id="some_ledger_id", balance=10, ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_balance(self): """Test the _handle_balance method of the ledger_api handler.""" # setup balance = 10 ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": self.ledger_id, "address": self.address}, ), ), counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=self.ledger_id, balance=balance, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {self.ledger_id} ledger={incoming_message.balance}.", ) def test_handle_transaction_digest(self): """Test the _handle_transaction_digest method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:3], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=self.transaction_digest, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully submitted. Transaction digest={incoming_message.transaction_digest}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, target=incoming_message.message_id, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), transaction_digest=incoming_message.transaction_digest, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "requesting transaction receipt.") def test_handle_transaction_receipt_failed(self): """Test the _handle_transaction_receipt method of the ledger_api handler where the transaction is NOT settled.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object(LedgerApis, "is_transaction_settled", return_value=False): with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.ERROR, f"transaction failed. Transaction receipt={incoming_message.transaction_receipt}", ) def test_handle_transaction_receipt_succeeds(self): """Test the _handle_transaction_receipt method of the ledger_api handler where the transaction is NOT settled.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object(LedgerApis, "is_transaction_settled", return_value=True): with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully settled. Transaction receipt={incoming_message.transaction_receipt}", ) def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id="some_ledger_id", address=self.address, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(BaseSkillTestCase): """Test oef search handler of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef ) cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.service_registration_behaviour = cast( GoodsRegisterAndSearchBehaviour, cls._skill.skill_context.behaviours.tac_negotiation, ) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.controller_address = "some_controller_address" cls.self_address = cls._skill.skill_context.agent_address cls.found_agent_address_1 = "some_agent_address_1" cls.found_agent_address_2 = "some_agent_address_2" cls.found_agent_address_3 = "some_agent_address_3" cls.found_agents = [ cls.self_address, cls.found_agent_address_1, cls.found_agent_address_2, cls.found_agent_address_3, ] cls.found_agents_less_self = [ cls.found_agent_address_1, cls.found_agent_address_2, cls.found_agent_address_3, ] cls.cfp_query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( SUPPLY_DATAMODEL_NAME, [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) cls.list_of_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} ), ) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) cls.unregister_description = Description( {"key": "seller_service"}, data_model=DataModel("remove", [Attribute("key", str, True)]), ) cls.list_of_messages_unregister = ( DialogueMessage( OefSearchMessage.Performative.UNREGISTER_SERVICE, {"service_description": cls.unregister_description}, is_incoming=False, ), ) def test_setup(self): """Test the setup method of the oef handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.service_registration_behaviour.is_registered is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_service", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() assert self.service_registration_behaviour.is_registered is False def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH set_service_key data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_service[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.service_registration_behaviour.is_registered is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() assert self.service_registration_behaviour.is_registered is False def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.service_registration_behaviour.is_registered is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() assert self.service_registration_behaviour.is_registered is False def test_handle_success_iv(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.service_registration_behaviour.is_registered is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, and its service are successfully registered on the SOEF.", ) assert self.service_registration_behaviour.is_registered is True def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef successtargets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # before assert self.service_registration_behaviour.is_registered is False # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) assert self.service_registration_behaviour.is_registered is False def test_on_oef_error_i(self): """Test the _handle_error method of the oef_search handler where the oef error targets register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.WARNING, f"received OEF Search error: dialogue_reference={oef_dialogue.dialogue_label.dialogue_reference}, oef_error_operation={incoming_message.oef_error_operation}", ) assert ( self.service_registration_behaviour.failed_registration_msg == oef_dialogue.get_message_by_id(incoming_message.target) ) def test_on_oef_error_ii(self): """Test the _handle_error method of the oef_search handler where the oef error does NOT target register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_unregister[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.WARNING, f"received OEF Search error: dialogue_reference={oef_dialogue.dialogue_label.dialogue_reference}, oef_error_operation={incoming_message.oef_error_operation}", ) assert self.service_registration_behaviour.failed_registration_msg is None def test_on_search_result_i(self): """Test the _on_search_result method of the oef handler.""" # setup oef_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ), ) oef_dialogue._is_seller_search = True search_for = "sellers" incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), agents=tuple(self.found_agents), ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object( self.strategy, "get_own_services_query", return_value=self.cfp_query ) as mock_own: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(len(self.found_agents_less_self)) # _handle_search mock_logger.assert_any_call( logging.INFO, f"found potential {search_for} agents={list(map(lambda x: x[-5:], self.found_agents_less_self))} on search_id={incoming_message.dialogue_reference[0]}.", ) mock_own.assert_called_once() for agent in self.found_agents_less_self: mock_logger.assert_any_call( logging.INFO, f"sending CFP to agent={agent[-5:]}", ) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.CFP, to=agent, sender=self.self_address, query=self.cfp_query, ) assert has_attributes, error_str def test_on_search_result_ii(self): """Test the _on_search_result method of the oef handler where number of agents found is 0.""" # setup oef_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ), ) oef_dialogue._is_seller_search = False search_for = "buyers" incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), agents=tuple(), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) # _handle_search mock_logger.assert_any_call( logging.INFO, f"found no {search_for} agents on search_id={incoming_message.dialogue_reference[0]}, continue searching.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef handler.""" # setup invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=("1", ""), performative=invalid_performative, to=str(self.skill.skill_context.skill_id), service_description="some_service_description", ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestContractApiHandler(BaseSkillTestCase): """Test contract_api handler of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.contract_api_handler = cast( ContractApiHandler, cls._skill.skill_context.handlers.contract_api ) cls.logger = cls.contract_api_handler.context.logger cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_id = "some_ledger_id" cls.contract_id = "some_contract_id" cls.contract_address = "some_contract_address" cls.callable = "some_callable" cls.kwargs = Kwargs({"some_key": "some_value"}) cls.body = {"some_key": "some_value"} cls.body_bytes = b"some_body" cls.nonce = "some_nonce" cls.counterprty_address = COUNTERPARTY_AGENT_ADDRESS cls.amount_by_currency_id = {"1": 50} cls.quantities_by_good_id = {"2": -10} cls.terms = Terms( cls.ledger_id, cls._skill.skill_context.agent_address, cls.counterprty_address, cls.amount_by_currency_id, cls.quantities_by_good_id, cls.nonce, ) cls.list_of_contract_api_messages_get_deploy_tx = ( DialogueMessage( ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, { "ledger_id": cls.ledger_id, "contract_id": cls.contract_id, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.list_of_contract_api_messages_raw_msg = ( DialogueMessage( ContractApiMessage.Performative.GET_RAW_MESSAGE, { "ledger_id": cls.ledger_id, "contract_id": cls.contract_id, "contract_address": cls.contract_address, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.cfp_query = Query( [Constraint("some_attribute", ConstraintType("==", "some_service"))], DataModel( SUPPLY_DATAMODEL_NAME, [ Attribute( "some_attribute", str, False, "Some attribute descriptions." ) ], ), ) cls.list_of_fipa_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": cls.cfp_query}), ) def test_setup(self): """Test the setup method of the contract_api handler.""" assert self.contract_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=ContractApiMessage.Performative.STATE, state=State("some_ledger_id", {"some_key": "some_value"}), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid contract_api message={incoming_message}, unidentified dialogue.", ) def test_handle_raw_message(self): """Test the _handle_raw_message method of the signing handler.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:1], ), ) fipa_dialogue.terms = self.terms contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages_raw_msg[:1], ), ) contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.RAW_MESSAGE, raw_message=ContractApiMessage.RawMessage( self.ledger_id, self.body_bytes ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw message={incoming_message}" ) self.assert_quantity_in_decision_making_queue(1) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_MESSAGE, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=self.terms, raw_message=RawMessage( self.ledger_id, self.body_bytes, is_deprecated_mode=True, ), ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) mock_logger.assert_any_call( logging.INFO, "proposing the message to the decision maker. Waiting for confirmation ...", ) def test_handle_raw_transaction(self): """Test the _handle_signed_transaction method of the signing handler.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:1], ), ) fipa_dialogue.terms = self.terms contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages_get_deploy_tx[:1], ), ) contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=ContractApiMessage.RawTransaction( self.ledger_id, self.body ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw transaction={incoming_message}" ) self.assert_quantity_in_decision_making_queue(1) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_TRANSACTION, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=self.terms, raw_transaction=incoming_message.raw_transaction, ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_fipa_dialogue == fipa_dialogue ) mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup contract_api_dialogue = self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages_get_deploy_tx[:1], ) incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received contract_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION incoming_message = self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=self.ledger_id, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle contract_api message of performative={invalid_performative} in dialogue={self.contract_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the contract_api handler.""" assert self.contract_api_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/test_helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the helpers module of the tac negotiation.""" from pathlib import Path from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, ) from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.tac_negotiation.helpers import ( DEMAND_DATAMODEL_NAME, SUPPLY_DATAMODEL_NAME, _build_goods_datamodel, build_goods_description, build_goods_query, ) from tests.conftest import ROOT_DIR class TestHelpers(BaseSkillTestCase): """Test Helper module methods of tac control.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" super().setup() def test_build_goods_datamodel_supply(self): """Test the _build_goods_datamodel of Helpers module for a supply.""" good_ids = ["1", "2"] is_supply = True attributes = [ Attribute("1", int, True, "A good on offer."), Attribute("2", int, True, "A good on offer."), Attribute("ledger_id", str, True, "The ledger for transacting."), Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods.", ), Attribute("price", int, False, "The price of the goods in the currency."), Attribute( "fee", int, False, "The transaction fee payable by the buyer in the currency.", ), Attribute( "nonce", str, False, "The nonce to distinguish identical descriptions." ), ] expected_data_model = DataModel(SUPPLY_DATAMODEL_NAME, attributes) actual_data_model = _build_goods_datamodel(good_ids, is_supply) assert actual_data_model == expected_data_model def test_build_goods_datamodel_demand(self): """Test the _build_goods_datamodel of Helpers module for a demand.""" good_ids = ["1", "2"] is_supply = False attributes = [ Attribute("1", int, True, "A good on offer."), Attribute("2", int, True, "A good on offer."), Attribute("ledger_id", str, True, "The ledger for transacting."), Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods.", ), Attribute("price", int, False, "The price of the goods in the currency."), Attribute( "fee", int, False, "The transaction fee payable by the buyer in the currency.", ), Attribute( "nonce", str, False, "The nonce to distinguish identical descriptions." ), ] expected_data_model = DataModel(DEMAND_DATAMODEL_NAME, attributes) actual_data_model = _build_goods_datamodel(good_ids, is_supply) assert actual_data_model == expected_data_model def test_build_goods_description_supply(self): """Test the build_goods_description of Helpers module for supply.""" quantities_by_good_id = {"2": 5, "3": 10} currency_id = "1" ledger_id = "some_ledger_id" is_supply = True attributes = [ Attribute("2", int, True, "A good on offer."), Attribute("3", int, True, "A good on offer."), Attribute("ledger_id", str, True, "The ledger for transacting."), Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods.", ), Attribute("price", int, False, "The price of the goods in the currency."), Attribute( "fee", int, False, "The transaction fee payable by the buyer in the currency.", ), Attribute( "nonce", str, False, "The nonce to distinguish identical descriptions." ), ] expected_data_model = DataModel(SUPPLY_DATAMODEL_NAME, attributes) expected_values = {"currency_id": currency_id, "ledger_id": ledger_id} expected_values.update(quantities_by_good_id) expected_description = Description(expected_values, expected_data_model) actual_description = build_goods_description( quantities_by_good_id, currency_id, ledger_id, is_supply ) assert actual_description == expected_description def test_build_goods_description_demand(self): """Test the build_goods_description of Helpers module for demand (same as above).""" quantities_by_good_id = {"2": 5, "3": 10} currency_id = "1" ledger_id = "some_ledger_id" is_supply = False attributes = [ Attribute("2", int, True, "A good on offer."), Attribute("3", int, True, "A good on offer."), Attribute("ledger_id", str, True, "The ledger for transacting."), Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods.", ), Attribute("price", int, False, "The price of the goods in the currency."), Attribute( "fee", int, False, "The transaction fee payable by the buyer in the currency.", ), Attribute( "nonce", str, False, "The nonce to distinguish identical descriptions." ), ] expected_data_model = DataModel(DEMAND_DATAMODEL_NAME, attributes) expected_values = {"currency_id": currency_id, "ledger_id": ledger_id} expected_values.update(quantities_by_good_id) expected_description = Description(expected_values, expected_data_model) actual_description = build_goods_description( quantities_by_good_id, currency_id, ledger_id, is_supply ) assert actual_description == expected_description def test_build_goods_query(self): """Test the build_goods_query of Helpers module.""" good_ids = ["2", "3"] currency_id = "1" ledger_id = "some_ledger_id" is_searching_for_sellers = True attributes = [ Attribute("2", int, True, "A good on offer."), Attribute("3", int, True, "A good on offer."), Attribute("ledger_id", str, True, "The ledger for transacting."), Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods.", ), Attribute("price", int, False, "The price of the goods in the currency."), Attribute( "fee", int, False, "The transaction fee payable by the buyer in the currency.", ), Attribute( "nonce", str, False, "The nonce to distinguish identical descriptions." ), ] expected_data_model = DataModel(SUPPLY_DATAMODEL_NAME, attributes) expected_constraints = [ Constraint("2", ConstraintType(">=", 1)), Constraint("3", ConstraintType(">=", 1)), Constraint("ledger_id", ConstraintType("==", ledger_id)), Constraint("currency_id", ConstraintType("==", currency_id)), ] actual_query = build_goods_query( good_ids, currency_id, ledger_id, is_searching_for_sellers ) constraints = [ (c.constraint_type.type, c.constraint_type.value) for c in actual_query.constraints[0].constraints ] for constraint in expected_constraints: assert ( constraint.constraint_type.type, constraint.constraint_type.value, ) in constraints assert actual_query.model == expected_data_model def test_build_goods_query_1_good(self): """Test the build_goods_query of Helpers module where there is 1 good.""" good_ids = ["2"] currency_id = "1" ledger_id = "some_ledger_id" is_searching_for_sellers = True attributes = [ Attribute("2", int, True, "A good on offer."), Attribute("ledger_id", str, True, "The ledger for transacting."), Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods.", ), Attribute("price", int, False, "The price of the goods in the currency."), Attribute( "fee", int, False, "The transaction fee payable by the buyer in the currency.", ), Attribute( "nonce", str, False, "The nonce to distinguish identical descriptions." ), ] expected_data_model = DataModel(SUPPLY_DATAMODEL_NAME, attributes) expected_constraints = [ Constraint("2", ConstraintType(">=", 1)), Constraint("ledger_id", ConstraintType("==", ledger_id)), Constraint("currency_id", ConstraintType("==", currency_id)), ] actual_query = build_goods_query( good_ids, currency_id, ledger_id, is_searching_for_sellers ) for constraint in expected_constraints: assert constraint in actual_query.constraints assert actual_query.model == expected_data_model ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/test_logical.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for the logical behaviour of the tac negotiation skill.""" import copy from pathlib import Path from unittest.mock import patch from aea.decision_maker.gop import GoalPursuitReadiness, OwnershipState, Preferences from aea.helpers.preference_representations.base import ( linear_utility, logarithmic_utility, ) from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.tac_control.helpers import ( determine_scaling_factor, generate_utility_params, ) from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue from packages.fetchai.skills.tac_negotiation.strategy import Strategy from tests.conftest import ROOT_DIR class TestLogical(BaseSkillTestCase): """Logical Tests for tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" tac_dm_context_kwargs = { "goal_pursuit_readiness": GoalPursuitReadiness(), "ownership_state": OwnershipState(), "preferences": Preferences(), } super().setup(dm_context_kwargs=tac_dm_context_kwargs) cls.register_as = "both" cls.search_for = "both" cls.is_contract_tx = False cls.ledger_id = "some_ledger_id" cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.search_radius = 5.0 cls.service_key = "tac_service" cls.strategy = Strategy( register_as=cls.register_as, search_for=cls.search_for, is_contract_tx=cls.is_contract_tx, ledger_id=cls.ledger_id, location=cls.location, service_key=cls.service_key, search_radius=cls.search_radius, name="strategy", skill_context=cls._skill.skill_context, ) cls.sender = "some_sender_address" cls.counterparty = "some_counterparty_address" cls.mocked_currency_id = "12" cls.mocked_currency_amount = 2000000 cls.mocked_amount_by_currency_id = { cls.mocked_currency_id: cls.mocked_currency_amount } cls.mocked_good_ids = ["13", "14", "15", "16", "17", "18", "19", "20", "21"] cls.mocked_good_quantities = [5, 7, 4, 3, 5, 4, 3, 5, 6] cls.mocked_quantities_by_good_id = dict( zip(cls.mocked_good_ids, cls.mocked_good_quantities) ) cls.mocked_ownership_state = ( cls._skill.skill_context.decision_maker_handler_context.ownership_state ) cls.mocked_ownership_state.set( cls.mocked_amount_by_currency_id, cls.mocked_quantities_by_good_id ) cls.exchange_params_by_currency_id = {cls.mocked_currency_id: 1.0} cls.utility_params_by_good_id = generate_utility_params( [cls._skill.skill_context.agent_address], cls.mocked_good_ids, determine_scaling_factor(cls.mocked_currency_amount), )[cls._skill.skill_context.agent_address] cls.mocked_preferences = ( cls._skill.skill_context.decision_maker_handler_context.preferences ) cls.mocked_preferences.set( exchange_params_by_currency_id=cls.exchange_params_by_currency_id, utility_params_by_good_id=cls.utility_params_by_good_id, ) @staticmethod def _calculate_score(preferences, ownership_state): """Calculate the score given a preferences and an ownership_state object.""" goods_score = logarithmic_utility( preferences.utility_params_by_good_id, ownership_state.quantities_by_good_id, ) money_score = linear_utility( preferences.exchange_params_by_currency_id, ownership_state.amount_by_currency_id, ) return goods_score + money_score def test_generated_proposals_increase_score_seller(self): """Test whether the proposals generated by _generate_candidate_proposals method of the Strategy class actually increases agent's score where role is seller.""" # setup is_searching_for_sellers = True # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_proposals = self.strategy._generate_candidate_proposals( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=is_searching_for_sellers) current_score = self._calculate_score( self.mocked_preferences, self.mocked_ownership_state ) for proposal in actual_proposals: # applying proposal on a new ownership_state terms = self.strategy.terms_from_proposal( proposal, self.sender, self.counterparty, FipaDialogue.Role.SELLER ) new_ownership_state = copy.copy(self.mocked_ownership_state) new_ownership_state.apply_delta( terms.amount_by_currency_id, terms.quantities_by_good_id ) # new score new_score = self._calculate_score( self.mocked_preferences, new_ownership_state ) assert new_score >= current_score def test_generated_proposals_increase_score_buyer(self): """Test whether the proposals generated by _generate_candidate_proposals method of the Strategy class actually increases agent's score where role is buyer.""" # setup is_searching_for_sellers = False # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_proposals = self.strategy._generate_candidate_proposals( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=is_searching_for_sellers) current_score = self._calculate_score( self.mocked_preferences, self.mocked_ownership_state ) for proposal in actual_proposals: # applying proposal on a new ownership_state terms = self.strategy.terms_from_proposal( proposal, self.sender, self.counterparty, FipaDialogue.Role.BUYER ) new_ownership_state = copy.copy(self.mocked_ownership_state) new_ownership_state.apply_delta( terms.amount_by_currency_id, terms.quantities_by_good_id ) # new score new_score = self._calculate_score( self.mocked_preferences, new_ownership_state ) assert new_score >= current_score ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the tac negotiation skill.""" import re from pathlib import Path from unittest.mock import patch import pytest from aea.decision_maker.gop import GoalPursuitReadiness, OwnershipState, Preferences from aea.exceptions import AEAEnforceError from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Location, Query, ) from aea.helpers.transaction.base import Terms from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue from packages.fetchai.skills.tac_negotiation.helpers import ( build_goods_description, build_goods_query, ) from packages.fetchai.skills.tac_negotiation.strategy import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, CONTRACT_ID, DEFAULT_TX_FEE_PROPOSAL, Strategy, ) from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" tac_dm_context_kwargs = { "goal_pursuit_readiness": GoalPursuitReadiness(), "ownership_state": OwnershipState(), "preferences": Preferences(), } super().setup(dm_context_kwargs=tac_dm_context_kwargs) cls.register_as = "both" cls.search_for = "both" cls.is_contract_tx = False cls.ledger_id = "some_ledger_id" cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.search_radius = 5.0 cls.service_key = "tac_service" cls.strategy = Strategy( register_as=cls.register_as, search_for=cls.search_for, is_contract_tx=cls.is_contract_tx, ledger_id=cls.ledger_id, location=cls.location, service_key=cls.service_key, search_radius=cls.search_radius, name="strategy", skill_context=cls._skill.skill_context, ) cls.nonce = "125" cls.sender = "some_sender_address" cls.counterparty = "some_counterparty_address" cls.signature = "some_signature" cls.sender_pk = "some_sender_public_key" cls.counterparty_pk = "some_counterparty_public_key" cls.mocked_currency_id = "12" cls.mocked_currency_amount = 2000000 cls.mocked_amount_by_currency_id = { cls.mocked_currency_id: cls.mocked_currency_amount } cls.mocked_good_ids = ["13", "14", "15", "16", "17", "18", "19", "20", "21"] cls.mocked_good_quantities = [5, 7, 4, 3, 5, 4, 3, 5, 6] cls.mocked_quantities_by_good_id = dict( zip(cls.mocked_good_ids, cls.mocked_good_quantities) ) cls.mocked_ownership_state = ( cls._skill.skill_context.decision_maker_handler_context.ownership_state ) cls.mocked_ownership_state.set( cls.mocked_amount_by_currency_id, cls.mocked_quantities_by_good_id ) cls.exchange_params_by_currency_id = {cls.mocked_currency_id: 1.0} cls.utility_params_by_good_id = { "13": 48300.0, "14": 43700.0, "15": 163200.0, "16": 59800.0, "17": 114900.0, "18": 128700.00000000001, "19": 126400.00000000001, "20": 211500.0, "21": 103500.0, } cls.mocked_preferences = ( cls._skill.skill_context.decision_maker_handler_context.preferences ) cls.mocked_preferences.set( exchange_params_by_currency_id=cls.exchange_params_by_currency_id, utility_params_by_good_id=cls.utility_params_by_good_id, ) def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.registering_as == "buyer and seller" assert self.strategy.searching_for == "buyer and seller" assert self.strategy.searching_for_types == [ (True, "sellers"), (False, "buyers"), ] assert self.strategy.is_contract_tx == self.is_contract_tx assert self.strategy.ledger_id == self.ledger_id assert self.strategy.contract_id == str(CONTRACT_ID) with pytest.raises(AEAEnforceError, match="ERC1155Contract address not set!"): assert self.strategy.contract_address self.skill.skill_context._agent_context._shared_state = { "erc1155_contract_address": "some_address" } assert self.strategy.contract_address == "some_address" def test_get_location_description(self): """Test the get_location_description method of the Strategy class.""" description = self.strategy.get_location_description() assert type(description) == Description assert description.data_model is AGENT_LOCATION_MODEL assert description.values.get("location", "") == Location( latitude=self.location["latitude"], longitude=self.location["longitude"] ) def test_get_register_service_description(self): """Test the get_register_service_description method of the GenericStrategy class.""" # setup self.strategy.tac_version_id = "some_tac_id" # operation description = self.strategy.get_register_service_description() # after assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert ( description.values.get("key", "") == f"{self.service_key}_{self.strategy.tac_version_id}" ) assert description.values.get("value", "") == self.register_as def test_get_register_personality_description(self): """Test the get_register_personality_description method of the GenericStrategy class.""" description = self.strategy.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "genus" assert description.values.get("value", "") == "data" def test_get_register_classification_description(self): """Test the get_register_classification_description method of the GenericStrategy class.""" description = self.strategy.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "classification" assert description.values.get("value", "") == "tac.participant" def test_get_unregister_service_description(self): """Test the get_unregister_service_description method of the GenericStrategy class.""" description = self.strategy.get_unregister_service_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == self.service_key def test_get_location_and_service_query(self): """Test the get_location_and_service_query method of the Strategy class.""" # setup self.strategy.tac_version_id = "some_tac_id" # operation query = self.strategy.get_location_and_service_query() # after assert type(query) == Query assert len(query.constraints) == 2 assert query.model is None location_constraint = Constraint( "location", ConstraintType( "distance", ( Location( latitude=self.location["latitude"], longitude=self.location["longitude"], ), self.search_radius, ), ), ) assert query.constraints[0] == location_constraint service_key_filter = Constraint( f"{self.service_key}_{self.strategy.tac_version_id}", ConstraintType("==", self.search_for), ) assert query.constraints[1] == service_key_filter def test_get_own_service_description_is_supply(self): """Test the get_own_service_description method of the Strategy class where is_supply is True.""" # setup is_supply = True mocked_supplied_quantities_by_good_id = { good_id: quantity - 1 for good_id, quantity in self.mocked_quantities_by_good_id.items() } expected_description = build_goods_description( mocked_supplied_quantities_by_good_id, self.mocked_currency_id, self.ledger_id, is_supply, ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_description = self.strategy.get_own_service_description(is_supply) # after mock_ownership.assert_any_call(is_seller=is_supply) assert actual_description == expected_description def test_get_own_service_description_not_is_supply(self): """Test the get_own_service_description method of the Strategy class where is_supply is False.""" # setup is_supply = False mocked_demanded_quantities_by_good_id = { good_id: 1 for good_id in self.mocked_quantities_by_good_id.keys() } expected_description = build_goods_description( mocked_demanded_quantities_by_good_id, self.mocked_currency_id, self.ledger_id, is_supply, ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_description = self.strategy.get_own_service_description(is_supply) # after mock_ownership.assert_any_call(is_seller=is_supply) assert actual_description == expected_description def test_supplied_goods(self): """Test the _supplied_goods method of the Strategy class.""" good_holdings = {"1": 1, "2": 5, "3": 10, "4": 1, "5": 0} actual_supply = self.strategy._supplied_goods(good_holdings) expected_supply = {"1": 0, "2": 4, "3": 9, "4": 0, "5": 0} assert actual_supply == expected_supply def test_demanded_goods(self): """Test the _demanded_goods method of the Strategy class.""" good_holdings = {"1": 1, "2": 5, "3": 10, "4": 1, "5": 0} actual_demand = self.strategy._demanded_goods(good_holdings) expected_demand = {"1": 1, "2": 1, "3": 1, "4": 1, "5": 1} assert actual_demand == expected_demand def test_get_own_services_query_searching_seller(self): """Test the get_own_services_query method of the Strategy class where is_searching_for_sellers is True.""" # setup is_searching_for_sellers = True expected_query = build_goods_query( list(self.mocked_quantities_by_good_id.keys()), self.mocked_currency_id, self.ledger_id, is_searching_for_sellers, ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_query = self.strategy.get_own_services_query( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=not is_searching_for_sellers) assert actual_query == expected_query def test_get_own_services_query_searching_buyers(self): """Test the get_own_services_query method of the Strategy class where is_searching_for_sellers is False (same as above).""" # setup is_searching_for_sellers = False expected_query = build_goods_query( list(self.mocked_quantities_by_good_id.keys()), self.mocked_currency_id, self.ledger_id, is_searching_for_sellers, ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_query = self.strategy.get_own_services_query( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=not is_searching_for_sellers) assert actual_query == expected_query def test__get_proposal_for_query(self): """Test the _get_proposal_for_query method of the Strategy class.""" # setup is_seller = True mocked_query = Query( [Constraint("some_attribute_name", ConstraintType("==", "some_value"))], DataModel( "some_data_model_name", [ Attribute( "some_attribute_name", str, False, "Some attribute descriptions.", ) ], ), ) proposal_1 = Description( { "some_attribute_name": "some_value", "ledger_id": self.ledger_id, "price": 100, "currency_id": "1", "fee": 1, "nonce": self.nonce, } ) proposal_2 = Description( { "some_attribute_name": "some_value", "ledger_id": self.ledger_id, "price": -100, "currency_id": "1", "fee": 2, "nonce": self.nonce, } ) mocked_candidate_proposals = [proposal_1, proposal_2] # operation with patch.object( self.strategy, "_generate_candidate_proposals", return_value=mocked_candidate_proposals, ) as mock_candid: actual_query = self.strategy._get_proposal_for_query( mocked_query, is_seller ) # after mock_candid.assert_any_call(is_seller) assert actual_query in mocked_candidate_proposals def test_get_proposal_for_query(self): """Test the get_proposal_for_query method of the Strategy class.""" # setup role = FipaDialogue.Role.SELLER is_seller = True mocked_query = Query( [Constraint("some_attribute_name", ConstraintType("==", "some_value"))], DataModel( "some_data_model_name", [ Attribute( "some_attribute_name", str, False, "Some attribute descriptions.", ) ], ), ) own_description = Description( { "some_attribute_name": "some_value", "ledger_id": self.ledger_id, "price": 100, "currency_id": "1", "fee": 1, "nonce": self.nonce, } ) expected_proposal = own_description # operation with patch.object( self.strategy, "get_own_service_description", return_value=own_description ) as mock_own: with patch.object( self.strategy, "_get_proposal_for_query", return_value=expected_proposal ) as mock_get_proposal: actual_proposal = self.strategy.get_proposal_for_query( mocked_query, role ) # after mock_own.assert_any_call(is_supply=is_seller) mock_get_proposal.assert_any_call(mocked_query, is_seller=is_seller) assert actual_proposal == expected_proposal def test_generate_candidate_proposals_i(self): """Test the _generate_candidate_proposals method of the Strategy class where role is seller.""" # setup is_searching_for_sellers = True expected_proposed_prices = [463, 411, 1578, 584, 1101, 1244, 1234, 2025, 982] # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_proposals = self.strategy._generate_candidate_proposals( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=is_searching_for_sellers) assert type(actual_proposals) == list assert len(actual_proposals) == 9 for index in range(9): assert type(actual_proposals[index]) == Description assert actual_proposals[index].values[self.mocked_good_ids[index]] == 1 for good_id_index in range(9): if index != good_id_index: assert ( actual_proposals[index].values[ self.mocked_good_ids[good_id_index] ] == 0 ) assert ( actual_proposals[index].values["currency_id"] == self.mocked_currency_id ) assert actual_proposals[index].values["ledger_id"] == self.ledger_id assert ( actual_proposals[index].values["price"] == expected_proposed_prices[index] ) assert actual_proposals[index].values["fee"] == DEFAULT_TX_FEE_PROPOSAL assert actual_proposals[index].values["nonce"] == str(index + 1) def test_generate_candidate_proposals_ii(self): """Test the _generate_candidate_proposals method of the Strategy class where role is buyer.""" # setup expected_proposed_prices = [457, 406, 1561, 577, 1088, 1231, 1220, 2004, 971] is_searching_for_sellers = False # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: with patch.object(self.strategy, "_tx_fee_proposal", 0): actual_proposals = self.strategy._generate_candidate_proposals( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=is_searching_for_sellers) assert type(actual_proposals) == list assert len(actual_proposals) == len(self.mocked_good_ids) for index in range(9): assert type(actual_proposals[index]) == Description assert actual_proposals[index].values[self.mocked_good_ids[index]] == 1 for good_id_index in range(9): if index != good_id_index: assert ( actual_proposals[index].values[ self.mocked_good_ids[good_id_index] ] == 0 ) assert ( actual_proposals[index].values["currency_id"] == self.mocked_currency_id ) assert actual_proposals[index].values["ledger_id"] == self.ledger_id assert ( actual_proposals[index].values["price"] == expected_proposed_prices[index] ) assert actual_proposals[index].values["fee"] == 0 # empty fee is ok here assert actual_proposals[index].values["nonce"] == str(index + 1) def test_generate_candidate_proposals_iii(self): """Test the _generate_candidate_proposals method of the Strategy class where is_seller and quantity is 0.""" # setup is_searching_for_sellers = True expected_proposed_price = 982 mocked_good_quantities = [1, 0, 0, 1, 0, 1, 0, 1, 6] mocked_quantities_by_good_id = dict( zip(self.mocked_good_ids, mocked_good_quantities) ) self.mocked_ownership_state._amount_by_currency_id = None self.mocked_ownership_state._quantities_by_good_id = None self.mocked_ownership_state.set( self.mocked_amount_by_currency_id, mocked_quantities_by_good_id ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_proposals = self.strategy._generate_candidate_proposals( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=is_searching_for_sellers) assert type(actual_proposals) == list assert len(actual_proposals) == 1 actual_proposal = actual_proposals[0] assert type(actual_proposal) == Description assert actual_proposal.values[self.mocked_good_ids[8]] == 1 for good_id_index in range(8): assert actual_proposal.values[self.mocked_good_ids[good_id_index]] == 0 assert actual_proposal.values["currency_id"] == self.mocked_currency_id assert actual_proposal.values["ledger_id"] == self.ledger_id assert actual_proposal.values["price"] == expected_proposed_price assert actual_proposal.values["fee"] == DEFAULT_TX_FEE_PROPOSAL assert actual_proposal.values["nonce"] == "1" def test_generate_candidate_proposals_iv(self): """Test the _generate_candidate_proposals method of the Strategy class where proposed price is 0.""" # setup is_searching_for_sellers = True mocked_good_quantities = [2, 0, 0, 1, 0, 1, 0, 1, 0] mocked_quantities_by_good_id = dict( zip(self.mocked_good_ids, mocked_good_quantities) ) self.mocked_ownership_state._amount_by_currency_id = None self.mocked_ownership_state._quantities_by_good_id = None self.mocked_ownership_state.set( self.mocked_amount_by_currency_id, mocked_quantities_by_good_id ) utility_params_by_good_id = { "13": -100.0, "14": 43700.0, "15": 163200.0, "16": 59800.0, "17": 114900.0, "18": 128700.00000000001, "19": 126400.00000000001, "20": 211500.0, "21": 103500.0, } self.mocked_preferences._exchange_params_by_currency_id = None self.mocked_preferences._utility_params_by_good_id = None self.mocked_preferences.set( exchange_params_by_currency_id=self.exchange_params_by_currency_id, utility_params_by_good_id=utility_params_by_good_id, ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: actual_proposals = self.strategy._generate_candidate_proposals( is_searching_for_sellers ) # after mock_ownership.assert_any_call(is_seller=is_searching_for_sellers) assert actual_proposals == [] def test_is_profitable_transaction_not_affordable(self): """Test the is_profitable_transaction method of the Strategy class where is_affordable_transaction is False.""" is_searching_for_sellers = False role = FipaDialogue.Role.BUYER proposal = Description( { "13": 1, "14": 0, "15": 0, "16": 0, "17": 0, "18": 0, "19": 0, "20": 0, "21": 0, "ledger_id": self.ledger_id, "price": 10000000, "currency_id": self.mocked_currency_id, "fee": 0, "nonce": self.nonce, } ) terms = self.strategy.terms_from_proposal( proposal, self.sender, self.counterparty, role ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: with patch.object( self.mocked_ownership_state, "is_affordable_transaction", return_value=False, ) as mock_is_affordable: is_profitable = self.strategy.is_profitable_transaction(terms, role) # after mock_ownership.assert_any_call(is_searching_for_sellers) mock_is_affordable.assert_any_call(terms) assert not is_profitable def test_is_profitable_transaction_is_affordable(self): """Test the is_profitable_transaction method of the Strategy class where is_affordable_transaction is True.""" is_searching_for_sellers = True role = FipaDialogue.Role.SELLER proposal = Description( { "13": 1, "14": 0, "15": 0, "16": 0, "17": 0, "18": 0, "19": 0, "20": 0, "21": 0, "ledger_id": self.ledger_id, "price": 463, "currency_id": self.mocked_currency_id, "fee": 0, "nonce": self.nonce, } ) terms = self.strategy.terms_from_proposal( proposal, self.sender, self.counterparty, role ) # operation with patch.object( self.skill.skill_context.transactions, "ownership_state_after_locks", return_value=self.mocked_ownership_state, ) as mock_ownership: with patch.object( self.mocked_ownership_state, "is_affordable_transaction", return_value=True, ) as mock_is_affordable: is_profitable = self.strategy.is_profitable_transaction(terms, role) # after mock_ownership.assert_any_call(is_searching_for_sellers) mock_is_affordable.assert_any_call(terms) assert is_profitable def test_terms_from_proposal_seller(self): """Test the terms_from_proposal method of the Strategy class where is_seller is True.""" proposal = Description( { "2": 5, "ledger_id": self.ledger_id, "price": 100, "currency_id": "FET", "fee": 1, "nonce": self.nonce, } ) role = FipaDialogue.Role.SELLER is_seller = True expected_terms = Terms( ledger_id=self.ledger_id, sender_address=self.sender, counterparty_address=self.counterparty, amount_by_currency_id={ proposal.values["currency_id"]: proposal.values["price"] }, quantities_by_good_id={"2": -5}, is_sender_payable_tx_fee=not is_seller, nonce=self.nonce, fee_by_currency_id={proposal.values["currency_id"]: proposal.values["fee"]}, ) actual_terms = self.strategy.terms_from_proposal( proposal, self.sender, self.counterparty, role ) assert actual_terms == expected_terms def test_terms_from_proposal_buyer(self): """Test the terms_from_proposal method of the Strategy class where is_seller is False.""" proposal = Description( { "2": 5, "ledger_id": self.ledger_id, "price": 100, "currency_id": "FET", "fee": 1, "nonce": self.nonce, } ) role = FipaDialogue.Role.BUYER is_seller = False expected_terms = Terms( ledger_id=self.ledger_id, sender_address=self.sender, counterparty_address=self.counterparty, amount_by_currency_id={ proposal.values["currency_id"]: -proposal.values["price"] }, quantities_by_good_id={"2": 5}, is_sender_payable_tx_fee=not is_seller, nonce=self.nonce, fee_by_currency_id={proposal.values["currency_id"]: proposal.values["fee"]}, ) actual_terms = self.strategy.terms_from_proposal( proposal, self.sender, self.counterparty, role ) assert actual_terms == expected_terms def test_kwargs_from_terms_seller_ethereum(self): """Test the kwargs_from_terms method of the Strategy class where is_seller is True.""" is_seller = True terms = Terms( ledger_id=self.ledger_id, sender_address=self.sender, counterparty_address=self.counterparty, amount_by_currency_id={"1": 10}, quantities_by_good_id={"2": -5}, is_sender_payable_tx_fee=not is_seller, nonce=self.nonce, fee_by_currency_id={"1": 1}, ) expected_kwargs = { "from_address": self.sender, "to_address": self.counterparty, "token_ids": [1, 2], "from_supplies": [0, 5], "to_supplies": [10, 0], "value": 0, "trade_nonce": 125, "signature": self.signature, "tx_fee": 1, } actual_kwargs = self.strategy.kwargs_from_terms( terms, self.signature, is_from_terms_sender=True ) assert actual_kwargs == expected_kwargs expected_kwargs = { "from_address": self.counterparty, "to_address": self.sender, "token_ids": [1, 2], "from_supplies": [10, 0], "to_supplies": [0, 5], "value": 0, "trade_nonce": 125, "signature": self.signature, "tx_fee": 1, } actual_kwargs = self.strategy.kwargs_from_terms( terms, self.signature, is_from_terms_sender=False ) assert actual_kwargs == expected_kwargs def test_kwargs_from_terms_buyer_fetchai(self): """Test the kwargs_from_terms method of the Strategy class where is_seller is False (no difference with seller).""" is_seller = False terms = Terms( ledger_id=self.ledger_id, sender_address=self.sender, counterparty_address=self.counterparty, amount_by_currency_id={"1": 10}, quantities_by_good_id={"2": -5}, is_sender_payable_tx_fee=not is_seller, nonce=self.nonce, fee_by_currency_id={"1": 1}, ) expected_kwargs = { "from_address": self.sender, "to_address": self.counterparty, "token_ids": [1, 2], "from_supplies": [0, 5], "to_supplies": [10, 0], "value": 1, "trade_nonce": 125, "from_pubkey": self.sender_pk, "to_pubkey": self.counterparty_pk, "tx_fee": 1, } actual_kwargs = self.strategy.kwargs_from_terms( terms, sender_public_key=self.sender_pk, counterparty_public_key=self.counterparty_pk, ) assert actual_kwargs == expected_kwargs def test_kwargs_from_terms_i(self): """Test the kwargs_from_terms method of the Strategy class where sender's IS and counterparty's public key is NOT provided.""" is_seller = True terms = Terms( ledger_id=self.ledger_id, sender_address=self.sender, counterparty_address=self.counterparty, amount_by_currency_id={"1": 10}, quantities_by_good_id={"2": -5}, is_sender_payable_tx_fee=not is_seller, nonce=self.nonce, fee_by_currency_id={"1": 1}, ) with pytest.raises( AEAEnforceError, match="Either provide both sender's and counterparty's public-keys or neither's.", ): self.strategy.kwargs_from_terms( terms, self.signature, is_from_terms_sender=True, sender_public_key="some_public_key", ) def test_kwargs_from_terms_ii(self): """Test the kwargs_from_terms method of the Strategy class where sender's is NOT and counterparty's public key IS provided.""" is_seller = True terms = Terms( ledger_id=self.ledger_id, sender_address=self.sender, counterparty_address=self.counterparty, amount_by_currency_id={"1": 10}, quantities_by_good_id={"2": -5}, is_sender_payable_tx_fee=not is_seller, nonce=self.nonce, fee_by_currency_id={"1": 1}, ) with pytest.raises( AEAEnforceError, match="Either provide both sender's and counterparty's public-keys or neither's.", ): self.strategy.kwargs_from_terms( terms, self.signature, is_from_terms_sender=True, counterparty_public_key="some_public_key", ) def test_kwargs_from_terms_iii(self): """Test the kwargs_from_terms method of the Strategy class where signature IS and a public_key is NOT provided.""" is_seller = True terms = Terms( ledger_id=self.ledger_id, sender_address=self.sender, counterparty_address=self.counterparty, amount_by_currency_id={"1": 10}, quantities_by_good_id={"2": -5}, is_sender_payable_tx_fee=not is_seller, nonce=self.nonce, fee_by_currency_id={"1": 1}, ) with pytest.raises( AEAEnforceError, match=re.escape( "Either provide signature (for Ethereum-based TAC) or sender and counterparty's public keys (for Fetchai-based TAC), or neither (for and non-contract-based Tac)" ), ): self.strategy.kwargs_from_terms( terms, self.signature, is_from_terms_sender=True, sender_public_key="some_public_key", counterparty_public_key="some_other_public_key", ) ================================================ FILE: tests/test_packages/test_skills/test_tac_negotiation/test_transactions.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the Transactions class of the tac negotiation skill.""" import datetime import logging from pathlib import Path from typing import Deque, Tuple, cast from unittest.mock import Mock, patch import pytest from aea.decision_maker.gop import GoalPursuitReadiness, OwnershipState, Preferences from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue from packages.fetchai.skills.tac_negotiation.transactions import Transactions from tests.conftest import ROOT_DIR class TestTransactions(BaseSkillTestCase): """Test Transactions class of tac negotiation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_negotiation") @classmethod def setup(cls): """Setup the test class.""" tac_dm_context_kwargs = { "goal_pursuit_readiness": GoalPursuitReadiness(), "ownership_state": OwnershipState(), "preferences": Preferences(), } super().setup(dm_context_kwargs=tac_dm_context_kwargs) cls.pending_transaction_timeout = 30 cls.transactions = Transactions( pending_transaction_timeout=cls.pending_transaction_timeout, name="transactions", skill_context=cls._skill.skill_context, ) cls.nonce = "125" cls.sender = "some_sender_address" cls.counterparty = "some_counterparty_address" cls.ledger_id = "some_ledger_id" cls.terms = Terms( ledger_id=cls.ledger_id, sender_address=cls.sender, counterparty_address=cls.counterparty, amount_by_currency_id={"1": 10}, quantities_by_good_id={"2": -5}, is_sender_payable_tx_fee=True, nonce=cls.nonce, fee_by_currency_id={"1": 1}, ) cls.dialogue_label = DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, cls._skill.skill_context.agent_address, ) cls.proposal_id = 5 cls.transaction_id = "some_transaction_id" def test_simple_properties(self): """Test the properties of Transactions class.""" assert self.transactions.pending_proposals == {} assert self.transactions.pending_initial_acceptances == {} def test_get_next_nonce(self): """Test the get_next_nonce method of the Transactions class.""" assert self.transactions.get_next_nonce() == "1" assert self.transactions._nonce == 1 def test_update_confirmed_transactions(self): """Test the update_confirmed_transactions method of the Transactions class.""" # setup self.skill.skill_context._get_agent_context().shared_state[ "confirmed_tx_ids" ] = [self.transaction_id] self.transactions._locked_txs[self.transaction_id] = self.terms self.transactions._locked_txs_as_buyer[self.transaction_id] = self.terms self.transactions._locked_txs_as_seller[self.transaction_id] = self.terms # operation self.transactions.update_confirmed_transactions() # after assert self.transactions._locked_txs == {} assert self.transactions._locked_txs_as_buyer == {} assert self.transactions._locked_txs_as_seller == {} def test_cleanup_pending_transactions_i(self): """Test the cleanup_pending_transactions method of the Transactions class where _last_update_for_transactions is NOT empty.""" # setup datetime_mock = Mock(wraps=datetime.datetime) datetime_mock.now.return_value = datetime.datetime.strptime( "01 01 2020 00:03", "%d %m %Y %H:%M" ) with patch("datetime.datetime", new=datetime_mock): self.transactions._register_transaction_with_time(self.transaction_id) self.transactions._locked_txs[self.transaction_id] = self.terms self.transactions._locked_txs_as_buyer[self.transaction_id] = self.terms self.transactions._locked_txs_as_seller[self.transaction_id] = self.terms # operation with patch.object(self.skill.skill_context.logger, "log") as mock_logger: self.transactions.cleanup_pending_transactions() # after mock_logger.assert_any_call( logging.DEBUG, f"removing transaction from pending list: {self.transaction_id}", ) assert self.transactions._locked_txs == {} assert self.transactions._locked_txs_as_buyer == {} assert self.transactions._locked_txs_as_seller == {} def test_cleanup_pending_transactions_ii(self): """Test the cleanup_pending_transactions method of the Transactions class where _last_update_for_transactions is empty.""" # setup cast( Deque[Tuple[datetime.datetime, str]], self.transactions._last_update_for_transactions, ) assert self.transactions._locked_txs == {} assert self.transactions._locked_txs_as_buyer == {} assert self.transactions._locked_txs_as_seller == {} # operation self.transactions.cleanup_pending_transactions() # after assert self.transactions._locked_txs == {} assert self.transactions._locked_txs_as_buyer == {} assert self.transactions._locked_txs_as_seller == {} def test_add_pending_proposal_i(self): """Test the add_pending_proposal method of the Transactions class.""" # before assert self.dialogue_label not in self.transactions._pending_proposals # operation self.transactions.add_pending_proposal( self.dialogue_label, self.proposal_id, self.terms ) # after assert ( self.transactions._pending_proposals[self.dialogue_label][self.proposal_id] == self.terms ) def test_add_pending_proposal_ii(self): """Test the add_pending_proposal method of the Transactions class where dialogue_label IS in _pending_proposals.""" # setup self.transactions._pending_proposals[self.dialogue_label] = {1: self.terms} # operation with pytest.raises( AEAEnforceError, match="Proposal is already in the list of pending proposals.", ): self.transactions.add_pending_proposal( self.dialogue_label, self.proposal_id, self.terms ) def test_add_pending_proposal_iii(self): """Test the add_pending_proposal method of the Transactions class where proposal_id IS in _pending_proposals.""" # setup self.transactions._pending_proposals[self.dialogue_label][ self.proposal_id ] = self.terms # operation with pytest.raises( AEAEnforceError, match="Proposal is already in the list of pending proposals.", ): self.transactions.add_pending_proposal( self.dialogue_label, self.proposal_id, self.terms ) def test_pop_pending_proposal_i(self): """Test the pop_pending_proposal method of the Transactions class.""" # setup self.transactions.add_pending_proposal( self.dialogue_label, self.proposal_id, self.terms ) # operation actual_terms = self.transactions.pop_pending_proposal( self.dialogue_label, self.proposal_id ) # after assert actual_terms == self.terms assert ( self.proposal_id not in self.transactions._pending_proposals[self.dialogue_label] ) def test_pop_pending_proposal_ii(self): """Test the pop_pending_proposal method of the Transactions class where dialogue_label IS in _pending_proposals.""" # setup self.transactions.add_pending_proposal( self.dialogue_label, self.proposal_id, self.terms ) self.transactions._pending_proposals = {} # operation with pytest.raises( AEAEnforceError, match="Cannot find the proposal in the list of pending proposals.", ): assert self.transactions.pop_pending_proposal( self.dialogue_label, self.proposal_id ) def test_pop_pending_proposal_iii(self): """Test the pop_pending_proposal method of the Transactions class where dialogue_label and proposal_id IS in _pending_proposals.""" # setup self.transactions.add_pending_proposal( self.dialogue_label, self.proposal_id, self.terms ) self.transactions._pending_proposals[self.dialogue_label] = {1: self.terms} # operation with pytest.raises( AEAEnforceError, match="Cannot find the proposal in the list of pending proposals.", ): assert self.transactions.pop_pending_proposal( self.dialogue_label, self.proposal_id ) def test_add_pending_initial_acceptance_i(self): """Test the add_pending_initial_acceptance method of the Transactions class.""" # before assert self.transactions._pending_initial_acceptances == {} # operation self.transactions.add_pending_initial_acceptance( self.dialogue_label, self.proposal_id, self.terms, ) # after assert ( self.transactions._pending_initial_acceptances[self.dialogue_label][ self.proposal_id ] == self.terms ) def test_add_pending_initial_acceptance_ii(self): """Test the add_pending_initial_acceptance method of the Transactions class where dialogue_label IS in _pending_initial_acceptances.""" # setup self.transactions._pending_initial_acceptances[self.dialogue_label] = { 1: self.terms } # operation with pytest.raises( AEAEnforceError, match="Initial acceptance is already in the list of pending initial acceptances.", ): self.transactions.add_pending_initial_acceptance( self.dialogue_label, self.proposal_id, self.terms, ) def test_add_pending_initial_acceptance_iii(self): """Test the add_pending_initial_acceptance method of the Transactions class where dialogue_label and proposal_id IS in _pending_initial_acceptances.""" # setup self.transactions._pending_initial_acceptances[self.dialogue_label] = { self.proposal_id: self.terms } # operation with pytest.raises( AEAEnforceError, match="Initial acceptance is already in the list of pending initial acceptances.", ): self.transactions.add_pending_initial_acceptance( self.dialogue_label, self.proposal_id, self.terms, ) def test_pop_pending_initial_acceptance_i(self): """Test the pop_pending_initial_acceptance method of the Transactions class.""" # setup self.transactions.add_pending_initial_acceptance( self.dialogue_label, self.proposal_id, self.terms, ) # operation actual_terms = self.transactions.pop_pending_initial_acceptance( self.dialogue_label, self.proposal_id ) # after assert actual_terms == self.terms assert ( self.proposal_id not in self.transactions._pending_proposals[self.dialogue_label] ) def test_pop_pending_initial_acceptance_ii(self): """Test the pop_pending_initial_acceptance method of the Transactions class where dialogue_label IS in _pending_initial_acceptances.""" # setup self.transactions.add_pending_initial_acceptance( self.dialogue_label, self.proposal_id, self.terms, ) self.transactions._pending_initial_acceptances = {} # operation with pytest.raises( AEAEnforceError, match="Cannot find the initial acceptance in the list of pending initial acceptances.", ): assert self.transactions.pop_pending_initial_acceptance( self.dialogue_label, self.proposal_id ) def test_pop_pending_initial_acceptance_iii(self): """Test the pop_pending_initial_acceptance method of the Transactions class where dialogue_label and proposal_id IS in _pending_initial_acceptances.""" # setup self.transactions.add_pending_initial_acceptance( self.dialogue_label, self.proposal_id, self.terms, ) self.transactions._pending_initial_acceptances[self.dialogue_label] = { 1: self.terms } # operation with pytest.raises( AEAEnforceError, match="Cannot find the initial acceptance in the list of pending initial acceptances.", ): assert self.transactions.pop_pending_initial_acceptance( self.dialogue_label, self.proposal_id ) def test_register_transaction_with_time(self): """Test the _register_transaction_with_time method of the Transactions class.""" # setup datetime_mock = Mock(wraps=datetime.datetime) mocked_now = datetime.datetime.strptime("01 01 2020 00:03", "%d %m %Y %H:%M") datetime_mock.now.return_value = mocked_now # operation with patch("datetime.datetime", new=datetime_mock): self.transactions._register_transaction_with_time(self.transaction_id) # after assert (mocked_now, self.transaction_id,)[ 1 ] == self.transactions._last_update_for_transactions[0][1] def test_add_locked_tx_seller(self): """Test the add_locked_tx method of the Transactions class as Seller.""" # setup datetime_mock = Mock(wraps=datetime.datetime) mocked_now = datetime.datetime.strptime("01 01 2020 00:03", "%d %m %Y %H:%M") datetime_mock.now.return_value = mocked_now # operation with patch("datetime.datetime", new=datetime_mock): self.transactions.add_locked_tx(self.terms, FipaDialogue.Role.SELLER) # after assert (mocked_now, self.terms.id,)[ 1 ] == self.transactions._last_update_for_transactions[0][1] assert self.transactions._locked_txs[self.terms.id] == self.terms assert self.transactions._locked_txs_as_seller[self.terms.id] == self.terms assert self.terms.id not in self.transactions._locked_txs_as_buyer def test_add_locked_tx_buyer(self): """Test the add_locked_tx method of the Transactions class as Seller.""" # setup datetime_mock = Mock(wraps=datetime.datetime) mocked_now = datetime.datetime.strptime("01 01 2020 00:03", "%d %m %Y %H:%M") datetime_mock.now.return_value = mocked_now # operation with patch("datetime.datetime", new=datetime_mock): self.transactions.add_locked_tx(self.terms, FipaDialogue.Role.BUYER) # after assert (mocked_now, self.terms.id,)[ 1 ] == self.transactions._last_update_for_transactions[0][1] assert self.transactions._locked_txs[self.terms.id] == self.terms assert self.transactions._locked_txs_as_buyer[self.terms.id] == self.terms assert self.terms.id not in self.transactions._locked_txs_as_seller def test_add_locked_tx_fails(self): """Test the add_locked_tx method of the Transactions class where transaction_id IS in _locked_txs.""" # setup self.transactions._locked_txs[self.terms.id] = self.terms datetime_mock = Mock(wraps=datetime.datetime) mocked_now = datetime.datetime.strptime("01 01 2020 00:03", "%d %m %Y %H:%M") datetime_mock.now.return_value = mocked_now # operation with patch("datetime.datetime", new=datetime_mock): with pytest.raises( AEAEnforceError, match="This transaction is already a locked transaction.", ): self.transactions.add_locked_tx(self.terms, FipaDialogue.Role.BUYER) # after assert ( mocked_now, self.terms.id, ) not in self.transactions._last_update_for_transactions assert self.terms.id not in self.transactions._locked_txs_as_buyer assert self.terms.id not in self.transactions._locked_txs_as_seller def test_pop_locked_tx(self): """Test the pop_locked_tx method of the Transactions class.""" # setup self.transactions.add_locked_tx(self.terms, FipaDialogue.Role.BUYER) # before assert self.terms.id in self.transactions._locked_txs assert self.terms.id in self.transactions._locked_txs_as_buyer assert self.terms.id not in self.transactions._locked_txs_as_seller # operation actual_terms = self.transactions.pop_locked_tx(self.terms) # after assert actual_terms == self.terms assert self.terms.id not in self.transactions._locked_txs assert self.terms.id not in self.transactions._locked_txs_as_buyer assert self.terms.id not in self.transactions._locked_txs_as_seller def test_pop_locked_tx_fails(self): """Test the pop_locked_tx method of the Transactions class where terms.id is NOT in _locked_txs.""" # before assert self.terms.id not in self.transactions._locked_txs assert self.terms.id not in self.transactions._locked_txs_as_buyer assert self.terms.id not in self.transactions._locked_txs_as_seller # operation with pytest.raises( AEAEnforceError, match="Cannot find this transaction in the list of locked transactions.", ): self.transactions.pop_locked_tx(self.terms) # after assert self.terms.id not in self.transactions._locked_txs assert self.terms.id not in self.transactions._locked_txs_as_buyer assert self.terms.id not in self.transactions._locked_txs_as_seller def test_ownership_state_after_locks(self): """Test the ownership_state_after_locks method of the Transactions class.""" # setup is_seller = True self.transactions._locked_txs_as_seller[self.transaction_id] = self.terms expected_apply_transactions_argument = [self.terms] expected_ownership_state = OwnershipState() # operation with patch.object( self.skill.skill_context.decision_maker_handler_context.ownership_state, "apply_transactions", return_value=expected_ownership_state, ) as mock_apply_transactions: actual_ownership_states = self.transactions.ownership_state_after_locks( is_seller ) # after mock_apply_transactions.assert_any_call(expected_apply_transactions_argument) assert actual_ownership_states == expected_ownership_state ================================================ FILE: tests/test_packages/test_skills/test_tac_participation/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/tac_participation dir.""" ================================================ FILE: tests/test_packages/test_skills/test_tac_participation/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the tac participation skill.""" import logging from collections import OrderedDict from pathlib import Path from typing import cast from unittest.mock import patch import pytest from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.behaviours import ( TacSearchBehaviour, TransactionProcessBehaviour, ) from packages.fetchai.skills.tac_participation.dialogues import TacDialogues from packages.fetchai.skills.tac_participation.game import Game, Phase from tests.conftest import ROOT_DIR class TestTacSearchBehaviour(BaseSkillTestCase): """Test tac behaviour of tac participation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_participation") is_agent_to_agent_messages = True @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.tac_search_behaviour = cast( TacSearchBehaviour, cls._skill.skill_context.behaviours.tac_search ) cls.game = cast(Game, cls._skill.skill_context.game) cls.logger = cls.tac_search_behaviour.context.logger def test_setup(self): """Test the setup method of the tac_search behaviour.""" assert self.tac_search_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act_i(self): """Test the act method of the tac_search behaviour where phase is not PRE_GAME.""" # setup self.game._phase = Phase.GAME # operation with patch.object(self.logger, "log"): self.tac_search_behaviour.act() # after assert self.game.phase == Phase.GAME self.assert_quantity_in_outbox(0) def test_act_ii(self): """Test the act method of the tac_search behaviour where phase is PRE_GAME.""" # setup self.game._phase = Phase.PRE_GAME mocked_query = "some_query" # operation with patch.object(self.game, "get_game_query", return_value=mocked_query): with patch.object(self.logger, "log") as mock_logger: self.tac_search_behaviour.act() # after self.assert_quantity_in_outbox(1) # _search_for_tac message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), query=mocked_query, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"searching for TAC, search_id={message.dialogue_reference}" ) def test_teardown(self): """Test the teardown method of the tac_search behaviour.""" assert self.tac_search_behaviour.teardown() is None self.assert_quantity_in_outbox(0) class TestTransactionProcessBehaviour(BaseSkillTestCase): """Test tac behaviour of tac participation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_participation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.transaction_process_behaviour = cast( TransactionProcessBehaviour, cls._skill.skill_context.behaviours.transaction_processing, ) cls.game = cast(Game, cls._skill.skill_context.game) cls.logger = cls.transaction_process_behaviour.context.logger cls.tac_dialogues = cast(TacDialogues, cls._skill.skill_context.tac_dialogues) cls.list_of_tac_messages = ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": "some_agent_name"} ), DialogueMessage( TacMessage.Performative.GAME_DATA, { "amount_by_currency_id": {"FET": 1}, "exchange_params_by_currency_id": {"FET": 1.0}, "quantities_by_good_id": {"2": 10}, "utility_params_by_good_id": {"2": 1.0}, "fee_by_currency_id": {"1": 1}, "agent_addr_to_name": { COUNTERPARTY_AGENT_ADDRESS: "some_agent_name" }, "currency_id_to_name": {"1": "FETCH"}, "good_id_to_name": {"2": "Good_1"}, "version_id": "v1", }, ), ) cls.tx_ids = ["tx_1", "tx_2"] cls.terms_1 = Terms( "some_ledger_id", "some_sender_address_1", "some_counterparty_address_1", {"1": 10}, {"2": 5}, "some_nonce_1", fee_by_currency_id={"1": 1}, ) cls.terms_2 = Terms( "some_ledger_id", "some_sender_address_2", "some_counterparty_address_2", {"1": 11}, {"2": 6}, "some_nonce_2", fee_by_currency_id={"1": 2}, ) cls.terms = [cls.terms_1, cls.terms_2] cls.sender_signatures = ["sender_signature_1", "sender_signature_2"] cls.counterparty_signatures = [ "counterparty_signature_1", "counterparty_signature_2", ] cls.txs = OrderedDict( { cls.tx_ids[0]: { "terms": cls.terms[0], "sender_signature": cls.sender_signatures[0], "counterparty_signature": cls.counterparty_signatures[0], }, cls.tx_ids[1]: { "terms": cls.terms[1], "sender_signature": cls.sender_signatures[1], "counterparty_signature": cls.counterparty_signatures[1], }, } ) def test_setup(self): """Test the setup method of the transaction_process behaviour.""" assert self.transaction_process_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act_i(self): """Test the act method of the transaction_process behaviour where phase is not GAME.""" # setup self.game._phase = Phase.PRE_GAME # operation with patch.object(self.logger, "log"): self.transaction_process_behaviour.act() # after assert self.game.phase == Phase.PRE_GAME self.assert_quantity_in_outbox(0) def test_act_ii(self): """Test the act method of the transaction_process behaviour where phase is GAME.""" # setup self.game._phase = Phase.GAME no_tx = len(self.txs) self.skill.skill_context._agent_context._shared_state = { "transactions": self.txs } tac_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_tac_messages, ) self.game._tac_dialogue = tac_dialogue # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_process_behaviour.act() # after self.assert_quantity_in_outbox(no_tx) # _process_transactions count = 0 while count != no_tx: message = self.get_message_from_outbox() mock_logger.assert_any_call( logging.INFO, f"sending transaction {self.tx_ids[count]} to controller, message={message}.", ) has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, transaction_id=self.tx_ids[count], ledger_id=self.terms[count].ledger_id, sender_address=self.terms[count].sender_address, counterparty_address=self.terms[count].counterparty_address, amount_by_currency_id=self.terms[count].amount_by_currency_id, fee_by_currency_id=self.terms[count].fee_by_currency_id, quantities_by_good_id=self.terms[count].quantities_by_good_id, sender_signature=self.sender_signatures[count], counterparty_signature=self.counterparty_signatures[count], nonce=self.terms[count].nonce, ) assert has_attributes, error_str count += 1 def test_process_transactions_tac_dialogue_is_empty(self): """Test the _process_transactions method of the transaction_process behaviour where last message of tac_dialogue is None.""" # setup self.game._phase = Phase.GAME self.skill.skill_context._agent_context._shared_state = { "transactions": self.txs } tac_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_tac_messages, ) tac_dialogue._incoming_messages = [] tac_dialogue._outgoing_messages = [] self.game._tac_dialogue = tac_dialogue # operation with pytest.raises(ValueError, match="No last message available."): self.transaction_process_behaviour.act() def test_process_transactions_invalid_tx(self): """Test the _process_transactions method of the transaction_process behaviour where transactions are None.""" # setup self.game._phase = Phase.GAME self.skill.skill_context._agent_context._shared_state = { "transactions": {self.tx_ids[0]: None, self.tx_ids[1]: None} } tac_dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_tac_messages, ) self.game._tac_dialogue = tac_dialogue # operation with pytest.raises(ValueError, match=f"Tx for id={self.tx_ids[0]} not found."): self.transaction_process_behaviour.act() def test_teardown(self): """Test the teardown method of the transaction_process behaviour.""" assert self.transaction_process_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_tac_participation/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the tac participation skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.state_update.message import StateUpdateMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.dialogues import ( OefSearchDialogue, OefSearchDialogues, StateUpdateDialogue, StateUpdateDialogues, TacDialogue, TacDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue classes of tac participation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_participation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.state_update_dialogues = cast( StateUpdateDialogues, cls._skill.skill_context.state_update_dialogues ) cls.tac_dialogues = cast(TacDialogues, cls._skill.skill_context.tac_dialogues) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query="some_query", ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_state_update_dialogues(self): """Test the StateUpdateDialogues class.""" _, dialogue = self.state_update_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=StateUpdateMessage.Performative.INITIALIZE, exchange_params_by_currency_id={"some_currency_id": 1.0}, utility_params_by_good_id={"some_good_id": 2.0}, amount_by_currency_id={"some_currency_id": 10}, quantities_by_good_id={"some_good_id": 5}, ) assert dialogue.role == StateUpdateDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_tac_dialogues(self): """Test the TacDialogues class.""" _, dialogue = self.tac_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=TacMessage.Performative.REGISTER, agent_name="some_agent_name", ) assert dialogue.role == TacDialogue.Role.PARTICIPANT assert dialogue.self_address == self.skill.skill_context.agent_address ================================================ FILE: tests/test_packages/test_skills/test_tac_participation/test_game.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the models of the tac participation skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch import pytest from aea.exceptions import AEAEnforceError from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.state_update.message import StateUpdateMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.dialogues import ( StateUpdateDialogues, TacDialogues, ) from packages.fetchai.skills.tac_participation.game import Configuration, Game, Phase from tests.conftest import ROOT_DIR class TestConfiguration: """Test Configuration class of tac participation.""" @classmethod def setup(cls): """Setup the test class.""" cls.version_id = "some_version_id" cls.fee_by_currency_id = {"1": 1} cls.agent_addr_to_name = { "agent_address_1": "agent_name_1", "agent_address_2": "agent_name_2", } cls.good_id_to_name = {"3": "good_1", "4": "good_2"} cls.controller_addr = "some_controller_address" cls.configuration = Configuration( cls.version_id, cls.fee_by_currency_id, cls.agent_addr_to_name, cls.good_id_to_name, cls.controller_addr, ) def test_simple_properties(self): """Test the properties of Game class.""" assert self.configuration.version_id == self.version_id assert self.configuration.nb_agents == len(self.agent_addr_to_name) assert self.configuration.nb_goods == len(self.good_id_to_name) assert self.configuration.tx_fee == 1 self.configuration._fee_by_currency_id = {"1": 1, "2": 2} with pytest.raises(AEAEnforceError, match="More than one currency id present!"): assert self.configuration.tx_fee self.configuration._fee_by_currency_id = self.fee_by_currency_id assert self.configuration.fee_by_currency_id == self.fee_by_currency_id assert self.configuration.agent_addr_to_name == self.agent_addr_to_name assert self.configuration.good_id_to_name == self.good_id_to_name assert self.configuration.agent_addresses == list( self.agent_addr_to_name.keys() ) assert self.configuration.agent_names == list(self.agent_addr_to_name.values()) assert self.configuration.good_ids == list(self.good_id_to_name.keys()) assert self.configuration.good_names == list(self.good_id_to_name.values()) assert self.configuration.controller_addr == self.controller_addr def test_check_consistency_succeeds(self): """Test the _check_consistency of Configuration class which succeeds.""" self.configuration._check_consistency() def test_check_consistency_fails_i(self): """Test the _check_consistency of Configuration class which fails on version being None.""" self.configuration._version_id = None with pytest.raises(AEAEnforceError, match="A version id must be set."): assert self.configuration._check_consistency() def test_check_consistency_fails_ii(self): """Test the _check_consistency of Configuration class which fails because _fee_by_currency_id has more than one currencies.""" self.configuration._fee_by_currency_id = {"1": 1, "2": 2} with pytest.raises(AEAEnforceError, match="Tx fee must be non-negative."): assert self.configuration._check_consistency() def test_check_consistency_fails_iii(self): """Test the _check_consistency of Configuration class which fails because tx_fee < 0.""" self.configuration._fee_by_currency_id = {"1": -5} with pytest.raises(AEAEnforceError, match="Tx fee must be non-negative."): assert self.configuration._check_consistency() def test_check_consistency_fails_iv(self): """Test the _check_consistency of Configuration class which fails because number of agents is less than 2.""" incorrect_agent_addr_to_name = {"agent_address_1": "agent_name_1"} with pytest.raises(AEAEnforceError, match="Must have at least two agents."): Configuration( self.version_id, self.fee_by_currency_id, incorrect_agent_addr_to_name, self.good_id_to_name, self.controller_addr, ) def test_check_consistency_fails_v(self): """Test the _check_consistency of Configuration class which fails because number of goods is less than 2.""" incorrect_good_id_to_name = {"3": "good_1"} with pytest.raises(AEAEnforceError, match="Must have at least two goods."): Configuration( self.version_id, self.fee_by_currency_id, self.agent_addr_to_name, incorrect_good_id_to_name, self.controller_addr, ) def test_check_consistency_fails_vi(self): """Test the _check_consistency of Configuration class which fails because number of goods is less than 2.""" self.configuration._agent_addr_to_name = {"agent_address_1": "agent_name_1"} with pytest.raises( AEAEnforceError, match="There must be one address for each agent." ): self.configuration._check_consistency() def test_check_consistency_fails_vii(self): """Test the _check_consistency of Configuration class which fails because number of goods is less than 2.""" incorrect_agent_addr_to_name = { "agent_address_1": "agent_name_1", "agent_address_2": "agent_name_1", } with pytest.raises(AEAEnforceError, match="Agents' names must be unique."): Configuration( self.version_id, self.fee_by_currency_id, incorrect_agent_addr_to_name, self.good_id_to_name, self.controller_addr, ) def test_check_consistency_fails_viii(self): """Test the _check_consistency of Configuration class which fails because number of goods is less than 2.""" self.configuration._good_id_to_name = { "3": "good_1", "4": "good_2", "5": "good_3", } with pytest.raises( AEAEnforceError, match="There must be one id for each good." ): self.configuration._check_consistency() def test_check_consistency_fails_ix(self): """Test the _check_consistency of Configuration class which fails because number of goods is less than 2.""" incorrect_good_id_to_name = {"3": "good_1", "4": "good_1"} with pytest.raises(AEAEnforceError, match="Goods' names must be unique."): Configuration( self.version_id, self.fee_by_currency_id, self.agent_addr_to_name, incorrect_good_id_to_name, self.controller_addr, ) class TestGame(BaseSkillTestCase): """Test Game class of tac participation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_participation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.expected_version_id = "v1" cls.expected_controller_addr = "some_controller_address" cls.search_query = { "search_key": "tac", "search_value": "v1", "constraint_type": "==", } cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.search_radius = 5.0 cls.ledger_id = "some_ledger_id" cls.is_using_contract = False cls.game = Game( expected_version_id=cls.expected_version_id, expected_controller_addr=cls.expected_controller_addr, search_query=cls.search_query, location=cls.location, search_radius=cls.search_radius, ledger_id=cls.ledger_id, is_using_contract=cls.is_using_contract, name="game", skill_context=cls._skill.skill_context, ) def test_simple_properties(self): """Test the properties of Game class.""" assert self.game.ledger_id == self.ledger_id assert self.game.is_using_contract == self.is_using_contract assert self.game.expected_version_id == self.expected_version_id assert self.game.phase == Phase.PRE_GAME with pytest.raises(AEAEnforceError, match="Contract address not set!"): assert self.game.contract_address self.game.contract_address = "some_contract_address" assert self.game.contract_address == "some_contract_address" with pytest.raises(AEAEnforceError, match="Contract address already set!"): self.game.contract_address = "some_other_contract_address" with pytest.raises(AEAEnforceError, match="TacDialogue not set!"): assert self.game.tac_dialogue _, tac_dialogue = cast( TacDialogues, self.skill.skill_context.tac_dialogues ).create( counterparty="some_address", performative=TacMessage.Performative.REGISTER, agent_name="some_agent_name", ) self.game.tac_dialogue = tac_dialogue assert self.game.tac_dialogue == tac_dialogue with pytest.raises(AEAEnforceError, match="TacDialogue already set!"): self.game.tac_dialogue = tac_dialogue with pytest.raises(AEAEnforceError, match="StateUpdateDialogue not set!"): assert self.game.state_update_dialogue _, state_update_dialogue = cast( StateUpdateDialogues, self.skill.skill_context.state_update_dialogues ).create( counterparty="some_address", performative=StateUpdateMessage.Performative.INITIALIZE, exchange_params_by_currency_id={"some_currency_id": 1.0}, utility_params_by_good_id={"some_good_id": 2.0}, amount_by_currency_id={"some_currency_id": 10}, quantities_by_good_id={"some_good_id": 5}, ) self.game.state_update_dialogue = state_update_dialogue assert self.game.state_update_dialogue == state_update_dialogue with pytest.raises(AEAEnforceError, match="StateUpdateDialogue already set!"): self.game.state_update_dialogue = state_update_dialogue assert self.game.expected_controller_addr == self.expected_controller_addr self.game._expected_controller_addr = None with pytest.raises( AEAEnforceError, match="Expected controller address not assigned!" ): assert self.game.expected_controller_addr with pytest.raises(AEAEnforceError, match="Game configuration not assigned!"): assert self.game.conf configuration = Configuration( "some_version_id", {"1": 1}, {"agent_address_1": "agent_name_1", "agent_address_2": "agent_name_2"}, {"3": "good_1", "4": "good_2"}, "some_controller_address", ) self.game._conf = configuration assert self.game.conf == configuration def test_init_succeeds(self): """Test the init method of the Game class which succeeds.""" fee_by_currency_id = {"1": 1} agent_addr_to_name = { "some_address_1": "some_name_1", "some_address_2": "some_name_2", } good_id_to_name = {"2": "good_2", "3": "good_3"} tac_message = cast( TacMessage, self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id={"1": 10}, exchange_params_by_currency_id={"1": 1.0}, quantities_by_good_id={"2": 10, "3": 11}, utility_params_by_good_id={"2": 1.2, "3": 1.1}, fee_by_currency_id=fee_by_currency_id, agent_addr_to_name=agent_addr_to_name, currency_id_to_name={"1": "FETCH"}, good_id_to_name=good_id_to_name, version_id=self.expected_version_id, ), ) self.game.init(tac_message, self.expected_controller_addr) assert self.game.conf is not None assert self.game.conf.version_id == self.expected_version_id assert self.game.conf.fee_by_currency_id == fee_by_currency_id assert self.game.conf.agent_addr_to_name == agent_addr_to_name assert self.game.conf.good_id_to_name == good_id_to_name assert self.game.conf.controller_addr == self.expected_controller_addr def test_init_fails_i(self): """Test the init method of the Game class which fails because performative is NOT GAME_DATA.""" tac_message = cast( TacMessage, self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.REGISTER, agent_name="some_agent_name", ), ) with pytest.raises( AEAEnforceError, match="Wrong TacMessage for initialization of TAC game." ): self.game.init(tac_message, self.expected_controller_addr) def test_init_fails_ii(self): """Test the init method of the Game class which fails because controller address is incorrect.""" fee_by_currency_id = {"1": 1} agent_addr_to_name = { "some_address_1": "some_name_1", "some_address_2": "some_name_2", } good_id_to_name = {"2": "good_2", "3": "good_3"} incorrect_controller_addr = "some_other_controller_address" tac_message = cast( TacMessage, self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id={"1": 10}, exchange_params_by_currency_id={"1": 1.0}, quantities_by_good_id={"2": 10, "3": 11}, utility_params_by_good_id={"2": 1.2, "3": 1.1}, fee_by_currency_id=fee_by_currency_id, agent_addr_to_name=agent_addr_to_name, currency_id_to_name={"1": "FETCH"}, good_id_to_name=good_id_to_name, version_id=self.expected_version_id, ), ) with pytest.raises( AEAEnforceError, match="TacMessage from unexpected controller." ): self.game.init(tac_message, incorrect_controller_addr) def test_init_fails_iii(self): """Test the init method of the Game class which fails because version id is incorrect.""" fee_by_currency_id = {"1": 1} agent_addr_to_name = { "some_address_1": "some_name_1", "some_address_2": "some_name_2", } good_id_to_name = {"2": "good_2", "3": "good_3"} incorrect_version_id = "some_other_version_id" tac_message = cast( TacMessage, self.build_incoming_message( message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id={"1": 10}, exchange_params_by_currency_id={"1": 1.0}, quantities_by_good_id={"2": 10, "3": 11}, utility_params_by_good_id={"2": 1.2, "3": 1.1}, fee_by_currency_id=fee_by_currency_id, agent_addr_to_name=agent_addr_to_name, currency_id_to_name={"1": "FETCH"}, good_id_to_name=good_id_to_name, version_id=incorrect_version_id, ), ) with pytest.raises(AEAEnforceError, match="TacMessage for unexpected game."): self.game.init(tac_message, self.expected_controller_addr) def test_update_expected_controller_addr(self): """Test the update_expected_controller_addr method of the Game class.""" new_contract_address = "some_different_controller_address" with patch.object(self.skill.skill_context.logger, "log") as mock_logger: self.game.update_expected_controller_addr(new_contract_address) mock_logger.assert_any_call( logging.WARNING, "TAKE CARE! Circumventing controller identity check! For added security provide the expected controller key as an argument to the Game instance " "and check against it.", ) assert self.game.expected_controller_addr == new_contract_address def test_update_game_phase(self): """Test the update_game_phase method of the Game class.""" assert self.game.phase == Phase.PRE_GAME self.game.update_game_phase(Phase.GAME) assert self.game.phase == Phase.GAME def test_get_game_query(self): """Test the get_game_query method of the Game class.""" expected_location = Location( latitude=self.location["latitude"], longitude=self.location["longitude"] ) expected_query = Query( [ Constraint( "location", ConstraintType("distance", (expected_location, self.search_radius)), ), Constraint( self.search_query["search_key"], ConstraintType( self.search_query["constraint_type"], self.search_query["search_value"], ), ), ], ) actual_query = self.game.get_game_query() assert actual_query == expected_query ================================================ FILE: tests/test_packages/test_skills/test_tac_participation/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the tac participation skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch import pytest from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.state_update.message import StateUpdateMessage from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.dialogues import ( OefSearchDialogues, StateUpdateDialogue, StateUpdateDialogues, TacDialogues, ) from packages.fetchai.skills.tac_participation.game import Game, Phase from packages.fetchai.skills.tac_participation.handlers import ( OefSearchHandler, TacHandler, ) from tests.conftest import ROOT_DIR class TestOefSearchHandler(BaseSkillTestCase): """Test oef search handler of tac participation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_participation") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef ) cls.game = cast(Game, cls._skill.skill_context.game) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.list_of_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} ), ) cls.controller_address = "some_controller_address" def test_setup(self): """Test the setup method of the oef handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, to=str(self.skill.skill_context.skill_id), oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"received OEF Search error: dialogue_reference={oef_dialogue.dialogue_label.dialogue_reference}, oef_error_operation={incoming_message.oef_error_operation}", ) def test_on_search_result(self): """Test the _on_search_result method of the oef handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), agents=(self.controller_address,), ), ) self.game._phase = Phase.PRE_GAME # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) # _on_search_result mock_logger.assert_any_call( logging.DEBUG, f"on search result: dialogue_reference={oef_dialogue.dialogue_label.dialogue_reference} agents={incoming_message.agents}", ) # _on_controller_search_result mock_logger.assert_any_call( logging.INFO, "found the TAC controller. Registering...", ) # _register_to_tac assert self.game._expected_controller_addr == self.controller_address assert self.game.phase == Phase.GAME_REGISTRATION has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=TacMessage, performative=TacMessage.Performative.REGISTER, to=self.controller_address, sender=self.skill.skill_context.agent_address, agent_name=self.skill.skill_context.agent_name, ) assert has_attributes, error_str assert self.game._tac_dialogue is not None assert self.skill.skill_context.behaviours.tac_search.is_active is False assert ( self.skill.skill_context.shared_state.get("tac_version_id", None) == self.game.expected_version_id ) def test_on_controller_search_result_i(self): """Test the _on_controller_search_result method of the oef handler where phase is not PRE_GAME.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), agents=("agent_1", "agent_2"), ), ) self.game._phase = Phase.GAME # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after # _on_search_result mock_logger.assert_any_call( logging.DEBUG, f"on search result: dialogue_reference={oef_dialogue.dialogue_label.dialogue_reference} agents={incoming_message.agents}", ) # _on_controller_search_result mock_logger.assert_any_call( logging.DEBUG, "ignoring controller search result, the agent is already competing.", ) def test_on_controller_search_result_ii(self): """Test the _on_controller_search_result method of the oef handler where list of agent addresses is empty.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), agents=tuple(), ), ) self.game._phase = Phase.PRE_GAME # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after # _on_search_result mock_logger.assert_any_call( logging.DEBUG, f"on search result: dialogue_reference={oef_dialogue.dialogue_label.dialogue_reference} agents={incoming_message.agents}", ) # _on_controller_search_result mock_logger.assert_any_call( logging.INFO, "couldn't find the TAC controller. Retrying...", ) def test_on_controller_search_result_more_than_one_agents(self): """Test the _on_controller_search_result method of the oef handler where list of agents contains more than one agents.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, to=str(self.skill.skill_context.skill_id), agents=("agent_1", "agent_2"), ), ) self.game._phase = Phase.PRE_GAME # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after # _on_search_result mock_logger.assert_any_call( logging.DEBUG, f"on search result: dialogue_reference={oef_dialogue.dialogue_label.dialogue_reference} agents={incoming_message.agents}", ) # _on_controller_search_result mock_logger.assert_any_call( logging.WARNING, "found more than one TAC controller. Retrying...", ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef handler.""" # setup invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=("1", ""), performative=invalid_performative, to=str(self.skill.skill_context.skill_id), service_description="some_service_description", ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestTacHandler(BaseSkillTestCase): """Test tac handler of tac participation.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "tac_participation") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.tac_handler = cast(TacHandler, cls._skill.skill_context.handlers.tac) cls.tac_dialogues = cast(TacDialogues, cls._skill.skill_context.tac_dialogues) cls.state_update_dialogues = cast( StateUpdateDialogues, cls._skill.skill_context.state_update_dialogues ) cls.game = cast(Game, cls._skill.skill_context.game) cls.logger = cls.tac_handler.context.logger cls.agent_name = "some_agent_name" cls.amount_by_currency_id = {"1": 10} cls.exchange_params_by_currency_id = {"1": 1.0} cls.quantities_by_good_id = {"2": 10} cls.utility_params_by_good_id = {"2": 1.0} cls.fee_by_currency_id = {"1": 1} cls.agent_addr_to_name = {COUNTERPARTY_AGENT_ADDRESS: "some_name"} cls.currency_id_to_name = {"1": "FETCH"} cls.good_id_to_name = {"2": "Good_1"} cls.version_id = "v1" cls.list_of_messages = ( DialogueMessage( TacMessage.Performative.REGISTER, {"agent_name": cls.agent_name}, True ), DialogueMessage( TacMessage.Performative.GAME_DATA, { "amount_by_currency_id": cls.amount_by_currency_id, "exchange_params_by_currency_id": cls.exchange_params_by_currency_id, "quantities_by_good_id": cls.quantities_by_good_id, "utility_params_by_good_id": cls.utility_params_by_good_id, "fee_by_currency_id": cls.fee_by_currency_id, "agent_addr_to_name": cls.agent_addr_to_name, "currency_id_to_name": cls.currency_id_to_name, "good_id_to_name": cls.good_id_to_name, "version_id": cls.version_id, }, ), DialogueMessage( TacMessage.Performative.TRANSACTION, { "transaction_id": "some_transaction_id", "ledger_id": "some_ledger_id", "sender_address": "some_sender_address", "counterparty_address": "some_counterparty_address", "amount_by_currency_id": {"1": 5}, "fee_by_currency_id": {"1": 1}, "quantities_by_good_id": {"2": -5}, "nonce": "some_nonce", "sender_signature": "some_sender_signature", "counterparty_signature": "some_counterparty_signature", }, ), ) cls.list_of_state_update_messages = ( DialogueMessage( StateUpdateMessage.Performative.INITIALIZE, { "amount_by_currency_id": cls.amount_by_currency_id, "quantities_by_good_id": cls.quantities_by_good_id, }, ), ) cls.game._expected_controller_addr = COUNTERPARTY_AGENT_ADDRESS def test_setup(self): """Test the setup method of the tac handler.""" assert self.tac_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_sender_not_equal_to_expected_controller(self): """Test the handle method of the tac handler where message sender is NOT equal to the expected controller.""" # setup self.game._expected_controller_addr = "some_different_controller_address" dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.AGENT_ADDR_ALREADY_REGISTERED, ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises( ValueError, match="The sender of the message is not the controller agent we registered with.", ): self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.DEBUG, f"handling controller response. performative={incoming_message.performative}", ) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the tac handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=TacMessage, dialogue_reference=incorrect_dialogue_reference, performative=TacMessage.Performative.CANCELLED, ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"received invalid tac message={incoming_message}, unidentified dialogue.", ) def test_on_tac_error_code_i(self): """Test the _on_tac_error method of the tac handler where error_code is NOT TRANSACTION_NOT_VALID.""" # setup dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) error_code = TacMessage.ErrorCode.AGENT_ADDR_ALREADY_REGISTERED incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, error_code=error_code, ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.DEBUG, f"received error from the controller in dialogue={dialogue}. error_msg={TacMessage.ErrorCode.to_msg(error_code.value)}", ) def test_on_tac_error_code_ii(self): """Test the _on_tac_error method of the tac handler where error_code is TRANSACTION_NOT_VALID.""" # setup dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) tx_id = "some_tx_id" error_code = TacMessage.ErrorCode.TRANSACTION_NOT_VALID incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.TAC_ERROR, error_code=error_code, info={"transaction_id": tx_id}, ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.DEBUG, f"received error from the controller in dialogue={dialogue}. error_msg={TacMessage.ErrorCode.to_msg(error_code.value)}", ) mock_logger.assert_any_call( logging.WARNING, f"received error on transaction id: {tx_id[-10:]}", ) def test_on_start_i(self): """Test the _on_start method of the tac handler.""" # setup self.game._phase = Phase.GAME_REGISTRATION self.game._is_using_contract = False dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id=self.amount_by_currency_id, exchange_params_by_currency_id=self.exchange_params_by_currency_id, quantities_by_good_id=self.quantities_by_good_id, utility_params_by_good_id=self.utility_params_by_good_id, fee_by_currency_id=self.fee_by_currency_id, agent_addr_to_name=self.agent_addr_to_name, currency_id_to_name=self.currency_id_to_name, good_id_to_name=self.good_id_to_name, version_id=self.version_id, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(self.game, "init"): self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received start event from the controller. Starting to compete...", ) self.assert_quantity_in_decision_making_queue(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_decision_maker_inbox(), message_type=StateUpdateMessage, performative=StateUpdateMessage.Performative.INITIALIZE, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), amount_by_currency_id=incoming_message.amount_by_currency_id, quantities_by_good_id=incoming_message.quantities_by_good_id, exchange_params_by_currency_id=incoming_message.exchange_params_by_currency_id, utility_params_by_good_id=incoming_message.utility_params_by_good_id, ) assert has_attributes, error_str assert ( self.skill.skill_context.shared_state["fee_by_currency_id"] == incoming_message.fee_by_currency_id ) assert self.game.state_update_dialogue is not None def test_on_start_ii(self): """Test the _on_start method of the tac handler where phase is NOT GAME_REGISTRATION.""" # setup self.game._phase = Phase.PRE_GAME self.game._is_using_contract = False dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id=self.amount_by_currency_id, exchange_params_by_currency_id=self.exchange_params_by_currency_id, quantities_by_good_id=self.quantities_by_good_id, utility_params_by_good_id=self.utility_params_by_good_id, fee_by_currency_id=self.fee_by_currency_id, agent_addr_to_name=self.agent_addr_to_name, currency_id_to_name=self.currency_id_to_name, good_id_to_name=self.good_id_to_name, version_id=self.version_id, ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"we do not expect a start message in game phase={self.game.phase.value}", ) def test_on_start_iii(self): """Test the _on_start method of the tac handler where game uses contract.""" # setup self.game._phase = Phase.GAME_REGISTRATION self.game._is_using_contract = True dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) contract_address = "some_contract_address" incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id=self.amount_by_currency_id, exchange_params_by_currency_id=self.exchange_params_by_currency_id, quantities_by_good_id=self.quantities_by_good_id, utility_params_by_good_id=self.utility_params_by_good_id, fee_by_currency_id=self.fee_by_currency_id, agent_addr_to_name=self.agent_addr_to_name, currency_id_to_name=self.currency_id_to_name, good_id_to_name=self.good_id_to_name, version_id=self.version_id, info={"contract_address": contract_address}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(self.game, "init"): with patch.object( self.tac_handler, "_update_ownership_and_preferences" ) as mocked_uoap: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received start event from the controller. Starting to compete...", ) assert self.game.contract_address == contract_address assert ( self.skill.skill_context.shared_state["erc1155_contract_address"] == contract_address ) mock_logger.assert_any_call( logging.INFO, f"received a contract address: {contract_address}", ) mocked_uoap.assert_called_once() def test_on_start_iv(self): """Test the _on_start method of the tac handler where game uses contract.""" # setup self.game._phase = Phase.GAME_REGISTRATION self.game._is_using_contract = True dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id=self.amount_by_currency_id, exchange_params_by_currency_id=self.exchange_params_by_currency_id, quantities_by_good_id=self.quantities_by_good_id, utility_params_by_good_id=self.utility_params_by_good_id, fee_by_currency_id=self.fee_by_currency_id, agent_addr_to_name=self.agent_addr_to_name, currency_id_to_name=self.currency_id_to_name, good_id_to_name=self.good_id_to_name, version_id=self.version_id, info={"contract_address": None}, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with patch.object(self.game, "init"): with patch.object( self.tac_handler, "_update_ownership_and_preferences" ) as mocked_uoap: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received start event from the controller. Starting to compete...", ) mock_logger.assert_any_call( logging.WARNING, "did not receive a contract address!", ) mocked_uoap.assert_not_called() def test_on_cancelled_i(self): """Test the _on_cancelled method of the tac handler where phase is GAME_REGISTRATION.""" # setup self.game._phase = Phase.GAME_REGISTRATION dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.CANCELLED, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received cancellation from the controller.", ) assert self.game.phase == Phase.POST_GAME assert self.skill.skill_context.is_active is False assert self.skill.skill_context.shared_state["is_game_finished"] is True def test_on_cancelled_ii(self): """Test the _on_cancelled method of the tac handler where phase is GAME.""" # setup self.game._phase = Phase.GAME dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.CANCELLED, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, "received cancellation from the controller.", ) assert self.game.phase == Phase.POST_GAME assert self.skill.skill_context.is_active is False assert self.skill.skill_context.shared_state["is_game_finished"] is True def test_on_cancelled_iii(self): """Test the _on_cancelled method of the tac handler where phase is NOT GAME_REGISTRATION nor GAME.""" # setup self.game._phase = Phase.PRE_GAME dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:1] ) incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.CANCELLED, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"we do not expect a message in game phase={self.game.phase.value}, received msg={incoming_message}", ) def test_on_transaction_confirmed_i(self): """Test the _on_transaction_confirmed method of the tac handler.""" # setup self.game._phase = Phase.GAME state_update_dialogue = cast( StateUpdateDialogue, self.prepare_skill_dialogue( dialogues=self.state_update_dialogues, messages=self.list_of_state_update_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) self.game._state_update_dialogue = state_update_dialogue dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:3] ) transaction_id = "some_transaction_id" incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, transaction_id=transaction_id, amount_by_currency_id=self.amount_by_currency_id, quantities_by_good_id=self.quantities_by_good_id, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received transaction confirmation from the controller: transaction_id={transaction_id}", ) self.assert_quantity_in_decision_making_queue(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_decision_maker_inbox(), message_type=StateUpdateMessage, performative=StateUpdateMessage.Performative.APPLY, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), amount_by_currency_id=incoming_message.amount_by_currency_id, quantities_by_good_id=incoming_message.quantities_by_good_id, ) assert has_attributes, error_str assert ( incoming_message.transaction_id in self.skill.skill_context.shared_state["confirmed_tx_ids"] ) def test_on_transaction_confirmed_ii(self): """Test the _on_transaction_confirmed method of the tac handler where phase is not GAME.""" # setup self.game._phase = Phase.PRE_GAME dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:3] ) transaction_id = "some_transaction_id" incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, transaction_id=transaction_id, amount_by_currency_id=self.amount_by_currency_id, quantities_by_good_id=self.quantities_by_good_id, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"we do not expect a transaction in game phase={self.game.phase.value}, received msg={incoming_message}", ) def test_on_transaction_confirmed_iii(self): """Test the _on_transaction_confirmed method of the tac handler where state_update dialogue is empty.""" # setup self.game._phase = Phase.GAME state_update_dialogue = cast( StateUpdateDialogue, self.prepare_skill_dialogue( dialogues=self.state_update_dialogues, messages=self.list_of_state_update_messages[:1], counterparty=self.skill.skill_context.decision_maker_address, ), ) state_update_dialogue._incoming_messages = [] state_update_dialogue._outgoing_messages = [] self.game._state_update_dialogue = state_update_dialogue dialogue = self.prepare_skill_dialogue( self.tac_dialogues, self.list_of_messages[:3] ) transaction_id = "some_transaction_id" incoming_message = cast( TacMessage, self.build_incoming_message_for_skill_dialogue( dialogue=dialogue, message_type=TacMessage, performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, transaction_id=transaction_id, amount_by_currency_id=self.amount_by_currency_id, quantities_by_good_id=self.quantities_by_good_id, ), ) # operation with patch.object(self.logger, "log") as mock_logger: with pytest.raises(ValueError, match="Could not retrieve last message."): self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received transaction confirmation from the controller: transaction_id={transaction_id}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the tac handler.""" # setup tac_dialogue = self.prepare_skill_dialogue( dialogues=self.tac_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=tac_dialogue, performative=TacMessage.Performative.UNREGISTER, sender=COUNTERPARTY_AGENT_ADDRESS, ) # operation with patch.object(self.logger, "log") as mock_logger: self.tac_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle tac message of performative={incoming_message.performative} in dialogue={tac_dialogue}.", ) def test_teardown(self): """Test the teardown method of the tac handler.""" assert self.tac_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages/test_skills/test_thermometer/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/thermometer dir.""" ================================================ FILE: tests/test_packages/test_skills/test_thermometer/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the thermometer skill.""" import logging from pathlib import Path from unittest.mock import Mock, patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.thermometer.strategy import MAX_RETRIES, Strategy from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of thermometer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "thermometer") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.strategy = Strategy( name="strategy", skill_context=cls._skill.skill_context, ) cls.logger = cls._skill.skill_context.logger def test_collect_from_data_source_i(self): """Test the collect_from_data_source method of the Strategy class where max retires result is successful.""" # setup temp = 24 results = [{"internal temperature": temp, "some_other_info": "some_info"}] expected_degree = {"thermometer_data": str(temp)} temper_mock = Mock() temper_mock.read.return_value = results # operation with patch("temper.Temper.__init__", return_value=None) as mock_init: with patch("temper.Temper.read", return_value=results) as mock_read: with patch.object(self.logger, "log") as mock_logger: degree = self.strategy.collect_from_data_source() # after mock_init.assert_called_once() mock_read.assert_called_once() mock_logger.assert_not_called() assert degree == expected_degree def test_collect_from_data_source_ii(self): """Test the collect_from_data_source method of the Strategy class where max retires result is successful.""" # setup temp = 24 results = [{"NOT internal temperature": temp, "some_other_info": "some_info"}] temper_mock = Mock() temper_mock.read.return_value = results # operation with patch("temper.Temper.__init__", return_value=None) as mock_init: with patch("temper.Temper.read", return_value=results) as mock_read: with patch.object(self.logger, "log") as mock_logger: degree = self.strategy.collect_from_data_source() # after mock_init.assert_called_once() assert mock_read.call_count == MAX_RETRIES mock_logger.assert_any_call( logging.DEBUG, "Couldn't read the sensor I am re-trying." ) assert degree == {} ================================================ FILE: tests/test_packages/test_skills_integration/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the integration tests of the packages/skills dir.""" ================================================ FILE: tests/test_packages/test_skills_integration/test_aries_demo.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This package contains integration test for the aries_alice skill and the aries_faber skill.""" import random import string import subprocess # nosec from random import randint # nosec import pytest from aea.test_tools.test_cases import AEATestCaseMany from packages.fetchai.connections.p2p_libp2p.connection import ( LIBP2P_SUCCESS_MESSAGE, P2PLibp2pConnection, ) from packages.fetchai.skills.aries_alice import PUBLIC_ID as ALICE_SKILL_PUBLIC_ID from packages.fetchai.skills.aries_faber import PUBLIC_ID as FABER_SKILL_PUBLIC_ID def _rand_seed(): return "".join( random.choice(string.ascii_uppercase + string.digits) # nosec for _ in range(32) ) README = """ To start with test: `apt install libindy` to instal indy library binary file. required by acapy `pip install aries-cloudagent[indy]` acaPY is required ## VON Network In the first terminal move to the `von-network` directory and run an instance of `von-network` locally in docker. This tutorial has information on starting (and stopping) the network locally. ``` bash ./manage build ./manage start 172.17.0.1,172.17.0.1,172.17.0.1,172.17.0.1 --logs ``` 172.17.0.1 - is ip address of the docker0 network interface, can be used any address assigned to the host except 127.0.0.1 UPDATE `ACAPY_HOST` at line 76 to the same host selected for von network change `SKIP_TEST = True` to `SKIP_TEST = False` """ # set to False to run it manually SKIP_TEST = True @pytest.mark.unstable @pytest.mark.integration class TestAriesSkillsDemo(AEATestCaseMany): """Test integrated aries skills.""" ACAPY_HOST = "172.17.0.1" capture_log = True alice_seed: str bob_seed: str faber_seed: str @classmethod def get_port(cls) -> int: """Get next tcp port number.""" cls.port += 1 # type: ignore return cls.port # type: ignore @classmethod def start_acapy( cls, name: str, base_port: int, seed: str, endpoint_host: str, genesis_url: str ) -> subprocess.Popen: """Start acapy process.""" return subprocess.Popen( # nosec [ "python3", "-m", "aries_cloudagent", "start", "--auto-ping-connection", "--auto-respond-messages", "--auto-store-credential", "--auto-accept-invites", "--auto-accept-requests", "--auto-respond-credential-proposal", "--auto-respond-credential-offer", "--auto-respond-credential-request", "--auto-respond-presentation-proposal", "--auto-respond-presentation-request", # "--debug-credentials", # "--debug-presentations", # "--debug-connections", "--admin", "127.0.0.1", str(base_port + 1), "--admin-insecure-mode", "--inbound-transport", "http", "0.0.0.0", str(base_port), "--outbound-transp", "http", "--webhook-url", f"http://127.0.0.1:{str(base_port+2)}/webhooks", "-e", f"http://{endpoint_host}:{base_port}", "--genesis-url", genesis_url, "--wallet-type", "indy", "--wallet-name", name + str(randint(10000000, 999999999999)), # nosec "--wallet-key", "walkey", "--seed", seed, # type: ignore "--recreate-wallet", "--wallet-local-did", "--auto-provision", "--label", name, ] ) @classmethod def setup_class(cls) -> None: """Setup test case.""" if SKIP_TEST: cls._is_teardown_class_called = True # fix for teardown check fixture raise pytest.skip("test skipped, check code to enable it") check_acapy = subprocess.run("aca-py", shell=True, capture_output=True) # nosec assert b"usage: aca-py" in check_acapy.stdout, "aca-py is not installed!" cls.port = 10001 # type: ignore super(TestAriesSkillsDemo, cls).setup_class() acapy_host = cls.ACAPY_HOST cls.alice = "alice" # type: ignore cls.soef_id = "intro_aries" + str( # type: ignore randint(1000000, 99999999999999) # nosec ) cls.alice_seed = _rand_seed() # type: ignore cls.bob = "bob" # type: ignore cls.bob_seed = _rand_seed() # type: ignore cls.faber = "faber" # type: ignore cls.faber_seed = _rand_seed() # type: ignore cls.controller = "controller" # type: ignore cls.fetch_agent("fetchai/aries_alice", cls.alice, is_local=True) # type: ignore cls.fetch_agent("fetchai/aries_alice", cls.bob, is_local=True) # type: ignore cls.fetch_agent("fetchai/aries_faber", cls.faber, is_local=True) # type: ignore cls.create_agents( cls.controller, # type: ignore ) cls.set_agent_context(cls.controller) # type: ignore cls.add_item("connection", "fetchai/p2p_libp2p") addr = f"127.0.0.1:{cls.get_port()}" p2p_config = { # type: ignore "delegate_uri": None, # f"127.0.0.1:{cls.get_port()}", "entry_peers": [], "local_uri": addr, "public_uri": addr, } cls.nested_set_config( "vendor.fetchai.connections.p2p_libp2p.config", p2p_config ) cls.generate_private_key("fetchai", "fetchai.key") cls.add_private_key("fetchai", "fetchai.key") cls.add_private_key("fetchai", "fetchai.key", connection=True) cls.run_cli_command("build", cwd=cls._get_cwd()) cls.run_cli_command("issue-certificates", cwd=cls._get_cwd()) r = cls.run_cli_command( "get-multiaddress", "fetchai", "-c", "-i", str(P2PLibp2pConnection.connection_id), "-u", "public_uri", cwd=cls._get_cwd(), ) peer_addr = r.stdout.strip() for agent_name in [cls.alice, cls.bob, cls.faber]: # type: ignore cls.set_agent_context(agent_name) p2p_config = { "delegate_uri": None, # f"127.0.0.1:{cls.get_port()}", "entry_peers": [peer_addr], "local_uri": f"127.0.0.1:{cls.get_port()}", "public_uri": None, } cls.generate_private_key("fetchai", "fetchai.key") cls.add_private_key("fetchai", "fetchai.key") cls.add_private_key( "fetchai", "fetchai.key", connection=True, ) cls.nested_set_config( "vendor.fetchai.connections.p2p_libp2p.config", p2p_config ) cls.run_cli_command("build", cwd=cls._get_cwd()) cls.run_cli_command("issue-certificates", cwd=cls._get_cwd()) cls.set_agent_context(cls.alice) # type: ignore cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.seed", cls.alice_seed, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.service_data.value", cls.soef_id, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.search_query.search_value", cls.soef_id, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.admin_host", "127.0.0.1", ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.admin_port", "8031", "int", ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_port", "8032", "int" ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_address", "127.0.0.1" ) cls.set_config( "vendor.fetchai.connections.webhook.config.target_skill_id", str(ALICE_SKILL_PUBLIC_ID), ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_url_path", "/webhooks/topic/{topic}/", ) cls.set_agent_context(cls.bob) # type: ignore cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.seed", cls.bob_seed, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.service_data.value", cls.soef_id, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.search_query.search_value", cls.soef_id, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.admin_host", "127.0.0.1", ) cls.set_config( "vendor.fetchai.skills.aries_alice.models.strategy.args.admin_port", "8041", "int", ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_port", "8042", "int" ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_address", "127.0.0.1" ) cls.set_config( "vendor.fetchai.connections.webhook.config.target_skill_id", str(ALICE_SKILL_PUBLIC_ID), ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_url_path", "/webhooks/topic/{topic}/", ) cls.set_agent_context(cls.faber) # type: ignore cls.set_config( "vendor.fetchai.skills.aries_faber.models.strategy.args.seed", cls.faber_seed, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_faber.models.strategy.args.search_query.search_value", cls.soef_id, # type: ignore ) cls.set_config( "vendor.fetchai.skills.aries_faber.models.strategy.args.admin_host", "127.0.0.1", ) cls.set_config( "vendor.fetchai.skills.aries_faber.models.strategy.args.admin_port", "8021", "int", ) cls.set_config( "vendor.fetchai.connections.webhook.config.target_skill_id", str(FABER_SKILL_PUBLIC_ID), ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_port", "8022", "int" ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_address", "127.0.0.1" ) cls.set_config( "vendor.fetchai.connections.webhook.config.webhook_url_path", "/webhooks/topic/{topic}/", ) cls.extra_processes = [ # type: ignore cls.start_acapy( "alice", 8030, cls.alice_seed, acapy_host, "http://localhost:9000/genesis", ), cls.start_acapy( "bob", 8040, cls.bob_seed, acapy_host, "http://localhost:9000/genesis", ), cls.start_acapy( "faber", 8020, cls.faber_seed, acapy_host, "http://localhost:9000/genesis", ), ] def test_alice_faber_demo(self): """Run demo test.""" self.set_agent_context(self.controller) controller_process = self.run_agent() self.extra_processes.append(controller_process) check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( controller_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in controller output.".format(missing_strings) self.set_agent_context(self.faber) faber_process = self.run_agent() self.extra_processes.append(faber_process) self.set_agent_context(self.alice) alice_process = self.run_agent() self.extra_processes.append(alice_process) self.set_agent_context(self.bob) bob_process = self.run_agent() self.extra_processes.append(bob_process) missing_strings = self.missing_from_output( faber_process, ["Connected to alice", "Connected to bob"], timeout=80, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in faber output.".format(missing_strings) missing_strings = self.missing_from_output( alice_process, ["Connected to faber", "Got credentials proof from bob"], timeout=80, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in alice output.".format(missing_strings) missing_strings = self.missing_from_output( bob_process, ["Connected to faber", "Got credentials proof from alice"], timeout=80, is_terminating=False, ) assert missing_strings == [], "Strings {} didn't appear in bob output.".format( missing_strings ) @classmethod def teardown_class(cls) -> None: """Tear down test case.""" super(TestAriesSkillsDemo, cls).teardown_class() for proc in cls.extra_processes: # type: ignore proc.kill() proc.wait(10) if __name__ == "__main__": pytest.main([__file__]) ================================================ FILE: tests/test_packages/test_skills_integration/test_carpark.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the weather skills.""" from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, wait_for_localhost_ports_to_close, ) @pytest.mark.integration class TestCarPark(AEATestCaseManyFlaky): """Test that carpark skills work.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_carpark(self): """Run the weather skills sequence.""" carpark_aea_name = "my_carpark_aea" carpark_client_aea_name = "my_carpark_client_aea" self.create_agents(carpark_aea_name, carpark_client_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # Setup agent one self.set_agent_context(carpark_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/carpark_detection:0.27.6") setting_path = ( "vendor.fetchai.skills.carpark_detection.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) # replace location setting_path = ( "vendor.fetchai.skills.carpark_detection.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # Setup agent two self.set_agent_context(carpark_client_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/carpark_client:0.27.6") setting_path = ( "vendor.fetchai.skills.carpark_client.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.carpark_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # Fire the sub-processes and the threads. self.set_agent_context(carpark_aea_name) carpark_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_aea output.".format(missing_strings) self.set_agent_context(carpark_client_aea_name) carpark_client_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_client_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_client_aea output.".format( missing_strings ) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( carpark_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( carpark_client_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_client_aea output.".format( missing_strings ) self.terminate_agents(carpark_aea_process, carpark_client_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) @pytest.mark.integration class TestCarParkFetchaiLedger(AEATestCaseManyFlaky): """Test that carpark skills work.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_carpark(self): """Run the weather skills sequence.""" carpark_aea_name = "my_carpark_aea" carpark_client_aea_name = "my_carpark_client_aea" self.create_agents(carpark_aea_name, carpark_client_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # Setup agent one self.set_agent_context(carpark_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/carpark_detection:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/car_detector:0.32.5", carpark_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) # replace location setting_path = ( "vendor.fetchai.skills.carpark_detection.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # Setup agent two self.set_agent_context(carpark_client_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/carpark_client:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/car_data_buyer:0.33.5", carpark_client_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.carpark_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # Fire the sub-processes and the threads. self.set_agent_context(carpark_aea_name) carpark_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_aea output.".format(missing_strings) self.set_agent_context(carpark_client_aea_name) carpark_client_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_client_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_client_aea output.".format( missing_strings ) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "checking whether transaction=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( carpark_aea_process, check_strings, timeout=120, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "requesting transfer transaction from ledger api for message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( carpark_client_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in carpark_client_aea output.".format( missing_strings ) self.terminate_agents(carpark_aea_process, carpark_client_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_packages/test_skills_integration/test_coin_price.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the coin price skill.""" import time from typing import Dict import pytest from aea.helpers import http_requests as requests from aea.test_tools.test_cases import AEATestCaseEmpty from tests.common.utils import wait_for_condition def parse_prometheus_output(prom_data: bytes) -> Dict[str, float]: """Convert prometheus text output to a dict of {"metric": value}""" metrics = {} for line in prom_data.decode().splitlines(): tokens = line.split() if tokens[0] != "#": metrics.update({tokens[0]: float(tokens[1])}) return metrics @pytest.mark.integration class TestCoinPriceSkill(AEATestCaseEmpty): """Test that coin price skill works.""" def test_coin_price(self): """Run the coin price skill sequence.""" coin_price_feed_aea_name = self.agent_name self.generate_private_key() self.add_private_key() self.add_item("connection", "fetchai/http_client:0.24.6") self.add_item("connection", "fetchai/http_server:0.23.6") self.add_item("connection", "fetchai/prometheus:0.9.6") self.add_item("skill", "fetchai/advanced_data_request:0.7.6") self.set_config("agent.default_connection", "fetchai/http_server:0.23.6") default_routing = { "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", "fetchai/prometheus:1.1.7": "fetchai/prometheus:0.9.6", } setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) # set 'api spec path' *after* comparison with fetched agent. self.set_config( "vendor.fetchai.connections.http_server.config.api_spec_path", "vendor/fetchai/skills/advanced_data_request/api_spec.yaml", ) self.set_config( "vendor.fetchai.connections.http_server.config.target_skill_id", "fetchai/advanced_data_request:0.7.6", ) self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.use_http_server", True, type_="bool", ) self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url", "https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd", type_="str", ) self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs", '[{"name": "price", "json_path": "fetch-ai.usd"}]', type_="list", ) diff = self.difference_to_fetched_agent( "fetchai/coin_price_feed:0.15.5", coin_price_feed_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) self.run_install() process = self.run_agent() is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" time.sleep(6) # we wait a bit longer than the tick rate of the behaviour def wait(): response = requests.get("http://127.0.0.1:8000/data") if response.status_code != 200: return False coin_price = response.json() return "price" in coin_price wait_for_condition( wait, timeout=10, period=0.1, error_msg="Response does not contain 'price'" ) response = requests.get("http://127.0.0.1:8000") assert response.status_code == 404 assert response.content == b"", "Get request should not work without valid path" response = requests.post("http://127.0.0.1:8000/data") assert response.status_code == 404 assert response.content == b"", "Post not allowed" # test prometheus metrics prom_response = requests.get("http://127.0.0.1:9090/metrics") metrics = parse_prometheus_output(prom_response.content) assert metrics["num_retrievals"] > 0.0, "num_retrievals metric not updated" assert metrics["num_requests"] == 1.0, "num_requests metric not equal to 1" self.terminate_agents() assert ( self.is_successfully_terminated() ), "Http echo agent wasn't successfully terminated." ================================================ FILE: tests/test_packages/test_skills_integration/test_erc1155.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the generic buyer and seller skills.""" import json from random import uniform import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( ETHEREUM_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, FUNDED_ETH_PRIVATE_KEY_2, FUNDED_ETH_PRIVATE_KEY_3, MAX_FLAKY_RERUNS_ETH, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, UseGanache, wait_for_localhost_ports_to_close, ) @pytest.mark.integration class TestERCSkillsEthereumLedger(AEATestCaseManyFlaky, UseGanache): """Test that erc1155 skills work.""" @pytest.mark.integration @pytest.mark.ledger @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_ETH) # cause possible network issues def test_generic(self): """Run the generic skills sequence.""" deploy_aea_name = "deploy_aea" client_aea_name = "client_aea" self.create_agents(deploy_aea_name, client_aea_name) # add ethereum ledger in both configuration files default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one self.set_agent_context(deploy_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_ledger", EthereumCrypto.identifier) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.add_item("skill", "fetchai/erc1155_deploy:0.31.6") self.generate_private_key(EthereumCrypto.identifier) self.add_private_key(EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE) self.replace_private_key_in_file( FUNDED_ETH_PRIVATE_KEY_3, ETHEREUM_PRIVATE_KEY_FILE ) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "ethereum") setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") self.run_install() # replace location setting_path = ( "vendor.fetchai.skills.erc1155_deploy.models.strategy.args.location" ) self.nested_set_config(setting_path, location) diff = self.difference_to_fetched_agent( "fetchai/erc1155_deployer:0.34.5", deploy_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add packages for agent two self.set_agent_context(client_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_ledger", EthereumCrypto.identifier) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.add_item("skill", "fetchai/erc1155_client:0.29.6") self.generate_private_key(EthereumCrypto.identifier) self.add_private_key(EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE) self.replace_private_key_in_file( FUNDED_ETH_PRIVATE_KEY_2, ETHEREUM_PRIVATE_KEY_FILE ) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "ethereum") setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") self.run_install() # replace location setting_path = ( "vendor.fetchai.skills.erc1155_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) diff = self.difference_to_fetched_agent( "fetchai/erc1155_client:0.34.5", client_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # run agents self.set_agent_context(deploy_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) deploy_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( deploy_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in deploy_aea output.".format(missing_strings) check_strings = ( "starting balance on ethereum ledger=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "requesting create batch transaction...", "requesting mint batch transaction...", "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", ) missing_strings = self.missing_from_output( deploy_aea_process, check_strings, timeout=420, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in deploy_aea output.".format(missing_strings) self.set_agent_context(client_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) client_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( client_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in client_aea output.".format(missing_strings) check_strings = ( "received CFP from sender=", "sending PROPOSE to agent=", "received ACCEPT_W_INFORM from sender=", "requesting single atomic swap transaction...", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "demo finished!", ) missing_strings = self.missing_from_output( deploy_aea_process, check_strings, timeout=360, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in deploy_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received valid PROPOSE from sender=", "requesting single hash message from contract api...", "received raw message=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "sending ACCEPT_W_INFORM to agent=", ) missing_strings = self.missing_from_output( client_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in client_aea output.".format(missing_strings) self.terminate_agents(deploy_aea_process, client_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_packages/test_skills_integration/test_fetch_block.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the fetch block skill.""" import pytest from aea.test_tools.test_cases import AEATestCaseEmpty @pytest.mark.integration class TestFetchBlockSkill(AEATestCaseEmpty): """Test that fetch block skill works.""" def test_fetch_block(self): """Run the fetch block skill sequence.""" self.generate_private_key() self.add_private_key() self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/fetch_block:0.12.6") self.set_config("agent.default_connection", "fetchai/ledger:0.21.5") self.run_install() process = self.run_agent() is_running = self.is_running(process) assert is_running, "AEA not running within timeout!" check_strings = ( "setting up FetchBlockBehaviour", "Fetching latest block...", "Retrieved latest block:", ) missing_strings = self.missing_from_output(process, check_strings) assert len(missing_strings) in [ 0, 1, ], "Strings {} didn't appear in agent output.".format(missing_strings) self.terminate_agents() assert self.is_successfully_terminated(), "AEA wasn't successfully terminated." ================================================ FILE: tests/test_packages/test_skills_integration/test_generic.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the generic buyer and seller skills.""" from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, wait_for_localhost_ports_to_close, ) @pytest.mark.integration class TestGenericSkills(AEATestCaseManyFlaky): """Test that generic skills work.""" capture_log = True @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_generic(self, pytestconfig): """Run the generic skills sequence.""" seller_aea_name = "my_generic_seller" buyer_aea_name = "my_generic_buyer" self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # prepare seller agent self.set_agent_context(seller_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_seller:0.28.6") setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" self.set_config(setting_path, False, "bool") # replace location setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # prepare buyer agent self.set_agent_context(buyer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_buyer:0.27.6") setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # make runable: setting_path = "vendor.fetchai.skills.generic_buyer.is_abstract" self.set_config(setting_path, False, "bool") # replace location setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # run AEAs self.set_agent_context(seller_aea_name) seller_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) self.set_agent_context(buyer_aea_name) buyer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) self.terminate_agents(seller_aea_process, buyer_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) @pytest.mark.sync @pytest.mark.integration class TestGenericSkillsFetchaiLedger(AEATestCaseManyFlaky): """Test that generic skills work.""" capture_log = True @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_generic(self, pytestconfig): """Run the generic skills sequence.""" seller_aea_name = "my_generic_seller" buyer_aea_name = "my_generic_buyer" self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # prepare seller agent self.set_agent_context(seller_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_seller:0.28.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/generic_seller:0.29.5", seller_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" self.set_config(setting_path, False, "bool") # replace location setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # prepare buyer agent self.set_agent_context(buyer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/generic_buyer:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/generic_buyer:0.30.5", buyer_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) setting_path = "vendor.fetchai.skills.generic_buyer.is_abstract" self.set_config(setting_path, False, "bool") self.set_config( "vendor.fetchai.skills.generic_buyer.models.strategy.args.max_tx_fee", 7750000000000000, ) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # run AEAs self.set_agent_context(seller_aea_name) seller_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) self.set_agent_context(buyer_aea_name) buyer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "checking whether transaction=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=120, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in seller_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "requesting transfer transaction from ledger api for message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "transaction confirmed, informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in buyer_aea output.".format(missing_strings) self.terminate_agents(seller_aea_process, buyer_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_packages/test_skills_integration/test_gym.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the gym skill.""" import os import shutil from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import ROOT_DIR class TestGymSkill(AEATestCaseEmpty): """Test that gym skill works.""" def test_gym(self): """Run the gym skill sequence.""" self.generate_private_key() self.add_private_key() self.add_item("skill", "fetchai/gym:0.21.6") self.run_install() # change default connection setting_path = "agent.default_connection" self.set_config(setting_path, "fetchai/gym:0.20.6") diff = self.difference_to_fetched_agent( "fetchai/gym_aea:0.26.5", self.agent_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # change connection config setting_path = "vendor.fetchai.connections.gym.config.env" self.set_config(setting_path, "gyms.env.BanditNArmedRandom") # add gyms folder from examples gyms_src = os.path.join(ROOT_DIR, "examples", "gym_ex", "gyms") gyms_dst = os.path.join(self.agent_name, "gyms") shutil.copytree(gyms_src, gyms_dst) # change number of training steps setting_path = "vendor.fetchai.skills.gym.handlers.gym.args.nb_steps" self.set_config(setting_path, 20, "int") gym_aea_process = self.run_agent() check_strings = ( "Training starting ...", "Training finished. You can exit now via CTRL+C.", ) missing_strings = self.missing_from_output(gym_aea_process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) assert ( self.is_successfully_terminated() ), "Gym agent wasn't successfully terminated." ================================================ FILE: tests/test_packages/test_skills_integration/test_ml_skills.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the weather skills.""" import importlib from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, wait_for_localhost_ports_to_close, ) def _is_not_tensorflow_installed(): tf_spec = importlib.util.find_spec("tensorflow") return tf_spec is None @pytest.mark.integration class TestMLSkills(AEATestCaseManyFlaky): """Test that ml skills work.""" capture_log = True cli_log_options = ["-v", "DEBUG"] @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues @pytest.mark.skipif( _is_not_tensorflow_installed(), reason="This test requires Tensorflow.", ) def test_ml_skills(self, pytestconfig): """Run the ml skills sequence.""" data_provider_aea_name = "ml_data_provider" model_trainer_aea_name = "ml_model_trainer" self.create_agents(data_provider_aea_name, model_trainer_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # prepare data provider agent self.set_agent_context(data_provider_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/ml_data_provider:0.27.6") setting_path = ( "vendor.fetchai.skills.ml_data_provider.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = ( "vendor.fetchai.skills.ml_data_provider.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # prepare model trainer agent self.set_agent_context(model_trainer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/ml_train:0.29.6") setting_path = ( "vendor.fetchai.skills.ml_train.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = "vendor.fetchai.skills.ml_train.models.strategy.args.location" self.nested_set_config(setting_path, location) self.set_agent_context(data_provider_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) data_provider_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( data_provider_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in data_provider_aea output.".format( missing_strings ) self.set_agent_context(model_trainer_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) model_trainer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( model_trainer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in model_trainer_aea output.".format( missing_strings ) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "got a Call for Terms from", "a Terms message:", "got an Accept from", "a Data message:", ) missing_strings = self.missing_from_output( data_provider_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in data_provider_aea output.".format( missing_strings ) check_strings = ( "found agents=", "sending CFT to agent=", "received terms message from", "sending dummy transaction digest ...", "received data message from", "Loss:", ) missing_strings = self.missing_from_output( model_trainer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in model_trainer_aea output.".format( missing_strings ) self.terminate_agents(data_provider_aea_process, model_trainer_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) @pytest.mark.integration class TestMLSkillsFetchaiLedger(AEATestCaseManyFlaky): """Test that ml skills work.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues @pytest.mark.skipif( _is_not_tensorflow_installed(), reason="This test requires Tensorflow.", ) def test_ml_skills(self, pytestconfig): """Run the ml skills sequence.""" data_provider_aea_name = "ml_data_provider" model_trainer_aea_name = "ml_model_trainer" self.create_agents(data_provider_aea_name, model_trainer_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # prepare data provider agent self.set_agent_context(data_provider_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/ml_data_provider:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/ml_data_provider:0.32.5", data_provider_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = ( "vendor.fetchai.skills.ml_data_provider.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # prepare model trainer agent self.set_agent_context(model_trainer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/ml_train:0.29.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/ml_model_trainer:0.33.5", model_trainer_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = "vendor.fetchai.skills.ml_train.models.strategy.args.location" self.nested_set_config(setting_path, location) self.set_agent_context(data_provider_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) data_provider_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( data_provider_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in data_provider_aea output.".format( missing_strings ) self.set_agent_context(model_trainer_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) model_trainer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( model_trainer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in model_trainer_aea output.".format( missing_strings ) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "got a Call for Terms from", "a Terms message:", "got an Accept from", "a Data message:", ) missing_strings = self.missing_from_output( data_provider_aea_process, check_strings, timeout=240, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in data_provider_aea output.".format( missing_strings ) check_strings = ( "found agents=", "sending CFT to agent=", "received terms message from", "requesting transfer transaction from ledger api for message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "informing counterparty=", "received data message from", "Loss:", ) missing_strings = self.missing_from_output( model_trainer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in model_trainer_aea output.".format( missing_strings ) self.terminate_agents(data_provider_aea_process, model_trainer_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_packages/test_skills_integration/test_simple_aggregation.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the simple aggregation skill.""" import json import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, UseSOEF, ) MAX_RERUNS = 1 COIN_URLS = [ "https://api.coinbase.com/v2/prices/BTC-USD/buy", "https://api.coinpaprika.com/v1/tickers/btc-bitcoin", "https://api.cryptowat.ch/markets/kraken/btcusd/price", "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd", ] JSON_PATHS = [ "data.amount", "quotes.USD.price", "result.price", "bitcoin.usd", ] SERVICE_ID = "generic_aggregation_service" @pytest.mark.integration class TestSimpleAggregationSkill(AEATestCaseManyFlaky, UseSOEF): """Test that simple aggregation skill works.""" @pytest.mark.flaky(reruns=MAX_RERUNS) # cause possible network issues def test_simple_aggregation(self): """Run the simple aggregation skill sequence.""" agg0_name = "agg0_aea" agg1_name = "agg1_aea" agg2_name = "agg2_aea" agg3_name = "agg3_aea" agents = (agg0_name, agg1_name, agg2_name, agg3_name) self.create_agents(*agents) aea_processes = [] for (i, agent) in enumerate(agents): # add packages for agent self.set_agent_context(agent) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/http_client:0.24.6") self.add_item("connection", "fetchai/http_server:0.23.6") self.add_item("connection", "fetchai/soef:0.27.6") self.add_item("connection", "fetchai/prometheus:0.9.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier], ) self.add_item("skill", "fetchai/advanced_data_request:0.7.6") self.add_item("skill", "fetchai/simple_aggregation:0.3.6") self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.decimals", 0, type_="int", ) self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.use_http_server", "false", type_="bool", ) setting_path = ( "vendor.fetchai.connections.http_server.config.target_skill_id" ) self.set_config(setting_path, "fetchai/advanced_data_request:0.7.6") self.set_config( "vendor.fetchai.skills.simple_aggregation.models.strategy.args.quantity_name", "price", ) self.set_config( "vendor.fetchai.skills.simple_aggregation.models.strategy.args.aggregation_function", "mean", ) self.set_config( "vendor.fetchai.skills.simple_aggregation.models.strategy.args.search_query.search_value", SERVICE_ID, ) self.set_config( "vendor.fetchai.skills.simple_aggregation.models.strategy.args.service_id", SERVICE_ID, ) self.generate_private_key(FetchAICrypto.identifier) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.run_install() self.run_cli_command("issue-certificates", cwd=self._get_cwd()) self.run_cli_command("build", cwd=self._get_cwd()) setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": FetchAICrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") if i == 0: diff = self.difference_to_fetched_agent( "fetchai/simple_aggregator:0.5.5", agent ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format( diff ) result = self.run_cli_command( "get-multiaddress", "fetchai", "--connection", cwd=self._get_cwd() ) multiaddr = result.stdout else: setting_path = "vendor.fetchai.connections.p2p_libp2p.config" settings = json.dumps( { "delegate_uri": f"127.0.0.1:{11000 + i}", "entry_peers": [f"/dns4/127.0.0.1/tcp/9000/p2p/{multiaddr}"], "local_uri": f"127.0.0.1:{9000 + i}", "log_file": "libp2p_node.log", "public_uri": f"127.0.0.1:{9000 + i}", } ) self.set_config(setting_path, settings, type_="dict") setting_path = "vendor.fetchai.connections.prometheus.config.port" self.set_config(setting_path, 20000 + i) setting_path = "vendor.fetchai.connections.http_server.config.port" self.set_config(setting_path, 8000 + i) # set SOEF configuration setting_path = "vendor.fetchai.connections.soef.config.is_https" self.set_config(setting_path, False) setting_path = "vendor.fetchai.connections.soef.config.soef_addr" self.set_config(setting_path, "127.0.0.1") setting_path = "vendor.fetchai.connections.soef.config.soef_port" self.set_config(setting_path, 12002) # set up data request skill to fetch coin price self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url", COIN_URLS[i], type_="str", ) self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs", f'[{{"name": "price", "json_path": "{JSON_PATHS[i]}"}}]', type_="list", ) # run agent aea_processes.append(self.run_agent()) for agent in agents: self.set_agent_context(agent) check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( aea_processes[0], check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in aea output.".format(missing_strings) for agent in agents: self.set_agent_context(agent) check_strings = ( "setting up HttpHandler", "setting up PrometheusHandler", "setting up AdvancedDataRequestBehaviour", "Adding Prometheus metric: num_retrievals", "Adding Prometheus metric: num_requests", "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "Start processing messages...", "Fetching data from", "Observation: {'price': {'value': ", "found agents=", "sending observation to peer=", "received observation from sender=", "Observations:", "Aggregation (mean):", ) missing_strings = self.missing_from_output( aea_processes[0], check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in aea output.".format(missing_strings) self.terminate_agents(*aea_processes, timeout=30) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." ================================================ FILE: tests/test_packages/test_skills_integration/test_simple_oracle.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the generic buyer and seller skills.""" import os import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from tests.conftest import ( CUR_PATH, DEFAULT_FETCH_LEDGER_ADDR, DEFAULT_FETCH_LEDGER_REST_PORT, ETHEREUM_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, FUNDED_ETH_PRIVATE_KEY_2, FUNDED_ETH_PRIVATE_KEY_3, FUNDED_FETCHAI_PRIVATE_KEY_1, FUNDED_FETCHAI_PRIVATE_KEY_2, MAX_FLAKY_RERUNS_ETH, UseGanache, UseLocalFetchNode, ) ORACLE_CONTRACT_ADDRESS_FILE = os.path.join(CUR_PATH, "oracle_contract_address.txt") @pytest.mark.integration class TestOracleSkillsFetchAI(AEATestCaseManyFlaky, UseLocalFetchNode): """Test that oracle skills work.""" @pytest.mark.ledger # @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_ETH) # cause possible network issues def test_oracle( self, fund_fetchai_accounts, ): """Run the oracle skills sequence.""" oracle_agent_name = "oracle_aea" client_agent_name = "client_aea" processes = [] try: ledger_id = "fetchai" private_key_file = FETCHAI_PRIVATE_KEY_FILE funded_private_key_1 = FUNDED_FETCHAI_PRIVATE_KEY_1 funded_private_key_2 = FUNDED_FETCHAI_PRIVATE_KEY_2 update_function = "update_oracle_value" query_function = "query_oracle_value" self.create_agents(oracle_agent_name, client_agent_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", "fetchai/prometheus:1.1.7": "fetchai/prometheus:0.9.6", } # add packages for oracle agent self.set_agent_context(oracle_agent_name) self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("connection", "fetchai/http_client:0.24.6") self.add_item("connection", "fetchai/prometheus:0.9.6") self.set_config("agent.default_connection", "fetchai/ledger:0.21.5") self.set_config("agent.default_ledger", ledger_id) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.add_item("skill", "fetchai/advanced_data_request:0.7.6") self.add_item("contract", "fetchai/oracle:0.12.3") self.add_item("skill", "fetchai/simple_oracle:0.16.5") # set up data request skill to fetch coin price self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url", "https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd", type_="str", ) self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs", '[{"name": "price", "json_path": "fetch-ai.usd"}]', type_="list", ) setting_path = ( "vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id" ) self.set_config(setting_path, ledger_id) setting_path = "vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function" self.set_config(setting_path, update_function) setting_path = "vendor.fetchai.skills.simple_oracle.models.strategy.args.oracle_value_name" self.set_config(setting_path, "price") self.generate_private_key(ledger_id) self.add_private_key(ledger_id, private_key_file) self.replace_private_key_in_file(funded_private_key_1, private_key_file) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/coin_price_oracle:0.17.6", oracle_agent_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format( diff ) # redirect fetchai ledger address to local test node setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.address" ) self.set_config( setting_path, f"{DEFAULT_FETCH_LEDGER_ADDR}:{DEFAULT_FETCH_LEDGER_REST_PORT}", ) # use alternate port for prometheus connection setting_path = "vendor.fetchai.connections.prometheus.config.port" self.set_config( setting_path, 19091, type_="int" ) # 9091 used by fetchd docker image setting_path = "vendor.fetchai.skills.simple_oracle.models.strategy.args.contract_address_file" self.set_config(setting_path, ORACLE_CONTRACT_ADDRESS_FILE) # add packages for oracle client agent self.set_agent_context(client_agent_name) self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("connection", "fetchai/http_client:0.24.6") self.set_config("agent.default_connection", "fetchai/ledger:0.21.5") self.set_config("agent.default_ledger", ledger_id) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier], ) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", } setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.add_item("contract", "fetchai/oracle_client:0.11.3") self.add_item("contract", "fetchai/fet_erc20:0.9.2") self.add_item("skill", "fetchai/simple_oracle_client:0.13.5") self.generate_private_key(ledger_id) self.add_private_key(ledger_id, private_key_file) self.replace_private_key_in_file(funded_private_key_2, private_key_file) setting_path = "vendor.fetchai.skills.simple_oracle_client.models.strategy.args.ledger_id" self.set_config(setting_path, ledger_id) setting_path = "vendor.fetchai.skills.simple_oracle_client.models.strategy.args.query_function" self.set_config(setting_path, query_function) diff = self.difference_to_fetched_agent( "fetchai/coin_price_oracle_client:0.12.6", client_agent_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format( diff ) # redirect fetchai ledger address to local test node setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.address" ) self.set_config( setting_path, f"{DEFAULT_FETCH_LEDGER_ADDR}:{DEFAULT_FETCH_LEDGER_REST_PORT}", ) # run oracle agent self.set_agent_context(oracle_agent_name) oracle_aea_process = self.run_agent() processes.append(oracle_aea_process) check_strings = ( "setting up HttpHandler", "setting up AdvancedDataRequestBehaviour", "Setting up Fetch oracle contract...", "Fetching data from https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd", "received raw transaction=", "Observation: {'price': {'value': ", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "Oracle value successfully updated!", ) missing_strings = self.missing_from_output( oracle_aea_process, check_strings, timeout=60, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in aea output: \n{}".format( missing_strings, self.stdout[oracle_aea_process.pid] ) # Get oracle contract address from file with open(ORACLE_CONTRACT_ADDRESS_FILE) as file: oracle_address = file.read() # run oracle client agent self.set_agent_context(client_agent_name) # set oracle contract address in oracle client setting_path = "vendor.fetchai.skills.simple_oracle_client.models.strategy.args.oracle_contract_address" self.set_config(setting_path, oracle_address) client_aea_process = self.run_agent() processes.append(client_aea_process) check_strings = ( "requesting contract deployment transaction...", "received raw transaction=", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "Oracle value successfully requested!", ) missing_strings = self.missing_from_output( client_aea_process, check_strings, timeout=60, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in aea output: \n{}".format( missing_strings, self.stdout[client_aea_process.pid] ) finally: if processes: self.terminate_agents(*processes) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." @pytest.mark.integration class TestOracleSkillsETH(AEATestCaseManyFlaky, UseGanache): """Test that oracle skills work.""" @pytest.mark.ledger @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_ETH) # cause possible network issues def test_oracle( self, erc20_contract, oracle_contract, ): """Run the oracle skills sequence.""" oracle_agent_name = "oracle_aea" client_agent_name = "client_aea" processes = [] try: _, erc20_address = erc20_contract _, oracle_address = oracle_contract ledger_id = "ethereum" private_key_file = ETHEREUM_PRIVATE_KEY_FILE funded_private_key_1 = FUNDED_ETH_PRIVATE_KEY_3 funded_private_key_2 = FUNDED_ETH_PRIVATE_KEY_2 update_function = "updateOracleValue" query_function = "queryOracleValue" self.create_agents(oracle_agent_name, client_agent_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", "fetchai/prometheus:1.1.7": "fetchai/prometheus:0.9.6", } # add packages for oracle agent self.set_agent_context(oracle_agent_name) self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("connection", "fetchai/http_client:0.24.6") self.add_item("connection", "fetchai/prometheus:0.9.6") self.set_config("agent.default_connection", "fetchai/ledger:0.21.5") self.set_config("agent.default_ledger", ledger_id) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.add_item("skill", "fetchai/advanced_data_request:0.7.6") self.add_item("contract", "fetchai/oracle:0.12.3") self.add_item("skill", "fetchai/simple_oracle:0.16.5") # set up data request skill to fetch coin price self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.url", "https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd", type_="str", ) self.set_config( "vendor.fetchai.skills.advanced_data_request.models.advanced_data_request_model.args.outputs", '[{"name": "price", "json_path": "fetch-ai.usd"}]', type_="list", ) setting_path = ( "vendor.fetchai.skills.simple_oracle.models.strategy.args.ledger_id" ) self.set_config(setting_path, ledger_id) setting_path = "vendor.fetchai.skills.simple_oracle.models.strategy.args.update_function" self.set_config(setting_path, update_function) setting_path = "vendor.fetchai.skills.simple_oracle.models.strategy.args.oracle_value_name" self.set_config(setting_path, "price") self.generate_private_key(ledger_id) self.add_private_key(ledger_id, private_key_file) self.replace_private_key_in_file(funded_private_key_1, private_key_file) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.run_install() # set erc20 address setting_path = ( "vendor.fetchai.skills.simple_oracle.models.strategy.args.erc20_address" ) self.set_config(setting_path, erc20_address) setting_path = "vendor.fetchai.skills.simple_oracle.models.strategy.args.contract_address" self.set_config(setting_path, oracle_address) setting_path = "vendor.fetchai.skills.simple_oracle.models.strategy.args.contract_address_file" self.set_config(setting_path, ORACLE_CONTRACT_ADDRESS_FILE) # add packages for oracle client agent self.set_agent_context(client_agent_name) self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("connection", "fetchai/http_client:0.24.6") self.set_config("agent.default_connection", "fetchai/ledger:0.21.5") self.set_config("agent.default_ledger", ledger_id) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/http:1.1.7": "fetchai/http_client:0.24.6", } setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.add_item("contract", "fetchai/oracle_client:0.11.3") self.add_item("contract", "fetchai/fet_erc20:0.9.2") self.add_item("skill", "fetchai/simple_oracle_client:0.13.5") self.generate_private_key(ledger_id) self.add_private_key(ledger_id, private_key_file) self.replace_private_key_in_file(funded_private_key_2, private_key_file) setting_path = "vendor.fetchai.skills.simple_oracle_client.models.strategy.args.ledger_id" self.set_config(setting_path, ledger_id) setting_path = "vendor.fetchai.skills.simple_oracle_client.models.strategy.args.query_function" self.set_config(setting_path, query_function) # set addresses *after* comparison with fetched agent! setting_path = "vendor.fetchai.skills.simple_oracle_client.models.strategy.args.erc20_address" self.set_config(setting_path, erc20_address) # run oracle agent self.set_agent_context(oracle_agent_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) oracle_aea_process = self.run_agent() processes.append(oracle_aea_process) check_strings = ( "setting up HttpHandler", "setting up AdvancedDataRequestBehaviour", "Setting up Fetch oracle contract...", "Fetching data from https://api.coingecko.com/api/v3/simple/price?ids=fetch-ai&vs_currencies=usd", "received raw transaction=", "Observation: {'price': {'value': ", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "Oracle value successfully updated!", ) missing_strings = self.missing_from_output( oracle_aea_process, check_strings, timeout=60, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in aea output: \n{}".format( missing_strings, self.stdout[oracle_aea_process.pid] ) if ledger_id == FetchAICrypto.identifier: # Get oracle contract address from file with open(ORACLE_CONTRACT_ADDRESS_FILE) as file: oracle_address = file.read() # run oracle client agent self.set_agent_context(client_agent_name) # set oracle contract address in oracle client setting_path = "vendor.fetchai.skills.simple_oracle_client.models.strategy.args.oracle_contract_address" self.set_config(setting_path, oracle_address) client_aea_process = self.run_agent() processes.append(client_aea_process) check_strings = ( "requesting contract deployment transaction...", "received raw transaction=", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "Oracle value successfully requested!", ) missing_strings = self.missing_from_output( client_aea_process, check_strings, timeout=60, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in aea output: \n{}".format( missing_strings, self.stdout[client_aea_process.pid] ) finally: if processes: self.terminate_agents(*processes) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." ================================================ FILE: tests/test_packages/test_skills_integration/test_tac.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the tac skills.""" import datetime import json import uuid from random import uniform import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( DEFAULT_DENOMINATION, DEFAULT_FETCH_CHAIN_ID, DEFAULT_FETCH_LEDGER_ADDR, DEFAULT_FETCH_LEDGER_REST_PORT, ETHEREUM_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, FUNDED_ETH_PRIVATE_KEY_1, FUNDED_ETH_PRIVATE_KEY_2, FUNDED_ETH_PRIVATE_KEY_3, MAX_FLAKY_RERUNS_ETH, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, NON_GENESIS_CONFIG_TWO, UseGanache, UseLocalFetchNode, UseSOEF, fund_accounts_from_local_validator, ) MAX_FLAKY_RERUNS_ETH -= 1 class TestTacSkills(AEATestCaseManyFlaky): """Test that tac skills work.""" capture_log = True @pytest.mark.integration @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_tac(self): """Run the tac skills sequence.""" tac_aea_one = "tac_participant_one" tac_aea_two = "tac_participant_two" tac_controller_name = "tac_controller" # create tac controller, agent one and agent two self.create_agents( tac_aea_one, tac_aea_two, tac_controller_name, ) default_routing = { "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # tac name tac_id = uuid.uuid4().hex tac_service = f"tac_service_{tac_id[:5]}" # prepare tac controller for test self.set_agent_context(tac_controller_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.add_item("skill", "fetchai/tac_control:0.25.6") self.set_config("agent.default_ledger", FetchAICrypto.identifier) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/tac_controller:0.30.5", tac_controller_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = ( "vendor.fetchai.skills.tac_control.models.parameters.args.location" ) self.nested_set_config(setting_path, location) # set tac id data = {"key": "tac", "value": tac_id} setting_path = ( "vendor.fetchai.skills.tac_control.models.parameters.args.service_data" ) self.nested_set_config(setting_path, data) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # prepare agents for test for agent_name, config in ( (tac_aea_one, NON_GENESIS_CONFIG), (tac_aea_two, NON_GENESIS_CONFIG_TWO), ): self.set_agent_context(agent_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/tac_participation:0.25.6") self.add_item("skill", "fetchai/tac_negotiation:0.29.6") self.set_config("agent.default_ledger", FetchAICrypto.identifier) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) data = { "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": None, "config": {}, } setting_path = "agent.decision_maker_handler" self.nested_set_config(setting_path, data) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/tac_participant:0.32.5", agent_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format( diff ) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, config) # replace location setting_path = ( "vendor.fetchai.skills.tac_participation.models.game.args.location" ) self.nested_set_config(setting_path, location) # set tac id data = { "search_key": "tac", "search_value": tac_id, "constraint_type": "==", } setting_path = ( "vendor.fetchai.skills.tac_participation.models.game.args.search_query" ) self.nested_set_config(setting_path, data) self.set_config( "vendor.fetchai.skills.tac_negotiation.models.strategy.args.service_key", tac_service, ) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) # run tac controller self.set_agent_context(tac_controller_name) now = datetime.datetime.now().strftime("%d %m %Y %H:%M") now_min = datetime.datetime.strptime(now, "%d %m %Y %H:%M") fut = now_min + datetime.timedelta(0, 60) start_time = fut.strftime("%d %m %Y %H:%M") setting_path = "vendor.fetchai.skills.tac_control.models.parameters.args.registration_start_time" self.set_config(setting_path, start_time) tac_controller_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( tac_controller_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_controller output.".format(missing_strings) # run two agents (participants) self.set_agent_context(tac_aea_one) tac_aea_one_process = self.run_agent() self.set_agent_context(tac_aea_two) tac_aea_two_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_one output.".format(missing_strings) check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( tac_aea_two_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_two output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering TAC data model on SOEF.", "TAC open for registration until:", "registered as 'tac_participant_one'", "registered as 'tac_participant_two'", "started competition:", "unregistering TAC data model from SOEF.", "handling valid transaction:", "Current good & money allocation & score: ", ) missing_strings = self.missing_from_output( tac_controller_process, check_strings, timeout=240, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_controller output.".format(missing_strings) check_strings = ( "searching for TAC, search_id=", "found the TAC controller. Registering...", "received start event from the controller. Starting to compete...", "registering agent on SOEF.", "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", "received cfp from", "received decline from", "received propose from", "received accept from", "received match_accept_w_inform from", "sending CFP to agent=", "sending propose to", "sending accept to", "requesting signature, sending sign_message to decision_maker, message=", "received signed_message from decision_maker, message=", "sending transaction to controller, tx=", "received transaction confirmation from the controller:", "Applying state update!", "found potential buyers agents=", "sending CFP to agent=", ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=240, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_one output.".format(missing_strings) # Note, we do not need to check std output of the other participant as it is implied self.terminate_agents( tac_controller_process, tac_aea_one_process, tac_aea_two_process ) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." class TestTacSkillsContractEthereum(AEATestCaseManyFlaky, UseGanache, UseSOEF): """Test that tac skills work.""" capture_log = True @pytest.mark.integration @pytest.mark.ledger @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_ETH) # cause possible network issues def test_tac(self): """Run the tac skills sequence.""" tac_aea_one = "tac_participant_one" tac_aea_two = "tac_participant_two" tac_controller_name = "tac_controller_contract" # create tac controller, agent one and agent two self.create_agents( tac_aea_one, tac_aea_two, tac_controller_name, ) # default routing (both for controller and participants) default_routing = { "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # tac name tac_id = uuid.uuid4().hex tac_service = f"tac_service_{tac_id[:5]}" # prepare tac controller for test self.set_agent_context(tac_controller_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/tac_control_contract:0.27.6") self.set_config("agent.default_ledger", FetchAICrypto.identifier) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(EthereumCrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( FUNDED_ETH_PRIVATE_KEY_1, ETHEREUM_PRIVATE_KEY_FILE ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") settings = json.dumps( [ { "identifier": "acn", "ledger_id": FetchAICrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") # set SOEF configuration setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "fetchai_v2_misc") setting_path = "vendor.fetchai.skills.tac_control.is_abstract" self.set_config(setting_path, True, "bool") # replace location setting_path = ( "vendor.fetchai.skills.tac_control_contract.models.parameters.args.location" ) self.nested_set_config(setting_path, location) # set tac id data = {"key": "tac", "value": tac_id} setting_path = "vendor.fetchai.skills.tac_control_contract.models.parameters.args.service_data" self.nested_set_config(setting_path, data) # check manually built agent is the same as the fetched one diff = self.difference_to_fetched_agent( "fetchai/tac_controller_contract:0.32.5", tac_controller_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # change default ledger to Ethereum self.set_config("agent.default_ledger", EthereumCrypto.identifier) # set SOEF connection configuration setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, EthereumCrypto.identifier) # set p2p_libp2p connection config setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") # change SOEF configuration to local setting_path = "vendor.fetchai.connections.soef.config.is_https" self.set_config(setting_path, False) setting_path = "vendor.fetchai.connections.soef.config.soef_addr" self.set_config(setting_path, "127.0.0.1") setting_path = "vendor.fetchai.connections.soef.config.soef_port" self.set_config(setting_path, 12002) # prepare agents for test for agent_name, config, private_key in ( (tac_aea_one, NON_GENESIS_CONFIG, FUNDED_ETH_PRIVATE_KEY_2), (tac_aea_two, NON_GENESIS_CONFIG_TWO, FUNDED_ETH_PRIVATE_KEY_3), ): self.set_agent_context(agent_name) # add items self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/tac_participation:0.25.6") self.add_item("skill", "fetchai/tac_negotiation:0.29.6") # set AEA config (no component overrides) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_ledger", FetchAICrypto.identifier) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) data = { "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": None, "config": {}, } setting_path = "agent.decision_maker_handler" self.nested_set_config(setting_path, data) # install PyPI dependencies self.run_install() # add keys self.generate_private_key(EthereumCrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file(private_key, ETHEREUM_PRIVATE_KEY_FILE) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, config) setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") settings = json.dumps( [ { "identifier": "acn", "ledger_id": FetchAICrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") # set SOEF configuration setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "fetchai_v2_misc") # set tac participant configuration self.set_config( "vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract", True, "bool", ) # set tac negotiation configuration self.set_config( "vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx", True, "bool", ) # replace location setting_path = ( "vendor.fetchai.skills.tac_participation.models.game.args.location" ) self.nested_set_config(setting_path, location) # set tac id data = { "search_key": "tac", "search_value": tac_id, "constraint_type": "==", } setting_path = ( "vendor.fetchai.skills.tac_participation.models.game.args.search_query" ) self.nested_set_config(setting_path, data) diff = self.difference_to_fetched_agent( "fetchai/tac_participant_contract:0.22.5", agent_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format( diff ) # change default ledger to Ethereum self.set_config("agent.default_ledger", EthereumCrypto.identifier) # set SOEF connection configuration setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, EthereumCrypto.identifier) # set p2p_libp2p connection config setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") # change SOEF configuration to local setting_path = "vendor.fetchai.connections.soef.config.is_https" self.set_config(setting_path, False) setting_path = "vendor.fetchai.connections.soef.config.soef_addr" self.set_config(setting_path, "127.0.0.1") setting_path = "vendor.fetchai.connections.soef.config.soef_port" self.set_config(setting_path, 12002) self.set_config( "vendor.fetchai.skills.tac_negotiation.models.strategy.args.service_key", tac_service, ) # run tac controller self.set_agent_context(tac_controller_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) now = datetime.datetime.now().strftime("%d %m %Y %H:%M") now_min = datetime.datetime.strptime(now, "%d %m %Y %H:%M") fut = now_min + datetime.timedelta( 0, 120 ) # we provide 2 minutes time for contract deployment start_time = fut.strftime("%d %m %Y %H:%M") setting_path = "vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time" self.set_config(setting_path, start_time) tac_controller_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, "registering agent on SOEF.", "requesting contract deployment transaction...", "Start processing messages...", "received raw transaction=", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "contract deployed.", "registering TAC data model on SOEF.", "TAC open for registration until:", ) missing_strings = self.missing_from_output( tac_controller_process, check_strings, timeout=180, is_terminating=False ) # we need to wait sufficiently long (at least 2 minutes - see above for deployment) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_controller output.".format(missing_strings) # run two agents (participants) self.set_agent_context(tac_aea_one) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) tac_aea_one_process = self.run_agent() self.set_agent_context(tac_aea_two) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) tac_aea_two_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, "Start processing messages...", "searching for TAC, search_id=", ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=30, is_terminating=False ) check_strings = ("found the TAC controller. Registering...",) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=60, is_terminating=False ) # we need to wait sufficiently long (at least 1 minutes - for registration) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_one output.".format(missing_strings) check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, "Start processing messages...", "searching for TAC, search_id=", "found the TAC controller. Registering...", ) missing_strings = self.missing_from_output( tac_aea_two_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_two output.".format(missing_strings) check_strings = ( "registered as 'tac_participant_one'", "registered as 'tac_participant_two'", "closing registration!", "unregistering TAC data model from SOEF.", "requesting create items transaction...", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "tokens created.", "requesting mint_items transactions for agent=", "tokens minted.", "requesting mint_items transactions for agent=", "tokens minted.", "all tokens minted.", "started competition:", ) missing_strings = self.missing_from_output( tac_controller_process, check_strings, timeout=240, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_controller output.".format(missing_strings) check_strings = ( "received start event from the controller. Starting to compete...", "received a contract address:", "registering agent on SOEF.", "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", "found potential buyers agents=", "sending CFP to agent=", "received cfp from", "received propose from", "received decline from", "received accept from", "received match_accept_w_inform from", "sending propose to", "sending accept to", "requesting batch transaction hash, sending get_raw_message to fetchai/erc1155:0.23.3, message=", "requesting batch atomic swap transaction, sending get_raw_transaction to fetchai/erc1155:0.23.3, message=", "received raw transaction=", "received raw message=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "proposing the message to the decision maker. Waiting for confirmation ...", "received signed_message from decision_maker, message=", "received signed_transaction from decision_maker, message=", "sending send_signed_transaction to ledger ethereum, message=", "transaction was successfully submitted. Transaction digest=", "transaction was successfully settled. Transaction receipt=", ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=300, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_one output.".format(missing_strings) check_strings = ( "received start event from the controller. Starting to compete...", "received a contract address:", "registering agent on SOEF.", "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", "found potential buyers agents=", "sending CFP to agent=", "received cfp from", "received propose from", "received decline from", "received accept from", "received match_accept_w_inform from", "sending propose to", "sending accept to", "requesting batch transaction hash, sending get_raw_message to fetchai/erc1155:0.23.3, message=", "requesting batch atomic swap transaction, sending get_raw_transaction to fetchai/erc1155:0.23.3, message=", "received raw transaction=", "received raw message=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "proposing the message to the decision maker. Waiting for confirmation ...", "received signed_message from decision_maker, message=", "received signed_transaction from decision_maker, message=", "sending send_signed_transaction to ledger ethereum, message=", "transaction was successfully submitted. Transaction digest=", "transaction was successfully settled. Transaction receipt=", ) missing_strings = self.missing_from_output( tac_aea_two_process, check_strings, timeout=360, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_two output.".format(missing_strings) # Note, we do not need to check std output of the other participant as it is implied self.terminate_agents( tac_controller_process, tac_aea_one_process, tac_aea_two_process ) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." class TestTacSkillsContractFetchai(AEATestCaseManyFlaky, UseLocalFetchNode, UseSOEF): """Test that tac skills work.""" capture_log = True LOCAL_TESTNET_CHAIN_ID = DEFAULT_FETCH_CHAIN_ID @pytest.mark.integration @pytest.mark.ledger @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_ETH) # cause possible network issues def test_tac(self): """Run the tac skills sequence.""" tac_aea_one = "tac_participant_one" tac_aea_two = "tac_participant_two" tac_controller_name = "tac_controller_contract" # create tac controller, agent one and agent two self.create_agents( tac_aea_one, tac_aea_two, tac_controller_name, ) # default routing (both for controller and participants) default_routing = { "fetchai/contract_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # tac name tac_id = uuid.uuid4().hex tac_service = f"tac_service_{tac_id[:5]}" # prepare tac controller for test self.set_agent_context(tac_controller_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/tac_control_contract:0.27.6") self.set_config("agent.default_ledger", FetchAICrypto.identifier) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund controller account controller_address = self.get_address(FetchAICrypto.identifier) fund_accounts_from_local_validator([controller_address], 10000000000000000000) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") settings = json.dumps( [ { "identifier": "acn", "ledger_id": FetchAICrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") # set SOEF configuration setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "fetchai_v2_misc") setting_path = "vendor.fetchai.skills.tac_control.is_abstract" self.set_config(setting_path, True, "bool") # replace location setting_path = ( "vendor.fetchai.skills.tac_control_contract.models.parameters.args.location" ) self.nested_set_config(setting_path, location) # set tac id data = {"key": "tac", "value": tac_id} setting_path = "vendor.fetchai.skills.tac_control_contract.models.parameters.args.service_data" self.nested_set_config(setting_path, data) # check manually built agent is the same as the fetched one diff = self.difference_to_fetched_agent( "fetchai/tac_controller_contract:0.32.5", tac_controller_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # use local test-net setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.address" ) self.set_config( setting_path, f"{DEFAULT_FETCH_LEDGER_ADDR}:{DEFAULT_FETCH_LEDGER_REST_PORT}", ) setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.denom" ) self.set_config(setting_path, DEFAULT_DENOMINATION) setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.chain_id" ) self.set_config(setting_path, self.LOCAL_TESTNET_CHAIN_ID) # change SOEF configuration to local setting_path = "vendor.fetchai.connections.soef.config.is_https" self.set_config(setting_path, False) setting_path = "vendor.fetchai.connections.soef.config.soef_addr" self.set_config(setting_path, "127.0.0.1") setting_path = "vendor.fetchai.connections.soef.config.soef_port" self.set_config(setting_path, 12002) # prepare agents for test for agent_name, config in ( (tac_aea_one, NON_GENESIS_CONFIG), (tac_aea_two, NON_GENESIS_CONFIG_TWO), ): self.set_agent_context(agent_name) # add items self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/tac_participation:0.25.6") self.add_item("skill", "fetchai/tac_negotiation:0.29.6") # set AEA config (no component overrides) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.set_config("agent.default_ledger", FetchAICrypto.identifier) self.nested_set_config( "agent.required_ledgers", [FetchAICrypto.identifier, EthereumCrypto.identifier], ) setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) data = { "dotted_path": "aea.decision_maker.gop:DecisionMakerHandler", "file_path": None, "config": {}, } setting_path = "agent.decision_maker_handler" self.nested_set_config(setting_path, data) # install PyPI dependencies self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund participant account participant_address = self.get_address(FetchAICrypto.identifier) fund_accounts_from_local_validator( [participant_address], 10000000000000000000 ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, config) setting_path = "vendor.fetchai.connections.p2p_libp2p.cert_requests" settings = json.dumps( [ { "identifier": "acn", "ledger_id": EthereumCrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") settings = json.dumps( [ { "identifier": "acn", "ledger_id": FetchAICrypto.identifier, "not_after": "2023-01-01", "not_before": "2022-01-01", "public_key": FetchAICrypto.identifier, "message_format": "{public_key}", "save_path": ".certs/conn_cert.txt", } ] ) self.set_config(setting_path, settings, type_="list") # set SOEF configuration setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "fetchai_v2_misc") # set tac participant configuration self.set_config( "vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract", True, "bool", ) # set tac negotiation configuration self.set_config( "vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx", True, "bool", ) # replace location setting_path = ( "vendor.fetchai.skills.tac_participation.models.game.args.location" ) self.nested_set_config(setting_path, location) # set tac id data = { "search_key": "tac", "search_value": tac_id, "constraint_type": "==", } setting_path = ( "vendor.fetchai.skills.tac_participation.models.game.args.search_query" ) self.nested_set_config(setting_path, data) diff = self.difference_to_fetched_agent( "fetchai/tac_participant_contract:0.22.5", agent_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format( diff ) # use local test-net setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.address" ) self.set_config( setting_path, f"{DEFAULT_FETCH_LEDGER_ADDR}:{DEFAULT_FETCH_LEDGER_REST_PORT}", ) setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.denom" ) self.set_config(setting_path, DEFAULT_DENOMINATION) setting_path = ( "vendor.fetchai.connections.ledger.config.ledger_apis.fetchai.chain_id" ) self.set_config(setting_path, self.LOCAL_TESTNET_CHAIN_ID) # change SOEF configuration to local setting_path = "vendor.fetchai.connections.soef.config.is_https" self.set_config(setting_path, False) setting_path = "vendor.fetchai.connections.soef.config.soef_addr" self.set_config(setting_path, "127.0.0.1") setting_path = "vendor.fetchai.connections.soef.config.soef_port" self.set_config(setting_path, 12002) self.set_config( "vendor.fetchai.skills.tac_negotiation.models.strategy.args.service_key", tac_service, ) # run tac controller self.set_agent_context(tac_controller_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) now = datetime.datetime.now().strftime("%d %m %Y %H:%M") now_min = datetime.datetime.strptime(now, "%d %m %Y %H:%M") fut = now_min + datetime.timedelta( 0, 120 ) # we provide 2 minutes time for contract deployment start_time = fut.strftime("%d %m %Y %H:%M") setting_path = "vendor.fetchai.skills.tac_control_contract.models.parameters.args.registration_start_time" self.set_config(setting_path, start_time) tac_controller_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, "registering agent on SOEF.", "requesting contract deployment transaction...", "Start processing messages...", "received raw transaction=", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "requesting contract initialisation transaction...", "contract deployed.", "registering TAC data model on SOEF.", "TAC open for registration until:", ) missing_strings = self.missing_from_output( tac_controller_process, check_strings, timeout=180, is_terminating=False ) # we need to wait sufficiently long (at least 2 minutes - see above for deployment) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_controller output.".format(missing_strings) # run two agents (participants) self.set_agent_context(tac_aea_one) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) tac_aea_one_process = self.run_agent() self.set_agent_context(tac_aea_two) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) tac_aea_two_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, "Start processing messages...", "searching for TAC, search_id=", ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=30, is_terminating=False ) check_strings = ("found the TAC controller. Registering...",) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=60, is_terminating=False ) # we need to wait sufficiently long (at least 1 minutes - for registration) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_one output.".format(missing_strings) check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, "Start processing messages...", "searching for TAC, search_id=", "found the TAC controller. Registering...", ) missing_strings = self.missing_from_output( tac_aea_two_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_two output.".format(missing_strings) check_strings = ( "registered as 'tac_participant_one'", "registered as 'tac_participant_two'", "closing registration!", "unregistering TAC data model from SOEF.", "requesting create items transaction...", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", "tokens created.", "requesting mint_items transactions for agent=", "tokens minted.", "requesting mint_items transactions for agent=", "tokens minted.", "all tokens minted.", "started competition:", ) missing_strings = self.missing_from_output( tac_controller_process, check_strings, timeout=240, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_controller output.".format(missing_strings) check_strings = ( "received start event from the controller. Starting to compete...", "received a contract address:", "registering agent on SOEF.", "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", "found potential buyers agents=", "sending CFP to agent=", "received cfp from", "received propose from", "received decline from", "received accept from", "received match_accept_w_inform from", "sending propose to", "sending accept to", "sending match_accept_w_inform to", "requesting batch atomic swap transaction, sending get_raw_transaction to fetchai/erc1155:0.23.3, message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "received signed_transaction from decision_maker, message=", "sending inform_signed_transaction to", "received inform_signed_transaction from", "sending send_signed_transaction to ledger fetchai, message=", "transaction was successfully submitted. Transaction digest=", "transaction was successfully settled. Transaction receipt=", ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=300, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_one output.".format(missing_strings) check_strings = ( "received start event from the controller. Starting to compete...", "received a contract address:", "registering agent on SOEF.", "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", "found potential buyers agents=", "sending CFP to agent=", "received cfp from", "received propose from", "received decline from", "received accept from", "received match_accept_w_inform from", "sending propose to", "sending accept to", "sending match_accept_w_inform to", "requesting batch atomic swap transaction, sending get_raw_transaction to fetchai/erc1155:0.23.3, message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "received signed_transaction from decision_maker, message=", "sending inform_signed_transaction to", "received inform_signed_transaction from", "sending send_signed_transaction to ledger fetchai, message=", "transaction was successfully submitted. Transaction digest=", "transaction was successfully settled. Transaction receipt=", ) missing_strings = self.missing_from_output( tac_aea_two_process, check_strings, timeout=360, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in tac_aea_two output.".format(missing_strings) # Note, we do not need to check std output of the other participant as it is implied self.terminate_agents( tac_controller_process, tac_aea_one_process, tac_aea_two_process ) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." ================================================ FILE: tests/test_packages/test_skills_integration/test_thermometer.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the thermometer skills.""" from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, wait_for_localhost_ports_to_close, ) @pytest.mark.integration class TestThermometerSkill(AEATestCaseManyFlaky): """Test that thermometer skills work.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_thermometer(self): """Run the thermometer skills sequence.""" thermometer_aea_name = "my_thermometer" thermometer_client_aea_name = "my_thermometer_client" self.create_agents(thermometer_aea_name, thermometer_client_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one and run it self.set_agent_context(thermometer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/thermometer:0.27.6") setting_path = ( "vendor.fetchai.skills.thermometer.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = "vendor.fetchai.skills.thermometer.models.strategy.args.location" self.nested_set_config(setting_path, location) # add packages for agent two and run it self.set_agent_context(thermometer_client_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/thermometer_client:0.26.6") setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # run AEAs self.set_agent_context(thermometer_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) thermometer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_aea output.".format(missing_strings) self.set_agent_context(thermometer_client_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) thermometer_client_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_client_aea_process, check_strings, timeout=240, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_client_aea output.".format( missing_strings ) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( thermometer_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( thermometer_client_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_client_aea output.".format( missing_strings ) self.terminate_agents(thermometer_aea_process, thermometer_client_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) @pytest.mark.integration class TestThermometerSkillFetchaiLedger(AEATestCaseManyFlaky): """Test that thermometer skills work.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_thermometer(self): """Run the thermometer skills sequence.""" thermometer_aea_name = "my_thermometer" thermometer_client_aea_name = "my_thermometer_client" self.create_agents(thermometer_aea_name, thermometer_client_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one and run it self.set_agent_context(thermometer_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/thermometer:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/thermometer_aea:0.30.5", thermometer_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = "vendor.fetchai.skills.thermometer.models.strategy.args.location" self.nested_set_config(setting_path, location) # add packages for agent two and run it self.set_agent_context(thermometer_client_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/thermometer_client:0.26.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/thermometer_client:0.32.5", thermometer_client_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # run AEAs self.set_agent_context(thermometer_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) thermometer_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_aea_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_aea output.".format(missing_strings) self.set_agent_context(thermometer_client_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) thermometer_client_aea_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_client_aea_process, check_strings, timeout=30, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_client_aea output.".format( missing_strings ) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "checking whether transaction=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( thermometer_aea_process, check_strings, timeout=240, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_aea output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "requesting transfer transaction from ledger api for message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( thermometer_client_aea_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in thermometer_client_aea output.".format( missing_strings ) self.terminate_agents(thermometer_aea_process, thermometer_client_aea_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_packages/test_skills_integration/test_weather.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the integration test for the weather skills.""" from random import uniform import pytest from aea_ledger_fetchai import FetchAICrypto from aea.test_tools.test_cases import AEATestCaseManyFlaky from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE from tests.conftest import ( FETCHAI_PRIVATE_KEY_FILE, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_FETCHAI_PRIVATE_KEY_1, NON_GENESIS_CONFIG, wait_for_localhost_ports_to_close, ) @pytest.mark.integration class TestWeatherSkills(AEATestCaseManyFlaky): """Test that weather skills work.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_weather(self): """Run the weather skills sequence.""" weather_station_aea_name = "my_weather_station" weather_client_aea_name = "my_weather_client" self.create_agents(weather_station_aea_name, weather_client_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # prepare agent one (weather station) self.set_agent_context(weather_station_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/weather_station:0.27.6") dotted_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx" ) self.set_config(dotted_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # prepare agent two (weather client) self.set_agent_context(weather_client_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/weather_client:0.26.6") self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") dotted_path = ( "vendor.fetchai.skills.weather_client.models.strategy.args.is_ledger_tx" ) self.set_config(dotted_path, False, "bool") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.weather_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # run agents self.set_agent_context(weather_station_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) weather_station_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_station output.".format(missing_strings) self.set_agent_context(weather_client_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) weather_client_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_client_process, check_strings, timeout=30, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_client output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( weather_station_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_station output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( weather_client_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_client output.".format(missing_strings) self.terminate_agents(weather_station_process, weather_client_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) @pytest.mark.integration class TestWeatherSkillsFetchaiLedger(AEATestCaseManyFlaky): """Test that weather skills work.""" @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues def test_weather(self): """Run the weather skills sequence.""" weather_station_aea_name = "my_weather_station" weather_client_aea_name = "my_weather_client" self.create_agents(weather_station_aea_name, weather_client_aea_name) default_routing = { "fetchai/ledger_api:1.1.7": "fetchai/ledger:0.21.5", "fetchai/oef_search:1.1.7": "fetchai/soef:0.27.6", } # generate random location location = { "latitude": round(uniform(-90, 90), 2), # nosec "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one self.set_agent_context(weather_station_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/weather_station:0.27.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/weather_station:0.32.5", weather_station_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) self.replace_private_key_in_file( NON_FUNDED_FETCHAI_PRIVATE_KEY_1, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" self.set_config(setting_path, FetchAICrypto.identifier) # replace location setting_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) self.nested_set_config(setting_path, location) # add packages for agent two self.set_agent_context(weather_client_aea_name) self.add_item("connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/soef:0.27.6") self.set_config( "agent.dependencies", '{\ "aea-ledger-fetchai": {"version": "<2.0.0,>=1.0.0"}\ }', type_="dict", ) self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.27.5") self.add_item("connection", "fetchai/ledger:0.21.5") self.add_item("skill", "fetchai/weather_client:0.26.6") setting_path = "agent.default_routing" self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( "fetchai/weather_client:0.33.5", weather_client_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) # add keys self.generate_private_key(FetchAICrypto.identifier) self.generate_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION ) self.add_private_key(FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE) self.add_private_key( FetchAICrypto.identifier, FETCHAI_PRIVATE_KEY_FILE_CONNECTION, connection=True, ) # fund key self.generate_wealth(FetchAICrypto.identifier) # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.weather_client.models.strategy.args.location" ) self.nested_set_config(setting_path, location) self.set_agent_context(weather_station_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) weather_station_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=30, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_station output.".format(missing_strings) self.set_agent_context(weather_client_aea_name) self.run_cli_command("build", cwd=self._get_cwd()) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) weather_client_process = self.run_agent() check_strings = ( "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_client_process, check_strings, timeout=30, is_terminating=False, ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_client output.".format(missing_strings) check_strings = ( "registering agent on SOEF.", "registering agent's service on the SOEF.", "registering agent's personality genus on the SOEF.", "registering agent's personality classification on the SOEF.", "received CFP from sender=", "sending a PROPOSE with proposal=", "received ACCEPT from sender=", "sending MATCH_ACCEPT_W_INFORM to sender=", "received INFORM from sender=", "checking whether transaction=", "transaction confirmed, sending data=", ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=120, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_station output.".format(missing_strings) check_strings = ( "found agents=", "sending CFP to agent=", "received proposal=", "accepting the proposal from sender=", "received MATCH_ACCEPT_W_INFORM from sender=", "requesting transfer transaction from ledger api for message=", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", "sending transaction to ledger.", "transaction was successfully submitted. Transaction digest=", "informing counterparty=", "received INFORM from sender=", "received the following data=", ) missing_strings = self.missing_from_output( weather_client_process, check_strings, is_terminating=False ) assert ( missing_strings == [] ), "Strings {} didn't appear in weather_client output.".format(missing_strings) self.terminate_agents(weather_station_process, weather_client_process) assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." wait_for_localhost_ports_to_close([9000, 9001]) ================================================ FILE: tests/test_packages_for_aea_tests/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests for packages required for AEA tests to run.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/contracts dir.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_gym/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the gym connection implementation.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_gym/test_gym.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the gym connection module.""" import asyncio import logging import os from typing import cast from unittest.mock import MagicMock, patch import gym import pytest from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.mail.base import Envelope, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.gym.connection import GymConnection from packages.fetchai.protocols.gym.dialogues import GymDialogue from packages.fetchai.protocols.gym.dialogues import GymDialogues as BaseGymDialogues from packages.fetchai.protocols.gym.message import GymMessage from tests.conftest import ROOT_DIR, UNKNOWN_PROTOCOL_PUBLIC_ID logger = logging.getLogger(__name__) class GymDialogues(BaseGymDialogues): """The dialogues class keeps track of all gym dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return GymDialogue.Role.AGENT BaseGymDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class TestGymConnection: """Test the packages/connection/gym/connection.py.""" def setup(self): """Initialise the class.""" self.env = gym.GoalEnv() configuration = ConnectionConfig(connection_id=GymConnection.connection_id) self.agent_address = "my_address" self.agent_public_key = "my_public_key" identity = Identity( "name", address=self.agent_address, public_key=self.agent_public_key ) self.gym_con = GymConnection( gym_env=self.env, identity=identity, configuration=configuration, data_dir=MagicMock(), ) self.loop = asyncio.get_event_loop() self.gym_address = str(GymConnection.connection_id) self.skill_id = "some/skill:0.1.0" self.dialogues = GymDialogues(self.skill_id) def teardown(self): """Clean up after tests.""" self.loop.run_until_complete(self.gym_con.disconnect()) @pytest.mark.asyncio async def test_gym_connection_connect(self): """Test the connection None return value after connect().""" assert self.gym_con.channel._queue is None await self.gym_con.channel.connect() assert self.gym_con.channel._queue is not None @pytest.mark.asyncio async def test_decode_envelope_error(self): """Test the decoding error for the envelopes.""" await self.gym_con.connect() envelope = Envelope( to=self.gym_address, sender=self.skill_id, protocol_specification_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=b"hello", ) with pytest.raises(ValueError): await self.gym_con.send(envelope) @pytest.mark.asyncio async def test_send_connection_error(self): """Test send connection error.""" msg, sending_dialogue = self.dialogues.create( counterparty=self.gym_address, performative=GymMessage.Performative.RESET, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) with pytest.raises(ConnectionError): await self.gym_con.send(envelope) @pytest.mark.asyncio async def test_send_act(self): """Test send act message.""" sending_dialogue = await self.send_reset() assert sending_dialogue.last_message is not None msg = sending_dialogue.reply( performative=GymMessage.Performative.ACT, action=GymMessage.AnyObject("any_action"), step_id=1, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self.gym_con.connect() observation = 1 reward = 1.0 done = True info = "some info" with patch.object( self.env, "step", return_value=(observation, reward, done, info) ) as mock: await self.gym_con.send(envelope) mock.assert_called() response = await asyncio.wait_for(self.gym_con.receive(), timeout=3) response_msg = cast(GymMessage, response.message) response_dialogue = self.dialogues.update(response_msg) assert response_msg.performative == GymMessage.Performative.PERCEPT assert response_msg.step_id == msg.step_id assert response_msg.observation.any == observation assert response_msg.reward == reward assert response_msg.done == done assert response_msg.info.any == info assert sending_dialogue == response_dialogue @pytest.mark.asyncio async def test_send_reset(self): """Test send reset message.""" _ = await self.send_reset() @pytest.mark.asyncio async def test_send_close(self): """Test send close message.""" sending_dialogue = await self.send_reset() assert sending_dialogue.last_message is not None msg = sending_dialogue.reply( performative=GymMessage.Performative.CLOSE, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self.gym_con.connect() with patch.object(self.env, "close") as mock: await self.gym_con.send(envelope) mock.assert_called() @pytest.mark.asyncio async def test_send_close_negative(self): """Test send close message with invalid reference and message id and target.""" incorrect_msg = GymMessage( performative=GymMessage.Performative.CLOSE, dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), ) incorrect_msg.to = self.gym_address incorrect_msg.sender = self.skill_id # the incorrect message cannot be sent into a dialogue, so this is omitted. envelope = Envelope( to=incorrect_msg.to, sender=incorrect_msg.sender, protocol_specification_id=incorrect_msg.protocol_specification_id, message=incorrect_msg, ) await self.gym_con.connect() with patch.object(self.gym_con.channel.logger, "warning") as mock_logger: await self.gym_con.send(envelope) mock_logger.assert_any_call( f"Could not create dialogue from message={incorrect_msg}" ) async def send_reset(self) -> GymDialogue: """Send a reset.""" msg, sending_dialogue = self.dialogues.create( counterparty=self.gym_address, performative=GymMessage.Performative.RESET, ) assert sending_dialogue is not None envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await self.gym_con.connect() with patch.object(self.env, "reset") as mock: await self.gym_con.send(envelope) mock.assert_called() response = await asyncio.wait_for(self.gym_con.receive(), timeout=3) response_msg = cast(GymMessage, response.message) response_dialogue = self.dialogues.update(response_msg) assert response_msg.performative == GymMessage.Performative.STATUS assert response_msg.content == {"reset": "success"} assert sending_dialogue == response_dialogue return sending_dialogue @pytest.mark.asyncio async def test_receive_connection_error(self): """Test receive connection error and Cancel Error.""" with pytest.raises(ConnectionError): await self.gym_con.receive() def test_gym_env_load(self): """Load gym env from file.""" curdir = os.getcwd() os.chdir(os.path.join(ROOT_DIR, "examples", "gym_ex")) gym_env_path = "gyms.env.BanditNArmedRandom" configuration = ConnectionConfig( connection_id=GymConnection.connection_id, env=gym_env_path ) identity = Identity( "name", address=self.agent_address, public_key=self.agent_public_key ) gym_con = GymConnection( gym_env=None, identity=identity, configuration=configuration, data_dir=MagicMock(), ) assert gym_con.channel.gym_env is not None os.chdir(curdir) ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_local/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the local OEF node implementation.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_local/test_misc.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the local OEF node implementation.""" import asyncio import unittest.mock import pytest from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.local.connection import LocalNode from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from tests.conftest import _make_local_connection def test_connection(): """Test that two OEF local connection can connect to a local node.""" with LocalNode() as node: multiplexer1 = Multiplexer( [_make_local_connection("multiplexer1", "my_public_key_1", node)] ) multiplexer2 = Multiplexer( [_make_local_connection("multiplexer2", "my_public_key_2", node)] ) multiplexer1.connect() multiplexer2.connect() multiplexer1.disconnect() multiplexer2.disconnect() @pytest.mark.asyncio async def test_connection_twice_return_none(): """Test that connecting twice works.""" with LocalNode() as node: address = "address" public_key = "public_key" connection = _make_local_connection(address, public_key, node) await connection.connect() await node.connect(address, connection._reader) message = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_envelope = Envelope( to=address, sender=address, message=message, ) await connection.send(expected_envelope) actual_envelope = await connection.receive() assert expected_envelope == actual_envelope await connection.disconnect() @pytest.mark.asyncio async def test_receiving_when_not_connected_raise_exception(): """Test that when we try to receive an envelope from a not connected connection we raise exception.""" with LocalNode() as node: with pytest.raises(ConnectionError): address = "address" public_key = "public_key" connection = _make_local_connection(address, public_key, node) await connection.receive() @pytest.mark.asyncio async def test_receiving_returns_none_when_error_occurs(): """Test that when we try to receive an envelope and an error occurs we return None.""" with LocalNode() as node: address = "address" public_key = "public_key" connection = _make_local_connection(address, public_key, node) await connection.connect() with unittest.mock.patch.object( connection._reader, "get", side_effect=Exception ): result = await connection.receive() assert result is None await connection.disconnect() def test_communication(): """Test that two multiplexer can communicate through the node.""" with LocalNode() as node: multiplexer1 = Multiplexer( [_make_local_connection("multiplexer1", "multiplexer1_public_key", node)] ) multiplexer2 = Multiplexer( [_make_local_connection("multiplexer2", "multiplexer1_public_key", node)] ) multiplexer1.connect() multiplexer2.connect() msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to="multiplexer2", sender="multiplexer1", message=msg, ) multiplexer1.put(envelope) msg = FipaMessage( performative=FipaMessage.Performative.CFP, dialogue_reference=(str(0), ""), message_id=1, target=0, query=Query([Constraint("something", ConstraintType(">", 1))]), ) envelope = Envelope( to="multiplexer2", sender="multiplexer1", message=msg, ) multiplexer1.put(envelope) msg = FipaMessage( performative=FipaMessage.Performative.PROPOSE, dialogue_reference=(str(0), ""), message_id=2, target=1, proposal=Description({}), ) envelope = Envelope( to="multiplexer2", sender="multiplexer1", message=msg, ) multiplexer1.put(envelope) msg = FipaMessage( performative=FipaMessage.Performative.ACCEPT, dialogue_reference=(str(0), ""), message_id=1, target=0, ) envelope = Envelope( to="multiplexer2", sender="multiplexer1", message=msg, ) multiplexer1.put(envelope) msg = FipaMessage( performative=FipaMessage.Performative.DECLINE, dialogue_reference=(str(0), ""), message_id=1, target=0, ) envelope = Envelope( to="multiplexer2", sender="multiplexer1", message=msg, ) multiplexer1.put(envelope) envelope = multiplexer2.get(block=True, timeout=1.0) msg = envelope.message assert ( envelope.protocol_specification_id == DefaultMessage.protocol_specification_id ) assert msg.content == b"hello" envelope = multiplexer2.get(block=True, timeout=1.0) msg = envelope.message assert ( envelope.protocol_specification_id == FipaMessage.protocol_specification_id ) assert msg.performative == FipaMessage.Performative.CFP envelope = multiplexer2.get(block=True, timeout=1.0) msg = envelope.message assert ( envelope.protocol_specification_id == FipaMessage.protocol_specification_id ) assert msg.performative == FipaMessage.Performative.PROPOSE envelope = multiplexer2.get(block=True, timeout=1.0) msg = envelope.message assert ( envelope.protocol_specification_id == FipaMessage.protocol_specification_id ) assert msg.performative == FipaMessage.Performative.ACCEPT envelope = multiplexer2.get(block=True, timeout=1.0) msg = envelope.message assert ( envelope.protocol_specification_id == FipaMessage.protocol_specification_id ) assert msg.performative == FipaMessage.Performative.DECLINE multiplexer1.disconnect() multiplexer2.disconnect() @pytest.mark.asyncio async def test_connecting_to_node_with_same_key(): """Test that connecting twice with the same key works correctly.""" with LocalNode() as node: address = "my_address" my_queue = asyncio.Queue() ret = await node.connect(address, my_queue) assert ret is not None and isinstance(ret, asyncio.Queue) ret = await node.connect(address, my_queue) assert ret is None def test_stop_before_start(): """Test stop called before start.""" node = LocalNode() with pytest.raises(ValueError, match="Connection not started!"): node.stop() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_local/test_search_services.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the search feature of the local OEF node.""" import unittest.mock from typing import cast import pytest from aea.common import Address from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Query, ) from aea.mail.base import Envelope, EnvelopeContext, Message from aea.multiplexer import InBox, Multiplexer from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.local.connection import ( LocalNode, OEFLocalConnection, OEF_LOCAL_NODE_ADDRESS, OEF_LOCAL_NODE_SEARCH_ADDRESS, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.dialogues import FipaDialogue, FipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.oef_search.dialogues import OefSearchDialogue from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage from tests.common.mocks import AnyStringWith from tests.common.utils import wait_for_condition from tests.conftest import MAX_FLAKY_RERUNS, _make_local_connection class OefSearchDialogues(BaseOefSearchDialogues): """The dialogues class keeps track of all http dialogues.""" def __init__(self, self_address: Address, **kwargs) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return OefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class TestNoValidDialogue: """Test that the search request returns an empty search result.""" @classmethod def setup_class(cls): """Set up the test.""" cls.node = LocalNode() cls.node.start() cls.address_1 = "address_1" cls.public_key_1 = "public_key_1" cls.connection = _make_local_connection( cls.address_1, cls.public_key_1, cls.node, ) cls.multiplexer = Multiplexer([cls.connection]) cls.multiplexer.connect() cls.dialogues = OefSearchDialogues(cls.address_1) @pytest.mark.asyncio async def test_wrong_dialogue(self): """Test that at the beginning, the search request returns an empty search result.""" query = Query( constraints=[Constraint("foo", ConstraintType("==", 1))], model=None ) # build and send the request search_services_request = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, message_id=2, target=1, dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), query=query, ) search_services_request.to = OEF_LOCAL_NODE_SEARCH_ADDRESS # the incorrect message cannot be sent into a dialogue, so this is omitted. search_services_request.sender = self.address_1 envelope = Envelope( to=search_services_request.to, sender=search_services_request.sender, message=search_services_request, ) with unittest.mock.patch.object( self.node, "_handle_oef_message", side_effect=self.node._handle_oef_message ) as mock_handle: with unittest.mock.patch.object(self.node.logger, "warning") as mock_logger: self.multiplexer.put(envelope) wait_for_condition(lambda: mock_handle.called, timeout=1.0) mock_logger.assert_any_call( AnyStringWith("Could not create dialogue for message=") ) @classmethod def teardown_class(cls): """Teardown the test.""" cls.multiplexer.disconnect() cls.node.stop() class TestEmptySearch: """Test that the search request returns an empty search result.""" @classmethod def setup_class(cls): """Set up the test.""" cls.node = LocalNode() cls.node.start() cls.address_1 = "address_1" cls.public_key_1 = "public_key_1" cls.multiplexer = Multiplexer( [ _make_local_connection( cls.address_1, cls.public_key_1, cls.node, ) ] ) cls.multiplexer.connect() cls.dialogues = OefSearchDialogues(cls.address_1) def test_empty_search_result(self): """Test that at the beginning, the search request returns an empty search result.""" search_services_request, sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query(constraints=[], model=None), ) envelope = Envelope( to=search_services_request.to, sender=search_services_request.sender, message=search_services_request, ) self.multiplexer.put(envelope) # check the result response_envelope = self.multiplexer.get(block=True, timeout=2.0) assert ( response_envelope.protocol_specification_id == OefSearchMessage.protocol_specification_id ) search_result = cast(OefSearchMessage, response_envelope.message) response_dialogue = self.dialogues.update(search_result) assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert search_result.agents == () @classmethod def teardown_class(cls): """Teardown the test.""" cls.multiplexer.disconnect() cls.node.stop() class TestSimpleSearchResult: """Test that a simple search result return the expected result.""" def setup(self): """Set up the test.""" self.node = LocalNode() self.node.start() self.address_1 = "address" self.public_key_1 = "public_key_1" self.multiplexer = Multiplexer( [ _make_local_connection( self.address_1, self.public_key_1, self.node, ) ] ) self.multiplexer.connect() # register a service. self.dialogues = OefSearchDialogues(self.address_1) self.data_model = DataModel( "foobar", attributes=[Attribute("foo", int, True), Attribute("bar", str, True)], ) service_description = Description( {"foo": 1, "bar": "baz"}, data_model=self.data_model ) register_service_request, self.sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=register_service_request.to, sender=register_service_request.sender, message=register_service_request, ) self.multiplexer.put(envelope) @pytest.mark.flaky( reruns=MAX_FLAKY_RERUNS ) # TODO: check reasons!. quite unstable test def test_not_empty_search_result(self): """Test that the search result contains one entry after a successful registration.""" # build and send the request search_services_request, sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query(constraints=[], model=self.data_model), ) envelope = Envelope( to=search_services_request.to, sender=search_services_request.sender, message=search_services_request, ) self.multiplexer.put(envelope) # check the result response_envelope = self.multiplexer.get(block=True, timeout=2.0) assert ( response_envelope.protocol_specification_id == OefSearchMessage.protocol_specification_id ) search_result = cast(OefSearchMessage, response_envelope.message) response_dialogue = self.dialogues.update(search_result) assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert search_result.agents == (self.address_1,) def teardown(self): """Teardown the test.""" self.multiplexer.disconnect() self.node.stop() class TestUnregister: """Test that the unregister service results to Error Message.""" @classmethod def setup_class(cls): """Set up the test.""" cls.node = LocalNode() cls.node.start() cls.address_1 = "address_1" cls.public_key_1 = "public_key_1" cls.multiplexer1 = Multiplexer( [ _make_local_connection( cls.address_1, cls.public_key_1, cls.node, ) ] ) cls.address_2 = "address_2" cls.public_key_2 = "public_key_2" cls.multiplexer2 = Multiplexer( [ _make_local_connection( cls.address_2, cls.public_key_2, cls.node, ) ] ) cls.multiplexer1.connect() cls.multiplexer2.connect() cls.dialogues = OefSearchDialogues(cls.address_1) def test_unregister_service_result(self): """Test that at the beginning, the search request returns an empty search result.""" data_model = DataModel( "foobar", attributes=[Attribute("foo", int, True), Attribute("bar", str, True)], ) service_description = Description( {"foo": 1, "bar": "baz"}, data_model=data_model ) msg, sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) self.multiplexer1.put(envelope) # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) assert ( response_envelope.protocol_specification_id == OefSearchMessage.protocol_specification_id ) response = cast(OefSearchMessage, response_envelope.message) response_dialogue = self.dialogues.update(response) assert response_dialogue == sending_dialogue assert response.performative == OefSearchMessage.Performative.OEF_ERROR msg, sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) self.multiplexer1.put(envelope) # Search for the registered service msg, sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query([Constraint("foo", ConstraintType("==", 1))]), ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) self.multiplexer1.put(envelope) # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) search_result = cast(OefSearchMessage, response_envelope.message) response_dialogue = self.dialogues.update(search_result) assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert len(search_result.agents) == 1 # unregister the service msg, sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) self.multiplexer1.put(envelope) # the same query returns empty # Search for the register agent msg, sending_dialogue = self.dialogues.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query([Constraint("foo", ConstraintType("==", 1))]), ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) self.multiplexer1.put(envelope) # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) search_result = cast(OefSearchMessage, response_envelope.message) response_dialogue = self.dialogues.update(search_result) assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert search_result.agents == () @classmethod def teardown_class(cls): """Teardown the test.""" cls.multiplexer1.disconnect() cls.multiplexer2.disconnect() cls.node.stop() class TestAgentMessage: """Test the the OEF will return Dialogue Error if it doesn't know the agent address.""" @classmethod def setup_class(cls): """Set up the test.""" cls.node = LocalNode() cls.node.start() cls.address_1 = "address_1" cls.public_key_1 = "public_key_1" cls.multiplexer1 = Multiplexer( [ _make_local_connection( cls.address_1, cls.public_key_1, cls.node, ) ] ) def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.SELLER cls.dialogues = FipaDialogues(cls.address_1, role_from_first_message) @pytest.mark.asyncio async def test_messages(self): """Test that at the beginning, the search request returns an empty search result.""" msg, sending_dialogue = self.dialogues.create( counterparty="some_agent", performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) with pytest.raises(ConnectionError): await _make_local_connection( self.address_1, self.public_key_1, self.node, ).send(envelope) self.multiplexer1.connect() msg, sending_dialogue = self.dialogues.create( counterparty="this_address_does_not_exist", performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) self.multiplexer1.put(envelope) # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) assert ( response_envelope.protocol_specification_id == DefaultMessage.protocol_specification_id ) assert response_envelope.sender == OEF_LOCAL_NODE_ADDRESS result = response_envelope.message assert result.performative == DefaultMessage.Performative.ERROR @classmethod def teardown_class(cls): """Teardown the test.""" cls.multiplexer1.disconnect() cls.node.stop() class TestFilteredSearchResult: """Test that the query system of the search gives the expected result.""" @classmethod def setup_class(cls): """Set up the test.""" cls.node = LocalNode() cls.node.start() cls.address_1 = "multiplexer1" cls.public_key_1 = "public_key_1" cls.address_2 = "multiplexer2" cls.public_key_2 = "public_key_2" cls.multiplexer1 = Multiplexer( [ _make_local_connection( cls.address_1, cls.public_key_1, cls.node, ) ] ) cls.multiplexer2 = Multiplexer( [ _make_local_connection( cls.address_2, cls.public_key_2, cls.node, ) ] ) cls.multiplexer1.connect() cls.multiplexer2.connect() cls.dialogues1 = OefSearchDialogues(cls.address_1) cls.dialogues2 = OefSearchDialogues(cls.address_2) # register 'multiplexer1' as a service 'foobar'. cls.data_model_foobar = DataModel( "foobar", attributes=[Attribute("foo", int, True), Attribute("bar", str, True)], ) service_description = Description( {"foo": 1, "bar": "baz"}, data_model=cls.data_model_foobar ) register_service_request, sending_dialogue = cls.dialogues1.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=register_service_request.to, sender=register_service_request.sender, message=register_service_request, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) cls.multiplexer1.put(envelope) wait_for_condition(lambda: len(cls.node.services) == 1, timeout=10) # register 'multiplexer2' as a service 'barfoo'. cls.data_model_barfoo = DataModel( "barfoo", attributes=[Attribute("foo", int, True), Attribute("bar", str, True)], ) service_description = Description( {"foo": 1, "bar": "baz"}, data_model=cls.data_model_barfoo ) register_service_request, sending_dialogue = cls.dialogues2.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=register_service_request.to, sender=register_service_request.sender, message=register_service_request, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) cls.multiplexer2.put(envelope) wait_for_condition(lambda: len(cls.node.services) == 2, timeout=10) # unregister multiplexer1 data_model = DataModel( "foobar", attributes=[Attribute("foo", int, True), Attribute("bar", str, True)], ) service_description = Description( {"foo": 1, "bar": "baz"}, data_model=data_model ) msg, sending_dialogue = cls.dialogues1.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) cls.multiplexer1.put(envelope) # ensure one service stays registered wait_for_condition(lambda: len(cls.node.services) == 1, timeout=10) def test_filtered_search_result(self): """Test that the search result contains only the entries matching the query.""" # build and send the request search_services_request, sending_dialogue = self.dialogues1.create( counterparty=OEF_LOCAL_NODE_SEARCH_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query(constraints=[], model=self.data_model_barfoo), ) envelope = Envelope( to=search_services_request.to, sender=search_services_request.sender, message=search_services_request, context=EnvelopeContext(connection_id=OEFLocalConnection.connection_id), ) self.multiplexer1.put(envelope) # check the result response_envelope = InBox(self.multiplexer1).get(block=True, timeout=5.0) search_result = cast(OefSearchMessage, response_envelope.message) response_dialogue = self.dialogues1.update(search_result) assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert search_result.agents == (self.address_2,), self.node.services @classmethod def teardown_class(cls): """Teardown the test.""" cls.multiplexer1.disconnect() cls.multiplexer2.disconnect() cls.node.stop() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_oef/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the OEF channel.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_oef/test_communication.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the OEF communication using an OEF.""" import asyncio import logging import sys import time import unittest import warnings from contextlib import suppress from typing import cast from unittest import mock from unittest.mock import patch import pytest from oef.messages import OEFErrorOperation from oef.query import ConstraintExpr from aea.common import Address from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, ConstraintTypes, DataModel, Description, Location, Query, ) from aea.mail.base import Envelope from aea.mail.base_pb2 import DialogueMessage from aea.mail.base_pb2 import Message as ProtobufMessage from aea.multiplexer import Multiplexer from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.oef.connection import OEFObjectTranslator from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa import fipa_pb2 from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.oef_search.dialogues import OefSearchDialogue from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage from tests.common.utils import UseOef from tests.conftest import ( FETCHAI_ADDRESS_ONE, FETCHAI_ADDRESS_TWO, _make_oef_connection, ) logger = logging.getLogger(__name__) SOME_SKILL_ID = "some/skill:0.1.0" DUMMY_PUBLIC_KEY = "some_public_key" class OefSearchDialogues(BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: str) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return OefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class TestDefault(UseOef): """Test that the default protocol is correctly implemented by the OEF channel.""" @classmethod def setup_class(cls): """Set the test up.""" cls.connection = _make_oef_connection( FETCHAI_ADDRESS_ONE, DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) cls.multiplexer = Multiplexer( [cls.connection], protocols=[FipaMessage, DefaultMessage] ) cls.multiplexer.connect() def test_send_message(self): """Test that a default byte message can be sent correctly.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) self.multiplexer.put( Envelope( to=FETCHAI_ADDRESS_ONE, sender=FETCHAI_ADDRESS_ONE, message=msg, ) ) recv_msg = self.multiplexer.get(block=True, timeout=3.0) assert recv_msg is not None @classmethod def teardown_class(cls): """Teardowm the test.""" cls.multiplexer.disconnect() class TestOEF(UseOef): """Test that the OEF protocol is correctly implemented by the OEF channel.""" class TestSearchServices: """Tests related to service search functionality.""" def setup(self): """Set the test up.""" self.connection = _make_oef_connection( FETCHAI_ADDRESS_ONE, DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) self.multiplexer = Multiplexer( [self.connection], protocols=[FipaMessage, DefaultMessage] ) self.multiplexer.connect() self.oef_search_dialogues = OefSearchDialogues(SOME_SKILL_ID) def test_search_services_with_query_without_model(self): """Test that a search services request can be sent correctly. In this test, the query has no data model. """ search_query_empty_model = Query( [Constraint("foo", ConstraintType("==", "bar"))], model=None ) oef_search_request, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(self.connection.connection_id), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=search_query_empty_model, ) self.multiplexer.put( Envelope( to=oef_search_request.to, sender=oef_search_request.sender, message=oef_search_request, ) ) envelope = self.multiplexer.get(block=True, timeout=5.0) oef_search_response = envelope.message oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative == OefSearchMessage.Performative.SEARCH_RESULT ) assert oef_search_dialogue is not None assert oef_search_dialogue == sending_dialogue assert oef_search_response.agents == () def test_search_services_with_query_with_model(self): """Test that a search services request can be sent correctly. In this test, the query has a simple data model. """ data_model = DataModel("foobar", [Attribute("foo", str, True)]) search_query = Query( [Constraint("foo", ConstraintType("==", "bar"))], model=data_model ) oef_search_request, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(self.connection.connection_id), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=search_query, ) self.multiplexer.put( Envelope( to=oef_search_request.to, sender=oef_search_request.sender, message=oef_search_request, ) ) envelope = self.multiplexer.get(block=True, timeout=5.0) oef_search_response = envelope.message oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative == OefSearchMessage.Performative.SEARCH_RESULT ) assert oef_search_dialogue is not None assert oef_search_dialogue == sending_dialogue assert oef_search_response.agents == () def test_search_services_with_distance_query(self): """Test that a search services request can be sent correctly. In this test, the query has a simple data model. """ tour_eiffel = Location(48.8581064, 2.29447) attribute = Attribute("latlon", Location, True) data_model = DataModel("geolocation", [attribute]) search_query = Query( [ Constraint( attribute.name, ConstraintType("distance", (tour_eiffel, 1.0)) ) ], model=data_model, ) oef_search_request, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(self.connection.connection_id), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=search_query, ) self.multiplexer.put( Envelope( to=oef_search_request.to, sender=oef_search_request.sender, message=oef_search_request, ) ) envelope = self.multiplexer.get(block=True, timeout=5.0) oef_search_response = envelope.message oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative == OefSearchMessage.Performative.SEARCH_RESULT ) assert oef_search_dialogue is not None assert oef_search_dialogue == sending_dialogue assert oef_search_response.agents == () def teardown(self): """Teardowm the test.""" self.multiplexer.disconnect() class TestRegisterService: """Tests related to service registration functionality.""" @classmethod def setup_class(cls): """Set the test up.""" cls.connection = _make_oef_connection( FETCHAI_ADDRESS_ONE, DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) cls.multiplexer = Multiplexer( [cls.connection], protocols=[FipaMessage, DefaultMessage] ) cls.multiplexer.connect() cls.oef_search_dialogues = OefSearchDialogues(SOME_SKILL_ID) def test_register_service(self): """Test that a register service request works correctly.""" foo_datamodel = DataModel( "foo", [Attribute("bar", int, True, "A bar attribute.")] ) desc = Description({"bar": 1}, data_model=foo_datamodel) oef_search_registration, _ = self.oef_search_dialogues.create( counterparty=str(self.connection.connection_id), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=desc, ) self.multiplexer.put( Envelope( to=oef_search_registration.to, sender=oef_search_registration.sender, message=oef_search_registration, ) ) time.sleep(1) oef_search_request, sending_dialogue_2 = self.oef_search_dialogues.create( counterparty=str(self.connection.connection_id), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query( [Constraint("bar", ConstraintType("==", 1))], model=foo_datamodel ), ) self.multiplexer.put( Envelope( to=oef_search_request.to, sender=oef_search_request.sender, message=oef_search_request, ) ) envelope = self.multiplexer.get(block=True, timeout=5.0) oef_search_response = envelope.message oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative == OefSearchMessage.Performative.SEARCH_RESULT ) assert oef_search_dialogue == sending_dialogue_2 assert oef_search_response.agents == ( FETCHAI_ADDRESS_ONE, ), "search_result.agents != [FETCHAI_ADDRESS_ONE] FAILED in test_oef/test_communication.py" @classmethod def teardown_class(cls): """Teardowm the test.""" cls.multiplexer.disconnect() class TestUnregisterService: """Tests related to service unregistration functionality.""" @classmethod def setup_class(cls): """ Set the test up. Steps: - Register a service - Check that the registration worked. """ cls.connection = _make_oef_connection( FETCHAI_ADDRESS_ONE, DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) cls.multiplexer = Multiplexer( [cls.connection], protocols=[FipaMessage, DefaultMessage] ) cls.multiplexer.connect() cls.oef_search_dialogues = OefSearchDialogues(SOME_SKILL_ID) cls.foo_datamodel = DataModel( "foo", [Attribute("bar", int, True, "A bar attribute.")] ) cls.desc = Description({"bar": 1}, data_model=cls.foo_datamodel) oef_search_registration, _ = cls.oef_search_dialogues.create( counterparty=str(cls.connection.connection_id), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=cls.desc, ) cls.multiplexer.put( Envelope( to=oef_search_registration.to, sender=oef_search_registration.sender, message=oef_search_registration, ) ) time.sleep(1.0) (oef_search_request, sending_dialogue_2,) = cls.oef_search_dialogues.create( counterparty=str(cls.connection.connection_id), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query( [Constraint("bar", ConstraintType("==", 1))], model=cls.foo_datamodel, ), ) cls.multiplexer.put( Envelope( to=oef_search_request.to, sender=oef_search_request.sender, message=oef_search_request, ) ) envelope = cls.multiplexer.get(block=True, timeout=5.0) oef_search_response = envelope.message oef_search_dialogue = cls.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative == OefSearchMessage.Performative.SEARCH_RESULT ) assert oef_search_dialogue == sending_dialogue_2 assert oef_search_response.agents == ( FETCHAI_ADDRESS_ONE, ), "search_result.agents != [FETCHAI_ADDRESS_ONE] FAILED in test_oef/test_communication.py" def test_unregister_service(self): """Test that an unregister service request works correctly. Steps: 2. unregister the service 3. search for that service 4. assert that no result is found. """ oef_search_deregistration, _ = self.oef_search_dialogues.create( counterparty=str(self.connection.connection_id), performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=self.desc, ) self.multiplexer.put( Envelope( to=oef_search_deregistration.to, sender=oef_search_deregistration.sender, message=oef_search_deregistration, ) ) time.sleep(1.0) oef_search_request, sending_dialogue_2 = self.oef_search_dialogues.create( counterparty=str(self.connection.connection_id), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query( [Constraint("bar", ConstraintType("==", 1))], model=self.foo_datamodel, ), ) self.multiplexer.put( Envelope( to=oef_search_request.to, sender=oef_search_request.sender, message=oef_search_request, ) ) envelope = self.multiplexer.get(block=True, timeout=5.0) oef_search_response = envelope.message oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative == OefSearchMessage.Performative.SEARCH_RESULT ) assert oef_search_dialogue == sending_dialogue_2 assert oef_search_response.agents == () @classmethod def teardown_class(cls): """Teardown the test.""" cls.multiplexer.disconnect() class TestFIPA(UseOef): """Test that the FIPA protocol is correctly implemented by the OEF channel.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.connection1 = _make_oef_connection( FETCHAI_ADDRESS_ONE, DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) cls.connection2 = _make_oef_connection( FETCHAI_ADDRESS_TWO, DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) cls.multiplexer1 = Multiplexer( [cls.connection1], protocols=[FipaMessage, DefaultMessage] ) cls.multiplexer2 = Multiplexer( [cls.connection2], protocols=[FipaMessage, DefaultMessage] ) cls.multiplexer1.connect() cls.multiplexer2.connect() def test_cfp(self): """Test that a CFP can be sent correctly.""" cfp_message = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) cfp_message.to = FETCHAI_ADDRESS_TWO cfp_message.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=cfp_message.to, sender=cfp_message.sender, message=cfp_message, ) ) envelope = self.multiplexer2.get(block=True, timeout=5.0) expected_cfp_message = FipaMessage.serializer.decode(envelope.message) expected_cfp_message.to = cfp_message.to expected_cfp_message.sender = cfp_message.sender assert expected_cfp_message == cfp_message cfp_none = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) cfp_none.to = FETCHAI_ADDRESS_TWO cfp_none.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=cfp_none.to, sender=cfp_none.sender, message=cfp_none, ) ) envelope = self.multiplexer2.get(block=True, timeout=5.0) expected_cfp_none = FipaMessage.serializer.decode(envelope.message) expected_cfp_none.to = cfp_none.to expected_cfp_none.sender = cfp_none.sender assert expected_cfp_none == cfp_none def test_propose(self): """Test that a Propose can be sent correctly.""" propose_empty = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.PROPOSE, proposal=Description({"foo": "bar"}), ) propose_empty.to = FETCHAI_ADDRESS_TWO propose_empty.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=propose_empty.to, sender=propose_empty.sender, message=propose_empty, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_propose_empty = FipaMessage.serializer.decode(envelope.message) expected_propose_empty.to = propose_empty.to expected_propose_empty.sender = propose_empty.sender assert expected_propose_empty == propose_empty propose_descriptions = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.PROPOSE, proposal=Description( {"foo": "bar"}, DataModel("foobar", [Attribute("foo", str, True)]) ), ) propose_descriptions.to = FETCHAI_ADDRESS_TWO propose_descriptions.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=propose_descriptions.to, sender=propose_descriptions.sender, message=propose_descriptions, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_propose_descriptions = FipaMessage.serializer.decode(envelope.message) expected_propose_descriptions.to = propose_descriptions.to expected_propose_descriptions.sender = propose_descriptions.sender assert expected_propose_descriptions == propose_descriptions def test_accept(self): """Test that an Accept can be sent correctly.""" accept = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT, ) accept.to = FETCHAI_ADDRESS_TWO accept.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=accept.to, sender=accept.sender, message=accept, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_accept = FipaMessage.serializer.decode(envelope.message) expected_accept.to = accept.to expected_accept.sender = accept.sender assert expected_accept == accept def test_match_accept(self): """Test that a match accept can be sent correctly.""" # NOTE since the OEF SDK doesn't support the match accept, we have to use a fixed message id! match_accept = FipaMessage( message_id=4, dialogue_reference=(str(0), ""), target=3, performative=FipaMessage.Performative.MATCH_ACCEPT, ) match_accept.to = FETCHAI_ADDRESS_TWO match_accept.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=match_accept.to, sender=match_accept.sender, message=match_accept, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_match_accept = FipaMessage.serializer.decode(envelope.message) expected_match_accept.to = match_accept.to expected_match_accept.sender = match_accept.sender assert expected_match_accept == match_accept def test_decline(self): """Test that a Decline can be sent correctly.""" decline = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.DECLINE, ) decline.to = FETCHAI_ADDRESS_TWO decline.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=decline.to, sender=decline.sender, message=decline, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_decline = FipaMessage.serializer.decode(envelope.message) expected_decline.to = decline.to expected_decline.sender = decline.sender assert expected_decline == decline def test_match_accept_w_inform(self): """Test that a match accept with inform can be sent correctly.""" match_accept_w_inform = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"address": "my_address"}, ) match_accept_w_inform.to = FETCHAI_ADDRESS_TWO match_accept_w_inform.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=match_accept_w_inform.to, sender=match_accept_w_inform.sender, message=match_accept_w_inform, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) returned_match_accept_w_inform = FipaMessage.serializer.decode(envelope.message) returned_match_accept_w_inform.to = match_accept_w_inform.to returned_match_accept_w_inform.sender = match_accept_w_inform.sender assert returned_match_accept_w_inform == match_accept_w_inform def test_accept_w_inform(self): """Test that an accept with address can be sent correctly.""" accept_w_inform = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT_W_INFORM, info={"address": "my_address"}, ) accept_w_inform.to = FETCHAI_ADDRESS_TWO accept_w_inform.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=accept_w_inform.to, sender=accept_w_inform.sender, message=accept_w_inform, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) returned_accept_w_inform = FipaMessage.serializer.decode(envelope.message) returned_accept_w_inform.to = accept_w_inform.to returned_accept_w_inform.sender = accept_w_inform.sender assert returned_accept_w_inform == accept_w_inform def test_inform(self): """Test that an inform can be sent correctly.""" payload = {"foo": "bar"} inform = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.INFORM, info=payload, ) inform.to = FETCHAI_ADDRESS_TWO inform.sender = FETCHAI_ADDRESS_ONE self.multiplexer1.put( Envelope( to=inform.to, sender=inform.sender, message=inform, ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) returned_inform = FipaMessage.serializer.decode(envelope.message) returned_inform.to = inform.to returned_inform.sender = inform.sender assert returned_inform == inform def test_serialisation_fipa(self): """Tests a Value Error flag for wrong CFP query.""" def _encode_fipa_cfp(msg: FipaMessage) -> bytes: """Helper function to serialize FIPA CFP message.""" message_pb = ProtobufMessage() dialogue_message_pb = DialogueMessage() fipa_msg = fipa_pb2.FipaMessage() dialogue_message_pb.message_id = msg.message_id dialogue_reference = msg.dialogue_reference dialogue_message_pb.dialogue_starter_reference = dialogue_reference[0] dialogue_message_pb.dialogue_responder_reference = dialogue_reference[1] dialogue_message_pb.target = msg.target performative = fipa_pb2.FipaMessage.Cfp_Performative() # type: ignore # the following are commented to make the decoding to fail. # query = msg.query # noqa: E800 # Query.encode(performative.query, query) # noqa: E800 fipa_msg.cfp.CopyFrom(performative) dialogue_message_pb.content = fipa_msg.SerializeToString() message_pb.dialogue_message.CopyFrom(dialogue_message_pb) fipa_bytes = message_pb.SerializeToString() return fipa_bytes with pytest.raises(ValueError): msg = FipaMessage( performative=FipaMessage.Performative.CFP, message_id=1, dialogue_reference=(str(0), ""), target=0, query=Query([Constraint("something", ConstraintType(">", 1))]), ) with mock.patch.object( FipaMessage, "Performative" ) as mock_performative_enum: mock_performative_enum.CFP.value = "unknown" FipaMessage.serializer.encode(msg), "Raises Value Error" # with pytest.raises(EOFError): # noqa: E800 # cfp_msg = FipaMessage( # noqa: E800 # message_id=1, # noqa: E800 # dialogue_reference=(str(0), ""), # noqa: E800 # target=0, # noqa: E800 # performative=FipaMessage.Performative.CFP, # noqa: E800 # query=Query([Constraint("something", ConstraintType(">", 1))]), # noqa: E800 # ) # noqa: E800 # cfp_msg.set("query", "hello") # noqa: E800 # fipa_bytes = _encode_fipa_cfp(cfp_msg) # noqa: E800 # # The encoded message is not a valid FIPA message. # noqa: E800 # FipaMessage.serializer.decode(fipa_bytes) # noqa: E800 with pytest.raises(ValueError): cfp_msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) with mock.patch.object( FipaMessage, "Performative" ) as mock_performative_enum: mock_performative_enum.CFP.value = "unknown" fipa_bytes = _encode_fipa_cfp(cfp_msg) # The encoded message is not a FIPA message FipaMessage.serializer.decode(fipa_bytes) def test_on_oef_error(self): """Test the oef error.""" oef_connection = self.multiplexer1.connections[0] oef_channel = oef_connection.channel oef_channel.oef_msg_id += 1 dialogue_reference = ("1", "") query = Query( constraints=[Constraint("foo", ConstraintType("==", "bar"))], model=None, ) dialogues = oef_channel.oef_search_dialogues oef_search_msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, dialogue_reference=dialogue_reference, query=query, ) oef_search_msg.to = str(oef_connection.connection_id) oef_search_msg.sender = SOME_SKILL_ID dialogue = dialogues.update(oef_search_msg) assert dialogue is not None oef_channel.oef_msg_id_to_dialogue[oef_channel.oef_msg_id] = dialogue oef_channel.on_oef_error( answer_id=oef_channel.oef_msg_id, operation=OEFErrorOperation.SEARCH_SERVICES, ) envelope = self.multiplexer1.get(block=True, timeout=5.0) dec_msg = envelope.message assert dec_msg.dialogue_reference[0] == dialogue_reference[0] assert ( dec_msg.performative is OefSearchMessage.Performative.OEF_ERROR ), "It should be an error message" def test_send(self): """Test the send method.""" envelope = Envelope( to=str(self.connection1.connection_id), sender=SOME_SKILL_ID, protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"Hello", ) self.multiplexer1.put(envelope) received_envelope = self.multiplexer1.get(block=True, timeout=5.0) assert received_envelope is not None @classmethod def teardown_class(cls): """Teardown the test.""" cls.multiplexer1.disconnect() cls.multiplexer2.disconnect() class TestOefConnection(UseOef): """Tests the con.connection_status.is_connected property.""" def test_connection(self): """Test that an OEF connection can be established to the OEF.""" connection = _make_oef_connection( FETCHAI_ADDRESS_ONE, DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) multiplexer = Multiplexer([connection], protocols=[FipaMessage, DefaultMessage]) multiplexer.connect() multiplexer.disconnect() class TestOefConstraint: """Tests oef_constraint expressions.""" @classmethod def setup_class(cls): """ Set the test up. Steps: - Register a service - Check that the registration worked. """ cls.obj_transaltor = OEFObjectTranslator() def test_oef_constraint_types(self): """Test the constraint types of the OEF.""" with pytest.raises(ValueError): m_constraint = self.obj_transaltor.from_oef_constraint_type( ConstraintType(ConstraintTypes.EQUAL, "==") ) eq = self.obj_transaltor.to_oef_constraint_type(m_constraint) assert eq.value == "==" m_constraint = ConstraintType(ConstraintTypes.NOT_EQUAL, "!=") neq = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(neq) assert m_constraint == m_constr assert neq.value == "!=" m_constraint = ConstraintType(ConstraintTypes.LESS_THAN, "<") lt = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(lt) assert m_constraint == m_constr assert lt.value == "<" m_constraint = ConstraintType(ConstraintTypes.LESS_THAN_EQ, "<=") lt_eq = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(lt_eq) assert m_constraint == m_constr assert lt_eq.value == "<=" m_constraint = ConstraintType(ConstraintTypes.GREATER_THAN, ">") gt = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(gt) assert m_constraint == m_constr assert gt.value == ">" m_constraint = ConstraintType(ConstraintTypes.GREATER_THAN_EQ, ">=") gt_eq = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(gt_eq) assert m_constraint == m_constr assert gt_eq.value == ">=" m_constraint = ConstraintType("within", (-10.0, 10.0)) with_in = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(with_in) assert m_constraint == m_constr assert with_in._value[0] <= 10 <= with_in._value[1] m_constraint = ConstraintType("in", (1, 2, 3)) in_set = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(in_set) assert m_constraint == m_constr assert 2 in in_set._value m_constraint = ConstraintType("not_in", ("C", "Java", "Python")) not_in = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(not_in) assert m_constraint == m_constr assert "C++" not in not_in._value location = Location(47.692180, 10.039470) distance_float = 0.2 m_constraint = ConstraintType("distance", (location, distance_float)) distance = self.obj_transaltor.to_oef_constraint_type(m_constraint) m_constr = self.obj_transaltor.from_oef_constraint_type(distance) assert m_constraint == m_constr assert ( distance.center == self.obj_transaltor.to_oef_location(location) and distance.distance == distance_float ) with pytest.raises(ValueError): m_constraint = ConstraintType(ConstraintTypes.EQUAL, "foo") with mock.patch.object( m_constraint, "type", return_value="unknown_constraint_type" ): eq = self.obj_transaltor.to_oef_constraint_type(m_constraint) with pytest.raises(ValueError): self.obj_transaltor.from_oef_constraint_expr( oef_constraint_expr=cast(ConstraintExpr, DummyConstrainExpr()) ) def test_oef_constraint_expr(self): """Test the value error of constraint type.""" with pytest.raises(ValueError): self.obj_transaltor.to_oef_constraint_expr( constraint_expr=cast(ConstraintExpr, DummyConstrainExpr()) ) @classmethod def teardown_class(cls): """Teardown the test.""" pass class DummyConstrainExpr(ConstraintExpr): """This class is used to represent a constraint expression.""" def check(self, description: Description) -> bool: """ Check if a description satisfies the constraint expression. :param description: the description to check. :return: ``True`` if the description satisfy the constraint expression, ``False`` otherwise. """ pass def is_valid(self, data_model: DataModel) -> bool: """ Check whether a constraint expression is valid wrt a data model. Specifically, check the following conditions. - If all the attributes referenced by the constraints are correctly associated with the Data Model attributes. :param data_model: the data model used to check the validity of the constraint expression. :return: ``True`` if the constraint expression is valid wrt the data model, ``False`` otherwise. """ pass def _check_validity(self) -> None: """Check whether a Constraint Expression satisfies some basic requirements. E.g. an :class:`~oef.query.And` expression must have at least 2 subexpressions. :return ``None`` :raises ValueError: if the object does not satisfy some requirements. """ return @property def _node(self): pass class TestSendWithOEF(UseOef): """Test other usecases with OEF.""" @pytest.mark.asyncio async def test_send_oef_message(self, pytestconfig, caplog): """Test the send oef message.""" with warnings.catch_warnings(): warnings.simplefilter("ignore") oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, public_key=DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) await oef_connection.connect() oef_search_dialogues = OefSearchDialogues(SOME_SKILL_ID) msg = OefSearchMessage( performative=OefSearchMessage.Performative.OEF_ERROR, dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) msg.to = str(oef_connection.connection_id) msg.sender = SOME_SKILL_ID envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) with caplog.at_level(logging.DEBUG, "aea.packages.fetchai.connections.oef"): await oef_connection.send(envelope) assert "Could not create dialogue for message=" in caplog.text data_model = DataModel("foobar", attributes=[Attribute("foo", str, True)]) query = Query( constraints=[Constraint("foo", ConstraintType("==", "bar"))], model=data_model, ) msg, sending_dialogue = oef_search_dialogues.create( counterparty=str(oef_connection.connection_id), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) envelope = Envelope( to=msg.to, sender=msg.sender, message=msg, ) await oef_connection.send(envelope) envelope = await oef_connection.receive() search_result = envelope.message response_dialogue = oef_search_dialogues.update(search_result) assert ( search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT ) assert sending_dialogue == response_dialogue await asyncio.sleep(2.0) await oef_connection.disconnect() @pytest.mark.asyncio async def test_cancelled_receive(self, pytestconfig, caplog): """Test the case when a receive request is cancelled.""" oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, public_key=DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) await oef_connection.connect() with caplog.at_level(logging.DEBUG, "aea.packages.fetchai.connections.oef"): async def receive(): await oef_connection.receive() task = asyncio.ensure_future(receive(), loop=asyncio.get_event_loop()) await asyncio.sleep(0.1) task.cancel() await asyncio.sleep(0.1) await oef_connection.disconnect() assert "Receive cancelled." in caplog.text @pytest.mark.asyncio async def test_exception_during_receive(self, pytestconfig): """Test the case when there is an exception during a receive request.""" oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, public_key=DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) await oef_connection.connect() with unittest.mock.patch.object( oef_connection.channel._in_queue, "get", side_effect=Exception ): result = await oef_connection.receive() assert result is None await oef_connection.disconnect() @pytest.mark.asyncio async def test_connecting_twice_is_ok(self, pytestconfig): """Test that calling 'connect' twice works as expected.""" oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, public_key=DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=10000, ) assert not oef_connection.is_connected await oef_connection.connect() assert oef_connection.is_connected await oef_connection.connect() assert oef_connection.is_connected await oef_connection.disconnect() @pytest.mark.asyncio @pytest.mark.skipif( sys.version_info < (3, 7), reason="Python version < 3.7 not supported by the OEF." ) async def test_cannot_connect_to_oef(): """Test the case when we can't connect to the OEF.""" oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, public_key=DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=61234, # use addr instead of hostname to avoid name resolution ) with mock.patch.object(oef_connection.logger, "warning") as mock_logger: task = asyncio.ensure_future( oef_connection.connect(), loop=asyncio.get_event_loop() ) await asyncio.sleep(3.0) mock_logger.assert_any_call( "Cannot connect to OEFChannel. Retrying in 5 seconds..." ) with suppress(asyncio.CancelledError): task.cancel() await task await oef_connection.disconnect() @pytest.mark.asyncio @pytest.mark.skipif( sys.version_info < (3, 7), reason="Python version < 3.7 not supported by the OEF." ) async def test_methods_with_logging_only(): """Test the case when we can't connect to the OEF.""" oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, public_key=DUMMY_PUBLIC_KEY, oef_addr="127.0.0.1", oef_port=61234, # use addr instead of hostname to avoid name resolution ) with patch.object(oef_connection.channel, "_oef_agent_connect", return_value=True): await oef_connection.connect() oef_connection.channel.on_cfp( msg_id=1, dialogue_id=1, origin="some", target=1, query=b"" ) oef_connection.channel.on_decline(msg_id=1, dialogue_id=1, origin="some", target=1) oef_connection.channel.on_propose( msg_id=1, dialogue_id=1, origin="some", target=1, proposals=b"" ) oef_connection.channel.on_accept(msg_id=1, dialogue_id=1, origin="some", target=1) try: await oef_connection.disconnect() except Exception: # nosec pass ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_oef/test_models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the OEF models.""" import pickle # nosec from unittest import mock import pytest from aea.helpers.search.models import ( And, Attribute, Constraint, ConstraintType, DataModel, Description, Location, Not, Or, Query, ) from packages.fetchai.connections.oef.connection import OEFObjectTranslator class TestTranslator: """Test that the translation of the OEF classes from and to the SDK classes works correctly.""" def test_attribute(self): """Test that the translation for the Attribute class works.""" attribute = Attribute("foo", int, True, "a foo attribute.") oef_attribute = OEFObjectTranslator.to_oef_attribute(attribute) expected_attribute = OEFObjectTranslator.from_oef_attribute(oef_attribute) actual_attribute = attribute assert expected_attribute == actual_attribute def test_data_model(self): """Test that the translation for the DataModel class works.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) oef_data_model = OEFObjectTranslator.to_oef_data_model(data_model_foobar) expected_data_model = OEFObjectTranslator.from_oef_data_model(oef_data_model) actual_data_model = data_model_foobar assert expected_data_model == actual_data_model def test_description(self): """Test that the translation for the Description class works.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) description_foobar = Description( {"foo": 1, "bar": "baz"}, data_model=data_model_foobar ) oef_description = OEFObjectTranslator.to_oef_description(description_foobar) expected_description = OEFObjectTranslator.from_oef_description(oef_description) actual_description = description_foobar assert expected_description == actual_description m_desc = iter(description_foobar.values) assert next(m_desc) == "foo" assert {"foo", "bar"} == set(iter(description_foobar)) def test_query(self): """Test that the translation for the Query class works.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) query = Query( [ And( [ Or( [ Not(Constraint("foo", ConstraintType("==", 1))), Not(Constraint("bar", ConstraintType("==", "baz"))), ] ), Constraint("foo", ConstraintType("<", 2)), ] ) ], data_model_foobar, ) oef_query = OEFObjectTranslator.to_oef_query(query) expected_query = OEFObjectTranslator.from_oef_query(oef_query) actual_query = query assert expected_query == actual_query class TestPickable: """Test that the OEF objects can be pickled.""" def test_pickable_attribute(self): """Test that an istance of the Attribute class is pickable.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") try: pickle.dumps(attribute_foo) except Exception: pytest.fail("Error during pickling.") def test_pickable_data_model(self): """Test that an istance of the DataModel class is pickable.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) try: pickle.dumps(data_model_foobar) except Exception: pytest.fail("Error during pickling.") def test_pickable_description(self): """Test that an istance of the Description class is pickable.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) description_foobar = Description( {"foo": 1, "bar": "baz"}, data_model=data_model_foobar ) try: pickle.dumps(description_foobar) except Exception: pytest.fail("Error during pickling.") def test_pickable_query(self): """Test that an istance of the Query class is pickable.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) query = Query( [ And( [ Or( [ Not(Constraint("foo", ConstraintType("==", 1))), Not(Constraint("bar", ConstraintType("==", "baz"))), ] ), Constraint("foo", ConstraintType("<", 2)), ] ) ], data_model_foobar, ) try: pickle.dumps(query) except Exception: pytest.fail("Error during pickling.") class TestCheckValidity: """Test the initialization of the Constraint type.""" def test_validity(self): """Test the validity of the Constraint type.""" m_constraint = ConstraintType("==", 3) assert m_constraint.check(3) assert str(m_constraint.type) == "==" m_constraint = ConstraintType("!=", "London") assert m_constraint.check("Paris") assert str(m_constraint.type) == "!=" m_constraint = ConstraintType("<", 3.14) assert m_constraint.check(3.0) assert str(m_constraint.type) == "<" m_constraint = ConstraintType(">", 3.14) assert m_constraint.check(5.0) assert str(m_constraint.type) == ">" m_constraint = ConstraintType("<=", 5) assert m_constraint.check(5) assert str(m_constraint.type) == "<=" m_constraint = ConstraintType(">=", 5) assert m_constraint.check(5) assert str(m_constraint.type) == ">=" m_constraint = ConstraintType("within", (-10.0, 10.0)) assert m_constraint.check(5) assert str(m_constraint.type) == "within" m_constraint = ConstraintType("in", (1, 2, 3)) assert m_constraint.check(2) assert str(m_constraint.type) == "in" m_constraint = ConstraintType("not_in", ("C", "Java", "Python")) assert m_constraint.check("C++") assert str(m_constraint.type) == "not_in" tour_eiffel = Location(48.8581064, 2.29447) colosseum = Location(41.8902102, 12.4922309) le_jules_verne_restaurant = Location(48.8579675, 2.2951849) m_constraint = ConstraintType("distance", (tour_eiffel, 1.0)) assert m_constraint.check(tour_eiffel) assert m_constraint.check(le_jules_verne_restaurant) assert not m_constraint.check(colosseum) m_constraint.type = "unknown" with pytest.raises(ValueError): m_constraint.check("HelloWorld") m_constraint = ConstraintType("==", 3) with mock.patch("aea.helpers.search.models.ConstraintTypes") as mocked_types: mocked_types.EQUAL.value = "unknown" assert not m_constraint.check_validity(), "My constraint must not be valid" def test_not_check(self): """Test the not().check function.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) description_foobar = Description( {"foo": 1, "bar": "baz"}, data_model=data_model_foobar ) no_constraint_1 = Not(Constraint("foo", ConstraintType("==", 5))) assert no_constraint_1.check(description_foobar) no_constraint_2 = Not(Constraint("bar", ConstraintType("==", "hi"))) assert no_constraint_2.check(description_foobar) no_constraint_3 = Not(Constraint("foo", ConstraintType("==", 1))) assert not no_constraint_3.check(description_foobar) no_constraint_4 = Not(Constraint("bar", ConstraintType("==", "baz"))) assert not no_constraint_4.check(description_foobar) no_constraint_5 = Constraint("foo", ConstraintType("!=", 98273)) assert no_constraint_5.check(description_foobar) no_constraint_6 = Constraint("bar", ConstraintType("!=", "hello_again")) assert no_constraint_6.check(description_foobar) no_constraint_7 = Constraint("foo", ConstraintType("!=", 1)) assert not no_constraint_7.check(description_foobar) no_constraint_8 = Constraint("bar", ConstraintType("!=", "baz")) assert not no_constraint_8.check(description_foobar) def test_or_check(self): """Test the or().check function.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) description_foobar = Description( {"foo": 1, "bar": "baz"}, data_model=data_model_foobar ) constraint = Or( [ (Constraint("foo", ConstraintType("==", 1))), (Constraint("bar", ConstraintType("==", "baz"))), ] ) assert constraint.check(description_foobar) def test_and_check(self): """Test the and().check function.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) description_foobar = Description( {"foo": 1, "bar": "baz"}, data_model=data_model_foobar ) constraint = And( [ (Constraint("foo", ConstraintType("==", 1))), (Constraint("bar", ConstraintType("==", "baz"))), ] ) assert constraint.check(description_foobar) def test_query_check(self): """Test that the query.check() method works.""" attribute_foo = Attribute("foo", int, True, "a foo attribute.") attribute_bar = Attribute("bar", str, True, "a bar attribute.") data_model_foobar = DataModel( "foobar", [attribute_foo, attribute_bar], "A foobar data model." ) description_foobar = Description( {"foo": 1, "bar": "baz"}, data_model=data_model_foobar ) query = Query( [ And( [ Or( [ Not(Constraint("foo", ConstraintType("==", 1))), Not(Constraint("bar", ConstraintType("==", "baz"))), ] ), Constraint("foo", ConstraintType("<", 2)), ] ) ], data_model_foobar, ) assert not query.check(description=description_foobar) ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_oef/test_oef_serializer.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the OEF serializer.""" from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Location, Query, ) from packages.fetchai.connections.oef.object_translator import OEFObjectTranslator from packages.fetchai.protocols.oef_search.message import OefSearchMessage def test_oef_serialization_description(): """Testing the serialization of the OEF.""" foo_datamodel = DataModel( "foo", [ Attribute("bar", int, True, "A bar attribute."), Attribute("location", Location, True, "A location attribute."), ], ) desc = Description( {"bar": 1, "location": Location(10.0, 10.0)}, data_model=foo_datamodel ) msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, dialogue_reference=(str(1), ""), service_description=desc, ) msg_bytes = OefSearchMessage.serializer.encode(msg) assert len(msg_bytes) > 0 recovered_msg = OefSearchMessage.serializer.decode(msg_bytes) assert recovered_msg == msg def test_oef_object_transator(): """Test oef description and description tranlations.""" foo_datamodel = DataModel( "foo", [ Attribute("bar", int, True, "A bar attribute."), Attribute("location", Location, True, "A location attribute."), ], ) desc = Description( {"bar": 1, "location": Location(10.0, 10.0)}, data_model=foo_datamodel ) oef_desc = OEFObjectTranslator.to_oef_description(desc) new_desc = OEFObjectTranslator.from_oef_description(oef_desc) assert desc.values["location"] == new_desc.values["location"] def test_oef_serialization_query(): """Testing the serialization of the OEF.""" query = Query([Constraint("foo", ConstraintType("==", "bar"))], model=None) msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, dialogue_reference=(str(1), ""), query=query, ) msg_bytes = OefSearchMessage.serializer.encode(msg) assert len(msg_bytes) > 0 recovered_msg = OefSearchMessage.serializer.decode(msg_bytes) assert recovered_msg == msg ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the Libp2p client connection.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_client/test_aea_cli.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains AEA cli tests for Libp2p tcp client connection.""" import os from aea_ledger_fetchai import FetchAICrypto from aea.helpers.base import CertRequest from aea.multiplexer import Multiplexer from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.connections.p2p_libp2p_client.connection import PUBLIC_ID from tests.conftest import ( _make_libp2p_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 DEFAULT_DELEGATE_PORT = 11234 DEFAULT_HOST = "127.0.0.1" DEFAULT_CLIENTS_PER_NODE = 4 DEFAULT_LAUNCH_TIMEOUT = 10 @libp2p_log_on_failure_all class TestP2PLibp2pClientConnectionAEARunning(AEATestCaseEmpty): """Test AEA with p2p_libp2p_client connection is correctly run""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set up the test class.""" super(TestP2PLibp2pClientConnectionAEARunning, cls).setup_class() temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.node_connection = _make_libp2p_connection( data_dir=temp_dir, delegate_host=DEFAULT_HOST, delegate_port=DEFAULT_DELEGATE_PORT, delegate=True, ) cls.node_multiplexer = Multiplexer([cls.node_connection]) cls.log_files = [cls.node_connection.node.log_file] cls.node_multiplexer.connect() def test_node(self): """Test the node is connected.""" assert self.node_connection.is_connected is True def test_connection(self): """Test the connection can be used in an aea.""" self.generate_private_key() self.add_private_key() self.add_item("connection", str(PUBLIC_ID)) conn_path = "vendor.fetchai.connections.p2p_libp2p_client" self.nested_set_config( conn_path + ".config", { "nodes": [ { "uri": "{}:{}".format(DEFAULT_HOST, DEFAULT_DELEGATE_PORT), "public_key": self.node_connection.node.pub, } ] }, ) # generate certificates for connection self.nested_set_config( conn_path + ".cert_requests", [ CertRequest( identifier="acn", ledger_id=FetchAICrypto.identifier, not_after="2022-01-01", not_before="2021-01-01", public_key=self.node_connection.node.pub, message_format="{public_key}", save_path="./cli_test_cert.txt", ) ], ) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) process = self.run_agent() is_running = self.is_running(process, timeout=DEFAULT_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" check_strings = "Successfully connected to libp2p node {}:{}".format( DEFAULT_HOST, DEFAULT_DELEGATE_PORT ) missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) self.terminate_agents(process) assert self.is_successfully_terminated( process ), "AEA wasn't successfully terminated." @classmethod def teardown_class(cls): """Tear down the test""" cls.terminate_agents() super(TestP2PLibp2pClientConnectionAEARunning, cls).teardown_class() cls.node_multiplexer.disconnect() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_client/test_communication.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for Libp2p tcp client connection.""" import asyncio import os import shutil import tempfile import time from unittest import mock from unittest.mock import Mock, call import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.mail.base import Empty, Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.p2p_libp2p_client.connection import NodeClient, Uri from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.default.serialization import DefaultSerializer from tests.common.mocks import RegexComparator from tests.common.utils import wait_for_condition from tests.conftest import ( _make_libp2p_client_connection, _make_libp2p_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 DEFAULT_DELEGATE_PORT = 11234 DEFAULT_HOST = "127.0.0.1" DEFAULT_CLIENTS_PER_NODE = 1 MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) @pytest.mark.asyncio class TestLibp2pClientConnectionConnectDisconnect: """Test that connection is established and torn down correctly""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.connection_node = _make_libp2p_connection(data_dir=temp_dir, delegate=True) temp_dir_client = os.path.join(cls.t, "temp_dir_client") os.mkdir(temp_dir_client) cls.connection = _make_libp2p_client_connection( data_dir=temp_dir_client, peer_public_key=cls.connection_node.node.pub ) @pytest.mark.asyncio async def test_libp2pclientconnection_connect_disconnect(self): """Test connnect then disconnect.""" assert self.connection.is_connected is False try: await self.connection_node.connect() await self.connection.connect() assert self.connection.is_connected is True await self.connection.disconnect() assert self.connection.is_connected is False except Exception: raise finally: await self.connection_node.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestLibp2pClientConnectionEchoEnvelope: """Test that connection will route envelope to destination through the same libp2p node""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.connection_node = _make_libp2p_connection( data_dir=temp_dir, port=DEFAULT_PORT + 1, delegate=True ) cls.multiplexer_node = Multiplexer( [cls.connection_node], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node.node.log_file) cls.multiplexer_node.connect() try: temp_dir_client_1 = os.path.join(cls.t, "temp_dir_client_1") os.mkdir(temp_dir_client_1) cls.connection_client_1 = _make_libp2p_client_connection( data_dir=temp_dir_client_1, peer_public_key=cls.connection_node.node.pub, ledger_api_id=FetchAICrypto.identifier, ) cls.multiplexer_client_1 = Multiplexer( [cls.connection_client_1], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_1.connect() temp_dir_client_2 = os.path.join(cls.t, "temp_dir_client_2") os.mkdir(temp_dir_client_2) cls.connection_client_2 = _make_libp2p_client_connection( data_dir=temp_dir_client_2, peer_public_key=cls.connection_node.node.pub, ledger_api_id=EthereumCrypto.identifier, ) cls.multiplexer_client_2 = Multiplexer( [cls.connection_client_2], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_2.connect() wait_for_condition(lambda: cls.connection_client_1.is_connected is True, 10) wait_for_condition(lambda: cls.connection_client_2.is_connected is True, 10) except Exception: cls.multiplexer_node.disconnect() raise def test_connection_is_established(self): """Test connection is established.""" assert self.connection_client_1.is_connected is True assert self.connection_client_2.is_connected is True def test_envelope_routed(self): """Test the envelope is routed.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message def test_envelope_echoed_back(self): """Test the envelope is echoed back.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_2 self.multiplexer_client_2.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message def test_envelope_echoed_back_node_agent(self): """Test the envelope is echoed back.""" addr_1 = self.connection_client_1.address addr_n = self.connection_node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_n, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_node.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_n self.multiplexer_node.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message @classmethod def teardown_class(cls): """Tear down the test""" cls.multiplexer_client_1.disconnect() cls.multiplexer_client_2.disconnect() cls.multiplexer_node.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestLibp2pClientConnectionEchoEnvelopeTwoDHTNode: """Test that connection will route envelope to destination connected to different node""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.mutliplexers = [] temp_dir_node_1 = os.path.join(cls.t, "temp_dir_node_1") os.mkdir(temp_dir_node_1) cls.connection_node_1 = _make_libp2p_connection( data_dir=temp_dir_node_1, port=DEFAULT_PORT + 1, delegate_port=DEFAULT_DELEGATE_PORT + 1, delegate=True, ) cls.multiplexer_node_1 = Multiplexer( [cls.connection_node_1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_1.node.log_file) cls.multiplexer_node_1.connect() cls.mutliplexers.append(cls.multiplexer_node_1) genesis_peer = cls.connection_node_1.node.multiaddrs[0] temp_dir_node_2 = os.path.join(cls.t, "temp_dir_node_2") os.mkdir(temp_dir_node_2) try: cls.connection_node_2 = _make_libp2p_connection( data_dir=temp_dir_node_2, port=DEFAULT_PORT + 2, delegate_port=DEFAULT_DELEGATE_PORT + 2, entry_peers=[genesis_peer], delegate=True, ) cls.multiplexer_node_2 = Multiplexer( [cls.connection_node_2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_2.node.log_file) cls.multiplexer_node_2.connect() cls.mutliplexers.append(cls.multiplexer_node_2) temp_dir_client_1 = os.path.join(cls.t, "temp_dir_client_1") os.mkdir(temp_dir_client_1) cls.connection_client_1 = _make_libp2p_client_connection( data_dir=temp_dir_client_1, peer_public_key=cls.connection_node_1.node.pub, node_port=DEFAULT_DELEGATE_PORT + 1, ) cls.multiplexer_client_1 = Multiplexer( [cls.connection_client_1], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_1.connect() cls.mutliplexers.append(cls.multiplexer_client_1) temp_dir_client_2 = os.path.join(cls.t, "temp_dir_client_2") os.mkdir(temp_dir_client_2) cls.connection_client_2 = _make_libp2p_client_connection( data_dir=temp_dir_client_2, peer_public_key=cls.connection_node_2.node.pub, node_port=DEFAULT_DELEGATE_PORT + 2, ) cls.multiplexer_client_2 = Multiplexer( [cls.connection_client_2], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_2.connect() cls.mutliplexers.append(cls.multiplexer_client_2) wait_for_condition(lambda: cls.connection_node_1.is_connected is True, 10) wait_for_condition(lambda: cls.connection_node_2.is_connected is True, 10) wait_for_condition(lambda: cls.connection_client_1.is_connected is True, 10) wait_for_condition(lambda: cls.connection_client_2.is_connected is True, 10) except Exception: cls.teardown_class() raise def test_connection_is_established(self): """Test the connection is established.""" assert self.connection_node_1.is_connected is True assert self.connection_node_2.is_connected is True assert self.connection_client_1.is_connected is True assert self.connection_client_2.is_connected is True def test_envelope_routed(self): """Test the envelope is routed.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message def test_envelope_echoed_back(self): """Test the envelope is echoed back.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_2 self.multiplexer_client_2.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message def test_envelope_echoed_back_node_agent(self): """Test the envelope is echoed back node agent.""" addr_1 = self.connection_client_1.address addr_n = self.connection_node_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_n, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_node_2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_n self.multiplexer_node_2.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message @classmethod def teardown_class(cls): """Tear down the test""" for mux in reversed(cls.mutliplexers): mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestLibp2pClientConnectionRouting: """Test that libp2p DHT network will reliably route envelopes from clients connected to different nodes""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] try: temp_dir_node_1 = os.path.join(cls.t, "temp_dir_node_1") os.mkdir(temp_dir_node_1) cls.connection_node_1 = _make_libp2p_connection( data_dir=temp_dir_node_1, port=DEFAULT_PORT + 1, delegate_port=DEFAULT_DELEGATE_PORT + 1, delegate=True, ) cls.multiplexer_node_1 = Multiplexer( [cls.connection_node_1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_1.node.log_file) cls.multiplexer_node_1.connect() cls.multiplexers.append(cls.multiplexer_node_1) entry_peer = cls.connection_node_1.node.multiaddrs[0] temp_dir_node_2 = os.path.join(cls.t, "temp_dir_node_2") os.mkdir(temp_dir_node_2) cls.connection_node_2 = _make_libp2p_connection( data_dir=temp_dir_node_2, port=DEFAULT_PORT + 2, delegate_port=DEFAULT_DELEGATE_PORT + 2, entry_peers=[entry_peer], delegate=True, ) cls.multiplexer_node_2 = Multiplexer( [cls.connection_node_2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_2.node.log_file) cls.multiplexer_node_2.connect() cls.multiplexers.append(cls.multiplexer_node_2) wait_for_condition(lambda: cls.multiplexer_node_1.is_connected, 10) wait_for_condition(lambda: cls.multiplexer_node_2.is_connected, 10) wait_for_condition(lambda: cls.connection_node_1.is_connected, 10) wait_for_condition(lambda: cls.connection_node_2.is_connected, 10) cls.connections = [cls.connection_node_1, cls.connection_node_2] cls.addresses = [ cls.connection_node_1.address, cls.connection_node_2.address, ] for j in range(DEFAULT_CLIENTS_PER_NODE): ports = [DEFAULT_DELEGATE_PORT + 1, DEFAULT_DELEGATE_PORT + 2] peers_public_keys = [ cls.connection_node_1.node.pub, cls.connection_node_2.node.pub, ] for i in range(len(ports)): port = ports[i] peer_public_key = peers_public_keys[i] temp_dir_client = os.path.join(cls.t, f"temp_dir_client__{j}_{i}") os.mkdir(temp_dir_client) conn = _make_libp2p_client_connection( data_dir=temp_dir_client, peer_public_key=peer_public_key, node_port=port, ) mux = Multiplexer([conn], protocols=[MockDefaultMessageProtocol]) cls.connections.append(conn) cls.addresses.append(conn.address) mux.connect() wait_for_condition((lambda m: lambda: m.is_connected)(mux), 10) wait_for_condition((lambda c: lambda: c.is_connected)(conn), 10) cls.multiplexers.append(mux) break except Exception: cls.teardown_class() raise def test_connection_is_established(self): """Test connection is established.""" for conn in self.connections: assert conn.is_connected is True def test_star_routing_connectivity(self): """Test routing with star connectivity.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) for source in range(len(self.multiplexers)): for destination in range(len(self.multiplexers)): if destination == source: continue envelope = Envelope( to=self.addresses[destination], sender=self.addresses[source], protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexers[source].put(envelope) delivered_envelope = self.multiplexers[destination].get( block=True, timeout=10 ) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message @classmethod def teardown_class(cls): """Tear down the test""" for mux in reversed(cls.multiplexers): mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass def test_libp2pclientconnection_uri(): """Test the uri.""" uri = Uri(host="127.0.0.1") uri = Uri(host="127.0.0.1", port=10000) assert uri.host == "127.0.0.1" and uri.port == 10000 @libp2p_log_on_failure_all class BaseTestLibp2pClientSamePeer: """Base test class for reconnection tests.""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) cls.log_files = [] temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.connection_node = _make_libp2p_connection( data_dir=temp_dir, port=DEFAULT_PORT + 1, delegate=True ) cls.multiplexer_node = Multiplexer( [cls.connection_node], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node.node.log_file) cls.multiplexer_node.connect() try: temp_dir_client_1 = os.path.join(cls.t, "temp_dir_client_1") os.mkdir(temp_dir_client_1) cls.connection_client_1 = _make_libp2p_client_connection( data_dir=temp_dir_client_1, peer_public_key=cls.connection_node.node.pub, ledger_api_id=FetchAICrypto.identifier, ) cls.multiplexer_client_1 = Multiplexer( [cls.connection_client_1], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_1.connect() temp_dir_client_2 = os.path.join(cls.t, "temp_dir_client_2") os.mkdir(temp_dir_client_2) cls.connection_client_2 = _make_libp2p_client_connection( data_dir=temp_dir_client_2, peer_public_key=cls.connection_node.node.pub, ledger_api_id=EthereumCrypto.identifier, ) cls.multiplexer_client_2 = Multiplexer( [cls.connection_client_2], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_2.connect() wait_for_condition(lambda: cls.multiplexer_client_2.is_connected, 20) wait_for_condition(lambda: cls.multiplexer_client_1.is_connected, 20) wait_for_condition(lambda: cls.connection_client_2.is_connected, 20) wait_for_condition(lambda: cls.connection_client_1.is_connected, 20) wait_for_condition(lambda: cls.connection_node.is_connected, 20) except Exception: cls.multiplexer_node.disconnect() raise @classmethod def teardown_class(cls): """Tear down the test""" cls.multiplexer_client_1.disconnect() cls.multiplexer_client_2.disconnect() cls.multiplexer_node.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass def _make_envelope( self, sender_address: str, receiver_address: str, message_id: int = 1, target: int = 0, ): """Make an envelope for testing purposes.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=message_id, target=target, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=receiver_address, sender=sender_address, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) return envelope @libp2p_log_on_failure_all class TestLibp2pClientReconnectionSendEnvelope(BaseTestLibp2pClientSamePeer): """Test that connection will send envelope with error, and that reconnection fixes it.""" def test_envelope_sent(self): """Test the envelope is routed.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address envelope = self._make_envelope(addr_1, addr_2) # make the send to fail with mock.patch.object( self.connection_client_1.logger, "exception" ) as _mock_logger, mock.patch.object( self.connection_client_1._node_client, "_write", side_effect=Exception ): self.multiplexer_client_1.put(envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=20) _mock_logger.assert_has_calls( [ call( "Exception raised on message send. Try reconnect and send again." ) ] ) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message @libp2p_log_on_failure_all class TestLibp2pClientReconnectionReceiveEnvelope(BaseTestLibp2pClientSamePeer): """Test that connection will receive envelope with error, and that reconnection fixes it.""" def test_envelope_received(self): """Test the envelope is routed.""" sender = self.connection_client_2.address receiver = self.connection_client_1.address envelope = self._make_envelope(sender, receiver) # make the receive to fail with mock.patch.object( self.connection_client_1.logger, "error" ) as _mock_logger, mock.patch.object( self.connection_client_1._node_client, "_read", side_effect=ConnectionError(), ): # this envelope will be lost. self.multiplexer_client_2.put(envelope) # give time to reconnect time.sleep(2.0) _mock_logger.assert_has_calls( [ call( RegexComparator( "Connection error:.*Try to reconnect and read again" ) ) ] ) # proceed as usual. Now we expect the connection to have reconnected successfully delivered_envelope = self.multiplexer_client_1.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message class TestLibp2pClientEnvelopeOrderSamePeer(BaseTestLibp2pClientSamePeer): """Test that the order of envelope is the guaranteed to be the same.""" NB_ENVELOPES = 1000 def test_burst_order(self): """Test order of envelope burst is guaranteed on receiving end.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address sent_envelopes = [ self._make_envelope(addr_1, addr_2, i, i - 1) for i in range(1, self.NB_ENVELOPES + 1) ] for envelope in sent_envelopes: self.multiplexer_client_1.put(envelope) received_envelopes = [] for _ in range(1, self.NB_ENVELOPES + 1): envelope = self.multiplexer_client_2.get(block=True, timeout=20) received_envelopes.append(envelope) # test no new message is "created" with pytest.raises(Empty): self.multiplexer_client_2.get(block=True, timeout=1) assert len(sent_envelopes) == len( received_envelopes ), f"expected number of envelopes {len(sent_envelopes)}, got {len(received_envelopes)}" for expected, actual in zip(sent_envelopes, received_envelopes): assert expected.message == actual.message, ( "message content differ; probably a wrong message " "ordering on the receiving end" ) @pytest.mark.asyncio async def test_nodeclient_pipe_connect(): """Test pipe.connect called on NodeClient.connect.""" f = asyncio.Future() f.set_result(None) pipe = Mock() pipe.connect.return_value = f node_client = NodeClient(pipe, Mock()) await node_client.connect() pipe.connect.assert_called_once() @pytest.mark.asyncio async def test_write_acn_error(): """Test pipe.connect called on NodeClient.connect.""" f = asyncio.Future() f.set_result(None) pipe = Mock() pipe.write.return_value = f node_client = NodeClient(pipe, Mock()) await node_client.write_acn_status_error("some message") pipe.write.assert_called_once() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_client/test_errors.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains negative tests for Libp2p tcp client connection.""" import asyncio import os import shutil import tempfile from asyncio.futures import Future from unittest.mock import Mock, patch import pytest from aea.configurations.base import ConnectionConfig from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.registries import make_crypto from aea.helpers.base import CertRequest from aea.identity.base import Identity from aea.multiplexer import Multiplexer from packages.fetchai.connections.p2p_libp2p_client.connection import ( NodeClient, P2PLibp2pClientConnection, POR_DEFAULT_SERVICE_ID, ) from tests.conftest import ( _make_libp2p_client_connection, _make_libp2p_connection, _process_cert, libp2p_log_on_failure, ) @pytest.mark.asyncio class TestLibp2pClientConnectionFailureNodeNotConnected: """Test that connection fails when node not running""" @pytest.mark.asyncio async def test_node_not_running(self): """Test the node is not running.""" with tempfile.TemporaryDirectory() as dirname: conn = _make_libp2p_client_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) with pytest.raises(Exception): await conn.connect() class TestLibp2pClientConnectionFailureConnectionSetup: """Test that connection fails when setup incorrectly""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) crypto = make_crypto(DEFAULT_LEDGER) cls.node_host = "localhost" cls.node_port = "11234" cls.identity = Identity( "identity", address=crypto.address, public_key=crypto.public_key ) cls.key_file = os.path.join(cls.t, "keyfile") crypto.dump(cls.key_file) cls.peer_crypto = make_crypto(DEFAULT_LEDGER) cls.cert_request = CertRequest( cls.peer_crypto.public_key, POR_DEFAULT_SERVICE_ID, DEFAULT_LEDGER, "2021-01-01", "2021-01-02", "{public_key}", f"./{crypto.address}_cert.txt", ) _process_cert(crypto, cls.cert_request, cls.t) def test_empty_nodes(self): """Test empty nodes.""" configuration = ConnectionConfig( tcp_key_file=self.key_file, nodes=[ { "uri": "{}:{}".format(self.node_host, self.node_port), "public_key": self.peer_crypto.public_key, } ], connection_id=P2PLibp2pClientConnection.connection_id, cert_requests=[self.cert_request], ) P2PLibp2pClientConnection( configuration=configuration, data_dir=self.t, identity=self.identity ) configuration = ConnectionConfig( tcp_key_file=self.key_file, nodes=None, connection_id=P2PLibp2pClientConnection.connection_id, ) with pytest.raises(Exception): P2PLibp2pClientConnection( configuration=configuration, data_dir=self.t, identity=self.identity, ) @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestLibp2pClientConnectionNodeDisconnected: """Test that connection will properly handle node disconnecting""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] temp_node_dir = os.path.join(cls.t, "node_dir") os.mkdir(temp_node_dir) try: cls.connection_node = _make_libp2p_connection( data_dir=temp_node_dir, delegate=True ) cls.multiplexer_node = Multiplexer([cls.connection_node]) cls.log_files.append(cls.connection_node.node.log_file) cls.multiplexer_node.connect() cls.multiplexers.append(cls.multiplexer_node) temp_client_dir = os.path.join(cls.t, "client_dir") os.mkdir(temp_client_dir) cls.connection_client = _make_libp2p_client_connection( data_dir=temp_client_dir, peer_public_key=cls.connection_node.node.pub ) cls.multiplexer_client = Multiplexer([cls.connection_client]) cls.multiplexer_client.connect() cls.multiplexers.append(cls.multiplexer_client) except Exception: cls.teardown_class() raise def test_node_disconnected(self): """Test node disconnected.""" assert self.connection_client.is_connected is True self.multiplexer_client.disconnect() self.multiplexer_node.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass done_future: asyncio.Future = asyncio.Future() done_future.set_result(None) @pytest.mark.asyncio async def test_connect_attempts(): """Test connect attempts.""" # test connects with tempfile.TemporaryDirectory() as dirname: con = _make_libp2p_client_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) con.connect_retries = 2 with patch( "aea.helpers.pipe.TCPSocketChannelClient.connect", side_effect=Exception("test exception on connect"), ) as open_connection_mock: with pytest.raises(Exception, match="test exception on connect"): await con.connect() assert open_connection_mock.call_count == con.connect_retries @pytest.mark.asyncio async def test_reconnect_on_receive_fail(): """Test reconnect on receive fails.""" with tempfile.TemporaryDirectory() as dirname: con = _make_libp2p_client_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) con._in_queue = Mock() con._node_client = Mock() exception_future = Future() exception_future.set_exception(ConnectionError("oops")) result = Future() result.set_result(None) con._node_client.read_envelope.side_effect = [exception_future, result] with patch.object( con, "_perform_connection_to_node", return_value=done_future ) as connect_mock: assert await con._read_envelope_from_node() is None connect_mock.assert_called() @pytest.mark.asyncio async def test_reconnect_on_send_fail(): """Test reconnect on send fails.""" with tempfile.TemporaryDirectory() as dirname: con = _make_libp2p_client_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) con._node_client = Mock() f = Future() f.set_exception(Exception("oops")) con._node_client.send_envelope.side_effect = Exception("oops") # test reconnect on send fails with patch.object( con, "_perform_connection_to_node", return_value=done_future ) as connect_mock, patch.object( con, "_ensure_valid_envelope_for_external_comms" ): with pytest.raises(Exception, match="oops"): await con._send_envelope_with_node_client(Mock()) connect_mock.assert_called() @pytest.mark.asyncio async def test_acn_decode_error_on_read(): """Test nodeclient send fails on read.""" f = Future() f.set_result(b"some_data") pipe = Mock() pipe.connect = Mock(return_value=f) node_client = NodeClient(pipe, Mock()) with patch.object(node_client, "_read", lambda: f), patch.object( node_client, "write_acn_status_error", return_value=f ) as mocked_write_acn_status_error, pytest.raises( Exception, match=r"Error parsing acn message:" ): await node_client.read_envelope() mocked_write_acn_status_error.assert_called_once() @pytest.mark.asyncio class TestLibp2pClientConnectionCheckSignature: """Test that TLS signature is checked properly.""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.connection_node = _make_libp2p_connection(data_dir=temp_dir, delegate=True) temp_dir_client = os.path.join(cls.t, "temp_dir_client") os.mkdir(temp_dir_client) cls.connection = _make_libp2p_client_connection( data_dir=temp_dir_client, peer_public_key=cls.connection_node.node.pub ) @pytest.mark.asyncio async def test_signature_check_fail(self): """Test signature check failed.""" key = make_crypto(DEFAULT_LEDGER) assert self.connection.is_connected is False await self.connection_node.connect() self.connection.connect_retries = 1 try: self.connection.node_por._representative_public_key = key.public_key with pytest.raises( ValueError, match=".*Invalid TLS session key signature: Signature verification failed.*", ): await self.connection.connect() assert self.connection.is_connected is False finally: await self.connection_node.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_mailbox/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the Libp2p client connection.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_mailbox/test_aea_cli.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains AEA cli tests for Libp2p tcp client connection.""" import os from aea_ledger_fetchai import FetchAICrypto from aea.helpers.base import CertRequest from aea.multiplexer import Multiplexer from aea.test_tools.test_cases import AEATestCaseEmpty from packages.fetchai.connections.p2p_libp2p_mailbox.connection import PUBLIC_ID from tests.conftest import ( _make_libp2p_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 DEFAULT_DELEGATE_PORT = 11234 DEFAULT_HOST = "127.0.0.1" DEFAULT_MAILBOX_PORT = 8888 DEFAULT_CLIENTS_PER_NODE = 4 DEFAULT_LAUNCH_TIMEOUT = 10 @libp2p_log_on_failure_all class TestP2PLibp2pClientConnectionAEARunning(AEATestCaseEmpty): """Test AEA with p2p_libp2p_client connection is correctly run""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set up the test class.""" super(TestP2PLibp2pClientConnectionAEARunning, cls).setup_class() temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.node_connection = _make_libp2p_connection( data_dir=temp_dir, delegate_host=DEFAULT_HOST, delegate_port=DEFAULT_DELEGATE_PORT, mailbox_port=DEFAULT_MAILBOX_PORT, delegate=True, mailbox=True, ) cls.node_multiplexer = Multiplexer([cls.node_connection]) cls.log_files = [cls.node_connection.node.log_file] cls.node_multiplexer.connect() def test_node(self): """Test the node is connected.""" assert self.node_connection.is_connected is True def test_connection(self): """Test the connection can be used in an aea.""" self.generate_private_key() self.add_private_key() self.add_item("connection", str(PUBLIC_ID)) conn_path = "vendor.fetchai.connections.p2p_libp2p_mailbox" self.nested_set_config( conn_path + ".config", { "nodes": [ { "uri": "{}:{}".format(DEFAULT_HOST, DEFAULT_MAILBOX_PORT), "public_key": self.node_connection.node.pub, } ] }, ) # generate certificates for connection self.nested_set_config( conn_path + ".cert_requests", [ CertRequest( identifier="acn", ledger_id=FetchAICrypto.identifier, not_after="2022-01-01", not_before="2021-01-01", public_key=self.node_connection.node.pub, message_format="{public_key}", save_path="./cli_test_cert.txt", ) ], ) self.run_cli_command("issue-certificates", cwd=self._get_cwd()) process = self.run_agent() is_running = self.is_running(process, timeout=DEFAULT_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" check_strings = "Successfully connected to libp2p node {}:{}".format( DEFAULT_HOST, DEFAULT_MAILBOX_PORT ) missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] ), "Strings {} didn't appear in agent output.".format(missing_strings) self.terminate_agents(process) assert self.is_successfully_terminated( process ), "AEA wasn't successfully terminated." @classmethod def teardown_class(cls): """Tear down the test""" cls.terminate_agents() super(TestP2PLibp2pClientConnectionAEARunning, cls).teardown_class() cls.node_multiplexer.disconnect() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_mailbox/test_communication.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for Libp2p tcp client connection.""" import os import shutil import tempfile from unittest.mock import Mock import pytest from aea_ledger_ethereum import EthereumCrypto from aea_ledger_fetchai import FetchAICrypto from aea.mail.base import Empty, Envelope from aea.multiplexer import Multiplexer from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.default.serialization import DefaultSerializer from tests.common.utils import wait_for_condition from tests.conftest import ( _make_libp2p_connection, _make_libp2p_mailbox_connection, libp2p_log_on_failure, libp2p_log_on_failure_all, ) DEFAULT_PORT = 10234 DEFAULT_DELEGATE_PORT = 11234 DEFAULT_MAILBOX_PORT = 8888 DEFAULT_HOST = "127.0.0.1" DEFAULT_CLIENTS_PER_NODE = 1 MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) @pytest.mark.asyncio class TestLibp2pClientConnectionConnectDisconnect: """Test that connection is established and torn down correctly""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.connection_node = _make_libp2p_connection( data_dir=temp_dir, delegate=True, mailbox=True ) temp_dir_client = os.path.join(cls.t, "temp_dir_client") os.mkdir(temp_dir_client) cls.connection = _make_libp2p_mailbox_connection( data_dir=temp_dir_client, peer_public_key=cls.connection_node.node.pub, ) @pytest.mark.asyncio async def test_libp2pclientconnection_connect_disconnect(self): """Test connnect then disconnect.""" assert self.connection.is_connected is False try: await self.connection_node.connect() await self.connection.connect() assert self.connection.is_connected is True await self.connection.disconnect() assert self.connection.is_connected is False except Exception: raise finally: await self.connection_node.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestLibp2pClientConnectionEchoEnvelope: """Test that connection will route envelope to destination through the same libp2p node""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.connection_node = _make_libp2p_connection( data_dir=temp_dir, port=DEFAULT_PORT + 1, delegate=True, mailbox=True ) cls.multiplexer_node = Multiplexer( [cls.connection_node], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node.node.log_file) cls.multiplexer_node.connect() try: temp_dir_client_1 = os.path.join(cls.t, "temp_dir_client_1") os.mkdir(temp_dir_client_1) cls.connection_client_1 = _make_libp2p_mailbox_connection( data_dir=temp_dir_client_1, peer_public_key=cls.connection_node.node.pub, ledger_api_id=FetchAICrypto.identifier, ) cls.multiplexer_client_1 = Multiplexer( [cls.connection_client_1], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_1.connect() temp_dir_client_2 = os.path.join(cls.t, "temp_dir_client_2") os.mkdir(temp_dir_client_2) cls.connection_client_2 = _make_libp2p_mailbox_connection( data_dir=temp_dir_client_2, peer_public_key=cls.connection_node.node.pub, ledger_api_id=EthereumCrypto.identifier, ) cls.multiplexer_client_2 = Multiplexer( [cls.connection_client_2], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_2.connect() wait_for_condition(lambda: cls.connection_client_1.is_connected is True, 10) wait_for_condition(lambda: cls.connection_client_2.is_connected is True, 10) except Exception: cls.multiplexer_node.disconnect() raise def test_connection_is_established(self): """Test connection is established.""" assert self.connection_client_1.is_connected is True assert self.connection_client_2.is_connected is True def test_envelope_routed(self): """Test the envelope is routed.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message def test_envelope_echoed_back(self): """Test the envelope is echoed back.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_2 self.multiplexer_client_2.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message def test_envelope_echoed_back_node_agent(self): """Test the envelope is echoed back.""" addr_1 = self.connection_client_1.address addr_n = self.connection_node.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_n, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_node.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_n self.multiplexer_node.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message @classmethod def teardown_class(cls): """Tear down the test""" cls.multiplexer_client_1.disconnect() cls.multiplexer_client_2.disconnect() cls.multiplexer_node.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestLibp2pClientConnectionEchoEnvelopeTwoDHTNode: """Test that connection will route envelope to destination connected to different node""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.mutliplexers = [] temp_dir_node_1 = os.path.join(cls.t, "temp_dir_node_1") os.mkdir(temp_dir_node_1) cls.connection_node_1 = _make_libp2p_connection( data_dir=temp_dir_node_1, port=DEFAULT_PORT + 1, delegate_port=DEFAULT_DELEGATE_PORT + 1, mailbox_port=DEFAULT_MAILBOX_PORT + 1, delegate=True, mailbox=True, ) cls.multiplexer_node_1 = Multiplexer( [cls.connection_node_1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_1.node.log_file) cls.multiplexer_node_1.connect() cls.mutliplexers.append(cls.multiplexer_node_1) genesis_peer = cls.connection_node_1.node.multiaddrs[0] temp_dir_node_2 = os.path.join(cls.t, "temp_dir_node_2") os.mkdir(temp_dir_node_2) try: cls.connection_node_2 = _make_libp2p_connection( data_dir=temp_dir_node_2, port=DEFAULT_PORT + 2, delegate_port=DEFAULT_DELEGATE_PORT + 2, mailbox_port=DEFAULT_MAILBOX_PORT + 2, entry_peers=[genesis_peer], delegate=True, mailbox=True, ) cls.multiplexer_node_2 = Multiplexer( [cls.connection_node_2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_2.node.log_file) cls.multiplexer_node_2.connect() cls.mutliplexers.append(cls.multiplexer_node_2) temp_dir_client_1 = os.path.join(cls.t, "temp_dir_client_1") os.mkdir(temp_dir_client_1) cls.connection_client_1 = _make_libp2p_mailbox_connection( data_dir=temp_dir_client_1, peer_public_key=cls.connection_node_1.node.pub, node_port=DEFAULT_MAILBOX_PORT + 1, ) cls.multiplexer_client_1 = Multiplexer( [cls.connection_client_1], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_1.connect() cls.mutliplexers.append(cls.multiplexer_client_1) temp_dir_client_2 = os.path.join(cls.t, "temp_dir_client_2") os.mkdir(temp_dir_client_2) cls.connection_client_2 = _make_libp2p_mailbox_connection( data_dir=temp_dir_client_2, peer_public_key=cls.connection_node_2.node.pub, node_port=DEFAULT_MAILBOX_PORT + 2, ) cls.multiplexer_client_2 = Multiplexer( [cls.connection_client_2], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_2.connect() cls.mutliplexers.append(cls.multiplexer_client_2) wait_for_condition(lambda: cls.connection_node_1.is_connected is True, 10) wait_for_condition(lambda: cls.connection_node_2.is_connected is True, 10) wait_for_condition(lambda: cls.connection_client_1.is_connected is True, 10) wait_for_condition(lambda: cls.connection_client_2.is_connected is True, 10) except Exception: cls.teardown_class() raise def test_connection_is_established(self): """Test the connection is established.""" assert self.connection_node_1.is_connected is True assert self.connection_node_2.is_connected is True assert self.connection_client_1.is_connected is True assert self.connection_client_2.is_connected is True def test_envelope_routed(self): """Test the envelope is routed.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=20) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message def test_envelope_echoed_back(self): """Test the envelope is echoed back.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_2, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_client_2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_2 self.multiplexer_client_2.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message def test_envelope_echoed_back_node_agent(self): """Test the envelope is echoed back node agent.""" addr_1 = self.connection_client_1.address addr_n = self.connection_node_2.address msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) original_envelope = Envelope( to=addr_n, sender=addr_1, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexer_client_1.put(original_envelope) delivered_envelope = self.multiplexer_node_2.get(block=True, timeout=10) assert delivered_envelope is not None delivered_envelope.to = addr_1 delivered_envelope.sender = addr_n self.multiplexer_node_2.put(delivered_envelope) echoed_envelope = self.multiplexer_client_1.get(block=True, timeout=5) assert echoed_envelope is not None assert echoed_envelope.to == original_envelope.sender assert delivered_envelope.sender == original_envelope.to assert ( delivered_envelope.protocol_specification_id == original_envelope.protocol_specification_id ) assert delivered_envelope.message == original_envelope.message @classmethod def teardown_class(cls): """Tear down the test""" for mux in reversed(cls.mutliplexers): mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class TestLibp2pClientConnectionRouting: """Test that libp2p DHT network will reliably route envelopes from clients connected to different nodes""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] try: temp_dir_node_1 = os.path.join(cls.t, "temp_dir_node_1") os.mkdir(temp_dir_node_1) cls.connection_node_1 = _make_libp2p_connection( data_dir=temp_dir_node_1, port=DEFAULT_PORT + 1, delegate_port=DEFAULT_DELEGATE_PORT + 1, mailbox_port=DEFAULT_MAILBOX_PORT + 1, delegate=True, mailbox=True, ) cls.multiplexer_node_1 = Multiplexer( [cls.connection_node_1], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_1.node.log_file) cls.multiplexer_node_1.connect() cls.multiplexers.append(cls.multiplexer_node_1) entry_peer = cls.connection_node_1.node.multiaddrs[0] temp_dir_node_2 = os.path.join(cls.t, "temp_dir_node_2") os.mkdir(temp_dir_node_2) cls.connection_node_2 = _make_libp2p_connection( data_dir=temp_dir_node_2, port=DEFAULT_PORT + 2, delegate_port=DEFAULT_DELEGATE_PORT + 2, mailbox_port=DEFAULT_MAILBOX_PORT + 2, entry_peers=[entry_peer], delegate=True, mailbox=True, ) cls.multiplexer_node_2 = Multiplexer( [cls.connection_node_2], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node_2.node.log_file) cls.multiplexer_node_2.connect() cls.multiplexers.append(cls.multiplexer_node_2) wait_for_condition(lambda: cls.multiplexer_node_1.is_connected, 10) wait_for_condition(lambda: cls.multiplexer_node_2.is_connected, 10) wait_for_condition(lambda: cls.connection_node_1.is_connected, 10) wait_for_condition(lambda: cls.connection_node_2.is_connected, 10) cls.connections = [cls.connection_node_1, cls.connection_node_2] cls.addresses = [ cls.connection_node_1.address, cls.connection_node_2.address, ] for j in range(DEFAULT_CLIENTS_PER_NODE): ports = [DEFAULT_MAILBOX_PORT + 1, DEFAULT_MAILBOX_PORT + 2] peers_public_keys = [ cls.connection_node_1.node.pub, cls.connection_node_2.node.pub, ] for i in range(len(ports)): port = ports[i] peer_public_key = peers_public_keys[i] temp_dir_client = os.path.join(cls.t, f"temp_dir_client__{j}_{i}") os.mkdir(temp_dir_client) conn = _make_libp2p_mailbox_connection( data_dir=temp_dir_client, peer_public_key=peer_public_key, node_port=port, ) mux = Multiplexer([conn], protocols=[MockDefaultMessageProtocol]) cls.connections.append(conn) cls.addresses.append(conn.address) mux.connect() wait_for_condition((lambda m: lambda: m.is_connected)(mux), 10) wait_for_condition((lambda c: lambda: c.is_connected)(conn), 10) cls.multiplexers.append(mux) break except Exception: cls.teardown_class() raise def test_connection_is_established(self): """Test connection is established.""" for conn in self.connections: assert conn.is_connected is True def test_star_routing_connectivity(self): """Test routing with star connectivity.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) for source in range(len(self.multiplexers)): for destination in range(len(self.multiplexers)): if destination == source: continue envelope = Envelope( to=self.addresses[destination], sender=self.addresses[source], protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) self.multiplexers[source].put(envelope) delivered_envelope = self.multiplexers[destination].get( block=True, timeout=10 ) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message @classmethod def teardown_class(cls): """Tear down the test""" for mux in reversed(cls.multiplexers): mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass @libp2p_log_on_failure_all class BaseTestLibp2pClientSamePeer: """Base test class for reconnection tests.""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) cls.log_files = [] temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(temp_dir) cls.connection_node = _make_libp2p_connection( data_dir=temp_dir, port=DEFAULT_PORT + 1, delegate=True, mailbox=True ) cls.multiplexer_node = Multiplexer( [cls.connection_node], protocols=[MockDefaultMessageProtocol] ) cls.log_files.append(cls.connection_node.node.log_file) cls.multiplexer_node.connect() try: temp_dir_client_1 = os.path.join(cls.t, "temp_dir_client_1") os.mkdir(temp_dir_client_1) cls.connection_client_1 = _make_libp2p_mailbox_connection( data_dir=temp_dir_client_1, peer_public_key=cls.connection_node.node.pub, ledger_api_id=FetchAICrypto.identifier, ) cls.multiplexer_client_1 = Multiplexer( [cls.connection_client_1], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_1.connect() temp_dir_client_2 = os.path.join(cls.t, "temp_dir_client_2") os.mkdir(temp_dir_client_2) cls.connection_client_2 = _make_libp2p_mailbox_connection( data_dir=temp_dir_client_2, peer_public_key=cls.connection_node.node.pub, ledger_api_id=EthereumCrypto.identifier, ) cls.multiplexer_client_2 = Multiplexer( [cls.connection_client_2], protocols=[MockDefaultMessageProtocol] ) cls.multiplexer_client_2.connect() wait_for_condition(lambda: cls.multiplexer_client_2.is_connected, 20) wait_for_condition(lambda: cls.multiplexer_client_1.is_connected, 20) wait_for_condition(lambda: cls.connection_client_2.is_connected, 20) wait_for_condition(lambda: cls.connection_client_1.is_connected, 20) wait_for_condition(lambda: cls.connection_node.is_connected, 20) except Exception: cls.multiplexer_node.disconnect() raise @classmethod def teardown_class(cls): """Tear down the test""" cls.multiplexer_client_1.disconnect() cls.multiplexer_client_2.disconnect() cls.multiplexer_node.disconnect() os.chdir(cls.cwd) print(open(cls.connection_node.node.log_file, "r").read()) try: shutil.rmtree(cls.t) except (OSError, IOError): pass def _make_envelope( self, sender_address: str, receiver_address: str, message_id: int = 1, target: int = 0, ): """Make an envelope for testing purposes.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=message_id, target=target, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=receiver_address, sender=sender_address, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) return envelope @libp2p_log_on_failure_all class TestLibp2pClientEnvelopeOrderSamePeer(BaseTestLibp2pClientSamePeer): """Test that the order of envelope is the guaranteed to be the same.""" NB_ENVELOPES = 1000 def test_burst_order(self): """Test order of envelope burst is guaranteed on receiving end.""" addr_1 = self.connection_client_1.address addr_2 = self.connection_client_2.address sent_envelopes = [ self._make_envelope(addr_1, addr_2, i, i - 1) for i in range(1, self.NB_ENVELOPES + 1) ] for envelope in sent_envelopes: self.multiplexer_client_1.put(envelope) received_envelopes = [] for _ in range(1, self.NB_ENVELOPES + 1): envelope = self.multiplexer_client_2.get(block=True, timeout=20) received_envelopes.append(envelope) # test no new message is "created" with pytest.raises(Empty): self.multiplexer_client_2.get(block=True, timeout=1) assert len(sent_envelopes) == len( received_envelopes ), f"expected number of envelopes {len(sent_envelopes)}, got {len(received_envelopes)}" for expected, actual in zip(sent_envelopes, received_envelopes): assert expected.message == actual.message, ( "message content differ; probably a wrong message " "ordering on the receiving end" ) ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_mailbox/test_errors.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains negative tests for Libp2p tcp client connection.""" import asyncio import os import shutil import tempfile from asyncio.futures import Future from unittest.mock import Mock, patch import pytest from aea.configurations.base import ConnectionConfig from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.registries import make_crypto from aea.helpers.base import CertRequest from aea.identity.base import Identity from aea.multiplexer import Multiplexer from packages.fetchai.connections.p2p_libp2p_client.connection import ( POR_DEFAULT_SERVICE_ID, ) from packages.fetchai.connections.p2p_libp2p_mailbox.connection import ( P2PLibp2pMailboxConnection, ) from tests.conftest import ( _make_libp2p_client_connection, _make_libp2p_connection, _make_libp2p_mailbox_connection, _process_cert, libp2p_log_on_failure, ) @pytest.mark.asyncio class TestLibp2pClientConnectionFailureNodeNotConnected: """Test that connection fails when node not running""" @pytest.mark.asyncio async def test_node_not_running(self): """Test the node is not running.""" with tempfile.TemporaryDirectory() as dirname: conn = _make_libp2p_mailbox_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) with pytest.raises(Exception): await conn.connect() class TestLibp2pClientConnectionFailureConnectionSetup: """Test that connection fails when setup incorrectly""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) crypto = make_crypto(DEFAULT_LEDGER) cls.node_host = "localhost" cls.node_port = "11234" cls.identity = Identity( "identity", address=crypto.address, public_key=crypto.public_key ) cls.key_file = os.path.join(cls.t, "keyfile") crypto.dump(cls.key_file) cls.peer_crypto = make_crypto(DEFAULT_LEDGER) cls.cert_request = CertRequest( cls.peer_crypto.public_key, POR_DEFAULT_SERVICE_ID, DEFAULT_LEDGER, "2021-01-01", "2021-01-02", "{public_key}", f"./{crypto.address}_cert.txt", ) _process_cert(crypto, cls.cert_request, cls.t) def test_empty_nodes(self): """Test empty nodes.""" configuration = ConnectionConfig( tcp_key_file=self.key_file, nodes=[ { "uri": "{}:{}".format(self.node_host, self.node_port), "public_key": self.peer_crypto.public_key, } ], connection_id=P2PLibp2pMailboxConnection.connection_id, cert_requests=[self.cert_request], ) P2PLibp2pMailboxConnection( configuration=configuration, data_dir=self.t, identity=self.identity ) configuration = ConnectionConfig( tcp_key_file=self.key_file, nodes=None, connection_id=P2PLibp2pMailboxConnection.connection_id, ) with pytest.raises(Exception): P2PLibp2pMailboxConnection( configuration=configuration, data_dir=self.t, identity=self.identity, ) @classmethod def teardown_class(cls): """Tear down the test""" os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass class TestLibp2pClientConnectionNodeDisconnected: """Test that connection will properly handle node disconnecting""" @classmethod @libp2p_log_on_failure def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.log_files = [] cls.multiplexers = [] temp_node_dir = os.path.join(cls.t, "node_dir") os.mkdir(temp_node_dir) try: cls.connection_node = _make_libp2p_connection( data_dir=temp_node_dir, delegate=True ) cls.multiplexer_node = Multiplexer([cls.connection_node]) cls.log_files.append(cls.connection_node.node.log_file) cls.multiplexer_node.connect() cls.multiplexers.append(cls.multiplexer_node) temp_client_dir = os.path.join(cls.t, "client_dir") os.mkdir(temp_client_dir) cls.connection_client = _make_libp2p_client_connection( data_dir=temp_client_dir, peer_public_key=cls.connection_node.node.pub ) cls.multiplexer_client = Multiplexer([cls.connection_client]) cls.multiplexer_client.connect() cls.multiplexers.append(cls.multiplexer_client) except Exception: cls.teardown_class() raise def test_node_disconnected(self): """Test node disconnected.""" assert self.connection_client.is_connected is True self.multiplexer_client.disconnect() self.multiplexer_node.disconnect() @classmethod def teardown_class(cls): """Tear down the test""" for mux in cls.multiplexers: mux.disconnect() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) except (OSError, IOError): pass done_future: asyncio.Future = asyncio.Future() done_future.set_result(None) @pytest.mark.asyncio async def test_connect_attempts(): """Test connect attempts.""" # test connects with tempfile.TemporaryDirectory() as dirname: con = _make_libp2p_client_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) con.connect_retries = 2 with patch( "aea.helpers.pipe.TCPSocketChannelClient.connect", side_effect=Exception("test exception on connect"), ) as open_connection_mock: with pytest.raises(Exception, match="test exception on connect"): await con.connect() assert open_connection_mock.call_count == con.connect_retries @pytest.mark.asyncio async def test_reconnect_on_receive_fail(): """Test reconnect on receive fails.""" with tempfile.TemporaryDirectory() as dirname: con = _make_libp2p_mailbox_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) con._in_queue = Mock() con._node_client = Mock() f = Future() f.set_exception(ConnectionError("oops")) con._node_client.read_envelope.return_value = f with patch.object( con, "_perform_connection_to_node", return_value=done_future ) as connect_mock: assert await con._read_envelope_from_node() is None connect_mock.assert_called() @pytest.mark.asyncio async def test_reconnect_on_send_fail(): """Test reconnect on send fails.""" with tempfile.TemporaryDirectory() as dirname: con = _make_libp2p_mailbox_connection( data_dir=dirname, peer_public_key=make_crypto(DEFAULT_LEDGER).public_key ) con._node_client = Mock() f = Future() f.set_exception(Exception("oops")) con._node_client.send_envelope.side_effect = Exception("oops") # test reconnect on send fails with patch.object( con, "_perform_connection_to_node", return_value=done_future ) as connect_mock, patch.object( con, "_ensure_valid_envelope_for_external_comms" ): with pytest.raises(Exception, match="oops"): await con._send_envelope_with_node_client(Mock()) connect_mock.assert_called() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_libp2p_mailbox/test_mailbox_service.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains tests for P2PLibp2p connection.""" import os import re import shutil import tempfile from unittest.mock import Mock import pytest import requests from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.p2p_libp2p_mailbox.connection import NodeClient from packages.fetchai.protocols.acn import acn_pb2 from packages.fetchai.protocols.acn.message import AcnMessage from packages.fetchai.protocols.default import DefaultSerializer from packages.fetchai.protocols.default.message import DefaultMessage from tests.common.utils import wait_for_condition from tests.conftest import _make_libp2p_client_connection, _make_libp2p_connection MockDefaultMessageProtocol = Mock() MockDefaultMessageProtocol.protocol_id = DefaultMessage.protocol_id MockDefaultMessageProtocol.protocol_specification_id = ( DefaultMessage.protocol_specification_id ) @pytest.mark.asyncio class TestMailboxAPI: """Test that connection is established and torn down correctly""" @classmethod def setup_class(cls): """Set the test up""" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) cls.temp_dir = os.path.join(cls.t, "temp_dir_node") os.mkdir(cls.temp_dir) cls.connection_node = _make_libp2p_connection( data_dir=cls.temp_dir, delegate=True, mailbox=True ) temp_dir_client1 = os.path.join(cls.t, "temp_dir_client") os.mkdir(temp_dir_client1) temp_dir_client2 = os.path.join(cls.t, "temp_dir_client2") os.mkdir(temp_dir_client2) cls.connection1 = _make_libp2p_client_connection( data_dir=temp_dir_client1, peer_public_key=cls.connection_node.node.pub ) cls.connection2 = _make_libp2p_client_connection( data_dir=temp_dir_client2, peer_public_key=cls.connection_node.node.pub ) cls.multiplexer1 = Multiplexer([cls.connection_node]) cls.multiplexer1.connect() wait_for_condition(lambda: cls.connection_node.is_connected is True, 10) @pytest.mark.asyncio async def test_message_delivery(self): # nosec """Test connnect then disconnect.""" r = requests.get("https://localhost:8888/ssl_signature", verify=False) # nosec assert r.status_code == 200, r.text node_client = NodeClient(Mock(), self.connection2.node_por) agent_record = node_client.make_agent_record() addr = agent_record.address performative = acn_pb2.AcnMessage.Register_Performative() # type: ignore AcnMessage.AgentRecord.encode( performative.record, agent_record # pylint: disable=no-member ) data = performative.record.SerializeToString() # pylint: disable=no-member r = requests.post( "https://localhost:8888/register", data=data, verify=False # nosec ) assert r.status_code == 200, r.text assert re.match( "[0-9a-f]{32}", r.text, re.I ), r.text # pylint: disable=no-member session_id = r.text envelope = self.make_envelope(addr, addr) r = requests.post( "https://localhost:8888/send_envelope", data=envelope.encode(), headers={"Session-Id": session_id}, verify=False, # nosec ) assert r.status_code == 200, r.text r = requests.get( "https://localhost:8888/get_envelope", headers={"Session-Id": session_id}, verify=False, # nosec ) assert r.status_code == 200, r.text assert r.content, "no envelope" delivered_envelope = Envelope.decode(r.content) assert delivered_envelope is not None assert delivered_envelope.to == envelope.to assert delivered_envelope.sender == envelope.sender assert ( delivered_envelope.protocol_specification_id == envelope.protocol_specification_id ) assert delivered_envelope.message == envelope.message # no new envelopes r = requests.get( "https://localhost:8888/get_envelope", headers={"Session-Id": session_id}, verify=False, # nosec ) assert r.status_code == 200, r.text assert not r.content # unregister r = requests.get( "https://localhost:8888/unregister", headers={"Session-Id": session_id}, verify=False, # nosec ) assert r.status_code == 200, r.text # bad session! r = requests.get( "https://localhost:8888/get_envelope", headers={"Session-Id": session_id}, verify=False, # nosec ) assert r.status_code == 400, r.text assert "session_id" in r.text def make_envelope(self, from_: str, to_: str) -> Envelope: """Make sample envelope.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) envelope = Envelope( to=to_, sender=from_, protocol_specification_id=DefaultMessage.protocol_specification_id, message=DefaultSerializer().encode(msg), ) return envelope @classmethod def teardown_class(cls): """Tear down the test""" cls.multiplexer1.disconnect() os.chdir(cls.cwd) print(open(cls.connection_node.node.log_file, "r").read()) try: shutil.rmtree(cls.t) except (OSError, IOError): pass ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_stub/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the p2p stub connection package.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_p2p_stub/test_p2p_stub.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the p2p_stub connection.""" import asyncio import os import shutil import tempfile from pathlib import Path from unittest.mock import MagicMock import pytest from aea.configurations.base import ConnectionConfig from aea.identity.base import Identity from aea.mail.base import Envelope from packages.fetchai.connections.p2p_stub.connection import P2PStubConnection from packages.fetchai.protocols.default.message import DefaultMessage SEPARATOR = "," def make_test_envelope(to_="any", sender_="sender") -> Envelope: """Create a test envelope.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = to_ msg.sender = sender_ envelope = Envelope( to=to_, sender=sender_, message=msg, ) return envelope class Testp2pStubConnectionReception: """Test that the stub connection is implemented correctly.""" def setup(self): """Set the test up.""" self.cwd = os.getcwd() self.tmpdir = Path(tempfile.mkdtemp()) d = self.tmpdir / "test_p2p_stub" d.mkdir(parents=True) configuration = ConnectionConfig( namespace_dir=d, connection_id=P2PStubConnection.connection_id, ) self.loop = asyncio.get_event_loop() self.identity1 = Identity("test", "con1", "public_key_1") self.identity2 = Identity("test", "con2", "public_key_2") self.connection1 = P2PStubConnection( configuration=configuration, data_dir=MagicMock(), identity=self.identity1 ) self.connection2 = P2PStubConnection( configuration=configuration, data_dir=MagicMock(), identity=self.identity2 ) os.chdir(self.tmpdir) @pytest.mark.asyncio async def test_send(self): """Test that the connection receives what has been enqueued in the input file.""" await self.connection1.connect() assert self.connection1.is_connected await self.connection2.connect() assert self.connection2.is_connected envelope = make_test_envelope(to_="con2") await self.connection1.send(envelope) received_envelope = await asyncio.wait_for( self.connection2.receive(), timeout=5 ) assert received_envelope assert received_envelope.message == envelope.message.encode() def teardown(self): """Clean up after tests.""" os.chdir(self.cwd) self.loop.run_until_complete(self.connection1.disconnect()) self.loop.run_until_complete(self.connection2.disconnect()) try: shutil.rmtree(self.tmpdir) except (OSError, IOError): pass ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_soef/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the soef connection.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_soef/models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains models for soef connection tests.""" from aea.helpers.search.models import Attribute, DataModel, Location from packages.fetchai.connections.soef.connection import ModelNames AGENT_LOCATION_MODEL = DataModel( ModelNames.LOCATION_AGENT.value, [ Attribute("location", Location, True, "The location where the agent is."), Attribute("disclosure_accuracy", str, False, "Optional disclosure accuracy."), ], "A data model to describe location of an agent.", ) AGENT_PERSONALITY_MODEL = DataModel( ModelNames.PERSONALITY_AGENT.value, [ Attribute("piece", str, True, "The personality piece key."), Attribute("value", str, True, "The personality piece value."), ], "A data model to describe the personality of an agent.", ) SET_SERVICE_KEY_MODEL = DataModel( ModelNames.SET_SERVICE_KEY.value, [ Attribute("key", str, True, "Service key name."), Attribute("value", str, True, "Service key value."), ], "A data model to set service key.", ) REMOVE_SERVICE_KEY_MODEL = DataModel( ModelNames.REMOVE_SERVICE_KEY.value, [Attribute("key", str, True, "Service key name.")], "A data model to remove service key.", ) PING_MODEL = DataModel( ModelNames.PING.value, [], "A data model for ping command.", ) SEARCH_MODEL = DataModel( ModelNames.SEARCH_MODEL.value, [Attribute("location", Location, True, "The location where the agent is.")], "A data model to perform search.", ) AGENT_GENERIC_COMMAND_MODEL = DataModel( ModelNames.GENERIC_COMMAND.value, [ Attribute("command", str, True, "Command name to execute."), Attribute("parameters", str, False, "Url encoded parameters string."), ], "A data model to describe the generic soef command.", ) ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_soef/test_soef.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the soef connection module.""" import asyncio import os import shutil import tempfile from pathlib import Path from typing import Any, Callable from unittest.mock import MagicMock, patch import pytest from aea.common import Address from aea.configurations.base import ConnectionConfig from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.registries import make_crypto from aea.exceptions import AEAEnforceError from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Location, Query, ) from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from packages.fetchai.connections.soef.connection import ( SOEFConnection, SOEFException, SOEFNetworkConnectionError, SOEFServerBadResponseError, requests, ) from packages.fetchai.protocols.oef_search.dialogues import OefSearchDialogue from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage from tests.conftest import UNKNOWN_PROTOCOL_PUBLIC_ID from tests.test_packages_for_aea_tests.test_connections.test_soef import models def make_async(return_value: Any) -> Callable: """Wrap value into async function.""" # pydocstyle async def fn(*args, **kwargs): return return_value return fn def wrap_future(return_value: Any) -> asyncio.Future: """Wrap value into future.""" f: asyncio.Future = asyncio.Future() f.set_result(return_value) return f class OefSearchDialogues(BaseOefSearchDialogues): """This class keeps track of all oef_search dialogues.""" def __init__(self, self_address: str) -> None: """ Initialize dialogues. :param self_address: the address of the entity for whom dialogues are maintained :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return OefSearchDialogue.Role.AGENT BaseOefSearchDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, ) class TestSoefTokenStorage: """Set of unit tests for soef connection token storage.""" def setup(self): """Set up.""" self.skill_id = "some_author/some_skill:0.1.0" self.crypto = make_crypto(DEFAULT_LEDGER) self.crypto2 = make_crypto(DEFAULT_LEDGER) self.data_dir = tempfile.mkdtemp() identity = Identity( "identity", address=self.crypto.address, public_key=self.crypto.public_key ) self.oef_search_dialogues = OefSearchDialogues(self.skill_id) # create the connection and multiplexer objects self.token_storage_path = "test.storage" # nosec configuration = ConnectionConfig( api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="s-oef.fetch.ai", soef_port=443, token_storage_path=self.token_storage_path, restricted_to_protocols={OefSearchMessage.protocol_specification_id}, connection_id=SOEFConnection.connection_id, ) self.connection = SOEFConnection( configuration=configuration, data_dir=self.data_dir, identity=identity, ) def teardown(self): """Tear down.""" try: os.remove(self.token_storage_path) shutil.rmtree(self.data_dir) except Exception as e: print(e) def test_unique_page_address_default_no_file(self): """Test unique page address does not raise if file not found.""" assert self.connection.channel.unique_page_address is None def test_unique_page_address_default_file(self): """Test unique page address is None by default for new file.""" with open(self.token_storage_path, "w"): os.utime(self.token_storage_path, None) assert self.connection.channel.unique_page_address is None def test_unique_page_address_set_and_get(self): """Test unique page address set and get including None.""" self.connection.channel.unique_page_address = "test" assert self.connection.channel._unique_page_address == "test" assert self.connection.channel.unique_page_address == "test" expected_token_storage_path = Path(self.data_dir) / self.token_storage_path with expected_token_storage_path.open() as f: in_file = f.read() assert in_file == "test" self.connection.channel.unique_page_address = None assert self.connection.channel._unique_page_address is None assert self.connection.channel.unique_page_address is None with expected_token_storage_path.open() as f: in_file = f.read() assert in_file == self.connection.channel.NONE_UNIQUE_PAGE_ADDRESS class TestSoef: """Set of unit tests for soef connection.""" search_success_response = """1202ayYmgrCg76R1mzr2zWCmivzJG31hXtFVwQvR4XrXrD88Rc3sT02DvN8QNXKE2tjnKgMKvBy9ZFyC6JaFYFrcLyWSS4A9RDWeTP4k0""" search_empty_response = """100""" search_fail_response = ( """""" ) generic_success_response = """1""" def setup(self): """Set up.""" self.skill_id = "some_author/some_skill:0.1.0" self.crypto = make_crypto(DEFAULT_LEDGER) self.crypto2 = make_crypto(DEFAULT_LEDGER) identity = Identity( "identity", address=self.crypto.address, public_key=self.crypto.public_key ) self.oef_search_dialogues = OefSearchDialogues(self.skill_id) self.data_dir = tempfile.mkdtemp() # create the connection and multiplexer objects configuration = ConnectionConfig( api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="s-oef.fetch.ai", soef_port=443, restricted_to_protocols={OefSearchMessage.protocol_specification_id}, connection_id=SOEFConnection.connection_id, ) self.connection = SOEFConnection( configuration=configuration, data_dir=self.data_dir, identity=identity, ) self.connection2 = SOEFConnection( configuration=configuration, data_dir=self.data_dir, identity=Identity( "identity", address=self.crypto2.address, public_key=self.crypto2.public_key, ), ) self.loop = asyncio.get_event_loop() self.loop.run_until_complete(self.connection.connect()) self.loop.run_until_complete(self.connection2.connect()) self.connection.channel.unique_page_address = "some_addr" @pytest.mark.asyncio async def test_set_service_key(self): """Test set service key.""" service_instance = {"key": "test", "value": "test"} service_description = Description( service_instance, data_model=models.SET_SERVICE_KEY_MODEL ) message, _ = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async(self.generic_success_response), ): await self.connection.send(envelope) response = await asyncio.wait_for(self.connection.receive(), timeout=1) assert response.message.performative == OefSearchMessage.Performative.SUCCESS assert response.message.agents_info.body == {} @pytest.mark.asyncio async def test_remove_service_key(self): """Test remove service key.""" await self.test_set_service_key() service_instance = {"key": "test"} service_description = Description( service_instance, data_model=models.REMOVE_SERVICE_KEY_MODEL ) message, _ = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async(self.generic_success_response), ): await self.connection.send(envelope) response = await asyncio.wait_for(self.connection.receive(), timeout=1) assert response.message.performative == OefSearchMessage.Performative.SUCCESS assert response.message.agents_info.body == {} def test_connected(self): """Test connected==True.""" assert self.connection.is_connected @pytest.mark.asyncio async def test_disconnected(self): """Test disconnect.""" assert self.connection.is_connected with patch.object( self.connection.channel, "_request_text", make_async("Goodbye!"), ): await self.connection.disconnect() assert not self.connection.is_connected @pytest.mark.asyncio async def test_register_service(self): """Test register service.""" agent_location = Location(52.2057092, 2.1183431) service_instance = {"location": agent_location} service_description = Description( service_instance, data_model=models.AGENT_LOCATION_MODEL ) message, _ = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async(self.generic_success_response), ): await self.connection.send(envelope) response = await asyncio.wait_for(self.connection.receive(), timeout=1) assert response.message.performative == OefSearchMessage.Performative.SUCCESS assert response.message.agents_info.body == {} assert self.connection.channel.agent_location == agent_location @pytest.mark.asyncio async def test_bad_register_service(self): """Test register service fails on bad values provided.""" bad_location_model = DataModel( "not_location_agent", [ Attribute( "non_location", Location, True, "The location where the agent is." ) ], "A data model to describe location of an agent.", ) agent_location = Location(52.2057092, 2.1183431) service_instance = {"non_location": agent_location} service_description = Description( service_instance, data_model=bad_location_model ) message, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) await self.connection.send(envelope) expected_envelope = await asyncio.wait_for(self.connection.receive(), timeout=1) assert expected_envelope assert ( expected_envelope.message.performative == OefSearchMessage.Performative.OEF_ERROR ) message = expected_envelope.message receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue @pytest.mark.asyncio async def test_unregister_service(self): """Test unregister service.""" agent_location = Location(52.2057092, 2.1183431) service_instance = {"location": agent_location} service_description = Description( service_instance, data_model=models.AGENT_LOCATION_MODEL ) message, _ = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async("Goodbye!"), ): await self.connection.send(envelope) assert self.connection.channel.unique_page_address is None @pytest.mark.asyncio async def test_register_personailty_pieces(self): """Test register service with personality pieces.""" service_instance = {"piece": "genus", "value": "service"} service_description = Description( service_instance, data_model=models.AGENT_PERSONALITY_MODEL ) message, _ = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async(self.generic_success_response), ): await self.connection.send(envelope) response = await asyncio.wait_for(self.connection.receive(), timeout=1) assert response.message.performative == OefSearchMessage.Performative.SUCCESS assert response.message.agents_info.body == {} @pytest.mark.asyncio async def test_bad_message(self): """Test fail on bad message.""" envelope = Envelope( to="soef", sender=self.crypto.address, protocol_specification_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=b"some msg", ) with pytest.raises( AEAEnforceError, match=r"Message not of type OefSearchMessage" ): await self.connection.send(envelope) @pytest.mark.asyncio async def test_bad_performative(self): """Test fail on bad perfromative.""" agent_location = Location(52.2057092, 2.1183431) service_instance = {"location": agent_location} service_description = Description( service_instance, data_model=models.AGENT_LOCATION_MODEL ) message = OefSearchMessage( performative="oef_error", dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), service_description=service_description, ) message.to = str(SOEFConnection.connection_id.to_any()) message.sender = self.skill_id envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with pytest.raises(ValueError): await self.connection.send(envelope) @pytest.mark.asyncio async def test_bad_search_query(self): """Test fail on invalid query for search.""" await self.test_register_service() closeness_query = Query([], model=models.AGENT_LOCATION_MODEL) message, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=closeness_query, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async(self.search_empty_response), ): await self.connection.send(envelope) expected_envelope = await asyncio.wait_for(self.connection.receive(), timeout=1) assert expected_envelope message = expected_envelope.message assert message.performative == OefSearchMessage.Performative.OEF_ERROR message = expected_envelope.message receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue @pytest.mark.asyncio async def test_search(self): """Test search.""" agent_location = Location(52.2057092, 2.1183431) radius = 0.1 close_to_my_service = Constraint( "location", ConstraintType("distance", (agent_location, radius)) ) personality_filters = [ Constraint("genus", ConstraintType("==", "vehicle")), Constraint( "classification", ConstraintType("==", "mobility.railway.train") ), ] service_key_filters = [ Constraint("custom_key", ConstraintType("==", "custom_value")), ] closeness_query = Query( [close_to_my_service] + personality_filters + service_key_filters ) message, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=closeness_query, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async(self.search_success_response), ): await self.connection.send(envelope) expected_envelope = await asyncio.wait_for( self.connection.receive(), timeout=1 ) assert expected_envelope message = expected_envelope.message assert len(message.agents) >= 1 message = expected_envelope.message receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue @pytest.mark.asyncio async def test_find_around_me(self): """Test internal method find around me.""" agent_location = Location(52.2057092, 2.1183431) radius = 0.1 close_to_my_service = Constraint( "location", ConstraintType("distance", (agent_location, radius)) ) personality_filters = [ Constraint("genus", ConstraintType("==", "vehicle")), Constraint( "classification", ConstraintType("==", "mobility.railway.train") ), ] service_key_filters = [ Constraint("custom_key", ConstraintType("==", "custom_value")), ] closeness_query = Query( [close_to_my_service] + personality_filters + service_key_filters ) message_1, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=closeness_query, ) internal_dialogue_1 = self.connection.channel.oef_search_dialogues.update( message_1 ) assert internal_dialogue_1 is not None message_2, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=closeness_query, ) internal_dialogue_2 = self.connection.channel.oef_search_dialogues.update( message_2 ) assert internal_dialogue_2 is not None message_3, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=closeness_query, ) internal_dialogue_3 = self.connection.channel.oef_search_dialogues.update( message_3 ) assert internal_dialogue_3 is not None with patch.object( self.connection.channel, "_request_text", new_callable=MagicMock, side_effect=[ wrap_future(self.search_empty_response), wrap_future(self.search_success_response), wrap_future(self.search_fail_response), ], ): await self.connection.channel._find_around_me_handle_request( message_1, internal_dialogue_1, 1, {} ) await self.connection.channel._find_around_me_handle_request( message_2, internal_dialogue_2, 1, {} ) with pytest.raises( SOEFException, match=r".* `find_around_me` .*Exception: .*" ): await self.connection.channel._find_around_me_handle_request( message_3, internal_dialogue_3, 1, {} ) @pytest.mark.asyncio async def test_register_agent(self): """Test internal method register agent.""" resp_text = '' with patch.object( self.connection.channel, "_request_text", make_async(resp_text) ): with pytest.raises( SOEFException, match="Agent registration error - page address or token not received", ): await self.connection.channel._register_agent() resp_text = '0672DB3B67780F98984ABF1123BD11oef_C95B21A4D5759C8FE7A6304B62B726AB8077BEE4BA191A7B92B388F9B1' with patch.object( self.connection.channel, "_request_text", make_async(resp_text) ): with pytest.raises( SOEFException, match=r"`acknowledge` .*Exception: .*", ): await self.connection.channel._register_agent() resp_text1 = '0672DB3B67780F98984ABF1123BD11oef_C95B21A4D5759C8FE7A6304B62B726AB8077BEE4BA191A7B92B388F9B1' resp_text2 = '1' resp_text3 = '1' with patch.object( self.connection.channel, "_request_text", new_callable=MagicMock, side_effect=[ wrap_future(resp_text1), wrap_future(resp_text2), wrap_future(resp_text3), ], ): await self.connection.channel._register_agent() assert self.connection.channel._ping_periodic_task is not None @pytest.mark.asyncio async def test_check_server_reachable(self): """Test server can not be reached.""" resp = MagicMock() resp.status_code = 200 resp.text = "" async def slow_request(*args, **kwargs): await asyncio.sleep(10) with patch.object( self.connection.channel, "_request_text", slow_request ), patch.object(self.connection.channel, "connection_check_timeout", 0.01): with pytest.raises( SOEFNetworkConnectionError, match="Goodbye!"), ): self.loop.run_until_complete(self.connection.disconnect()) shutil.rmtree(self.data_dir) except Exception: # nosec pass @pytest.mark.asyncio async def test__set_value(self): """Test set pieces.""" resp_text = '' with patch.object( self.connection.channel, "_request_text", make_async(resp_text) ): with pytest.raises( SOEFException, match=r"`set_personality_piece`.*Exception:" ): await self.connection.channel._set_personality_piece(1, 1) resp_text = '1' with patch.object( self.connection.channel, "_request_text", make_async(resp_text) ): await self.connection.channel._set_personality_piece(1, 1) def test_chain_identifier_fail(self): """Test fail on invalid chain id.""" chain_identifier = "test" identity = Identity("identity", "", "") configuration = ConnectionConfig( api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="s-oef.fetch.ai", soef_port=443, restricted_to_protocols={OefSearchMessage.protocol_specification_id}, connection_id=SOEFConnection.connection_id, chain_identifier=chain_identifier, ) with pytest.raises(ValueError, match="Unsupported chain_identifier"): SOEFConnection( configuration=configuration, data_dir=MagicMock(), identity=identity, ) def test_chain_identifier_ok(self): """Test set valid chain id.""" chain_identifier = "fetchai_cosmos" identity = Identity("identity", "", "") configuration = ConnectionConfig( api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="s-oef.fetch.ai", soef_port=443, restricted_to_protocols={OefSearchMessage.protocol_specification_id}, connection_id=SOEFConnection.connection_id, chain_identifier=chain_identifier, ) connection = SOEFConnection( configuration=configuration, data_dir=MagicMock(), identity=identity, ) assert connection.channel.chain_identifier == chain_identifier @pytest.mark.asyncio async def test_ping_command(self): """Test set service key.""" service_description = Description({}, data_model=models.PING_MODEL) message, _ = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) with patch.object( self.connection.channel, "_request_text", make_async(self.generic_success_response), ): await self.connection.send(envelope) response = await asyncio.wait_for(self.connection.receive(), timeout=1) assert response.message.performative == OefSearchMessage.Performative.SUCCESS assert response.message.agents_info.body == {} @pytest.mark.asyncio async def test_periodic_ping_task_is_set(self): """Test periodic ping task is set on agent register.""" resp_text1 = '0672DB3B67780F98984ABF1123BD11oef_C95B21A4D5759C8FE7A6304B62B726AB8077BEE4BA191A7B92B388F9B1' resp_text2 = '1' resp_text3 = '1' self.connection.channel.PING_PERIOD = 0.1 with patch.object( self.connection.channel, "_request_text", new_callable=MagicMock, side_effect=[ wrap_future(resp_text1), wrap_future(resp_text2), wrap_future(resp_text3), ], ): with patch.object( self.connection.channel, "_ping_command", return_value=wrap_future(None) ) as mocked_ping: await self.connection.channel._register_agent() assert self.connection.channel._ping_periodic_task is not None await asyncio.sleep(0.3) assert mocked_ping.call_count > 1 ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_soef/test_soef_integration.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the soef connection module.""" import logging import time import urllib from threading import Thread from typing import Any, Dict, Optional, Tuple, cast from unittest.mock import MagicMock from urllib.parse import urlencode import pytest from defusedxml import ElementTree as ET # pylint: disable=wrong-import-order from aea.configurations.base import ConnectionConfig from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.base import Crypto from aea.crypto.registries import make_crypto from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.soef.connection import SOEFConnection from packages.fetchai.protocols.oef_search.message import OefSearchMessage from .test_soef import OefSearchDialogues from tests.common.utils import wait_for_condition from tests.test_packages_for_aea_tests.test_connections.test_soef import models logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) def make_multiplexer_and_dialogues() -> Tuple[ Multiplexer, OefSearchDialogues, Crypto, SOEFConnection ]: """Return multplexer, dialogues and crypto instances.""" crypto = make_crypto(DEFAULT_LEDGER) identity = Identity( "identity", address=crypto.address, public_key=crypto.public_key ) skill_id = "some/skill:0.1.0" oef_search_dialogues = OefSearchDialogues(skill_id) # create the connection and multiplexer objects configuration = ConnectionConfig( api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="s-oef.fetch.ai", soef_port=443, restricted_to_protocols={ OefSearchMessage.protocol_specification_id, OefSearchMessage.protocol_id, }, connection_id=SOEFConnection.connection_id, ) soef_connection = SOEFConnection( configuration=configuration, data_dir=MagicMock(), identity=identity, ) multiplexer = Multiplexer([soef_connection]) return multiplexer, oef_search_dialogues, crypto, soef_connection class Instance: """Test agent instance.""" def __init__(self, location: Location) -> None: """Init instance with location provided.""" self.location = location ( self.multiplexer, self.oef_search_dialogues, self.crypto, self.connection, ) = make_multiplexer_and_dialogues() self.thread = Thread(target=self.multiplexer.connect) @property def address(self) -> str: """Get agent adress.""" return self.crypto.address def start(self) -> None: """Start multipelxer.""" self.thread.start() wait_for_condition(lambda: self.multiplexer.is_connected, timeout=5) self.register_location() def stop(self): """Stop multipelxer and wait.""" self.multiplexer.disconnect() self.thread.join() def register_location(self, disclosure_accuracy: Optional[str] = None) -> None: """Register location.""" service_instance: Dict[str, Any] = {"location": self.location} if disclosure_accuracy: service_instance["disclosure_accuracy"] = disclosure_accuracy service_description = Description( service_instance, data_model=models.AGENT_LOCATION_MODEL ) message, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) logger.info( "Registering agent at location=({},{}) by agent={}".format( self.location.latitude, self.location.longitude, self.crypto.address, ) ) self.multiplexer.put(envelope) # check for register results envelope = self.get() assert envelope message = cast(OefSearchMessage, envelope.message) receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue def wait_registered(self) -> None: """Wait connection gets unique_page_address.""" wait_for_condition( lambda: self.connection.channel.unique_page_address, timeout=10 ) def register_personality_pieces( self, piece: str = "genus", value: str = "service" ) -> None: """Register personality pieces.""" service_instance = {"piece": piece, "value": value} service_description = Description( service_instance, data_model=models.AGENT_PERSONALITY_MODEL ) message, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) logger.info("Registering agent personality") self.multiplexer.put(envelope) # check for register results envelope = self.get() assert envelope message = cast(OefSearchMessage, envelope.message) receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue def register_service_key(self, key: str, value: str) -> None: """Register service key.""" service_instance = {"key": "test", "value": "test"} service_description = Description( service_instance, data_model=models.SET_SERVICE_KEY_MODEL ) message, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) logger.info("Registering agent service key") self.multiplexer.put(envelope) # check for register results envelope = self.get() assert envelope message = cast(OefSearchMessage, envelope.message) receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue def search(self, query: Query) -> OefSearchMessage: """Perform search with query provided.""" message, sending_dialogue = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) search_envelope = Envelope( to=message.to, sender=message.sender, message=message, ) logger.info(f"Searching for agents with query: {query}") self.multiplexer.put(search_envelope) # check for search results envelope = self.get() assert envelope message = cast(OefSearchMessage, envelope.message) receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue return message def get(self): """Get an instance.""" wait_for_condition(lambda: not self.multiplexer.in_queue.empty(), timeout=20) return self.multiplexer.get() def generic_command(self, command: str, parameters: Optional[dict] = None) -> None: """Register personality pieces.""" service_instance = {"command": command} if parameters: service_instance["parameters"] = urlencode(parameters) service_description = Description( service_instance, data_model=models.AGENT_GENERIC_COMMAND_MODEL ) message, _ = self.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) logger.info(f"Send generic command {command} {parameters}") self.multiplexer.put(envelope) class TestRealNetwork: """Perform tests using real soef server.""" LOCATION = (52.2057092, 2.1183431) @pytest.mark.integration def test_search_no_filters(self): """Perform tests over real networ with no filters.""" agent_location = Location(*self.LOCATION) agent = Instance(agent_location) agent2 = Instance(agent_location) try: agent.start() agent2.start() agent.wait_registered() agent2.wait_registered() time.sleep(2) # find agents near me radius = 0.1 close_to_my_service = Constraint( "location", ConstraintType("distance", (agent_location, radius)) ) closeness_query = Query( [close_to_my_service], model=models.AGENT_LOCATION_MODEL ) # search for agents close to me message = agent.search(closeness_query) assert message.performative == OefSearchMessage.Performative.SEARCH_RESULT assert len(message.agents) >= 1 # second message in a raw to check we dont hit limit message = agent.search(closeness_query) assert message.performative == OefSearchMessage.Performative.SEARCH_RESULT assert len(message.agents) >= 1 assert agent2.address in message.agents finally: agent.stop() agent2.stop() @pytest.mark.integration def test_search_filters(self): """Test find agents near me with filter.""" agent_location = Location(*self.LOCATION) agent = Instance(agent_location) agent2 = Instance(agent_location) agent3 = Instance(agent_location) agent.start() agent2.start() agent3.start() try: agent2.register_personality_pieces(piece="genus", value="service") agent2.register_service_key(key="test", value="test") agent2.register_location(disclosure_accuracy="medium") agent3.register_personality_pieces(piece="genus", value="service") agent3.register_service_key(key="test", value="test") time.sleep(3) radius = 0.1 close_to_my_service = Constraint( "location", ConstraintType("distance", (agent_location, radius)) ) personality_filters = [ Constraint("genus", ConstraintType("==", "service")), ] service_key_filters = [ Constraint("test", ConstraintType("==", "test")), ] constraints = ( [close_to_my_service] + personality_filters + service_key_filters ) closeness_query = Query(constraints) logger.info( "Searching for agents in radius={} of myself at location=({},{}) with personality filters".format( radius, agent_location.latitude, agent_location.longitude, ) ) message = agent.search(closeness_query) assert message.performative == OefSearchMessage.Performative.SEARCH_RESULT assert len(message.agents) >= 1 assert agent2.address in message.agents assert agent3.address in message.agents agent2_info = message.agents_info.get_info_for_agent(agent2.address) assert agent2_info assert "name" in agent2_info assert "location" in agent2_info assert agent2_info["genus"] == "service" agent3_info = message.agents_info.get_info_for_agent(agent3.address) assert agent3_info assert "name" in agent3_info assert "location" not in agent3_info assert agent3_info["genus"] == "service" finally: agent.stop() agent2.stop() agent3.stop() @pytest.mark.integration def test_ping(self): """Test ping command.""" agent_location = Location(*self.LOCATION) agent = Instance(agent_location) agent.start() try: service_description = Description({}, data_model=models.PING_MODEL) message, _ = agent.oef_search_dialogues.create( counterparty=str(SOEFConnection.connection_id.to_any()), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, ) envelope = Envelope( to=message.to, sender="some/skill:0.1.0", message=message, ) logger.info("Pinging") agent.multiplexer.put(envelope) envelope = agent.get() assert ( envelope.message.performative == OefSearchMessage.Performative.SUCCESS ) finally: agent.stop() @pytest.mark.integration def test_generic_command(self): """Test generic command.""" agent_location = Location(*self.LOCATION) agent = Instance(agent_location) agent.start() try: agent.generic_command("set_service_key", {"key": "test", "value": "test"}) envelope = agent.get() assert ( envelope.message.performative == OefSearchMessage.Performative.SUCCESS ) ET.fromstring(envelope.message.agents_info.body["response"]["content"]) agent.generic_command("bad_command") envelope = agent.get() assert ( envelope.message.performative == OefSearchMessage.Performative.OEF_ERROR ) finally: agent.stop() @pytest.mark.integration def test_generic_command_set_declared_name(self): """Test generic command.""" agent_location = Location(*self.LOCATION) agent1 = Instance(agent_location) agent1.start() agent2 = Instance(agent_location) agent2.start() declared_name = "new_declared_name" try: # send generic command.""" service_description = Description( { "command": "set_declared_name", "parameters": urllib.parse.urlencode({"name": declared_name}), }, data_model=models.AGENT_GENERIC_COMMAND_MODEL, ) message, _ = agent1.oef_search_dialogues.create( performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=service_description, counterparty=str(SOEFConnection.connection_id.to_any()), ) envelope = Envelope( to=message.to, sender=message.sender, message=message, ) agent1.multiplexer.put(envelope) envelope = agent1.get() assert ( envelope.message.performative == OefSearchMessage.Performative.SUCCESS ) radius = 0.1 close_to_my_service = Constraint( "location", ConstraintType("distance", (agent_location, radius)) ) closeness_query = Query( [close_to_my_service], model=models.AGENT_LOCATION_MODEL ) message = agent2.search(closeness_query) assert message.performative == OefSearchMessage.Performative.SEARCH_RESULT assert len(message.agents) >= 1 assert agent1.address in message.agents_info.body assert message.agents_info.body[agent1.address]["name"] == declared_name finally: agent1.stop() agent2.stop() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_stub/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the stub connection implementation.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_stub/test_stub.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This test module contains the tests for the stub connection.""" import asyncio import base64 import os import shutil import tempfile import time from asyncio.tasks import ensure_future from pathlib import Path from unittest import mock import pytest from aea.configurations.base import PublicId from aea.crypto.wallet import CryptoStore from aea.helpers.file_io import write_with_lock from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.connections.stub.connection import ( StubConnection, envelope_from_bytes, lock_file, write_envelope, ) from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from tests.conftest import ROOT_DIR, _make_stub_connection SEPARATOR = "," def make_test_envelope() -> Envelope: """Create a test envelope.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg.to = "any" envelope = Envelope( to="any", sender="any", message=msg, ) return envelope class TestStubConnectionReception: """Test that the stub connection is implemented correctly.""" def setup(self): """Set the test up.""" self.cwd = os.getcwd() self.tmpdir = Path(tempfile.mkdtemp()) d = self.tmpdir / "test_stub" d.mkdir(parents=True) self.input_file_path = d / "input_file.csv" self.output_file_path = d / "output_file.csv" self.connection = _make_stub_connection( self.input_file_path, self.output_file_path ) self.multiplexer = Multiplexer([self.connection]) self.multiplexer.connect() os.chdir(self.tmpdir) def test_reception_a(self): """Test that the connection receives what has been enqueued in the input file.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_envelope = Envelope( to="any", sender="anys", message=msg, ) with open(self.input_file_path, "ab+") as f: write_envelope(expected_envelope, f) actual_envelope = self.multiplexer.get(block=True, timeout=3.0) assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) msg = DefaultMessage.serializer.decode(actual_envelope.message) msg.to = actual_envelope.to msg.sender = actual_envelope.sender assert expected_envelope.message == msg def test_reception_b(self): """Test that the connection receives what has been enqueued in the input file.""" # a message containing delimiters and newline characters msg = b"\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468d\n,\nB8Ab795\n\n49B49C88DC991990E7910891,,dbd\n" protocol_specification_id = PublicId.from_str("some_author/some_name:0.1.0") encoded_envelope = "{}{}{}{}{}{}{}{}".format( "any", SEPARATOR, "any", SEPARATOR, protocol_specification_id, SEPARATOR, msg.decode("utf-8"), SEPARATOR, ) encoded_envelope = encoded_envelope.encode("utf-8") with open(self.input_file_path, "ab+") as f: write_with_lock(f, encoded_envelope) actual_envelope = self.multiplexer.get(block=True, timeout=3.0) assert "any" == actual_envelope.to assert "any" == actual_envelope.sender assert protocol_specification_id == actual_envelope.protocol_specification_id assert msg == actual_envelope.message def test_reception_c(self): """Test that the connection receives what has been enqueued in the input file.""" encoded_envelope = b"0x5E22777dD831A459535AA4306AceC9cb22eC4cB5,default_oef,fetchai/oef_search:1.0.0,\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd," expected_envelope = Envelope( to="0x5E22777dD831A459535AA4306AceC9cb22eC4cB5", sender="default_oef", protocol_specification_id=OefSearchMessage.protocol_specification_id, message=b"\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd", ) with open(self.input_file_path, "ab+") as f: write_with_lock(f, encoded_envelope) actual_envelope = self.multiplexer.get(block=True, timeout=3.0) assert expected_envelope == actual_envelope def teardown(self): """Tear down the test.""" os.chdir(self.cwd) try: shutil.rmtree(self.tmpdir) except (OSError, IOError): pass self.multiplexer.disconnect() class TestStubConnectionSending: """Test that the stub connection is implemented correctly.""" @classmethod def setup_class(cls): """Set the test up.""" cls.cwd = os.getcwd() cls.tmpdir = Path(tempfile.mkdtemp()) d = cls.tmpdir / "test_stub" d.mkdir(parents=True) cls.input_file_path = d / "input_file.csv" cls.output_file_path = d / "output_file.csv" cls.connection = _make_stub_connection( cls.input_file_path, cls.output_file_path ) cls.multiplexer = Multiplexer([cls.connection]) cls.multiplexer.connect() os.chdir(cls.tmpdir) def test_connection_is_established(self): """Test the stub connection is established and then bad formatted messages.""" assert self.connection.is_connected msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) encoded_envelope = "{}{}{}{}{}{}{}{}".format( "any", SEPARATOR, "any", SEPARATOR, DefaultMessage.protocol_specification_id, SEPARATOR, DefaultMessage.serializer.encode(msg).decode("utf-8"), SEPARATOR, ) encoded_envelope = base64.b64encode(encoded_envelope.encode("utf-8")) envelope = envelope_from_bytes(encoded_envelope) if envelope is not None: self.connection._put_envelopes([envelope]) assert ( self.connection.in_queue.empty() ), "The inbox must be empty due to bad encoded message" def test_send_message(self): """Test that the messages in the outbox are posted on the output file.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_envelope = Envelope( to="any", sender="anys", message=msg, ) self.multiplexer.put(expected_envelope) time.sleep(0.1) with open(self.output_file_path, "rb+") as f: lines = f.readlines() assert len(lines) == 2 line = lines[0] + lines[1] to, sender, protocol_specification_id, message, end = line.strip().split( "{}".format(SEPARATOR).encode("utf-8"), maxsplit=4 ) to = to.decode("utf-8") sender = sender.decode("utf-8") protocol_specification_id = PublicId.from_str( protocol_specification_id.decode("utf-8") ) assert end in [b"", b"\n"] actual_envelope = Envelope( to=to, sender=sender, protocol_specification_id=protocol_specification_id, message=message, ) assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) msg = DefaultMessage.serializer.decode(actual_envelope.message) msg.to = actual_envelope.to msg.sender = actual_envelope.sender assert expected_envelope.message == msg @classmethod def teardown_class(cls): """Tear down the test.""" os.chdir(cls.cwd) try: shutil.rmtree(cls.tmpdir) except (OSError, IOError): pass cls.multiplexer.disconnect() @pytest.mark.asyncio async def test_disconnection_when_already_disconnected(): """Test the case when disconnecting a connection already disconnected.""" tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "output_file.csv" connection = _make_stub_connection(input_file_path, output_file_path) assert not connection.is_connected await connection.disconnect() assert not connection.is_connected @pytest.mark.asyncio async def test_connection_when_already_connected(): """Test the case when connecting a connection already connected.""" tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "output_file.csv" connection = _make_stub_connection(input_file_path, output_file_path) assert not connection.is_connected await connection.connect() assert connection.is_connected await connection.connect() assert connection.is_connected await connection.disconnect() @pytest.mark.asyncio async def test_receiving_returns_none_when_error_occurs(): """Test that when we try to receive an envelope and an error occurs we return None.""" tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "output_file.csv" connection = _make_stub_connection(input_file_path, output_file_path) await connection.connect() with mock.patch.object(connection.in_queue, "get", side_effect=Exception): ret = await connection.receive() assert ret is None await connection.disconnect() @pytest.mark.asyncio async def test_multiple_envelopes(): """Test many envelopes received.""" tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "output_file.csv" connection = _make_stub_connection(input_file_path, output_file_path) num_envelopes = 5 await connection.connect() assert connection.is_connected async def wait_num(num): for _ in range(num): assert await connection.receive() task = asyncio.get_event_loop().create_task(wait_num(num_envelopes)) with open(input_file_path, "ab+") as f: for _ in range(num_envelopes): write_envelope(make_test_envelope(), f) await asyncio.sleep(0.01) # spin asyncio loop await asyncio.wait_for(task, timeout=3) await connection.disconnect() @pytest.mark.asyncio async def test_bad_envelope(): """Test bad format envelop.""" tmpdir = Path(tempfile.mkdtemp()) d = tmpdir / "test_stub" d.mkdir(parents=True) input_file_path = d / "input_file.csv" output_file_path = d / "output_file.csv" connection = _make_stub_connection(input_file_path, output_file_path) await connection.connect() with open(input_file_path, "ab+") as f: f.write(b"1,2,3,4,5,") f.flush() with pytest.raises(asyncio.TimeoutError): f = ensure_future(connection.receive()) await asyncio.wait_for(f, timeout=0.1) await connection.disconnect() @pytest.mark.asyncio async def test_load_from_dir(): """Test stub connection can be loaded from dir.""" StubConnection.from_dir( ROOT_DIR + "/packages/fetchai/connections/stub", Identity("name", "address", "public_key"), CryptoStore(), os.getcwd(), ) class TestFileLock: """Test for filelocks.""" def test_lock_file_ok(self): """Work ok ok for random file.""" with tempfile.TemporaryFile() as fp: with lock_file(fp): pass def test_lock_file_error(self): """Fail on closed file.""" with tempfile.TemporaryFile() as fp: fp.close() with pytest.raises(ValueError): with lock_file(fp): pass ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_tcp/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the TCP connection.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_tcp/test_base.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the TCP base module.""" import asyncio import unittest.mock from asyncio import CancelledError import pytest from aea.mail.base import Envelope from packages.fetchai.protocols.default.message import DefaultMessage from tests.conftest import ( _make_tcp_client_connection, _make_tcp_server_connection, get_unused_tcp_port, ) @pytest.mark.asyncio async def test_connect_twice(): """Test that connecting twice the tcp connection works correctly.""" port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection( "address", "public_key", "127.0.0.1", port ) await tcp_connection.connect() await asyncio.sleep(0.1) with unittest.mock.patch.object(tcp_connection.logger, "warning") as mock_logger: await tcp_connection.connect() mock_logger.assert_called_with("Connection already set up.") await tcp_connection.disconnect() @pytest.mark.asyncio async def test_connect_raises_exception(): """Test the case that a connection attempt raises an exception.""" port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection( "address", "public_key", "127.0.0.1", port ) with unittest.mock.patch.object(tcp_connection.logger, "error") as mock_logger: with unittest.mock.patch.object( tcp_connection, "setup", side_effect=Exception("error during setup") ): await tcp_connection.connect() mock_logger.assert_called_with("error during setup") @pytest.mark.asyncio async def test_disconnect_when_already_disconnected(): """Test that disconnecting a connection already disconnected works correctly.""" port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection( "address", "public_key", "127.0.0.1", port ) with unittest.mock.patch.object(tcp_connection.logger, "warning") as mock_logger: await tcp_connection.disconnect() mock_logger.assert_called_with("Connection already disconnected.") @pytest.mark.asyncio async def test_send_to_unknown_destination(): """Test that a message to an unknown destination logs an error.""" address = "address" port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection( "address", "public_key", "127.0.0.1", port ) envelope = Envelope( to="non_existing_destination", sender="address", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", ) with unittest.mock.patch.object(tcp_connection.logger, "error") as mock_logger: await tcp_connection.send(envelope) mock_logger.assert_called_with( "[{}]: Cannot send envelope {}".format(address, envelope) ) @pytest.mark.asyncio async def test_send_cancelled(): """Test that cancelling a send works correctly.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection( "address_server", "public_key", "127.0.0.1", port ) tcp_client = _make_tcp_client_connection( "address_client", "public_key_client", "127.0.0.1", port ) await tcp_server.connect() await tcp_client.connect() with unittest.mock.patch.object( tcp_client._writer, "drain", side_effect=CancelledError ): envelope = Envelope( to="address_client", sender="address_server", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", ) await tcp_client.send(envelope) await tcp_client.disconnect() await tcp_server.disconnect() ================================================ FILE: tests/test_packages_for_aea_tests/test_connections/test_tcp/test_communication.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the TCP connection communication.""" import asyncio import struct import unittest.mock import pytest from aea.mail.base import Envelope from aea.multiplexer import Multiplexer from packages.fetchai.protocols.default.message import DefaultMessage from tests.conftest import ( _make_tcp_client_connection, _make_tcp_server_connection, get_unused_tcp_port, ) class TestTCPCommunication: """Test that TCP Server and TCP Client can communicate.""" @classmethod def setup_class(cls): """Set up the test class.""" cls.host = "127.0.0.1" cls.port = get_unused_tcp_port() cls.server_addr = "server_addr" cls.server_public_key = "server_public_key" cls.client_addr_1 = "client_addr_1" cls.client_public_key_1 = "client_public_key_1" cls.client_addr_2 = "client_addr_2" cls.client_public_key_2 = "client_public_key_2" cls.server_conn = _make_tcp_server_connection( cls.server_addr, cls.server_public_key, cls.host, cls.port, ) cls.client_conn_1 = _make_tcp_client_connection( cls.client_addr_1, cls.client_public_key_1, cls.host, cls.port, ) cls.client_conn_2 = _make_tcp_client_connection( cls.client_addr_2, cls.client_public_key_2, cls.host, cls.port, ) cls.server_multiplexer = Multiplexer([cls.server_conn]) cls.client_1_multiplexer = Multiplexer([cls.client_conn_1]) cls.client_2_multiplexer = Multiplexer([cls.client_conn_2]) assert not cls.server_conn.is_connected assert not cls.client_conn_1.is_connected assert not cls.client_conn_2.is_connected cls.server_multiplexer.connect() cls.client_1_multiplexer.connect() cls.client_2_multiplexer.connect() def test_is_connected(self): """Test that the connection status are connected.""" assert self.server_conn.is_connected assert self.client_conn_1.is_connected assert self.client_conn_2.is_connected def test_communication_client_server(self): """Test that envelopes can be sent from a client to a server.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_envelope = Envelope( to=self.server_addr, sender=self.client_addr_1, message=msg, ) self.client_1_multiplexer.put(expected_envelope) actual_envelope = self.server_multiplexer.get(block=True, timeout=5.0) assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message msg = DefaultMessage.serializer.decode(actual_envelope.message) msg.to = actual_envelope.to msg.sender = actual_envelope.sender assert expected_envelope.message == msg def test_communication_server_client(self): """Test that envelopes can be sent from a server to a client.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_envelope = Envelope( to=self.client_addr_1, sender=self.server_addr, message=msg, ) self.server_multiplexer.put(expected_envelope) actual_envelope = self.client_1_multiplexer.get(block=True, timeout=5.0) assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message msg = DefaultMessage.serializer.decode(actual_envelope.message) msg.to = actual_envelope.to msg.sender = actual_envelope.sender assert expected_envelope.message == msg msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) expected_envelope = Envelope( to=self.client_addr_2, sender=self.server_addr, message=msg, ) self.server_multiplexer.put(expected_envelope) actual_envelope = self.client_2_multiplexer.get(block=True, timeout=5.0) assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message msg = DefaultMessage.serializer.decode(actual_envelope.message) msg.to = actual_envelope.to msg.sender = actual_envelope.sender assert expected_envelope.message == msg @classmethod def teardown_class(cls): """Tear down the test class.""" cls.server_multiplexer.disconnect() cls.client_1_multiplexer.disconnect() cls.client_2_multiplexer.disconnect() class TestTCPClientConnection: """Test TCP Client code.""" @pytest.mark.asyncio async def test_receive_cancelled(self): """Test that cancelling a receive task works correctly.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection( "address_server", "public_key_server", "127.0.0.1", port, ) tcp_client = _make_tcp_client_connection( "address_client", "public_key_client", "127.0.0.1", port, ) await tcp_server.connect() await tcp_client.connect() with unittest.mock.patch.object(tcp_client.logger, "debug") as mock_logger: task = asyncio.ensure_future(tcp_client.receive()) await asyncio.sleep(0.1) task.cancel() await asyncio.sleep(0.1) mock_logger.assert_called_with( "[{}] Read cancelled.".format("address_client") ) assert task.result() is None await tcp_client.disconnect() await tcp_server.disconnect() @pytest.mark.asyncio async def test_receive_raises_struct_error(self): """Test the case when a receive raises a struct error.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection( "address_server", "public_key_server", "127.0.0.1", port, ) tcp_client = _make_tcp_client_connection( "address_client", "public_key_client", "127.0.0.1", port, ) await tcp_server.connect() await tcp_client.connect() with unittest.mock.patch.object(tcp_client.logger, "debug") as mock_logger: with unittest.mock.patch.object( tcp_client, "_recv", side_effect=struct.error ): task = asyncio.ensure_future(tcp_client.receive()) await asyncio.sleep(0.1) mock_logger.assert_called_with("Struct error: ") assert task.result() is None await tcp_client.disconnect() await tcp_server.disconnect() @pytest.mark.asyncio async def test_receive_raises_exception(self): """Test the case when a receive raises a generic exception.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection( "address_server", "public_key_server", "127.0.0.1", port, ) tcp_client = _make_tcp_client_connection( "address_client", "public_key_client", "127.0.0.1", port, ) await tcp_server.connect() await tcp_client.connect() with pytest.raises(Exception, match="generic exception"): with unittest.mock.patch.object( tcp_client, "_recv", side_effect=Exception("generic exception") ): task = asyncio.ensure_future(tcp_client.receive()) await asyncio.sleep(0.1) assert task.result() is None await tcp_client.disconnect() await tcp_server.disconnect() class TestTCPServerConnection: """Test TCP Server code.""" @pytest.mark.asyncio async def test_receive_raises_exception(self): """Test the case when a receive raises a generic exception.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection( "address_server", "public_key_server", "127.0.0.1", port, ) tcp_client = _make_tcp_client_connection( "address_client", "public_key_client", "127.0.0.1", port, ) await tcp_server.connect() await tcp_client.connect() await asyncio.sleep(0.1) with unittest.mock.patch.object(tcp_server.logger, "error") as mock_logger: with unittest.mock.patch( "asyncio.wait", side_effect=Exception("generic exception") ): result = await tcp_server.receive() assert result is None mock_logger.assert_any_call( "Error in the receiving loop: generic exception" ) await tcp_client.disconnect() await tcp_server.disconnect() ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_acn.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the messages module.""" from typing import Type from unittest import mock from unittest.mock import patch import pytest from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel from packages.fetchai.protocols.acn.dialogues import AcnDialogue as BaseAcnDialogue from packages.fetchai.protocols.acn.dialogues import AcnDialogues as BaseAcnDialogues from packages.fetchai.protocols.acn.message import AcnMessage def test_acn_aea_envelope_serialization(): """Test that the serialization for the 'simple' protocol works for the AEA_ENVELOPE message.""" expected_msg = AcnMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=AcnMessage.Performative.AEA_ENVELOPE, envelope=b"envelope", record=AcnMessage.AgentRecord( address="address", public_key="pbk", peer_public_key="peerpbk", signature="sign", service_id="acn", ledger_id="fetchai", ), ) msg_bytes = AcnMessage.serializer.encode(expected_msg) actual_msg = AcnMessage.serializer.decode(msg_bytes) assert expected_msg == actual_msg def test_acn_lookup_request_serialization(): """Test that the serialization for the 'simple' protocol works for the LOOKUP_REQUEST message.""" msg = AcnMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=AcnMessage.Performative.LOOKUP_REQUEST, agent_address="some_address", ) msg_bytes = AcnMessage.serializer.encode(msg) actual_msg = AcnMessage.serializer.decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg def test_acn_lookup_response_serialization(): """Test that the serialization for the 'simple' protocol works for the LOOKUP_RESPONSE message.""" msg = AcnMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=AcnMessage.Performative.LOOKUP_RESPONSE, record=AcnMessage.AgentRecord( address="address", public_key="pbk", peer_public_key="peerpbk", signature="sign", service_id="acn", ledger_id="fetchai", ), ) msg_bytes = AcnMessage.serializer.encode(msg) actual_msg = AcnMessage.serializer.decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg def test_acn_record_serialization(): """Test that the serialization for the 'simple' protocol works for the REGISTER message.""" msg = AcnMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=AcnMessage.Performative.REGISTER, record=AcnMessage.AgentRecord( address="address", public_key="pbk", peer_public_key="peerpbk", signature="sign", service_id="acn", ledger_id="fetchai", ), ) msg_bytes = AcnMessage.serializer.encode(msg) actual_msg = AcnMessage.serializer.decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg def test_acn_status_serialization(): """Test that the serialization for the 'simple' protocol works for the STATUS message.""" msg = AcnMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=AcnMessage.Performative.STATUS, body=AcnMessage.StatusBody( status_code=AcnMessage.StatusBody.StatusCode.ERROR_UNSUPPORTED_VERSION, msgs=["pbk"], ), ) msg_bytes = AcnMessage.serializer.encode(msg) actual_msg = AcnMessage.serializer.decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg def test_acn_message_str_values(): """Tests the returned string values of acn Message.""" assert ( str(AcnMessage.Performative.LOOKUP_REQUEST) == "lookup_request" ), "AcnMessage.Performative.LOOKUP_REQUEST must be lookup_request" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = AcnMessage( performative=AcnMessage.Performative.LOOKUP_REQUEST, agent_address="address", ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(AcnMessage.Performative, "__eq__", return_value=False): AcnMessage.serializer.encode(msg) def test_check_consistency_raises_exception_when_type_not_recognized(): """Test that we raise exception when the type of the message is not recognized.""" message = AcnMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=AcnMessage.Performative.LOOKUP_REQUEST, agent_address="address", ) # mock the __eq__ method such that any kind of matching is going to fail. with mock.patch.object(AcnMessage.Performative, "__eq__", return_value=False): assert not message._is_consistent() def test_acn_valid_performatives(): """Test 'valid_performatives' getter.""" msg = AcnMessage(AcnMessage.Performative.LOOKUP_REQUEST, agent_address="address") assert msg.valid_performatives == set( map(lambda x: x.value, iter(AcnMessage.Performative)) ) def test_serializer_performative_not_found(): """Test the serializer when the performative is not found.""" message = AcnMessage( message_id=1, target=0, performative=AcnMessage.Performative.LOOKUP_REQUEST, agent_address="address", ) message_bytes = message.serializer.encode(message) with patch.object(AcnMessage.Performative, "__eq__", return_value=False): with pytest.raises(ValueError, match="Performative not valid: .*"): message.serializer.decode(message_bytes) def test_dialogues(): """Test intiaontiation of dialogues.""" acn_dialogues = AcnDialogues("agent_addr") msg, dialogue = acn_dialogues.create( counterparty="abc", performative=AcnMessage.Performative.LOOKUP_REQUEST, agent_address="address", ) assert dialogue is not None class AcnDialogue(BaseAcnDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[AcnMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ BaseAcnDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AcnDialogues(BaseAcnDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return AcnDialogue.Role.NODE BaseAcnDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AcnDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_contract_api.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the contract_api protocol package.""" import logging import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.contract_api.dialogues import ( ContractApiDialogue, ContractApiDialogues, ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.contract_api.message import ( _default_logger as contract_api_message_logger, ) from tests.conftest import ROOT_DIR logger = logging.getLogger(__name__) sys.path.append(ROOT_DIR) def test_get_deploy_transaction_serialization(): """Test the serialization for 'get_deploy_transaction' speech-act works.""" kwargs_arg = ContractApiMessage.Kwargs({"key_1": 1, "key_2": 2}) msg = ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id="some_ledger_id", contract_id="some_contract_id", callable="some_callable", kwargs=kwargs_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_get_raw_transaction_serialization(): """Test the serialization for 'get_raw_transaction' speech-act works.""" kwargs_arg = ContractApiMessage.Kwargs({"key_1": 1, "key_2": 2}) msg = ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id="some_ledger_id", contract_id="some_contract_id", contract_address="some_contract_address", callable="some_callable", kwargs=kwargs_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_get_raw_message_serialization(): """Test the serialization for 'get_raw_message' speech-act works.""" kwargs_arg = ContractApiMessage.Kwargs({"key_1": 1, "key_2": 2}) msg = ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, ledger_id="some_ledger_id", contract_id="some_contract_id", contract_address="some_contract_address", callable="some_callable", kwargs=kwargs_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_get_state_serialization(): """Test the serialization for 'get_state' speech-act works.""" kwargs_arg = ContractApiMessage.Kwargs({"key_1": 1, "key_2": 2}) msg = ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.GET_STATE, ledger_id="some_ledger_id", contract_id="some_contract_id", contract_address="some_contract_address", callable="some_callable", kwargs=kwargs_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_state_serialization(): """Test the serialization for 'state' speech-act works.""" state_arg = ContractApiMessage.State("some_ledger_id", {"key": "some_body"}) msg = ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.STATE, state=state_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_raw_transaction_serialization(): """Test the serialization for 'raw_transaction' speech-act works.""" raw_transaction_arg = ContractApiMessage.RawTransaction( "some_ledger_id", {"body": "some_body"} ) msg = ContractApiMessage( message_id=2, target=1, performative=ContractApiMessage.Performative.RAW_TRANSACTION, raw_transaction=raw_transaction_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_raw_message_serialization(): """Test the serialization for 'raw_message' speech-act works.""" raw_message_arg = ContractApiMessage.RawMessage("some_ledger_id", b"some_body") msg = ContractApiMessage( performative=ContractApiMessage.Performative.RAW_MESSAGE, raw_message=raw_message_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_error_serialization(): """Test the serialization for 'error' speech-act works.""" msg = ContractApiMessage( performative=ContractApiMessage.Performative.ERROR, code=7, message="some_error_message", data=b"some_error_data", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = ContractApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert ( str(ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION) == "get_deploy_transaction" ), "The str value must be get_deploy_transaction" assert ( str(ContractApiMessage.Performative.GET_RAW_TRANSACTION) == "get_raw_transaction" ), "The str value must be get_raw_transaction" assert ( str(ContractApiMessage.Performative.GET_RAW_MESSAGE) == "get_raw_message" ), "The str value must be get_raw_message" assert ( str(ContractApiMessage.Performative.GET_STATE) == "get_state" ), "The str value must be get_state" assert ( str(ContractApiMessage.Performative.STATE) == "state" ), "The str value must be state" assert ( str(ContractApiMessage.Performative.RAW_TRANSACTION) == "raw_transaction" ), "The str value must be raw_transaction" assert ( str(ContractApiMessage.Performative.RAW_MESSAGE) == "raw_message" ), "The str value must be raw_message" assert ( str(ContractApiMessage.Performative.ERROR) == "error" ), "The str value must be error" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.RAW_MESSAGE, raw_message=ContractApiMessage.RawMessage("some_ledger_id", b"some_body"), ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( ContractApiMessage.Performative, "__eq__", return_value=False ): ContractApiMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.RAW_MESSAGE, raw_message=ContractApiMessage.RawMessage("some_ledger_id", b"some_body"), ) encoded_msg = ContractApiMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( ContractApiMessage.Performative, "__eq__", return_value=False ): ContractApiMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.contract_api.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the message is incorrect.""" with mock.patch.object(contract_api_message_logger, "error") as mock_logger: ContractApiMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=ContractApiMessage.Performative.RAW_MESSAGE, raw_message=ContractApiMessage.RawMessage("some_ledger_id", b"some_body"), ) mock_logger.assert_any_call("some error") def test_kwargs(): """Test the kwargs custom type.""" body = {"key_1": 1, "key_2": 2} kwargs = ContractApiMessage.Kwargs(body) assert str(kwargs) == "Kwargs: body={}".format(body) class TestDialogues: """Tests contract_api dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.ledger_addr = "ledger address" cls.agent_dialogues = AgentDialogues(cls.agent_addr) cls.ledger_dialogues = LedgerDialogues(cls.ledger_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.ledger_addr, dialogue_reference=(str(0), ""), role=ContractApiDialogue.Role.AGENT, ) assert isinstance(result, ContractApiDialogue) assert result.role == ContractApiDialogue.Role.AGENT, "The role must be Agent." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.ledger_addr, dialogue_reference=(str(0), ""), role=ContractApiDialogue.Role.AGENT, ) assert isinstance(result, ContractApiDialogue) assert result.role == ContractApiDialogue.Role.AGENT, "The role must be agent." class AgentDialogue(ContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ ContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AgentDialogues(ContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.AGENT ContractApiDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AgentDialogue, ) class LedgerDialogue(ContractApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[ContractApiMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ ContractApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class LedgerDialogues(ContractApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return ContractApiDialogue.Role.LEDGER ContractApiDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=LedgerDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_default.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the messages module.""" from typing import Type from unittest import mock from unittest.mock import patch import pytest from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel from packages.fetchai.protocols.default.dialogues import ( DefaultDialogue as BaseDefaultDialogue, ) from packages.fetchai.protocols.default.dialogues import ( DefaultDialogues as BaseDefaultDialogues, ) from packages.fetchai.protocols.default.message import DefaultMessage def test_default_bytes_serialization(): """Test that the serialization for the 'simple' protocol works for the BYTES message.""" expected_msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg_bytes = DefaultMessage.serializer.encode(expected_msg) actual_msg = DefaultMessage.serializer.decode(msg_bytes) assert expected_msg == actual_msg def test_default_error_serialization(): """Test that the serialization for the 'simple' protocol works for the ERROR message.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.UNSUPPORTED_PROTOCOL, error_msg="An error", error_data={"error": b"Some error data"}, ) msg_bytes = DefaultMessage.serializer.encode(msg) actual_msg = DefaultMessage.serializer.decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg def test_default_end_serialization(): """Test that the serialization for the 'simple' protocol works for the END message.""" msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.END, ) msg_bytes = DefaultMessage.serializer.encode(msg) actual_msg = DefaultMessage.serializer.decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg def test_default_message_str_values(): """Tests the returned string values of default Message.""" assert ( str(DefaultMessage.Performative.BYTES) == "bytes" ), "DefaultMessage.Performative.BYTES must be bytes" assert ( str(DefaultMessage.Performative.ERROR) == "error" ), "DefaultMessage.Performative.ERROR must be error" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content=b"hello" ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( DefaultMessage.Performative, "__eq__", return_value=False ): DefaultMessage.serializer.encode(msg) def test_check_consistency_raises_exception_when_type_not_recognized(): """Test that we raise exception when the type of the message is not recognized.""" message = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) # mock the __eq__ method such that any kind of matching is going to fail. with mock.patch.object(DefaultMessage.Performative, "__eq__", return_value=False): assert not message._is_consistent() def test_default_valid_performatives(): """Test 'valid_performatives' getter.""" msg = DefaultMessage(DefaultMessage.Performative.BYTES, content=b"") assert msg.valid_performatives == set( map(lambda x: x.value, iter(DefaultMessage.Performative)) ) def test_serializer_performative_not_found(): """Test the serializer when the performative is not found.""" message = DefaultMessage( message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"", ) message_bytes = message.serializer.encode(message) with patch.object(DefaultMessage.Performative, "__eq__", return_value=False): with pytest.raises(ValueError, match="Performative not valid: .*"): message.serializer.decode(message_bytes) def test_dialogues(): """Test intiaontiation of dialogues.""" default_dialogues = DefaultDialogues("agent_addr") msg, dialogue = default_dialogues.create( counterparty="abc", performative=DefaultMessage.Performative.BYTES, content=b"hello", ) assert dialogue is not None class DefaultDialogue(BaseDefaultDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[DefaultMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ BaseDefaultDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class DefaultDialogues(BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return DefaultDialogue.Role.AGENT BaseDefaultDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=DefaultDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_fipa.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the fipa protocol package.""" import logging import sys from typing import Any, Optional, Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.fipa.dialogues import FipaDialogue, FipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.fipa.message import ( _default_logger as fipa_message_logger, ) from tests.conftest import ROOT_DIR logger = logging.getLogger(__name__) sys.path.append(ROOT_DIR) def test_cfp_serialization(): """Test that the serialization for the 'fipa' protocol works.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_propose_serialization(): """Test that the serialization for the 'fipa' protocol works.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.PROPOSE, proposal=Description({"foo1": 1, "bar1": 2}), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_accept_serialization(): """Test that the serialization for the 'fipa' protocol works.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_decline_serialization(): """Test that the serialization for the 'fipa' protocol works.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.DECLINE, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_match_accept_serialization(): """Test the serialization - deserialization of the match_accept performative.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.MATCH_ACCEPT, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_accept_with_inform_serialization(): """Test the serialization - deserialization of the accept_with_address performative.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT_W_INFORM, info={"address": "dummy_address"}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_match_accept_with_inform_serialization(): """Test the serialization - deserialization of the match_accept_with_address performative.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"address": "dummy_address", "signature": "my_signature"}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_inform_serialization(): """Test the serialization-deserialization of the inform performative.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.INFORM, info={"foo": "bar"}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_end_serialization(): """Test the serialization-deserialization of the end performative.""" msg = FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.END, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = FipaMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert str(FipaMessage.Performative.CFP) == "cfp", "The str value must be cfp" assert ( str(FipaMessage.Performative.PROPOSE) == "propose" ), "The str value must be propose" assert ( str(FipaMessage.Performative.DECLINE) == "decline" ), "The str value must be decline" assert ( str(FipaMessage.Performative.ACCEPT) == "accept" ), "The str value must be accept" assert ( str(FipaMessage.Performative.MATCH_ACCEPT) == "match_accept" ), "The str value must be match_accept" assert ( str(FipaMessage.Performative.ACCEPT_W_INFORM) == "accept_w_inform" ), "The str value must be accept_w_inform" assert ( str(FipaMessage.Performative.MATCH_ACCEPT_W_INFORM) == "match_accept_w_inform" ), "The str value must be match_accept_w_inform" assert ( str(FipaMessage.Performative.INFORM) == "inform" ), "The str value must be inform" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = FipaMessage( performative=FipaMessage.Performative.ACCEPT, ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(FipaMessage.Performative, "__eq__", return_value=False): FipaMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = FipaMessage( performative=FipaMessage.Performative.ACCEPT, ) encoded_msg = FipaMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(FipaMessage.Performative, "__eq__", return_value=False): FipaMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.fipa.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the fipa message is incorrect.""" with mock.patch.object(fipa_message_logger, "error") as mock_logger: FipaMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=FipaMessage.Performative.ACCEPT, ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests fipa dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.buyer_addr = "buyer address" cls.seller_addr = "seller address" cls.buyer_dialogues = BuyerDialogues(cls.buyer_addr) cls.seller_dialogues = SellerDialogues(cls.seller_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.buyer_dialogues._create_self_initiated( dialogue_opponent_addr=self.seller_addr, dialogue_reference=(str(0), ""), role=FipaDialogue.Role.SELLER, ) assert isinstance(result, FipaDialogue) assert result.role == FipaDialogue.Role.SELLER, "The role must be seller." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.buyer_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.seller_addr, dialogue_reference=(str(0), ""), role=FipaDialogue.Role.BUYER, ) assert isinstance(result, FipaDialogue) assert result.role == FipaDialogue.Role.BUYER, "The role must be buyer." def test_dialogue_endstates(self): """Test the end states of a dialogue.""" assert self.buyer_dialogues.dialogue_stats is not None self.buyer_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.SUCCESSFUL, is_self_initiated=True ) self.buyer_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_CFP, is_self_initiated=False ) assert self.buyer_dialogues.dialogue_stats.self_initiated == { FipaDialogue.EndState.SUCCESSFUL: 1, FipaDialogue.EndState.DECLINED_PROPOSE: 0, FipaDialogue.EndState.DECLINED_ACCEPT: 0, FipaDialogue.EndState.DECLINED_CFP: 0, } assert self.buyer_dialogues.dialogue_stats.other_initiated == { FipaDialogue.EndState.SUCCESSFUL: 0, FipaDialogue.EndState.DECLINED_PROPOSE: 0, FipaDialogue.EndState.DECLINED_ACCEPT: 0, FipaDialogue.EndState.DECLINED_CFP: 1, } def test_dialogues_self_initiated(self): """Test an end to end scenario of client-seller dialogue.""" # Create a message destined for the seller. cfp_msg, buyer_dialogue = self.buyer_dialogues.create( counterparty=self.seller_addr, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) # Checking that I can retrieve the dialogue. retrieved_dialogue = self.buyer_dialogues.get_dialogue(cfp_msg) assert ( retrieved_dialogue == buyer_dialogue ), "Should have found correct dialogue" assert ( cfp_msg.dialogue_reference[0] != "" and cfp_msg.dialogue_reference[1] == "" ), "The dialogue_reference is not set correctly." # MESSAGE BEING SENT BETWEEN AGENTS # Creates a new dialogue for the seller side based on the income message. seller_dialogue = self.seller_dialogues.update(cfp_msg) # Check that both fields in the dialogue_reference are set. last_msg = seller_dialogue.last_incoming_message assert last_msg == cfp_msg, "The messages must be equal" # Generate a proposal message to send to the buyer. proposal = Description({"foo1": 1, "bar1": 2}) proposal_msg = seller_dialogue.reply( target_message=cfp_msg, performative=FipaMessage.Performative.PROPOSE, proposal=proposal, ) # MESSAGE BEING SENT BETWEEN AGENTS # Client received the message and we extend the incoming messages list. buyer_dialogue = self.buyer_dialogues.update(proposal_msg) # Check that both fields in the dialogue_reference are set. last_msg = buyer_dialogue.last_incoming_message assert last_msg == proposal_msg, "The two messages must be equal." # Retrieve the dialogue based on the received message. retrieved_dialogue = self.buyer_dialogues.get_dialogue(proposal_msg) assert retrieved_dialogue == buyer_dialogue, "Should have found dialogue" # Create an accept_w_inform message to send seller. accept_msg = buyer_dialogue.reply( target_message=proposal_msg, performative=FipaMessage.Performative.ACCEPT_W_INFORM, info={"address": "dummy_address"}, ) # MESSAGE BEING SENT BETWEEN AGENTS # Adds the message to the seller incoming message list. seller_dialogue = self.seller_dialogues.update(accept_msg) retrieved_dialogue = self.seller_dialogues.get_dialogue(accept_msg) assert seller_dialogue == retrieved_dialogue, "Should have found dialogue" def test_update(self): """Test the `update` functionality.""" cfp_msg, buyer_dialogue = self.buyer_dialogues.create( counterparty=self.seller_addr, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) assert len(buyer_dialogue._outgoing_messages) == 1, "No outgoing message." assert len(buyer_dialogue._incoming_messages) == 0, "Some incoming messages." assert ( buyer_dialogue.last_outgoing_message == cfp_msg ), "Wrong outgoing message." assert ( buyer_dialogue.dialogue_label.dialogue_reference[0] != "" ), "Dialogue reference incorrect." assert ( buyer_dialogue.dialogue_label.dialogue_reference[1] == "" ), "Dialogue reference incorrect." dialogue_reference_left_part = buyer_dialogue.dialogue_label.dialogue_reference[ 0 ] # message arrives at counterparty seller_dialogue = self.seller_dialogues.update(cfp_msg) assert len(seller_dialogue._outgoing_messages) == 0, "Some outgoing message." assert len(seller_dialogue._incoming_messages) == 1, "No incoming messages." assert ( seller_dialogue.last_incoming_message == cfp_msg ), "Wrong incoming message." assert ( seller_dialogue.dialogue_label.dialogue_reference[0] != "" ), "Dialogue reference incorrect." assert ( seller_dialogue.dialogue_label.dialogue_reference[1] != "" ), "Dialogue reference incorrect." # seller creates response message proposal_msg = seller_dialogue.reply( target_message=cfp_msg, performative=FipaMessage.Performative.PROPOSE, proposal=Description({"foo1": 1, "bar1": 2}), ) assert len(seller_dialogue._outgoing_messages) == 1, "No outgoing messages." assert len(seller_dialogue._incoming_messages) == 1, "No incoming messages." assert ( seller_dialogue.last_outgoing_message == proposal_msg ), "Wrong outgoing message." # message arrives at counterparty self.buyer_dialogues.update(proposal_msg) assert len(buyer_dialogue._outgoing_messages) == 1, "No outgoing messages." assert len(buyer_dialogue._incoming_messages) == 1, "No incoming messages." assert ( buyer_dialogue.last_outgoing_message == cfp_msg ), "Wrong outgoing message." assert ( buyer_dialogue.last_incoming_message == proposal_msg ), "Wrong incoming message." assert ( buyer_dialogue.dialogue_label.dialogue_reference[0] != "" ), "Dialogue reference incorrect." assert ( buyer_dialogue.dialogue_label.dialogue_reference[1] != "" ), "Dialogue reference incorrect." assert ( dialogue_reference_left_part == buyer_dialogue.dialogue_label.dialogue_reference[0] ), "Dialogue refernce changed unexpectedly." def test_counter_proposing(self): """Test that fipa supports counter proposing.""" cfp_msg, buyer_dialogue = self.buyer_dialogues.create( counterparty=self.seller_addr, performative=FipaMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) assert len(buyer_dialogue._outgoing_messages) == 1, "No outgoing message." assert len(buyer_dialogue._incoming_messages) == 0, "Some incoming messages." assert ( buyer_dialogue.last_outgoing_message == cfp_msg ), "wrong outgoing message in buyer dialogue after sending cfp." assert ( buyer_dialogue.dialogue_label.dialogue_reference[0] != "" ), "Dialogue reference incorrect." assert ( buyer_dialogue.dialogue_label.dialogue_reference[1] == "" ), "Dialogue reference incorrect." dialogue_reference_left_part = buyer_dialogue.dialogue_label.dialogue_reference[ 0 ] # cfp arrives at seller seller_dialogue = self.seller_dialogues.update(cfp_msg) assert len(seller_dialogue._outgoing_messages) == 0, "Some outgoing message." assert len(seller_dialogue._incoming_messages) == 1, "No incoming messages." assert ( seller_dialogue.last_incoming_message == cfp_msg ), "wrong incoming message in seller dialogue after receiving cfp." assert ( seller_dialogue.dialogue_label.dialogue_reference[0] != "" ), "Dialogue reference incorrect." assert ( seller_dialogue.dialogue_label.dialogue_reference[1] != "" ), "Dialogue reference incorrect." # seller creates proposal proposal_msg = seller_dialogue.reply( target_message=cfp_msg, performative=FipaMessage.Performative.PROPOSE, proposal=Description({"foo1": 1, "bar1": 2}), ) assert len(seller_dialogue._outgoing_messages) == 1, "No outgoing messages." assert len(seller_dialogue._incoming_messages) == 1, "No incoming messages." assert ( seller_dialogue.last_outgoing_message == proposal_msg ), "wrong outgoing message in seller dialogue after sending proposal." # proposal arrives at buyer buyer_dialogue = self.buyer_dialogues.update(proposal_msg) assert len(buyer_dialogue._outgoing_messages) == 1, "No outgoing messages." assert len(buyer_dialogue._incoming_messages) == 1, "No incoming messages." assert ( buyer_dialogue.last_incoming_message == proposal_msg ), "wrong incoming message in buyer dialogue after receiving proposal." assert ( buyer_dialogue.last_incoming_message == proposal_msg ), "Wrong incoming message." assert ( buyer_dialogue.dialogue_label.dialogue_reference[0] != "" ), "Dialogue reference incorrect." assert ( buyer_dialogue.dialogue_label.dialogue_reference[1] != "" ), "Dialogue reference incorrect." assert ( dialogue_reference_left_part == buyer_dialogue.dialogue_label.dialogue_reference[0] ), "Dialogue refernce changed unexpectedly." # buyer creates counter proposal 1 counter_proposal_msg_1 = buyer_dialogue.reply( target_message=proposal_msg, performative=FipaMessage.Performative.PROPOSE, proposal=Description({"foo1": 3, "bar1": 3}), ) assert ( len(buyer_dialogue._outgoing_messages) == 2 ), "incorrect number of outgoing_messages in buyer dialogue after sending counter-proposal 1." assert ( len(buyer_dialogue._incoming_messages) == 1 ), "incorrect number of incoming_messages in buyer dialogue after sending counter-proposal 1." assert ( buyer_dialogue.last_outgoing_message == counter_proposal_msg_1 ), "wrong outgoing message in buyer dialogue after sending counter-proposal 1." # counter-proposal 1 arrives at seller seller_dialogue = self.seller_dialogues.update(counter_proposal_msg_1) assert ( len(seller_dialogue._outgoing_messages) == 1 ), "incorrect number of outgoing_messages in seller dialogue after receiving counter-proposal 1." assert ( len(seller_dialogue._incoming_messages) == 2 ), "incorrect number of incoming_messages in seller dialogue after receiving counter-proposal 1." assert ( seller_dialogue.last_incoming_message == counter_proposal_msg_1 ), "wrong incoming message in seller dialogue after receiving counter-proposal 1." # seller creates counter-proposal 2 counter_proposal_msg_2 = seller_dialogue.reply( target_message=counter_proposal_msg_1, performative=FipaMessage.Performative.PROPOSE, proposal=Description({"foo1": 2, "bar1": 2}), ) assert ( len(seller_dialogue._outgoing_messages) == 2 ), "incorrect number of outgoing_messages in seller dialogue after sending counter-proposal 2." assert ( len(seller_dialogue._incoming_messages) == 2 ), "incorrect number of incoming_messages in seller dialogue after sending counter-proposal 2." assert ( seller_dialogue.last_outgoing_message == counter_proposal_msg_2 ), "wrong outgoing message in seller dialogue after sending counter-proposal 2." # counter-proposal 2 arrives at buyer buyer_dialogue = self.buyer_dialogues.update(counter_proposal_msg_2) assert ( len(buyer_dialogue._outgoing_messages) == 2 ), "incorrect number of outgoing_messages in buyer dialogue after receiving counter-proposal 2." assert ( len(buyer_dialogue._incoming_messages) == 2 ), "incorrect number of incoming_messages in buyer dialogue after receiving counter-proposal 2." assert ( buyer_dialogue.last_incoming_message == counter_proposal_msg_2 ), "wrong incoming message in buyer dialogue after receiving counter-proposal 2." class BuyerDialogue(FipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ FipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class BuyerDialogues(FipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.BUYER FipaDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=BuyerDialogue, ) class SellerDialogue(FipaDialogue): """The dialogue class maintains state of a dialogue and manages it.""" __slots__ = ("some_object",) def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[FipaMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ FipaDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) self.some_object = None # type: Optional[Any] class SellerDialogues(FipaDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return FipaDialogue.Role.SELLER FipaDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=SellerDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_gym.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the gym protocol package.""" import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.gym.dialogues import GymDialogue, GymDialogues from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.protocols.gym.message import _default_logger as gym_message_logger from tests.conftest import ROOT_DIR sys.path.append(ROOT_DIR) def test_act_serialization(): """Test the serialization for 'act' speech-act works.""" msg = GymMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=GymMessage.Performative.ACT, action=GymMessage.AnyObject("some_action"), step_id=1, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = GymMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_percept_serialization(): """Test the serialization for 'percept' speech-act works.""" msg = GymMessage( message_id=2, dialogue_reference=(str(0), ""), target=1, performative=GymMessage.Performative.PERCEPT, step_id=1, observation=GymMessage.AnyObject("some_observation"), reward=10.0, done=False, info=GymMessage.AnyObject("some_info"), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = GymMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_status_serialization(): """Test the serialization for 'status' speech-act works.""" content_arg = { "key_1": "value_1", "key_2": "value_2", } msg = GymMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=GymMessage.Performative.STATUS, content=content_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = GymMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_reset_serialization(): """Test the serialization for 'reset' speech-act works.""" msg = GymMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=GymMessage.Performative.RESET, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = GymMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_close_serialization(): """Test the serialization for 'close' speech-act works.""" msg = GymMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=GymMessage.Performative.CLOSE, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = GymMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert str(GymMessage.Performative.ACT) == "act", "The str value must be act" assert ( str(GymMessage.Performative.PERCEPT) == "percept" ), "The str value must be percept" assert ( str(GymMessage.Performative.STATUS) == "status" ), "The str value must be status" assert str(GymMessage.Performative.RESET) == "reset", "The str value must be reset" assert str(GymMessage.Performative.CLOSE) == "close", "The str value must be close" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = GymMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=GymMessage.Performative.RESET, ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(GymMessage.Performative, "__eq__", return_value=False): GymMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = GymMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=GymMessage.Performative.RESET, ) encoded_msg = GymMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(GymMessage.Performative, "__eq__", return_value=False): GymMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.gym.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the message is incorrect.""" with mock.patch.object(gym_message_logger, "error") as mock_logger: GymMessage( message_id=1, dialogue_reference=(str(0), ""), target=0, performative=GymMessage.Performative.RESET, ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests gym dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.env_addr = "env address" cls.agent_dialogues = AgentDialogues(cls.agent_addr) cls.env_dialogues = EnvironmentDialogues(cls.env_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.env_addr, dialogue_reference=(str(0), ""), role=GymDialogue.Role.AGENT, ) assert isinstance(result, GymDialogue) assert result.role == GymDialogue.Role.AGENT, "The role must be Agent." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.env_addr, dialogue_reference=(str(0), ""), role=GymDialogue.Role.AGENT, ) assert isinstance(result, GymDialogue) assert result.role == GymDialogue.Role.AGENT, "The role must be agent." class AgentDialogue(GymDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[GymMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ GymDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AgentDialogues(GymDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return GymDialogue.Role.AGENT GymDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AgentDialogue, ) class EnvironmentDialogue(GymDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[GymMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ GymDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class EnvironmentDialogues(GymDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return GymDialogue.Role.ENVIRONMENT GymDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=EnvironmentDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_http.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the http protocol package.""" import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage from packages.fetchai.protocols.http.message import ( _default_logger as http_message_logger, ) from tests.conftest import ROOT_DIR sys.path.append(ROOT_DIR) def test_request_serialization(): """Test the serialization for 'request' speech-act works.""" msg = HttpMessage( performative=HttpMessage.Performative.REQUEST, method="some_method", url="url", version="some_version", headers="some_headers", body=b"some_body", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = HttpMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_response_serialization(): """Test the serialization for 'response' speech-act works.""" msg = HttpMessage( message_id=2, target=1, performative=HttpMessage.Performative.RESPONSE, version="some_version", status_code=1, status_text="some_status_text", headers="some_headers", body=b"some_body", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = HttpMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert ( str(HttpMessage.Performative.REQUEST) == "request" ), "The str value must be request" assert ( str(HttpMessage.Performative.RESPONSE) == "response" ), "The str value must be response" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = HttpMessage( performative=HttpMessage.Performative.REQUEST, method="some_method", url="url", version="some_version", headers="some_headers", body=b"some_body", ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(HttpMessage.Performative, "__eq__", return_value=False): HttpMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = HttpMessage( performative=HttpMessage.Performative.REQUEST, method="some_method", url="url", version="some_version", headers="some_headers", body=b"some_body", ) encoded_msg = HttpMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(HttpMessage.Performative, "__eq__", return_value=False): HttpMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.http.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the message is incorrect.""" with mock.patch.object(http_message_logger, "error") as mock_logger: HttpMessage( performative=HttpMessage.Performative.REQUEST, method="some_method", url="url", version="some_version", headers="some_headers", body=b"some_body", ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests http dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.server_addr = "server address" cls.agent_dialogues = AgentDialogues(cls.agent_addr) cls.server_dialogues = ServerDialogues(cls.server_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.server_addr, dialogue_reference=(str(0), ""), role=HttpDialogue.Role.CLIENT, ) assert isinstance(result, HttpDialogue) assert result.role == HttpDialogue.Role.CLIENT, "The role must be client." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.server_addr, dialogue_reference=(str(0), ""), role=HttpDialogue.Role.CLIENT, ) assert isinstance(result, HttpDialogue) assert result.role == HttpDialogue.Role.CLIENT, "The role must be client." class AgentDialogue(HttpDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[HttpMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ HttpDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AgentDialogues(HttpDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return HttpDialogue.Role.CLIENT HttpDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AgentDialogue, ) class ServerDialogue(HttpDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[HttpMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ HttpDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class ServerDialogues(HttpDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return HttpDialogue.Role.SERVER HttpDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=ServerDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_ledger_api.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the messages module.""" import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import State from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.ledger_api.custom_types import Kwargs from packages.fetchai.protocols.ledger_api.dialogues import ( LedgerApiDialogue, LedgerApiDialogues, ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ledger_api.message import ( _default_logger as ledger_api_message_logger, ) from tests.conftest import ROOT_DIR sys.path.append(ROOT_DIR) def test_get_balance_serialization(): """Test the serialization for 'get_balance' speech-act works.""" msg = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_get_state_serialization(): """Test the serialization for 'get_state' speech-act works.""" args = ("arg1", "arg2") kwargs = Kwargs({"key": "value"}) assert str(kwargs) == "Kwargs: body={'key': 'value'}" msg = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_STATE, ledger_id="some_ledger_id", callable="some_function", args=args, kwargs=kwargs, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_get_raw_transaction_serialization(): """Test the serialization for 'get_raw_transaction' speech-act works.""" terms_arg = LedgerApiMessage.Terms( ledger_id="some_ledger_id", sender_address="some_sender_address", counterparty_address="some_counterparty_address", amount_by_currency_id={"currency_id_1": 1}, quantities_by_good_id={"good_id_1": -1, "good_id_2": -2}, nonce="some_nonce", is_sender_payable_tx_fee=False, fee_by_currency_id={"currency_id_1": 1}, is_strict=True, ) msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, terms=terms_arg, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_send_signed_transaction_serialization(): """Test the serialization for 'send_signed_transaction' speech-act works.""" msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, signed_transaction=LedgerApiMessage.SignedTransaction( "some_ledger_id", {"body": "some_body"} ), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_get_transaction_receipt_serialization(): """Test the serialization for 'get_transaction_receipt' speech-act works.""" msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, transaction_digest=LedgerApiMessage.TransactionDigest( "some_ledger_id", "some_body" ), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_balance_serialization(): """Test the serialization for 'balance' speech-act works.""" msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.BALANCE, ledger_id="some_ledger_id", balance=125, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_state_serialization(): """Test the serialization for 'state' speech-act works.""" ledger_id = "some_ledger_id" state = State(ledger_id, {"key": "some_state"}) msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.STATE, ledger_id=ledger_id, state=state, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_raw_transaction_serialization(): """Test the serialization for 'raw_transaction' speech-act works.""" msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.RAW_TRANSACTION, raw_transaction=LedgerApiMessage.RawTransaction( "some_ledger_id", {"body": "some_body"} ), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_transaction_digest_serialization(): """Test the serialization for 'transaction_digest' speech-act works.""" msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=LedgerApiMessage.TransactionDigest( "some_ledger_id", "some_body" ), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_transaction_receipt_serialization(): """Test the serialization for 'transaction_receipt' speech-act works.""" msg = LedgerApiMessage( message_id=2, target=1, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=LedgerApiMessage.TransactionReceipt( "some_ledger_id", {"key": "some_receipt"}, {"key": "some_transaction"} ), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_error_serialization(): """Test the serialization for 'error' speech-act works.""" msg = LedgerApiMessage( performative=LedgerApiMessage.Performative.ERROR, code=7, message="some_error_message", data=b"some_error_data", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = LedgerApiMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_performative_string_value(): """Test the string value of the performatives.""" assert ( str(LedgerApiMessage.Performative.GET_BALANCE) == "get_balance" ), "The str value must be get_balance" assert ( str(LedgerApiMessage.Performative.GET_RAW_TRANSACTION) == "get_raw_transaction" ), "The str value must be get_raw_transaction" assert ( str(LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION) == "send_signed_transaction" ), "The str value must be send_signed_transaction" assert ( str(LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT) == "get_transaction_receipt" ), "The str value must be get_transaction_receipt" assert ( str(LedgerApiMessage.Performative.BALANCE) == "balance" ), "The str value must be balance" assert ( str(LedgerApiMessage.Performative.RAW_TRANSACTION) == "raw_transaction" ), "The str value must be raw_transaction" assert ( str(LedgerApiMessage.Performative.TRANSACTION_DIGEST) == "transaction_digest" ), "The str value must be transaction_digest" assert ( str(LedgerApiMessage.Performative.TRANSACTION_RECEIPT) == "transaction_receipt" ), "The str value must be transaction_receipt" assert ( str(LedgerApiMessage.Performative.ERROR) == "error" ), "The str value must be error" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( LedgerApiMessage.Performative, "__eq__", return_value=False ): LedgerApiMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) encoded_msg = LedgerApiMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( LedgerApiMessage.Performative, "__eq__", return_value=False ): LedgerApiMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.ledger_api.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the message is incorrect.""" with mock.patch.object(ledger_api_message_logger, "error") as mock_logger: LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests ledger_api dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.ledger_addr = "ledger address" cls.agent_dialogues = AgentDialogues(cls.agent_addr) cls.ledger_dialogues = LedgerDialogues(cls.ledger_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.ledger_addr, dialogue_reference=(str(0), ""), role=LedgerApiDialogue.Role.AGENT, ) assert isinstance(result, LedgerApiDialogue) assert result.role == LedgerApiDialogue.Role.AGENT, "The role must be agent." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.ledger_addr, dialogue_reference=(str(0), ""), role=LedgerApiDialogue.Role.AGENT, ) assert isinstance(result, LedgerApiDialogue) assert result.role == LedgerApiDialogue.Role.AGENT, "The role must be agen t." class AgentDialogue(LedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ LedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AgentDialogues(LedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return LedgerApiDialogue.Role.AGENT LedgerApiDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AgentDialogue, ) class LedgerDialogue(LedgerApiDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[LedgerApiMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ LedgerApiDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class LedgerDialogues(LedgerApiDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return LedgerApiDialogue.Role.LEDGER LedgerApiDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=LedgerDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_oef_search.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the oef_search protocol package.""" import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue, OefSearchDialogues, ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.oef_search.message import ( _default_logger as oef_search_message_logger, ) from tests.conftest import ROOT_DIR sys.path.append(ROOT_DIR) def test_register_service_serialization(): """Test the serialization for 'register_service' speech-act works.""" msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=Description({"foo1": 1, "bar1": 2}), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = OefSearchMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_unregister_service_serialization(): """Test the serialization for 'unregister_service' speech-act works.""" msg = OefSearchMessage( message_id=2, target=1, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=Description({"foo1": 1, "bar1": 2}), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = OefSearchMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_search_services_serialization(): """Test the serialization for 'search_services' speech-act works.""" msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=Query([Constraint("something", ConstraintType(">", 1))]), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = OefSearchMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_search_result_serialization(): """Test the serialization for 'search_result' speech-act works.""" msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=("agent_1", "agent_2", "agent_3"), agents_info=OefSearchMessage.AgentsInfo( { "key_1": {"key_1": b"value_1", "key_2": b"value_2"}, "key_2": {"key_3": b"value_3", "key_4": b"value_4"}, } ), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = OefSearchMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_success_serialization(): """Test the serialization for 'success' speech-act works.""" msg = OefSearchMessage( performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo( { "key_1": {"key_1": b"value_1", "key_2": b"value_2"}, "key_2": {"key_3": b"value_3", "key_4": b"value_4"}, } ), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = OefSearchMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_oef_error_serialization(): """Test the serialization for 'oef_error' speech-act works.""" msg = OefSearchMessage( performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.OTHER, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = OefSearchMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_oef_type_string_value(): """Test the string value of the type.""" assert ( str(OefSearchMessage.Performative.REGISTER_SERVICE) == "register_service" ), "The str value must be register_service" assert ( str(OefSearchMessage.Performative.UNREGISTER_SERVICE) == "unregister_service" ), "The str value must be unregister_service" assert ( str(OefSearchMessage.Performative.SEARCH_SERVICES) == "search_services" ), "The str value must be search_services" assert ( str(OefSearchMessage.Performative.OEF_ERROR) == "oef_error" ), "The str value must be oef_error" assert ( str(OefSearchMessage.Performative.SEARCH_RESULT) == "search_result" ), "The str value must be search_result" def test_oef_error_operation(): """Test the string value of the error operation.""" assert ( str(OefSearchMessage.OefErrorOperation.REGISTER_SERVICE) == "0" ), "The str value must be 0" assert ( str(OefSearchMessage.OefErrorOperation.UNREGISTER_SERVICE) == "1" ), "The str value must be 1" assert ( str(OefSearchMessage.OefErrorOperation.SEARCH_SERVICES) == "2" ), "The str value must be 2" assert ( str(OefSearchMessage.OefErrorOperation.SEND_MESSAGE) == "3" ), "The str value must be 3" assert ( str(OefSearchMessage.OefErrorOperation.OTHER) == "10000" ), "The str value must be 10000" def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=Description({"foo1": 1, "bar1": 2}), ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( OefSearchMessage.Performative, "__eq__", return_value=False ): OefSearchMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=Description({"foo1": 1, "bar1": 2}), ) encoded_msg = OefSearchMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object( OefSearchMessage.Performative, "__eq__", return_value=False ): OefSearchMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.oef_search.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the fipa message is incorrect.""" with mock.patch.object(oef_search_message_logger, "error") as mock_logger: OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=Description({"foo1": 1, "bar1": 2}), ) mock_logger.assert_any_call("some error") def test_agent_info(): """Test the agent_info custom type.""" agents_info = OefSearchMessage.AgentsInfo( { "agent_address_1": {"key_1": b"value_1", "key_2": b"value_2"}, "agent_address_2": {"key_3": b"value_3", "key_4": b"value_4"}, } ) assert agents_info.get_info_for_agent("agent_address_1") == { "key_1": b"value_1", "key_2": b"value_2", } with pytest.raises(ValueError, match="body must not be None"): OefSearchMessage.AgentsInfo(None) class TestDialogues: """Tests oef_search dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.oef_node_addr = "oef_node address" cls.agent_dialogues = BuyerDialogues(cls.agent_addr) cls.oef_node_dialogues = OEFNodeDialogues(cls.oef_node_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.oef_node_addr, dialogue_reference=(str(0), ""), role=OefSearchDialogue.Role.AGENT, ) assert isinstance(result, OefSearchDialogue) assert result.role == OefSearchDialogue.Role.AGENT, "The role must be agent." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.oef_node_addr, dialogue_reference=(str(0), ""), role=OefSearchDialogue.Role.AGENT, ) assert isinstance(result, OefSearchDialogue) assert result.role == OefSearchDialogue.Role.AGENT, "The role must be agent." class BuyerDialogue(OefSearchDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[OefSearchMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ OefSearchDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class BuyerDialogues(OefSearchDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return OefSearchDialogue.Role.AGENT OefSearchDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=BuyerDialogue, ) class OEFNodeDialogue(OefSearchDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[OefSearchMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ OefSearchDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class OEFNodeDialogues(OefSearchDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return OefSearchDialogue.Role.OEF_NODE OefSearchDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=OEFNodeDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_register.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for the register protocol.""" from typing import Type from unittest.mock import patch import pytest from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel from packages.fetchai.protocols.register.dialogues import ( RegisterDialogue as BaseRegisterDialogue, ) from packages.fetchai.protocols.register.dialogues import ( RegisterDialogues as BaseRegisterDialogues, ) from packages.fetchai.protocols.register.message import RegisterMessage class TestRegisterMessage: """Test the register message module.""" @classmethod def setup_class(cls): """Setup class for test case.""" cls.info = {"a": "b", "c": "d"} def test_register(self): """Test for an error for a register message.""" tx_msg = RegisterMessage( performative=RegisterMessage.Performative.REGISTER, info=self.info ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg def test_success(self): """Test for an error for a success message.""" tx_msg = RegisterMessage( performative=RegisterMessage.Performative.SUCCESS, info=self.info, target=1, message_id=2, ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg def test_error(self): """Test for an error for a register error message.""" some_error_code = 1 some_error_msg = "Some error message" tx_msg = RegisterMessage( performative=RegisterMessage.Performative.ERROR, error_code=some_error_code, error_msg=some_error_msg, info=self.info, ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg def test_consistency_check_negative(): """Test the consistency check, negative case.""" tx_msg = RegisterMessage( performative=RegisterMessage.Performative.REGISTER, ) assert not tx_msg._is_consistent() def test_serialization_negative(): """Test serialization when performative is not recognized.""" tx_msg = RegisterMessage( performative=RegisterMessage.Performative.REGISTER, info={}, ) with patch.object(RegisterMessage.Performative, "__eq__", return_value=False): with pytest.raises( ValueError, match=f"Performative not valid: {tx_msg.performative}" ): tx_msg.serializer.encode(tx_msg) encoded_tx_bytes = tx_msg.serializer.encode(tx_msg) with patch.object(RegisterMessage.Performative, "__eq__", return_value=False): with pytest.raises( ValueError, match=f"Performative not valid: {tx_msg.performative}" ): tx_msg.serializer.decode(encoded_tx_bytes) def test_dialogues(): """Test instantiation of dialogues.""" register_dialogues = RegisterDialogues("agent_addr") msg, dialogue = register_dialogues.create( counterparty="abc", performative=RegisterMessage.Performative.REGISTER, info={} ) assert dialogue is not None class RegisterDialogue(BaseRegisterDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[RegisterMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ BaseRegisterDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class RegisterDialogues(BaseRegisterDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return RegisterDialogue.Role.AGENT BaseRegisterDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=RegisterDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_signing.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for transaction.""" from typing import Type from unittest.mock import patch import pytest from aea_ledger_cosmos import CosmosCrypto from aea.common import Address from aea.helpers.transaction.base import ( RawMessage, RawTransaction, SignedMessage, SignedTransaction, Terms, ) from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel from packages.fetchai.protocols.signing.dialogues import ( SigningDialogue as BaseSigningDialogue, ) from packages.fetchai.protocols.signing.dialogues import ( SigningDialogues as BaseSigningDialogues, ) from packages.fetchai.protocols.signing.message import SigningMessage class TestSigningMessage: """Test the signing message module.""" @classmethod def setup_class(cls): """Setup class for test case.""" cls.ledger_id = CosmosCrypto.identifier cls.terms = Terms( ledger_id=cls.ledger_id, sender_address="address1", counterparty_address="address2", amount_by_currency_id={"FET": -2}, quantities_by_good_id={"good_id": 10}, is_sender_payable_tx_fee=True, nonce="transaction nonce", ) def test_sign_transaction(self): """Test for an error for a sign transaction message.""" tx_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, terms=self.terms, raw_transaction=RawTransaction(self.ledger_id, {"tx": "transaction"}), ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg def test_sign_message(self): """Test for an error for a sign transaction message.""" tx_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, terms=self.terms, raw_message=RawMessage(self.ledger_id, b"message"), ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg def test_signed_transaction(self): """Test for an error for a signed transaction.""" tx_msg = SigningMessage( performative=SigningMessage.Performative.SIGNED_TRANSACTION, message_id=2, target=1, signed_transaction=SignedTransaction(self.ledger_id, {"sig": "signature"}), ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg def test_signed_message(self): """Test for an error for a signed message.""" tx_msg = SigningMessage( performative=SigningMessage.Performative.SIGNED_MESSAGE, message_id=2, target=1, signed_message=SignedMessage(self.ledger_id, "message"), ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg def test_error_message(self): """Test for an error for an error message.""" tx_msg = SigningMessage( performative=SigningMessage.Performative.ERROR, message_id=2, target=1, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, ) assert tx_msg._is_consistent() encoded_tx_msg = tx_msg.encode() decoded_tx_msg = tx_msg.serializer.decode(encoded_tx_msg) assert tx_msg == decoded_tx_msg assert str(tx_msg.performative) == "error" assert len(tx_msg.valid_performatives) == 5 def test_consistency_check_negative(): """Test the consistency check, negative case.""" tx_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, ) assert not tx_msg._is_consistent() def test_serialization_negative(): """Test serialization when performative is not recognized.""" tx_msg = SigningMessage( performative=SigningMessage.Performative.ERROR, message_id=2, target=1, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, ) with patch.object(SigningMessage.Performative, "__eq__", return_value=False): with pytest.raises( ValueError, match=f"Performative not valid: {tx_msg.performative}" ): tx_msg.serializer.encode(tx_msg) encoded_tx_bytes = tx_msg.serializer.encode(tx_msg) with patch.object(SigningMessage.Performative, "__eq__", return_value=False): with pytest.raises( ValueError, match=f"Performative not valid: {tx_msg.performative}" ): tx_msg.serializer.decode(encoded_tx_bytes) def test_dialogues(): """Test intiaontiation of dialogues.""" signing_dialogues = SigningDialogues("agent_addr") msg, dialogue = signing_dialogues.create( counterparty="abc", performative=SigningMessage.Performative.SIGN_TRANSACTION, terms=Terms( ledger_id="ledger_id", sender_address="address1", counterparty_address="address2", amount_by_currency_id={"FET": -2}, quantities_by_good_id={"good_id": 10}, is_sender_payable_tx_fee=True, nonce="transaction nonce", ), raw_transaction=RawTransaction("ledger_id", {"tx": "transaction"}), ) assert dialogue is not None class SigningDialogue(BaseSigningDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[SigningMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ BaseSigningDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class SigningDialogues(BaseSigningDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return SigningDialogue.Role.SKILL BaseSigningDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=SigningDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_state_update.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains tests for transaction.""" from typing import Type from unittest.mock import patch import pytest from aea.common import Address from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel from packages.fetchai.protocols.state_update.dialogues import ( StateUpdateDialogue as BaseStateUpdateDialogue, ) from packages.fetchai.protocols.state_update.dialogues import ( StateUpdateDialogues as BaseStateUpdateDialogues, ) from packages.fetchai.protocols.state_update.message import StateUpdateMessage class TestStateUpdateMessage: """Test the StateUpdateMessage.""" def test_message_consistency(self): """Test for an error in consistency of a message.""" currency_endowment = {"FET": 100} good_endowment = {"a_good": 2} exchange_params = {"FET": 10.0} utility_params = {"a_good": 20.0} assert StateUpdateMessage( performative=StateUpdateMessage.Performative.INITIALIZE, amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, exchange_params_by_currency_id=exchange_params, utility_params_by_good_id=utility_params, ) currency_change = {"FET": 10} good_change = {"a_good": 1} stum = StateUpdateMessage( performative=StateUpdateMessage.Performative.APPLY, amount_by_currency_id=currency_change, quantities_by_good_id=good_change, ) assert stum._is_consistent() assert len(stum.valid_performatives) == 3 stum = StateUpdateMessage( performative=StateUpdateMessage.Performative.END, ) assert stum._is_consistent() def test_message_inconsistency(self): """Test for an error in consistency of a message.""" currency_endowment = {"FET": 100} good_endowment = {"a_good": 2} exchange_params = {"UNKNOWN": 10.0} utility_params = {"a_good": 20.0} with pytest.raises(ValueError, match="Field .* is not supported"): StateUpdateMessage( performative=StateUpdateMessage.Performative.INITIALIZE, amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, exchange_params_by_currency_id=exchange_params, utility_params_by_good_id=utility_params, non_exists_field="some value", ) class TestSerialization: """Test state update message serialization.""" def test_serialization_initialize(self): """Test serialization of initialize message.""" currency_endowment = {"FET": 100} good_endowment = {"a_good": 2} exchange_params = {"FET": 10.0} utility_params = {"a_good": 20.0} msg = StateUpdateMessage( performative=StateUpdateMessage.Performative.INITIALIZE, amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, exchange_params_by_currency_id=exchange_params, utility_params_by_good_id=utility_params, ) encoded_msg = msg.serializer.encode(msg) decoded_msg = msg.serializer.decode(encoded_msg) assert msg == decoded_msg def test_serialization_apply(self): """Test serialization of apply message.""" currency_change = {"FET": 10} good_change = {"a_good": 1} msg = StateUpdateMessage( performative=StateUpdateMessage.Performative.APPLY, amount_by_currency_id=currency_change, quantities_by_good_id=good_change, ) assert msg._is_consistent() assert len(msg.valid_performatives) == 3 encoded_msg = msg.serializer.encode(msg) decoded_msg = msg.serializer.decode(encoded_msg) assert msg == decoded_msg def test_serialization_end(self): """Test serialization of end message.""" msg = StateUpdateMessage( performative=StateUpdateMessage.Performative.END, ) assert msg._is_consistent() assert len(msg.valid_performatives) == 3 encoded_msg = msg.serializer.encode(msg) decoded_msg = msg.serializer.decode(encoded_msg) assert msg == decoded_msg def test_serialization_negative(): """Test serialization when performative is not recognized.""" currency_change = {"FET": 10} good_change = {"a_good": 1} msg = StateUpdateMessage( performative=StateUpdateMessage.Performative.APPLY, amount_by_currency_id=currency_change, quantities_by_good_id=good_change, ) with patch.object(StateUpdateMessage.Performative, "__eq__", return_value=False): with pytest.raises( ValueError, match=f"Performative not valid: {msg.performative}" ): msg.serializer.encode(msg) encoded_tx_bytes = msg.serializer.encode(msg) with patch.object(StateUpdateMessage.Performative, "__eq__", return_value=False): with pytest.raises( ValueError, match=f"Performative not valid: {msg.performative}" ): msg.serializer.decode(encoded_tx_bytes) def test_performative_str(): """Test performative __str__.""" assert str(StateUpdateMessage.Performative.INITIALIZE) == "initialize" assert str(StateUpdateMessage.Performative.APPLY) == "apply" def test_dialogues(): """Test intiaontiation of dialogues.""" state_update_dialogues = StateUpdateDialogues("agent_addr") msg, dialogue = state_update_dialogues.create( counterparty="abc", performative=StateUpdateMessage.Performative.INITIALIZE, amount_by_currency_id={}, quantities_by_good_id={}, exchange_params_by_currency_id={}, utility_params_by_good_id={}, ) assert dialogue is not None class StateUpdateDialogue(BaseStateUpdateDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[StateUpdateMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ BaseStateUpdateDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class StateUpdateDialogues(BaseStateUpdateDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return StateUpdateDialogue.Role.SKILL BaseStateUpdateDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=StateUpdateDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_protocols/test_tac.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the http protocol package.""" import sys from typing import Type from unittest import mock import pytest from aea.common import Address from aea.exceptions import AEAEnforceError from aea.mail.base import Envelope from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue from aea.protocols.dialogue.base import DialogueLabel import packages from packages.fetchai.protocols.tac.dialogues import TacDialogue, TacDialogues from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.protocols.tac.message import _default_logger as tac_message_logger from tests.conftest import ROOT_DIR sys.path.append(ROOT_DIR) def test_tac_message_instantiation(): """Test instantiation of the tac message.""" assert TacMessage( performative=TacMessage.Performative.REGISTER, agent_name="some_name" ) assert TacMessage(performative=TacMessage.Performative.UNREGISTER) assert TacMessage( performative=TacMessage.Performative.TRANSACTION, transaction_id="some_id", ledger_id="some_ledger", sender_address="some_address", counterparty_address="some_other_address", amount_by_currency_id={"FET": 10}, fee_by_currency_id={"FET": 1}, quantities_by_good_id={"123": 0, "1234": 10}, nonce=1, sender_signature="some_signature", counterparty_signature="some_other_signature", ) assert TacMessage(performative=TacMessage.Performative.CANCELLED) assert TacMessage( performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id={"FET": 10}, exchange_params_by_currency_id={"FET": 10.0}, quantities_by_good_id={"123": 20, "1234": 15}, utility_params_by_good_id={"123": 30.0, "1234": 50.0}, fee_by_currency_id={"FET": 1}, agent_addr_to_name={"agent_1": "Agent one", "agent_2": "Agent two"}, currency_id_to_name={"FET": "currency_name"}, good_id_to_name={"123": "First good", "1234": "Second good"}, version_id="game_version_1", ) assert TacMessage( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, transaction_id="some_id", amount_by_currency_id={"FET": 10}, quantities_by_good_id={"123": 20, "1234": 15}, ) assert TacMessage( performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.GENERIC_ERROR, info={"msg": "This is info msg."}, ) assert str(TacMessage.Performative.REGISTER) == "register" def test_register_serialization(): """Test the serialization for 'register' speech-act works.""" msg = TacMessage( performative=TacMessage.Performative.REGISTER, agent_name="some_agent_name", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = TacMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_unregister_serialization(): """Test the serialization for 'unregister' speech-act works.""" msg = TacMessage( message_id=2, target=1, performative=TacMessage.Performative.UNREGISTER, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = TacMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_transaction_serialization(): """Test the serialization for 'transaction' speech-act works.""" msg = TacMessage( performative=TacMessage.Performative.TRANSACTION, transaction_id="some_transaction_id", ledger_id="some_ledger_id", sender_address="some_sender_address", counterparty_address="some_counterparty_address", amount_by_currency_id={"key_1": 1, "key_2": 2}, fee_by_currency_id={"key_1": 1, "key_2": 2}, quantities_by_good_id={"key_1": 1, "key_2": 2}, nonce="some_nonce", sender_signature="some_sender_signature", counterparty_signature="some_counterparty_signature", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = TacMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_cancelled_serialization(): """Test the serialization for 'cancelled' speech-act works.""" msg = TacMessage( performative=TacMessage.Performative.CANCELLED, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = TacMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_game_data_serialization(): """Test the serialization for 'game_data' speech-act works.""" msg = TacMessage( performative=TacMessage.Performative.GAME_DATA, amount_by_currency_id={"key_1": 1, "key_2": 2}, exchange_params_by_currency_id={"key_1": 1.0, "key_2": 2.0}, quantities_by_good_id={"key_1": 1, "key_2": 2}, utility_params_by_good_id={"key_1": 1.0, "key_2": 2.0}, fee_by_currency_id={"key_1": 1, "key_2": 2}, agent_addr_to_name={"key_1": "value_1", "key_2": "value_2"}, currency_id_to_name={"key_1": "value_1", "key_2": "value_2"}, good_id_to_name={"key_1": "value_1", "key_2": "value_2"}, version_id="some_version_id", info={"key_1": "value_1", "key_2": "value_2"}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = TacMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_transaction_confirmation_serialization(): """Test the serialization for 'transaction_confirmation' speech-act works.""" msg = TacMessage( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, transaction_id="some_transaction_id", amount_by_currency_id={"key_1": 1, "key_2": 2}, quantities_by_good_id={"key_1": 1, "key_2": 2}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = TacMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_tac_error_serialization(): """Test the serialization for 'tac_error' speech-act works.""" msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.GENERIC_ERROR, info={"key_1": "value_1", "key_2": "value_2"}, ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert ( expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id ) assert expected_envelope.message != actual_envelope.message actual_msg = TacMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg def test_oef_type_string_value(): """Test the string value of the type.""" assert ( str(TacMessage.Performative.REGISTER) == "register" ), "The str value must be register" assert ( str(TacMessage.Performative.UNREGISTER) == "unregister" ), "The str value must be unregister" assert ( str(TacMessage.Performative.TRANSACTION) == "transaction" ), "The str value must be transaction" assert ( str(TacMessage.Performative.CANCELLED) == "cancelled" ), "The str value must be cancelled" assert ( str(TacMessage.Performative.GAME_DATA) == "game_data" ), "The str value must be game_data" assert ( str(TacMessage.Performative.TRANSACTION_CONFIRMATION) == "transaction_confirmation" ), "The str value must be transaction_confirmation" assert ( str(TacMessage.Performative.TAC_ERROR) == "tac_error" ), "The str value must be tac_error" def test_error_code_to_msg(): """Test the serialization for 'tac_error' speech-act works.""" assert ( str(TacMessage.ErrorCode.to_msg(0)) == "Unexpected error." ), 'The str value must be "Unexpected error."' assert ( str(TacMessage.ErrorCode.to_msg(1)) == "Request not recognized" ), 'The str value must be "Request not recognized"' assert ( str(TacMessage.ErrorCode.to_msg(2)) == "Agent addr already registered." ), 'The str value must be "Agent addr already registered."' assert ( str(TacMessage.ErrorCode.to_msg(3)) == "Agent name already registered." ), 'The str value must be "Agent name already registered."' assert ( str(TacMessage.ErrorCode.to_msg(4)) == "Agent not registered." ), 'The str value must be "Agent not registered."' assert ( str(TacMessage.ErrorCode.to_msg(5)) == "Error in checking transaction" ), 'The str value must be "Error in checking transaction"' assert ( str(TacMessage.ErrorCode.to_msg(6)) == "The transaction request does not match with a previous transaction request with the same id." ), 'The str value must be "The transaction request does not match with a previous transaction request with the same id."' assert ( str(TacMessage.ErrorCode.to_msg(7)) == "Agent name not in whitelist." ), 'The str value must be "Agent name not in whitelist."' assert ( str(TacMessage.ErrorCode.to_msg(8)) == "The competition is not running yet." ), 'The str value must be "The competition is not running yet."' assert ( str(TacMessage.ErrorCode.to_msg(9)) == "The message is inconsistent with the dialogue." ), 'The str value must be "The message is inconsistent with the dialogue."' def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = TacMessage( performative=TacMessage.Performative.CANCELLED, ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(TacMessage.Performative, "__eq__", return_value=False): TacMessage.serializer.encode(msg) def test_decoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during decoding.""" msg = TacMessage( performative=TacMessage.Performative.CANCELLED, ) encoded_msg = TacMessage.serializer.encode(msg) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(TacMessage.Performative, "__eq__", return_value=False): TacMessage.serializer.decode(encoded_msg) @mock.patch.object( packages.fetchai.protocols.tac.message, "enforce", side_effect=AEAEnforceError("some error"), ) def test_incorrect_message(mocked_enforce): """Test that we raise an exception when the message is incorrect.""" with mock.patch.object(tac_message_logger, "error") as mock_logger: TacMessage( performative=TacMessage.Performative.CANCELLED, ) mock_logger.assert_any_call("some error") class TestDialogues: """Tests tac dialogues.""" @classmethod def setup_class(cls): """Set up the test.""" cls.agent_addr = "agent address" cls.controller_addr = "controller address" cls.agent_dialogues = AgentDialogues(cls.agent_addr) cls.controller_dialogues = ControllerDialogues(cls.controller_addr) def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.agent_dialogues._create_self_initiated( dialogue_opponent_addr=self.controller_addr, dialogue_reference=(str(0), ""), role=TacDialogue.Role.PARTICIPANT, ) assert isinstance(result, TacDialogue) assert ( result.role == TacDialogue.Role.PARTICIPANT ), "The role must be participant." def test_create_opponent_initiated(self): """Test the opponent initialisation of a dialogue.""" result = self.agent_dialogues._create_opponent_initiated( dialogue_opponent_addr=self.controller_addr, dialogue_reference=(str(0), ""), role=TacDialogue.Role.PARTICIPANT, ) assert isinstance(result, TacDialogue) assert ( result.role == TacDialogue.Role.PARTICIPANT ), "The role must be participant." class AgentDialogue(TacDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[TacMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ TacDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class AgentDialogues(TacDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return TacDialogue.Role.PARTICIPANT TacDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=AgentDialogue, ) class ControllerDialogue(TacDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__( self, dialogue_label: DialogueLabel, self_address: Address, role: BaseDialogue.Role, message_class: Type[TacMessage], ) -> None: """ Initialize a dialogue. :param dialogue_label: the identifier of the dialogue :param self_address: the address of the entity for whom this dialogue is maintained :param role: the role of the agent this dialogue is maintained for :return: None """ TacDialogue.__init__( self, dialogue_label=dialogue_label, self_address=self_address, role=role, message_class=message_class, ) class ControllerDialogues(TacDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, self_address: Address) -> None: """ Initialize dialogues. :return: None """ def role_from_first_message( # pylint: disable=unused-argument message: Message, receiver_address: Address ) -> BaseDialogue.Role: """Infer the role of the agent from an incoming/outgoing first message :param message: an incoming/outgoing first message :param receiver_address: the address of the receiving agent :return: The role of the agent """ return TacDialogue.Role.CONTROLLER TacDialogues.__init__( self, self_address=self_address, role_from_first_message=role_from_first_message, dialogue_class=ControllerDialogue, ) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the AEA package.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_echo/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/echo dir.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_echo/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour class of the echo skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.echo.behaviours import EchoBehaviour from tests.conftest import ROOT_DIR class TestEchoBehaviour(BaseSkillTestCase): """Test EchoBehaviour behaviour of echo.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "echo") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.echo_behaviour = cast( EchoBehaviour, cls._skill.skill_context.behaviours.echo ) cls.logger = cls._skill.skill_context.logger def test_setup(self): """Test the setup method of the echo behaviour.""" # operation with patch.object(self.logger, "log") as mock_logger: assert self.echo_behaviour.setup() is None # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, "Echo Behaviour: setup method called." ) def test_act(self): """Test the act method of the echo behaviour.""" # operation with patch.object(self.logger, "log") as mock_logger: assert self.echo_behaviour.act() is None # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call(logging.INFO, "Echo Behaviour: act method called.") def test_teardown(self): """Test the teardown method of the echo behaviour.""" # operation with patch.object(self.logger, "log") as mock_logger: assert self.echo_behaviour.teardown() is None # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, "Echo Behaviour: teardown method called." ) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_echo/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the echo skill.""" from pathlib import Path from typing import cast from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.skills.echo.dialogues import DefaultDialogue, DefaultDialogues from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue class of echo.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "echo") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_name ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_echo/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler class of the echo skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.skills.echo.dialogues import DefaultDialogues from packages.fetchai.skills.echo.handlers import EchoHandler from tests.conftest import ROOT_DIR class TestEchoHandler(BaseSkillTestCase): """Test EchoHandler of echo.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "echo") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.echo_handler = cast(EchoHandler, cls._skill.skill_context.handlers.echo) cls.logger = cls._skill.skill_context.logger cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.content = b"some_content" cls.list_of_messages = ( DialogueMessage( DefaultMessage.Performative.BYTES, {"content": cls.content} ), ) def test_setup(self): """Test the setup method of the echo handler.""" with patch.object(self.logger, "log") as mock_logger: assert self.echo_handler.setup() is None # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call(logging.INFO, "Echo Handler: setup method called.") def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=DefaultMessage, dialogue_reference=incorrect_dialogue_reference, performative=DefaultMessage.Performative.BYTES, content=self.content, to=self.skill.skill_context.agent_name, ) # operation with patch.object(self.logger, "log") as mock_logger: self.echo_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid default message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup default_dialogue = self.prepare_skill_dialogue( dialogues=self.default_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=default_dialogue, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"default_message": b"some_bytes"}, ) # operation with patch.object(self.logger, "log") as mock_logger: self.echo_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received default error message={incoming_message} in dialogue={default_dialogue}.", ) def test_handle_bytes(self): """Test the _handle_error method of the oef_search handler.""" # setup default_dialogue = self.prepare_skill_dialogue( dialogues=self.default_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( DefaultMessage, self.build_incoming_message_for_skill_dialogue( dialogue=default_dialogue, performative=DefaultMessage.Performative.BYTES, content=self.content, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.echo_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(1) mock_logger.assert_any_call( logging.INFO, f"Echo Handler: message={incoming_message}, sender={incoming_message.sender}", ) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=DefaultMessage, performative=DefaultMessage.Performative.BYTES, to=incoming_message.sender, sender=self.skill.skill_context.agent_name, target=incoming_message.message_id, content=incoming_message.content, ) assert has_attributes, error_str def test_handle_invalid(self): """Test the _handle_invalid method of the echo handler.""" # setup default_dialogue = self.prepare_skill_dialogue( dialogues=self.default_dialogues, messages=self.list_of_messages[:1], ) incoming_message = cast( DefaultMessage, self.build_incoming_message_for_skill_dialogue( dialogue=default_dialogue, performative=DefaultMessage.Performative.END, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.echo_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid message={incoming_message} in dialogue={self.default_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the echo handler.""" with patch.object(self.logger, "log") as mock_logger: assert self.echo_handler.teardown() is None # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, "Echo Handler: teardown method called." ) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_erc1155_client/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/erc1155_client dir.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_erc1155_client/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module sets up test environment for erc1155_client skill.""" from pathlib import Path from typing import cast from aea.helpers.search.models import ( Attribute, Constraint, ConstraintType, DataModel, Description, Query, ) from aea.helpers.transaction.base import RawMessage, RawTransaction, Terms from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.contract_api.custom_types import Kwargs from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_client.behaviours import SearchBehaviour from packages.fetchai.skills.erc1155_client.dialogues import ( ContractApiDialogues, DefaultDialogues, FipaDialogues, LedgerApiDialogues, OefSearchDialogues, SigningDialogues, ) from packages.fetchai.skills.erc1155_client.handlers import ( ContractApiHandler, FipaHandler, LedgerApiHandler, OefSearchHandler, SigningHandler, ) from packages.fetchai.skills.erc1155_client.strategy import Strategy from tests.conftest import ROOT_DIR class ERC1155ClientTestCase(BaseSkillTestCase): """Sets the erc1155_client class up for testing.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "erc1155_client") @classmethod def setup(cls): """Setup the test class.""" cls.location = {"longitude": 0.1270, "latitude": 51.5194} cls.search_query = { "search_key": "seller_service", "search_value": "erc1155_contract", "constraint_type": "==", } cls.search_radius = 5.0 config_overrides = { "models": { "strategy": { "args": { "location": cls.location, "search_query": cls.search_query, "search_radius": cls.search_radius, } } }, } super().setup(config_overrides=config_overrides) # behaviours cls.search_behaviour = cast( SearchBehaviour, cls._skill.skill_context.behaviours.search ) # dialogues cls.contract_api_dialogues = cast( ContractApiDialogues, cls._skill.skill_context.contract_api_dialogues ) cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) # handlers cls.fipa_handler = cast(FipaHandler, cls._skill.skill_context.handlers.fipa) cls.oef_search_handler = cast( OefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.contract_api_handler = cast( ContractApiHandler, cls._skill.skill_context.handlers.contract_api ) cls.signing_handler = cast( SigningHandler, cls._skill.skill_context.handlers.signing ) cls.ledger_api_handler = cast( LedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) # models cls.strategy = cast(Strategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger # mocked objects cls.ledger_id = "some_ledger_id" cls.contract_id = "some_contract_id" cls.contract_address = "some_contract_address" cls.callable = "some_callable" cls.body = {"some_key": "some_value"} cls.kwargs = Kwargs(cls.body) cls.address = "some_address" cls.mocked_terms = Terms( cls.ledger_id, cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.mocked_query = Query( [Constraint("some_attribute_name", ConstraintType("==", "some_value"))], DataModel( "some_data_model_name", [ Attribute( "some_attribute_name", str, False, "Some attribute descriptions.", ) ], ), ) cls.mocked_proposal = Description( { "contract_address": "some_contract_address", "token_id": "123456", "trade_nonce": "876438756348568", "from_supply": "543", "to_supply": "432", "value": "67", } ) cls.mocked_raw_tx = (RawTransaction(cls.ledger_id, {"some_key": "some_value"}),) cls.mocked_raw_msg = RawMessage(cls.ledger_id, b"some_body") # list of messages cls.list_of_fipa_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": cls.mocked_query}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": cls.mocked_proposal} ), ) cls.list_of_oef_search_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": cls.mocked_query}, ), ) cls.list_of_contract_api_messages = ( DialogueMessage( ContractApiMessage.Performative.GET_RAW_MESSAGE, { "ledger_id": cls.ledger_id, "contract_id": cls.contract_id, "contract_address": cls.contract_address, "callable": cls.callable, "kwargs": cls.kwargs, }, ), ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_MESSAGE, {"terms": cls.mocked_terms, "raw_message": cls.mocked_raw_msg}, ), ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": cls.ledger_id, "address": "some_address"}, ), ) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_erc1155_client/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the erc1155_client skill.""" from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.erc1155_client.behaviours import LEDGER_API_ADDRESS from tests.test_packages_for_aea_tests.test_skills.test_erc1155_client.intermediate_class import ( ERC1155ClientTestCase, ) class TestSearchBehaviour(ERC1155ClientTestCase): """Test search behaviour of erc1155_client.""" def test_setup(self): """Test the setup method of the search behaviour.""" # operation self.search_behaviour.setup() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, to=LEDGER_API_ADDRESS, sender=str(self.skill.public_id), ledger_id=self.strategy.ledger_id, address=self.skill.skill_context.agent_address, ) assert has_attributes, error_str def test_act_is_searching(self): """Test the act method of the search behaviour where is_searching is True.""" # setup self.strategy._is_searching = True # operation self.search_behaviour.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=str(self.skill.public_id), query=self.skill.skill_context.strategy.get_location_and_service_query(), ) assert has_attributes, error_str def test_act_not_is_searching(self): """Test the act method of the search behaviour where is_searching is False.""" # setup self.strategy.is_searching = False # operation self.search_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_teardown(self): """Test the teardown method of the search behaviour.""" assert self.search_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_erc1155_client/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the erc1155_client skill.""" import pytest from aea.exceptions import AEAEnforceError from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_client.dialogues import ( ContractApiDialogue, DefaultDialogue, FipaDialogue, LedgerApiDialogue, OefSearchDialogue, SigningDialogue, ) from tests.test_packages_for_aea_tests.test_skills.test_erc1155_client.intermediate_class import ( ERC1155ClientTestCase, ) class TestDialogues(ERC1155ClientTestCase): """Test dialogue classes of erc1155_client.""" def test_contract_api_dialogue(self): """Test the ContractApiDialogue class.""" contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_fipa_dialogue with pytest.raises(ValueError, match="Associated fipa dialogue not set!"): assert contract_api_dialogue.associated_fipa_dialogue fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=FipaDialogue.Role.BUYER, ) contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue with pytest.raises( AEAEnforceError, match="Associated fipa dialogue already set!" ): contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue assert contract_api_dialogue.associated_fipa_dialogue == fipa_dialogue # terms with pytest.raises(ValueError, match="Terms not set!"): assert contract_api_dialogue.terms contract_api_dialogue.terms = self.mocked_terms with pytest.raises(AEAEnforceError, match="Terms already set!"): contract_api_dialogue.terms = self.mocked_terms assert contract_api_dialogue.terms == self.mocked_terms def test_contract_api_dialogues(self): """Test the ContractApiDialogues class.""" _, dialogue = self.contract_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=self.ledger_id, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ) assert dialogue.role == ContractApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_fipa_dialogues(self): """Test the FipaDialogues class.""" _, dialogue = self.fipa_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=FipaMessage.Performative.CFP, query=self.mocked_query, ) assert dialogue.role == FipaDialogue.Role.SELLER assert dialogue.self_address == self.skill.skill_context.agent_address def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id=self.ledger_id, address=self.address, ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=self.mocked_query, ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_signing_dialogue(self): """Test the SigningDialogue class.""" signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) # associated_contract_api_dialogue with pytest.raises( ValueError, match="Associated contract api dialogue not set!" ): assert signing_dialogue.associated_contract_api_dialogue contract_api_dialogue = ContractApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=ContractApiDialogue.Role.AGENT, ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue with pytest.raises( AEAEnforceError, match="Associated contract api dialogue already set!" ): signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue assert ( signing_dialogue.associated_contract_api_dialogue == contract_api_dialogue ) def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms=self.mocked_terms, raw_transaction=self.mocked_raw_tx, ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_erc1155_client/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the erc1155_client skill.""" import logging from typing import cast from unittest.mock import patch from aea.helpers.search.models import Description from aea.helpers.transaction.base import RawMessage, State, Terms from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.erc1155_client.dialogues import ( ContractApiDialogue, FipaDialogue, LedgerApiDialogue, OefSearchDialogue, SigningDialogue, ) from packages.fetchai.skills.erc1155_client.handlers import LEDGER_API_ADDRESS from tests.test_packages_for_aea_tests.test_skills.test_erc1155_client.intermediate_class import ( ERC1155ClientTestCase, ) class TestFipaHandler(ERC1155ClientTestCase): """Test fipa handler of erc1155_client.""" def test_setup(self): """Test the setup method of the fipa handler.""" assert self.fipa_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the fipa handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( FipaMessage, self.build_incoming_message( message_type=FipaMessage, dialogue_reference=incorrect_dialogue_reference, performative=FipaMessage.Performative.ACCEPT, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"unidentified dialogue for message={incoming_message}.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_propose_i(self): """Test the _handle_propose method of the fipa handler where all expected keys exist in the proposal.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:1], ), ) incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.PROPOSE, proposal=self.mocked_proposal, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received valid PROPOSE from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}: proposal={incoming_message.proposal.values}", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=ContractApiMessage, performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.strategy.ledger_id, contract_id=self.strategy.contract_id, contract_address=incoming_message.proposal.values["contract_address"], callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( { "from_address": incoming_message.sender, "to_address": self.skill.skill_context.agent_address, "token_id": int(incoming_message.proposal.values["token_id"]), "from_supply": int(incoming_message.proposal.values["from_supply"]), "to_supply": int(incoming_message.proposal.values["to_supply"]), "value": int(incoming_message.proposal.values["value"]), "trade_nonce": int(incoming_message.proposal.values["trade_nonce"]), } ), ) assert has_attributes, error_str contract_api_dialogue = cast( ContractApiDialogue, self.contract_api_dialogues.get_dialogue(message) ) expected_terms = Terms( ledger_id=self.strategy.ledger_id, sender_address=self.skill.skill_context.agent_address, counterparty_address=incoming_message.sender, amount_by_currency_id={}, quantities_by_good_id={ str(incoming_message.proposal.values["token_id"]): int( incoming_message.proposal.values["from_supply"] ) - int(incoming_message.proposal.values["to_supply"]) }, is_sender_payable_tx_fee=False, nonce=str(incoming_message.proposal.values["trade_nonce"]), ) assert contract_api_dialogue.terms == expected_terms assert contract_api_dialogue.associated_fipa_dialogue == fipa_dialogue mock_logger.assert_any_call( logging.INFO, "requesting single hash message from contract api...", ) def test_handle_propose_ii(self): """Test the _handle_propose method of the fipa handler where some expected keys do NOT exist in the proposal.""" # setup invalid_proposal = Description({"some_key": "v1", "some_key_2": "12"}) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:1], ), ) incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.PROPOSE, proposal=invalid_proposal, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid PROPOSE from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}: proposal={incoming_message.proposal.values}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the fipa handler.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:2], ), ) incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle fipa message of performative={incoming_message.performative} in dialogue={fipa_dialogue}.", ) def test_teardown(self): """Test the teardown method of the fipa handler.""" assert self.fipa_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestOefSearchHandler(ERC1155ClientTestCase): """Test oef_search handler of erc1155_client.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_oef_search_messages[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.REGISTER_SERVICE, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_search_dialogue}.", ) def test_handle_search_i(self): """Test the _handle_search method of the oef_search handler where the number of agents found is NOT 0.""" # setup agents = ("agent_1", "agent_2") oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_oef_search_messages[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( { "agent_1": {"key_1": "value_1", "key_2": "value_2"}, "agent_2": {"key_3": "value_3", "key_4": "value_4"}, } ), ), ) # before assert self.strategy.is_searching is True # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found agents={list(map(lambda x: x[-5:], incoming_message.agents))}, stopping search.", ) assert self.strategy.is_searching is False self.assert_quantity_in_outbox(len(agents)) for agent in agents: has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.CFP, to=agent, sender=self.skill.skill_context.agent_address, query=self.strategy.get_service_query(), ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending CFP to agent={agent[-5:]}" ) def test_handle_search_ii(self): """Test the _handle_search method of the oef_search handler where the number of agents found is 0.""" # setup agents = tuple() oef_search_dialogue = cast( OefSearchDialogue, self.prepare_skill_dialogue( dialogues=self.oef_search_dialogues, messages=self.list_of_oef_search_messages[:1], ), ) incoming_message = cast( OefSearchMessage, self.build_incoming_message_for_skill_dialogue( dialogue=oef_search_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo({}), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found no agents in dialogue={oef_search_dialogue}, continue searching.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup incoming_message = cast( OefSearchMessage, self.build_incoming_message( message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=self.mocked_proposal, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={incoming_message.performative} in dialogue={self.oef_search_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestContractApiHandler(ERC1155ClientTestCase): """Test contract_api handler of erc1155_client.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the contract_api handler.""" assert self.contract_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( ContractApiMessage, self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=ContractApiMessage.Performative.STATE, state=State(self.ledger_id, self.body), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid contract_api message={incoming_message}, unidentified dialogue.", ) def test_handle_raw_message(self): """Test the _handle_raw_message method of the signing handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) contract_api_dialogue.terms = self.mocked_terms incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.RAW_MESSAGE, raw_message=self.mocked_raw_msg, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw message={incoming_message}" ) self.assert_quantity_in_decision_making_queue(1) message = self.get_message_from_decision_maker_inbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_MESSAGE, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), raw_message=RawMessage( incoming_message.raw_message.ledger_id, incoming_message.raw_message.body, is_deprecated_mode=True, ), terms=contract_api_dialogue.terms, ) assert has_attributes, error_str assert ( cast( SigningDialogue, self.signing_dialogues.get_dialogue(message) ).associated_contract_api_dialogue == contract_api_dialogue ) mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:1], ), ) incoming_message = cast( ContractApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=contract_api_dialogue, performative=ContractApiMessage.Performative.ERROR, data=b"some_data", ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received contract_api error message={incoming_message} in dialogue={contract_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION incoming_message = cast( ContractApiMessage, self.build_incoming_message( message_type=ContractApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=self.ledger_id, contract_id=self.contract_id, callable=self.callable, kwargs=self.kwargs, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.contract_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle contract_api message of performative={invalid_performative} in dialogue={self.contract_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the contract_api handler.""" assert self.contract_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestSigningHandler(ERC1155ClientTestCase): """Test signing handler of erc1155_client.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( SigningMessage, self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_message( self, ): """Test the _handle_signed_message method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:2], counterparty=COUNTERPARTY_AGENT_ADDRESS, ), ) signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) contract_api_dialogue = cast( ContractApiDialogue, self.prepare_skill_dialogue( dialogues=self.contract_api_dialogues, messages=self.list_of_contract_api_messages[:4], ), ) signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_MESSAGE, signed_message=SigningMessage.SignedMessage( self.ledger_id, "some_body", ), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after fipa_dialogue_opponent = fipa_dialogue.dialogue_label.dialogue_opponent_addr mock_logger.assert_any_call( logging.INFO, f"sending ACCEPT_W_INFORM to agent={fipa_dialogue_opponent[-5:]}: tx_signature={incoming_message.signed_message}", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=FipaMessage, performative=FipaMessage.Performative.ACCEPT_W_INFORM, to=fipa_dialogue_opponent, sender=self.skill.skill_context.agent_address, info={"tx_signature": incoming_message.signed_message.body}, ) assert has_attributes, error_str def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ) incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.mocked_terms, raw_transaction=SigningMessage.RawTransaction( self.ledger_id, {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestLedgerApiHandler(ERC1155ClientTestCase): """Test ledger_api handler of erc1155_client.""" is_agent_to_agent_messages = False def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = cast( LedgerApiMessage, self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=self.ledger_id, balance=10, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_balance(self): """Test the _handle_balance method of the ledger_api handler.""" # setup balance = 10 ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=self.ledger_id, balance=balance, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {self.ledger_id} ledger={incoming_message.balance}.", ) def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = cast( LedgerApiMessage, self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id=self.ledger_id, address=self.address, to=str(self.skill.skill_context.skill_id), ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_erc1155_client/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the erc1155_client skill.""" from aea.helpers.search.models import Constraint, ConstraintType, Query from packages.fetchai.skills.erc1155_client.strategy import ( CONTRACT_ID, SIMPLE_SERVICE_MODEL, ) from tests.test_packages_for_aea_tests.test_skills.test_erc1155_client.intermediate_class import ( ERC1155ClientTestCase, ) class TestStrategy(ERC1155ClientTestCase): """Test Strategy of erc1155_client.""" def test_properties(self): """Test the properties of Strategy class.""" assert self.strategy.ledger_id == self.skill.skill_context.default_ledger_id assert self.strategy.contract_id == str(CONTRACT_ID) def test_get_location_and_service_query(self): """Test the get_location_and_service_query method of the Strategy class.""" query = self.strategy.get_location_and_service_query() assert type(query) == Query assert len(query.constraints) == 2 assert query.model is None location_constraint = Constraint( "location", ConstraintType( "distance", (self.strategy._agent_location, self.search_radius) ), ) assert query.constraints[0] == location_constraint service_key_constraint = Constraint( self.search_query["search_key"], ConstraintType( self.search_query["constraint_type"], self.search_query["search_value"], ), ) assert query.constraints[1] == service_key_constraint def test_get_service_query(self): """Test the get_service_query method of the Strategy class.""" query = self.strategy.get_service_query() assert type(query) == Query assert len(query.constraints) == 1 assert query.model == SIMPLE_SERVICE_MODEL service_key_constraint = Constraint( self.search_query["search_key"], ConstraintType( self.search_query["constraint_type"], self.search_query["search_value"], ), ) assert query.constraints[0] == service_key_constraint ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_buyer/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/generic_buyer dir.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_buyer/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the generic buyer skill.""" import logging from pathlib import Path from typing import Tuple, cast from unittest.mock import patch import pytest from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.connections.ledger.base import CONNECTION_ID as LEDGER_PUBLIC_ID from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_buyer.behaviours import ( GenericSearchBehaviour, GenericTransactionBehaviour, LEDGER_API_ADDRESS, ) from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, ) from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy from tests.conftest import ROOT_DIR FETCHAI = "fetchai" class TestSearchBehaviour(BaseSkillTestCase): """Test Search behaviour of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.search_behaviour = cast( GenericSearchBehaviour, cls._skill.skill_context.behaviours.search ) cls.tx_behaviour = cast( GenericTransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger def test_setup_is_ledger_tx(self): """Test the setup method of the search behaviour where is_ledger_tx is True.""" # operation self.search_behaviour.setup() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, to=str(LEDGER_PUBLIC_ID), sender=str(self.skill.public_id), ledger_id=FETCHAI, address=self.skill.skill_context.agent_address, ) assert has_attributes, error_str def test_setup_not_is_ledger_tx(self): """Test the setup method of the search behaviour where is_ledger_tx is False.""" # setup self.strategy._is_ledger_tx = False # before assert not self.strategy.is_searching # operation self.search_behaviour.setup() # after assert self.strategy.is_searching def test_act_is_searching(self): """Test the act method of the search behaviour where is_searching is True.""" # setup self.strategy._is_searching = True # operation self.search_behaviour.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.SEARCH_SERVICES, to=self.skill.skill_context.search_service_address, sender=str(self.skill.public_id), query=self.skill.skill_context.strategy.get_location_and_service_query(), ) assert has_attributes, error_str def test_act_not_is_searching(self): """Test the act method of the search behaviour where is_searching is False.""" # setup self.strategy._is_searching = False # operation self.search_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_act_remaining_transactions(self): """Test the act method of the search behaviour where remaining_transactions_count > 0.""" # setup self.strategy._is_searching = True self.tx_behaviour.waiting = ["some_dialogue"] # operation with patch.object(self.logger, "log") as mock_logger: self.search_behaviour.act() # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"Transaction behaviour has {len(self.tx_behaviour.waiting)} transactions remaining. Skipping search!", ) def test_teardown(self): """Test the teardown method of the search behaviour.""" assert self.search_behaviour.teardown() is None self.assert_quantity_in_outbox(0) class TestTransactionBehaviour(BaseSkillTestCase): """Test transaction behaviour of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.transaction_behaviour = cast( GenericTransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.list_of_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), ) @staticmethod def _check_start_processing_effects(self_, fipa_dialogue, mock_logger) -> None: """Perform checks related to running _start_processing.""" # _start_processing mock_logger.assert_any_call( logging.INFO, f"Processing transaction, {len(self_.transaction_behaviour.waiting)} transactions remaining", ) message = self_.get_message_from_outbox() has_attributes, error_str = self_.message_has_attributes( actual_message=message, message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self_.skill.public_id), terms=fipa_dialogue.terms, ) assert has_attributes, error_str ledger_api_dialogue = cast( LedgerApiDialogue, self_.ledger_api_dialogues.get_dialogue(message) ) assert ledger_api_dialogue.associated_fipa_dialogue == fipa_dialogue assert self_.transaction_behaviour.processing_time == 0.0 assert self_.transaction_behaviour.processing == ledger_api_dialogue mock_logger.assert_any_call( logging.INFO, f"requesting transfer transaction from ledger api for message={message}...", ) @staticmethod def _setup_fipa_ledger_api_dialogues( self_, ) -> Tuple[LedgerApiDialogue, FipaDialogue]: """Setup fipa and ledger_api dialogues for some of the following tests.""" fipa_dialogue = cast( FipaDialogue, self_.prepare_skill_dialogue( dialogues=self_.fipa_dialogues, messages=self_.list_of_messages, ), ) fipa_dialogue.terms = "terms" # type: ignore ledger_api_dialogue = cast( LedgerApiDialogue, self_.prepare_skill_dialogue( dialogues=self_.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": "some_ledger_id", "address": "some_address"}, ), ), ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue return ledger_api_dialogue, fipa_dialogue def test_setup(self): """Test the setup method of the transaction behaviour.""" assert self.transaction_behaviour.setup() is None self.assert_quantity_in_outbox(0) def test_act_i(self): """Test the act method of the transaction behaviour where processing IS None and len(self.waiting) is NOT 0.""" # setup _, fipa_dialogue = self._setup_fipa_ledger_api_dialogues(self) processing_time = 5.0 max_processing = 120 self.transaction_behaviour.processing = None self.transaction_behaviour.max_processing = max_processing self.transaction_behaviour.processing_time = processing_time self.transaction_behaviour.waiting = [fipa_dialogue] # before assert self.transaction_behaviour.processing_time == processing_time assert self.transaction_behaviour.processing is None # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(1) # _start_processing self._check_start_processing_effects(self, fipa_dialogue, mock_logger) def test_act_ii(self): """Test the act method of the transaction behaviour where processing is NOT None and processing_time < max_processing.""" # setup processing_time = 5.0 self.transaction_behaviour.processing = "some_dialogue" self.transaction_behaviour.max_processing = 120 self.transaction_behaviour.processing_time = processing_time # operation self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(0) assert ( self.transaction_behaviour.processing_time == processing_time + self.transaction_behaviour.tick_interval ) def test_act_iii(self): """Test the act method of the transaction behaviour where processing is NOT None and processing_time > max_processing.""" # setup ledger_api_dialogue, fipa_dialogue = self._setup_fipa_ledger_api_dialogues(self) processing_time = 121.0 self.transaction_behaviour.processing = ledger_api_dialogue self.transaction_behaviour.max_processing = 120 self.transaction_behaviour.processing_time = processing_time # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(1) # _timeout_processing assert ledger_api_dialogue.dialogue_label in self.transaction_behaviour.timedout # below is overridden in _start_processing # assert fipa_dialogue in self.transaction_behaviour.waiting assert self.transaction_behaviour.processing_time == 0.0 # below is overridden in _start_processing # assert self.transaction_behaviour.processing is None # _start_processing self._check_start_processing_effects(self, fipa_dialogue, mock_logger) def test_timeout_processing(self): """Test the _timeout_processing method of the transaction behaviour where self.processing IS None.""" # setup self.transaction_behaviour.processing_time = None # operation self.transaction_behaviour._timeout_processing() # after self.assert_quantity_in_outbox(0) def test_act_iv(self): """Test the act method of the transaction behaviour where len(waiting) == 0.""" # setup self.transaction_behaviour.processing = None self.transaction_behaviour.waiting = [] # operation self.transaction_behaviour.act() # after self.assert_quantity_in_outbox(0) def test_failed_processing(self): """Test the failed_processing method of the transaction behaviour.""" # setup ledger_api_dialogue, fipa_dialogue = self._setup_fipa_ledger_api_dialogues(self) self.transaction_behaviour.timedout.add(ledger_api_dialogue.dialogue_label) # operation with patch.object(self.logger, "log") as mock_logger: self.transaction_behaviour.failed_processing(ledger_api_dialogue) # after self.assert_quantity_in_outbox(0) # finish_processing assert self.transaction_behaviour.timedout == set() mock_logger.assert_any_call( logging.DEBUG, f"Timeout dialogue in transaction processing: {ledger_api_dialogue}", ) # failed_processing assert fipa_dialogue in self.transaction_behaviour.waiting def test_finish_processing_i(self): """Test the finish_processing method of the transaction behaviour where self.processing == ledger_api_dialogue.""" # setup ledger_api_dialogue, fipa_dialogue = self._setup_fipa_ledger_api_dialogues(self) self.transaction_behaviour.processing = ledger_api_dialogue # operation self.transaction_behaviour.failed_processing(ledger_api_dialogue) # after assert self.transaction_behaviour.processing_time == 0.0 assert self.transaction_behaviour.processing is None def test_finish_processing_ii(self): """Test the finish_processing method of the transaction behaviour where ledger_api_dialogue's dialogue_label is NOT in self.timedout.""" # setup ledger_api_dialogue, fipa_dialogue = self._setup_fipa_ledger_api_dialogues(self) # operation with pytest.raises(ValueError) as err: self.transaction_behaviour.finish_processing(ledger_api_dialogue) # after assert ( err.value.args[0] == f"Non-matching dialogues in transaction behaviour: {self.transaction_behaviour.processing} and {ledger_api_dialogue}" ) def test_teardown(self): """Test the teardown method of the transaction behaviour.""" assert self.transaction_behaviour.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_buyer/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the generic buyer skill.""" from pathlib import Path from typing import cast import pytest from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.generic_buyer.dialogues import ( DefaultDialogue, DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue classes of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_fipa_dialogue(self): """Test the FipaDialogue class.""" fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=DefaultDialogue.Role.AGENT, ) # terms with pytest.raises(AEAEnforceError, match="Terms not set!"): assert fipa_dialogue.terms terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) fipa_dialogue.terms = terms with pytest.raises(AEAEnforceError, match="Terms already set!"): fipa_dialogue.terms = terms assert fipa_dialogue.terms == terms def test_fipa_dialogues(self): """Test the FipaDialogues class.""" _, dialogue = self.fipa_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=FipaMessage.Performative.CFP, query="some_query", ) assert dialogue.role == FipaDialogue.Role.BUYER assert dialogue.self_address == self.skill.skill_context.agent_address def test_ledger_api_dialogue(self): """Test the LedgerApiDialogue class.""" ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=LedgerApiDialogue.Role.AGENT, ) # associated_fipa_dialogue with pytest.raises(AEAEnforceError, match="FipaDialogue not set!"): assert ledger_api_dialogue.associated_fipa_dialogue fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=FipaDialogue.Role.BUYER, ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue with pytest.raises(AEAEnforceError, match="FipaDialogue already set!"): ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue assert ledger_api_dialogue.associated_fipa_dialogue == fipa_dialogue def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.public_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query="some_query", ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.public_id) def test_signing_dialogue(self): """Test the SigningDialogue class.""" signing_dialogue = SigningDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=SigningDialogue.Role.SKILL, ) # associated_ledger_api_dialogue with pytest.raises(AEAEnforceError, match="LedgerApiDialogue not set!"): assert signing_dialogue.associated_ledger_api_dialogue ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=LedgerApiDialogue.Role.AGENT, ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue with pytest.raises(AEAEnforceError, match="LedgerApiDialogue already set!"): signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue assert signing_dialogue.associated_ledger_api_dialogue == ledger_api_dialogue def test_signing_dialogues(self): """Test the SigningDialogues class.""" _, dialogue = self.signing_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=SigningMessage.Performative.SIGN_TRANSACTION, terms="some_terms", raw_transaction="some_raw_transaction", ) assert dialogue.role == SigningDialogue.Role.SKILL assert dialogue.self_address == str(self.skill.public_id) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_buyer/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the generic buyer skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch import pytest from aea.crypto.ledger_apis import LedgerApis from aea.helpers.search.models import Description from aea.helpers.transaction.base import ( RawTransaction, SignedTransaction, Terms, TransactionDigest, TransactionReceipt, ) from aea.protocols.dialogue.base import DialogueMessage from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.signing.message import SigningMessage from packages.fetchai.skills.generic_buyer.behaviours import GenericTransactionBehaviour from packages.fetchai.skills.generic_buyer.dialogues import ( FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.generic_buyer.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, GenericSigningHandler, LEDGER_API_ADDRESS, ) from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy from tests.conftest import ROOT_DIR class TestGenericFipaHandler(BaseSkillTestCase): """Test fipa handler of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.fipa_handler = cast( GenericFipaHandler, cls._skill.skill_context.handlers.fipa ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.list_of_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) def test_setup(self): """Test the setup method of the fipa handler.""" assert self.fipa_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the fipa handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=FipaMessage, dialogue_reference=incorrect_dialogue_reference, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid fipa message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_propose_is_affordable_and_is_acceptable(self): """Test the _handle_propose method of the fipa handler.""" # setup proposal = Description( { "ledger_id": self.strategy.ledger_id, "price": 100, "currency_id": "FET", "service_id": "some_service_id", "quantity": 1, "tx_nonce": "some_tx_nonce", } ) fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.PROPOSE, proposal=proposal, ) # operation with patch.object( self.strategy, "is_acceptable_proposal", return_value=True, ): with patch.object( self.strategy, "is_affordable_proposal", return_value=True, ): with patch.object( self.fipa_handler.context.logger, "log" ) as mock_logger: self.fipa_handler.handle(incoming_message) # after incoming_message = cast(FipaMessage, incoming_message) mock_logger.assert_any_call( logging.INFO, f"received proposal={incoming_message.proposal.values} from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.INFO, f"accepting the proposal from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.ACCEPT, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, ) assert has_attributes, error_str def test_handle_propose_not_is_affordable_or_not_is_acceptable(self): """Test the _handle_propose method of the fipa handler.""" # setup proposal = Description( { "ledger_id": self.strategy.ledger_id, "price": 100, "currency_id": "FET", "service_id": "some_service_id", "quantity": 1, "tx_nonce": "some_tx_nonce", } ) fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.PROPOSE, proposal=proposal, ) # operation with patch.object( self.strategy, "is_acceptable_proposal", return_value=False, ): with patch.object( self.strategy, "is_affordable_proposal", return_value=False, ): with patch.object( self.fipa_handler.context.logger, "log" ) as mock_logger: self.fipa_handler.handle(incoming_message) # after incoming_message = cast(FipaMessage, incoming_message) mock_logger.assert_any_call( logging.INFO, f"received proposal={incoming_message.proposal.values} from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.INFO, f"declining the proposal from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.DECLINE, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, ) assert has_attributes, error_str def test_handle_decline_decline_cfp(self): """Test the _handle_decline method of the fipa handler where the end state is decline_cfp.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, ) # before for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): assert end_state_numbers == 0 for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): assert end_state_numbers == 0 # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received DECLINE from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): assert end_state_numbers == 0 for ( end_state, end_state_numbers, ) in self.fipa_dialogues.dialogue_stats.self_initiated.items(): if end_state == FipaDialogue.EndState.DECLINED_CFP: assert end_state_numbers == 1 else: assert end_state_numbers == 0 def test_handle_decline_decline_accept(self): """Test the _handle_decline method of the fipa handler where the end state is decline_accept.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:3], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, ) # before for end_state_numbers in list( self.fipa_dialogues.dialogue_stats.self_initiated.values() ) + list(self.fipa_dialogues.dialogue_stats.other_initiated.values()): assert end_state_numbers == 0 # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received DECLINE from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): assert end_state_numbers == 0 for ( end_state, end_state_numbers, ) in self.fipa_dialogues.dialogue_stats.self_initiated.items(): if end_state == FipaDialogue.EndState.DECLINED_ACCEPT: assert end_state_numbers == 1 else: assert end_state_numbers == 0 def test_handle_match_accept_is_ledger_tx(self): """Test the _handle_match_accept method of the fipa handler where is_ledger_tx is True.""" # setup self.strategy._is_ledger_tx = True fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:3], ) fipa_dialogue.terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"info": {"address": "some_term_sender_address"}}, ), ) # operation with patch.object( self.fipa_handler.context.logger, "log" ) as mock_logger_handler: self.fipa_handler.handle(incoming_message) # after mock_logger_handler.assert_any_call( logging.INFO, f"received MATCH_ACCEPT_W_INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]} with info={incoming_message.info}", ) # operation with patch.object( self.fipa_handler.context.behaviours.transaction.context.logger, "log" ) as _: self.fipa_handler.context.behaviours.transaction.act() self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), terms=fipa_dialogue.terms, ) assert has_attributes, error_str def test_handle_match_accept_not_is_ledger_tx(self): """Test the _handle_match_accept method of the fipa handler where is_ledger_tx is False.""" # setup self.strategy._is_ledger_tx = False fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:3], ) incoming_message = cast( FipaMessage, self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"info": {"address": "some_term_sender_address"}}, ), ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received MATCH_ACCEPT_W_INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]} with info={incoming_message.info}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.INFORM, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, info={"Done": "Sending payment via bank transfer"}, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"informing counterparty={COUNTERPARTY_AGENT_ADDRESS[-5:]} of payment.", ) def test_handle_inform_with_data(self): """Test the _handle_inform method of the fipa handler where info has data.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.INFORM, info={"data_name": "data"}, ) # before for end_state_numbers in list( self.fipa_dialogues.dialogue_stats.self_initiated.values() ) + list(self.fipa_dialogues.dialogue_stats.other_initiated.values()): assert end_state_numbers == 0 # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.INFO, "received the following data={'data_name': 'data'}" ) for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): assert end_state_numbers == 0 for ( end_state, end_state_numbers, ) in self.fipa_dialogues.dialogue_stats.self_initiated.items(): if end_state == FipaDialogue.EndState.SUCCESSFUL: assert end_state_numbers == 1 else: assert end_state_numbers == 0 def test_handle_inform_without_data(self): """Test the _handle_inform method of the fipa handler where info has NO data.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.INFORM, info={}, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.INFO, f"received no data from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) def test_handle_invalid(self): """Test the _handle_invalid method of the fipa handler.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle fipa message of performative={incoming_message.performative} in dialogue={fipa_dialogue}.", ) def test_teardown(self): """Test the teardown method of the fipa handler.""" assert self.fipa_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestGenericOefSearchHandler(BaseSkillTestCase): """Test oef search handler of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( GenericOefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.list_of_messages = ( DialogueMessage( OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} ), ) def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_error(self): """Test the _handle_error method of the oef_search handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) def test_handle_search_zero_agents(self): """Test the _handle_search method of the oef_search handler.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=tuple(), agents_info=OefSearchMessage.AgentsInfo({}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found no agents in dialogue={oef_dialogue}, continue searching.", ) def test_handle_search_i(self): """Test the _handle_search method of the oef_search handler where is_stop_searching_on_result is True.""" # setup self.strategy._max_negotiations = 3 self.strategy._is_stop_searching_on_result = True self.strategy._is_searching = True oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) agents = ("agnt1", "agnt2") incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( {"agent_1": {"key_1": "value_1"}, "agent_2": {"key_2": "value_2"}} ), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found agents={list(agents)}, stopping search." ) assert self.strategy.is_searching is False self.assert_quantity_in_outbox(len(agents)) for agent in agents: has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.CFP, to=agent, sender=self.skill.skill_context.agent_address, target=0, query=self.strategy.get_service_query(), ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, f"sending CFP to agent={agent}") def test_handle_search_ii(self): """Test the _handle_search method of the oef_search handler where is_stop_searching_on_result is False.""" # setup self.strategy._max_negotiations = 3 self.strategy._is_stop_searching_on_result = False self.strategy._is_searching = True oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) agents = ("agnt1", "agnt2") incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( {"agent_1": {"key_1": "value_1"}, "agent_2": {"key_2": "value_2"}} ), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, f"found agents={list(agents)}.") assert self.strategy.is_searching is True self.assert_quantity_in_outbox(len(agents)) for agent in agents: has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.CFP, to=agent, sender=self.skill.skill_context.agent_address, target=0, query=self.strategy.get_service_query(), ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, f"sending CFP to agent={agent}") def test_handle_search_more_than_max_negotiation(self): """Test the _handle_search method of the oef_search handler where number of agents is more than max_negotiation.""" # setup self.strategy._max_negotiations = 1 oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], ) agents = ("agnt1", "agnt2") incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SEARCH_RESULT, agents=agents, agents_info=OefSearchMessage.AgentsInfo( {"agent_1": {"key_1": "value_1"}, "agent_2": {"key_2": "value_2"}} ), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"found agents={list(agents)}, stopping search." ) assert not self.strategy.is_searching self.assert_quantity_in_outbox(self.strategy._max_negotiations) for idx in range(0, self.strategy._max_negotiations): has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.CFP, to=agents[idx], sender=self.skill.skill_context.agent_address, target=0, query=self.strategy.get_service_query(), ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"sending CFP to agent={agents[idx]}" ) def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=("1", ""), performative=invalid_performative, service_description="some_service_description", ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestGenericSigningHandler(BaseSkillTestCase): """Test signing handler of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.signing_handler = cast( GenericSigningHandler, cls._skill.skill_context.handlers.signing ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.signing_dialogues = cast( SigningDialogues, cls._skill.skill_context.signing_dialogues ) cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_fipa_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) cls.list_of_signing_messages = ( DialogueMessage( SigningMessage.Performative.SIGN_TRANSACTION, { "terms": cls.terms, "raw_transaction": SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), }, ), ) cls.list_of_ledger_api_messages = ( DialogueMessage(LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {}), DialogueMessage(LedgerApiMessage.Performative.RAW_TRANSACTION, {}), DialogueMessage(LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {}), DialogueMessage(LedgerApiMessage.Performative.TRANSACTION_DIGEST, {}), ) def test_setup(self): """Test the setup method of the signing handler.""" assert self.signing_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the signing handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=incorrect_dialogue_reference, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.signing_handler.context.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid signing message={incoming_message}, unidentified dialogue.", ) def test_handle_signed_transaction_last_ledger_api_message_is_none( self, ): """Test the _handle_signed_transaction method of the signing handler.""" # setup signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], ), ) ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:2], ), ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue signing_dialogue.associated_ledger_api_dialogue._incoming_messages = [] incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SigningMessage.SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ), ) # operation with pytest.raises( ValueError, match="Could not retrieve last message in ledger api dialogue" ): with patch.object( self.signing_handler.context.logger, "log" ) as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") def test_handle_signed_transaction_last_ledger_api_message_is_not_none( self, ): """Test the _handle_signed_transaction method of the signing handler where the last ledger_api message is not None.""" # setup signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:2], counterparty=LEDGER_API_ADDRESS, ), ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.SIGNED_TRANSACTION, signed_transaction=SigningMessage.SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ), ), ) # operation with patch.object(self.signing_handler.context.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), signed_transaction=incoming_message.signed_transaction, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "sending transaction to ledger.") def test_handle_error(self): """Test the _handle_error method of the signing handler.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], counterparty=COUNTERPARTY_AGENT_ADDRESS, is_agent_to_agent_messages=True, ), ) ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:4], counterparty=LEDGER_API_ADDRESS, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue signing_counterparty = self.skill.skill_context.decision_maker_address signing_dialogue = cast( SigningDialogue, self.prepare_skill_dialogue( dialogues=self.signing_dialogues, messages=self.list_of_signing_messages[:1], counterparty=signing_counterparty, ), ) signing_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue incoming_message = cast( SigningMessage, self.build_incoming_message_for_skill_dialogue( dialogue=signing_dialogue, performative=SigningMessage.Performative.ERROR, error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, ), ) # operation with patch.object( self.signing_handler.context.behaviours.transaction, "failed_processing" ): with patch.object( self.signing_handler.context.logger, "log" ) as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", ) behaviour = cast( GenericTransactionBehaviour, self.skill.skill_context.behaviours.transaction ) # finish_processing assert behaviour.processing_time == 0.0 assert behaviour.processing is None def test_handle_invalid(self): """Test the _handle_invalid method of the signing handler.""" # setup invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION incoming_message = self.build_incoming_message( message_type=SigningMessage, dialogue_reference=("1", ""), performative=invalid_performative, terms=self.terms, raw_transaction=SigningMessage.RawTransaction( "some_ledger_id", {"some_key": "some_value"} ), to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.signing_handler.context.logger, "log") as mock_logger: self.signing_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the signing handler.""" assert self.signing_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestGenericLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_api_handler = cast( GenericLedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.transaction_behaviour = cast( GenericTransactionBehaviour, cls._skill.skill_context.behaviours.transaction ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_fipa_messages = ( DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) cls.raw_transaction = RawTransaction( "some_ledger_id", {"some_key": "some_value"} ) cls.signed_transaction = SignedTransaction( "some_ledger_id", {"some_key": "some_value"} ) cls.transaction_digest = TransactionDigest("some_ledger_id", "some_body") cls.transaction_receipt = TransactionReceipt( "some_ledger_id", {"receipt_key": "receipt_value"}, {"transaction_key": "transaction_value"}, ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {"terms": cls.terms} ), DialogueMessage( LedgerApiMessage.Performative.RAW_TRANSACTION, {"raw_transaction": cls.raw_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {"signed_transaction": cls.signed_transaction}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_DIGEST, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_RECEIPT, {"transaction_receipt": cls.transaction_receipt}, ), ) def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_balance_positive_balance(self): """Test the _handle_balance method of the ledger_api handler where balance is positive.""" # setup balance = 10 ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": "some_ledger_id", "address": "some_address"}, ), ), counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id="some-Ledger_id", balance=balance, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {self.strategy.ledger_id} ledger={incoming_message.balance}.", ) assert self.strategy.balance == balance assert self.strategy.is_searching def test_handle_balance_zero_balance(self): """Test the _handle_balance method of the ledger_api handler where balance is zero.""" # setup balance = 0 ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": "some_ledger_id", "address": "some_address"}, ), ), counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id="some-Ledger_id", balance=balance, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"you have no starting balance on {self.strategy.ledger_id} ledger! Stopping skill {self.strategy.context.skill_id}.", ) assert not self.skill.skill_context.is_active def test_handle_raw_transaction(self): """Test the _handle_raw_transaction method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], counterparty=LEDGER_API_ADDRESS, ), ) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], is_agent_to_agent_messages=True, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue fipa_dialogue.terms = self.terms incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.RAW_TRANSACTION, raw_transaction=self.raw_transaction, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received raw transaction={incoming_message}" ) message_quantity = self.get_quantity_in_decision_maker_inbox() assert ( message_quantity == 1 ), f"Invalid number of messages in decision maker queue. Expected {1}. Found {message_quantity}." has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_decision_maker_inbox(), message_type=SigningMessage, performative=SigningMessage.Performative.SIGN_TRANSACTION, to=self.skill.skill_context.decision_maker_address, sender=str(self.skill.skill_context.skill_id), terms=self.terms, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "proposing the transaction to the decision maker. Waiting for confirmation ...", ) def test_handle_transaction_digest(self): """Test the _handle_transaction_digest method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:3], counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, transaction_digest=self.transaction_digest, ), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction was successfully submitted. Transaction digest={incoming_message.transaction_digest}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, to=incoming_message.sender, sender=str(self.skill.skill_context.skill_id), transaction_digest=self.transaction_digest, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "checking transaction is settled.", ) def test_handle_transaction_receipt_i(self): """Test the _handle_transaction_receipt method of the ledger_api handler.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], is_agent_to_agent_messages=True, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue fipa_dialogue.terms = self.terms incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( self.ledger_api_handler.context.behaviours.transaction, "finish_processing" ): with patch.object(LedgerApis, "is_transaction_settled", return_value=True): with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction confirmed, informing counterparty={fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:]} of transaction digest.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.INFORM, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, info={"transaction_digest": self.transaction_digest.body}, ) assert has_attributes, error_str def test_handle_transaction_receipt_ii(self): """Test the _handle_transaction_receipt method of the ledger_api handler where fipa dialogue's last_incoming_message is None.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], is_agent_to_agent_messages=True, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue fipa_dialogue._incoming_messages = [] fipa_dialogue.terms = self.terms incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( self.ledger_api_handler.context.behaviours.transaction, "finish_processing" ): with patch.object(LedgerApis, "is_transaction_settled", return_value=True): with patch.object(self.logger, "log"): with pytest.raises( ValueError, match="Could not retrieve last fipa message" ): self.ledger_api_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) def test_handle_transaction_receipt_iii(self): """Test the _handle_transaction_receipt method of the ledger_api handler where tx is NOT settled.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:5], counterparty=LEDGER_API_ADDRESS, ), ) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], is_agent_to_agent_messages=True, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue fipa_dialogue.terms = self.terms incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( self.ledger_api_handler.context.behaviours.transaction, "failed_processing" ): with patch.object(LedgerApis, "is_transaction_settled", return_value=False): with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) assert self.transaction_behaviour.processing is None assert self.transaction_behaviour.processing_time == 0.0 mock_logger.assert_any_call( logging.INFO, f"transaction_receipt={self.transaction_receipt} not settled or not valid, aborting", ) def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) ledger_api_dialogue.associated_fipa_dialogue = "mock" # operation with patch.object( self.ledger_api_handler.context.behaviours.transaction, "failed_processing" ): with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id="some_ledger_id", address="some_address", to=str(self.skill.public_id), ) # operation with patch.object(self.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_buyer/test_models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the generic buyer skill.""" from pathlib import Path from aea.configurations.constants import DEFAULT_LEDGER from aea.helpers.search.models import Constraint, ConstraintType, Description, Query from aea.helpers.transaction.base import Terms from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.skills.generic_buyer.strategy import ( GenericStrategy, SIMPLE_SERVICE_MODEL, ) from tests.conftest import ROOT_DIR class TestGenericStrategy(BaseSkillTestCase): """Test GenericStrategy of generic buyer.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_id = DEFAULT_LEDGER cls.is_ledger_tx = True cls.currency_id = "some_currency_id" cls.max_unit_price = 20 cls.max_tx_fee = 1 cls.service_id = "some_service_id" cls.search_query = { "constraint_type": "==", "search_key": "seller_service", "search_value": "some_search_value", } cls.location = { "longitude": 0.127, "latitude": 51.5194, } cls.search_radius = 5.0 cls.max_negotiations = 2 cls.strategy = GenericStrategy( ledger_id=cls.ledger_id, is_ledger_tx=cls.is_ledger_tx, currency_id=cls.currency_id, max_unit_price=cls.max_unit_price, max_tx_fee=cls.max_tx_fee, service_id=cls.service_id, search_query=cls.search_query, location=cls.location, search_radius=cls.search_radius, max_negotiations=cls.max_negotiations, name="strategy", skill_context=cls._skill.skill_context, ) def test_properties(self): """Test the properties of GenericStrategy class.""" assert self.strategy.ledger_id == self.ledger_id assert self.strategy.is_ledger_tx == self.is_ledger_tx assert self.strategy.is_searching is False self.strategy.is_searching = True assert self.strategy.is_searching is True assert self.strategy.balance == 0 self.strategy.balance = 100 assert self.strategy.balance == 100 assert self.strategy.max_negotiations is self.max_negotiations def test_get_location_and_service_query(self): """Test the get_location_and_service_query method of the GenericStrategy class.""" query = self.strategy.get_location_and_service_query() assert type(query) == Query assert len(query.constraints) == 2 assert query.model is None location_constraint = Constraint( "location", ConstraintType( "distance", (self.strategy._agent_location, self.search_radius) ), ) assert query.constraints[0] == location_constraint service_key_constraint = Constraint( self.search_query["search_key"], ConstraintType( self.search_query["constraint_type"], self.search_query["search_value"], ), ) assert query.constraints[1] == service_key_constraint def test_get_service_query(self): """Test the get_service_query method of the GenericStrategy class.""" query = self.strategy.get_service_query() assert type(query) == Query assert len(query.constraints) == 1 assert query.model == SIMPLE_SERVICE_MODEL service_key_constraint = Constraint( self.search_query["search_key"], ConstraintType( self.search_query["constraint_type"], self.search_query["search_value"], ), ) assert query.constraints[0] == service_key_constraint def test_is_acceptable_proposal(self): """Test the is_acceptable_proposal method of the GenericStrategy class.""" acceptable_description = Description( { "ledger_id": self.ledger_id, "price": 150, "currency_id": self.currency_id, "service_id": self.service_id, "quantity": 10, "tx_nonce": "some_tx_nonce", } ) is_acceptable = self.strategy.is_acceptable_proposal(acceptable_description) assert is_acceptable unacceptable_description = Description( { "ledger_id": self.ledger_id, "price": 250, "currency_id": self.currency_id, "service_id": self.service_id, "quantity": 10, "tx_nonce": "some_tx_nonce", } ) is_acceptable = self.strategy.is_acceptable_proposal(unacceptable_description) assert not is_acceptable def test_is_affordable_proposal(self): """Test the is_affordable_proposal method of the GenericStrategy class.""" description = Description( { "ledger_id": self.ledger_id, "price": 150, "currency_id": self.currency_id, "service_id": self.service_id, "quantity": 10, "tx_nonce": "some_tx_nonce", } ) self.strategy.balance = 151 is_affordable = self.strategy.is_affordable_proposal(description) assert is_affordable self.strategy.balance = 150 is_affordable = self.strategy.is_affordable_proposal(description) assert not is_affordable self.strategy._is_ledger_tx = False is_affordable = self.strategy.is_affordable_proposal(description) assert is_affordable def test_terms_from_proposal(self): """Test the terms_from_proposal method of the GenericStrategy class.""" description = Description( { "ledger_id": self.ledger_id, "price": 150, "currency_id": self.currency_id, "service_id": self.service_id, "quantity": 10, "tx_nonce": "some_tx_nonce", } ) terms = Terms( ledger_id=self.ledger_id, sender_address=self.skill.skill_context.agent_address, counterparty_address=COUNTERPARTY_AGENT_ADDRESS, amount_by_currency_id={self.currency_id: -150}, quantities_by_good_id={self.service_id: 10}, is_sender_payable_tx_fee=True, nonce="some_tx_nonce", fee_by_currency_id={self.currency_id: self.max_tx_fee}, ) assert ( self.strategy.terms_from_proposal(description, COUNTERPARTY_AGENT_ADDRESS) == terms ) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_seller/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/generic_seller dir.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_seller/test_behaviours.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the behaviour classes of the generic seller skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch from aea.helpers.search.models import Description from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, LEDGER_API_ADDRESS, ) from packages.fetchai.skills.generic_seller.strategy import GenericStrategy from tests.conftest import ROOT_DIR class TestSkillBehaviour(BaseSkillTestCase): """Test behaviours of generic seller.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.service_registration = cast( GenericServiceRegistrationBehaviour, cls._skill.skill_context.behaviours.service_registration, ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.logger = cls._skill.skill_context.logger cls.registration_message = OefSearchMessage( dialogue_reference=("", ""), performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description="some_service_description", ) cls.registration_message.sender = str(cls._skill.skill_context.skill_id) cls.registration_message.to = cls._skill.skill_context.search_service_address cls.mocked_description = Description({"foo1": 1, "bar1": 2}) def test_setup_is_ledger_tx(self): """Test the setup method of the service_registration behaviour where is_ledger_tx is True.""" # setup self.strategy._is_ledger_tx = True mocked_description_1 = "some_description_1" # operation with patch.object( self.strategy, "get_location_description", return_value=mocked_description_1, ): with patch.object( self.service_registration.context.logger, "log" ) as mock_logger: self.service_registration.setup() # after self.assert_quantity_in_outbox(2) # message 1 has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_BALANCE, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), ledger_id=self.strategy.ledger_id, address=self.skill.skill_context.agent_address, ) assert has_attributes, error_str # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=mocked_description_1, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") def test_setup_not_is_ledger_tx(self): """Test the setup method of the service_registration behaviour: where is_ledger_tx is False.""" # setup self.strategy._is_ledger_tx = False mocked_description_1 = "some_description_1" # operation with patch.object( self.strategy, "get_location_description", return_value=mocked_description_1, ): with patch.object( self.service_registration.context.logger, "log" ) as mock_logger: self.service_registration.setup() # after self.assert_quantity_in_outbox(1) # _register_agent has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=mocked_description_1, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "registering agent on SOEF.") def test_act_i(self): """Test the act method of the service_registration behaviour where failed_registration_msg IS None.""" # setup self.service_registration.failed_registration_msg = None # operation assert self.service_registration.act() is None # after self.assert_quantity_in_outbox(0) def test_act_ii(self): """Test the act method of the service_registration behaviour where failed_registration_msg is NOT None.""" # setup self.service_registration.failed_registration_msg = self.registration_message # operation with patch.object( self.service_registration.context.logger, "log" ) as mock_logger: self.service_registration.act() # after self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=type(self.registration_message), performative=self.registration_message.performative, to=self.registration_message.to, sender=str(self.skill.skill_context.skill_id), service_description=self.registration_message.service_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, f"Retrying registration on SOEF. Retry {self.service_registration._nb_retries} out of {self.service_registration._max_soef_registration_retries}.", ) assert self.service_registration.failed_registration_msg is None def test_act_iii(self): """Test the act method of the service_registration behaviour where failed_registration_msg is NOT None and max retries is reached.""" # setup self.service_registration.failed_registration_msg = self.registration_message self.service_registration._max_soef_registration_retries = 2 self.service_registration._nb_retries = 2 # operation self.service_registration.act() # after self.assert_quantity_in_outbox(0) assert self.skill.skill_context.is_active is False def test_register_service(self): """Test the register_service method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_service_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.service_registration.register_service() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's service on the SOEF." ) def test_register_genus(self): """Test the register_genus method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_personality_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.service_registration.register_genus() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality genus on the SOEF." ) def test_register_classification(self): """Test the register_classification method of the service_registration behaviour.""" # operation with patch.object( self.strategy, "get_register_classification_description", return_value=self.mocked_description, ): with patch.object(self.logger, "log") as mock_logger: self.service_registration.register_classification() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=OefSearchMessage, performative=OefSearchMessage.Performative.REGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=self.mocked_description, ) assert has_attributes, error_str mock_logger.assert_any_call( logging.INFO, "registering agent's personality classification on the SOEF." ) def test_teardown(self): """Test the teardown method of the service_registration behaviour.""" # setup mocked_description_1 = "some_description_1" mocked_description_2 = "some_description_2" # operation with patch.object( self.strategy, "get_unregister_service_description", return_value=mocked_description_1, ): with patch.object( self.strategy, "get_location_description", return_value=mocked_description_2, ): with patch.object( self.service_registration.context.logger, "log" ) as mock_logger: self.service_registration.teardown() # after self.assert_quantity_in_outbox(2) # message 1 has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=mocked_description_1, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering service from SOEF.") # message 2 has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=OefSearchMessage, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, to=self.skill.skill_context.search_service_address, sender=str(self.skill.skill_context.skill_id), service_description=mocked_description_2, ) assert has_attributes, error_str mock_logger.assert_any_call(logging.INFO, "unregistering agent from SOEF.") ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_seller/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the generic seller skill.""" from pathlib import Path from typing import cast import pytest from aea.exceptions import AEAEnforceError from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueLabel from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_seller.dialogues import ( DefaultDialogue, DefaultDialogues, FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, ) from tests.conftest import ROOT_DIR class TestDialogues(BaseSkillTestCase): """Test dialogue classes of generic seller.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.oef_search_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=b"some_content", ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == self.skill.skill_context.agent_address def test_fipa_dialogue(self): """Test the FipaDialogue class.""" fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=DefaultDialogue.Role.AGENT, ) # terms with pytest.raises(AEAEnforceError, match="Terms not set!"): assert fipa_dialogue.terms terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) fipa_dialogue.terms = terms with pytest.raises(AEAEnforceError, match="Terms already set!"): fipa_dialogue.terms = terms assert fipa_dialogue.terms == terms def test_fipa_dialogues(self): """Test the FipaDialogues class.""" _, dialogue = self.fipa_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=FipaMessage.Performative.CFP, query="some_query", ) assert dialogue.role == FipaDialogue.Role.SELLER assert dialogue.self_address == self.skill.skill_context.agent_address def test_ledger_api_dialogue(self): """Test the LedgerApiDialogue class.""" ledger_api_dialogue = LedgerApiDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=LedgerApiDialogue.Role.AGENT, ) # associated_fipa_dialogue with pytest.raises(AEAEnforceError, match="FipaDialogue not set!"): assert ledger_api_dialogue.associated_fipa_dialogue fipa_dialogue = FipaDialogue( DialogueLabel( ("", ""), COUNTERPARTY_AGENT_ADDRESS, self.skill.skill_context.agent_address, ), self.skill.skill_context.agent_address, role=FipaDialogue.Role.BUYER, ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue with pytest.raises(AEAEnforceError, match="FipaDialogue already set!"): ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue assert ledger_api_dialogue.associated_fipa_dialogue == fipa_dialogue def test_ledger_api_dialogues(self): """Test the LedgerApiDialogues class.""" _, dialogue = self.ledger_api_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) assert dialogue.role == LedgerApiDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_oef_search_dialogues(self): """Test the OefSearchDialogues class.""" _, dialogue = self.oef_search_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query="some_query", ) assert dialogue.role == OefSearchDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_seller/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the generic seller skill.""" import logging from pathlib import Path from typing import cast from unittest.mock import patch import pytest import aea from aea.helpers.search.models import Attribute, DataModel, Description, Location from aea.helpers.transaction.base import Terms, TransactionDigest, TransactionReceipt from aea.protocols.dialogue.base import DialogueMessage, Dialogues from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.generic_seller.behaviours import ( GenericServiceRegistrationBehaviour, ) from packages.fetchai.skills.generic_seller.dialogues import ( FipaDialogue, FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, OefSearchDialogues, ) from packages.fetchai.skills.generic_seller.handlers import ( GenericFipaHandler, GenericLedgerApiHandler, GenericOefSearchHandler, LEDGER_API_ADDRESS, ) from packages.fetchai.skills.generic_seller.strategy import GenericStrategy from tests.conftest import ROOT_DIR class TestGenericFipaHandler(BaseSkillTestCase): """Test fipa handler of generic seller.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.fipa_handler = cast( GenericFipaHandler, cls._skill.skill_context.handlers.fipa ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.list_of_messages = ( DialogueMessage( FipaMessage.Performative.CFP, {"query": "some_query"}, True ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) def test_setup(self): """Test the setup method of the fipa handler.""" assert self.fipa_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the fipa handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=FipaMessage, dialogue_reference=incorrect_dialogue_reference, performative=FipaMessage.Performative.ACCEPT, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid fipa message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=self.skill.skill_context.agent_address, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"fipa_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_cfp_is_matching_supply(self): """Test the _handle_cfp method of the fipa handler where is_matching_supply is True.""" # setup proposal = Description( { "ledger_id": "some_ledger_id", "price": 100, "currency_id": "FET", "service_id": "some_service_id", "quantity": 1, "tx_nonce": "some_tx_nonce", } ) terms = "some_terms" data = {"data_type": "data"} incoming_message = self.build_incoming_message( message_type=FipaMessage, performative=FipaMessage.Performative.CFP, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), query="some_query", ) # operation with patch.object( self.strategy, "is_matching_supply", return_value=True, ): with patch.object( self.strategy, "generate_proposal_terms_and_data", return_value=(proposal, terms, data), ): with patch.object( self.fipa_handler.context.logger, "log" ) as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received CFP from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}" ) mock_logger.assert_any_call( logging.INFO, f"sending a PROPOSE with proposal={proposal.values} to sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.PROPOSE, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, proposal=proposal, ) assert has_attributes, error_str def test_handle_cfp_not_is_matching_supply(self): """Test the _handle_cfp method of the fipa handler where is_matching_supply is False.""" # setup incoming_message = self.build_incoming_message( message_type=FipaMessage, performative=FipaMessage.Performative.CFP, dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), query="some_query", ) # operation with patch.object(self.strategy, "is_matching_supply", return_value=False): with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received CFP from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}" ) mock_logger.assert_any_call( logging.INFO, f"declined the CFP from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.DECLINE, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, ) assert has_attributes, error_str def test_handle_decline(self): """Test the _handle_decline method of the fipa handler.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, ) # before for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): assert end_state_numbers == 0 for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): assert end_state_numbers == 0 # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received DECLINE from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): assert end_state_numbers == 0 for ( end_state, end_state_numbers, ) in self.fipa_dialogues.dialogue_stats.other_initiated.items(): if end_state == FipaDialogue.EndState.DECLINED_PROPOSE: assert end_state_numbers == 1 else: assert end_state_numbers == 0 def test_handle_accept(self): """Test the _handle_accept method of the fipa handler.""" # setup fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], ), ) fipa_dialogue.terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, ) info = {"address": fipa_dialogue.terms.sender_address} # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ACCEPT from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.INFO, f"sending MATCH_ACCEPT_W_INFORM to sender={COUNTERPARTY_AGENT_ADDRESS[-5:]} with info={info}", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, info=info, ) assert has_attributes, error_str def test_handle_inform_is_ledger_tx_and_with_tx_digest(self): """Test the _handle_inform method of the fipa handler where is_ledger_tx is True and info contains transaction_digest.""" # setup self.strategy._is_ledger_tx = True tx_digest = "some_transaction_digest_body" ledger_id = "some_ledger_id" fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], ) fipa_dialogue.terms = Terms( ledger_id, self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.INFORM, info={"transaction_digest": tx_digest}, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) incoming_message = cast(FipaMessage, incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.INFO, f"checking whether transaction={incoming_message.info['transaction_digest']} has been received ...", ) self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=LedgerApiMessage, performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, to=LEDGER_API_ADDRESS, sender=str(self.skill.skill_context.skill_id), target=0, transaction_digest=TransactionDigest(ledger_id, tx_digest), ) assert has_attributes, error_str def test_handle_inform_is_ledger_tx_and_no_tx_digest(self): """Test the _handle_inform method of the fipa handler where is_ledger_tx is True and info does not have a transaction_digest.""" # setup self.strategy._is_ledger_tx = True fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], ) fipa_dialogue.terms = Terms( "some_ledger_id", self.skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.INFORM, info={}, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.WARNING, f"did not receive transaction digest from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}.", ) def test_handle_inform_not_is_ledger_tx_and_with_done(self): """Test the _handle_inform method of the fipa handler where is_ledger_tx is False and info contains done.""" # setup self.strategy._is_ledger_tx = False data = { "data_type_1": "data_1", "data_type_2": "data_2", } fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], ), ) fipa_dialogue.data_for_sale = data incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.INFORM, info={"Done": "Sending payment via bank transfer"}, ) # before for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): assert end_state_numbers == 0 for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): assert end_state_numbers == 0 # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) # check outgoing message self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.INFORM, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=incoming_message.message_id, info=fipa_dialogue.data_for_sale, ) assert has_attributes, error_str # check updated end_state for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): assert end_state_numbers == 0 for ( end_state, end_state_numbers, ) in self.fipa_dialogues.dialogue_stats.other_initiated.items(): if end_state == FipaDialogue.EndState.SUCCESSFUL: assert end_state_numbers == 1 else: assert end_state_numbers == 0 # check logger output mock_logger.assert_any_call( logging.INFO, f"transaction confirmed, sending data={data} to buyer={COUNTERPARTY_AGENT_ADDRESS[-5:]}.", ) def test_handle_inform_not_is_ledger_tx_and_nothin_in_info(self): """Test the _handle_inform method of the fipa handler where is_ledger_tx is False and info does not contain done or transaction_digest.""" # setup self.strategy._is_ledger_tx = False fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.INFORM, info={}, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received INFORM from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}", ) mock_logger.assert_any_call( logging.WARNING, f"did not receive transaction confirmation from sender={COUNTERPARTY_AGENT_ADDRESS[-5:]}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the fipa handler.""" # setup fipa_dialogue = self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT_W_INFORM, info={}, ) # operation with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: self.fipa_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle fipa message of performative={incoming_message.performative} in dialogue={fipa_dialogue}.", ) def test_teardown(self): """Test the teardown method of the fipa handler.""" assert self.fipa_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestGenericLedgerApiHandler(BaseSkillTestCase): """Test ledger_api handler of generic seller.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_api_handler = cast( GenericLedgerApiHandler, cls._skill.skill_context.handlers.ledger_api ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.fipa_dialogues = cast( FipaDialogues, cls._skill.skill_context.fipa_dialogues ) cls.ledger_api_dialogues = cast( LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues ) cls.terms = Terms( "some_ledger_id", cls._skill.skill_context.agent_address, "counterprty", {"currency_id": 50}, {"good_id": -10}, "some_nonce", ) cls.list_of_fipa_messages = ( DialogueMessage( FipaMessage.Performative.CFP, {"query": "some_query"}, True ), DialogueMessage( FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} ), DialogueMessage(FipaMessage.Performative.ACCEPT), DialogueMessage( FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": {"address": "some_term_sender_address"}}, ), DialogueMessage( FipaMessage.Performative.INFORM, {"info": {"transaction_digest": "some_transaction_digest_body"}}, ), ) cls.transaction_digest = TransactionDigest("some_ledger_id", "some_body") cls.transaction_receipt = TransactionReceipt( "some_ledger_id", {"some_key": "some_value"}, {"some_key": "some_value"} ) cls.list_of_ledger_api_messages = ( DialogueMessage( LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, {"transaction_digest": cls.transaction_digest}, ), DialogueMessage( LedgerApiMessage.Performative.TRANSACTION_RECEIPT, {"transaction_receipt": cls.transaction_receipt}, ), ) def test_setup(self): """Test the setup method of the ledger_api handler.""" assert self.ledger_api_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=incorrect_dialogue_reference, performative=LedgerApiMessage.Performative.GET_BALANCE, ledger_id="some_ledger_id", address="some_address", ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", ) def test_handle_balance(self): """Test the _handle_balance method of the ledger_api handler.""" # setup ledger_id = "some_Ledger_id" ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=( DialogueMessage( LedgerApiMessage.Performative.GET_BALANCE, {"ledger_id": "some_ledger_id", "address": "some_address"}, ), ), counterparty=LEDGER_API_ADDRESS, ), ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.BALANCE, ledger_id=ledger_id, balance=10, ), ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"starting balance on {ledger_id} ledger={incoming_message.balance}.", ) def test_handle_transaction_receipt_is_settled_and_is_valid_last_incoming_fipa_message_is_none( self, ): """Test the _handle_transaction_receipt method of the ledger_api handler where is_settled and is_valid are True and the last incoming FipaMessage is None.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], counterparty=LEDGER_API_ADDRESS, ), ) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:5], is_agent_to_agent_messages=True, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue fipa_dialogue.terms = self.terms fipa_dialogue.data_for_sale = {"data_type_1": "data_1"} fipa_dialogue._incoming_messages = [] incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( aea.crypto.ledger_apis.LedgerApis, "is_transaction_settled", return_value=True, ): with patch.object( aea.crypto.ledger_apis.LedgerApis, "is_transaction_valid", return_value=True, ): with pytest.raises( ValueError, match="Cannot retrieve last fipa message." ): self.ledger_api_handler.handle(incoming_message) def test_handle_transaction_receipt_is_settled_and_is_valid(self): """Test the _handle_transaction_receipt method of the ledger_api handler where is_settled and is_valid are True.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], counterparty=LEDGER_API_ADDRESS, ), ) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:5], is_agent_to_agent_messages=True, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue fipa_dialogue.terms = self.terms fipa_dialogue.data_for_sale = {"data_type_1": "data_1"} incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # before for end_state_numbers in list( self.fipa_dialogues.dialogue_stats.self_initiated.values() ) + list(self.fipa_dialogues.dialogue_stats.other_initiated.values()): assert end_state_numbers == 0 # operation with patch.object( aea.crypto.ledger_apis.LedgerApis, "is_transaction_settled", return_value=True, ): with patch.object( aea.crypto.ledger_apis.LedgerApis, "is_transaction_valid", return_value=True, ): with patch.object( self.ledger_api_handler.context.logger, "log" ) as mock_logger: self.ledger_api_handler.handle(incoming_message) # after # check outgoing message self.assert_quantity_in_outbox(1) has_attributes, error_str = self.message_has_attributes( actual_message=self.get_message_from_outbox(), message_type=FipaMessage, performative=FipaMessage.Performative.INFORM, to=COUNTERPARTY_AGENT_ADDRESS, sender=self.skill.skill_context.agent_address, target=fipa_dialogue.last_incoming_message.message_id, info=fipa_dialogue.data_for_sale, ) assert has_attributes, error_str # check updated end_state for ( end_state_numbers ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): assert end_state_numbers == 0 for ( end_state, end_state_numbers, ) in self.fipa_dialogues.dialogue_stats.other_initiated.items(): if end_state == FipaDialogue.EndState.SUCCESSFUL: assert end_state_numbers == 1 else: assert end_state_numbers == 0 # check logger output mock_logger.assert_any_call( logging.INFO, f"transaction confirmed, sending data={fipa_dialogue.data_for_sale} to buyer={COUNTERPARTY_AGENT_ADDRESS[-5:]}.", ) def test_handle_transaction_receipt_not_is_settled_or_not_is_valid( self, ): """Test the _handle_transaction_receipt method of the ledger_api handler where is_settled or is_valid is False.""" # setup ledger_api_dialogue = cast( LedgerApiDialogue, self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], counterparty=LEDGER_API_ADDRESS, ), ) fipa_dialogue = cast( FipaDialogue, self.prepare_skill_dialogue( dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:5], is_agent_to_agent_messages=True, ), ) ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue fipa_dialogue.terms = self.terms incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, transaction_receipt=self.transaction_receipt, ), ) # operation with patch.object( aea.crypto.ledger_apis.LedgerApis, "is_transaction_settled", return_value=True, ): with patch.object( aea.crypto.ledger_apis.LedgerApis, "is_transaction_valid", return_value=False, ): with patch.object( self.ledger_api_handler.context.logger, "log" ) as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"transaction_receipt={self.transaction_receipt} not settled or not valid, aborting", ) def test_handle_error(self): """Test the _handle_error method of the ledger_api handler.""" # setup ledger_api_dialogue = self.prepare_skill_dialogue( dialogues=self.ledger_api_dialogues, messages=self.list_of_ledger_api_messages[:1], ) incoming_message = cast( LedgerApiMessage, self.build_incoming_message_for_skill_dialogue( dialogue=ledger_api_dialogue, performative=LedgerApiMessage.Performative.ERROR, code=1, ), ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the ledger_api handler.""" # setup invalid_performative = LedgerApiMessage.Performative.GET_BALANCE incoming_message = self.build_incoming_message( message_type=LedgerApiMessage, dialogue_reference=("1", ""), performative=invalid_performative, ledger_id="some_ledger_id", address="some_address", to=str(self.skill.skill_context.skill_id), ) # operation with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: self.ledger_api_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the ledger_api handler.""" assert self.ledger_api_handler.teardown() is None self.assert_quantity_in_outbox(0) class TestGenericOefSearchHandler(BaseSkillTestCase): """Test oef search handler of generic seller.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") is_agent_to_agent_messages = False @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.oef_search_handler = cast( GenericOefSearchHandler, cls._skill.skill_context.handlers.oef_search ) cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) cls.oef_dialogues = cast( OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues ) cls.service_registration_behaviour = cast( GenericServiceRegistrationBehaviour, cls._skill.skill_context.behaviours.service_registration, ) cls.register_location_description = Description( {"location": Location(51.5194, 0.1270)}, data_model=DataModel( "location_agent", [Attribute("location", Location, True)] ), ) cls.list_of_messages_register_location = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_location_description}, is_incoming=False, ), ) cls.register_service_description = Description( {"key": "some_key", "value": "some_value"}, data_model=DataModel( "set_service_key", [Attribute("key", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_service = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_service_description}, is_incoming=False, ), ) cls.register_genus_description = Description( {"piece": "genus", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_genus = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_genus_description}, is_incoming=False, ), ) cls.register_classification_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "personality_agent", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_classification = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_classification_description}, is_incoming=False, ), ) cls.register_invalid_description = Description( {"piece": "classification", "value": "some_value"}, data_model=DataModel( "some_different_name", [Attribute("piece", str, True), Attribute("value", str, True)], ), ) cls.list_of_messages_register_invalid = ( DialogueMessage( OefSearchMessage.Performative.REGISTER_SERVICE, {"service_description": cls.register_invalid_description}, is_incoming=False, ), ) cls.unregister_description = Description( {"key": "seller_service"}, data_model=DataModel("remove", [Attribute("key", str, True)]), ) cls.list_of_messages_unregister = ( DialogueMessage( OefSearchMessage.Performative.UNREGISTER_SERVICE, {"service_description": cls.unregister_description}, is_incoming=False, ), ) def test_setup(self): """Test the setup method of the oef_search handler.""" assert self.oef_search_handler.setup() is None self.assert_quantity_in_outbox(0) def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the oef_search handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=incorrect_dialogue_reference, performative=OefSearchMessage.Performative.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid oef_search message={incoming_message}, unidentified dialogue.", ) def test_handle_success_i(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH location_agent data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_service", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_ii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH set_service_key data model description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_service[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_genus", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iii(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and genus value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_genus[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: with patch.object( self.service_registration_behaviour, "register_classification", ) as mock_reg: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_reg.assert_called_once() def test_handle_success_iv(self): """Test the _handle_success method of the oef_search handler where the oef success targets register_service WITH personality_agent data model and classification value description.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_classification[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.INFO, "the agent, with its genus and classification, and its service are successfully registered on the SOEF.", ) def test_handle_success_v(self): """Test the _handle_success method of the oef_search handler where the oef success targets unregister_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_invalid[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.SUCCESS, agents_info=OefSearchMessage.AgentsInfo({"address": {"key": "value"}}), ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received oef_search success message={incoming_message} in dialogue={oef_dialogue}.", ) mock_logger.assert_any_call( logging.WARNING, f"received soef SUCCESS message as a reply to the following unexpected message: {oef_dialogue.get_message_by_id(incoming_message.target)}", ) def test_handle_error_i(self): """Test the _handle_error method of the oef_search handler where the oef error targets register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_register_location[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert ( self.service_registration_behaviour.failed_registration_msg == oef_dialogue.get_message_by_id(incoming_message.target) ) def test_handle_error_ii(self): """Test the _handle_error method of the oef_search handler where the oef error does NOT target register_service.""" # setup oef_dialogue = self.prepare_skill_dialogue( dialogues=self.oef_dialogues, messages=self.list_of_messages_unregister[:1], ) incoming_message = self.build_incoming_message_for_skill_dialogue( dialogue=oef_dialogue, performative=OefSearchMessage.Performative.OEF_ERROR, oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", ) assert self.service_registration_behaviour.failed_registration_msg is None def test_handle_invalid(self): """Test the _handle_invalid method of the oef_search handler.""" # setup invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE incoming_message = self.build_incoming_message( message_type=OefSearchMessage, dialogue_reference=("1", ""), performative=invalid_performative, service_description="some_service_description", ) # operation with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: self.oef_search_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", ) def test_teardown(self): """Test the teardown method of the oef_search handler.""" assert self.oef_search_handler.teardown() is None self.assert_quantity_in_outbox(0) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_generic_seller/test_models.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the generic seller skill.""" from pathlib import Path import pytest from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.ledger_apis import LedgerApis from aea.helpers.search.models import ( Constraint, ConstraintType, Description, Location, Query, ) from aea.helpers.transaction.base import Terms from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.skills.generic_seller.strategy import ( AGENT_LOCATION_MODEL, AGENT_PERSONALITY_MODEL, AGENT_REMOVE_SERVICE_MODEL, AGENT_SET_SERVICE_MODEL, GenericStrategy, SIMPLE_SERVICE_MODEL, ) from tests.conftest import ROOT_DIR class TestGenericStrategy(BaseSkillTestCase): """Test GenericStrategy of generic seller.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.ledger_id = DEFAULT_LEDGER cls.is_ledger_tx = True cls.currency_id = "some_currency_id" cls.unit_price = 20 cls.service_id = "some_service_id" cls.location = { "longitude": 0.127, "latitude": 51.5194, } cls.service_data = {"key": "seller_service", "value": "some_service"} cls.has_data_source = False cls.data_for_sale = {"some_data_type": "some_data"} cls.strategy = GenericStrategy( ledger_id=cls.ledger_id, is_ledger_tx=cls.is_ledger_tx, currency_id=cls.currency_id, unit_price=cls.unit_price, service_id=cls.service_id, location=cls.location, service_data=cls.service_data, has_data_source=cls.has_data_source, data_for_sale=cls.data_for_sale, name="strategy", skill_context=cls._skill.skill_context, ) def test_properties(self): """Test the properties of GenericStrategy class.""" assert self.strategy.ledger_id == self.ledger_id assert self.strategy.is_ledger_tx == self.is_ledger_tx def test_get_location_description(self): """Test the get_location_description method of the GenericStrategy class.""" description = self.strategy.get_location_description() assert type(description) == Description assert description.data_model is AGENT_LOCATION_MODEL assert description.values.get("location", "") == Location( latitude=self.location["latitude"], longitude=self.location["longitude"] ) def test_get_register_service_description(self): """Test the get_register_service_description method of the GenericStrategy class.""" description = self.strategy.get_register_service_description() assert type(description) == Description assert description.data_model is AGENT_SET_SERVICE_MODEL assert description.values.get("key", "") == "seller_service" assert description.values.get("value", "") == "some_service" def test_get_register_personality_description(self): """Test the get_register_personality_description method of the GenericStrategy class.""" description = self.strategy.get_register_personality_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "genus" assert description.values.get("value", "") == "data" def test_get_register_classification_description(self): """Test the get_register_classification_description method of the GenericStrategy class.""" description = self.strategy.get_register_classification_description() assert type(description) == Description assert description.data_model is AGENT_PERSONALITY_MODEL assert description.values.get("piece", "") == "classification" assert description.values.get("value", "") == "seller" def test_get_service_description(self): """Test the get_service_description method of the GenericStrategy class.""" description = self.strategy.get_service_description() assert type(description) == Description assert description.data_model is SIMPLE_SERVICE_MODEL assert description.values.get("seller_service", "") == "some_service" def test_get_unregister_service_description(self): """Test the get_unregister_service_description method of the GenericStrategy class.""" description = self.strategy.get_unregister_service_description() assert type(description) == Description assert description.data_model is AGENT_REMOVE_SERVICE_MODEL assert description.values.get("key", "") == "seller_service" def test_is_matching_supply(self): """Test the is_matching_supply method of the GenericStrategy class.""" acceptable_constraint = Constraint( "seller_service", ConstraintType("==", "some_service") ) matching_query = Query([acceptable_constraint]) is_matching_supply = self.strategy.is_matching_supply(matching_query) assert is_matching_supply unacceptable_constraint = Constraint( "seller_service", ConstraintType("==", "some_other_service") ) unmatching_query = Query([unacceptable_constraint]) is_matching_supply = self.strategy.is_matching_supply(unmatching_query) assert not is_matching_supply def test_generate_proposal_terms_and_data(self): """Test the generate_proposal_terms_and_data method of the GenericStrategy class.""" # setup seller = self.skill.skill_context.agent_address total_price = len(self.data_for_sale) * self.unit_price sale_quantity = len(self.data_for_sale) tx_nonce = LedgerApis.generate_tx_nonce( identifier=self.ledger_id, seller=seller, client=COUNTERPARTY_AGENT_ADDRESS, ) query = Query( [Constraint("seller_service", ConstraintType("==", "some_service"))] ) # expected returned values expected_proposal = Description( { "ledger_id": self.ledger_id, "price": total_price, "currency_id": self.currency_id, "service_id": self.service_id, "quantity": sale_quantity, "tx_nonce": tx_nonce, } ) expected_terms = Terms( ledger_id=self.ledger_id, sender_address=seller, counterparty_address=COUNTERPARTY_AGENT_ADDRESS, amount_by_currency_id={self.currency_id: total_price}, quantities_by_good_id={self.service_id: -sale_quantity}, is_sender_payable_tx_fee=False, nonce=tx_nonce, fee_by_currency_id={self.currency_id: 0}, ) # operation proposal, terms, data = self.strategy.generate_proposal_terms_and_data( query, COUNTERPARTY_AGENT_ADDRESS ) # after assert proposal == expected_proposal assert terms == expected_terms assert data == self.data_for_sale def test_collect_from_data_source(self): """Test the collect_from_data_source method of the GenericStrategy class.""" with pytest.raises(NotImplementedError): self.strategy.collect_from_data_source() ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/gym dir.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the ml_train skill.""" from typing import Tuple import numpy as np def produce_data(batch_size) -> Tuple: """Produce the data.""" from tensorflow import keras # pylint: disable=import-outside-toplevel ((train_x, train_y), _) = keras.datasets.fashion_mnist.load_data() idx = np.arange(train_x.shape[0]) mask = np.zeros_like(idx, dtype=bool) selected = np.random.choice(idx, batch_size, replace=False) mask[selected] = True x_sample = train_x[mask] y_sample = train_y[mask] return x_sample, y_sample ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/intermediate_class.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module sets up test environment for gym skill.""" from pathlib import Path from typing import cast from aea.protocols.dialogue.base import DialogueLabel, DialogueMessage from aea.skills.tasks import TaskManager from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.protocols.gym.custom_types import AnyObject from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.skills.gym.dialogues import ( DefaultDialogues, GymDialogue, GymDialogues, ) from packages.fetchai.skills.gym.handlers import GymHandler from packages.fetchai.skills.gym.helpers import ProxyEnv from packages.fetchai.skills.gym.rl_agent import GoodPriceModel, MyRLAgent, PriceBandit from packages.fetchai.skills.gym.tasks import GymTask from tests.conftest import ROOT_DIR class GymTestCase(BaseSkillTestCase): """Sets the gym class up for testing.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "gym") @classmethod def setup(cls): """Setup the test class.""" cls.nb_steps = 4000 config_overrides = { "handlers": {"gym": {"args": {"nb_steps": cls.nb_steps}}}, } super().setup(config_overrides=config_overrides) # dialogues cls.default_dialogues = cast( DefaultDialogues, cls._skill.skill_context.default_dialogues ) cls.gym_dialogues = cast(GymDialogues, cls._skill.skill_context.gym_dialogues) # handlers cls.gym_handler = cast(GymHandler, cls._skill.skill_context.handlers.gym) # models cls.task = GymTask( skill_context=cls._skill.skill_context, nb_steps=cls.nb_steps, ) cls.task_manager = cast(TaskManager, cls._skill.skill_context.task_manager) cls.task_manager.start() cls.logger = cls._skill.skill_context.logger # mocked objects cls.dict_str_str = {"some_key": "some_value"} cls.mocked_task_id = 1 cls.content_bytes = b"some_contents" cls.mocked_status_content = {"reset": "success"} cls.mocked_action = AnyObject("some_action") cls.mocked_observation = AnyObject("some_observation") cls.mocked_info = AnyObject("some_info") cls.mocked_step_id = 123 cls.mocked_reward = 3242.23423 cls.mocked_price = 765.23 cls.mocked_beta_a = 2.876 cls.mocked_beta_b = 0.8 cls.mocked_bound = 78 cls.mocked_nb_goods = 10 cls.dummy_gym_dialogue = GymDialogue( DialogueLabel( ("", ""), "some_counterparty_address", cls._skill.skill_context.agent_address, ), cls._skill.skill_context.agent_address, role=GymDialogue.Role.AGENT, ) cls.price_bandit = PriceBandit( cls.mocked_price, cls.mocked_beta_a, cls.mocked_beta_b ) cls.good_price_model = GoodPriceModel(cls.mocked_bound) cls.my_rl_agent = MyRLAgent(cls.mocked_nb_goods, cls.logger) cls.proxy_env = ProxyEnv(cls._skill.skill_context) # list of messages cls.list_of_gym_messages = ( DialogueMessage(GymMessage.Performative.RESET, {}), DialogueMessage( GymMessage.Performative.STATUS, {"content": cls.mocked_status_content} ), DialogueMessage( GymMessage.Performative.ACT, {"action": cls.mocked_action, "step_id": cls.mocked_step_id}, ), DialogueMessage( GymMessage.Performative.PERCEPT, { "step_id": cls.mocked_step_id, "observation": cls.mocked_observation, "reward": cls.mocked_reward, "done": True, "info": cls.mocked_info, }, ), DialogueMessage(GymMessage.Performative.CLOSE, {}), ) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/test_dialogues.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dialogue classes of the ml_train skill.""" from aea.test_tools.test_skill import COUNTERPARTY_AGENT_ADDRESS from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.skills.gym.dialogues import DefaultDialogue, GymDialogue from tests.test_packages_for_aea_tests.test_skills.test_gym.intermediate_class import ( GymTestCase, ) class TestDialogues(GymTestCase): """Test dialogue classes of gym.""" def test_default_dialogues(self): """Test the DefaultDialogues class.""" _, dialogue = self.default_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=DefaultMessage.Performative.BYTES, content=self.content_bytes, ) assert dialogue.role == DefaultDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) def test_gym_dialogues(self): """Test the GymDialogues class.""" _, dialogue = self.gym_dialogues.create( counterparty=COUNTERPARTY_AGENT_ADDRESS, performative=GymMessage.Performative.RESET, ) assert dialogue.role == GymDialogue.Role.AGENT assert dialogue.self_address == str(self.skill.skill_context.skill_id) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/test_handlers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the handler classes of the gym skill.""" import logging from multiprocessing.pool import ApplyResult from typing import cast from unittest.mock import Mock, patch from packages.fetchai.protocols.default.message import DefaultMessage from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.skills.gym.dialogues import GymDialogue from tests.test_packages_for_aea_tests.test_skills.test_gym.intermediate_class import ( GymTestCase, ) class TestGymHandler(GymTestCase): """Test Gym handler of gym.""" is_agent_to_agent_messages = False def test__init__(self): """Test the __init__ method of the gym handler.""" assert self.gym_handler._task_id is None def test_setup(self): """Test the setup method of the gym handler.""" # operation with patch.object( self.task_manager, "enqueue_task", return_value=self.mocked_task_id ) as mocked_enqueue_task: with patch.object(self.logger, "log") as mock_logger: assert self.gym_handler.setup() is None # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, "Gym handler: setup method called.", ) mocked_enqueue_task.assert_any_call(self.gym_handler.task) assert self.gym_handler._task_id == self.mocked_task_id def test_handle_unidentified_dialogue(self): """Test the _handle_unidentified_dialogue method of the gym handler.""" # setup incorrect_dialogue_reference = ("", "") incoming_message = self.build_incoming_message( message_type=GymMessage, dialogue_reference=incorrect_dialogue_reference, performative=GymMessage.Performative.RESET, ) # operation with patch.object(self.logger, "log") as mock_logger: self.gym_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.INFO, f"received invalid gym message={incoming_message}, unidentified dialogue.", ) self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=DefaultMessage, performative=DefaultMessage.Performative.ERROR, to=incoming_message.sender, sender=str(self.skill.skill_context.skill_id), error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"gym_message": incoming_message.encode()}, ) assert has_attributes, error_str def test_handle_percept_i(self): """Test the _handle_percept method of the gym handler where active_gym_dialogue==gym_dialogeu.""" # setup gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:3], ), ) incoming_message = cast( GymMessage, self.build_incoming_message_for_skill_dialogue( dialogue=gym_dialogue, performative=GymMessage.Performative.PERCEPT, step_id=self.mocked_step_id, observation=self.mocked_observation, reward=self.mocked_reward, done=True, info=self.mocked_info, ), ) self.gym_handler.task.proxy_env._active_dialogue = gym_dialogue # operation with patch.object( self.gym_handler.task.proxy_env_queue, "put", ) as mocked_put: self.gym_handler.handle(incoming_message) # after mocked_put.assert_any_call(incoming_message) def test_handle_percept_ii(self): """Test the _handle_percept method of the gym handler where active_gym_dialogue!=gym_dialogeu.""" # setup gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:3], ), ) incoming_message = cast( GymMessage, self.build_incoming_message_for_skill_dialogue( dialogue=gym_dialogue, performative=GymMessage.Performative.PERCEPT, step_id=self.mocked_step_id, observation=self.mocked_observation, reward=self.mocked_reward, done=True, info=self.mocked_info, ), ) gym_dialogue_ii = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:1], ), ) self.gym_handler.task.proxy_env._active_dialogue = gym_dialogue_ii # operation with patch.object(self.logger, "log") as mock_logger: self.gym_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, "gym dialogue not active dialogue.", ) def test_handle_status_i(self): """Test the _handle_status method of the gym handler where active_gym_dialogue==gym_dialogeu and reset == success.""" # setup gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:1], ), ) incoming_message = cast( GymMessage, self.build_incoming_message_for_skill_dialogue( dialogue=gym_dialogue, performative=GymMessage.Performative.STATUS, content=self.mocked_status_content, ), ) self.gym_handler.task.proxy_env._active_dialogue = gym_dialogue # operation with patch.object( self.gym_handler.task.proxy_env_queue, "put", ) as mocked_put: self.gym_handler.handle(incoming_message) # after mocked_put.assert_any_call(incoming_message) def test_handle_status_ii(self): """Test the _handle_status method of the gym handler where active_gym_dialogue==gym_dialogeu and reset == success.""" # setup gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:1], ), ) incoming_message = cast( GymMessage, self.build_incoming_message_for_skill_dialogue( dialogue=gym_dialogue, performative=GymMessage.Performative.STATUS, content={"reset": "failure"}, ), ) gym_dialogue_ii = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:1], ), ) self.gym_handler.task.proxy_env._active_dialogue = gym_dialogue_ii # operation with patch.object(self.logger, "log") as mock_logger: self.gym_handler.handle(incoming_message) # after mock_logger.assert_any_call( logging.WARNING, "gym dialogue not active dialogue.", ) def test_handle_invalid(self): """Test the _handle_invalid method of the gym handler.""" # setup incoming_message = self.build_incoming_message( message_type=GymMessage, performative=GymMessage.Performative.RESET, ) # operation with patch.object(self.logger, "log") as mock_logger: self.gym_handler.handle(incoming_message) # after gym_dialogue = cast( GymDialogue, self.gym_dialogues.get_dialogue(incoming_message) ) mock_logger.assert_any_call( logging.WARNING, f"cannot handle gym message of performative={incoming_message.performative} in dialogue={gym_dialogue}.", ) def test_teardown(self): """Test the teardown method of the gym handler.""" # setup self.gym_handler._task_id = self.mocked_task_id mock_task_result = Mock(wraps=ApplyResult) mock_task_result.ready.return_value = True mock_task_result.successful.return_value = False # operation with patch.object( self.gym_handler.task, "teardown" ) as mocked_gym_task_teardown: with patch.object( self.task_manager, "get_task_result", return_value=mock_task_result ) as mocked_get_result: with patch.object(self.logger, "log") as mock_logger: assert self.gym_handler.teardown() is None # after self.assert_quantity_in_outbox(0) mock_logger.assert_any_call( logging.INFO, "Gym handler: teardown method called.", ) mocked_gym_task_teardown.assert_called_once() mocked_get_result.assert_any_call(self.mocked_task_id) mock_logger.assert_any_call( logging.WARNING, "Task not successful!", ) ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/test_helpers.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the helpers module of the gym skill.""" from typing import cast from unittest.mock import patch import pytest from packages.fetchai.protocols.gym.message import GymMessage from packages.fetchai.skills.gym.dialogues import GymDialogue from tests.test_packages_for_aea_tests.test_skills.test_gym.intermediate_class import ( GymTestCase, ) class TestProxyEnv(GymTestCase): """Test ProxyEnv of gym.""" is_agent_to_agent_messages = False def test__init__(self): """Test the __init__ method of the ProxyEnv class.""" assert self.proxy_env._is_rl_agent_trained is False assert self.proxy_env._step_count == 0 assert self.proxy_env._active_dialogue is None def test_properties(self): """Test the properties of the ProxyEnv class.""" assert self.proxy_env.gym_dialogues == self.gym_dialogues with pytest.raises(ValueError, match="GymDialogue not set yet."): assert self.proxy_env.active_gym_dialogue self.proxy_env._active_dialogue = self.dummy_gym_dialogue assert self.proxy_env.active_gym_dialogue == self.dummy_gym_dialogue assert self.proxy_env.queue == self.proxy_env._queue assert self.proxy_env.is_rl_agent_trained is False def test_step_i(self): """Test the step method of the ProxyEnv class where no exception.""" # setup action = "some_action" gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:2], ), ) self.proxy_env._active_dialogue = gym_dialogue percept_msg = self.build_incoming_message( message_type=GymMessage, performative=GymMessage.Performative.PERCEPT, step_id=self.proxy_env._step_count + 1, observation=self.mocked_observation, reward=self.mocked_reward, done=True, info=self.mocked_info, ) # operation with patch.object( self.proxy_env._queue, "get", return_value=percept_msg ) as mocked_q_get: ( actual_observation, actual_reward, actual_done, actual_info, ) = self.proxy_env.step(action) # after # _encode_and_send_action self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=GymMessage, performative=GymMessage.Performative.ACT, to=gym_dialogue.dialogue_label.dialogue_opponent_addr, sender=str(self.skill.skill_context.skill_id), action=GymMessage.AnyObject(action), step_id=self.proxy_env._step_count, ) assert has_attributes, error_str mocked_q_get.assert_called_with(block=True, timeout=None) assert actual_observation == self.mocked_observation.any assert actual_reward == self.mocked_reward assert actual_done is True assert actual_info == self.mocked_info.any def test_step_ii(self): """Test the step method of the ProxyEnv class where performative is NOT percept.""" # setup action = "some_action" gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:2], ), ) self.proxy_env._active_dialogue = gym_dialogue invalid_percept_msg = self.build_incoming_message( message_type=GymMessage, performative=GymMessage.Performative.RESET, ) # operation with patch.object( self.proxy_env._queue, "get", return_value=invalid_percept_msg ) as mocked_q_get: with pytest.raises( ValueError, match=f"Unexpected performative. Expected={GymMessage.Performative.PERCEPT} got={invalid_percept_msg.performative}", ): self.proxy_env.step(action) # # after # _encode_and_send_action self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=GymMessage, performative=GymMessage.Performative.ACT, to=gym_dialogue.dialogue_label.dialogue_opponent_addr, sender=str(self.skill.skill_context.skill_id), action=GymMessage.AnyObject(action), step_id=self.proxy_env._step_count, ) assert has_attributes, error_str mocked_q_get.assert_called_with(block=True, timeout=None) def test_step_iii(self): """Test the step method of the ProxyEnv class where performative is NOT percept.""" # setup action = "some_action" gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:2], ), ) self.proxy_env._active_dialogue = gym_dialogue invalid_percept_msg = self.build_incoming_message( message_type=GymMessage, performative=GymMessage.Performative.PERCEPT, step_id=self.proxy_env._step_count, observation=self.mocked_observation, reward=self.mocked_reward, done=True, info=self.mocked_info, ) # operation with patch.object( self.proxy_env._queue, "get", return_value=invalid_percept_msg ) as mocked_q_get: with pytest.raises( ValueError, match=f"Unexpected step id! expected={self.proxy_env._step_count+1}, actual={self.proxy_env._step_count}", ): self.proxy_env.step(action) # # after # _encode_and_send_action self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=GymMessage, performative=GymMessage.Performative.ACT, to=gym_dialogue.dialogue_label.dialogue_opponent_addr, sender=str(self.skill.skill_context.skill_id), action=GymMessage.AnyObject(action), step_id=self.proxy_env._step_count, ) assert has_attributes, error_str mocked_q_get.assert_called_with(block=True, timeout=None) def test_reset_i(self): """Test the reset method of the ProxyEnv class where no exception.""" # setup status_msg = self.build_incoming_message( message_type=GymMessage, performative=GymMessage.Performative.STATUS, content=self.mocked_status_content, ) # operation with patch.object( self.proxy_env._queue, "get", return_value=status_msg ) as mocked_q_get: self.proxy_env.reset() # after assert self.proxy_env._step_count == 0 assert self.proxy_env._is_rl_agent_trained is False assert self.proxy_env._active_dialogue is not None self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=GymMessage, performative=GymMessage.Performative.RESET, to=self.proxy_env.gym_address, sender=str(self.skill.skill_context.skill_id), ) assert has_attributes, error_str mocked_q_get.assert_called_with(block=True, timeout=None) def test_reset_ii(self): """Test the reset method of the ProxyEnv class where performative is NOT status.""" # setup invalid_msg = self.build_incoming_message( message_type=GymMessage, performative=GymMessage.Performative.RESET, ) # operation with patch.object( self.proxy_env._queue, "get", return_value=invalid_msg ) as mocked_q_get: with pytest.raises( ValueError, match=f"Unexpected performative. Expected={GymMessage.Performative.STATUS} got={invalid_msg.performative}", ): self.proxy_env.reset() # after assert self.proxy_env._step_count == 0 assert self.proxy_env._is_rl_agent_trained is False assert self.proxy_env._active_dialogue is not None self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=GymMessage, performative=GymMessage.Performative.RESET, to=self.proxy_env.gym_address, sender=str(self.skill.skill_context.skill_id), ) assert has_attributes, error_str mocked_q_get.assert_called_with(block=True, timeout=None) def test_close_i(self): """Test the close method of the ProxyEnv class.""" # setup self.proxy_env._is_rl_agent_trained = True gym_dialogue = cast( GymDialogue, self.prepare_skill_dialogue( dialogues=self.gym_dialogues, messages=self.list_of_gym_messages[:4], ), ) self.proxy_env._active_dialogue = gym_dialogue # operation self.proxy_env.close() # after self.assert_quantity_in_outbox(1) message = self.get_message_from_outbox() has_attributes, error_str = self.message_has_attributes( actual_message=message, message_type=GymMessage, performative=GymMessage.Performative.CLOSE, to=gym_dialogue.dialogue_label.dialogue_opponent_addr, sender=str(self.skill.skill_context.skill_id), ) assert has_attributes, error_str ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/test_rl_agent.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests for the rl_agent module of the gym skill.""" import copy from unittest.mock import patch from packages.fetchai.skills.gym.helpers import ProxyEnv from packages.fetchai.skills.gym.rl_agent import GoodPriceModel from tests.test_packages_for_aea_tests.test_skills.test_gym.intermediate_class import ( GymTestCase, ) class TestPriceBandit(GymTestCase): """Test PriceBandit of gym.""" def test_sample(self): """Test the sample method of PriceBandit class.""" sample = self.price_bandit.sample() assert type(sample) == int def test_update(self): """Test the update method of the PriceBandit class.""" # before assert self.price_bandit.beta_a == self.mocked_beta_a assert self.price_bandit.beta_b == self.mocked_beta_b # operation self.price_bandit.update(True) # after & before assert self.price_bandit.beta_a == self.mocked_beta_a + 1 assert self.price_bandit.beta_b == self.mocked_beta_b # operation self.price_bandit.update(False) # after assert self.price_bandit.beta_a == self.mocked_beta_a + 1 assert self.price_bandit.beta_b == self.mocked_beta_b + 1 class TestGoodPriceModel(GymTestCase): """Test GoodPriceModel of gym.""" def test_update_i(self): """Test the update method of the GoodPriceModel class where outcome is True.""" # setup outcome = True mocked_price = 46 before_prices = copy.deepcopy(self.good_price_model.price_bandits) # operation self.good_price_model.update(outcome, mocked_price) # after assert ( self.good_price_model.price_bandits[mocked_price].beta_a != before_prices[mocked_price].beta_a and self.good_price_model.price_bandits[mocked_price].beta_b == before_prices[mocked_price].beta_b ) for price in range(self.mocked_bound + 1): if price != mocked_price: assert ( self.good_price_model.price_bandits[price].beta_a == before_prices[price].beta_a and self.good_price_model.price_bandits[price].beta_b == before_prices[price].beta_b ) def test_update_ii(self): """Test the update method of the GoodPriceModel class where outcome is False.""" # setup outcome = False mocked_price = 12 before_prices = copy.deepcopy(self.good_price_model.price_bandits) # operation self.good_price_model.update(outcome, mocked_price) # after assert ( self.good_price_model.price_bandits[mocked_price].beta_a == before_prices[mocked_price].beta_a and self.good_price_model.price_bandits[mocked_price].beta_b != before_prices[mocked_price].beta_b ) for price in range(self.mocked_bound + 1): if price != mocked_price: assert ( self.good_price_model.price_bandits[price].beta_a == before_prices[price].beta_a and self.good_price_model.price_bandits[price].beta_b == before_prices[price].beta_b ) def test_get_price_expectation(self): """Test the get_price_expectation method of GoodPriceModel class.""" expectation = self.good_price_model.get_price_expectation() assert type(expectation) == int class TestMyRLAgent(GymTestCase): """Test MyRLAgent of gym.""" def test_fit(self): """Test the fit method of the MyRLAgent class.""" # setup step_result = ("obs", "reward", "done", "info") # operation with patch.object(ProxyEnv, "reset") as mocked_reset: with patch.object( ProxyEnv, "step", return_value=step_result ) as mocked_step: with patch.object(ProxyEnv, "close") as mocked_close: with patch.object( GoodPriceModel, "get_price_expectation" ) as mocked_price_exp: with patch.object(GoodPriceModel, "update") as mocked_update: with patch.object(self.logger, "log") as mock_logger: self.my_rl_agent.fit(self.proxy_env, self.nb_steps) # after # fit mocked_reset.assert_called_once() # _pick_an_action mocked_price_exp.assert_called() assert mocked_price_exp.call_count == self.nb_steps # fit mocked_step.assert_called() assert mocked_step.call_count == self.nb_steps # _update_model mocked_update.assert_called() assert mocked_update.call_count == self.nb_steps # fit mock_logger.assert_called() mocked_close.assert_called() ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_gym/test_task.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the task class of the gym skill.""" from unittest.mock import patch from tests.test_packages_for_aea_tests.test_skills.test_gym.intermediate_class import ( GymTestCase, ) class TestTask(GymTestCase): """Test Task of gym.""" def test__init__(self): """Test the __init__ method of the GymTask class.""" assert self.task.nb_steps == self.nb_steps assert self.task.is_rl_agent_training is False def test_properties(self): """Test the properties of the GymTask class.""" assert self.task.proxy_env == self.task._proxy_env assert self.task.proxy_env_queue == self.task._proxy_env.queue def test_setup(self): """Test the setup method of the GymTask class.""" # operation with patch.object(self.logger, "info") as mock_logger: self.task.setup() # after mock_logger.assert_any_call("Gym task: setup method called.") def test_execute_i(self): """Test the execute method of the GymTask class where agent is NOT trained/training.""" # before self.task.proxy_env._is_rl_agent_trained = False self.task.is_rl_agent_training = False # operation with patch.object(self.task._rl_agent, "fit") as mock_fit: with patch.object(self.logger, "info") as mock_logger: self.task.execute() # after # _start_training mock_logger.assert_any_call("Training starting ...") assert self.task.is_rl_agent_training is True # _fit mock_fit.assert_called_with(self.task.proxy_env, self.nb_steps) mock_logger.assert_any_call("Training finished. You can exit now via CTRL+C.") def test_execute_ii(self): """Test the execute method of the GymTask class where agent IS trained/training.""" # before self.task.proxy_env._is_rl_agent_trained = True self.task.is_rl_agent_training = True # operation with patch.object(self.task.proxy_env, "close") as mock_close: self.task.execute() # after # _stop_training assert self.task.is_rl_agent_training is False mock_close.assert_called_once() def test_teardown(self): """Test the teardown method of the GymTask class.""" self.task.is_rl_agent_training = True # operation with patch.object(self.task.proxy_env, "close") as mock_close: with patch.object(self.logger, "info") as mock_logger: self.task.teardown() # after mock_logger.assert_any_call("Gym Task: teardown method called.") # _stop_training assert self.task.is_rl_agent_training is False mock_close.assert_called_once() ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_weather_station/__init__.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """The tests module contains the tests of the packages/skills/weather_station dir.""" ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_weather_station/test_dummy_weather_station_data.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the dummy_weather_station_data class of the weather station skill.""" import datetime import sqlite3 from pathlib import Path from unittest.mock import Mock, patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.weather_station.dummy_weather_station_data import Forecast from packages.fetchai.skills.weather_station.dummy_weather_station_data import ( _default_logger as logger, ) from tests.conftest import ROOT_DIR class TestDummyWeatherStationData(BaseSkillTestCase): """Test dummy_weather_station_data of weather station.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "weather_station") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.forecast = Forecast() def test_generate(self): """Test the generate method of the Forecast class.""" # operation with patch.object(self.forecast, "add_data") as mock_add: self.forecast.generate(2) # after assert mock_add.call_count == 2 def test_add_data(self): """Test the add_data method of the Forecast class.""" # setup tagged_data = { "abs_pressure": 100, "delay": 20, "hum_in": 20, "hum_out": 20, int( ( datetime.datetime.now() - datetime.datetime.fromtimestamp(0) ).total_seconds() ): 20, "rain": 20, "temp_in": 20, "temp_out": 20, "wind_ave": 20, "wind_dir": 20, "wind_gust": 20, } mocked_conn = Mock(wrap=sqlite3.Connection) mocked_cursor = Mock(wraps=sqlite3.Cursor) # operation with patch("sqlite3.connect", return_value=mocked_conn) as mock_conn: with patch.object( mocked_conn, "cursor", return_value=mocked_cursor ) as mock_curs: with patch.object(mocked_cursor, "execute") as mock_exe: with patch.object(logger, "info") as mock_logger: with patch.object(mocked_cursor, "close") as mock_cur_close: with patch.object(mocked_conn, "commit") as mock_con_commit: with patch.object( mocked_conn, "close" ) as mock_con_close: self.forecast.add_data(tagged_data) # after mock_conn.assert_called_once() mock_curs.assert_called_once() mock_exe.assert_any_call( """INSERT INTO data(abs_pressure, delay, hum_in, hum_out, idx, rain, temp_in, temp_out, wind_ave, wind_dir, wind_gust) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( tagged_data["abs_pressure"], tagged_data["delay"], tagged_data["hum_in"], tagged_data["hum_out"], int( ( datetime.datetime.now() - datetime.datetime.fromtimestamp(0) ).total_seconds() ), tagged_data["rain"], tagged_data["temp_in"], tagged_data["temp_out"], tagged_data["wind_ave"], tagged_data["wind_dir"], tagged_data["wind_gust"], ), ) mock_logger.assert_any_call("Wheather station: I added data in the db!") mock_cur_close.assert_called_once() mock_con_commit.assert_called_once() mock_con_close.assert_called_once() ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_weather_station/test_registration_db.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the RegistrationDB class of the weather station skill.""" import datetime import sqlite3 from pathlib import Path from unittest.mock import Mock, patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.weather_station.db_communication import ( DBCommunication, DB_SOURCE, ) from tests.conftest import ROOT_DIR class TestDBCommunication(BaseSkillTestCase): """Test RegistrationDB of weather station.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "weather_station") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.db = DBCommunication() def test_db_connection(self): """Test the db_connection method of the DBCommunication class.""" # setup mocked_conn = Mock(wrap=sqlite3.Connection) # operation with patch("sqlite3.connect", return_value=mocked_conn) as mock_conn: actual_con = self.db.db_connection() # after mock_conn.assert_any_call(DB_SOURCE) assert actual_con == mocked_conn def test_get_data_for_specific_dates(self): """Test the get_data_for_specific_dates method of the DBCommunication class.""" # setup start_date = "3/10/2019" end_date = "15/10/2019" result = {"abs_pressure": 100, "hum_in": 20} start_dt = datetime.datetime.strptime(start_date, "%d/%m/%Y") end_dt = datetime.datetime.strptime(end_date, "%d/%m/%Y") start = int((start_dt - datetime.datetime.fromtimestamp(0)).total_seconds()) end = int((end_dt - datetime.datetime.fromtimestamp(0)).total_seconds()) mocked_conn = Mock(wrap=sqlite3.Connection) mocked_cursor = Mock(wraps=sqlite3.Cursor) # operation with patch.object( self.db, "db_connection", return_value=mocked_conn ) as mock_conn: with patch.object( mocked_conn, "cursor", return_value=mocked_cursor ) as mock_curs: with patch.object(mocked_cursor, "execute") as mock_exe: with patch.object( mocked_cursor, "fetchall", return_value=result ) as mock_fetchall: with patch.object(mocked_cursor, "close") as mock_cur_close: with patch.object(mocked_conn, "close") as mock_con_close: actual_result = self.db.get_data_for_specific_dates( start_date, end_date ) # after mock_conn.assert_called_once() mock_curs.assert_called_once() mock_fetchall.assert_called_once() mock_exe.assert_any_call( "SELECT * FROM data WHERE idx BETWEEN ? AND ?", (str(start), str(end)), ) mock_cur_close.assert_called_once() mock_con_close.assert_called_once() assert actual_result == result ================================================ FILE: tests/test_packages_for_aea_tests/test_skills/test_weather_station/test_strategy.py ================================================ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018-2023 Fetch.AI Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ------------------------------------------------------------------------------ """This module contains the tests of the strategy class of the weather station skill.""" import json import time from pathlib import Path from unittest.mock import patch from aea.test_tools.test_skill import BaseSkillTestCase from packages.fetchai.skills.weather_station.strategy import ( DEFAULT_DATE_ONE, DEFAULT_DATE_TWO, Strategy, ) from tests.conftest import ROOT_DIR class TestStrategy(BaseSkillTestCase): """Test Strategy of weather station.""" path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "weather_station") @classmethod def setup(cls): """Setup the test class.""" super().setup() cls.strategy = Strategy( date_one=DEFAULT_DATE_ONE, date_two=DEFAULT_DATE_TWO, name="strategy", skill_context=cls._skill.skill_context, ) def test_collect_from_data_source(self): """Test the collect_from_data_source method of the Strategy class.""" # setup fetched_data = [ (1023, 4, 35, 70, 1615894962, 72, 23, 22, 5, 3, 4), (1024, 3, 34, 50, 1615894961, 71, 19, 5, 2, 12, 2), (1025, 5, 36, 40, 1615894963, 73, 28, 20, 6, 4, 7), (1023, 7, 38, 75, 1615894964, 74, 25, 16, 9, 2, 5), (1025, 6, 37, 80, 1615894965, 72, 22, 12, 3, 8, 3), ] counter = 0 row_data = {} for items in fetched_data: counter += 1 dict_of_data = { "abs_pressure": items[0], "delay": items[1], "hum_in": items[2], "hum_out": items[3], "idx": time.ctime(int(items[4])), "rain": items[5], "temp_in": items[6], "temp_out": items[7], "wind_ave": items[8], "wind_dir": items[9], "wind_gust": items[10], } row_data[counter] = dict_of_data expected_weather_data = {"weather_data": json.dumps(row_data)} # operation with patch.object( self.strategy.db, "get_data_for_specific_dates", return_value=fetched_data ) as mock_get_data: weather_data = self.strategy.collect_from_data_source() # after mock_get_data.assert_any_call(DEFAULT_DATE_ONE, DEFAULT_DATE_TWO) assert weather_data == expected_weather_data ================================================ FILE: tox.ini ================================================ ; By default, testenvs are configured to: ; - don't skip dist (skipsdist = False) ; - don't skip the package installation (skip_install = False) ; - don't use source installation (usedevelop = False) ; where one of those steps is not necessary for the test, ; we set the associated flag (e.g. for linting we don't need ; the package installation). [tox] envlist = bandit, black, black-check, isort, isort-check, copyright_check, docs, flake8, liccheck, mypy, py{3.8,3.9,3.10}, dependencies_check, plugins_deps isolated_build = True [testenv] basepython = python3 allowlist_externals = /bin/sh make passenv = * extras = all deps = poetry commands = poetry install --only testing,main,packages -E all poetry run pip install openapi-core==0.13.2 poetry run python ./install_packages.py jsonschema poetry run python -m pip install file://{toxinidir}/plugins/aea-ledger-ethereum poetry run python -m pip install file://{toxinidir}/plugins/aea-ledger-cosmos poetry run python -m pip install file://{toxinidir}/plugins/aea-ledger-fetchai poetry run python -m pip install file://{toxinidir}/plugins/aea-cli-ipfs poetry run python ./install_packages.py black isort poetry run pytest -rfE --cov=aea --cov-report=html --cov-report=term-missing --cov-config=pyproject.toml {posargs} [testenv:py3.8] basepython = python3.8 [testenv:py3.8-cov] basepython = python3.8 usedevelop = True [testenv:py3.9] basepython = python3.9 [testenv:py3.10] basepython = python3.10 [plugins] commands = poetry install --only main,testing -E all poetry run python -m pip install file://{toxinidir}/plugins/aea-ledger-ethereum poetry run python -m pip install file://{toxinidir}/plugins/aea-ledger-cosmos poetry run python -m pip install file://{toxinidir}/plugins/aea-ledger-fetchai poetry run python -m pip install file://{toxinidir}/plugins/aea-cli-ipfs poetry run pytest -rfE plugins/aea-ledger-fetchai/tests --cov-report=html --cov-report=term-missing --cov=aea_ledger_fetchai --cov-config=pyproject.toml --suppress-no-test-exit-code {posargs} poetry run pytest -rfE plugins/aea-ledger-ethereum/tests --cov-report=html --cov-report=term-missing --cov=aea_ledger_ethereum --cov-config=pyproject.toml --suppress-no-test-exit-code {posargs} poetry run pytest -rfE plugins/aea-ledger-cosmos/tests --cov-report=html --cov-report=term-missing --cov=aea_ledger_cosmos --cov-config=pyproject.toml --suppress-no-test-exit-code {posargs} poetry run pytest -rfE plugins/aea-cli-ipfs/tests --cov-report=html --cov-report=term-missing --cov=aea_cli_ipfs --cov-config=pyproject.toml --suppress-no-test-exit-code {posargs} [testenv:plugins-py3.8] basepython = python3.8 commands = {[plugins]commands} [testenv:plugins-py3.9] basepython = python3.9 commands = {[plugins]commands} [testenv:plugins-py3.10] basepython = python3.10 commands = {[plugins]commands} [testenv:plugins-py3.8-cov] basepython = python3.8 usedevelop = True commands = {[plugins]commands} [testenv:bandit] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py bandit make bandit [testenv:check_plugins_code_consistency] skipsdist = True skip_install = True allowlist_externals = diff commands = make plugin-diffs [testenv:check_go_code_consistency] skipsdist = True skip_install = True allowlist_externals = diff commands = make libp2p-diffs [testenv:black] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py black make black [testenv:black-check] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py black make black-check [testenv:isort] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py isort make isort [testenv:isort-check] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py isort make isort-check [testenv:copyright_check] skipsdist = True skip_install = True commands = python {toxinidir}/scripts/check_copyright_notice.py --directory {toxinidir} [testenv:hash_check] skipsdist = True usedevelop = True commands = poetry run python ./install_packages.py ipfshttpclient poetry run python {toxinidir}/scripts/generate_ipfs_hashes.py --check {posargs} [testenv:package_version_checks] skipsdist = True usedevelop = True deps = commands = make check-package-versions-in-docs [testenv:package_dependencies_checks] skipsdist = True usedevelop = True deps = commands = make check-packages [testenv:docs] skipsdist = True skip_install = True commands = poetry install --only docs make docs [testenv:docs-serve] skipsdist = True skip_install = True commands = poetry install --only docs poetry run mkdocs serve -a localhost:8080 [testenv:flake8] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py "flake8.*" pydocstyle darglint make flake8 [testenv:liccheck] skipsdist = True commands = poetry install --only packages,main poetry run python ./install_packages.py liccheck make liccheck [testenv:mypy] skipsdist = True skip_install = True commands = poetry install --only packages,main,testing,types poetry run python ./install_packages.py mypy make mypy [testenv:pylint] skipsdist = True commands = poetry run python ./install_packages.py pylint pytest gitpython # pytest gitpython installed for scripts and aea testing tools source code checks poetry run python -m pip install --no-deps file://{toxinidir}/plugins/aea-ledger-ethereum poetry run python -m pip install --no-deps file://{toxinidir}/plugins/aea-ledger-cosmos poetry run python -m pip install --no-deps file://{toxinidir}/plugins/aea-ledger-fetchai poetry run python -m pip install --no-deps file://{toxinidir}/plugins/aea-cli-ipfs make pylint [testenv:safety] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py safety make safety [testenv:vulture] skipsdist = True skip_install = True commands = poetry run python ./install_packages.py vulture make vulture [testenv:check-doc-links] skipsdist = True usedevelop = True deps = commands = make check-doc-links [testenv:check_api_docs] skipsdist = True usedevelop = True commands = poetry run python ./install_packages.py pydoc-markdown poetry run {toxinidir}/scripts/generate_api_docs.py --check-clean [testenv:check_generate_all_protocols] skipsdist = True usedevelop = True setenv = PYTHONPATH = {toxinidir} commands = poetry run pip install .[all] poetry run python ./install_packages.py black isort ipfshttpclient poetry run ./scripts/generate_all_protocols.py --no-bump --check-clean [testenv:spell_check] skipsdist = True usedevelop = True allowlist_externals = **/spell-check.sh deps = commands = {toxinidir}/scripts/spell-check.sh [testenv:dependencies_check] skipsdist = True skip_install = True commands = pip install {toxinidir}[all] pip uninstall aea -y python {toxinidir}/scripts/check_imports_and_dependencies.py [testenv:plugins_env] skipsdist = True skip_install = True passenv = * deps = .[all] allowlist_externals = **/sh commands = - /bin/sh -c "rm -fr ./*private_key.txt" {posargs} ================================================ FILE: user-image/Dockerfile ================================================ FROM ubuntu:18.04 RUN apt-get update && apt-get upgrade -y RUN apt-get install sudo -y RUN adduser --disabled-password --gecos '' ubuntu RUN adduser ubuntu sudo RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers RUN apt install -y python3 python3-pip RUN pip3 install --upgrade pip # utils RUN apt install -y wget # golang RUN wget https://dl.google.com/go/go1.13.8.linux-amd64.tar.gz && \ tar -xzvf go1.13.8.linux-amd64.tar.gz -C /usr/local && \ export PATH=$PATH:/usr/local/go/bin && echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc && \ mkdir $HOME/go USER ubuntu ENV PATH="${PATH}:/usr/local/go/bin" ENV DEBIAN_FRONTEND noninteractive ENV LC_ALL C.UTF-8 ENV LANG C.UTF-8 RUN echo 'PATH="$(python3.6 -m site --user-base)/bin:${PATH}"' >> ~/.bashrc RUN echo "alias pip=pip3" >> ~/.bashrc ENTRYPOINT [ "/bin/bash"] ================================================ FILE: user-image/README.md ================================================ # Docker User image All the commands must be executed from the parent directory, if not stated otherwise. ## Build We recommend using the following command for building: ./user-image/scripts/docker-build-img.sh -t fetchai/aea-user:latest -- ## Publish First, ./user-image/scripts/docker-publish-img.sh And then, in `docker-env.sh`, uncomment `DOCKER_IMAGE_TAG=fetchai/aea-user:latest` and comment the alternative line, then run the publish command again: ./user-image/scripts/docker-publish-img.sh ================================================ FILE: user-image/docker-env.sh ================================================ #!/bin/bash # Swap the following lines if you want to work with 'latest' DOCKER_IMAGE_TAG=fetchai/aea-user:1.1.1 # DOCKER_IMAGE_TAG=fetchai/aea-user:latest DOCKER_BUILD_CONTEXT_DIR=.. DOCKERFILE=./Dockerfile ================================================ FILE: user-image/scripts/docker-build-img.sh ================================================ #!/bin/bash -e # Usage: # ./docker-build-img.sh -- # Where: # * resulting docker build commandline will be: # docker build $IMMEDIATE_PARAMS -t $DOCKER_IMAGE_TAG $TAIL_PARAMS $DOCKER_BUILD_CONTEXT_DIR # * DOCKER_IMAGE_TAG and DOCKER_BUILD_CONTEXT_DIR variables are defined in the `docker-env.sh` and/or `docker-env-common.sh` # # Examples: # * the following example provides the `--cpus 4 --compress` parameters to `docker build` command as IMMEDIATE_PARAMS, **ommiting** the TAIL_PARAMS: # # ./docker-build-img.sh --cpus 4 ---compress -- # # the the resulting docker process commandline will be: # # docker build --cpus 4 --compress -t $DOCKER_IMAGE_TAG $TAIL_PARAMS $DOCKER_BUILD_CONTEXT_DIR # # * the following example provides the `--squash` parameters to `docker build` command as IMMEDIATE_PARAMS, and `../../` parameter as TAIL_PARAMS (what corresponds to the context directory, what also means that DOCKER_BUILD_CONTEXT_DIR variable needs to be unset or set to empty string in the `docker-env.sh`): # # ./docker-build-img.sh --squash -- ../../ # # the the resulting docker process commandline will be: # # docker build --squash -t $DOCKER_IMAGE_TAG ../../ $DOCKER_BUILD_CONTEXT_DIR # # the `DOCKER_BUILD_CONTEXT_DIR` shall be set to empty string in `docker-env.sh` file. # NOTE: For more details, please see description for the `split_params()` shell function in the `docker-common.sh` script. SCRIPTS_DIR=${0%/*} . "$SCRIPTS_DIR"/docker-env-common.sh docker_build_callback() { local IMMEDIATE_PARAMS="$1" local TAIL_PARAMS="$2" if [ -n "${DOCKERFILE}" ]; then TAIL_PARAMS="-f $DOCKERFILE $TAIL_PARAMS" fi local COMMAND="docker build $IMMEDIATE_PARAMS -t $DOCKER_IMAGE_TAG $TAIL_PARAMS $DOCKER_BUILD_CONTEXT_DIR" echo $COMMAND $COMMAND } split_params docker_build_callback "$@" ================================================ FILE: user-image/scripts/docker-publish-img.sh ================================================ #!/bin/bash -e # NOTE: First docker needs to be authorized to push image to container registry. # Normally this is done using 'docker login ', where the # 'registry_url' value is set in the $DOCKER_CONTAINER_REGISTRY environment # variable which is defined in the 'docker-env-common.sh' script file. # If you are using the Google cloud docker registry, please run the # 'gcloud auth configure-docker' instead. SCRIPTS_DIR=${0%/*} . "$SCRIPTS_DIR"/docker-env-common.sh docker tag "$DOCKER_IMAGE_TAG" "$REGISTRY_DOCKER_IMAGE_TAG" docker push "$REGISTRY_DOCKER_IMAGE_TAG"